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

     1  // Copyright 2022 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 sqle
    16  
    17  import (
    18  	"context"
    19  	goerrors "errors"
    20  	"fmt"
    21  	"testing"
    22  
    23  	"github.com/dolthub/go-mysql-server/sql"
    24  	"github.com/dolthub/go-mysql-server/sql/planbuilder"
    25  	gmstypes "github.com/dolthub/go-mysql-server/sql/types"
    26  	"github.com/dolthub/vitess/go/sqltypes"
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  	"gopkg.in/src-d/go-errors.v1"
    30  
    31  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    32  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
    33  	"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
    34  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    35  	"github.com/dolthub/dolt/go/libraries/doltcore/row"
    36  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    37  	"github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo"
    38  	"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
    39  	"github.com/dolthub/dolt/go/store/types"
    40  )
    41  
    42  func TestRenameTable(t *testing.T) {
    43  	setup := `
    44  	CREATE TABLE people (
    45  	    id varchar(36) primary key,
    46  	    name varchar(40) not null,
    47  	    age int unsigned,
    48  	    is_married int,
    49  	    title varchar(40),
    50  	    INDEX idx_name (name)
    51  	);
    52  	INSERT INTO people VALUES
    53  		('00000000-0000-0000-0000-000000000000', 'Bill Billerson', 32, 1, 'Senior Dufus'),
    54  		('00000000-0000-0000-0000-000000000001', 'John Johnson', 25, 0, 'Dufus'),
    55  		('00000000-0000-0000-0000-000000000002', 'Rob Robertson', 21, 0, '');
    56  	CREATE TABLE other (c0 int, c1 int);`
    57  
    58  	tests := []struct {
    59  		description string
    60  		oldName     string
    61  		newName     string
    62  		expectedErr string
    63  	}{
    64  		{
    65  			description: "rename table",
    66  			oldName:     "people",
    67  			newName:     "newPeople",
    68  		},
    69  		{
    70  			description: "table not found",
    71  			oldName:     "notFound",
    72  			newName:     "newNotfound",
    73  			expectedErr: doltdb.ErrTableNotFound.Error(),
    74  		},
    75  		{
    76  			description: "name already in use",
    77  			oldName:     "people",
    78  			newName:     "other",
    79  			expectedErr: doltdb.ErrTableExists.Error(),
    80  		},
    81  	}
    82  
    83  	for _, tt := range tests {
    84  		t.Run(tt.description, func(t *testing.T) {
    85  			ctx := context.Background()
    86  			dEnv := dtestutils.CreateTestEnv()
    87  			defer dEnv.DoltDB.Close()
    88  			root, err := dEnv.WorkingRoot(ctx)
    89  			require.NoError(t, err)
    90  
    91  			// setup tests
    92  			root, err = ExecuteSql(dEnv, root, setup)
    93  			require.NoError(t, err)
    94  
    95  			schemas, err := doltdb.GetAllSchemas(ctx, root)
    96  			require.NoError(t, err)
    97  			beforeSch := schemas[tt.oldName]
    98  
    99  			updatedRoot, err := renameTable(ctx, root, tt.oldName, tt.newName)
   100  			if len(tt.expectedErr) > 0 {
   101  				assert.Error(t, err)
   102  				assert.Contains(t, err.Error(), tt.expectedErr)
   103  				return
   104  			}
   105  			assert.NoError(t, err)
   106  			err = dEnv.UpdateWorkingRoot(ctx, root)
   107  			require.NoError(t, err)
   108  
   109  			has, err := updatedRoot.HasTable(ctx, tt.oldName)
   110  			require.NoError(t, err)
   111  			assert.False(t, has)
   112  			has, err = updatedRoot.HasTable(ctx, tt.newName)
   113  			require.NoError(t, err)
   114  			assert.True(t, has)
   115  
   116  			schemas, err = doltdb.GetAllSchemas(ctx, updatedRoot)
   117  			require.NoError(t, err)
   118  			require.Equal(t, beforeSch, schemas[tt.newName])
   119  		})
   120  	}
   121  }
   122  
   123  const tableName = "people"
   124  
   125  func TestAddColumnToTable(t *testing.T) {
   126  	origRows, sch, err := dtestutils.RowsAndSchema()
   127  	require.NoError(t, err)
   128  
   129  	tests := []struct {
   130  		name           string
   131  		tag            uint64
   132  		newColName     string
   133  		colKind        types.NomsKind
   134  		nullable       Nullable
   135  		defaultVal     *sql.ColumnDefaultValue
   136  		order          *sql.ColumnOrder
   137  		expectedSchema schema.Schema
   138  		expectedRows   []row.Row
   139  		expectedErr    string
   140  	}{
   141  		{
   142  			name:       "bool column no default",
   143  			tag:        dtestutils.NextTag,
   144  			newColName: "newCol",
   145  			colKind:    types.IntKind,
   146  			nullable:   Null,
   147  			expectedSchema: dtestutils.AddColumnToSchema(sch,
   148  				schema.NewColumn("newCol", dtestutils.NextTag, types.IntKind, false)),
   149  			expectedRows: origRows,
   150  		},
   151  		{
   152  			name:       "nullable with nil default",
   153  			tag:        dtestutils.NextTag,
   154  			newColName: "newCol",
   155  			colKind:    types.IntKind,
   156  			nullable:   Null,
   157  			expectedSchema: dtestutils.AddColumnToSchema(sch,
   158  				schemaNewColumnWithDefault("newCol", dtestutils.NextTag, types.IntKind, false, "")),
   159  			expectedRows: origRows,
   160  		},
   161  		{
   162  			name:       "nullable with non-nil default",
   163  			tag:        dtestutils.NextTag,
   164  			newColName: "newCol",
   165  			colKind:    types.IntKind,
   166  			nullable:   Null,
   167  			defaultVal: mustStringToColumnDefault("42"),
   168  			expectedSchema: dtestutils.AddColumnToSchema(sch,
   169  				schemaNewColumnWithDefault("newCol", dtestutils.NextTag, types.IntKind, false, "42")),
   170  			expectedRows: addColToRows(t, origRows, dtestutils.NextTag, types.NullValue),
   171  		},
   172  		{
   173  			name:       "first order",
   174  			tag:        dtestutils.NextTag,
   175  			newColName: "newCol",
   176  			colKind:    types.IntKind,
   177  			nullable:   Null,
   178  			defaultVal: mustStringToColumnDefault("42"),
   179  			order:      &sql.ColumnOrder{First: true},
   180  			expectedSchema: dtestutils.CreateSchema(
   181  				schemaNewColumnWithDefault("newCol", dtestutils.NextTag, types.IntKind, false, "42"),
   182  				schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   183  				schema.NewColumn("name", dtestutils.NameTag, types.StringKind, false, schema.NotNullConstraint{}),
   184  				schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   185  				schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, false, schema.NotNullConstraint{}),
   186  				schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   187  			),
   188  			expectedRows: addColToRows(t, origRows, dtestutils.NextTag, types.NullValue),
   189  		},
   190  		{
   191  			name:       "middle order",
   192  			tag:        dtestutils.NextTag,
   193  			newColName: "newCol",
   194  			colKind:    types.IntKind,
   195  			nullable:   Null,
   196  			defaultVal: mustStringToColumnDefault("42"),
   197  			order:      &sql.ColumnOrder{AfterColumn: "age"},
   198  			expectedSchema: dtestutils.CreateSchema(
   199  				schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   200  				schema.NewColumn("name", dtestutils.NameTag, types.StringKind, false, schema.NotNullConstraint{}),
   201  				schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   202  				schemaNewColumnWithDefault("newCol", dtestutils.NextTag, types.IntKind, false, "42"),
   203  				schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, false, schema.NotNullConstraint{}),
   204  				schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   205  			),
   206  			expectedRows: addColToRows(t, origRows, dtestutils.NextTag, types.NullValue),
   207  		},
   208  		{
   209  			name:        "tag collision",
   210  			tag:         dtestutils.AgeTag,
   211  			newColName:  "newCol",
   212  			colKind:     types.IntKind,
   213  			nullable:    NotNull,
   214  			expectedErr: fmt.Sprintf("cannot create column newCol on table people, the tag %d was already used in table people", dtestutils.AgeTag),
   215  		},
   216  		{
   217  			name:        "name collision",
   218  			tag:         dtestutils.NextTag,
   219  			newColName:  "age",
   220  			colKind:     types.IntKind,
   221  			nullable:    NotNull,
   222  			defaultVal:  mustStringToColumnDefault("10"),
   223  			expectedErr: "A column with the name age already exists",
   224  		},
   225  	}
   226  
   227  	for _, tt := range tests {
   228  		t.Run(tt.name, func(t *testing.T) {
   229  			ctx := context.Background()
   230  			dEnv, err := makePeopleTable(ctx, dtestutils.CreateTestEnv())
   231  			require.NoError(t, err)
   232  			defer dEnv.DoltDB.Close()
   233  
   234  			root, err := dEnv.WorkingRoot(ctx)
   235  			require.NoError(t, err)
   236  			tbl, ok, err := root.GetTable(ctx, doltdb.TableName{Name: tableName})
   237  			assert.True(t, ok)
   238  			assert.NoError(t, err)
   239  
   240  			updatedTable, err := addColumnToTable(ctx, root, tbl, tableName, tt.tag, tt.newColName, typeinfo.FromKind(tt.colKind), tt.nullable, tt.defaultVal, "", tt.order)
   241  			if len(tt.expectedErr) > 0 {
   242  				require.Error(t, err)
   243  				assert.Contains(t, err.Error(), tt.expectedErr)
   244  				return
   245  			} else {
   246  				require.NoError(t, err)
   247  				require.NoError(t, err)
   248  			}
   249  
   250  			sch, err := updatedTable.GetSchema(ctx)
   251  			require.NoError(t, err)
   252  			index := sch.Indexes().GetByName(dtestutils.IndexName)
   253  			assert.NotNil(t, index)
   254  			tt.expectedSchema.Indexes().AddIndex(index)
   255  			_, err = tt.expectedSchema.Checks().AddCheck("test-check", "age < 123", true)
   256  			require.NoError(t, err)
   257  			require.Equal(t, tt.expectedSchema, sch)
   258  		})
   259  	}
   260  }
   261  
   262  func makePeopleTable(ctx context.Context, dEnv *env.DoltEnv) (*env.DoltEnv, error) {
   263  	_, sch, err := dtestutils.RowsAndSchema()
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	root, err := dEnv.WorkingRoot(ctx)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	rows, err := durable.NewEmptyIndex(ctx, root.VRW(), root.NodeStore(), sch)
   273  	if err != nil {
   274  		return nil, err
   275  	}
   276  	indexes, err := durable.NewIndexSetWithEmptyIndexes(ctx, root.VRW(), root.NodeStore(), sch)
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  	tbl, err := doltdb.NewTable(ctx, root.VRW(), root.NodeStore(), sch, rows, indexes, nil)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  	root, err = root.PutTable(ctx, doltdb.TableName{Name: tableName}, tbl)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  	if err = dEnv.UpdateWorkingRoot(ctx, root); err != nil {
   289  		return nil, err
   290  	}
   291  	return dEnv, nil
   292  }
   293  
   294  func mustStringToColumnDefault(defaultString string) *sql.ColumnDefaultValue {
   295  	def, err := planbuilder.StringToColumnDefaultValue(sql.NewEmptyContext(), defaultString)
   296  	if err != nil {
   297  		panic(err)
   298  	}
   299  	return def
   300  }
   301  
   302  func schemaNewColumnWithDefault(name string, tag uint64, kind types.NomsKind, partOfPK bool, defaultVal string, constraints ...schema.ColConstraint) schema.Column {
   303  	col := schema.NewColumn(name, tag, kind, partOfPK, constraints...)
   304  	col.Default = defaultVal
   305  	return col
   306  }
   307  
   308  func TestDropPks(t *testing.T) {
   309  	var dropTests = []struct {
   310  		name        string
   311  		setup       []string
   312  		expectedErr *errors.Kind
   313  		fkIdxName   string
   314  	}{
   315  		{
   316  			name: "no error on drop pk",
   317  			setup: []string{
   318  				"create table parent (id int, name varchar(1), age int, primary key (id))",
   319  				"insert into parent values (1,1,1),(2,2,2)",
   320  			},
   321  		},
   322  		{
   323  			name: "no error if backup index",
   324  			setup: []string{
   325  				"create table parent (id int, name varchar(1), age int, primary key (id), key `backup` (id))",
   326  				"create table child (id int, name varchar(1), age int, primary key (id), constraint `fk` foreign key (id) references parent (id))",
   327  			},
   328  			fkIdxName: "backup",
   329  		},
   330  		{
   331  			name: "no error if backup index for single FK on compound pk drop",
   332  			setup: []string{
   333  				"create table parent (id int, name varchar(1), age int, primary key (id, age), key `backup` (age))",
   334  				"create table child (id int, name varchar(1), age int, primary key (id), constraint `fk` foreign key (age) references parent (age))",
   335  			},
   336  			fkIdxName: "backup",
   337  		},
   338  		{
   339  			name: "no error if compound backup index for compound FK",
   340  			setup: []string{
   341  				"create table parent (id int, name varchar(1), age int, primary key (id, age), key `backup` (id, age))",
   342  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (id, age) references parent (id, age))",
   343  			},
   344  			fkIdxName: "backup",
   345  		},
   346  		{
   347  			name: "no error if compound backup index for compound FK, 3-compound PK",
   348  			setup: []string{
   349  				"create table parent (id int, name varchar(1), age int, primary key (id, age, name), key `backup` (id, age))",
   350  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (id, age) references parent (id, age))",
   351  			},
   352  			fkIdxName: "backup",
   353  		},
   354  		{
   355  			name: "no error if single backup index for single FK, compound primary",
   356  			setup: []string{
   357  				"create table parent (id int, name varchar(1), age int, primary key (id, age), key `backup` (id))",
   358  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (id) references parent (id))",
   359  			},
   360  			fkIdxName: "backup",
   361  		},
   362  		{
   363  			name: "no error if both several invalid and one valid backup index",
   364  			setup: []string{
   365  				"create table parent (id int, name varchar(1), age int, primary key (id, age), key `bad_backup1` (age, id), key `bad_backup2` (age), key `backup` (id, age, name))",
   366  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (id) references parent (id))",
   367  			},
   368  			fkIdxName: "backup",
   369  		},
   370  		{
   371  			name: "no error if one invalid and several valid backup indexes",
   372  			setup: []string{
   373  				"create table parent (id int, name varchar(1), age int, primary key (id, age), key `bad_backup` (age, id), key `backup1` (id), key `backup2` (id, age, name))",
   374  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (id) references parent (id))",
   375  			},
   376  			fkIdxName: "backup1",
   377  		},
   378  		{
   379  			name: "prefer unique key",
   380  			setup: []string{
   381  				"create table parent (id int, name varchar(1), age int, primary key (id, age), key `bad_backup` (age, id), key `backup1` (id, age, name), unique key `backup2` (id, age, name))",
   382  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (id) references parent (id))",
   383  			},
   384  			fkIdxName: "backup2",
   385  		},
   386  		{
   387  			name: "backup index has more columns than pk or fk",
   388  			setup: []string{
   389  				"create table parent (id int, name varchar(1), age int, other int, primary key (id, age, name), key `backup` (id, age, other))",
   390  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (id, age) references parent (id, age))",
   391  			},
   392  			fkIdxName: "backup",
   393  		},
   394  		{
   395  			name: "error if FK ref but no backup index for single pk",
   396  			setup: []string{
   397  				"create table parent (id int, name varchar(1), age int, primary key (id))",
   398  				"create table child (id int, name varchar(1), age int, primary key (id), constraint `fk` foreign key (id) references parent (id))",
   399  			},
   400  			expectedErr: sql.ErrCantDropIndex,
   401  			fkIdxName:   "id",
   402  		},
   403  		{
   404  			name: "error if FK ref but bad backup index",
   405  			setup: []string{
   406  				"create table parent (id int, name varchar(1), age int, primary key (id), key `bad_backup2` (age))",
   407  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (id) references parent (id))",
   408  			},
   409  			expectedErr: sql.ErrCantDropIndex,
   410  			fkIdxName:   "id",
   411  		},
   412  		{
   413  			name: "error if misordered compound backup index for FK",
   414  			setup: []string{
   415  				"create table parent (id int, name varchar(1), age int, constraint `primary` primary key (id), key `backup` (age, id))",
   416  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (id) references parent (id))",
   417  			},
   418  			expectedErr: sql.ErrCantDropIndex,
   419  			fkIdxName:   "id",
   420  		},
   421  		{
   422  			name: "error if incomplete compound backup index for FK",
   423  			setup: []string{
   424  				"create table parent (id int, name varchar(1), age int, constraint `primary` primary key (age, id), key `backup` (age, name))",
   425  				"create table child (id int, name varchar(1), age int, constraint `fk` foreign key (age, id) references parent (age,  id))",
   426  			},
   427  			expectedErr: sql.ErrCantDropIndex,
   428  			fkIdxName:   "ageid",
   429  		},
   430  	}
   431  
   432  	for _, tt := range dropTests {
   433  		childName := "child"
   434  		parentName := "parent"
   435  		childFkName := "fk"
   436  
   437  		t.Run(tt.name, func(t *testing.T) {
   438  			dEnv := dtestutils.CreateTestEnv()
   439  			defer dEnv.DoltDB.Close()
   440  			ctx := context.Background()
   441  			tmpDir, err := dEnv.TempTableFilesDir()
   442  			require.NoError(t, err)
   443  			opts := editor.Options{Deaf: dEnv.DbEaFactory(), Tempdir: tmpDir}
   444  			db, err := NewDatabase(ctx, "dolt", dEnv.DbData(), opts)
   445  			require.NoError(t, err)
   446  
   447  			root, _ := dEnv.WorkingRoot(ctx)
   448  			engine, sqlCtx, err := NewTestEngine(dEnv, ctx, db)
   449  			require.NoError(t, err)
   450  
   451  			for _, query := range tt.setup {
   452  				_, _, err := engine.Query(sqlCtx, query)
   453  				require.NoError(t, err)
   454  			}
   455  
   456  			drop := "alter table parent drop primary key"
   457  			_, iter, err := engine.Query(sqlCtx, drop)
   458  			require.NoError(t, err)
   459  
   460  			err = drainIter(sqlCtx, iter)
   461  			if tt.expectedErr != nil {
   462  				require.Error(t, err)
   463  				assert.True(t, tt.expectedErr.Is(err), "Expected error of type %s but got %s", tt.expectedErr, err)
   464  			} else {
   465  				require.NoError(t, err)
   466  			}
   467  
   468  			if tt.fkIdxName != "" {
   469  				root, _ = db.GetRoot(sqlCtx)
   470  				foreignKeyCollection, err := root.GetForeignKeyCollection(ctx)
   471  				assert.NoError(t, err)
   472  
   473  				fk, ok := foreignKeyCollection.GetByNameCaseInsensitive(childFkName)
   474  				assert.True(t, ok)
   475  				assert.Equal(t, childName, fk.TableName)
   476  				if tt.fkIdxName != "" && fk.ReferencedTableIndex != "" {
   477  					assert.Equal(t, tt.fkIdxName, fk.ReferencedTableIndex)
   478  				}
   479  
   480  				parent, ok, err := root.GetTable(ctx, doltdb.TableName{Name: parentName})
   481  				assert.NoError(t, err)
   482  				assert.True(t, ok)
   483  
   484  				parentSch, err := parent.GetSchema(ctx)
   485  				assert.NoError(t, err)
   486  				err = fk.ValidateReferencedTableSchema(parentSch)
   487  				assert.NoError(t, err)
   488  			}
   489  		})
   490  	}
   491  }
   492  
   493  func TestNewPkOrdinals(t *testing.T) {
   494  	oldSch := schema.MustSchemaFromCols(
   495  		schema.NewColCollection(
   496  			schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   497  			schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   498  			schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   499  			schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, true, schema.NotNullConstraint{}),
   500  			schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   501  		),
   502  	)
   503  	err := oldSch.SetPkOrdinals([]int{3, 1})
   504  	require.NoError(t, err)
   505  
   506  	tests := []struct {
   507  		name          string
   508  		newSch        schema.Schema
   509  		expPkOrdinals []int
   510  		err           error
   511  	}{
   512  		{
   513  			name: "remove column",
   514  			newSch: schema.MustSchemaFromCols(
   515  				schema.NewColCollection(
   516  					schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   517  					schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   518  					schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, true, schema.NotNullConstraint{}),
   519  					schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   520  				),
   521  			),
   522  			expPkOrdinals: []int{2, 1},
   523  		},
   524  		{
   525  			name: "add column",
   526  			newSch: schema.MustSchemaFromCols(
   527  				schema.NewColCollection(
   528  					schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   529  					schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   530  					schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   531  					schema.NewColumn("new", dtestutils.NextTag, types.StringKind, false),
   532  					schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, true, schema.NotNullConstraint{}),
   533  					schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   534  				),
   535  			),
   536  			expPkOrdinals: []int{4, 1},
   537  		},
   538  		{
   539  			name: "transpose column",
   540  			newSch: schema.MustSchemaFromCols(
   541  				schema.NewColCollection(
   542  					schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   543  					schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   544  					schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   545  					schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   546  					schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, true, schema.NotNullConstraint{}),
   547  				),
   548  			),
   549  			expPkOrdinals: []int{4, 1},
   550  		},
   551  		{
   552  			name: "transpose PK column",
   553  			newSch: schema.MustSchemaFromCols(
   554  				schema.NewColCollection(
   555  					schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   556  					schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, true, schema.NotNullConstraint{}),
   557  					schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   558  					schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   559  					schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   560  				),
   561  			),
   562  			expPkOrdinals: []int{1, 2},
   563  		},
   564  		{
   565  			name: "drop PK column",
   566  			newSch: schema.MustSchemaFromCols(
   567  				schema.NewColCollection(
   568  					schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   569  					schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   570  					schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   571  					schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   572  				),
   573  			),
   574  			err: ErrPrimaryKeySetsIncompatible,
   575  		},
   576  		{
   577  			name: "add PK column",
   578  			newSch: schema.MustSchemaFromCols(
   579  				schema.NewColCollection(
   580  					schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   581  					schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   582  					schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   583  					schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, true, schema.NotNullConstraint{}),
   584  					schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   585  					schema.NewColumn("new", dtestutils.NextTag, types.StringKind, true),
   586  				),
   587  			),
   588  			err: ErrPrimaryKeySetsIncompatible,
   589  		},
   590  		{
   591  			name: "change PK tag",
   592  			newSch: schema.MustSchemaFromCols(
   593  				schema.NewColCollection(
   594  					schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   595  					schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   596  					schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   597  					schema.NewColumn("is_married", dtestutils.NextTag, types.IntKind, true, schema.NotNullConstraint{}),
   598  					schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   599  				),
   600  			),
   601  			expPkOrdinals: []int{3, 1},
   602  		},
   603  		{
   604  			name: "change PK name",
   605  			newSch: schema.MustSchemaFromCols(
   606  				schema.NewColCollection(
   607  					schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   608  					schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   609  					schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   610  					schema.NewColumn("new", dtestutils.IsMarriedTag, types.IntKind, true, schema.NotNullConstraint{}),
   611  					schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   612  				),
   613  			),
   614  			expPkOrdinals: []int{3, 1},
   615  		},
   616  		{
   617  			name: "changing PK tag and name is the same as dropping a PK",
   618  			newSch: schema.MustSchemaFromCols(
   619  				schema.NewColCollection(
   620  					schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, false, schema.NotNullConstraint{}),
   621  					schema.NewColumn("name", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   622  					schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   623  					schema.NewColumn("new", dtestutils.NextTag, types.IntKind, true, schema.NotNullConstraint{}),
   624  					schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   625  				),
   626  			),
   627  			err: ErrPrimaryKeySetsIncompatible,
   628  		},
   629  	}
   630  
   631  	for _, tt := range tests {
   632  		t.Run(tt.name, func(t *testing.T) {
   633  			res, err := modifyPkOrdinals(oldSch, tt.newSch)
   634  			if tt.err != nil {
   635  				require.True(t, goerrors.Is(err, tt.err))
   636  			} else {
   637  				require.Equal(t, res, tt.expPkOrdinals)
   638  			}
   639  		})
   640  	}
   641  }
   642  
   643  func TestModifyColumn(t *testing.T) {
   644  	alteredTypeSch := dtestutils.CreateSchema(
   645  		schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   646  		schema.NewColumn("name", dtestutils.NameTag, types.StringKind, false, schema.NotNullConstraint{}),
   647  		schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   648  		schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, false, schema.NotNullConstraint{}),
   649  		schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   650  	)
   651  	ti, err := typeinfo.FromSqlType(gmstypes.MustCreateStringWithDefaults(sqltypes.VarChar, 599))
   652  	require.NoError(t, err)
   653  	newNameColSameTag, err := schema.NewColumnWithTypeInfo("name", dtestutils.NameTag, ti, false, "", false, "", schema.NotNullConstraint{})
   654  	require.NoError(t, err)
   655  	alteredTypeSch2 := dtestutils.CreateSchema(
   656  		schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   657  		newNameColSameTag,
   658  		schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   659  		schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, false, schema.NotNullConstraint{}),
   660  		schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   661  	)
   662  
   663  	tests := []struct {
   664  		name           string
   665  		existingColumn schema.Column
   666  		newColumn      schema.Column
   667  		order          *sql.ColumnOrder
   668  		expectedSchema schema.Schema
   669  		expectedErr    string
   670  	}{
   671  		{
   672  			name:           "column rename",
   673  			existingColumn: schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   674  			newColumn:      schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   675  			expectedSchema: dtestutils.CreateSchema(
   676  				schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   677  				schema.NewColumn("name", dtestutils.NameTag, types.StringKind, false, schema.NotNullConstraint{}),
   678  				schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   679  				schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, false, schema.NotNullConstraint{}),
   680  				schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   681  			),
   682  		},
   683  		{
   684  			name:           "remove null constraint",
   685  			existingColumn: schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   686  			newColumn:      schema.NewColumn("newAge", dtestutils.AgeTag, types.UintKind, false),
   687  			expectedSchema: dtestutils.CreateSchema(
   688  				schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   689  				schema.NewColumn("name", dtestutils.NameTag, types.StringKind, false, schema.NotNullConstraint{}),
   690  				schema.NewColumn("newAge", dtestutils.AgeTag, types.UintKind, false),
   691  				schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, false, schema.NotNullConstraint{}),
   692  				schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   693  			),
   694  		},
   695  		{
   696  			name:           "reorder first",
   697  			existingColumn: schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   698  			newColumn:      schema.NewColumn("newAge", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   699  			order:          &sql.ColumnOrder{First: true},
   700  			expectedSchema: dtestutils.CreateSchema(
   701  				schema.NewColumn("newAge", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   702  				schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   703  				schema.NewColumn("name", dtestutils.NameTag, types.StringKind, false, schema.NotNullConstraint{}),
   704  				schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, false, schema.NotNullConstraint{}),
   705  				schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   706  			),
   707  		},
   708  		{
   709  			name:           "reorder middle",
   710  			existingColumn: schema.NewColumn("age", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   711  			newColumn:      schema.NewColumn("newAge", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   712  			order:          &sql.ColumnOrder{AfterColumn: "is_married"},
   713  			expectedSchema: dtestutils.CreateSchema(
   714  				schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   715  				schema.NewColumn("name", dtestutils.NameTag, types.StringKind, false, schema.NotNullConstraint{}),
   716  				schema.NewColumn("is_married", dtestutils.IsMarriedTag, types.IntKind, false, schema.NotNullConstraint{}),
   717  				schema.NewColumn("newAge", dtestutils.AgeTag, types.UintKind, false, schema.NotNullConstraint{}),
   718  				schema.NewColumn("title", dtestutils.TitleTag, types.StringKind, false),
   719  			),
   720  		},
   721  		{
   722  			name:           "tag collision",
   723  			existingColumn: schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   724  			newColumn:      schema.NewColumn("newId", dtestutils.NameTag, types.StringKind, true, schema.NotNullConstraint{}),
   725  			expectedErr:    "two different columns with the same tag",
   726  		},
   727  		{
   728  			name:           "name collision",
   729  			existingColumn: schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   730  			newColumn:      schema.NewColumn("name", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   731  			expectedErr:    "two different columns with the same name exist",
   732  		},
   733  		{
   734  			name:           "type change",
   735  			existingColumn: schema.NewColumn("id", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   736  			newColumn:      schema.NewColumn("newId", dtestutils.IdTag, types.StringKind, true, schema.NotNullConstraint{}),
   737  			expectedSchema: alteredTypeSch,
   738  		},
   739  		{
   740  			name:           "type change same tag",
   741  			existingColumn: schema.NewColumn("name", dtestutils.NameTag, types.StringKind, false, schema.NotNullConstraint{}),
   742  			newColumn:      newNameColSameTag,
   743  			expectedSchema: alteredTypeSch2,
   744  		},
   745  	}
   746  
   747  	for _, tt := range tests {
   748  		t.Run(tt.name, func(t *testing.T) {
   749  			ctx := context.Background()
   750  			dEnv, err := makePeopleTable(ctx, dtestutils.CreateTestEnv())
   751  			require.NoError(t, err)
   752  			defer dEnv.DoltDB.Close()
   753  
   754  			root, err := dEnv.WorkingRoot(ctx)
   755  			assert.NoError(t, err)
   756  			tbl, _, err := root.GetTable(ctx, doltdb.TableName{Name: tableName})
   757  			assert.NoError(t, err)
   758  			updatedTable, err := modifyColumn(ctx, tbl, tt.existingColumn, tt.newColumn, tt.order)
   759  			if len(tt.expectedErr) > 0 {
   760  				require.Error(t, err)
   761  				assert.Contains(t, err.Error(), tt.expectedErr)
   762  				return
   763  			} else {
   764  				require.NoError(t, err)
   765  			}
   766  
   767  			sch, err := updatedTable.GetSchema(ctx)
   768  			require.NoError(t, err)
   769  			index := sch.Indexes().GetByName(dtestutils.IndexName)
   770  			assert.NotNil(t, index)
   771  			tt.expectedSchema.Indexes().AddIndex(index)
   772  			err = tt.expectedSchema.SetPkOrdinals(sch.GetPkOrdinals())
   773  			require.NoError(t, err)
   774  			_, err = tt.expectedSchema.Checks().AddCheck("test-check", "age < 123", true)
   775  			require.NoError(t, err)
   776  			require.Equal(t, tt.expectedSchema, sch)
   777  		})
   778  	}
   779  }
   780  
   781  // addColToRows adds a column to all the rows given and returns it. This method relies on the fact that
   782  // noms_row.SetColVal doesn't need a full schema, just one that includes the column being set.
   783  func addColToRows(t *testing.T, rs []row.Row, tag uint64, val types.Value) []row.Row {
   784  	if types.IsNull(val) {
   785  		return rs
   786  	}
   787  
   788  	colColl := schema.NewColCollection(schema.NewColumn("unused", tag, val.Kind(), false))
   789  	fakeSch := schema.UnkeyedSchemaFromCols(colColl)
   790  
   791  	newRows := make([]row.Row, len(rs))
   792  	var err error
   793  	for i, r := range rs {
   794  		newRows[i], err = r.SetColVal(tag, val, fakeSch)
   795  		require.NoError(t, err)
   796  	}
   797  	return newRows
   798  }