package oci8

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"os"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"
)

// testGetDB connects to the test database and returns the database connection
func testGetDB(params string) *sql.DB {
	Driver.Logger = log.New(os.Stderr, "oci8 ", log.Ldate|log.Ltime|log.LUTC|log.Lshortfile)

	var openString string
	// [username/[password]@]host[:port][/service_name][?param1=value1&...&paramN=valueN]
	if len(TestUsername) > 0 {
		if len(TestPassword) > 0 {
			openString = TestUsername + "/" + TestPassword + "@"
		} else {
			openString = TestUsername + "@"
		}
	}
	openString += TestHostValid + params

	db, err := sql.Open("oci8", openString)
	if err != nil {
		fmt.Println("Open error:", err)
		return nil
	}
	if db == nil {
		fmt.Println("db is nil")
		return nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	err = db.PingContext(ctx)
	cancel()
	if err != nil {
		fmt.Println("PingContext error:", err)
		db.Close()
		return nil
	}

	return db
}

func testDropTable(t *testing.T, tableName string) {
	err := testExec(t, "drop table "+tableName, nil)
	if err != nil {
		t.Errorf("drop table %v error: %v", tableName, err)
	}
}

func testExecQuery(t *testing.T, query string, args []interface{}) {
	err := testExec(t, query, args)
	if err != nil {
		t.Errorf("query %v error: %v", query, err)
	}
}

// testGetRows runs a statement and returns the rows as [][]interface{}
func testGetRows(t testing.TB, stmt *sql.Stmt, args []interface{}) ([][]interface{}, error) {
	// get rows
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	defer cancel()
	var rows *sql.Rows
	rows, err := stmt.QueryContext(ctx, args...)
	if err != nil {
		return nil, fmt.Errorf("query error: %v", err)
	}

	// get column infomration
	var columns []string
	columns, err = rows.Columns()
	if err != nil {
		rows.Close()
		return nil, fmt.Errorf("columns error: %v", err)
	}

	// create values
	values := make([][]interface{}, 0, 1)

	// get values
	pRowInterface := make([]interface{}, len(columns))

	for rows.Next() {
		rowInterface := make([]interface{}, len(columns))
		for i := 0; i < len(rowInterface); i++ {
			pRowInterface[i] = &rowInterface[i]
		}

		err = rows.Err()
		if err != nil {
			rows.Close()
			return nil, fmt.Errorf("rows error: %v", err)
		}

		err = rows.Scan(pRowInterface...)
		if err != nil {
			rows.Close()
			return nil, fmt.Errorf("scan error: %v", err)
		}

		values = append(values, rowInterface)
	}

	err = rows.Err()
	if err != nil {
		rows.Close()
		return nil, fmt.Errorf("rows error: %v", err)
	}

	err = rows.Close()
	if err != nil {
		return nil, fmt.Errorf("close error: %v", err)
	}

	// return values
	return values, nil
}

// testExec runs an exec query and returns error
func testExec(t *testing.T, query string, args []interface{}) error {
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		return fmt.Errorf("prepare error: %v", err)
	}

	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	_, err = stmt.ExecContext(ctx, args...)
	cancel()
	if err != nil {
		stmt.Close()
		return fmt.Errorf("exec error: %v", err)
	}

	err = stmt.Close()
	if err != nil {
		return fmt.Errorf("stmt close error: %v", err)
	}

	return nil
}

// testExecRows runs exec query for each arg row and returns error
func testExecRows(t *testing.T, query string, args [][]interface{}) error {
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		return fmt.Errorf("prepare error: %v", err)
	}

	for i := 0; i < len(args); i++ {
		ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
		_, err = stmt.ExecContext(ctx, args[i]...)
		cancel()
		if err != nil {
			stmt.Close()
			return fmt.Errorf("exec - row %v - error: %v", i, err)
		}
	}

	err = stmt.Close()
	if err != nil {
		return fmt.Errorf("stmt close error: %v", err)
	}

	return nil
}

// testRunExecResults runs testRunExecResult for each execResults
func testRunExecResults(t *testing.T, execResults testExecResults) {
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, execResults.query)
	cancel()
	if err != nil {
		t.Errorf("prepare error: %v - query: %v", err, execResults.query)
		return
	}

	for _, execResult := range execResults.execResults {
		testRunExecResult(t, execResult, execResults.query, stmt)
	}

	err = stmt.Close()
	if err != nil {
		t.Errorf("close error: %v - query: %v", err, execResults.query)
	}
}

// testRunExecResult runs exec query for execResult and tests result
func testRunExecResult(t *testing.T, execResult testExecResult, query string, stmt *sql.Stmt) {
	var rv reflect.Value
	results := make(map[string]interface{}, len(execResult.args))

	// change args to namedArgs
	namedArgs := make([]interface{}, 0, len(execResult.args))
	for key, value := range execResult.args {
		// make pointer
		rv = reflect.ValueOf(value.Dest)
		out := reflect.New(rv.Type())
		if !out.Elem().CanSet() {
			t.Fatalf("unable to set pointer: %v - query: %v", key, query)
			return
		}
		out.Elem().Set(rv)
		results[key] = out.Interface()

		namedArgs = append(namedArgs, sql.Named(key, sql.Out{Dest: out.Interface(), In: value.In}))
	}

	// exec query with namedArgs
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	_, err := stmt.ExecContext(ctx, namedArgs...)
	cancel()
	if err != nil {
		t.Errorf("exec error: %v - query: %v - args: %v", err, query, execResult.args)
		return
	}

	// check results
	for key, value := range execResult.results {
		// check if have result
		result, ok := results[key]
		if !ok {
			t.Errorf("result not found: %v - query: %v", key, query)
			continue
		}

		// get result from result pointer
		rv = reflect.ValueOf(result)
		rv = reflect.Indirect(rv)
		result = rv.Interface()

		// check if value matches result
		if !reflect.DeepEqual(result, value) {
			t.Errorf("arg: %v - received: %T, %v - expected: %T, %v - query: %v",
				key, result, result, value, value, query)
		}
	}
}

// testRunQueryResults runs testRunQueryResult for each queryResults
func testRunQueryResults(t *testing.T, queryResults testQueryResults) {
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, queryResults.query)
	cancel()
	if err != nil {
		t.Errorf("prepare error: %v - query: %v", err, queryResults.query)
		return
	}

	for _, queryResult := range queryResults.queryResults {
		testRunQueryResult(t, queryResult, queryResults.query, stmt)
	}

	err = stmt.Close()
	if err != nil {
		t.Errorf("close error: %v - query: %v", err, queryResults.query)
	}
}

// testRunQueryResult runs a single testQueryResults test
func testRunQueryResult(t *testing.T, queryResult testQueryResult, query string, stmt *sql.Stmt) {
	result, err := testGetRows(t, stmt, queryResult.args)
	if err != nil {
		t.Errorf("get rows error: %v - query: %v", err, query)
		return
	}
	if result == nil && queryResult.results != nil {
		t.Errorf("result is nil - query: %v", query)
		return
	}
	if len(result) != len(queryResult.results) {
		t.Errorf("result rows len %v not equal to results len %v - query: %v",
			len(result), len(queryResult.results), query)
		return
	}

	for i := 0; i < len(result); i++ {
		if len(result[i]) != len(queryResult.results[i]) {
			t.Errorf("result columns len %v not equal to results len %v - query: %v",
				len(result[i]), len(queryResult.results[i]), query)
			continue
		}

		for j := 0; j < len(result[i]); j++ {
			bad := false
			type1 := reflect.TypeOf(result[i][j])
			type2 := reflect.TypeOf(queryResult.results[i][j])
			switch {
			case type1 == nil || type2 == nil:
				if type1 != type2 {
					bad = true
				}
			case type1 == TestTypeTime || type2 == TestTypeTime:
				if type1 != type2 {
					bad = true
					break
				}
				time1 := result[i][j].(time.Time)
				time2 := queryResult.results[i][j].(time.Time)
				if !time1.Equal(time2) {
					bad = true
				}
			case type1.Kind() == reflect.Slice || type2.Kind() == reflect.Slice:
				if !reflect.DeepEqual(result[i][j], queryResult.results[i][j]) {
					bad = true
				}
			default:
				if result[i][j] != queryResult.results[i][j] {
					bad = true
				}
			}
			if bad {
				t.Errorf("result - row %v, %v - received: %T, %v - expected: %T, %v - query: %v",
					i, j, result[i][j], result[i][j], queryResult.results[i][j], queryResult.results[i][j], query)
			}
		}

	}

}

// TestConnect checks basic invalid connection
func TestConnect(t *testing.T) {
	if TestDisableDatabase {
		t.SkipNow()
	}

	t.Parallel()

	// invalid
	db, err := sql.Open("oci8", TestHostInvalid)
	if err != nil {
		t.Fatal("open error:", err)
	}
	if db == nil {
		t.Fatal("db is nil")
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	err = db.PingContext(ctx)
	cancel()
	if err == nil || len(err.Error()) < 4 || err.Error()[0:4] != "ORA-" {
		t.Fatalf("ping error - received: %v - expected ORA- error", err)
	}

	err = db.Close()
	if err != nil {
		t.Fatal("close error:", err)
	}

	// wrong username
	db, err = sql.Open("oci8", "dFQXYoApiU2YbquMQnfPyqxR2kAoeuWngDvtTpl3@"+TestHostValid)
	if err != nil {
		t.Fatal("open error:", err)
	}
	if db == nil {
		t.Fatal("db is nil")
	}

	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	err = db.PingContext(ctx)
	cancel()
	if err == nil || len(err.Error()) < 4 || err.Error()[0:4] != "ORA-" {
		t.Fatalf("ping error - received: %v - expected ORA- error", err)
	}

	err = db.Close()
	if err != nil {
		t.Fatal("close error:", err)
	}
}

// TestSelectParallel checks parallel select from dual
func TestSelectParallel(t *testing.T) {
	if TestDisableDatabase {
		t.SkipNow()
	}

	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, "select :1 from dual")
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	var waitGroup sync.WaitGroup
	waitGroup.Add(50)

	for i := 0; i < 50; i++ {
		go func(num int) {
			defer waitGroup.Done()
			var result [][]interface{}
			result, err = testGetRows(t, stmt, []interface{}{num})
			if err != nil {
				t.Fatal("get rows error:", err)
			}
			if result == nil {
				t.Fatal("result is nil")
			}
			if len(result) != 1 {
				t.Fatal("len result not equal to 1")
			}
			if len(result[0]) != 1 {
				t.Fatal("len result[0] not equal to 1")
			}
			data, ok := result[0][0].(float64)
			if !ok {
				t.Fatal("result not float64")
			}
			if data != float64(num) {
				t.Fatal("result not equal to:", num)
			}
		}(i)
	}

	waitGroup.Wait()

	err = stmt.Close()
	if err != nil {
		t.Fatal("stmt close error:", err)
	}
}

// TestContextTimeoutBreak checks that ExecContext timeout works
func TestContextTimeoutBreak(t *testing.T) {
	if TestDisableDatabase {
		t.SkipNow()
	}

	t.Parallel()

	// define
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	_, err := TestDB.ExecContext(ctx, `
create or replace function SLEEP_SECONDS (p_seconds number) return integer is
begin
  dbms_lock.sleep(p_seconds);
  return 1;
end;
`)
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	// exec
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, "begin SYS.DBMS_LOCK.SLEEP(1); end;")
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	ctx, cancel = context.WithTimeout(context.Background(), 200*time.Millisecond)
	_, err = stmt.ExecContext(ctx)
	cancel()
	expected := "ORA-01013"
	if err == nil || len(err.Error()) < len(expected) || !strings.Contains(err.Error(), expected) {
		t.Fatalf("stmt exec - expected: %v - received: %v", expected, err)
	}

	err = stmt.Close()
	if err != nil {
		t.Fatal("stmt close error:", err)
	}

	// query
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = TestDB.PrepareContext(ctx, "select SLEEP_SECONDS(1) from dual")
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	ctx, cancel = context.WithTimeout(context.Background(), 200*time.Millisecond)
	_, err = stmt.QueryContext(ctx)
	cancel()
	if err == nil || len(err.Error()) < len(expected) || !strings.Contains(err.Error(), expected) {
		t.Fatalf("stmt query - expected: %v - received: %v", expected, err)
	}

	err = stmt.Close()
	if err != nil {
		t.Fatal("stmt close error:", err)
	}
}

// TestDestructiveTransaction tests a transaction
func TestDestructiveTransaction(t *testing.T) {
	if TestDisableDatabase || TestDisableDestructive {
		t.SkipNow()
	}

	err := testExec(t, "create table TRANSACTION_"+TestTimeString+
		" ( A INT, B INT, C INT )", nil)
	if err != nil {
		t.Fatal("create table error:", err)
	}

	defer testExecQuery(t, "drop table TRANSACTION_"+TestTimeString, nil)

	err = testExecRows(t, "insert into TRANSACTION_"+TestTimeString+" ( A, B, C ) values (:1, :2, :3)",
		[][]interface{}{
			{1, 2, 3},
			{4, 5, 6},
			{6, 7, 8},
		})
	if err != nil {
		t.Fatal("insert error:", err)
	}

	// TODO: How should context work? Probably should have more context create and cancel.

	var tx1 *sql.Tx
	var tx2 *sql.Tx
	ctx, cancel := context.WithTimeout(context.Background(), 2*TestContextTimeout)
	defer cancel()
	tx1, err = TestDB.BeginTx(ctx, nil)
	if err != nil {
		t.Fatal("begin tx error:", err)
	}
	tx2, err = TestDB.BeginTx(ctx, nil)
	if err != nil {
		t.Fatal("begin tx error:", err)
	}

	queryResults := testQueryResults{
		query: "select A, B, C from TRANSACTION_" + TestTimeString + " order by A",
		queryResults: []testQueryResult{
			{
				results: [][]interface{}{
					{int64(1), int64(2), int64(3)},
					{int64(4), int64(5), int64(6)},
					{int64(6), int64(7), int64(8)},
				},
			},
		},
	}
	testRunQueryResults(t, queryResults)

	var result sql.Result
	result, err = tx1.ExecContext(ctx, "update TRANSACTION_"+TestTimeString+" set B = :1 where A = :2", []interface{}{22, 1}...)
	if err != nil {
		t.Fatal("exec error:", err)
	}

	var count int64
	count, err = result.RowsAffected()
	if err != nil {
		t.Fatal("rows affected error:", err)
	}
	if count != 1 {
		t.Fatalf("rows affected %v not equal to 1", count)
	}

	result, err = tx2.ExecContext(ctx, "update TRANSACTION_"+TestTimeString+" set B = :1 where A = :2", []interface{}{55, 4}...)
	if err != nil {
		t.Fatal("exec error:", err)
	}

	count, err = result.RowsAffected()
	if err != nil {
		t.Fatal("rows affected error:", err)
	}
	if count != 1 {
		t.Fatalf("rows affected %v not equal to 1", count)
	}

	queryResults = testQueryResults{
		query: "select A, B, C from TRANSACTION_" + TestTimeString + " order by A",
		queryResults: []testQueryResult{
			{
				results: [][]interface{}{
					{int64(1), int64(2), int64(3)},
					{int64(4), int64(5), int64(6)},
					{int64(6), int64(7), int64(8)},
				},
			},
		},
	}
	testRunQueryResults(t, queryResults)

	// tx1 with rows A = 1
	var stmt *sql.Stmt
	stmt, err = tx1.PrepareContext(ctx, "select A, B, C from TRANSACTION_"+TestTimeString+" where A = :1")
	if err != nil {
		t.Fatal("prepare error:", err)
	}
	var rows [][]interface{}
	rows, err = testGetRows(t, stmt, []interface{}{1})
	if err != nil {
		t.Fatal("get rows error:", err)
	}
	if result == nil {
		t.Fatal("rows is nil")
	}
	if len(rows) != 1 {
		t.Fatal("len rows not equal to 1")
	}
	if len(rows[0]) != 3 {
		t.Fatal("len rows[0] not equal to 3")
	}
	data, ok := rows[0][0].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected := int64(1)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}
	data, ok = rows[0][1].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(22)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}
	data, ok = rows[0][2].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(3)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}

	// tx1 with rows A = 4
	rows, err = testGetRows(t, stmt, []interface{}{4})
	if err != nil {
		t.Fatal("get rows error:", err)
	}
	if rows == nil {
		t.Fatal("rows is nil")
	}
	if len(rows) != 1 {
		t.Fatal("len rows not equal to 1")
	}
	if len(rows[0]) != 3 {
		t.Fatal("len rows[0] not equal to 3")
	}
	data, ok = rows[0][0].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(4)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}
	data, ok = rows[0][1].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(5)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}
	data, ok = rows[0][2].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(6)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}

	// tx2 with rows A = 1
	stmt, err = tx2.PrepareContext(ctx, "select A, B, C from TRANSACTION_"+TestTimeString+" where A = :1")
	if err != nil {
		t.Fatal("prepare error:", err)
	}
	rows, err = testGetRows(t, stmt, []interface{}{1})
	if err != nil {
		t.Fatal("get rows error:", err)
	}
	if rows == nil {
		t.Fatal("rows is nil")
	}
	if len(rows) != 1 {
		t.Fatal("len rows not equal to 1")
	}
	if len(rows[0]) != 3 {
		t.Fatal("len rows[0] not equal to 3")
	}
	data, ok = rows[0][0].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(1)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}
	data, ok = rows[0][1].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(2)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}
	data, ok = rows[0][2].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(3)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}

	// tx2 with rows A = 4
	rows, err = testGetRows(t, stmt, []interface{}{4})
	if err != nil {
		t.Fatal("get rows error:", err)
	}
	if result == nil {
		t.Fatal("rows is nil")
	}
	if len(rows) != 1 {
		t.Fatal("len rows not equal to 1")
	}
	if len(rows[0]) != 3 {
		t.Fatal("len rows[0] not equal to 3")
	}
	data, ok = rows[0][0].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(4)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}
	data, ok = rows[0][1].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(55)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}
	data, ok = rows[0][2].(int64)
	if !ok {
		t.Fatal("rows not int64")
	}
	expected = int64(6)
	if data != expected {
		t.Fatal("rows not equal to:", expected)
	}

	err = tx1.Commit()
	if err != nil {
		t.Fatal("commit err:", err)
	}
	err = tx2.Commit()
	if err != nil {
		t.Fatal("commit err:", err)
	}

	queryResults = testQueryResults{
		query: "select A, B, C from TRANSACTION_" + TestTimeString + " order by A",
		queryResults: []testQueryResult{
			{
				results: [][]interface{}{
					{int64(1), int64(22), int64(3)},
					{int64(4), int64(55), int64(6)},
					{int64(6), int64(7), int64(8)},
				},
			},
		},
	}
	testRunQueryResults(t, queryResults)

	// Run new transactions after the others finished successfully.
	var tx3 *sql.Tx
	var tx4 *sql.Tx

	tx3, err = TestDB.BeginTx(ctx, nil)
	if err != nil {
		t.Fatal("begin tx error:", err)
	}

	tx4, err = TestDB.BeginTx(ctx, nil)
	if err != nil {
		t.Fatal("begin tx error:", err)
	}

	result, err = tx4.ExecContext(ctx, "update TRANSACTION_"+TestTimeString+" set B = :1 where A = :2", []interface{}{99, 1}...)
	if err != nil {
		t.Fatal("exec error:", err)
	}

	count, err = result.RowsAffected()
	if err != nil {
		t.Fatal("rows affected error:", err)
	}
	if count != 1 {
		t.Fatalf("rows affected %v not equal to 1", count)
	}

	result, err = tx3.ExecContext(ctx, "update TRANSACTION_"+TestTimeString+" set B = :1 where A = :2", []interface{}{88, 6}...)
	if err != nil {
		t.Fatal("exec error:", err)
	}

	count, err = result.RowsAffected()
	if err != nil {
		t.Fatal("rows affected error:", err)
	}
	if count != 1 {
		t.Fatalf("rows affected %v not equal to 1", count)
	}

	queryResults = testQueryResults{
		query: "select A, B, C from TRANSACTION_" + TestTimeString + " order by A",
		queryResults: []testQueryResult{
			{
				results: [][]interface{}{
					{int64(1), int64(22), int64(3)},
					{int64(4), int64(55), int64(6)},
					{int64(6), int64(7), int64(8)},
				},
			},
		},
	}
	testRunQueryResults(t, queryResults)

	err = tx3.Commit()
	if err != nil {
		t.Fatal("commit err:", err)
	}

	err = tx4.Rollback()
	if err != nil {
		t.Fatal("commit err:", err)
	}

	queryResults = testQueryResults{
		query: "select A, B, C from TRANSACTION_" + TestTimeString + " order by A",
		queryResults: []testQueryResult{
			{
				results: [][]interface{}{
					{int64(1), int64(22), int64(3)},
					{int64(4), int64(55), int64(6)},
					{int64(6), int64(88), int64(8)},
				},
			},
		},
	}
	testRunQueryResults(t, queryResults)
}

// TestSelectDualNull checks null from dual
func TestSelectDualNull(t *testing.T) {
	if TestDisableDatabase {
		t.SkipNow()
	}

	t.Parallel()

	queryResults := testQueryResults{
		query: "select null from dual",
		queryResults: []testQueryResult{{
			results: [][]interface{}{{nil}}}}}
	testRunQueryResults(t, queryResults)
}

func TestInsertRowid(t *testing.T) {
	if TestDisableDatabase || TestDisableDestructive {
		t.SkipNow()
	}

	// INSERT_ROWID
	tableName := "INSERT_ROWID_" + TestTimeString
	query := "create table " + tableName + " ( A INTEGER )"

	// create table
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	_, err = stmt.ExecContext(ctx)
	cancel()
	if err != nil {
		stmt.Close()
		t.Fatal("exec error:", err)
	}

	err = stmt.Close()
	if err != nil {
		t.Fatal("stmt close error:", err)
	}

	// drop table
	defer func() {
		query = "drop table " + tableName
		ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
		stmt, err = TestDB.PrepareContext(ctx, query)
		cancel()
		if err != nil {
			t.Fatal("prepare error:", err)
		}

		ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
		_, err = stmt.ExecContext(ctx)
		cancel()
		if err != nil {
			stmt.Close()
			t.Fatal("exec error:", err)
		}

		err = stmt.Close()
		if err != nil {
			t.Fatal("stmt close error:", err)
		}
	}()

	// insert into table
	query = "insert into " + tableName + " ( A ) values (:1) returning rowid into :rowid2"
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	rowids := make([]string, 3)
	var result sql.Result
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	result, err = stmt.ExecContext(ctx, 1, sql.Named("rowid2", sql.Out{Dest: &rowids[0]}))
	cancel()
	if err != nil {
		stmt.Close()
		t.Fatal("exec error:", err)
	}

	var id int64
	id, err = result.LastInsertId()
	if err != nil {
		stmt.Close()
		t.Fatal("exec error:", err)
	}

	rowids[1] = GetLastInsertId(id)

	err = stmt.Close()
	if err != nil {
		t.Fatal("stmt close error", err)
	}

	// get select rowid
	query = "select rowid from " + tableName
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	var rows *sql.Rows
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	rows, err = stmt.QueryContext(ctx)
	if err != nil {
		cancel()
		stmt.Close()
		t.Fatal("query error:", err)
	}

	if !rows.Next() {
		cancel()
		rows.Close()
		stmt.Close()
		t.Fatal("expected row")
	}
	err = rows.Scan(&rowids[2])
	if err != nil {
		cancel()
		rows.Close()
		stmt.Close()
		t.Fatal("scan error:", err)
	}

	if rows.Next() {
		cancel()
		rows.Close()
		stmt.Close()
		t.Fatal("more than one row")
	}

	err = rows.Err()
	if err != nil {
		cancel()
		rows.Close()
		stmt.Close()
		t.Fatal("rows error:", err)
	}

	cancel()

	err = rows.Close()
	if err != nil {
		stmt.Close()
		t.Fatal("rows close error", err)
	}

	err = stmt.Close()
	if err != nil {
		t.Fatal("stmt close error", err)
	}

	// select rowids
	query = "select A from " + tableName + " where rowid = :1"
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	var data int64
	for _, rowid := range rowids {
		ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
		rows, err = stmt.QueryContext(ctx, rowid)
		if err != nil {
			cancel()
			stmt.Close()
			t.Fatal("query error:", err)
		}

		if !rows.Next() {
			cancel()
			rows.Close()
			stmt.Close()
			t.Fatal("expected row")
		}

		err = rows.Scan(&data)
		if err != nil {
			cancel()
			rows.Close()
			stmt.Close()
			t.Fatal("scan error:", err)
		}

		if data != 1 {
			cancel()
			rows.Close()
			stmt.Close()
			t.Fatal("row not equal to 1")
		}

		if rows.Next() {
			cancel()
			rows.Close()
			stmt.Close()
			t.Fatal("more than one row")
		}

		err = rows.Err()
		if err != nil {
			cancel()
			rows.Close()
			stmt.Close()
			t.Fatal("rows error:", err)
		}

		cancel()

		err = rows.Close()
		if err != nil {
			stmt.Close()
			t.Fatal("rows close error", err)
		}
	}

	err = stmt.Close()
	if err != nil {
		t.Fatal("stmt close error", err)
	}

}

// TestNullBool tests NullBool
func TestNullBool(t *testing.T) {
	if TestDisableDatabase {
		t.SkipNow()
	}

	t.Parallel()

	query := `
declare
	function GET_BOOL(p_bool1 NUMERIC) return NUMERIC as
	begin
		if p_bool1 is null then
			return 1;
		end if;
		return 0;
	end GET_BOOL;
begin
	:bool1 := GET_BOOL(:bool1);
end;`

	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	var nullBool1 sql.NullBool

	nullBool1.Bool = false
	nullBool1.Valid = false

	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	_, err = stmt.ExecContext(ctx, sql.Named("bool1", sql.Out{Dest: &nullBool1, In: true}))
	cancel()
	if err != nil {
		t.Fatal("exec error:", err)
	}
	if !nullBool1.Valid {
		t.Fatal("nullBool1 not Valid")
	}
	if !nullBool1.Bool {
		t.Fatal("nullBool1 is false")
	}

	nullBool1.Bool = true
	nullBool1.Valid = true

	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	_, err = stmt.ExecContext(ctx, sql.Named("bool1", sql.Out{Dest: &nullBool1, In: true}))
	cancel()
	if err != nil {
		t.Fatal("exec error:", err)
	}
	if !nullBool1.Valid {
		t.Fatal("nullBool1 not Valid")
	}
	if nullBool1.Bool {
		t.Fatal("nullBool1 is true")
	}

	query = `
declare
	function GET_BOOL(p_bool1 NUMERIC) return NUMERIC as
	begin
		return null;
	end GET_BOOL;
begin
	:bool1 := GET_BOOL(:bool1);
end;`

	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	nullBool1.Bool = true
	nullBool1.Valid = true

	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	_, err = stmt.ExecContext(ctx, sql.Named("bool1", sql.Out{Dest: &nullBool1, In: true}))
	cancel()
	if err != nil {
		t.Fatal("exec error:", err)
	}
	if nullBool1.Valid {
		t.Fatal("nullBool1 is Valid")
	}
	if nullBool1.Bool {
		t.Fatal("nullBool1 is true")
	}
}

// TestQuestionMark tests question mark placeholder
func TestQuestionMark(t *testing.T) {
	if TestDisableDatabase {
		t.SkipNow()
	}

	t.Parallel()

	var err error

	db := testGetDB("?questionph=true")
	if db == nil {
		t.Fatal("db is null")
	}

	defer func() {
		err = db.Close()
		if err != nil {
			t.Fatal("db close error:", err)
		}
	}()

	var stmt *sql.Stmt
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = db.PrepareContext(ctx, "select ?, ?, ? from dual")
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}

	var result [][]interface{}
	result, err = testGetRows(t, stmt, []interface{}{1, 2.25, "three"})
	if err != nil {
		t.Fatal("get rows error:", err)
	}
	if result == nil {
		t.Fatal("result is nil")
	}
	if len(result) != 1 {
		t.Fatal("len result not equal to 1")
	}
	if len(result[0]) != 3 {
		t.Fatal("len result[0] not equal to 3")
	}
	float, ok := result[0][0].(float64)
	if !ok {
		t.Fatal("result not float64")
	}
	if float != float64(1) {
		t.Fatal("result not equal:", float)
	}
	float, ok = result[0][1].(float64)
	if !ok {
		t.Fatal("result not float64")
	}
	if float != float64(2.25) {
		t.Fatal("result not equal:", float)
	}
	var aString string
	aString, ok = result[0][2].(string)
	if !ok {
		t.Fatal("result not string")
	}
	if aString != "three" {
		t.Fatal("result not equal:", aString)
	}
}

func BenchmarkSimpleInsert(b *testing.B) {
	if TestDisableDatabase || TestDisableDestructive {
		b.SkipNow()
	}

	b.StopTimer()

	// SIMPLE_INSERT
	tableName := "SIMPLE_INSERT_" + TestTimeString
	query := "create table " + tableName + " ( A INTEGER )"

	// create table
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		b.Fatal("prepare error:", err)
	}

	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	_, err = stmt.ExecContext(ctx)
	cancel()
	if err != nil {
		stmt.Close()
		b.Fatal("exec error:", err)
	}

	err = stmt.Close()
	if err != nil {
		b.Fatal("stmt close error:", err)
	}

	// drop table
	defer func() {
		query = "drop table " + tableName
		ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
		stmt, err = TestDB.PrepareContext(ctx, query)
		cancel()
		if err != nil {
			b.Fatal("prepare error:", err)
		}

		ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
		_, err = stmt.ExecContext(ctx)
		cancel()
		if err != nil {
			stmt.Close()
			b.Fatal("exec error:", err)
		}

		err = stmt.Close()
		if err != nil {
			b.Fatal("stmt close error:", err)
		}
	}()

	// insert into table
	query = "insert into " + tableName + " ( A ) values (:1)"
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		b.Fatal("prepare error:", err)
	}

	b.ResetTimer()
	b.StartTimer()

	for n := 0; n < b.N; n++ {
		ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
		_, err = stmt.ExecContext(ctx, n)
		cancel()
		if err != nil {
			stmt.Close()
			b.Fatal("exec error:", err)
		}
	}

	err = stmt.Close()
	if err != nil {
		b.Fatal("stmt close error", err)
	}
}

func benchmarkSelectSetup(b *testing.B) {
	fmt.Println("benchmark select setup start")

	benchmarkSelectTableName = "BM_SELECT_" + TestTimeString

	// create table
	tableName := benchmarkSelectTableName
	query := "create table " + tableName +
		"( A INTEGER, B INTEGER, C INTEGER, D INTEGER, E VARCHAR2(255), F VARCHAR2(255), G VARCHAR2(255), H VARCHAR2(255) )"
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		b.Fatal("prepare error:", err)
	}

	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	_, err = stmt.ExecContext(ctx)
	cancel()
	if err != nil {
		stmt.Close()
		b.Fatal("exec error:", err)
	}

	// enable drop table in TestMain
	benchmarkSelectTableCreated = true

	err = stmt.Close()
	if err != nil {
		b.Fatal("stmt close error:", err)
	}

	// insert into table
	query = "insert into " + tableName + ` ( A, B, C, D, E, F, G, H )
select :1, :2, :3, :4, :5, :6, :7, :8 from dual
union all select :9, :10, :11, :12, :13, :14, :15, :16 from dual
union all select :17, :18, :19, :20, :21, :22, :23, :24 from dual
union all select :25, :26, :27, :28, :29, :30, :31, :32 from dual
union all select :33, :34, :35, :36, :37, :38, :39, :40 from dual
union all select :41, :42, :43, :44, :45, :46, :47, :48 from dual
union all select :49, :50, :51, :52, :53, :54, :55, :56 from dual
union all select :57, :58, :59, :60, :61, :62, :63, :64 from dual
union all select :65, :66, :67, :68, :69, :70, :71, :72 from dual
union all select :73, :74, :75, :76, :77, :78, :79, :80 from dual`
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		b.Fatal("prepare error:", err)
	}

	insertString := strings.Repeat("a", 255)
	for i := 0; i < 5000; i += 10 {
		ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
		_, err = stmt.ExecContext(ctx,
			i, i+20000, i+40000, i+60000, insertString, insertString, insertString, insertString,
			i+1, i+20001, i+40001, i+60001, insertString, insertString, insertString, insertString,
			i+2, i+20002, i+40002, i+60002, insertString, insertString, insertString, insertString,
			i+3, i+20003, i+40003, i+60003, insertString, insertString, insertString, insertString,
			i+4, i+20004, i+40004, i+60004, insertString, insertString, insertString, insertString,
			i+5, i+20005, i+40005, i+60005, insertString, insertString, insertString, insertString,
			i+6, i+20006, i+40006, i+60006, insertString, insertString, insertString, insertString,
			i+7, i+20007, i+40007, i+60007, insertString, insertString, insertString, insertString,
			i+8, i+20008, i+40008, i+60008, insertString, insertString, insertString, insertString,
			i+9, i+20009, i+40009, i+60009, insertString, insertString, insertString, insertString)
		cancel()
		if err != nil {
			stmt.Close()
			b.Fatal("exec error:", err)
		}
	}

	err = stmt.Close()
	if err != nil {
		b.Fatal("stmt close error", err)
	}

	// select from table to warm up database cache
	query = "select A, B, C, D, E, F, G, H from " + tableName
	ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = TestDB.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		b.Fatal("prepare error:", err)
	}

	defer func() {
		err = stmt.Close()
		if err != nil {
			b.Fatal("stmt close error", err)
		}
	}()

	var rows *sql.Rows
	ctx, cancel = context.WithTimeout(context.Background(), 20*TestContextTimeout)
	defer cancel()
	rows, err = stmt.QueryContext(ctx)
	if err != nil {
		b.Fatal("exec error:", err)
	}

	defer func() {
		err = rows.Close()
		if err != nil {
			b.Fatal("row close error:", err)
		}
	}()

	var data1 int64
	var data2 int64
	var data3 int64
	var data4 int64
	var data5 string
	var data6 string
	var data7 string
	var data8 string
	var count int64
	for rows.Next() {
		err = rows.Scan(&data1, &data2, &data3, &data4, &data5, &data6, &data7, &data8)
		if err != nil {
			b.Fatal("scan error:", err)
		}
		count++
	}
	fmt.Printf("select data is %v bytes\n", (count*4*8)+(count*4*255))

	err = rows.Err()
	if err != nil {
		b.Fatal("err error:", err)
	}

	b.ResetTimer()

	fmt.Println("benchmark select setup end")
}

func benchmarkPrefetchSelect(b *testing.B, prefetchRows int64, prefetchMemory int64, n *int) {
	benchmarkSelectTableOnce.Do(func() { benchmarkSelectSetup(b) })

	var err error

	db := testGetDB("?prefetch_rows=" + strconv.FormatInt(prefetchRows, 10) + "&prefetch_memory=" + strconv.FormatInt(prefetchMemory, 10))
	if db == nil {
		b.Fatal("db is null")
	}

	defer func() {
		err = db.Close()
		if err != nil {
			b.Fatal("db close error:", err)
		}
	}()

	b.StartTimer()

	var stmt *sql.Stmt
	tableName := benchmarkSelectTableName
	query := "select A, B, C, D, E, F, G, H from " + tableName
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err = db.PrepareContext(ctx, query)
	cancel()
	if err != nil {
		b.Fatal("prepare error:", err)
	}

	defer func() {
		err = stmt.Close()
		if err != nil {
			b.Fatal("stmt close error", err)
		}
	}()

	var rows *sql.Rows
	ctx, cancel = context.WithTimeout(context.Background(), 20*TestContextTimeout)
	defer cancel()
	rows, err = stmt.QueryContext(ctx)
	if err != nil {
		b.Fatal("exec error:", err)
	}

	defer func() {
		err = rows.Close()
		if err != nil {
			b.Fatal("row close error:", err)
		}
	}()

	var data1 int64
	var data2 int64
	var data3 int64
	var data4 int64
	var data5 string
	var data6 string
	var data7 string
	var data8 string
	for ; rows.Next() && *n < b.N; *n++ {
		err = rows.Scan(&data1, &data2, &data3, &data4, &data5, &data6, &data7, &data8)
		if err != nil {
			b.Fatal("scan error:", err)
		}
	}

	b.StopTimer()

	err = rows.Err()
	if err != nil {
		b.Fatal("err error:", err)
	}
}

// TestSelectParallelWithStatementCaching checks parallel select from dual but with statement caching enabled
func TestSelectParallelWithStatementCaching(t *testing.T) {
	if TestDisableDatabase {
		t.SkipNow()
	}
	db := testGetDB("?stmt_cache_size=100")
	if db == nil {
		t.Fatal("db is null")
	}

	var waitGroup sync.WaitGroup
	waitGroup.Add(50)

	for i := 0; i < 50; i++ {
		go func(num int) {
			defer waitGroup.Done()

			selectNumFromDual(t, db, float64(num))
		}(i)
	}

	waitGroup.Wait()
}

// selectNumFromDual will execute a "select :1 from dual" where the parameter is the num param of this function
func selectNumFromDual(t testing.TB, db *sql.DB, num float64) {
	ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout)
	stmt, err := db.PrepareContext(ctx, "select :1 from dual")
	cancel()
	if err != nil {
		t.Fatal("prepare error:", err)
	}
	defer func() {
		if stmt != nil {
			err := stmt.Close()
			if err != nil {
				t.Fatal("stmt close error:", err)
			}
		}
	}()

	var result [][]interface{}
	result, err = testGetRows(t, stmt, []interface{}{num})
	if err != nil {
		t.Fatal("get rows error:", err)
	}
	if result == nil {
		t.Fatal("result is nil")
	}
	if len(result) != 1 {
		t.Fatal("len result not equal to 1")
	}
	if len(result[0]) != 1 {
		t.Fatal("len result[0] not equal to 1")
	}
	data, ok := result[0][0].(float64)
	if !ok {
		t.Fatal("result not float64")
	}
	if data != num {
		t.Fatal("result not equal to:", num)
	}
}

func BenchmarkSelectNoCaching(b *testing.B) {
	if TestDisableDatabase || TestDisableDestructive {
		b.SkipNow()
	}
	for i := 0; i < b.N; i++ {
		selectNumFromDual(b, TestDB, float64(i))
	}
}

func BenchmarkSelectWithCaching(b *testing.B) {
	b.StopTimer()

	if TestDisableDatabase || TestDisableDestructive {
		b.SkipNow()
	}

	db := testGetDB("?stmt_cache_size=100")
	if db == nil {
		b.Fatal("db is null")
	}

	defer func() {
		err := db.Close()
		if err != nil {
			b.Fatal("db close error:", err)
		}
	}()

	b.StartTimer()
	for i := 0; i < b.N; i++ {
		selectNumFromDual(b, db, float64(i))
	}
}

func BenchmarkPrefetchR0M32768(b *testing.B) {
	b.StopTimer()

	if TestDisableDatabase || TestDisableDestructive {
		b.SkipNow()
	}

	for n := 0; n < b.N; {
		benchmarkPrefetchSelect(b, 0, 32768, &n)
	}
}

func BenchmarkPrefetchR0M16384(b *testing.B) {
	b.StopTimer()

	if TestDisableDatabase || TestDisableDestructive {
		b.SkipNow()
	}

	for n := 0; n < b.N; {
		benchmarkPrefetchSelect(b, 0, 16384, &n)
	}
}

func BenchmarkPrefetchR0M8192(b *testing.B) {
	b.StopTimer()

	if TestDisableDatabase || TestDisableDestructive {
		b.SkipNow()
	}

	for n := 0; n < b.N; {
		benchmarkPrefetchSelect(b, 0, 8192, &n)
	}
}

func BenchmarkPrefetchR0M4096(b *testing.B) {
	b.StopTimer()

	if TestDisableDatabase || TestDisableDestructive {
		b.SkipNow()
	}

	for n := 0; n < b.N; {
		benchmarkPrefetchSelect(b, 0, 4096, &n)
	}
}

func BenchmarkPrefetchR1000M0(b *testing.B) {
	b.StopTimer()

	if TestDisableDatabase || TestDisableDestructive {
		b.SkipNow()
	}

	for n := 0; n < b.N; {
		benchmarkPrefetchSelect(b, 1000, 0, &n)
	}
}
