github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/merge/schema_integration_test.go (about)

     1  // Copyright 2020 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package merge_test
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  
    25  	"github.com/dolthub/dolt/go/cmd/dolt/cli"
    26  	"github.com/dolthub/dolt/go/cmd/dolt/commands"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/merge"
    31  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    32  	"github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo"
    33  	"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
    34  	"github.com/dolthub/dolt/go/store/types"
    35  )
    36  
    37  type testCommand struct {
    38  	cmd  cli.Command
    39  	args args
    40  }
    41  
    42  func (tc testCommand) exec(t *testing.T, ctx context.Context, dEnv *env.DoltEnv) int {
    43  	cliCtx, err := commands.NewArgFreeCliContext(ctx, dEnv)
    44  	require.NoError(t, err)
    45  	return tc.cmd.Exec(ctx, tc.cmd.Name(), tc.args, dEnv, cliCtx)
    46  }
    47  
    48  type args []string
    49  
    50  // TestMergeSchemas are schema merge integration tests from 2020
    51  func TestMergeSchemas(t *testing.T) {
    52  	for _, test := range mergeSchemaTests {
    53  		t.Run(test.name, func(t *testing.T) {
    54  			testMergeSchemas(t, test)
    55  		})
    56  	}
    57  	for _, test := range mergeSchemaConflictTests {
    58  		t.Run(test.name, func(t *testing.T) {
    59  			testMergeSchemasWithConflicts(t, test)
    60  		})
    61  	}
    62  	for _, test := range mergeForeignKeyTests {
    63  		t.Run(test.name, func(t *testing.T) {
    64  			testMergeForeignKeys(t, test)
    65  		})
    66  	}
    67  }
    68  
    69  type mergeSchemaTest struct {
    70  	name  string
    71  	setup []testCommand
    72  	sch   schema.Schema
    73  	skip  bool
    74  }
    75  
    76  type mergeSchemaConflictTest struct {
    77  	name        string
    78  	setup       []testCommand
    79  	expConflict merge.SchemaConflict
    80  	expectedErr error
    81  }
    82  
    83  type mergeForeignKeyTest struct {
    84  	name          string
    85  	setup         []testCommand
    86  	fkColl        *doltdb.ForeignKeyCollection
    87  	expFKConflict []merge.FKConflict
    88  }
    89  
    90  var setupCommon = []testCommand{
    91  	{commands.SqlCmd{}, []string{"-q", "create table test (" +
    92  		"pk int not null primary key," +
    93  		"c1 int not null," +
    94  		"c2 int," +
    95  		"c3 int);"}},
    96  	{commands.SqlCmd{}, []string{"-q", "create index c1_idx on test(c1)"}},
    97  	{commands.AddCmd{}, []string{"."}},
    98  	{commands.CommitCmd{}, []string{"-m", "setup common"}},
    99  	{commands.BranchCmd{}, []string{"other"}},
   100  }
   101  
   102  var mergeSchemaTests = []mergeSchemaTest{
   103  	{
   104  		name:  "no changes",
   105  		setup: []testCommand{},
   106  		sch: schemaFromColsAndIdxs(
   107  			colCollection(
   108  				newColTypeInfo("pk", uint64(3228), typeinfo.Int32Type, true, schema.NotNullConstraint{}),
   109  				newColTypeInfo("c1", uint64(8201), typeinfo.Int32Type, false, schema.NotNullConstraint{}),
   110  				newColTypeInfo("c2", uint64(8539), typeinfo.Int32Type, false),
   111  				newColTypeInfo("c3", uint64(4696), typeinfo.Int32Type, false)),
   112  			schema.NewIndex("c1_idx", []uint64{8201}, []uint64{8201, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   113  		),
   114  	},
   115  	{
   116  		name: "add cols, drop cols, merge",
   117  		setup: []testCommand{
   118  			{commands.SqlCmd{}, []string{"-q", "alter table test drop column c2;"}},
   119  			{commands.SqlCmd{}, []string{"-q", "alter table test add column c8 int;"}},
   120  			{commands.AddCmd{}, []string{"."}},
   121  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   122  			{commands.CheckoutCmd{}, []string{"other"}},
   123  			{commands.SqlCmd{}, []string{"-q", "alter table test drop column c3;"}},
   124  			{commands.SqlCmd{}, []string{"-q", "alter table test add column c9 int;"}},
   125  			{commands.AddCmd{}, []string{"."}},
   126  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   127  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   128  		},
   129  		sch: schemaFromColsAndIdxs(
   130  			colCollection(
   131  				newColTypeInfo("pk", uint64(3228), typeinfo.Int32Type, true, schema.NotNullConstraint{}),
   132  				newColTypeInfo("c1", uint64(8201), typeinfo.Int32Type, false, schema.NotNullConstraint{}),
   133  				newColTypeInfo("c8", uint64(12393), typeinfo.Int32Type, false),
   134  				newColTypeInfo("c9", uint64(4508), typeinfo.Int32Type, false)),
   135  			schema.NewIndex("c1_idx", []uint64{8201}, []uint64{8201, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   136  		),
   137  	},
   138  	{
   139  		name: "add constraint, drop constraint, merge",
   140  		setup: []testCommand{
   141  			{commands.SqlCmd{}, []string{"-q", "alter table test modify c1 int null;"}},
   142  			{commands.AddCmd{}, []string{"."}},
   143  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   144  			{commands.CheckoutCmd{}, []string{"other"}},
   145  			{commands.SqlCmd{}, []string{"-q", "alter table test modify c2 int not null;"}},
   146  			{commands.AddCmd{}, []string{"."}},
   147  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   148  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   149  		},
   150  		sch: schemaFromColsAndIdxs(
   151  			colCollection(
   152  				newColTypeInfo("pk", uint64(3228), typeinfo.Int32Type, true, schema.NotNullConstraint{}),
   153  				newColTypeInfo("c1", uint64(8201), typeinfo.Int32Type, false),
   154  				newColTypeInfo("c2", uint64(8539), typeinfo.Int32Type, false, schema.NotNullConstraint{}),
   155  				newColTypeInfo("c3", uint64(4696), typeinfo.Int32Type, false)),
   156  			schema.NewIndex("c1_idx", []uint64{8201}, []uint64{8201, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   157  		),
   158  	},
   159  	{
   160  		name: "add index, drop index, merge",
   161  		setup: []testCommand{
   162  			{commands.SqlCmd{}, []string{"-q", "create index c3_idx on test(c3);"}},
   163  			{commands.AddCmd{}, []string{"."}},
   164  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   165  			{commands.CheckoutCmd{}, []string{"other"}},
   166  			{commands.SqlCmd{}, []string{"-q", "alter table test drop index c1_idx;"}},
   167  			{commands.AddCmd{}, []string{"."}},
   168  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   169  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   170  		},
   171  		sch: schemaFromColsAndIdxs(
   172  			colCollection(
   173  				newColTypeInfo("pk", uint64(3228), typeinfo.Int32Type, true, schema.NotNullConstraint{}),
   174  				newColTypeInfo("c1", uint64(8201), typeinfo.Int32Type, false, schema.NotNullConstraint{}),
   175  				newColTypeInfo("c2", uint64(8539), typeinfo.Int32Type, false),
   176  				newColTypeInfo("c3", uint64(4696), typeinfo.Int32Type, false)),
   177  			schema.NewIndex("c3_idx", []uint64{4696}, []uint64{4696, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   178  		),
   179  	},
   180  	{
   181  		name: "rename columns",
   182  		setup: []testCommand{
   183  			{commands.SqlCmd{}, []string{"-q", "alter table test rename column c3 to c33;"}},
   184  			{commands.AddCmd{}, []string{"."}},
   185  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   186  			{commands.CheckoutCmd{}, []string{"other"}},
   187  			{commands.SqlCmd{}, []string{"-q", "alter table test rename column c2 to c22;"}},
   188  			{commands.AddCmd{}, []string{"."}},
   189  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   190  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   191  		},
   192  		// hmmm, we created new columns with a rename?
   193  		sch: schemaFromColsAndIdxs(
   194  			colCollection(
   195  				newColTypeInfo("pk", uint64(3228), typeinfo.Int32Type, true, schema.NotNullConstraint{}),
   196  				newColTypeInfo("c1", uint64(8201), typeinfo.Int32Type, false, schema.NotNullConstraint{}),
   197  				newColTypeInfo("c22", uint64(8539), typeinfo.Int32Type, false),
   198  				newColTypeInfo("c33", uint64(4696), typeinfo.Int32Type, false)),
   199  			schema.NewIndex("c1_idx", []uint64{8201}, []uint64{8201, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   200  		),
   201  	},
   202  	{
   203  		name: "rename indexes",
   204  		setup: []testCommand{
   205  			{commands.SqlCmd{}, []string{"-q", "alter table test drop index c1_idx;"}},
   206  			{commands.SqlCmd{}, []string{"-q", "create index c1_index on test(c1);"}},
   207  			{commands.AddCmd{}, []string{"."}},
   208  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   209  		},
   210  		sch: schemaFromColsAndIdxs(
   211  			colCollection(
   212  				newColTypeInfo("pk", uint64(3228), typeinfo.Int32Type, true, schema.NotNullConstraint{}),
   213  				newColTypeInfo("c1", uint64(8201), typeinfo.Int32Type, false, schema.NotNullConstraint{}),
   214  				newColTypeInfo("c2", uint64(8539), typeinfo.Int32Type, false),
   215  				newColTypeInfo("c3", uint64(4696), typeinfo.Int32Type, false)),
   216  			schema.NewIndex("c1_index", []uint64{8201}, []uint64{8201, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   217  		),
   218  	},
   219  	{
   220  		name: "add same column on both branches, merge",
   221  		setup: []testCommand{
   222  			{commands.SqlCmd{}, []string{"-q", "alter table test add column c4 int;"}},
   223  			{commands.AddCmd{}, []string{"."}},
   224  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   225  			{commands.CheckoutCmd{}, []string{"other"}},
   226  			{commands.SqlCmd{}, []string{"-q", "alter table test add column c4 int;"}},
   227  			{commands.AddCmd{}, []string{"."}},
   228  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   229  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   230  		},
   231  		sch: schemaFromColsAndIdxs(
   232  			colCollection(
   233  				newColTypeInfo("pk", uint64(3228), typeinfo.Int32Type, true, schema.NotNullConstraint{}),
   234  				newColTypeInfo("c1", uint64(8201), typeinfo.Int32Type, false, schema.NotNullConstraint{}),
   235  				newColTypeInfo("c2", uint64(8539), typeinfo.Int32Type, false),
   236  				newColTypeInfo("c3", uint64(4696), typeinfo.Int32Type, false),
   237  				newColTypeInfo("c4", uint64(1716), typeinfo.Int32Type, false)),
   238  			schema.NewIndex("c1_idx", []uint64{8201}, []uint64{8201, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   239  		),
   240  	},
   241  	{
   242  		name: "add same index on both branches, merge",
   243  		setup: []testCommand{
   244  			{commands.SqlCmd{}, []string{"-q", "create index c3_idx on test(c3);"}},
   245  			{commands.AddCmd{}, []string{"."}},
   246  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   247  			{commands.CheckoutCmd{}, []string{"other"}},
   248  			{commands.SqlCmd{}, []string{"-q", "create index c3_idx on test(c3);"}},
   249  			{commands.AddCmd{}, []string{"."}},
   250  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   251  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   252  		},
   253  		sch: schemaFromColsAndIdxs(
   254  			colCollection(
   255  				newColTypeInfo("pk", uint64(3228), typeinfo.Int32Type, true, schema.NotNullConstraint{}),
   256  				newColTypeInfo("c1", uint64(8201), typeinfo.Int32Type, false, schema.NotNullConstraint{}),
   257  				newColTypeInfo("c2", uint64(8539), typeinfo.Int32Type, false),
   258  				newColTypeInfo("c3", uint64(4696), typeinfo.Int32Type, false)),
   259  			schema.NewIndex("c1_idx", []uint64{8201}, []uint64{8201, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   260  			schema.NewIndex("c3_idx", []uint64{4696}, []uint64{4696, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   261  		),
   262  	},
   263  }
   264  
   265  var mergeSchemaConflictTests = []mergeSchemaConflictTest{
   266  	{
   267  		name: "no conflicts",
   268  		expConflict: merge.SchemaConflict{
   269  			TableName: "test",
   270  		},
   271  	},
   272  	{
   273  		name: "column name collisions",
   274  		setup: []testCommand{
   275  			{commands.SqlCmd{}, []string{"-q", "alter table test rename column c3 to c4;"}},
   276  			{commands.SqlCmd{}, []string{"-q", "alter table test add column C6 int;"}},
   277  			{commands.AddCmd{}, []string{"."}},
   278  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   279  			{commands.CheckoutCmd{}, []string{"other"}},
   280  			{commands.SqlCmd{}, []string{"-q", "alter table test rename column c2 to c4;"}},
   281  			{commands.SqlCmd{}, []string{"-q", "alter table test add column c6 int;"}},
   282  			{commands.AddCmd{}, []string{"."}},
   283  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   284  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   285  		},
   286  		expConflict: merge.SchemaConflict{
   287  			TableName: "test",
   288  			ColConflicts: []merge.ColConflict{
   289  				{
   290  					Kind:   merge.NameCollision,
   291  					Ours:   newColTypeInfo("C6", uint64(13258), typeinfo.Int32Type, false),
   292  					Theirs: newColTypeInfo("c6", uint64(13258), typeinfo.Int32Type, false),
   293  				},
   294  				{
   295  					Kind:   merge.NameCollision,
   296  					Ours:   newColTypeInfo("c4", uint64(4696), typeinfo.Int32Type, false),
   297  					Theirs: newColTypeInfo("c4", uint64(8539), typeinfo.Int32Type, false),
   298  				},
   299  			},
   300  		},
   301  	},
   302  	{
   303  		name: "index name collisions",
   304  		setup: []testCommand{
   305  			{commands.SqlCmd{}, []string{"-q", "create index `both` on test (c1,c2);"}},
   306  			{commands.AddCmd{}, []string{"."}},
   307  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   308  			{commands.CheckoutCmd{}, []string{"other"}},
   309  			{commands.SqlCmd{}, []string{"-q", "create index `both` on test (c2, c3);"}},
   310  			{commands.AddCmd{}, []string{"."}},
   311  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   312  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   313  		},
   314  		expConflict: merge.SchemaConflict{
   315  			TableName: "test",
   316  			IdxConflicts: []merge.IdxConflict{
   317  				{
   318  					Kind:   merge.NameCollision,
   319  					Ours:   schema.NewIndex("both", []uint64{8201, 8539}, []uint64{8201, 8539, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   320  					Theirs: schema.NewIndex("both", []uint64{8539, 4696}, []uint64{8539, 4696, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   321  				},
   322  			},
   323  		},
   324  	},
   325  	{
   326  		name: "column definition collision",
   327  		setup: []testCommand{
   328  			{commands.SqlCmd{}, []string{"-q", "alter table test add column c40 int;"}},
   329  			{commands.SqlCmd{}, []string{"-q", "alter table test add column c6 bigint;"}},
   330  			{commands.AddCmd{}, []string{"."}},
   331  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   332  			{commands.CheckoutCmd{}, []string{"other"}},
   333  			{commands.SqlCmd{}, []string{"-q", "alter table test add column c40 int;"}},
   334  			{commands.SqlCmd{}, []string{"-q", "alter table test rename column c40 to c44;"}},
   335  			{commands.SqlCmd{}, []string{"-q", "alter table test add column c6 tinyint;"}},
   336  			{commands.AddCmd{}, []string{"."}},
   337  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   338  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   339  		},
   340  		expConflict: merge.SchemaConflict{
   341  			TableName: "test",
   342  			ColConflicts: []merge.ColConflict{
   343  				{
   344  					Kind:   merge.TagCollision,
   345  					Ours:   newColTypeInfo("c40", uint64(679), typeinfo.Int32Type, false),
   346  					Theirs: newColTypeInfo("c44", uint64(679), typeinfo.Int32Type, false),
   347  				},
   348  				{
   349  					Kind:   merge.TagCollision,
   350  					Ours:   newColTypeInfo("c6", uint64(10774), typeinfo.Int64Type, false),
   351  					Theirs: newColTypeInfo("c6", uint64(10774), typeinfo.Int8Type, false),
   352  				},
   353  			},
   354  		},
   355  	},
   356  	{
   357  		name: "index definition collision",
   358  		setup: []testCommand{
   359  			{commands.SqlCmd{}, []string{"-q", "create index c3_idx on test(c3);"}},
   360  			{commands.AddCmd{}, []string{"."}},
   361  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   362  			{commands.CheckoutCmd{}, []string{"other"}},
   363  			{commands.SqlCmd{}, []string{"-q", "create index c3_index on test(c3);"}},
   364  			{commands.AddCmd{}, []string{"."}},
   365  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   366  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   367  		},
   368  		expConflict: merge.SchemaConflict{
   369  			TableName: "test",
   370  			IdxConflicts: []merge.IdxConflict{
   371  				{
   372  					Kind:   merge.TagCollision,
   373  					Ours:   schema.NewIndex("c3_idx", []uint64{4696}, []uint64{4696, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   374  					Theirs: schema.NewIndex("c3_index", []uint64{4696}, []uint64{4696, 3228}, nil, schema.IndexProperties{IsUserDefined: true}),
   375  				},
   376  			},
   377  		},
   378  	},
   379  	{
   380  		name: "check definition collision",
   381  		setup: []testCommand{
   382  			{commands.SqlCmd{}, []string{"-q", "alter table test add constraint chk check (c3 > 0);"}},
   383  			{commands.AddCmd{}, []string{"."}},
   384  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   385  			{commands.CheckoutCmd{}, []string{"other"}},
   386  			{commands.SqlCmd{}, []string{"-q", "alter table test add constraint chk check (c3 < 0);"}},
   387  			{commands.AddCmd{}, []string{"."}},
   388  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   389  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   390  		},
   391  		expConflict: merge.SchemaConflict{
   392  			TableName: "test",
   393  			ChkConflicts: []merge.ChkConflict{
   394  				{
   395  					Kind:   merge.TagCollision,
   396  					Ours:   schema.NewCheck("chk", "(c3 > 0)", true),
   397  					Theirs: schema.NewCheck("chk", "(c3 < 0)", true),
   398  				},
   399  				{
   400  					Kind:   merge.NameCollision,
   401  					Ours:   schema.NewCheck("chk", "(c3 > 0)", true),
   402  					Theirs: schema.NewCheck("chk", "(c3 < 0)", true),
   403  				},
   404  			},
   405  		},
   406  	},
   407  	{
   408  		name: "modified check",
   409  		setup: []testCommand{
   410  			{commands.SqlCmd{}, []string{"-q", "alter table test add constraint chk check (c3 > 0);"}},
   411  			{commands.AddCmd{}, []string{"."}},
   412  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   413  
   414  			{commands.CheckoutCmd{}, []string{"other"}},
   415  			{commands.SqlCmd{}, []string{"-q", "alter table test add constraint chk check (c3 > 0);"}},
   416  			{commands.AddCmd{}, []string{"."}},
   417  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   418  
   419  			{commands.MergeCmd{}, []string{env.DefaultInitBranch}},
   420  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   421  			{commands.MergeCmd{}, []string{"other"}},
   422  
   423  			{commands.SqlCmd{}, []string{"-q", "alter table test drop constraint chk;"}},
   424  			{commands.SqlCmd{}, []string{"-q", "alter table test add constraint chk check (c3 > 10);"}},
   425  			{commands.AddCmd{}, []string{"."}},
   426  			{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   427  
   428  			{commands.CheckoutCmd{}, []string{"other"}},
   429  			{commands.SqlCmd{}, []string{"-q", "alter table test drop constraint chk;"}},
   430  			{commands.SqlCmd{}, []string{"-q", "alter table test add constraint chk check (c3 < 10);"}},
   431  			{commands.AddCmd{}, []string{"."}},
   432  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   433  		},
   434  		expConflict: merge.SchemaConflict{
   435  			TableName: "test",
   436  			ChkConflicts: []merge.ChkConflict{
   437  				{
   438  					Kind:   merge.TagCollision,
   439  					Ours:   schema.NewCheck("chk", "(c3 > 10)", true),
   440  					Theirs: schema.NewCheck("chk", "(c3 < 10)", true),
   441  				},
   442  				{
   443  					Kind:   merge.NameCollision,
   444  					Ours:   schema.NewCheck("chk", "(c3 > 10)", true),
   445  					Theirs: schema.NewCheck("chk", "(c3 < 10)", true),
   446  				},
   447  			},
   448  		},
   449  	},
   450  	{
   451  		name: "primary key conflicts",
   452  		setup: []testCommand{
   453  			{commands.CheckoutCmd{}, []string{"other"}},
   454  			{commands.SqlCmd{}, []string{"-q", "alter table test drop primary key;"}},
   455  			{commands.AddCmd{}, []string{"."}},
   456  			{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   457  			{commands.CheckoutCmd{}, []string{env.DefaultInitBranch}},
   458  		},
   459  		expectedErr: merge.ErrMergeWithDifferentPks.New("test"),
   460  	},
   461  }
   462  
   463  var setupForeignKeyTests = []testCommand{
   464  	{commands.SqlCmd{}, []string{"-q", "create table test (" +
   465  		"pk int not null primary key," +
   466  		"t1 int not null," +
   467  		"t2 int," +
   468  		"t3 int);"}},
   469  	{commands.SqlCmd{}, []string{"-q", "alter table test add index t1_idx (t1);"}},
   470  	{commands.SqlCmd{}, []string{"-q", "create table quiz (" +
   471  		"pk int not null primary key," +
   472  		"q1 int not null," +
   473  		"q2 int not null," +
   474  		"index q2_idx (q2)," +
   475  		"constraint q1_fk foreign key (q1) references test(t1));"}},
   476  	{commands.AddCmd{}, []string{"."}},
   477  	{commands.CommitCmd{}, []string{"-m", "setup common"}},
   478  	{commands.BranchCmd{}, []string{"other"}},
   479  }
   480  
   481  var mergeForeignKeyTests = []mergeForeignKeyTest{
   482  	{
   483  		name:  "no changes",
   484  		setup: []testCommand{},
   485  		fkColl: fkCollection(doltdb.ForeignKey{
   486  			Name:                   "q1_fk",
   487  			TableName:              "quiz",
   488  			TableIndex:             "q1",
   489  			TableColumns:           []uint64{13001},
   490  			ReferencedTableName:    "test",
   491  			ReferencedTableIndex:   "t1_idx",
   492  			ReferencedTableColumns: []uint64{12111},
   493  			UnresolvedFKDetails: doltdb.UnresolvedFKDetails{
   494  				TableColumns:           []string{"q1"},
   495  				ReferencedTableColumns: []string{"t1"},
   496  			},
   497  		}),
   498  		expFKConflict: []merge.FKConflict{},
   499  	},
   500  	//{
   501  	//	name: "add foreign key, drop foreign key, merge",
   502  	//	setup: []testCommand{
   503  	//		{commands.SqlCmd{}, []string{"-q", "alter table quiz add constraint q2_fk foreign key (q2) references test(t2);"}},
   504  	//		{commands.AddCmd{}, []string{"."}},
   505  	//		{commands.CommitCmd{}, []string{"-m", "modified branch main"}},
   506  	//		{commands.CheckoutCmd{}, []string{"other"}},
   507  	//		{commands.SqlCmd{}, []string{"-q", "alter table quiz drop constraint q1_fk;"}},
   508  	//		{commands.AddCmd{}, []string{"."}},
   509  	//		{commands.CommitCmd{}, []string{"-m", "modified branch other"}},
   510  	//		{commands.CheckoutCmd{}, []string{"main"}},
   511  	//	},
   512  	//	fkColl: fkCollection(
   513  	//		&doltdb.ForeignKey{
   514  	//			Name:                   "q2_fk",
   515  	//			TableName:              "quiz",
   516  	//			TableIndex:             "dolt_fk_2",
   517  	//			TableColumns:           []uint64{12},
   518  	//			ReferencedTableName:    "test",
   519  	//			ReferencedTableIndex:   "dolt_fk_2",
   520  	//			ReferencedTableColumns: []uint64{2}}),
   521  	//	expFKConflict: []merge.FKConflict{},
   522  	//},
   523  }
   524  
   525  func colCollection(cols ...schema.Column) *schema.ColCollection {
   526  	return schema.NewColCollection(cols...)
   527  }
   528  
   529  // SchemaFromColsAndIdxs creates a Schema from a ColCollection and an IndexCollection.
   530  func schemaFromColsAndIdxs(allCols *schema.ColCollection, indexes ...schema.Index) schema.Schema {
   531  	sch := schema.MustSchemaFromCols(allCols)
   532  	sch.Indexes().AddIndex(indexes...)
   533  	return sch
   534  }
   535  
   536  func newColTypeInfo(name string, tag uint64, typeInfo typeinfo.TypeInfo, partOfPK bool, constraints ...schema.ColConstraint) schema.Column {
   537  	c, err := schema.NewColumnWithTypeInfo(name, tag, typeInfo, partOfPK, "", false, "", constraints...)
   538  	if err != nil {
   539  		panic("could not create column")
   540  	}
   541  	return c
   542  }
   543  
   544  func fkCollection(fks ...doltdb.ForeignKey) *doltdb.ForeignKeyCollection {
   545  	fkc, err := doltdb.NewForeignKeyCollection(fks...)
   546  	if err != nil {
   547  		panic(err)
   548  	}
   549  	return fkc
   550  }
   551  
   552  func testMergeSchemas(t *testing.T, test mergeSchemaTest) {
   553  	if test.skip {
   554  		t.Skip()
   555  		return
   556  	}
   557  
   558  	dEnv := dtestutils.CreateTestEnv()
   559  	defer dEnv.DoltDB.Close()
   560  	ctx := context.Background()
   561  
   562  	cliCtx, _ := commands.NewArgFreeCliContext(ctx, dEnv)
   563  
   564  	for _, c := range setupCommon {
   565  		exit := c.exec(t, ctx, dEnv)
   566  		require.Equal(t, 0, exit)
   567  	}
   568  	for _, c := range test.setup {
   569  		exit := c.exec(t, ctx, dEnv)
   570  		require.Equal(t, 0, exit)
   571  	}
   572  
   573  	// assert that we're on main
   574  	exitCode := commands.CheckoutCmd{}.Exec(ctx, "checkout", []string{env.DefaultInitBranch}, dEnv, cliCtx)
   575  	require.Equal(t, 0, exitCode)
   576  
   577  	// merge branches
   578  	exitCode = commands.MergeCmd{}.Exec(ctx, "merge", []string{"other"}, dEnv, cliCtx)
   579  	assert.Equal(t, 0, exitCode)
   580  
   581  	wr, err := dEnv.WorkingRoot(ctx)
   582  	assert.NoError(t, err)
   583  	tbl, ok, err := wr.GetTable(ctx, doltdb.TableName{Name: "test"})
   584  	assert.True(t, ok)
   585  	require.NoError(t, err)
   586  	sch, err := tbl.GetSchema(ctx)
   587  	require.NoError(t, err)
   588  
   589  	assert.Equal(t, test.sch.GetAllCols(), sch.GetAllCols())
   590  	assert.Equal(t, test.sch.Indexes(), sch.Indexes())
   591  }
   592  
   593  func testMergeSchemasWithConflicts(t *testing.T, test mergeSchemaConflictTest) {
   594  	getSchema := func(t *testing.T, dEnv *env.DoltEnv) schema.Schema {
   595  		ctx := context.Background()
   596  		wr, err := dEnv.WorkingRoot(ctx)
   597  		assert.NoError(t, err)
   598  		tbl, ok, err := wr.GetTable(ctx, doltdb.TableName{Name: "test"})
   599  		assert.True(t, ok)
   600  		require.NoError(t, err)
   601  		sch, err := tbl.GetSchema(ctx)
   602  		require.NoError(t, err)
   603  		return sch
   604  	}
   605  
   606  	dEnv := dtestutils.CreateTestEnv()
   607  	defer dEnv.DoltDB.Close()
   608  	ctx := context.Background()
   609  	for _, c := range setupCommon {
   610  		exit := c.exec(t, ctx, dEnv)
   611  		require.Equal(t, 0, exit)
   612  	}
   613  
   614  	ancSch := getSchema(t, dEnv)
   615  
   616  	for _, c := range test.setup {
   617  		exit := c.exec(t, ctx, dEnv)
   618  		require.Equal(t, 0, exit)
   619  	}
   620  
   621  	cliCtx, _ := commands.NewArgFreeCliContext(ctx, dEnv)
   622  
   623  	// assert that we're on main
   624  	exitCode := commands.CheckoutCmd{}.Exec(ctx, "checkout", []string{env.DefaultInitBranch}, dEnv, cliCtx)
   625  	require.Equal(t, 0, exitCode)
   626  
   627  	mainSch := getSchema(t, dEnv)
   628  
   629  	exitCode = commands.CheckoutCmd{}.Exec(ctx, "checkout", []string{"other"}, dEnv, cliCtx)
   630  	require.Equal(t, 0, exitCode)
   631  
   632  	otherSch := getSchema(t, dEnv)
   633  
   634  	_, actConflicts, mergeInfo, _, err := merge.SchemaMerge(context.Background(), types.Format_Default, mainSch, otherSch, ancSch, "test")
   635  	assert.False(t, mergeInfo.InvalidateSecondaryIndexes)
   636  	if test.expectedErr != nil {
   637  		// We don't use errors.Is here because errors generated by `Kind.New` compare stack traces in their `Is` implementation.
   638  		assert.Equal(t, err.Error(), test.expectedErr.Error(), "Expected error '%s', instead got '%s'", test.expectedErr.Error(), err.Error())
   639  		return
   640  	}
   641  
   642  	require.NoError(t, err)
   643  	assert.Equal(t, actConflicts.TableName, "test")
   644  
   645  	assert.Equal(t, test.expConflict.Count(), actConflicts.Count())
   646  
   647  	require.Equal(t, len(test.expConflict.IdxConflicts), len(actConflicts.IdxConflicts))
   648  	for i, acc := range actConflicts.IdxConflicts {
   649  		assert.True(t, test.expConflict.IdxConflicts[i].Ours.Equals(acc.Ours))
   650  		assert.True(t, test.expConflict.IdxConflicts[i].Theirs.Equals(acc.Theirs))
   651  	}
   652  
   653  	require.Equal(t, len(test.expConflict.ColConflicts), len(actConflicts.ColConflicts))
   654  	for i, icc := range actConflicts.ColConflicts {
   655  		assert.True(t, test.expConflict.ColConflicts[i].Ours.Equals(icc.Ours))
   656  		assert.True(t, test.expConflict.ColConflicts[i].Theirs.Equals(icc.Theirs))
   657  	}
   658  
   659  	require.Equal(t, len(test.expConflict.ChkConflicts), len(actConflicts.ChkConflicts))
   660  	for i, icc := range actConflicts.ChkConflicts {
   661  		assert.True(t, test.expConflict.ChkConflicts[i].Ours == icc.Ours)
   662  		assert.True(t, test.expConflict.ChkConflicts[i].Theirs == icc.Theirs)
   663  	}
   664  }
   665  
   666  func testMergeForeignKeys(t *testing.T, test mergeForeignKeyTest) {
   667  	dEnv := dtestutils.CreateTestEnv()
   668  	defer dEnv.DoltDB.Close()
   669  	ctx := context.Background()
   670  	for _, c := range setupForeignKeyTests {
   671  		exit := c.exec(t, ctx, dEnv)
   672  		require.Equal(t, 0, exit)
   673  	}
   674  
   675  	ancRoot, err := dEnv.WorkingRoot(ctx)
   676  	require.NoError(t, err)
   677  
   678  	for _, c := range test.setup {
   679  		exit := c.exec(t, ctx, dEnv)
   680  		require.Equal(t, 0, exit)
   681  	}
   682  
   683  	cliCtx, _ := commands.NewArgFreeCliContext(ctx, dEnv)
   684  
   685  	// assert that we're on main
   686  	exitCode := commands.CheckoutCmd{}.Exec(ctx, "checkout", []string{env.DefaultInitBranch}, dEnv, cliCtx)
   687  	require.Equal(t, 0, exitCode)
   688  
   689  	mainWS, err := dEnv.WorkingSet(ctx)
   690  	require.NoError(t, err)
   691  	mainRoot := mainWS.WorkingRoot()
   692  
   693  	exitCode = commands.CheckoutCmd{}.Exec(ctx, "checkout", []string{"other"}, dEnv, cliCtx)
   694  	require.Equal(t, 0, exitCode)
   695  
   696  	otherWS, err := dEnv.WorkingSet(ctx)
   697  	require.NoError(t, err)
   698  	otherRoot := otherWS.WorkingRoot()
   699  
   700  	opts := editor.TestEditorOptions(dEnv.DoltDB.ValueReadWriter())
   701  	mo := merge.MergeOpts{IsCherryPick: false}
   702  	result, err := merge.MergeRoots(sql.NewContext(ctx), mainRoot, otherRoot, ancRoot, mainWS, otherWS, opts, mo)
   703  	assert.NoError(t, err)
   704  
   705  	fkc, err := result.Root.GetForeignKeyCollection(ctx)
   706  	assert.NoError(t, err)
   707  	assert.Equal(t, test.fkColl.Count(), fkc.Count())
   708  
   709  	err = test.fkColl.Iter(func(expFK doltdb.ForeignKey) (stop bool, err error) {
   710  		actFK, ok := fkc.GetByTags(expFK.TableColumns, expFK.ReferencedTableColumns)
   711  		assert.True(t, ok)
   712  		assert.Equal(t, expFK, actFK)
   713  		return false, nil
   714  	})
   715  	assert.NoError(t, err)
   716  }