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 }