diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go b/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go index 25195d1670c..45cdc1eae15 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go @@ -17,7 +17,6 @@ package enginetest import ( "context" "fmt" - "io" "os" "runtime" "sync" @@ -27,47 +26,26 @@ import ( "github.com/dolthub/go-mysql-server/enginetest" "github.com/dolthub/go-mysql-server/enginetest/queries" "github.com/dolthub/go-mysql-server/enginetest/scriptgen/setup" - "github.com/dolthub/go-mysql-server/server" "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/go-mysql-server/sql/memo" "github.com/dolthub/go-mysql-server/sql/mysql_db" "github.com/dolthub/go-mysql-server/sql/plan" - "github.com/dolthub/go-mysql-server/sql/transform" gmstypes "github.com/dolthub/go-mysql-server/sql/types" - "github.com/dolthub/vitess/go/mysql" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils" "github.com/dolthub/dolt/go/libraries/doltcore/env" - "github.com/dolthub/dolt/go/libraries/doltcore/schema" "github.com/dolthub/dolt/go/libraries/doltcore/sqle" "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" "github.com/dolthub/dolt/go/libraries/doltcore/sqle/statspro" "github.com/dolthub/dolt/go/libraries/utils/config" - "github.com/dolthub/dolt/go/store/datas" "github.com/dolthub/dolt/go/store/types" ) -var skipPrepared bool - // SkipPreparedsCount is used by the "ci-check-repo CI workflow // as a reminder to consider prepareds when adding a new // enginetest suite. const SkipPreparedsCount = 83 -const skipPreparedFlag = "DOLT_SKIP_PREPARED_ENGINETESTS" - -func init() { - sqle.MinRowsPerPartition = 8 - sqle.MaxRowsPerPartition = 1024 - - if v := os.Getenv(skipPreparedFlag); v != "" { - skipPrepared = true - } -} - func TestQueries(t *testing.T) { h := newDoltHarness(t) defer h.Close() @@ -120,26 +98,8 @@ func TestSingleQuery(t *testing.T) { } func TestSchemaOverrides(t *testing.T) { - tcc := &testCommitClock{} - cleanup := installTestCommitClock(tcc) - defer cleanup() - - for _, script := range SchemaOverrideTests { - sql.RunWithNowFunc(tcc.Now, func() error { - harness := newDoltHarness(t) - harness.Setup(setup.MydbData) - - engine, err := harness.NewEngine(t) - if err != nil { - panic(err) - } - // engine.EngineAnalyzer().Debug = true - // engine.EngineAnalyzer().Verbose = true - - enginetest.TestScriptWithEngine(t, engine, harness, script) - return nil - }) - } + harness := newDoltEnginetestHarness(t) + RunSchemaOverridesTest(t, harness) } // Convenience test for debugging a single query. Unskip and set to the desired query. @@ -177,122 +137,8 @@ func newUpdateResult(matched, updated int) gmstypes.OkResult { } func TestAutoIncrementTrackerLockMode(t *testing.T) { - for _, lockMode := range []int64{0, 1, 2} { - t.Run(fmt.Sprintf("lock mode %d", lockMode), func(t *testing.T) { - testAutoIncrementTrackerWithLockMode(t, lockMode) - }) - } -} - -// testAutoIncrementTrackerWithLockMode tests that interleaved inserts don't cause deadlocks, regardless of the value of innodb_autoinc_lock_mode. -// In a real use case, these interleaved operations would be happening in different sessions on different threads. -// In order to make the test behave predictably, we manually interleave the two iterators. -func testAutoIncrementTrackerWithLockMode(t *testing.T, lockMode int64) { - - err := sql.SystemVariables.AssignValues(map[string]interface{}{"innodb_autoinc_lock_mode": lockMode}) - require.NoError(t, err) - - setupScripts := []setup.SetupScript{[]string{ - "CREATE TABLE test1 (pk int NOT NULL PRIMARY KEY AUTO_INCREMENT,c0 int,index t1_c_index (c0));", - "CREATE TABLE test2 (pk int NOT NULL PRIMARY KEY AUTO_INCREMENT,c0 int,index t2_c_index (c0));", - "CREATE TABLE timestamps (pk int NOT NULL PRIMARY KEY AUTO_INCREMENT, t int);", - "CREATE TRIGGER t1 AFTER INSERT ON test1 FOR EACH ROW INSERT INTO timestamps VALUES (0, 1);", - "CREATE TRIGGER t2 AFTER INSERT ON test2 FOR EACH ROW INSERT INTO timestamps VALUES (0, 2);", - "CREATE VIEW bin AS SELECT 0 AS v UNION ALL SELECT 1;", - "CREATE VIEW sequence5bit AS SELECT b1.v + 2*b2.v + 4*b3.v + 8*b4.v + 16*b5.v AS v from bin b1, bin b2, bin b3, bin b4, bin b5;", - }} - - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData, setupScripts) - e := mustNewEngine(t, harness) - - defer e.Close() - ctx := enginetest.NewContext(harness) - - // Confirm that the system variable was correctly set. - _, iter, err := e.Query(ctx, "select @@innodb_autoinc_lock_mode") - require.NoError(t, err) - rows, err := sql.RowIterToRows(ctx, iter) - require.NoError(t, err) - assert.Equal(t, rows, []sql.Row{{lockMode}}) - - // Ordinarily QueryEngine.query manages transactions. - // Since we can't use that for this test, we manually start a new transaction. - ts := ctx.Session.(sql.TransactionSession) - tx, err := ts.StartTransaction(ctx, sql.ReadWrite) - require.NoError(t, err) - ctx.SetTransaction(tx) - - getTriggerIter := func(query string) sql.RowIter { - root, err := e.AnalyzeQuery(ctx, query) - require.NoError(t, err) - - var triggerNode *plan.TriggerExecutor - transform.Node(root, func(n sql.Node) (sql.Node, transform.TreeIdentity, error) { - if triggerNode != nil { - return n, transform.SameTree, nil - } - if t, ok := n.(*plan.TriggerExecutor); ok { - triggerNode = t - } - return n, transform.NewTree, nil - }) - iter, err := e.EngineAnalyzer().ExecBuilder.Build(ctx, triggerNode, nil) - require.NoError(t, err) - return iter - } - - iter1 := getTriggerIter("INSERT INTO test1 (c0) select v from sequence5bit;") - iter2 := getTriggerIter("INSERT INTO test2 (c0) select v from sequence5bit;") - - // Alternate the iterators until they're exhausted. - var err1 error - var err2 error - for err1 != io.EOF || err2 != io.EOF { - if err1 != io.EOF { - var row1 sql.Row - require.NoError(t, err1) - row1, err1 = iter1.Next(ctx) - _ = row1 - } - if err2 != io.EOF { - require.NoError(t, err2) - _, err2 = iter2.Next(ctx) - } - } - err = iter1.Close(ctx) - require.NoError(t, err) - err = iter2.Close(ctx) - require.NoError(t, err) - - dsess.DSessFromSess(ctx.Session).CommitTransaction(ctx, ctx.GetTransaction()) - - // Verify that the inserts are seen by the engine. - { - _, iter, err := e.Query(ctx, "select count(*) from timestamps") - require.NoError(t, err) - rows, err := sql.RowIterToRows(ctx, iter) - require.NoError(t, err) - assert.Equal(t, rows, []sql.Row{{int64(64)}}) - } - - // Verify that the insert operations are actually interleaved by inspecting the order that values were added to `timestamps` - { - _, iter, err := e.Query(ctx, "select (select min(pk) from timestamps where t = 1) < (select max(pk) from timestamps where t = 2)") - require.NoError(t, err) - rows, err := sql.RowIterToRows(ctx, iter) - require.NoError(t, err) - assert.Equal(t, rows, []sql.Row{{true}}) - } - - { - _, iter, err := e.Query(ctx, "select (select min(pk) from timestamps where t = 2) < (select max(pk) from timestamps where t = 1)") - require.NoError(t, err) - rows, err := sql.RowIterToRows(ctx, iter) - require.NoError(t, err) - assert.Equal(t, rows, []sql.Row{{true}}) - } + harness := newDoltEnginetestHarness(t) + RunAutoIncrementTrackerLockModeTest(t, harness) } // Convenience test for debugging a single query. Unskip and set to the desired query. @@ -530,20 +376,10 @@ func TestSingleScriptPrepared(t *testing.T) { } func TestVersionedQueries(t *testing.T) { - h := newDoltHarness(t) + h := newDoltEnginetestHarness(t) defer h.Close() - h.Setup(setup.MydbData, []setup.SetupScript{VersionedQuerySetup, VersionedQueryViews}) - - e, err := h.NewEngine(t) - require.NoError(t, err) - - for _, tt := range queries.VersionedQueries { - enginetest.TestQueryWithEngine(t, h, e, tt) - } - for _, tt := range queries.VersionedScripts { - enginetest.TestScriptWithEngine(t, e, h, tt) - } + RunVersionedQueriesTest(t, h) } func TestAnsiQuotesSqlMode(t *testing.T) { @@ -557,47 +393,12 @@ func TestAnsiQuotesSqlModePrepared(t *testing.T) { // Tests of choosing the correct execution plan independent of result correctness. Mostly useful for confirming that // the right indexes are being used for joining tables. func TestQueryPlans(t *testing.T) { - // Dolt supports partial keys, so the index matched is different for some plans - // TODO: Fix these differences by implementing partial key matching in the memory tables, or the engine itself - skipped := []string{ - "SELECT pk,pk1,pk2 FROM one_pk LEFT JOIN two_pk ON pk=pk1", - "SELECT pk,pk1,pk2 FROM one_pk JOIN two_pk ON pk=pk1", - "SELECT one_pk.c5,pk1,pk2 FROM one_pk JOIN two_pk ON pk=pk1 ORDER BY 1,2,3", - "SELECT opk.c5,pk1,pk2 FROM one_pk opk JOIN two_pk tpk ON opk.pk=tpk.pk1 ORDER BY 1,2,3", - "SELECT opk.c5,pk1,pk2 FROM one_pk opk JOIN two_pk tpk ON pk=pk1 ORDER BY 1,2,3", - "SELECT pk,pk1,pk2 FROM one_pk LEFT JOIN two_pk ON pk=pk1 ORDER BY 1,2,3", - "SELECT pk,pk1,pk2 FROM one_pk t1, two_pk t2 WHERE pk=1 AND pk2=1 AND pk1=1 ORDER BY 1,2", - } - // Parallelism introduces Exchange nodes into the query plans, so disable. - // TODO: exchange nodes should really only be part of the explain plan under certain debug settings - harness := newDoltHarness(t).WithSkippedQueries(skipped) - harness.configureStats = true - if !types.IsFormat_DOLT(types.Format_Default) { - // only new format supports reverse IndexTableAccess - reverseIndexSkip := []string{ - "SELECT * FROM one_pk ORDER BY pk", - "SELECT * FROM two_pk ORDER BY pk1, pk2", - "SELECT * FROM two_pk ORDER BY pk1", - "SELECT pk1 AS one, pk2 AS two FROM two_pk ORDER BY pk1, pk2", - "SELECT pk1 AS one, pk2 AS two FROM two_pk ORDER BY one, two", - "SELECT i FROM (SELECT i FROM mytable ORDER BY i DESC LIMIT 1) sq WHERE i = 3", - "SELECT i FROM (SELECT i FROM (SELECT i FROM mytable ORDER BY DES LIMIT 1) sql1)sql2 WHERE i = 3", - "SELECT s,i FROM mytable order by i DESC", - "SELECT s,i FROM mytable as a order by i DESC", - "SELECT pk1, pk2 FROM two_pk order by pk1 asc, pk2 asc", - "SELECT pk1, pk2 FROM two_pk order by pk1 desc, pk2 desc", - "SELECT i FROM (SELECT i FROM (SELECT i FROM mytable ORDER BY i DESC LIMIT 1) sq1) sq2 WHERE i = 3", - } - harness = harness.WithSkippedQueries(reverseIndexSkip) - } - - defer harness.Close() - enginetest.TestQueryPlans(t, harness, queries.PlanTests) + harness := newDoltEnginetestHarness(t) + RunQueryTestPlans(t, harness) } func TestIntegrationQueryPlans(t *testing.T) { - harness := newDoltHarness(t) - harness.configureStats = true + harness := newDoltEnginetestHarness(t).WithConfigureStats(true) defer harness.Close() enginetest.TestIntegrationPlans(t, harness) } @@ -607,38 +408,13 @@ func TestDoltDiffQueryPlans(t *testing.T) { t.Skip("only new format support system table indexing") } - harness := newDoltHarness(t).WithParallelism(2) // want Exchange nodes - defer harness.Close() - harness.Setup(setup.SimpleSetup...) - e, err := harness.NewEngine(t) - require.NoError(t, err) - defer e.Close() - - for _, tt := range append(DoltDiffPlanTests, DoltCommitPlanTests...) { - enginetest.TestQueryPlanWithName(t, tt.Query, harness, e, tt.Query, tt.ExpectedPlan, sql.DescribeOptions{}) - } + harness := newDoltEnginetestHarness(t).WithParallelism(2) // want Exchange nodes + RunDoltDiffQueryPlansTest(t, harness) } func TestBranchPlans(t *testing.T) { - for _, script := range BranchPlanTests { - t.Run(script.Name, func(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - - e := mustNewEngine(t, harness) - defer e.Close() - - for _, statement := range script.SetUpScript { - ctx := enginetest.NewContext(harness).WithQuery(statement) - enginetest.RunQueryWithContext(t, e, harness, ctx, statement) - } - for _, tt := range script.Queries { - t.Run(tt.Query, func(t *testing.T) { - TestIndexedAccess(t, e, harness, tt.Query, tt.Index) - }) - } - }) - } + harness := newDoltEnginetestHarness(t) + RunBranchPlanTests(t, harness) } func TestQueryErrors(t *testing.T) { @@ -648,17 +424,8 @@ func TestQueryErrors(t *testing.T) { } func TestInfoSchema(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestInfoSchema(t, h) - - for _, script := range DoltInfoSchemaScripts { - func() { - harness := newDoltHarness(t) - defer harness.Close() - enginetest.TestScript(t, harness, script) - }() - } + h := newDoltEnginetestHarness(t) + RunInfoSchemaTests(t, h) } func TestColumnAliases(t *testing.T) { @@ -726,27 +493,13 @@ func TestIgnoreIntoWithDuplicateUniqueKeyKeylessPrepared(t *testing.T) { } func TestInsertIntoErrors(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - h = h.WithSkippedQueries([]string{ - "create table bad (vb varbinary(65535))", - "insert into bad values (repeat('0', 65536))", - }) - enginetest.TestInsertIntoErrors(t, h) + h := newDoltEnginetestHarness(t) + RunInsertIntoErrorsTest(t, h) } func TestGeneratedColumns(t *testing.T) { - enginetest.TestGeneratedColumns(t, - // virtual indexes are failing for certain lookups on this test - newDoltHarness(t).WithSkippedQueries([]string{"create table t (pk int primary key, col1 int as (pk + 1));"})) - - for _, script := range GeneratedColumnMergeTestScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + harness := newDoltEnginetestHarness(t) + RunGeneratedColumnTests(t, harness) } func TestGeneratedColumnPlans(t *testing.T) { @@ -951,8 +704,7 @@ func TestJoinPlanning(t *testing.T) { if types.IsFormat_LD(types.Format_Default) { t.Skip("DOLT_LD keyless indexes are not sorted") } - h := newDoltHarness(t) - h.configureStats = true + h := newDoltEnginetestHarness(t).WithConfigureStats(true) defer h.Close() enginetest.TestJoinPlanning(t, h) } @@ -1031,23 +783,13 @@ func TestRowLimit(t *testing.T) { } func TestBranchDdl(t *testing.T) { - for _, script := range DdlBranchTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunBranchDdlTest(t, h) } func TestBranchDdlPrepared(t *testing.T) { - for _, script := range DdlBranchTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunBranchDdlTestPrepared(t, h) } func TestPkOrdinalsDDL(t *testing.T) { @@ -1120,63 +862,19 @@ func TestIndexes(t *testing.T) { func TestIndexPrefix(t *testing.T) { skipOldFormat(t) harness := newDoltHarness(t) - defer harness.Close() - enginetest.TestIndexPrefix(t, harness) - for _, script := range DoltIndexPrefixScripts { - enginetest.TestScript(t, harness, script) - } + RunIndexPrefixTest(t, harness) } func TestBigBlobs(t *testing.T) { skipOldFormat(t) h := newDoltHarness(t) - defer h.Close() - h.Setup(setup.MydbData, setup.BlobData) - for _, tt := range BigBlobQueries { - enginetest.RunWriteQueryTest(t, h, tt) - } + RunBigBlobsTest(t, h) } func TestDropDatabase(t *testing.T) { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, queries.ScriptTest{ - Name: "Drop database engine tests for Dolt only", - SetUpScript: []string{ - "CREATE DATABASE Test1db", - "CREATE DATABASE TEST2db", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "DROP DATABASE TeSt2DB", - Expected: []sql.Row{{gmstypes.OkResult{RowsAffected: 1}}}, - }, - { - Query: "USE test2db", - ExpectedErr: sql.ErrDatabaseNotFound, - }, - { - Query: "USE TEST1DB", - Expected: []sql.Row{}, - }, - { - Query: "DROP DATABASE IF EXISTS test1DB", - Expected: []sql.Row{{gmstypes.OkResult{RowsAffected: 1}}}, - }, - { - Query: "USE Test1db", - ExpectedErr: sql.ErrDatabaseNotFound, - }, - }, - }) - }() - - t.Skip("Dolt doesn't yet support dropping the primary database, which these tests do") - h := newDoltHarness(t) - defer h.Close() - enginetest.TestDropDatabase(t, h) + h := newDoltEnginetestHarness(t) + RunDropEngineTest(t, h) } func TestCreateForeignKeys(t *testing.T) { @@ -1198,61 +896,13 @@ func TestForeignKeys(t *testing.T) { } func TestForeignKeyBranches(t *testing.T) { - setupPrefix := []string{ - "call dolt_branch('b1')", - "use mydb/b1", - } - assertionsPrefix := []queries.ScriptTestAssertion{ - { - Query: "use mydb/b1", - SkipResultsCheck: true, - }, - } - for _, script := range queries.ForeignKeyTests { - // New harness for every script because we create branches - h := newDoltHarness(t) - h.Setup(setup.MydbData, setup.Parent_childData) - modifiedScript := script - modifiedScript.SetUpScript = append(setupPrefix, modifiedScript.SetUpScript...) - modifiedScript.Assertions = append(assertionsPrefix, modifiedScript.Assertions...) - enginetest.TestScript(t, h, modifiedScript) - } - - for _, script := range ForeignKeyBranchTests { - // New harness for every script because we create branches - h := newDoltHarness(t) - h.Setup(setup.MydbData, setup.Parent_childData) - enginetest.TestScript(t, h, script) - } + h := newDoltEnginetestHarness(t) + RunForeignKeyBranchesTest(t, h) } func TestForeignKeyBranchesPrepared(t *testing.T) { - setupPrefix := []string{ - "call dolt_branch('b1')", - "use mydb/b1", - } - assertionsPrefix := []queries.ScriptTestAssertion{ - { - Query: "use mydb/b1", - SkipResultsCheck: true, - }, - } - for _, script := range queries.ForeignKeyTests { - // New harness for every script because we create branches - h := newDoltHarness(t) - h.Setup(setup.MydbData, setup.Parent_childData) - modifiedScript := script - modifiedScript.SetUpScript = append(setupPrefix, modifiedScript.SetUpScript...) - modifiedScript.Assertions = append(assertionsPrefix, modifiedScript.Assertions...) - enginetest.TestScriptPrepared(t, h, modifiedScript) - } - - for _, script := range ForeignKeyBranchTests { - // New harness for every script because we create branches - h := newDoltHarness(t) - h.Setup(setup.MydbData, setup.Parent_childData) - enginetest.TestScriptPrepared(t, h, script) - } + h := newDoltEnginetestHarness(t) + RunForeignKeyBranchesPreparedTest(t, h) } func TestFulltextIndexes(t *testing.T) { @@ -1310,39 +960,18 @@ func TestViews(t *testing.T) { } func TestBranchViews(t *testing.T) { - for _, script := range ViewBranchTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunBranchViewsTest(t, h) } func TestBranchViewsPrepared(t *testing.T) { - for _, script := range ViewBranchTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunBranchViewsPreparedTest(t, h) } func TestVersionedViews(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - h.Setup(setup.MydbData, []setup.SetupScript{VersionedQuerySetup, VersionedQueryViews}) - - e, err := h.NewEngine(t) - require.NoError(t, err) - - for _, testCase := range queries.VersionedViewTests { - t.Run(testCase.Query, func(t *testing.T) { - ctx := enginetest.NewContext(h) - enginetest.TestQueryWithContext(t, ctx, e, h, testCase.Query, testCase.Expected, testCase.ExpectedColumns, nil) - }) - } + h := newDoltEnginetestHarness(t) + RunVersionedViewsTest(t, h) } func TestWindowFunctions(t *testing.T) { @@ -1413,12 +1042,8 @@ func TestAlterTable(t *testing.T) { } func TestVariables(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestVariables(t, h) - for _, script := range DoltSystemVariables { - enginetest.TestScript(t, h, script) - } + h := newDoltEnginetestHarness(t) + RunVariableTest(t, h) } func TestVariableErrors(t *testing.T) { @@ -1461,6 +1086,7 @@ func TestJsonScripts(t *testing.T) { skippedTests := []string{ "round-trip into table", // The current Dolt JSON format does not preserve decimals and unsigneds in JSON. } + // TODO: fix this, use a skipping harness enginetest.TestJsonScripts(t, h, skippedTests) } @@ -1477,38 +1103,18 @@ func TestRollbackTriggers(t *testing.T) { } func TestStoredProcedures(t *testing.T) { - tests := make([]queries.ScriptTest, 0, len(queries.ProcedureLogicTests)) - for _, test := range queries.ProcedureLogicTests { - //TODO: this passes locally but SOMETIMES fails tests on GitHub, no clue why - if test.Name != "ITERATE and LEAVE loops" { - tests = append(tests, test) - } - } - queries.ProcedureLogicTests = tests - - h := newDoltHarness(t) - defer h.Close() - enginetest.TestStoredProcedures(t, h) + h := newDoltEnginetestHarness(t) + RunStoredProceduresTest(t, h) } func TestDoltStoredProcedures(t *testing.T) { - for _, script := range DoltProcedureTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltStoredProceduresTest(t, h) } func TestDoltStoredProceduresPrepared(t *testing.T) { - for _, script := range DoltProcedureTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltStoredProceduresPreparedTest(t, h) } func TestEvents(t *testing.T) { @@ -1518,104 +1124,33 @@ func TestEvents(t *testing.T) { } func TestCallAsOf(t *testing.T) { - for _, script := range DoltCallAsOf { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunCallAsOfTest(t, h) } func TestLargeJsonObjects(t *testing.T) { - SkipByDefaultInCI(t) - harness := newDoltHarness(t) - defer harness.Close() - for _, script := range LargeJsonObjectScriptTests { - enginetest.TestScript(t, harness, script) - } -} - -func SkipByDefaultInCI(t *testing.T) { - if os.Getenv("CI") != "" && os.Getenv("DOLT_TEST_RUN_NON_RACE_TESTS") == "" { - t.Skip() - } + harness := newDoltEnginetestHarness(t) + RunLargeJsonObjectsTest(t, harness) } func TestTransactions(t *testing.T) { - for _, script := range queries.TransactionTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestTransactionScript(t, h, script) - }() - } - for _, script := range DoltTransactionTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestTransactionScript(t, h, script) - }() - } - for _, script := range DoltStoredProcedureTransactionTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestTransactionScript(t, h, script) - }() - } - for _, script := range DoltConflictHandlingTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestTransactionScript(t, h, script) - }() - } - for _, script := range DoltConstraintViolationTransactionTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestTransactionScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunTransactionTests(t, h) } func TestBranchTransactions(t *testing.T) { - for _, script := range BranchIsolationTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestTransactionScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunBranchTransactionTest(t, h) } func TestMultiDbTransactions(t *testing.T) { - for _, script := range MultiDbTransactionTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } - - for _, script := range MultiDbSavepointTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestTransactionScript(t, h, script) - }() - } -} + h := newDoltEnginetestHarness(t) + RunMultiDbTransactionsTest(t, h) +} func TestMultiDbTransactionsPrepared(t *testing.T) { - for _, script := range MultiDbTransactionTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunMultiDbTransactionsPreparedTest(t, h) } func TestConcurrentTransactions(t *testing.T) { @@ -1625,170 +1160,28 @@ func TestConcurrentTransactions(t *testing.T) { } func TestDoltScripts(t *testing.T) { - for _, script := range DoltScripts { - go func() { - harness := newDoltHarness(t) - defer harness.Close() - enginetest.TestScript(t, harness, script) - }() - } + harness := newDoltEnginetestHarness(t) + RunDoltScriptsTest(t, harness) } func TestDoltTempTableScripts(t *testing.T) { - for _, script := range DoltTempTableScripts { - harness := newDoltHarness(t) - enginetest.TestScript(t, harness, script) - harness.Close() - } + harness := newDoltEnginetestHarness(t) + RunDoltTempTableScripts(t, harness) } func TestDoltRevisionDbScripts(t *testing.T) { - for _, script := range DoltRevisionDbScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } - - // Testing a commit-qualified database revision spec requires - // a little extra work to get the generated commit hash - harness := newDoltHarness(t) - defer harness.Close() - e, err := harness.NewEngine(t) - require.NoError(t, err) - defer e.Close() - ctx := harness.NewContext() - - setupScripts := []setup.SetupScript{ - {"create table t01 (pk int primary key, c1 int)"}, - {"call dolt_add('.');"}, - {"call dolt_commit('-am', 'creating table t01 on main');"}, - {"insert into t01 values (1, 1), (2, 2);"}, - {"call dolt_commit('-am', 'adding rows to table t01 on main');"}, - {"insert into t01 values (3, 3);"}, - {"call dolt_commit('-am', 'adding another row to table t01 on main');"}, - } - _, err = enginetest.RunSetupScripts(ctx, harness.engine, setupScripts, true) - require.NoError(t, err) - - _, iter, err := harness.engine.Query(ctx, "select hashof('HEAD~2');") - require.NoError(t, err) - rows, err := sql.RowIterToRows(ctx, iter) - require.NoError(t, err) - assert.Equal(t, 1, len(rows)) - commithash := rows[0][0].(string) - - scriptTest := queries.ScriptTest{ - Name: "database revision specs: commit-qualified revision spec", - Assertions: []queries.ScriptTestAssertion{ - { - Query: "show databases;", - Expected: []sql.Row{{"mydb"}, {"information_schema"}, {"mysql"}}, - }, - { - Query: "use mydb/" + commithash, - Expected: []sql.Row{}, - }, - { - Query: "select active_branch();", - Expected: []sql.Row{ - {nil}, - }, - }, - { - Query: "select database();", - Expected: []sql.Row{{"mydb/" + commithash}}, - }, - { - Query: "show databases;", - Expected: []sql.Row{{"mydb"}, {"mydb/" + commithash}, {"information_schema"}, {"mysql"}}, - }, - { - Query: "select * from t01", - Expected: []sql.Row{}, - }, - { - Query: "call dolt_reset();", - ExpectedErrStr: "unable to reset HEAD in read-only databases", - }, - { - Query: "call dolt_checkout('main');", - Expected: []sql.Row{{0, "Switched to branch 'main'"}}, - }, - { - Query: "select database();", - Expected: []sql.Row{{"mydb"}}, - }, - { - Query: "select active_branch();", - Expected: []sql.Row{{"main"}}, - }, - { - Query: "use mydb;", - Expected: []sql.Row{}, - }, - { - Query: "select database();", - Expected: []sql.Row{{"mydb"}}, - }, - { - Query: "show databases;", - Expected: []sql.Row{{"mydb"}, {"information_schema"}, {"mysql"}}, - }, - }, - } - - enginetest.TestScript(t, harness, scriptTest) + h := newDoltEnginetestHarness(t) + RunDoltRevisionDbScriptsTest(t, h) } func TestDoltRevisionDbScriptsPrepared(t *testing.T) { - for _, script := range DoltRevisionDbScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltRevisionDbScriptsPreparedTest(t, h) } func TestDoltDdlScripts(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup() - - for _, script := range ModifyAndChangeColumnScripts { - e, err := harness.NewEngine(t) - require.NoError(t, err) - enginetest.TestScriptWithEngine(t, e, harness, script) - } - - for _, script := range ModifyColumnTypeScripts { - e, err := harness.NewEngine(t) - require.NoError(t, err) - enginetest.TestScriptWithEngine(t, e, harness, script) - } - - for _, script := range DropColumnScripts { - e, err := harness.NewEngine(t) - require.NoError(t, err) - enginetest.TestScriptWithEngine(t, e, harness, script) - } - if !types.IsFormat_DOLT(types.Format_Default) { - t.Skip("not fixing unique index on keyless tables for old format") - } - for _, script := range AddIndexScripts { - e, err := harness.NewEngine(t) - require.NoError(t, err) - enginetest.TestScriptWithEngine(t, e, harness, script) - } - - // TODO: these scripts should be general enough to go in GMS - for _, script := range AddDropPrimaryKeysScripts { - e, err := harness.NewEngine(t) - require.NoError(t, err) - enginetest.TestScriptWithEngine(t, e, harness, script) - } + harness := newDoltEnginetestHarness(t) + RunDoltDdlScripts(t, harness) } func TestBrokenDdlScripts(t *testing.T) { @@ -1804,23 +1197,13 @@ func TestDescribeTableAsOf(t *testing.T) { } func TestShowCreateTable(t *testing.T) { - for _, script := range ShowCreateTableScriptTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunShowCreateTableTests(t, h) } func TestShowCreateTablePrepared(t *testing.T) { - for _, script := range ShowCreateTableScriptTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunShowCreateTablePreparedTests(t, h) } func TestViewsWithAsOf(t *testing.T) { @@ -1837,146 +1220,60 @@ func TestViewsWithAsOfPrepared(t *testing.T) { } func TestDoltMerge(t *testing.T) { - for _, script := range MergeScripts { - // harness can't reset effectively when there are new commits / branches created, so use a new harness for - // each script - func() { - h := newDoltHarness(t) - defer h.Close() - h.Setup(setup.MydbData) - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltMergeTests(t, h) } -func TestDoltRebase(t *testing.T) { - for _, script := range DoltRebaseScriptTests { - func() { - h := newDoltHarness(t) - defer h.Close() - h.skipSetupCommit = true - enginetest.TestScript(t, h, script) - }() - } - - testMultiSessionScriptTests(t, DoltRebaseMultiSessionScriptTests) +func TestDoltMergePrepared(t *testing.T) { + h := newDoltEnginetestHarness(t) + RunDoltMergePreparedTests(t, h) } -func TestDoltRebasePrepared(t *testing.T) { - for _, script := range DoltRebaseScriptTests { - func() { - h := newDoltHarness(t) - defer h.Close() - h.skipSetupCommit = true - enginetest.TestScriptPrepared(t, h, script) - }() - } +func TestDoltRebase(t *testing.T) { + h := newDoltEnginetestHarness(t) + RunDoltRebaseTests(t, h) } -func TestDoltMergePrepared(t *testing.T) { - for _, script := range MergeScripts { - // harness can't reset effectively when there are new commits / branches created, so use a new harness for - // each script - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } +func TestDoltRebasePrepared(t *testing.T) { + h := newDoltHarness(t) + RunDoltRebasePreparedTests(t, h) } func TestDoltRevert(t *testing.T) { - for _, script := range RevertScripts { - // harness can't reset effectively. Use a new harness for each script - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltRevertTests(t, h) } func TestDoltRevertPrepared(t *testing.T) { - for _, script := range RevertScripts { - // harness can't reset effectively. Use a new harness for each script - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltRevertPreparedTests(t, h) } func TestDoltAutoIncrement(t *testing.T) { - for _, script := range DoltAutoIncrementTests { - // doing commits on different branches is antagonistic to engine reuse, use a new engine on each script - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltAutoIncrementTests(t, h) } func TestDoltAutoIncrementPrepared(t *testing.T) { - for _, script := range DoltAutoIncrementTests { - // doing commits on different branches is antagonistic to engine reuse, use a new engine on each script - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltAutoIncrementPreparedTests(t, h) } func TestDoltConflictsTableNameTable(t *testing.T) { - for _, script := range DoltConflictTableNameTableTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } - - if types.IsFormat_DOLT(types.Format_Default) { - for _, script := range Dolt1ConflictTableNameTableTests { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } - } + h := newDoltEnginetestHarness(t) + RunDoltConflictsTableNameTableTests(t, h) } // tests new format behavior for keyless merges that create CVs and conflicts func TestKeylessDoltMergeCVsAndConflicts(t *testing.T) { - if !types.IsFormat_DOLT(types.Format_Default) { - t.Skip() - } - for _, script := range KeylessMergeCVsAndConflictsScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunKelyessDoltMergeCVsAndConflictsTests(t, h) } // eventually this will be part of TestDoltMerge func TestDoltMergeArtifacts(t *testing.T) { - for _, script := range MergeArtifactsScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } - for _, script := range SchemaConflictScripts { - h := newDoltHarness(t) - enginetest.TestScript(t, h, script) - h.Close() - } + h := newDoltEnginetestHarness(t) + RunDoltMergeArtifacts(t, h) } // these tests are temporary while there is a difference between the old format @@ -1995,14 +1292,8 @@ func TestOldFormatMergeConflictsAndCVs(t *testing.T) { } func TestDoltReset(t *testing.T) { - for _, script := range DoltReset { - // dolt versioning conflicts with reset harness -- use new harness every time - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltResetTest(t, h) } func TestDoltGC(t *testing.T) { @@ -2017,104 +1308,33 @@ func TestDoltGC(t *testing.T) { } func TestDoltCheckout(t *testing.T) { - for _, script := range DoltCheckoutScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } - - h := newDoltHarness(t) - defer h.Close() - engine, err := h.NewEngine(t) - require.NoError(t, err) - readOnlyEngine, err := h.NewReadOnlyEngine(engine.EngineAnalyzer().Catalog.DbProvider) - require.NoError(t, err) - - for _, script := range DoltCheckoutReadOnlyScripts { - enginetest.TestScriptWithEngine(t, readOnlyEngine, h, script) - } + h := newDoltEnginetestHarness(t) + RunDoltCheckoutTests(t, h) } func TestDoltCheckoutPrepared(t *testing.T) { - for _, script := range DoltCheckoutScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, script) - }() - } - - h := newDoltHarness(t) - defer h.Close() - engine, err := h.NewEngine(t) - require.NoError(t, err) - readOnlyEngine, err := h.NewReadOnlyEngine(engine.EngineAnalyzer().Catalog.DbProvider) - require.NoError(t, err) - - for _, script := range DoltCheckoutReadOnlyScripts { - enginetest.TestScriptWithEnginePrepared(t, readOnlyEngine, h, script) - } + h := newDoltEnginetestHarness(t) + RunDoltCheckoutPreparedTests(t, h) } func TestDoltBranch(t *testing.T) { - for _, script := range DoltBranchScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltBranchTests(t, h) } func TestDoltTag(t *testing.T) { - for _, script := range DoltTagTestScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltTagTests(t, h) } func TestDoltRemote(t *testing.T) { - for _, script := range DoltRemoteTestScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunDoltRemoteTests(t, h) } func TestDoltUndrop(t *testing.T) { - h := newDoltHarnessForLocalFilesystem(t) - defer h.Close() - for _, script := range DoltUndropTestScripts { - enginetest.TestScript(t, h, script) - } -} - -type testCommitClock struct { - unixNano int64 -} - -func (tcc *testCommitClock) Now() time.Time { - now := time.Unix(0, tcc.unixNano) - tcc.unixNano += int64(time.Hour) - return now -} - -func installTestCommitClock(tcc *testCommitClock) func() { - oldNowFunc := datas.CommitterDate - oldCommitLoc := datas.CommitLoc - datas.CommitterDate = tcc.Now - datas.CommitLoc = time.UTC - return func() { - datas.CommitterDate = oldNowFunc - datas.CommitLoc = oldCommitLoc - } + h := newDoltEnginetestHarness(t) + RunDoltUndropTests(t, h) } // TestSingleTransactionScript is a convenience method for debugging a single transaction test. Unskip and set to the @@ -2198,27 +1418,13 @@ func TestBrokenSystemTableQueries(t *testing.T) { } func TestHistorySystemTable(t *testing.T) { - harness := newDoltHarness(t).WithParallelism(2) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range HistorySystemTableScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t).WithParallelism(2) + RunHistorySystemTableTests(t, harness) } func TestHistorySystemTablePrepared(t *testing.T) { - harness := newDoltHarness(t).WithParallelism(2) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range HistorySystemTableScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t).WithParallelism(2) + RunHistorySystemTableTestsPrepared(t, harness) } func TestBrokenHistorySystemTablePrepared(t *testing.T) { @@ -2235,450 +1441,153 @@ func TestBrokenHistorySystemTablePrepared(t *testing.T) { } func TestUnscopedDiffSystemTable(t *testing.T) { - for _, test := range UnscopedDiffSystemTableScriptTests { - t.Run(test.Name, func(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, test) - }) - } + h := newDoltEnginetestHarness(t) + RunUnscopedDiffSystemTableTests(t, h) } func TestUnscopedDiffSystemTablePrepared(t *testing.T) { - for _, test := range UnscopedDiffSystemTableScriptTests { - t.Run(test.Name, func(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, test) - }) - } + h := newDoltEnginetestHarness(t) + RunUnscopedDiffSystemTableTestsPrepared(t, h) } func TestColumnDiffSystemTable(t *testing.T) { - if !types.IsFormat_DOLT(types.Format_Default) { - t.Skip("correct behavior of dolt_column_diff only guaranteed on new format") - } - for _, test := range ColumnDiffSystemTableScriptTests { - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, newDoltHarness(t), test) - }) - } + h := newDoltEnginetestHarness(t) + RunColumnDiffSystemTableTests(t, h) } func TestColumnDiffSystemTablePrepared(t *testing.T) { - if !types.IsFormat_DOLT(types.Format_Default) { - t.Skip("correct behavior of dolt_column_diff only guaranteed on new format") - } - for _, test := range ColumnDiffSystemTableScriptTests { - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, newDoltHarness(t), test) - }) - } + h := newDoltEnginetestHarness(t) + RunColumnDiffSystemTableTestsPrepared(t, h) } func TestStatBranchTests(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - harness.configureStats = true - for _, test := range StatBranchTests { - t.Run(test.Name, func(t *testing.T) { - // reset engine so provider statistics are clean - harness.engine = nil - e := mustNewEngine(t, harness) - defer e.Close() - enginetest.TestScriptWithEngine(t, e, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunStatBranchTests(t, harness) } func TestStatsFunctions(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - harness.configureStats = true - harness.skipSetupCommit = true - for _, test := range StatProcTests { - t.Run(test.Name, func(t *testing.T) { - // reset engine so provider statistics are clean - harness.engine = nil - e := mustNewEngine(t, harness) - defer e.Close() - enginetest.TestScriptWithEngine(t, e, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunStatsFunctionsTest(t, harness) } func TestDiffTableFunction(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range DiffTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunDiffTableFunctionTests(t, harness) } func TestDiffTableFunctionPrepared(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range DiffTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunDiffTableFunctionTestsPrepared(t, harness) } func TestDiffStatTableFunction(t *testing.T) { - harness := newDoltHarness(t) - harness.Setup(setup.MydbData) - for _, test := range DiffStatTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunDiffStatTableFunctionTests(t, harness) } func TestDiffStatTableFunctionPrepared(t *testing.T) { - harness := newDoltHarness(t) - harness.Setup(setup.MydbData) - for _, test := range DiffStatTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunDiffStatTableFunctionTestsPrepared(t, harness) } func TestDiffSummaryTableFunction(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range DiffSummaryTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunDiffSummaryTableFunctionTests(t, harness) } func TestDiffSummaryTableFunctionPrepared(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range DiffSummaryTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunDiffSummaryTableFunctionTestsPrepared(t, harness) } -func TestPatchTableFunction(t *testing.T) { - harness := newDoltHarness(t) - harness.Setup(setup.MydbData) - for _, test := range PatchTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } -} - -func TestPatchTableFunctionPrepared(t *testing.T) { - harness := newDoltHarness(t) - harness.Setup(setup.MydbData) - for _, test := range PatchTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } -} - -func TestLogTableFunction(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range LogTableFunctionScriptTests { - harness.engine = nil - harness.skipSetupCommit = true - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } -} - -func TestLogTableFunctionPrepared(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range LogTableFunctionScriptTests { - harness.engine = nil - harness.skipSetupCommit = true - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } -} - -func TestDoltReflog(t *testing.T) { - for _, script := range DoltReflogTestScripts { - h := newDoltHarnessForLocalFilesystem(t) - h.SkipSetupCommit() - enginetest.TestScript(t, h, script) - h.Close() - } -} - -func TestDoltReflogPrepared(t *testing.T) { - for _, script := range DoltReflogTestScripts { - h := newDoltHarnessForLocalFilesystem(t) - h.SkipSetupCommit() - enginetest.TestScriptPrepared(t, h, script) - h.Close() - } -} - -func TestCommitDiffSystemTable(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range CommitDiffSystemTableScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } -} - -func TestCommitDiffSystemTablePrepared(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range CommitDiffSystemTableScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } -} - -func TestDiffSystemTable(t *testing.T) { - if !types.IsFormat_DOLT(types.Format_Default) { - t.Skip("only new format support system table indexing") - } - - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range DiffSystemTableScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } - - if types.IsFormat_DOLT(types.Format_Default) { - for _, test := range Dolt1DiffSystemTableScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, test) - }() - } - } -} - -func TestDiffSystemTablePrepared(t *testing.T) { - if !types.IsFormat_DOLT(types.Format_Default) { - t.Skip("only new format support system table indexing") - } - - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range DiffSystemTableScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } - - if types.IsFormat_DOLT(types.Format_Default) { - for _, test := range Dolt1DiffSystemTableScripts { - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, test) - }() - } - } -} - -func TestSchemaDiffTableFunction(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range SchemaDiffTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } +func TestPatchTableFunction(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunDoltPatchTableFunctionTests(t, harness) } -func TestSchemaDiffTableFunctionPrepared(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range SchemaDiffTableFunctionScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } +func TestPatchTableFunctionPrepared(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunDoltPatchTableFunctionTestsPrepared(t, harness) } -func TestDoltDatabaseCollationDiffs(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range DoltDatabaseCollationScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } +func TestLogTableFunction(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunLogTableFunctionTests(t, harness) } -func TestQueryDiff(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - harness.Setup(setup.MydbData) - for _, test := range QueryDiffTableScriptTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } +func TestLogTableFunctionPrepared(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunLogTableFunctionTestsPrepared(t, harness) } -func mustNewEngine(t *testing.T, h enginetest.Harness) enginetest.QueryEngine { - e, err := h.NewEngine(t) - if err != nil { - require.NoError(t, err) - } - return e +func TestDoltReflog(t *testing.T) { + h := newDoltEnginetestHarness(t) + RunDoltReflogTests(t, h) } -var biasedCosters = []memo.Coster{ - memo.NewInnerBiasedCoster(), - memo.NewLookupBiasedCoster(), - memo.NewHashBiasedCoster(), - memo.NewMergeBiasedCoster(), +func TestDoltReflogPrepared(t *testing.T) { + h := newDoltEnginetestHarness(t) + RunDoltReflogTestsPrepared(t, h) } -func TestSystemTableIndexes(t *testing.T) { - if !types.IsFormat_DOLT(types.Format_Default) { - t.Skip("only new format support system table indexing") - } +func TestCommitDiffSystemTable(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunCommitDiffSystemTableTests(t, harness) +} - for _, stt := range SystemTableIndexTests { - harness := newDoltHarness(t).WithParallelism(2) - defer harness.Close() - harness.SkipSetupCommit() - e := mustNewEngine(t, harness) - defer e.Close() - e.EngineAnalyzer().Coster = memo.NewMergeBiasedCoster() - - ctx := enginetest.NewContext(harness) - for _, q := range stt.setup { - enginetest.RunQueryWithContext(t, e, harness, ctx, q) - } +func TestCommitDiffSystemTablePrepared(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunCommitDiffSystemTableTestsPrepared(t, harness) +} - for i, c := range []string{"inner", "lookup", "hash", "merge"} { - e.EngineAnalyzer().Coster = biasedCosters[i] - for _, tt := range stt.queries { - if tt.query == "select count(*) from dolt_blame_xy" && c == "inner" { - // todo we either need join hints to work inside the blame view - // and force the window relation to be primary, or we need the - // blame view's timestamp columns to be specific enough to not - // overlap during testing. - t.Skip("the blame table is unstable as secondary table in join with exchange node") - } - t.Run(fmt.Sprintf("%s(%s): %s", stt.name, c, tt.query), func(t *testing.T) { - if tt.skip { - t.Skip() - } +func TestDiffSystemTable(t *testing.T) { + h := newDoltEnginetestHarness(t) + RunDoltDiffSystemTableTests(t, h) +} - ctx = ctx.WithQuery(tt.query) - if tt.exp != nil { - enginetest.TestQueryWithContext(t, ctx, e, harness, tt.query, tt.exp, nil, nil) - } - }) - } - } - } +func TestDiffSystemTablePrepared(t *testing.T) { + h := newDoltEnginetestHarness(t) + RunDoltDiffSystemTableTestsPrepared(t, h) } -func TestSystemTableIndexesPrepared(t *testing.T) { - if !types.IsFormat_DOLT(types.Format_Default) { - t.Skip("only new format support system table indexing") - } +func TestSchemaDiffTableFunction(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunSchemaDiffTableFunctionTests(t, harness) +} - for _, stt := range SystemTableIndexTests { - harness := newDoltHarness(t).WithParallelism(2) - defer harness.Close() - harness.SkipSetupCommit() - e := mustNewEngine(t, harness) - defer e.Close() +func TestSchemaDiffTableFunctionPrepared(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunSchemaDiffTableFunctionTestsPrepared(t, harness) +} - ctx := enginetest.NewContext(harness) - for _, q := range stt.setup { - enginetest.RunQueryWithContext(t, e, harness, ctx, q) - } +func TestDoltDatabaseCollationDiffs(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunDoltDatabaseCollationDiffsTests(t, harness) +} - for _, tt := range stt.queries { - t.Run(fmt.Sprintf("%s: %s", stt.name, tt.query), func(t *testing.T) { - if tt.skip { - t.Skip() - } +func TestQueryDiff(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunQueryDiffTests(t, harness) +} - ctx = ctx.WithQuery(tt.query) - if tt.exp != nil { - enginetest.TestPreparedQueryWithContext(t, ctx, e, harness, tt.query, tt.exp, nil, nil, false) - } - }) - } - } +func TestSystemTableIndexes(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunSystemTableIndexesTests(t, harness) +} + +func TestSystemTableIndexesPrepared(t *testing.T) { + harness := newDoltEnginetestHarness(t) + RunSystemTableIndexesTestsPrepared(t, harness) } func TestSystemTableFunctionIndexes(t *testing.T) { - harness := newDoltHarness(t) - harness.Setup(setup.MydbData) - for _, test := range SystemTableFunctionIndexTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScript(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunSystemTableFunctionIndexesTests(t, harness) } func TestSystemTableFunctionIndexesPrepared(t *testing.T) { - harness := newDoltHarness(t) - harness.Setup(setup.MydbData) - for _, test := range SystemTableFunctionIndexTests { - harness.engine = nil - t.Run(test.Name, func(t *testing.T) { - enginetest.TestScriptPrepared(t, harness, test) - }) - } + harness := newDoltEnginetestHarness(t) + RunSystemTableFunctionIndexesTestsPrepared(t, harness) } func TestReadOnlyDatabases(t *testing.T) { @@ -2694,12 +1603,8 @@ func TestAddDropPks(t *testing.T) { } func TestAddAutoIncrementColumn(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - - for _, script := range queries.AlterTableAddAutoIncrementScripts { - enginetest.TestScript(t, h, script) - } + h := newDoltEnginetestHarness(t) + RunAddAutoIncrementColumnTests(t, h) } func TestNullRanges(t *testing.T) { @@ -2733,35 +1638,23 @@ func TestTypesOverWire(t *testing.T) { } func TestDoltCherryPick(t *testing.T) { - for _, script := range DoltCherryPickTests { - harness := newDoltHarness(t) - enginetest.TestScript(t, harness, script) - harness.Close() - } + harness := newDoltEnginetestHarness(t) + RunDoltCherryPickTests(t, harness) } func TestDoltCherryPickPrepared(t *testing.T) { - for _, script := range DoltCherryPickTests { - harness := newDoltHarness(t) - enginetest.TestScriptPrepared(t, harness, script) - harness.Close() - } + harness := newDoltEnginetestHarness(t) + RunDoltCherryPickTestsPrepared(t, harness) } func TestDoltCommit(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - for _, script := range DoltCommitTests { - enginetest.TestScript(t, harness, script) - } + harness := newDoltEnginetestHarness(t) + RunDoltCommitTests(t, harness) } func TestDoltCommitPrepared(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - for _, script := range DoltCommitTests { - enginetest.TestScriptPrepared(t, harness, script) - } + harness := newDoltEnginetestHarness(t) + RunDoltCommitTestsPrepared(t, harness) } func TestQueriesPrepared(t *testing.T) { @@ -2771,42 +1664,20 @@ func TestQueriesPrepared(t *testing.T) { } func TestStatsHistograms(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - h.configureStats = true - for _, script := range DoltHistogramTests { - h.engine = nil - enginetest.TestScript(t, h, script) - } + h := newDoltEnginetestHarness(t) + RunStatsHistogramTests(t, h) } // TestStatsIO force a provider reload in-between setup and assertions that // forces a round trip of the statistics table before inspecting values. func TestStatsIO(t *testing.T) { - h := newDoltHarness(t) - h.configureStats = true - defer h.Close() - for _, script := range append(DoltStatsIOTests, DoltHistogramTests...) { - h.engine = nil - func() { - e := mustNewEngine(t, h) - if enginetest.IsServerEngine(e) { - return - } - defer e.Close() - TestProviderReloadScriptWithEngine(t, e, h, script) - }() - } + h := newDoltEnginetestHarness(t) + RunStatsIOTests(t, h) } func TestJoinStats(t *testing.T) { - // these are sensitive to cardinality estimates, - // particularly the join-filter tests that trade-off - // smallest table first vs smallest join first - h := newDoltHarness(t) - defer h.Close() - h.configureStats = true - enginetest.TestJoinStats(t, h) + h := newDoltEnginetestHarness(t) + RunJoinStatsTests(t, h) } func TestStatisticIndexes(t *testing.T) { @@ -2824,30 +1695,13 @@ func TestSpatialQueriesPrepared(t *testing.T) { } func TestPreparedStatistics(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - h.configureStats = true - for _, script := range DoltHistogramTests { - h.engine = nil - enginetest.TestScriptPrepared(t, h, script) - } + h := newDoltEnginetestHarness(t) + RunPreparedStatisticsTests(t, h) } func TestVersionedQueriesPrepared(t *testing.T) { - h := newDoltHarness(t) - defer h.Close() - h.Setup(setup.MydbData, []setup.SetupScript{VersionedQuerySetup, VersionedQueryViews}) - - e, err := h.NewEngine(t) - require.NoError(t, err) - - for _, tt := range queries.VersionedQueries { - enginetest.TestPreparedQueryWithEngine(t, h, e, tt) - } - - for _, tt := range queries.VersionedScripts { - enginetest.TestScriptWithEnginePrepared(t, e, h, tt) - } + h := newDoltEnginetestHarness(t) + RunVersionedQueriesPreparedTests(t, h) } func TestInfoSchemaPrepared(t *testing.T) { @@ -2936,7 +1790,7 @@ func TestInsertIgnoreScriptsPrepared(t *testing.T) { func TestInsertErrorScriptsPrepared(t *testing.T) { skipPreparedTests(t) - h := newDoltHarness(t) + h := newDoltEnginetestHarness(t) defer h.Close() h = h.WithSkippedQueries([]string{ "create table bad (vb varbinary(65535))", @@ -3017,222 +1871,18 @@ func TestDatabaseCollationWire(t *testing.T) { } func TestAddDropPrimaryKeys(t *testing.T) { - t.Run("adding and dropping primary keys does not result in duplicate NOT NULL constraints", func(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - addPkScript := queries.ScriptTest{ - Name: "add primary keys", - SetUpScript: []string{ - "create table test (id int not null, c1 int);", - "create index c1_idx on test(c1)", - "insert into test values (1,1),(2,2)", - "ALTER TABLE test ADD PRIMARY KEY(id)", - "ALTER TABLE test DROP PRIMARY KEY", - "ALTER TABLE test ADD PRIMARY KEY(id)", - "ALTER TABLE test DROP PRIMARY KEY", - "ALTER TABLE test ADD PRIMARY KEY(id)", - "ALTER TABLE test DROP PRIMARY KEY", - "ALTER TABLE test ADD PRIMARY KEY(id)", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "show create table test", - Expected: []sql.Row{ - {"test", "CREATE TABLE `test` (\n" + - " `id` int NOT NULL,\n" + - " `c1` int,\n" + - " PRIMARY KEY (`id`),\n" + - " KEY `c1_idx` (`c1`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, - }, - }, - }, - } - - enginetest.TestScript(t, harness, addPkScript) - - // make sure there is only one NOT NULL constraint after all those mutations - ctx := sql.NewContext(context.Background(), sql.WithSession(harness.session)) - ws, err := harness.session.WorkingSet(ctx, "mydb") - require.NoError(t, err) - - table, ok, err := ws.WorkingRoot().GetTable(ctx, doltdb.TableName{Name: "test"}) - require.NoError(t, err) - require.True(t, ok) - - sch, err := table.GetSchema(ctx) - for _, col := range sch.GetAllCols().GetColumns() { - count := 0 - for _, cc := range col.Constraints { - if cc.GetConstraintType() == schema.NotNullConstraintType { - count++ - } - } - require.Less(t, count, 2) - } - }) - - t.Run("Add primary key to table with index", func(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - script := queries.ScriptTest{ - Name: "add primary keys to table with index", - SetUpScript: []string{ - "create table test (id int not null, c1 int);", - "create index c1_idx on test(c1)", - "insert into test values (1,1),(2,2)", - "ALTER TABLE test ADD constraint test_check CHECK (c1 > 0)", - "ALTER TABLE test ADD PRIMARY KEY(id)", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "show create table test", - Expected: []sql.Row{ - {"test", "CREATE TABLE `test` (\n" + - " `id` int NOT NULL,\n" + - " `c1` int,\n" + - " PRIMARY KEY (`id`),\n" + - " KEY `c1_idx` (`c1`),\n" + - " CONSTRAINT `test_check` CHECK ((`c1` > 0))\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, - }, - }, - { - Query: "select * from test order by id", - Expected: []sql.Row{ - {1, 1}, - {2, 2}, - }, - }, - }, - } - enginetest.TestScript(t, harness, script) - - ctx := sql.NewContext(context.Background(), sql.WithSession(harness.session)) - ws, err := harness.session.WorkingSet(ctx, "mydb") - require.NoError(t, err) - - table, ok, err := ws.WorkingRoot().GetTable(ctx, doltdb.TableName{Name: "test"}) - require.NoError(t, err) - require.True(t, ok) - - // Assert the new index map is not empty - newRows, err := table.GetIndexRowData(ctx, "c1_idx") - require.NoError(t, err) - empty, err := newRows.Empty() - require.NoError(t, err) - assert.False(t, empty) - count, err := newRows.Count() - require.NoError(t, err) - assert.Equal(t, count, uint64(2)) - }) - - t.Run("Add primary key when one more cells contain NULL", func(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - script := queries.ScriptTest{ - Name: "Add primary key when one more cells contain NULL", - SetUpScript: []string{ - "create table test (id int not null, c1 int);", - "create index c1_idx on test(c1)", - "insert into test values (1,1),(2,2)", - "ALTER TABLE test ADD PRIMARY KEY (c1)", - "ALTER TABLE test ADD COLUMN (c2 INT NULL)", - "ALTER TABLE test DROP PRIMARY KEY", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "ALTER TABLE test ADD PRIMARY KEY (id, c1, c2)", - ExpectedErr: sql.ErrInsertIntoNonNullableProvidedNull, - }, - }, - } - enginetest.TestScript(t, harness, script) - }) - - t.Run("Drop primary key from table with index", func(t *testing.T) { - harness := newDoltHarness(t) - defer harness.Close() - script := queries.ScriptTest{ - Name: "Drop primary key from table with index", - SetUpScript: []string{ - "create table test (id int not null primary key, c1 int);", - "create index c1_idx on test(c1)", - "insert into test values (1,1),(2,2)", - "ALTER TABLE test DROP PRIMARY KEY", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "show create table test", - Expected: []sql.Row{ - {"test", "CREATE TABLE `test` (\n" + - " `id` int NOT NULL,\n" + - " `c1` int,\n" + - " KEY `c1_idx` (`c1`)\n" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, - }, - }, - { - Query: "select * from test order by id", - Expected: []sql.Row{ - {1, 1}, - {2, 2}, - }, - }, - }, - } - - enginetest.TestScript(t, harness, script) - - ctx := sql.NewContext(context.Background(), sql.WithSession(harness.session)) - ws, err := harness.session.WorkingSet(ctx, "mydb") - require.NoError(t, err) - - table, ok, err := ws.WorkingRoot().GetTable(ctx, doltdb.TableName{Name: "test"}) - require.NoError(t, err) - require.True(t, ok) - - // Assert the index map is not empty - newIdx, err := table.GetIndexRowData(ctx, "c1_idx") - assert.NoError(t, err) - empty, err := newIdx.Empty() - require.NoError(t, err) - assert.False(t, empty) - count, err := newIdx.Count() - require.NoError(t, err) - assert.Equal(t, count, uint64(2)) - }) + harness := newDoltEnginetestHarness(t) + RunAddDropPrimaryKeysTests(t, harness) } func TestDoltVerifyConstraints(t *testing.T) { - for _, script := range DoltVerifyConstraintsTestScripts { - func() { - harness := newDoltHarness(t) - defer harness.Close() - enginetest.TestScript(t, harness, script) - }() - } + harness := newDoltEnginetestHarness(t) + RunDoltVerifyConstraintsTests(t, harness) } func TestDoltStorageFormat(t *testing.T) { - var expectedFormatString string - if types.IsFormat_DOLT(types.Format_Default) { - expectedFormatString = "NEW ( __DOLT__ )" - } else { - expectedFormatString = fmt.Sprintf("OLD ( %s )", types.Format_Default.VersionString()) - } - script := queries.ScriptTest{ - Name: "dolt storage format function works", - Assertions: []queries.ScriptTestAssertion{ - { - Query: "select dolt_storage_format()", - Expected: []sql.Row{{expectedFormatString}}, - }, - }, - } - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, script) + h := newDoltEnginetestHarness(t) + RunDoltStorageFormatTests(t, h) } func TestDoltStorageFormatPrepared(t *testing.T) { @@ -3248,49 +1898,15 @@ func TestDoltStorageFormatPrepared(t *testing.T) { } func TestThreeWayMergeWithSchemaChangeScripts(t *testing.T) { - skipOldFormat(t) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsBasicCases, "basic cases", false) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsForDataConflicts, "data conflicts", false) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsCollations, "collation changes", false) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsConstraints, "constraint changes", false) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsSchemaConflicts, "schema conflicts", false) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsGeneratedColumns, "generated columns", false) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsForJsonConflicts, "json merge", false) - - // Run non-symmetric schema merge tests in just one direction - t.Run("type changes", func(t *testing.T) { - for _, script := range SchemaChangeTestsTypeChanges { - // run in a func() so we can cleanly defer closing the harness - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScript(t, h, convertMergeScriptTest(script, false)) - }() - } - }) + h := newDoltEnginetestHarness(t) + + RunThreeWayMergeWithSchemaChangeScripts(t, h) } func TestThreeWayMergeWithSchemaChangeScriptsPrepared(t *testing.T) { - skipOldFormat(t) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsBasicCases, "basic cases", true) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsForDataConflicts, "data conflicts", true) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsCollations, "collation changes", true) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsConstraints, "constraint changes", true) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsSchemaConflicts, "schema conflicts", true) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsGeneratedColumns, "generated columns", true) - runMergeScriptTestsInBothDirections(t, SchemaChangeTestsForJsonConflicts, "json merge", true) - - // Run non-symmetric schema merge tests in just one direction - t.Run("type changes", func(t *testing.T) { - for _, script := range SchemaChangeTestsTypeChanges { - // run in a func() so we can cleanly defer closing the harness - func() { - h := newDoltHarness(t) - defer h.Close() - enginetest.TestScriptPrepared(t, h, convertMergeScriptTest(script, false)) - }() - } - }) + h := newDoltEnginetestHarness(t) + + RunThreeWayMergeWithSchemaChangeScriptsPrepared(t, h) } // If CREATE DATABASE has an error within the DatabaseProvider, it should not @@ -3431,64 +2047,3 @@ func TestStatsAutoRefreshConcurrency(t *testing.T) { wg.Wait() } } - -// runMergeScriptTestsInBothDirections creates a new test run, named |name|, and runs the specified merge |tests| -// in both directions (right to left merge, and left to right merge). If -// |runAsPrepared| is true then the test scripts will be run using the prepared -// statement test code. -func runMergeScriptTestsInBothDirections(t *testing.T, tests []MergeScriptTest, name string, runAsPrepared bool) { - t.Run(name, func(t *testing.T) { - t.Run("right to left merges", func(t *testing.T) { - for _, script := range tests { - // run in a func() so we can cleanly defer closing the harness - func() { - h := newDoltHarness(t) - defer h.Close() - if runAsPrepared { - enginetest.TestScriptPrepared(t, h, convertMergeScriptTest(script, false)) - } else { - enginetest.TestScript(t, h, convertMergeScriptTest(script, false)) - } - }() - } - }) - t.Run("left to right merges", func(t *testing.T) { - for _, script := range tests { - func() { - h := newDoltHarness(t) - defer h.Close() - if runAsPrepared { - enginetest.TestScriptPrepared(t, h, convertMergeScriptTest(script, true)) - } else { - enginetest.TestScript(t, h, convertMergeScriptTest(script, true)) - } - }() - } - }) - }) -} - -var newFormatSkippedScripts = []string{ - // Different query plans - "Partial indexes are used and return the expected result", - "Multiple indexes on the same columns in a different order", -} - -func skipOldFormat(t *testing.T) { - if !types.IsFormat_DOLT(types.Format_Default) { - t.Skip() - } -} - -func skipPreparedTests(t *testing.T) { - if skipPrepared { - t.Skip("skip prepared") - } -} - -func newSessionBuilder(harness *DoltHarness) server.SessionBuilder { - return func(ctx context.Context, conn *mysql.Conn, host string) (sql.Session, error) { - newCtx := harness.NewSession() - return newCtx.Session, nil - } -} diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_engine_tests.go b/go/libraries/doltcore/sqle/enginetest/dolt_engine_tests.go new file mode 100755 index 00000000000..1d6cfeba360 --- /dev/null +++ b/go/libraries/doltcore/sqle/enginetest/dolt_engine_tests.go @@ -0,0 +1,1945 @@ +// Copyright 2024 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetest + +import ( + "context" + "fmt" + "io" + "os" + "testing" + "time" + + "github.com/dolthub/go-mysql-server/enginetest" + "github.com/dolthub/go-mysql-server/enginetest/queries" + "github.com/dolthub/go-mysql-server/enginetest/scriptgen/setup" + "github.com/dolthub/go-mysql-server/server" + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/memo" + "github.com/dolthub/go-mysql-server/sql/plan" + "github.com/dolthub/go-mysql-server/sql/transform" + gmstypes "github.com/dolthub/go-mysql-server/sql/types" + "github.com/dolthub/vitess/go/mysql" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" + "github.com/dolthub/dolt/go/libraries/doltcore/schema" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" + "github.com/dolthub/dolt/go/store/datas" + "github.com/dolthub/dolt/go/store/types" +) + +const skipPreparedFlag = "DOLT_SKIP_PREPARED_ENGINETESTS" + +var skipPrepared bool + +func init() { + sqle.MinRowsPerPartition = 8 + sqle.MaxRowsPerPartition = 1024 + + if v := os.Getenv(skipPreparedFlag); v != "" { + skipPrepared = true + } +} + +func RunSchemaOverridesTest(t *testing.T, harness DoltEnginetestHarness) { + tcc := &testCommitClock{} + cleanup := installTestCommitClock(tcc) + defer cleanup() + + for _, script := range SchemaOverrideTests { + sql.RunWithNowFunc(tcc.Now, func() error { + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + + engine, err := harness.NewEngine(t) + if err != nil { + panic(err) + } + + enginetest.TestScriptWithEngine(t, engine, harness, script) + return nil + }) + } +} + +type testCommitClock struct { + unixNano int64 +} + +func (tcc *testCommitClock) Now() time.Time { + now := time.Unix(0, tcc.unixNano) + tcc.unixNano += int64(time.Hour) + return now +} + +func installTestCommitClock(tcc *testCommitClock) func() { + oldNowFunc := datas.CommitterDate + oldCommitLoc := datas.CommitLoc + datas.CommitterDate = tcc.Now + datas.CommitLoc = time.UTC + return func() { + datas.CommitterDate = oldNowFunc + datas.CommitLoc = oldCommitLoc + } +} + +func RunAutoIncrementTrackerLockModeTest(t *testing.T, harness DoltEnginetestHarness) { + for _, lockMode := range []int64{0, 1, 2} { + t.Run(fmt.Sprintf("lock mode %d", lockMode), func(t *testing.T) { + testAutoIncrementTrackerWithLockMode(t, harness, lockMode) + }) + } +} + +// testAutoIncrementTrackerWithLockMode tests that interleaved inserts don't cause deadlocks, regardless of the value of innodb_autoinc_lock_mode. +// In a real use case, these interleaved operations would be happening in different sessions on different threads. +// In order to make the test behave predictably, we manually interleave the two iterators. +func testAutoIncrementTrackerWithLockMode(t *testing.T, harness DoltEnginetestHarness, lockMode int64) { + err := sql.SystemVariables.AssignValues(map[string]interface{}{"innodb_autoinc_lock_mode": lockMode}) + require.NoError(t, err) + + setupScripts := []setup.SetupScript{[]string{ + "CREATE TABLE test1 (pk int NOT NULL PRIMARY KEY AUTO_INCREMENT,c0 int,index t1_c_index (c0));", + "CREATE TABLE test2 (pk int NOT NULL PRIMARY KEY AUTO_INCREMENT,c0 int,index t2_c_index (c0));", + "CREATE TABLE timestamps (pk int NOT NULL PRIMARY KEY AUTO_INCREMENT, t int);", + "CREATE TRIGGER t1 AFTER INSERT ON test1 FOR EACH ROW INSERT INTO timestamps VALUES (0, 1);", + "CREATE TRIGGER t2 AFTER INSERT ON test2 FOR EACH ROW INSERT INTO timestamps VALUES (0, 2);", + "CREATE VIEW bin AS SELECT 0 AS v UNION ALL SELECT 1;", + "CREATE VIEW sequence5bit AS SELECT b1.v + 2*b2.v + 4*b3.v + 8*b4.v + 16*b5.v AS v from bin b1, bin b2, bin b3, bin b4, bin b5;", + }} + + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData, setupScripts) + e := mustNewEngine(t, harness) + + defer e.Close() + ctx := enginetest.NewContext(harness) + + // Confirm that the system variable was correctly set. + _, iter, err := e.Query(ctx, "select @@innodb_autoinc_lock_mode") + require.NoError(t, err) + rows, err := sql.RowIterToRows(ctx, iter) + require.NoError(t, err) + assert.Equal(t, rows, []sql.Row{{lockMode}}) + + // Ordinarily QueryEngine.query manages transactions. + // Since we can't use that for this test, we manually start a new transaction. + ts := ctx.Session.(sql.TransactionSession) + tx, err := ts.StartTransaction(ctx, sql.ReadWrite) + require.NoError(t, err) + ctx.SetTransaction(tx) + + getTriggerIter := func(query string) sql.RowIter { + root, err := e.AnalyzeQuery(ctx, query) + require.NoError(t, err) + + var triggerNode *plan.TriggerExecutor + transform.Node(root, func(n sql.Node) (sql.Node, transform.TreeIdentity, error) { + if triggerNode != nil { + return n, transform.SameTree, nil + } + if t, ok := n.(*plan.TriggerExecutor); ok { + triggerNode = t + } + return n, transform.NewTree, nil + }) + iter, err := e.EngineAnalyzer().ExecBuilder.Build(ctx, triggerNode, nil) + require.NoError(t, err) + return iter + } + + iter1 := getTriggerIter("INSERT INTO test1 (c0) select v from sequence5bit;") + iter2 := getTriggerIter("INSERT INTO test2 (c0) select v from sequence5bit;") + + // Alternate the iterators until they're exhausted. + var err1 error + var err2 error + for err1 != io.EOF || err2 != io.EOF { + if err1 != io.EOF { + var row1 sql.Row + require.NoError(t, err1) + row1, err1 = iter1.Next(ctx) + _ = row1 + } + if err2 != io.EOF { + require.NoError(t, err2) + _, err2 = iter2.Next(ctx) + } + } + err = iter1.Close(ctx) + require.NoError(t, err) + err = iter2.Close(ctx) + require.NoError(t, err) + + dsess.DSessFromSess(ctx.Session).CommitTransaction(ctx, ctx.GetTransaction()) + + // Verify that the inserts are seen by the engine. + { + _, iter, err := e.Query(ctx, "select count(*) from timestamps") + require.NoError(t, err) + rows, err := sql.RowIterToRows(ctx, iter) + require.NoError(t, err) + assert.Equal(t, rows, []sql.Row{{int64(64)}}) + } + + // Verify that the insert operations are actually interleaved by inspecting the order that values were added to `timestamps` + { + _, iter, err := e.Query(ctx, "select (select min(pk) from timestamps where t = 1) < (select max(pk) from timestamps where t = 2)") + require.NoError(t, err) + rows, err := sql.RowIterToRows(ctx, iter) + require.NoError(t, err) + assert.Equal(t, rows, []sql.Row{{true}}) + } + + { + _, iter, err := e.Query(ctx, "select (select min(pk) from timestamps where t = 2) < (select max(pk) from timestamps where t = 1)") + require.NoError(t, err) + rows, err := sql.RowIterToRows(ctx, iter) + require.NoError(t, err) + assert.Equal(t, rows, []sql.Row{{true}}) + } +} + +func RunVersionedQueriesTest(t *testing.T, h DoltEnginetestHarness) { + h.Setup(setup.MydbData, []setup.SetupScript{VersionedQuerySetup, VersionedQueryViews}) + + e, err := h.NewEngine(t) + require.NoError(t, err) + + for _, tt := range queries.VersionedQueries { + enginetest.TestQueryWithEngine(t, h, e, tt) + } + + for _, tt := range queries.VersionedScripts { + enginetest.TestScriptWithEngine(t, e, h, tt) + } +} + +func RunQueryTestPlans(t *testing.T, harness DoltEnginetestHarness) { + // Dolt supports partial keys, so the index matched is different for some plans + // TODO: Fix these differences by implementing partial key matching in the memory tables, or the engine itself + skipped := []string{ + "SELECT pk,pk1,pk2 FROM one_pk LEFT JOIN two_pk ON pk=pk1", + "SELECT pk,pk1,pk2 FROM one_pk JOIN two_pk ON pk=pk1", + "SELECT one_pk.c5,pk1,pk2 FROM one_pk JOIN two_pk ON pk=pk1 ORDER BY 1,2,3", + "SELECT opk.c5,pk1,pk2 FROM one_pk opk JOIN two_pk tpk ON opk.pk=tpk.pk1 ORDER BY 1,2,3", + "SELECT opk.c5,pk1,pk2 FROM one_pk opk JOIN two_pk tpk ON pk=pk1 ORDER BY 1,2,3", + "SELECT pk,pk1,pk2 FROM one_pk LEFT JOIN two_pk ON pk=pk1 ORDER BY 1,2,3", + "SELECT pk,pk1,pk2 FROM one_pk t1, two_pk t2 WHERE pk=1 AND pk2=1 AND pk1=1 ORDER BY 1,2", + } + // Parallelism introduces Exchange nodes into the query plans, so disable. + // TODO: exchange nodes should really only be part of the explain plan under certain debug settings + harness = harness.NewHarness(t).WithSkippedQueries(skipped).WithConfigureStats(true) + if !types.IsFormat_DOLT(types.Format_Default) { + // only new format supports reverse IndexTableAccess + reverseIndexSkip := []string{ + "SELECT * FROM one_pk ORDER BY pk", + "SELECT * FROM two_pk ORDER BY pk1, pk2", + "SELECT * FROM two_pk ORDER BY pk1", + "SELECT pk1 AS one, pk2 AS two FROM two_pk ORDER BY pk1, pk2", + "SELECT pk1 AS one, pk2 AS two FROM two_pk ORDER BY one, two", + "SELECT i FROM (SELECT i FROM mytable ORDER BY i DESC LIMIT 1) sq WHERE i = 3", + "SELECT i FROM (SELECT i FROM (SELECT i FROM mytable ORDER BY DES LIMIT 1) sql1)sql2 WHERE i = 3", + "SELECT s,i FROM mytable order by i DESC", + "SELECT s,i FROM mytable as a order by i DESC", + "SELECT pk1, pk2 FROM two_pk order by pk1 asc, pk2 asc", + "SELECT pk1, pk2 FROM two_pk order by pk1 desc, pk2 desc", + "SELECT i FROM (SELECT i FROM (SELECT i FROM mytable ORDER BY i DESC LIMIT 1) sq1) sq2 WHERE i = 3", + } + harness = harness.WithSkippedQueries(reverseIndexSkip) + } + + defer harness.Close() + enginetest.TestQueryPlans(t, harness, queries.PlanTests) +} + +func RunDoltDiffQueryPlansTest(t *testing.T, harness DoltEnginetestHarness) { + defer harness.Close() + harness.Setup(setup.SimpleSetup...) + e, err := harness.NewEngine(t) + require.NoError(t, err) + defer e.Close() + + for _, tt := range append(DoltDiffPlanTests, DoltCommitPlanTests...) { + enginetest.TestQueryPlanWithName(t, tt.Query, harness, e, tt.Query, tt.ExpectedPlan, sql.DescribeOptions{}) + } +} + +func RunBranchPlanTests(t *testing.T, harness DoltEnginetestHarness) { + for _, script := range BranchPlanTests { + t.Run(script.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + + e := mustNewEngine(t, harness) + defer e.Close() + + for _, statement := range script.SetUpScript { + ctx := enginetest.NewContext(harness).WithQuery(statement) + enginetest.RunQueryWithContext(t, e, harness, ctx, statement) + } + for _, tt := range script.Queries { + t.Run(tt.Query, func(t *testing.T) { + TestIndexedAccess(t, e, harness, tt.Query, tt.Index) + }) + } + }) + } +} + +func RunInfoSchemaTests(t *testing.T, h DoltEnginetestHarness) { + defer h.Close() + enginetest.TestInfoSchema(t, h) + + for _, script := range DoltInfoSchemaScripts { + func() { + h = h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunInsertIntoErrorsTest(t *testing.T, h DoltEnginetestHarness) { + h = h.WithSkippedQueries([]string{ + "create table bad (vb varbinary(65535))", + "insert into bad values (repeat('0', 65536))", + }) + defer h.Close() + enginetest.TestInsertIntoErrors(t, h) +} + +func RunGeneratedColumnTests(t *testing.T, harness DoltEnginetestHarness) { + defer harness.Close() + enginetest.TestGeneratedColumns(t, + // virtual indexes are failing for certain lookups on this test + harness.WithSkippedQueries([]string{"create table t (pk int primary key, col1 int as (pk + 1));"})) + + for _, script := range GeneratedColumnMergeTestScripts { + func() { + h := harness.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunBranchDdlTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DdlBranchTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunBranchDdlTestPrepared(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DdlBranchTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} + +func RunIndexPrefixTest(t *testing.T, harness DoltEnginetestHarness) { + defer harness.Close() + enginetest.TestIndexPrefix(t, harness) + for _, script := range DoltIndexPrefixScripts { + enginetest.TestScript(t, harness, script) + } +} + +func RunBigBlobsTest(t *testing.T, h DoltEnginetestHarness) { + defer h.Close() + h.Setup(setup.MydbData, setup.BlobData) + for _, tt := range BigBlobQueries { + enginetest.RunWriteQueryTest(t, h, tt) + } +} + +func RunDropEngineTest(t *testing.T, h DoltEnginetestHarness) { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, queries.ScriptTest{ + Name: "Drop database engine tests for Dolt only", + SetUpScript: []string{ + "CREATE DATABASE Test1db", + "CREATE DATABASE TEST2db", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "DROP DATABASE TeSt2DB", + Expected: []sql.Row{{gmstypes.OkResult{RowsAffected: 1}}}, + }, + { + Query: "USE test2db", + ExpectedErr: sql.ErrDatabaseNotFound, + }, + { + Query: "USE TEST1DB", + Expected: []sql.Row{}, + }, + { + Query: "DROP DATABASE IF EXISTS test1DB", + Expected: []sql.Row{{gmstypes.OkResult{RowsAffected: 1}}}, + }, + { + Query: "USE Test1db", + ExpectedErr: sql.ErrDatabaseNotFound, + }, + }, + }) + }() + + t.Skip("Dolt doesn't yet support dropping the primary database, which these tests do") + h = h.NewHarness(t) + defer h.Close() + enginetest.TestDropDatabase(t, h) +} + +func RunForeignKeyBranchesTest(t *testing.T, h DoltEnginetestHarness) { + setupPrefix := []string{ + "call dolt_branch('b1')", + "use mydb/b1", + } + assertionsPrefix := []queries.ScriptTestAssertion{ + { + Query: "use mydb/b1", + SkipResultsCheck: true, + }, + } + for _, script := range queries.ForeignKeyTests { + // New harness for every script because we create branches + h := h.NewHarness(t) + h.Setup(setup.MydbData, setup.Parent_childData) + modifiedScript := script + modifiedScript.SetUpScript = append(setupPrefix, modifiedScript.SetUpScript...) + modifiedScript.Assertions = append(assertionsPrefix, modifiedScript.Assertions...) + enginetest.TestScript(t, h, modifiedScript) + } + + for _, script := range ForeignKeyBranchTests { + // New harness for every script because we create branches + h := h.NewHarness(t) + h.Setup(setup.MydbData, setup.Parent_childData) + enginetest.TestScript(t, h, script) + } +} + +func RunForeignKeyBranchesPreparedTest(t *testing.T, h DoltEnginetestHarness) { + setupPrefix := []string{ + "call dolt_branch('b1')", + "use mydb/b1", + } + assertionsPrefix := []queries.ScriptTestAssertion{ + { + Query: "use mydb/b1", + SkipResultsCheck: true, + }, + } + for _, script := range queries.ForeignKeyTests { + // New harness for every script because we create branches + h := h.NewHarness(t) + h.Setup(setup.MydbData, setup.Parent_childData) + modifiedScript := script + modifiedScript.SetUpScript = append(setupPrefix, modifiedScript.SetUpScript...) + modifiedScript.Assertions = append(assertionsPrefix, modifiedScript.Assertions...) + enginetest.TestScriptPrepared(t, h, modifiedScript) + } + + for _, script := range ForeignKeyBranchTests { + // New harness for every script because we create branches + h := h.NewHarness(t) + h.Setup(setup.MydbData, setup.Parent_childData) + enginetest.TestScriptPrepared(t, h, script) + } +} + +func RunBranchViewsTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range ViewBranchTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunBranchViewsPreparedTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range ViewBranchTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} + +func RunVersionedViewsTest(t *testing.T, h DoltEnginetestHarness) { + defer h.Close() + h.Setup(setup.MydbData, []setup.SetupScript{VersionedQuerySetup, VersionedQueryViews}) + + e, err := h.NewEngine(t) + require.NoError(t, err) + + for _, testCase := range queries.VersionedViewTests { + t.Run(testCase.Query, func(t *testing.T) { + ctx := enginetest.NewContext(h) + enginetest.TestQueryWithContext(t, ctx, e, h, testCase.Query, testCase.Expected, testCase.ExpectedColumns, nil) + }) + } +} + +func RunVariableTest(t *testing.T, h DoltEnginetestHarness) { + defer h.Close() + enginetest.TestVariables(t, h) + for _, script := range DoltSystemVariables { + enginetest.TestScript(t, h, script) + } +} + +func RunStoredProceduresTest(t *testing.T, h DoltEnginetestHarness) { + tests := make([]queries.ScriptTest, 0, len(queries.ProcedureLogicTests)) + for _, test := range queries.ProcedureLogicTests { + // TODO: this passes locally but SOMETIMES fails tests on GitHub, no clue why + if test.Name != "ITERATE and LEAVE loops" { + tests = append(tests, test) + } + } + queries.ProcedureLogicTests = tests + + defer h.Close() + enginetest.TestStoredProcedures(t, h) +} + +func RunDoltStoredProceduresTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltProcedureTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltStoredProceduresPreparedTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltProcedureTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} + +func RunCallAsOfTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltCallAsOf { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunLargeJsonObjectsTest(t *testing.T, harness DoltEnginetestHarness) { + SkipByDefaultInCI(t) + defer harness.Close() + for _, script := range LargeJsonObjectScriptTests { + enginetest.TestScript(t, harness, script) + } +} + +func RunTransactionTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range queries.TransactionTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestTransactionScript(t, h, script) + }() + } + for _, script := range DoltTransactionTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestTransactionScript(t, h, script) + }() + } + for _, script := range DoltStoredProcedureTransactionTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestTransactionScript(t, h, script) + }() + } + for _, script := range DoltConflictHandlingTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestTransactionScript(t, h, script) + }() + } + for _, script := range DoltConstraintViolationTransactionTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestTransactionScript(t, h, script) + }() + } +} + +func RunBranchTransactionTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range BranchIsolationTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestTransactionScript(t, h, script) + }() + } +} + +func RunMultiDbTransactionsTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range MultiDbTransactionTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } + + for _, script := range MultiDbSavepointTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestTransactionScript(t, h, script) + }() + } +} + +func RunMultiDbTransactionsPreparedTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range MultiDbTransactionTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} + +func RunDoltScriptsTest(t *testing.T, harness DoltEnginetestHarness) { + for _, script := range DoltScripts { + go func() { + harness := harness.NewHarness(t) + defer harness.Close() + enginetest.TestScript(t, harness, script) + }() + } +} + +func RunDoltTempTableScripts(t *testing.T, harness DoltEnginetestHarness) { + for _, script := range DoltTempTableScripts { + harness := harness.NewHarness(t) + enginetest.TestScript(t, harness, script) + harness.Close() + } +} + +func RunDoltRevisionDbScriptsTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltRevisionDbScripts { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } + + // Testing a commit-qualified database revision spec requires + // a little extra work to get the generated commit hash + h = h.NewHarness(t) + defer h.Close() + e, err := h.NewEngine(t) + require.NoError(t, err) + defer e.Close() + ctx := h.NewContext() + + setupScripts := []setup.SetupScript{ + {"create table t01 (pk int primary key, c1 int)"}, + {"call dolt_add('.');"}, + {"call dolt_commit('-am', 'creating table t01 on main');"}, + {"insert into t01 values (1, 1), (2, 2);"}, + {"call dolt_commit('-am', 'adding rows to table t01 on main');"}, + {"insert into t01 values (3, 3);"}, + {"call dolt_commit('-am', 'adding another row to table t01 on main');"}, + } + _, err = enginetest.RunSetupScripts(ctx, h.Engine(), setupScripts, true) + require.NoError(t, err) + + _, iter, err := h.Engine().Query(ctx, "select hashof('HEAD~2');") + require.NoError(t, err) + rows, err := sql.RowIterToRows(ctx, iter) + require.NoError(t, err) + assert.Equal(t, 1, len(rows)) + commithash := rows[0][0].(string) + + scriptTest := queries.ScriptTest{ + Name: "database revision specs: commit-qualified revision spec", + Assertions: []queries.ScriptTestAssertion{ + { + Query: "show databases;", + Expected: []sql.Row{{"mydb"}, {"information_schema"}, {"mysql"}}, + }, + { + Query: "use mydb/" + commithash, + Expected: []sql.Row{}, + }, + { + Query: "select active_branch();", + Expected: []sql.Row{ + {nil}, + }, + }, + { + Query: "select database();", + Expected: []sql.Row{{"mydb/" + commithash}}, + }, + { + Query: "show databases;", + Expected: []sql.Row{{"mydb"}, {"mydb/" + commithash}, {"information_schema"}, {"mysql"}}, + }, + { + Query: "select * from t01", + Expected: []sql.Row{}, + }, + { + Query: "call dolt_reset();", + ExpectedErrStr: "unable to reset HEAD in read-only databases", + }, + { + Query: "call dolt_checkout('main');", + Expected: []sql.Row{{0, "Switched to branch 'main'"}}, + }, + { + Query: "select database();", + Expected: []sql.Row{{"mydb"}}, + }, + { + Query: "select active_branch();", + Expected: []sql.Row{{"main"}}, + }, + { + Query: "use mydb;", + Expected: []sql.Row{}, + }, + { + Query: "select database();", + Expected: []sql.Row{{"mydb"}}, + }, + { + Query: "show databases;", + Expected: []sql.Row{{"mydb"}, {"information_schema"}, {"mysql"}}, + }, + }, + } + + enginetest.TestScript(t, h, scriptTest) +} + +func RunDoltRevisionDbScriptsPreparedTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltRevisionDbScripts { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} + +func RunDoltDdlScripts(t *testing.T, harness DoltEnginetestHarness) { + defer harness.Close() + harness.Setup() + + for _, script := range ModifyAndChangeColumnScripts { + e, err := harness.NewEngine(t) + require.NoError(t, err) + enginetest.TestScriptWithEngine(t, e, harness, script) + } + + for _, script := range ModifyColumnTypeScripts { + e, err := harness.NewEngine(t) + require.NoError(t, err) + enginetest.TestScriptWithEngine(t, e, harness, script) + } + + for _, script := range DropColumnScripts { + e, err := harness.NewEngine(t) + require.NoError(t, err) + enginetest.TestScriptWithEngine(t, e, harness, script) + } + if !types.IsFormat_DOLT(types.Format_Default) { + t.Skip("not fixing unique index on keyless tables for old format") + } + for _, script := range AddIndexScripts { + e, err := harness.NewEngine(t) + require.NoError(t, err) + enginetest.TestScriptWithEngine(t, e, harness, script) + } + + // TODO: these scripts should be general enough to go in GMS + for _, script := range AddDropPrimaryKeysScripts { + e, err := harness.NewEngine(t) + require.NoError(t, err) + enginetest.TestScriptWithEngine(t, e, harness, script) + } +} + +func RunShowCreateTableTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range ShowCreateTableScriptTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunShowCreateTablePreparedTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range ShowCreateTableScriptTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} + +func RunDoltMergeTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range MergeScripts { + // harness can't reset effectively when there are new commits / branches created, so use a new harness for + // each script + func() { + h := h.NewHarness(t) + defer h.Close() + h.Setup(setup.MydbData) + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltMergePreparedTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range MergeScripts { + // harness can't reset effectively when there are new commits / branches created, so use a new harness for + // each script + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} + +func RunDoltRebaseTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltRebaseScriptTests { + func() { + h := h.NewHarness(t) + defer h.Close() + h.SkipSetupCommit() + enginetest.TestScript(t, h, script) + }() + } + + testMultiSessionScriptTests(t, DoltRebaseMultiSessionScriptTests) +} + +func RunDoltRebasePreparedTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltRebaseScriptTests { + func() { + h := h.NewHarness(t) + defer h.Close() + h.SkipSetupCommit() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} + +func RunDoltRevertTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range RevertScripts { + // harness can't reset effectively. Use a new harness for each script + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltRevertPreparedTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range RevertScripts { + // harness can't reset effectively. Use a new harness for each script + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltAutoIncrementTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltAutoIncrementTests { + // doing commits on different branches is antagonistic to engine reuse, use a new engine on each script + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltAutoIncrementPreparedTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltAutoIncrementTests { + // doing commits on different branches is antagonistic to engine reuse, use a new engine on each script + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltConflictsTableNameTableTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltConflictTableNameTableTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } + + if types.IsFormat_DOLT(types.Format_Default) { + for _, script := range Dolt1ConflictTableNameTableTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } + } +} + +func RunKelyessDoltMergeCVsAndConflictsTests(t *testing.T, h DoltEnginetestHarness) { + if !types.IsFormat_DOLT(types.Format_Default) { + t.Skip() + } + for _, script := range KeylessMergeCVsAndConflictsScripts { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltMergeArtifacts(t *testing.T, h DoltEnginetestHarness) { + for _, script := range MergeArtifactsScripts { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } + for _, script := range SchemaConflictScripts { + h := h.NewHarness(t) + enginetest.TestScript(t, h, script) + h.Close() + } +} + +func RunDoltResetTest(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltReset { + // dolt versioning conflicts with reset harness -- use new harness every time + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltCheckoutTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltCheckoutScripts { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } + + h = h.NewHarness(t) + defer h.Close() + engine, err := h.NewEngine(t) + require.NoError(t, err) + readOnlyEngine, err := h.NewReadOnlyEngine(engine.EngineAnalyzer().Catalog.DbProvider) + require.NoError(t, err) + + for _, script := range DoltCheckoutReadOnlyScripts { + enginetest.TestScriptWithEngine(t, readOnlyEngine, h, script) + } +} + +func RunDoltCheckoutPreparedTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltCheckoutScripts { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } + + h = h.NewHarness(t) + defer h.Close() + engine, err := h.NewEngine(t) + require.NoError(t, err) + readOnlyEngine, err := h.NewReadOnlyEngine(engine.EngineAnalyzer().Catalog.DbProvider) + require.NoError(t, err) + + for _, script := range DoltCheckoutReadOnlyScripts { + enginetest.TestScriptWithEnginePrepared(t, readOnlyEngine, h, script) + } +} + +func RunDoltBranchTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltBranchScripts { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltTagTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltTagTestScripts { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltRemoteTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltRemoteTestScripts { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltUndropTests(t *testing.T, h DoltEnginetestHarness) { + h.UseLocalFileSystem() + defer h.Close() + for _, script := range DoltUndropTestScripts { + enginetest.TestScript(t, h, script) + } +} + +func RunHistorySystemTableTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range HistorySystemTableScriptTests { + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + t.Run(test.Name, func(t *testing.T) { + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunHistorySystemTableTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range HistorySystemTableScriptTests { + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + t.Run(test.Name, func(t *testing.T) { + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunUnscopedDiffSystemTableTests(t *testing.T, h DoltEnginetestHarness) { + for _, test := range UnscopedDiffSystemTableScriptTests { + t.Run(test.Name, func(t *testing.T) { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, test) + }) + } +} + +func RunUnscopedDiffSystemTableTestsPrepared(t *testing.T, h DoltEnginetestHarness) { + for _, test := range UnscopedDiffSystemTableScriptTests { + t.Run(test.Name, func(t *testing.T) { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScriptPrepared(t, h, test) + }) + } +} + +func RunColumnDiffSystemTableTests(t *testing.T, h DoltEnginetestHarness) { + if !types.IsFormat_DOLT(types.Format_Default) { + t.Skip("correct behavior of dolt_column_diff only guaranteed on new format") + } + for _, test := range ColumnDiffSystemTableScriptTests { + t.Run(test.Name, func(t *testing.T) { + enginetest.TestScript(t, h.NewHarness(t), test) + }) + } +} + +func RunColumnDiffSystemTableTestsPrepared(t *testing.T, h DoltEnginetestHarness) { + if !types.IsFormat_DOLT(types.Format_Default) { + t.Skip("correct behavior of dolt_column_diff only guaranteed on new format") + } + for _, test := range ColumnDiffSystemTableScriptTests { + t.Run(test.Name, func(t *testing.T) { + enginetest.TestScriptPrepared(t, h.NewHarness(t), test) + }) + } +} + +func RunStatBranchTests(t *testing.T, harness DoltEnginetestHarness) { + defer harness.Close() + for _, test := range StatBranchTests { + t.Run(test.Name, func(t *testing.T) { + // reset engine so provider statistics are clean + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + harness = harness.WithConfigureStats(true) + e := mustNewEngine(t, harness) + defer e.Close() + enginetest.TestScriptWithEngine(t, e, harness, test) + }) + } +} + +func mustNewEngine(t *testing.T, h enginetest.Harness) enginetest.QueryEngine { + e, err := h.NewEngine(t) + if err != nil { + require.NoError(t, err) + } + return e +} + +func RunStatsFunctionsTest(t *testing.T, harness DoltEnginetestHarness) { + defer harness.Close() + for _, test := range StatProcTests { + t.Run(test.Name, func(t *testing.T) { + // reset engine so provider statistics are clean + harness = harness.NewHarness(t).WithConfigureStats(true) + harness.Setup(setup.MydbData) + harness.SkipSetupCommit() + e := mustNewEngine(t, harness) + defer e.Close() + enginetest.TestScriptWithEngine(t, e, harness, test) + }) + } +} + +func RunDiffTableFunctionTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range DiffTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunDiffTableFunctionTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range DiffTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunDiffStatTableFunctionTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range DiffStatTableFunctionScriptTests { + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + t.Run(test.Name, func(t *testing.T) { + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunDiffStatTableFunctionTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range DiffStatTableFunctionScriptTests { + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + t.Run(test.Name, func(t *testing.T) { + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunDiffSummaryTableFunctionTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range DiffSummaryTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunDiffSummaryTableFunctionTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range DiffSummaryTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunDoltPatchTableFunctionTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range PatchTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunDoltPatchTableFunctionTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range PatchTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunLogTableFunctionTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range LogTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + harness.SkipSetupCommit() + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunLogTableFunctionTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range LogTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + harness.SkipSetupCommit() + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunCommitDiffSystemTableTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range CommitDiffSystemTableScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunCommitDiffSystemTableTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range CommitDiffSystemTableScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunDoltDiffSystemTableTests(t *testing.T, h DoltEnginetestHarness) { + if !types.IsFormat_DOLT(types.Format_Default) { + t.Skip("only new format support system table indexing") + } + + for _, test := range DiffSystemTableScriptTests { + t.Run(test.Name, func(t *testing.T) { + h = h.NewHarness(t) + defer h.Close() + h.Setup(setup.MydbData) + enginetest.TestScript(t, h, test) + }) + } + + if types.IsFormat_DOLT(types.Format_Default) { + for _, test := range Dolt1DiffSystemTableScripts { + func() { + h = h.NewHarness(t) + defer h.Close() + h.Setup(setup.MydbData) + enginetest.TestScript(t, h, test) + }() + } + } +} + +func RunDoltDiffSystemTableTestsPrepared(t *testing.T, h DoltEnginetestHarness) { + if !types.IsFormat_DOLT(types.Format_Default) { + t.Skip("only new format support system table indexing") + } + + for _, test := range DiffSystemTableScriptTests { + t.Run(test.Name, func(t *testing.T) { + h = h.NewHarness(t) + defer h.Close() + h.Setup(setup.MydbData) + enginetest.TestScriptPrepared(t, h, test) + }) + } + + if types.IsFormat_DOLT(types.Format_Default) { + for _, test := range Dolt1DiffSystemTableScripts { + func() { + h = h.NewHarness(t) + defer h.Close() + h.Setup(setup.MydbData) + enginetest.TestScriptPrepared(t, h, test) + }() + } + } +} + +func RunSchemaDiffTableFunctionTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range SchemaDiffTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunSchemaDiffTableFunctionTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range SchemaDiffTableFunctionScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunDoltDatabaseCollationDiffsTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range DoltDatabaseCollationScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunQueryDiffTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range QueryDiffTableScriptTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + harness.Setup(setup.MydbData) + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunSystemTableIndexesTests(t *testing.T, harness DoltEnginetestHarness) { + if !types.IsFormat_DOLT(types.Format_Default) { + t.Skip("only new format support system table indexing") + } + + for _, stt := range SystemTableIndexTests { + harness = harness.NewHarness(t).WithParallelism(2) + defer harness.Close() + harness.SkipSetupCommit() + e := mustNewEngine(t, harness) + defer e.Close() + e.EngineAnalyzer().Coster = memo.NewMergeBiasedCoster() + + ctx := enginetest.NewContext(harness) + for _, q := range stt.setup { + enginetest.RunQueryWithContext(t, e, harness, ctx, q) + } + + for i, c := range []string{"inner", "lookup", "hash", "merge"} { + e.EngineAnalyzer().Coster = biasedCosters[i] + for _, tt := range stt.queries { + if tt.query == "select count(*) from dolt_blame_xy" && c == "inner" { + // todo we either need join hints to work inside the blame view + // and force the window relation to be primary, or we need the + // blame view's timestamp columns to be specific enough to not + // overlap during testing. + t.Skip("the blame table is unstable as secondary table in join with exchange node") + } + t.Run(fmt.Sprintf("%s(%s): %s", stt.name, c, tt.query), func(t *testing.T) { + if tt.skip { + t.Skip() + } + + ctx = ctx.WithQuery(tt.query) + if tt.exp != nil { + enginetest.TestQueryWithContext(t, ctx, e, harness, tt.query, tt.exp, nil, nil) + } + }) + } + } + } +} + +var biasedCosters = []memo.Coster{ + memo.NewInnerBiasedCoster(), + memo.NewLookupBiasedCoster(), + memo.NewHashBiasedCoster(), + memo.NewMergeBiasedCoster(), +} + +func RunSystemTableIndexesTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + if !types.IsFormat_DOLT(types.Format_Default) { + t.Skip("only new format support system table indexing") + } + + for _, stt := range SystemTableIndexTests { + harness = harness.NewHarness(t).WithParallelism(2) + defer harness.Close() + harness.SkipSetupCommit() + e := mustNewEngine(t, harness) + defer e.Close() + + ctx := enginetest.NewContext(harness) + for _, q := range stt.setup { + enginetest.RunQueryWithContext(t, e, harness, ctx, q) + } + + for _, tt := range stt.queries { + t.Run(fmt.Sprintf("%s: %s", stt.name, tt.query), func(t *testing.T) { + if tt.skip { + t.Skip() + } + + ctx = ctx.WithQuery(tt.query) + if tt.exp != nil { + enginetest.TestPreparedQueryWithContext(t, ctx, e, harness, tt.query, tt.exp, nil, nil, false) + } + }) + } + } +} + +func RunSystemTableFunctionIndexesTests(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range SystemTableFunctionIndexTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + enginetest.TestScript(t, harness, test) + }) + } +} + +func RunSystemTableFunctionIndexesTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, test := range SystemTableFunctionIndexTests { + t.Run(test.Name, func(t *testing.T) { + harness = harness.NewHarness(t) + harness.Setup(setup.MydbData) + enginetest.TestScriptPrepared(t, harness, test) + }) + } +} + +func RunAddAutoIncrementColumnTests(t *testing.T, h DoltEnginetestHarness) { + defer h.Close() + for _, script := range queries.AlterTableAddAutoIncrementScripts { + enginetest.TestScript(t, h, script) + } +} + +func RunDoltCherryPickTests(t *testing.T, harness DoltEnginetestHarness) { + for _, script := range DoltCherryPickTests { + harness = harness.NewHarness(t) + enginetest.TestScript(t, harness, script) + harness.Close() + } +} + +func RunDoltCherryPickTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + for _, script := range DoltCherryPickTests { + harness = harness.NewHarness(t) + enginetest.TestScriptPrepared(t, harness, script) + harness.Close() + } +} + +func RunDoltCommitTests(t *testing.T, harness DoltEnginetestHarness) { + defer harness.Close() + for _, script := range DoltCommitTests { + enginetest.TestScript(t, harness, script) + } +} + +func RunDoltCommitTestsPrepared(t *testing.T, harness DoltEnginetestHarness) { + defer harness.Close() + for _, script := range DoltCommitTests { + enginetest.TestScriptPrepared(t, harness, script) + } +} + +func RunStatsHistogramTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltHistogramTests { + func() { + h = h.NewHarness(t).WithConfigureStats(true) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunStatsIOTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range append(DoltStatsIOTests, DoltHistogramTests...) { + func() { + h = h.NewHarness(t).WithConfigureStats(true) + defer h.Close() + e := mustNewEngine(t, h) + if enginetest.IsServerEngine(e) { + return + } + defer e.Close() + TestProviderReloadScriptWithEngine(t, e, h, script) + }() + } +} + +// these are sensitive to cardinality estimates, +// particularly the join-filter tests that trade-off +// smallest table first vs smallest join first +func RunJoinStatsTests(t *testing.T, h DoltEnginetestHarness) { + defer h.Close() + h = h.WithConfigureStats(true) + enginetest.TestJoinStats(t, h) +} + +func RunPreparedStatisticsTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltHistogramTests { + func() { + h := h.NewHarness(t).WithConfigureStats(true) + defer h.Close() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} + +func RunVersionedQueriesPreparedTests(t *testing.T, h DoltEnginetestHarness) { + defer h.Close() + h.Setup(setup.MydbData, []setup.SetupScript{VersionedQuerySetup, VersionedQueryViews}) + + e, err := h.NewEngine(t) + require.NoError(t, err) + + for _, tt := range queries.VersionedQueries { + enginetest.TestPreparedQueryWithEngine(t, h, e, tt) + } + + for _, tt := range queries.VersionedScripts { + enginetest.TestScriptWithEnginePrepared(t, e, h, tt) + } +} + +func RunAddDropPrimaryKeysTests(t *testing.T, harness DoltEnginetestHarness) { + t.Run("adding and dropping primary keys does not result in duplicate NOT NULL constraints", func(t *testing.T) { + harness = harness.NewHarness(t) + defer harness.Close() + addPkScript := queries.ScriptTest{ + Name: "add primary keys", + SetUpScript: []string{ + "create table test (id int not null, c1 int);", + "create index c1_idx on test(c1)", + "insert into test values (1,1),(2,2)", + "ALTER TABLE test ADD PRIMARY KEY(id)", + "ALTER TABLE test DROP PRIMARY KEY", + "ALTER TABLE test ADD PRIMARY KEY(id)", + "ALTER TABLE test DROP PRIMARY KEY", + "ALTER TABLE test ADD PRIMARY KEY(id)", + "ALTER TABLE test DROP PRIMARY KEY", + "ALTER TABLE test ADD PRIMARY KEY(id)", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "show create table test", + Expected: []sql.Row{ + {"test", "CREATE TABLE `test` (\n" + + " `id` int NOT NULL,\n" + + " `c1` int,\n" + + " PRIMARY KEY (`id`),\n" + + " KEY `c1_idx` (`c1`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + }, + } + + enginetest.TestScript(t, harness, addPkScript) + + // make sure there is only one NOT NULL constraint after all those mutations + ctx := sql.NewContext(context.Background(), sql.WithSession(harness.Session())) + ws, err := harness.Session().WorkingSet(ctx, "mydb") + require.NoError(t, err) + + table, ok, err := ws.WorkingRoot().GetTable(ctx, doltdb.TableName{Name: "test"}) + require.NoError(t, err) + require.True(t, ok) + + sch, err := table.GetSchema(ctx) + for _, col := range sch.GetAllCols().GetColumns() { + count := 0 + for _, cc := range col.Constraints { + if cc.GetConstraintType() == schema.NotNullConstraintType { + count++ + } + } + require.Less(t, count, 2) + } + }) + + t.Run("Add primary key to table with index", func(t *testing.T) { + harness := harness.NewHarness(t) + defer harness.Close() + script := queries.ScriptTest{ + Name: "add primary keys to table with index", + SetUpScript: []string{ + "create table test (id int not null, c1 int);", + "create index c1_idx on test(c1)", + "insert into test values (1,1),(2,2)", + "ALTER TABLE test ADD constraint test_check CHECK (c1 > 0)", + "ALTER TABLE test ADD PRIMARY KEY(id)", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "show create table test", + Expected: []sql.Row{ + {"test", "CREATE TABLE `test` (\n" + + " `id` int NOT NULL,\n" + + " `c1` int,\n" + + " PRIMARY KEY (`id`),\n" + + " KEY `c1_idx` (`c1`),\n" + + " CONSTRAINT `test_check` CHECK ((`c1` > 0))\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "select * from test order by id", + Expected: []sql.Row{ + {1, 1}, + {2, 2}, + }, + }, + }, + } + enginetest.TestScript(t, harness, script) + + ctx := sql.NewContext(context.Background(), sql.WithSession(harness.Session())) + ws, err := harness.Session().WorkingSet(ctx, "mydb") + require.NoError(t, err) + + table, ok, err := ws.WorkingRoot().GetTable(ctx, doltdb.TableName{Name: "test"}) + require.NoError(t, err) + require.True(t, ok) + + // Assert the new index map is not empty + newRows, err := table.GetIndexRowData(ctx, "c1_idx") + require.NoError(t, err) + empty, err := newRows.Empty() + require.NoError(t, err) + assert.False(t, empty) + count, err := newRows.Count() + require.NoError(t, err) + assert.Equal(t, count, uint64(2)) + }) + + t.Run("Add primary key when one more cells contain NULL", func(t *testing.T) { + harness := harness.NewHarness(t) + defer harness.Close() + script := queries.ScriptTest{ + Name: "Add primary key when one more cells contain NULL", + SetUpScript: []string{ + "create table test (id int not null, c1 int);", + "create index c1_idx on test(c1)", + "insert into test values (1,1),(2,2)", + "ALTER TABLE test ADD PRIMARY KEY (c1)", + "ALTER TABLE test ADD COLUMN (c2 INT NULL)", + "ALTER TABLE test DROP PRIMARY KEY", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "ALTER TABLE test ADD PRIMARY KEY (id, c1, c2)", + ExpectedErr: sql.ErrInsertIntoNonNullableProvidedNull, + }, + }, + } + enginetest.TestScript(t, harness, script) + }) + + t.Run("Drop primary key from table with index", func(t *testing.T) { + harness := harness.NewHarness(t) + defer harness.Close() + script := queries.ScriptTest{ + Name: "Drop primary key from table with index", + SetUpScript: []string{ + "create table test (id int not null primary key, c1 int);", + "create index c1_idx on test(c1)", + "insert into test values (1,1),(2,2)", + "ALTER TABLE test DROP PRIMARY KEY", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "show create table test", + Expected: []sql.Row{ + {"test", "CREATE TABLE `test` (\n" + + " `id` int NOT NULL,\n" + + " `c1` int,\n" + + " KEY `c1_idx` (`c1`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}, + }, + }, + { + Query: "select * from test order by id", + Expected: []sql.Row{ + {1, 1}, + {2, 2}, + }, + }, + }, + } + + enginetest.TestScript(t, harness, script) + + ctx := sql.NewContext(context.Background(), sql.WithSession(harness.Session())) + ws, err := harness.Session().WorkingSet(ctx, "mydb") + require.NoError(t, err) + + table, ok, err := ws.WorkingRoot().GetTable(ctx, doltdb.TableName{Name: "test"}) + require.NoError(t, err) + require.True(t, ok) + + // Assert the index map is not empty + newIdx, err := table.GetIndexRowData(ctx, "c1_idx") + assert.NoError(t, err) + empty, err := newIdx.Empty() + require.NoError(t, err) + assert.False(t, empty) + count, err := newIdx.Count() + require.NoError(t, err) + assert.Equal(t, count, uint64(2)) + }) +} + +func RunDoltVerifyConstraintsTests(t *testing.T, harness DoltEnginetestHarness) { + for _, script := range DoltVerifyConstraintsTestScripts { + func() { + harness = harness.NewHarness(t) + defer harness.Close() + enginetest.TestScript(t, harness, script) + }() + } +} + +func RunDoltStorageFormatTests(t *testing.T, h DoltEnginetestHarness) { + var expectedFormatString string + if types.IsFormat_DOLT(types.Format_Default) { + expectedFormatString = "NEW ( __DOLT__ )" + } else { + expectedFormatString = fmt.Sprintf("OLD ( %s )", types.Format_Default.VersionString()) + } + script := queries.ScriptTest{ + Name: "dolt storage format function works", + Assertions: []queries.ScriptTestAssertion{ + { + Query: "select dolt_storage_format()", + Expected: []sql.Row{{expectedFormatString}}, + }, + }, + } + defer h.Close() + enginetest.TestScript(t, h, script) +} + +func RunThreeWayMergeWithSchemaChangeScripts(t *testing.T, h DoltEnginetestHarness) { + skipOldFormat(t) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsBasicCases, "basic cases", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsForDataConflicts, "data conflicts", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsCollations, "collation changes", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsConstraints, "constraint changes", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsSchemaConflicts, "schema conflicts", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsGeneratedColumns, "generated columns", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsForJsonConflicts, "json merge", false) + + // Run non-symmetric schema merge tests in just one direction + t.Run("type changes", func(t *testing.T) { + for _, script := range SchemaChangeTestsTypeChanges { + // run in a func() so we can cleanly defer closing the harness + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, convertMergeScriptTest(script, false)) + }() + } + }) +} + +func RunThreeWayMergeWithSchemaChangeScriptsPrepared(t *testing.T, h DoltEnginetestHarness) { + skipOldFormat(t) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsBasicCases, "basic cases", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsForDataConflicts, "data conflicts", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsCollations, "collation changes", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsConstraints, "constraint changes", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsSchemaConflicts, "schema conflicts", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsGeneratedColumns, "generated columns", false) + runMergeScriptTestsInBothDirections(t, SchemaChangeTestsForJsonConflicts, "json merge", false) + + // Run non-symmetric schema merge tests in just one direction + t.Run("type changes", func(t *testing.T) { + for _, script := range SchemaChangeTestsTypeChanges { + // run in a func() so we can cleanly defer closing the harness + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScriptPrepared(t, h, convertMergeScriptTest(script, false)) + }() + } + }) +} + +// runMergeScriptTestsInBothDirections creates a new test run, named |name|, and runs the specified merge |tests| +// in both directions (right to left merge, and left to right merge). If +// |runAsPrepared| is true then the test scripts will be run using the prepared +// statement test code. +func runMergeScriptTestsInBothDirections(t *testing.T, tests []MergeScriptTest, name string, runAsPrepared bool) { + t.Run(name, func(t *testing.T) { + t.Run("right to left merges", func(t *testing.T) { + for _, script := range tests { + // run in a func() so we can cleanly defer closing the harness + func() { + h := newDoltHarness(t) + defer h.Close() + if runAsPrepared { + enginetest.TestScriptPrepared(t, h, convertMergeScriptTest(script, false)) + } else { + enginetest.TestScript(t, h, convertMergeScriptTest(script, false)) + } + }() + } + }) + t.Run("left to right merges", func(t *testing.T) { + for _, script := range tests { + func() { + h := newDoltHarness(t) + defer h.Close() + if runAsPrepared { + enginetest.TestScriptPrepared(t, h, convertMergeScriptTest(script, true)) + } else { + enginetest.TestScript(t, h, convertMergeScriptTest(script, true)) + } + }() + } + }) + }) +} + +func SkipByDefaultInCI(t *testing.T) { + if os.Getenv("CI") != "" && os.Getenv("DOLT_TEST_RUN_NON_RACE_TESTS") == "" { + t.Skip() + } +} + +var newFormatSkippedScripts = []string{ + // Different query plans + "Partial indexes are used and return the expected result", + "Multiple indexes on the same columns in a different order", +} + +func skipOldFormat(t *testing.T) { + if !types.IsFormat_DOLT(types.Format_Default) { + t.Skip() + } +} + +func skipPreparedTests(t *testing.T) { + if skipPrepared { + t.Skip("skip prepared") + } +} + +func newSessionBuilder(harness *DoltHarness) server.SessionBuilder { + return func(ctx context.Context, conn *mysql.Conn, host string) (sql.Session, error) { + newCtx := harness.NewSession() + return newCtx.Session, nil + } +} + +func RunDoltReflogTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltReflogTestScripts { + func() { + h = h.NewHarness(t) + defer h.Close() + h.UseLocalFileSystem() + h.SkipSetupCommit() + enginetest.TestScript(t, h, script) + }() + } +} + +func RunDoltReflogTestsPrepared(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltReflogTestScripts { + func() { + h = h.NewHarness(t) + defer h.Close() + h.UseLocalFileSystem() + h.SkipSetupCommit() + enginetest.TestScriptPrepared(t, h, script) + }() + } +} diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_harness.go b/go/libraries/doltcore/sqle/enginetest/dolt_harness.go index 7c3635f03f8..09ec1a106c7 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_harness.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_harness.go @@ -61,15 +61,62 @@ type DoltHarness struct { setupTestProcedures bool } -var _ enginetest.Harness = (*DoltHarness)(nil) -var _ enginetest.SkippingHarness = (*DoltHarness)(nil) -var _ enginetest.ClientHarness = (*DoltHarness)(nil) -var _ enginetest.IndexHarness = (*DoltHarness)(nil) -var _ enginetest.VersionedDBHarness = (*DoltHarness)(nil) -var _ enginetest.ForeignKeyHarness = (*DoltHarness)(nil) -var _ enginetest.KeylessTableHarness = (*DoltHarness)(nil) -var _ enginetest.ReadOnlyDatabaseHarness = (*DoltHarness)(nil) -var _ enginetest.ValidatingHarness = (*DoltHarness)(nil) +func (d *DoltHarness) UseLocalFileSystem() { + d.useLocalFilesystem = true +} + +func (d *DoltHarness) Session() *dsess.DoltSession { + return d.session +} + +func (d *DoltHarness) WithConfigureStats(configureStats bool) DoltEnginetestHarness { + nd := *d + nd.configureStats = configureStats + return &nd +} + +func (d *DoltHarness) NewHarness(t *testing.T) DoltEnginetestHarness { + return newDoltHarness(t) +} + +type DoltEnginetestHarness interface { + enginetest.Harness + enginetest.SkippingHarness + enginetest.ClientHarness + enginetest.IndexHarness + enginetest.VersionedDBHarness + enginetest.ForeignKeyHarness + enginetest.KeylessTableHarness + enginetest.ReadOnlyDatabaseHarness + enginetest.ValidatingHarness + + // NewHarness returns a new uninitialized harness of the same type + NewHarness(t *testing.T) DoltEnginetestHarness + + // WithSkippedQueries returns a copy of the harness with the given queries skipped + WithSkippedQueries(skipped []string) DoltEnginetestHarness + + // WithParallelism returns a copy of the harness with parallelism set to the given number of threads + WithParallelism(parallelism int) DoltEnginetestHarness + + // WithConfigureStats returns a copy of the harness with the given configureStats value + WithConfigureStats(configureStats bool) DoltEnginetestHarness + + // SkipSetupCommit configures to harness to skip the commit after setup scripts are run + SkipSetupCommit() + + // UseLocalFileSystem configures the harness to use the local filesystem for all storage, instead of in-memory versions + UseLocalFileSystem() + + // Close closes the harness, freeing up any resources it may have allocated + Close() + + Engine() *gms.Engine + + Session() *dsess.DoltSession +} + +var _ DoltEnginetestHarness = &DoltHarness{} // newDoltHarness creates a new harness for testing Dolt, using an in-memory filesystem and an in-memory blob store. func newDoltHarness(t *testing.T) *DoltHarness { @@ -82,6 +129,10 @@ func newDoltHarness(t *testing.T) *DoltHarness { return dh } +func newDoltEnginetestHarness(t *testing.T) DoltEnginetestHarness { + return newDoltHarness(t) +} + // newDoltHarnessForLocalFilesystem creates a new harness for testing Dolt, using // the local filesystem for all storage, instead of in-memory versions. This setup // is useful for testing functionality that requires a real filesystem. @@ -279,19 +330,23 @@ func filterStatsOnlyQueries(scripts []setup.SetupScript) []setup.SetupScript { // WithParallelism returns a copy of the harness with parallelism set to the given number of threads. A value of 0 or // less means to use the system parallelism settings. -func (d *DoltHarness) WithParallelism(parallelism int) *DoltHarness { +func (d *DoltHarness) WithParallelism(parallelism int) DoltEnginetestHarness { nd := *d nd.parallelism = parallelism return &nd } // WithSkippedQueries returns a copy of the harness with the given queries skipped -func (d *DoltHarness) WithSkippedQueries(queries []string) *DoltHarness { +func (d *DoltHarness) WithSkippedQueries(queries []string) DoltEnginetestHarness { nd := *d nd.skippedQueries = append(d.skippedQueries, queries...) return &nd } +func (d *DoltHarness) Engine() *gms.Engine { + return d.engine +} + // SkipQueryTest returns whether to skip a query func (d *DoltHarness) SkipQueryTest(query string) bool { lowerQuery := strings.ToLower(query) diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_server_test.go b/go/libraries/doltcore/sqle/enginetest/dolt_server_test.go index ca95d7a0cef..d3b9d216661 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_server_test.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_server_test.go @@ -15,465 +15,17 @@ package enginetest import ( - "context" - gosql "database/sql" - "math/rand" "runtime" "strings" "testing" - "time" - "github.com/dolthub/go-mysql-server/enginetest/queries" "github.com/dolthub/go-mysql-server/sql" - "github.com/gocraft/dbr/v2" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/dolthub/dolt/go/cmd/dolt/commands/sqlserver" - "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils" - "github.com/dolthub/dolt/go/libraries/doltcore/env" "github.com/dolthub/dolt/go/libraries/doltcore/servercfg" - "github.com/dolthub/dolt/go/libraries/utils/svcs" ) -// DoltBranchMultiSessionScriptTests contain tests that need to be run in a multi-session server environment -// in order to fully test branch deletion and renaming logic. -var DoltBranchMultiSessionScriptTests = []queries.ScriptTest{ - { - Name: "Test multi-session behavior for deleting branches", - SetUpScript: []string{ - "call dolt_branch('branch1');", - "call dolt_branch('branch2');", - "call dolt_branch('branch3');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "/* client a */ CALL DOLT_CHECKOUT('branch1');", - Expected: []sql.Row{{0, "Switched to branch 'branch1'"}}, - }, - { - Query: "/* client a */ select active_branch();", - Expected: []sql.Row{{"branch1"}}, - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch1');", - ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", - }, - { - Query: "/* client a */ CALL DOLT_CHECKOUT('branch2');", - Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch1');", - Expected: []sql.Row{{0}}, - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch2');", - ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-df', 'branch2');", - Expected: []sql.Row{{0}}, - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch3');", - Expected: []sql.Row{{0}}, - }, - }, - }, - { - Name: "Test multi-session behavior for renaming branches", - SetUpScript: []string{ - "call dolt_branch('branch1');", - "call dolt_branch('branch2');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "/* client a */ CALL DOLT_CHECKOUT('branch1');", - Expected: []sql.Row{{0, "Switched to branch 'branch1'"}}, - }, - { - Query: "/* client a */ select active_branch();", - Expected: []sql.Row{{"branch1"}}, - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-m', 'branch1', 'movedBranch1');", - ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-mf', 'branch1', 'movedBranch1');", - Expected: []sql.Row{{0}}, - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-m', 'branch2', 'movedBranch2');", - Expected: []sql.Row{{0}}, - }, - }, - }, - { - Name: "Test branch deletion when clients are using a branch-qualified database", - SetUpScript: []string{ - "call dolt_branch('branch1');", - "call dolt_branch('branch2');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "/* client a */ use dolt/branch1;", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ SELECT DATABASE(), ACTIVE_BRANCH();", - Expected: []sql.Row{{"dolt/branch1", "branch1"}}, - }, - { - Query: "/* client b */ use dolt/branch2;", - Expected: []sql.Row{}, - }, - { - Query: "/* client b */ SELECT DATABASE(), ACTIVE_BRANCH();", - Expected: []sql.Row{{"dolt/branch2", "branch2"}}, - }, - { - Query: "/* client a */ SHOW DATABASES;", - Expected: []sql.Row{{"dolt"}, {"dolt/branch1"}, {"information_schema"}, {"mysql"}}, - }, - { - Query: "/* client a */ CALL DOLT_BRANCH('-d', 'branch2');", - ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", - }, - { - Query: "/* client a */ CALL DOLT_BRANCH('-df', 'branch2');", - Expected: []sql.Row{{0}}, - }, - { - Query: "/* client a */ SHOW DATABASES;", - Expected: []sql.Row{{"dolt"}, {"dolt/branch1"}, {"information_schema"}, {"mysql"}}, - }, - { - Query: "/* client a */ SELECT DATABASE(), ACTIVE_BRANCH();", - Expected: []sql.Row{{"dolt/branch1", "branch1"}}, - }, - { - // Call a stored procedure since this searches across all databases and will - // fail if a branch-qualified database exists for a missing branch. - Query: "/* client a */ CALL DOLT_BRANCH('branch3');", - Expected: []sql.Row{{0}}, - }, - }, - }, - { - Name: "Test branch renaming when clients are using a branch-qualified database", - SetUpScript: []string{ - "call dolt_branch('branch1');", - "call dolt_branch('branch2');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "/* client a */ use dolt/branch1;", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ SELECT DATABASE(), ACTIVE_BRANCH();", - Expected: []sql.Row{{"dolt/branch1", "branch1"}}, - }, - { - Query: "/* client b */ use dolt/branch2;", - Expected: []sql.Row{}, - }, - { - Query: "/* client b */ SELECT DATABASE(), ACTIVE_BRANCH();", - Expected: []sql.Row{{"dolt/branch2", "branch2"}}, - }, - { - Query: "/* client a */ SHOW DATABASES;", - Expected: []sql.Row{{"dolt"}, {"dolt/branch1"}, {"information_schema"}, {"mysql"}}, - }, - { - Query: "/* client a */ CALL DOLT_BRANCH('-m', 'branch2', 'newName');", - ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", - }, - { - Query: "/* client a */ CALL DOLT_BRANCH('-mf', 'branch2', 'newName');", - Expected: []sql.Row{{0}}, - }, - { - Query: "/* client a */ SHOW DATABASES;", - Expected: []sql.Row{{"dolt"}, {"dolt/branch1"}, {"information_schema"}, {"mysql"}}, - }, - { - // Call a stored procedure since this searches across all databases and will - // fail if a branch-qualified database exists for a missing branch. - Query: "/* client a */ CALL DOLT_BRANCH('branch3');", - Expected: []sql.Row{{0}}, - }, - }, - }, - { - Name: "Test multi-session behavior for force deleting active branch with autocommit on", - Assertions: []queries.ScriptTestAssertion{ - { - Query: "/* client a */ SET @@autocommit=1;", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ CALL DOLT_CHECKOUT('-b', 'branch1');", - Expected: []sql.Row{{0, "Switched to branch 'branch1'"}}, - }, - { - Query: "/* client a */ select active_branch();", - Expected: []sql.Row{{"branch1"}}, - }, - { - Query: "/* client b */ select active_branch();", - Expected: []sql.Row{{"main"}}, - }, - { - Query: "/* client b */ select name from dolt_branches order by name;", - Expected: []sql.Row{{"branch1"}, {"main"}}, - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-D', 'branch1');", - Expected: []sql.Row{{0}}, - }, - { - Query: "/* client b */ select name from dolt_branches;", - Expected: []sql.Row{{"main"}}, - }, - { - Query: "/* client a */ select name from dolt_branches;", - ExpectedErrStr: "Error 1049 (HY000): database not found: dolt/branch1", - }, - { - Query: "/* client a */ CALL DOLT_CHECKOUT('main');", - ExpectedErrStr: "Error 1049 (HY000): database not found: dolt/branch1", - }, - { - Query: "/* client a */ USE dolt/main;", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ select active_branch();", - Expected: []sql.Row{{"main"}}, - }, - }, - }, - { - Name: "Test multi-session behavior for force deleting active branch with autocommit off", - Assertions: []queries.ScriptTestAssertion{ - { - Query: "/* client a */ SET @@autocommit=0;", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ CALL DOLT_CHECKOUT('-b', 'branch1');", - Expected: []sql.Row{{0, "Switched to branch 'branch1'"}}, - }, - { - Query: "/* client a */ select active_branch();", - Expected: []sql.Row{{"branch1"}}, - }, - { - Query: "/* client b */ select active_branch();", - Expected: []sql.Row{{"main"}}, - }, - { - Query: "/* client b */ select name from dolt_branches order by name;", - Expected: []sql.Row{{"branch1"}, {"main"}}, - }, - { - Query: "/* client b */ CALL DOLT_BRANCH('-D', 'branch1');", - Expected: []sql.Row{{0}}, - }, - { - Query: "/* client b */ select name from dolt_branches;", - Expected: []sql.Row{{"main"}}, - }, - { - Query: "/* client a */ select name from dolt_branches;", - ExpectedErrStr: "Error 1049 (HY000): database not found: dolt/branch1", - }, - { - // TODO: this could be handled better, not the best experience. Maybe kill the session? - Query: "/* client a */ CALL DOLT_CHECKOUT('main');", - ExpectedErrStr: "Error 1049 (HY000): database not found: dolt/branch1", - }, - { - Query: "/* client a */ USE dolt/main;", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ select active_branch();", - Expected: []sql.Row{{"main"}}, - }, - }, - }, -} - -// DropDatabaseMultiSessionScriptTests test that when dropping a database, other sessions are properly updated -// and don't get left with old state that causes incorrect results. -// Note: this test needs to be run against a real Dolt sql-server, and not just with our transaction test scripts, -// because the transaction tests currently have a different behavior for session management and don't emulate prod. -var DropDatabaseMultiSessionScriptTests = []queries.ScriptTest{ - { - Name: "Test multi-session behavior for dropping databases", - SetUpScript: []string{ - "create database db01;", - "create table db01.t01 (pk int primary key);", - "insert into db01.t01 values (101), (202), (303);", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "/* client a */ use db01;", - Expected: []sql.Row{}, - }, - { - Query: "/* client b */ use db01;", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ show tables;", - Expected: []sql.Row{{"t01"}}, - }, - { - Query: "/* client b */ show tables;", - Expected: []sql.Row{{"t01"}}, - }, - { - Query: "/* client a */ drop database db01;", - Expected: []sql.Row{}, - }, - { - // TODO: This test runner doesn't currently support asserting against null values - Query: "/* client a */ select database() is NULL;", - Expected: []sql.Row{{1}}, - }, - { - Query: "/* client a */ show databases like 'db01';", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ create database db01;", - Expected: []sql.Row{}, - }, - { - Query: "/* client b */ select database();", - Expected: []sql.Row{{"db01"}}, - }, - { - Query: "/* client b */ show tables;", - Expected: []sql.Row{}, - }, - }, - }, - { - Name: "Test multi-session behavior for dropping databases with a revision db", - SetUpScript: []string{ - "create database db01;", - "use db01;", - "create table db01.t01 (pk int primary key);", - "insert into db01.t01 values (101), (202), (303);", - "call dolt_commit('-Am', 'commit on main');", - "call dolt_checkout('-b', 'branch1');", - "insert into db01.t01 values (1001), (2002), (3003);", - "call dolt_commit('-Am', 'commit on branch1');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "/* client a */ use db01;", - Expected: []sql.Row{}, - }, - { - Query: "/* client b */ use `db01/branch1`;", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ show tables;", - Expected: []sql.Row{{"t01"}}, - }, - { - Query: "/* client b */ show tables;", - Expected: []sql.Row{{"t01"}}, - }, - { - Query: "/* client a */ drop database db01;", - Expected: []sql.Row{}, - }, - { - // TODO: This test runner doesn't currently support asserting against null values - Query: "/* client a */ select database() is NULL;", - Expected: []sql.Row{{1}}, - }, - { - Query: "/* client a */ show databases like 'db01';", - Expected: []sql.Row{}, - }, - { - Query: "/* client a */ create database db01;", - Expected: []sql.Row{}, - }, - { - Query: "/* client b */ select database();", - Expected: []sql.Row{{"db01/branch1"}}, - }, - { - Query: "/* client b */ show tables;", - ExpectedErrStr: "Error 1049 (HY000): database not found: db01/branch1", - }, - }, - }, -} - -var PersistVariableTests = []queries.ScriptTest{ - { - Name: "set persisted variables with on and off", - SetUpScript: []string{ - "set @@persist.dolt_skip_replication_errors = on;", - "set @@persist.dolt_read_replica_force_pull = off;", - }, - }, - { - Name: "retrieve persisted variables", - Assertions: []queries.ScriptTestAssertion{ - { - Query: "select @@dolt_skip_replication_errors", - Expected: []sql.Row{ - {1}, - }, - }, - { - Query: "select @@dolt_read_replica_force_pull", - Expected: []sql.Row{ - {0}, - }, - }, - }, - }, - { - Name: "set persisted variables with 1 and 0", - SetUpScript: []string{ - "set @@persist.dolt_skip_replication_errors = 0;", - "set @@persist.dolt_read_replica_force_pull = 1;", - }, - }, - { - Name: "retrieve persisted variables", - Assertions: []queries.ScriptTestAssertion{ - { - Query: "select @@dolt_skip_replication_errors", - Expected: []sql.Row{ - {0}, - }, - }, - { - Query: "select @@dolt_read_replica_force_pull", - Expected: []sql.Row{ - {1}, - }, - }, - }, - }, -} - // TestDoltMultiSessionBehavior runs tests that exercise multi-session logic on a running SQL server. Statements // are sent through the server, from out of process, instead of directly to the in-process engine API. func TestDoltMultiSessionBehavior(t *testing.T) { @@ -491,215 +43,6 @@ func TestPersistVariable(t *testing.T) { testSerialSessionScriptTests(t, PersistVariableTests) } -func testMultiSessionScriptTests(t *testing.T, tests []queries.ScriptTest) { - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - dEnv, sc, serverConfig := startServer(t, true, "", "") - err := sc.WaitForStart() - require.NoError(t, err) - defer dEnv.DoltDB.Close() - - conn1, sess1 := newConnection(t, serverConfig) - conn2, sess2 := newConnection(t, serverConfig) - - t.Run(test.Name, func(t *testing.T) { - for _, setupStatement := range test.SetUpScript { - _, err := sess1.Exec(setupStatement) - require.NoError(t, err) - } - - for _, assertion := range test.Assertions { - t.Run(assertion.Query, func(t *testing.T) { - var activeSession *dbr.Session - if strings.Contains(strings.ToLower(assertion.Query), "/* client a */") { - activeSession = sess1 - } else if strings.Contains(strings.ToLower(assertion.Query), "/* client b */") { - activeSession = sess2 - } else { - require.Fail(t, "unsupported client specification: "+assertion.Query) - } - - rows, err := activeSession.Query(assertion.Query) - - if len(assertion.ExpectedErrStr) > 0 { - require.EqualError(t, err, assertion.ExpectedErrStr) - } else if assertion.ExpectedErr != nil { - require.True(t, assertion.ExpectedErr.Is(err), "expected error %v, got %v", assertion.ExpectedErr, err) - } else if assertion.Expected != nil { - require.NoError(t, err) - assertResultsEqual(t, assertion.Expected, rows) - } else if assertion.SkipResultsCheck { - // no-op - } else { - t.Fatalf("unsupported ScriptTestAssertion property: %v", assertion) - } - if rows != nil { - require.NoError(t, rows.Close()) - } - }) - } - }) - - require.NoError(t, conn1.Close()) - require.NoError(t, conn2.Close()) - - sc.Stop() - err = sc.WaitForStop() - require.NoError(t, err) - }) - } -} - -// testSerialSessionScriptTests creates an environment, then for each script starts a server and runs assertions, -// stopping the server in between scripts. Unlike other script test executors, scripts may influence later scripts in -// the block. -func testSerialSessionScriptTests(t *testing.T, tests []queries.ScriptTest) { - dEnv := dtestutils.CreateTestEnv() - serverConfig := sqlserver.DefaultCommandLineServerConfig() - rand.Seed(time.Now().UnixNano()) - port := 15403 + rand.Intn(25) - serverConfig = serverConfig.WithPort(port) - defer dEnv.DoltDB.Close() - - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - sc, serverConfig := startServerOnEnv(t, serverConfig, dEnv) - err := sc.WaitForStart() - require.NoError(t, err) - - conn1, sess1 := newConnection(t, serverConfig) - - t.Run(test.Name, func(t *testing.T) { - for _, setupStatement := range test.SetUpScript { - _, err := sess1.Exec(setupStatement) - require.NoError(t, err) - } - - for _, assertion := range test.Assertions { - t.Run(assertion.Query, func(t *testing.T) { - activeSession := sess1 - rows, err := activeSession.Query(assertion.Query) - - if len(assertion.ExpectedErrStr) > 0 { - require.EqualError(t, err, assertion.ExpectedErrStr) - } else if assertion.ExpectedErr != nil { - require.True(t, assertion.ExpectedErr.Is(err), "expected error %v, got %v", assertion.ExpectedErr, err) - } else if assertion.Expected != nil { - require.NoError(t, err) - assertResultsEqual(t, assertion.Expected, rows) - } else { - require.Fail(t, "unsupported ScriptTestAssertion property: %v", assertion) - } - if rows != nil { - require.NoError(t, rows.Close()) - } - }) - } - }) - - require.NoError(t, conn1.Close()) - - sc.Stop() - err = sc.WaitForStop() - require.NoError(t, err) - }) - } -} - -func makeDestinationSlice(t *testing.T, columnTypes []*gosql.ColumnType) []interface{} { - dest := make([]any, len(columnTypes)) - for i, columnType := range columnTypes { - switch strings.ToLower(columnType.DatabaseTypeName()) { - case "int", "tinyint", "bigint": - var integer int - dest[i] = &integer - case "text": - var s string - dest[i] = &s - default: - require.Fail(t, "unsupported type: "+columnType.DatabaseTypeName()) - } - } - - return dest -} - -func assertResultsEqual(t *testing.T, expected []sql.Row, rows *gosql.Rows) { - columnTypes, err := rows.ColumnTypes() - assert.NoError(t, err) - dest := makeDestinationSlice(t, columnTypes) - - for _, expectedRow := range expected { - ok := rows.Next() - if !ok { - assert.Fail(t, "Fewer results than expected") - } - err := rows.Scan(dest...) - assert.NoError(t, err) - assert.Equal(t, len(expectedRow), len(dest), - "Different number of columns returned than expected") - - for j, expectedValue := range expectedRow { - switch strings.ToUpper(columnTypes[j].DatabaseTypeName()) { - case "TEXT": - actualValue, ok := dest[j].(*string) - assert.True(t, ok) - assert.Equal(t, expectedValue, *actualValue) - case "INT", "TINYINT", "BIGINT": - actualValue, ok := dest[j].(*int) - assert.True(t, ok) - assert.Equal(t, expectedValue, *actualValue) - default: - assert.Fail(t, "Unsupported datatype: %s", columnTypes[j].DatabaseTypeName()) - } - } - } - - if rows.Next() { - assert.Fail(t, "More results than expected") - } -} - -// startServer will start sql-server with given host, unix socket file path and whether to use specific port, which is defined randomly. -func startServer(t *testing.T, withPort bool, host string, unixSocketPath string) (*env.DoltEnv, *svcs.Controller, servercfg.ServerConfig) { - dEnv := dtestutils.CreateTestEnv() - serverConfig := sqlserver.DefaultCommandLineServerConfig() - if withPort { - rand.Seed(time.Now().UnixNano()) - port := 15403 + rand.Intn(25) - serverConfig = serverConfig.WithPort(port) - } - if host != "" { - serverConfig = serverConfig.WithHost(host) - } - if unixSocketPath != "" { - serverConfig = serverConfig.WithSocket(unixSocketPath) - } - - onEnv, config := startServerOnEnv(t, serverConfig, dEnv) - return dEnv, onEnv, config -} - -func startServerOnEnv(t *testing.T, serverConfig servercfg.ServerConfig, dEnv *env.DoltEnv) (*svcs.Controller, servercfg.ServerConfig) { - sc := svcs.NewController() - go func() { - _, _ = sqlserver.Serve(context.Background(), "0.0.0", serverConfig, sc, dEnv) - }() - err := sc.WaitForStart() - require.NoError(t, err) - - return sc, serverConfig -} - -// newConnection takes sqlserver.serverConfig and opens a connection, and will return that connection with a new session -func newConnection(t *testing.T, serverConfig servercfg.ServerConfig) (*dbr.Connection, *dbr.Session) { - const dbName = "dolt" - conn, err := dbr.Open("mysql", servercfg.ConnectionString(serverConfig, dbName), nil) - require.NoError(t, err) - sess := conn.NewSession(nil) - return conn, sess -} - func TestDoltServerRunningUnixSocket(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("unix sockets not supported on Windows") diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_server_tests.go b/go/libraries/doltcore/sqle/enginetest/dolt_server_tests.go new file mode 100755 index 00000000000..fffa257cdf2 --- /dev/null +++ b/go/libraries/doltcore/sqle/enginetest/dolt_server_tests.go @@ -0,0 +1,683 @@ +// Copyright 2024 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetest + +import ( + "context" + gosql "database/sql" + "math/rand" + "strings" + "testing" + "time" + + "github.com/dolthub/go-mysql-server/enginetest/queries" + "github.com/dolthub/go-mysql-server/sql" + "github.com/gocraft/dbr/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/dolthub/dolt/go/cmd/dolt/commands/sqlserver" + "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils" + "github.com/dolthub/dolt/go/libraries/doltcore/env" + "github.com/dolthub/dolt/go/libraries/doltcore/servercfg" + "github.com/dolthub/dolt/go/libraries/utils/svcs" +) + +// DoltBranchMultiSessionScriptTests contain tests that need to be run in a multi-session server environment +// in order to fully test branch deletion and renaming logic. +var DoltBranchMultiSessionScriptTests = []queries.ScriptTest{ + { + Name: "Test multi-session behavior for deleting branches", + SetUpScript: []string{ + "call dolt_branch('branch1');", + "call dolt_branch('branch2');", + "call dolt_branch('branch3');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "/* client a */ CALL DOLT_CHECKOUT('branch1');", + Expected: []sql.Row{{0, "Switched to branch 'branch1'"}}, + }, + { + Query: "/* client a */ select active_branch();", + Expected: []sql.Row{{"branch1"}}, + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch1');", + ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", + }, + { + Query: "/* client a */ CALL DOLT_CHECKOUT('branch2');", + Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch1');", + Expected: []sql.Row{{0}}, + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch2');", + ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-df', 'branch2');", + Expected: []sql.Row{{0}}, + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch3');", + Expected: []sql.Row{{0}}, + }, + }, + }, + { + Name: "Test multi-session behavior for renaming branches", + SetUpScript: []string{ + "call dolt_branch('branch1');", + "call dolt_branch('branch2');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "/* client a */ CALL DOLT_CHECKOUT('branch1');", + Expected: []sql.Row{{0, "Switched to branch 'branch1'"}}, + }, + { + Query: "/* client a */ select active_branch();", + Expected: []sql.Row{{"branch1"}}, + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-m', 'branch1', 'movedBranch1');", + ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-mf', 'branch1', 'movedBranch1');", + Expected: []sql.Row{{0}}, + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-m', 'branch2', 'movedBranch2');", + Expected: []sql.Row{{0}}, + }, + }, + }, + { + Name: "Test branch deletion when clients are using a branch-qualified database", + SetUpScript: []string{ + "call dolt_branch('branch1');", + "call dolt_branch('branch2');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "/* client a */ use dolt/branch1;", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ SELECT DATABASE(), ACTIVE_BRANCH();", + Expected: []sql.Row{{"dolt/branch1", "branch1"}}, + }, + { + Query: "/* client b */ use dolt/branch2;", + Expected: []sql.Row{}, + }, + { + Query: "/* client b */ SELECT DATABASE(), ACTIVE_BRANCH();", + Expected: []sql.Row{{"dolt/branch2", "branch2"}}, + }, + { + Query: "/* client a */ SHOW DATABASES;", + Expected: []sql.Row{{"dolt"}, {"dolt/branch1"}, {"information_schema"}, {"mysql"}}, + }, + { + Query: "/* client a */ CALL DOLT_BRANCH('-d', 'branch2');", + ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", + }, + { + Query: "/* client a */ CALL DOLT_BRANCH('-df', 'branch2');", + Expected: []sql.Row{{0}}, + }, + { + Query: "/* client a */ SHOW DATABASES;", + Expected: []sql.Row{{"dolt"}, {"dolt/branch1"}, {"information_schema"}, {"mysql"}}, + }, + { + Query: "/* client a */ SELECT DATABASE(), ACTIVE_BRANCH();", + Expected: []sql.Row{{"dolt/branch1", "branch1"}}, + }, + { + // Call a stored procedure since this searches across all databases and will + // fail if a branch-qualified database exists for a missing branch. + Query: "/* client a */ CALL DOLT_BRANCH('branch3');", + Expected: []sql.Row{{0}}, + }, + }, + }, + { + Name: "Test branch renaming when clients are using a branch-qualified database", + SetUpScript: []string{ + "call dolt_branch('branch1');", + "call dolt_branch('branch2');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "/* client a */ use dolt/branch1;", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ SELECT DATABASE(), ACTIVE_BRANCH();", + Expected: []sql.Row{{"dolt/branch1", "branch1"}}, + }, + { + Query: "/* client b */ use dolt/branch2;", + Expected: []sql.Row{}, + }, + { + Query: "/* client b */ SELECT DATABASE(), ACTIVE_BRANCH();", + Expected: []sql.Row{{"dolt/branch2", "branch2"}}, + }, + { + Query: "/* client a */ SHOW DATABASES;", + Expected: []sql.Row{{"dolt"}, {"dolt/branch1"}, {"information_schema"}, {"mysql"}}, + }, + { + Query: "/* client a */ CALL DOLT_BRANCH('-m', 'branch2', 'newName');", + ExpectedErrStr: "Error 1105 (HY000): unsafe to delete or rename branches in use in other sessions; use --force to force the change", + }, + { + Query: "/* client a */ CALL DOLT_BRANCH('-mf', 'branch2', 'newName');", + Expected: []sql.Row{{0}}, + }, + { + Query: "/* client a */ SHOW DATABASES;", + Expected: []sql.Row{{"dolt"}, {"dolt/branch1"}, {"information_schema"}, {"mysql"}}, + }, + { + // Call a stored procedure since this searches across all databases and will + // fail if a branch-qualified database exists for a missing branch. + Query: "/* client a */ CALL DOLT_BRANCH('branch3');", + Expected: []sql.Row{{0}}, + }, + }, + }, + { + Name: "Test multi-session behavior for force deleting active branch with autocommit on", + Assertions: []queries.ScriptTestAssertion{ + { + Query: "/* client a */ SET @@autocommit=1;", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ CALL DOLT_CHECKOUT('-b', 'branch1');", + Expected: []sql.Row{{0, "Switched to branch 'branch1'"}}, + }, + { + Query: "/* client a */ select active_branch();", + Expected: []sql.Row{{"branch1"}}, + }, + { + Query: "/* client b */ select active_branch();", + Expected: []sql.Row{{"main"}}, + }, + { + Query: "/* client b */ select name from dolt_branches order by name;", + Expected: []sql.Row{{"branch1"}, {"main"}}, + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-D', 'branch1');", + Expected: []sql.Row{{0}}, + }, + { + Query: "/* client b */ select name from dolt_branches;", + Expected: []sql.Row{{"main"}}, + }, + { + Query: "/* client a */ select name from dolt_branches;", + ExpectedErrStr: "Error 1049 (HY000): database not found: dolt/branch1", + }, + { + Query: "/* client a */ CALL DOLT_CHECKOUT('main');", + ExpectedErrStr: "Error 1049 (HY000): database not found: dolt/branch1", + }, + { + Query: "/* client a */ USE dolt/main;", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ select active_branch();", + Expected: []sql.Row{{"main"}}, + }, + }, + }, + { + Name: "Test multi-session behavior for force deleting active branch with autocommit off", + Assertions: []queries.ScriptTestAssertion{ + { + Query: "/* client a */ SET @@autocommit=0;", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ CALL DOLT_CHECKOUT('-b', 'branch1');", + Expected: []sql.Row{{0, "Switched to branch 'branch1'"}}, + }, + { + Query: "/* client a */ select active_branch();", + Expected: []sql.Row{{"branch1"}}, + }, + { + Query: "/* client b */ select active_branch();", + Expected: []sql.Row{{"main"}}, + }, + { + Query: "/* client b */ select name from dolt_branches order by name;", + Expected: []sql.Row{{"branch1"}, {"main"}}, + }, + { + Query: "/* client b */ CALL DOLT_BRANCH('-D', 'branch1');", + Expected: []sql.Row{{0}}, + }, + { + Query: "/* client b */ select name from dolt_branches;", + Expected: []sql.Row{{"main"}}, + }, + { + Query: "/* client a */ select name from dolt_branches;", + ExpectedErrStr: "Error 1049 (HY000): database not found: dolt/branch1", + }, + { + // TODO: this could be handled better, not the best experience. Maybe kill the session? + Query: "/* client a */ CALL DOLT_CHECKOUT('main');", + ExpectedErrStr: "Error 1049 (HY000): database not found: dolt/branch1", + }, + { + Query: "/* client a */ USE dolt/main;", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ select active_branch();", + Expected: []sql.Row{{"main"}}, + }, + }, + }, +} + +// DropDatabaseMultiSessionScriptTests test that when dropping a database, other sessions are properly updated +// and don't get left with old state that causes incorrect results. +// Note: this test needs to be run against a real Dolt sql-server, and not just with our transaction test scripts, +// because the transaction tests currently have a different behavior for session management and don't emulate prod. +var DropDatabaseMultiSessionScriptTests = []queries.ScriptTest{ + { + Name: "Test multi-session behavior for dropping databases", + SetUpScript: []string{ + "create database db01;", + "create table db01.t01 (pk int primary key);", + "insert into db01.t01 values (101), (202), (303);", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "/* client a */ use db01;", + Expected: []sql.Row{}, + }, + { + Query: "/* client b */ use db01;", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ show tables;", + Expected: []sql.Row{{"t01"}}, + }, + { + Query: "/* client b */ show tables;", + Expected: []sql.Row{{"t01"}}, + }, + { + Query: "/* client a */ drop database db01;", + Expected: []sql.Row{}, + }, + { + // TODO: This test runner doesn't currently support asserting against null values + Query: "/* client a */ select database() is NULL;", + Expected: []sql.Row{{1}}, + }, + { + Query: "/* client a */ show databases like 'db01';", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ create database db01;", + Expected: []sql.Row{}, + }, + { + Query: "/* client b */ select database();", + Expected: []sql.Row{{"db01"}}, + }, + { + Query: "/* client b */ show tables;", + Expected: []sql.Row{}, + }, + }, + }, + { + Name: "Test multi-session behavior for dropping databases with a revision db", + SetUpScript: []string{ + "create database db01;", + "use db01;", + "create table db01.t01 (pk int primary key);", + "insert into db01.t01 values (101), (202), (303);", + "call dolt_commit('-Am', 'commit on main');", + "call dolt_checkout('-b', 'branch1');", + "insert into db01.t01 values (1001), (2002), (3003);", + "call dolt_commit('-Am', 'commit on branch1');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "/* client a */ use db01;", + Expected: []sql.Row{}, + }, + { + Query: "/* client b */ use `db01/branch1`;", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ show tables;", + Expected: []sql.Row{{"t01"}}, + }, + { + Query: "/* client b */ show tables;", + Expected: []sql.Row{{"t01"}}, + }, + { + Query: "/* client a */ drop database db01;", + Expected: []sql.Row{}, + }, + { + // TODO: This test runner doesn't currently support asserting against null values + Query: "/* client a */ select database() is NULL;", + Expected: []sql.Row{{1}}, + }, + { + Query: "/* client a */ show databases like 'db01';", + Expected: []sql.Row{}, + }, + { + Query: "/* client a */ create database db01;", + Expected: []sql.Row{}, + }, + { + Query: "/* client b */ select database();", + Expected: []sql.Row{{"db01/branch1"}}, + }, + { + Query: "/* client b */ show tables;", + ExpectedErrStr: "Error 1049 (HY000): database not found: db01/branch1", + }, + }, + }, +} + +var PersistVariableTests = []queries.ScriptTest{ + { + Name: "set persisted variables with on and off", + SetUpScript: []string{ + "set @@persist.dolt_skip_replication_errors = on;", + "set @@persist.dolt_read_replica_force_pull = off;", + }, + }, + { + Name: "retrieve persisted variables", + Assertions: []queries.ScriptTestAssertion{ + { + Query: "select @@dolt_skip_replication_errors", + Expected: []sql.Row{ + {1}, + }, + }, + { + Query: "select @@dolt_read_replica_force_pull", + Expected: []sql.Row{ + {0}, + }, + }, + }, + }, + { + Name: "set persisted variables with 1 and 0", + SetUpScript: []string{ + "set @@persist.dolt_skip_replication_errors = 0;", + "set @@persist.dolt_read_replica_force_pull = 1;", + }, + }, + { + Name: "retrieve persisted variables", + Assertions: []queries.ScriptTestAssertion{ + { + Query: "select @@dolt_skip_replication_errors", + Expected: []sql.Row{ + {0}, + }, + }, + { + Query: "select @@dolt_read_replica_force_pull", + Expected: []sql.Row{ + {1}, + }, + }, + }, + }, +} + +// testSerialSessionScriptTests creates an environment, then for each script starts a server and runs assertions, +// stopping the server in between scripts. Unlike other script test executors, scripts may influence later scripts in +// the block. +func testSerialSessionScriptTests(t *testing.T, tests []queries.ScriptTest) { + dEnv := dtestutils.CreateTestEnv() + serverConfig := sqlserver.DefaultCommandLineServerConfig() + rand.Seed(time.Now().UnixNano()) + port := 15403 + rand.Intn(25) + serverConfig = serverConfig.WithPort(port) + defer dEnv.DoltDB.Close() + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + sc, serverConfig := startServerOnEnv(t, serverConfig, dEnv) + err := sc.WaitForStart() + require.NoError(t, err) + + conn1, sess1 := newConnection(t, serverConfig) + + t.Run(test.Name, func(t *testing.T) { + for _, setupStatement := range test.SetUpScript { + _, err := sess1.Exec(setupStatement) + require.NoError(t, err) + } + + for _, assertion := range test.Assertions { + t.Run(assertion.Query, func(t *testing.T) { + activeSession := sess1 + rows, err := activeSession.Query(assertion.Query) + + if len(assertion.ExpectedErrStr) > 0 { + require.EqualError(t, err, assertion.ExpectedErrStr) + } else if assertion.ExpectedErr != nil { + require.True(t, assertion.ExpectedErr.Is(err), "expected error %v, got %v", assertion.ExpectedErr, err) + } else if assertion.Expected != nil { + require.NoError(t, err) + assertResultsEqual(t, assertion.Expected, rows) + } else { + require.Fail(t, "unsupported ScriptTestAssertion property: %v", assertion) + } + if rows != nil { + require.NoError(t, rows.Close()) + } + }) + } + }) + + require.NoError(t, conn1.Close()) + + sc.Stop() + err = sc.WaitForStop() + require.NoError(t, err) + }) + } +} + +func makeDestinationSlice(t *testing.T, columnTypes []*gosql.ColumnType) []interface{} { + dest := make([]any, len(columnTypes)) + for i, columnType := range columnTypes { + switch strings.ToLower(columnType.DatabaseTypeName()) { + case "int", "tinyint", "bigint": + var integer int + dest[i] = &integer + case "text": + var s string + dest[i] = &s + default: + require.Fail(t, "unsupported type: "+columnType.DatabaseTypeName()) + } + } + + return dest +} + +func startServerOnEnv(t *testing.T, serverConfig servercfg.ServerConfig, dEnv *env.DoltEnv) (*svcs.Controller, servercfg.ServerConfig) { + sc := svcs.NewController() + go func() { + _, _ = sqlserver.Serve(context.Background(), "0.0.0", serverConfig, sc, dEnv) + }() + err := sc.WaitForStart() + require.NoError(t, err) + + return sc, serverConfig +} + +// newConnection takes sqlserver.serverConfig and opens a connection, and will return that connection with a new session +func newConnection(t *testing.T, serverConfig servercfg.ServerConfig) (*dbr.Connection, *dbr.Session) { + const dbName = "dolt" + conn, err := dbr.Open("mysql", servercfg.ConnectionString(serverConfig, dbName), nil) + require.NoError(t, err) + sess := conn.NewSession(nil) + return conn, sess +} + +// startServer will start sql-server with given host, unix socket file path and whether to use specific port, which is defined randomly. +func startServer(t *testing.T, withPort bool, host string, unixSocketPath string) (*env.DoltEnv, *svcs.Controller, servercfg.ServerConfig) { + dEnv := dtestutils.CreateTestEnv() + serverConfig := sqlserver.DefaultCommandLineServerConfig() + if withPort { + rand.Seed(time.Now().UnixNano()) + port := 15403 + rand.Intn(25) + serverConfig = serverConfig.WithPort(port) + } + if host != "" { + serverConfig = serverConfig.WithHost(host) + } + if unixSocketPath != "" { + serverConfig = serverConfig.WithSocket(unixSocketPath) + } + + onEnv, config := startServerOnEnv(t, serverConfig, dEnv) + return dEnv, onEnv, config +} + +func assertResultsEqual(t *testing.T, expected []sql.Row, rows *gosql.Rows) { + columnTypes, err := rows.ColumnTypes() + assert.NoError(t, err) + dest := makeDestinationSlice(t, columnTypes) + + for _, expectedRow := range expected { + ok := rows.Next() + if !ok { + assert.Fail(t, "Fewer results than expected") + } + err := rows.Scan(dest...) + assert.NoError(t, err) + assert.Equal(t, len(expectedRow), len(dest), + "Different number of columns returned than expected") + + for j, expectedValue := range expectedRow { + switch strings.ToUpper(columnTypes[j].DatabaseTypeName()) { + case "TEXT": + actualValue, ok := dest[j].(*string) + assert.True(t, ok) + assert.Equal(t, expectedValue, *actualValue) + case "INT", "TINYINT", "BIGINT": + actualValue, ok := dest[j].(*int) + assert.True(t, ok) + assert.Equal(t, expectedValue, *actualValue) + default: + assert.Fail(t, "Unsupported datatype: %s", columnTypes[j].DatabaseTypeName()) + } + } + } + + if rows.Next() { + assert.Fail(t, "More results than expected") + } +} + +func testMultiSessionScriptTests(t *testing.T, tests []queries.ScriptTest) { + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + dEnv, sc, serverConfig := startServer(t, true, "", "") + err := sc.WaitForStart() + require.NoError(t, err) + defer dEnv.DoltDB.Close() + + conn1, sess1 := newConnection(t, serverConfig) + conn2, sess2 := newConnection(t, serverConfig) + + t.Run(test.Name, func(t *testing.T) { + for _, setupStatement := range test.SetUpScript { + _, err := sess1.Exec(setupStatement) + require.NoError(t, err) + } + + for _, assertion := range test.Assertions { + t.Run(assertion.Query, func(t *testing.T) { + var activeSession *dbr.Session + if strings.Contains(strings.ToLower(assertion.Query), "/* client a */") { + activeSession = sess1 + } else if strings.Contains(strings.ToLower(assertion.Query), "/* client b */") { + activeSession = sess2 + } else { + require.Fail(t, "unsupported client specification: "+assertion.Query) + } + + rows, err := activeSession.Query(assertion.Query) + + if len(assertion.ExpectedErrStr) > 0 { + require.EqualError(t, err, assertion.ExpectedErrStr) + } else if assertion.ExpectedErr != nil { + require.True(t, assertion.ExpectedErr.Is(err), "expected error %v, got %v", assertion.ExpectedErr, err) + } else if assertion.Expected != nil { + require.NoError(t, err) + assertResultsEqual(t, assertion.Expected, rows) + } else if assertion.SkipResultsCheck { + // no-op + } else { + t.Fatalf("unsupported ScriptTestAssertion property: %v", assertion) + } + if rows != nil { + require.NoError(t, rows.Close()) + } + }) + } + }) + + require.NoError(t, conn1.Close()) + require.NoError(t, conn2.Close()) + + sc.Stop() + err = sc.WaitForStop() + require.NoError(t, err) + }) + } +}