Skip to content

Commit 171f5a2

Browse files
Allow adding a column to a table created in the same migration (#449)
Allow the `add_column` operation to add a column to a table that was created by an operation earlier in the same migration. The following migration would previously have failed to start: ```json { "name": "43_multiple_ops", "operations": [ { "create_table": { "name": "players", "columns": [ { "name": "id", "type": "serial", "pk": true }, { "name": "name", "type": "varchar(255)", "check": { "name": "name_length_check", "constraint": "length(name) > 2" } } ] } }, { "add_column": { "table": "players", "column": { "name": "rating", "type": "integer", "comment": "hello world", "check": { "name": "rating_check", "constraint": "rating > 0 AND rating < 100" }, "nullable": false } } } ] } ``` As of this PR, the migration can be started. The above migration does not validate yet, but it can be started successfully with the `--skip-validation` flag to the `start` command. Part of #239
1 parent 999a299 commit 171f5a2

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

pkg/migrations/op_add_column.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,19 @@ func (o *OpAddColumn) Start(ctx context.Context, conn db.DB, latestSchema string
2424
}
2525

2626
if o.Column.Comment != nil {
27-
if err := addCommentToColumn(ctx, conn, o.Table, TemporaryName(o.Column.Name), o.Column.Comment); err != nil {
27+
if err := addCommentToColumn(ctx, conn, table.Name, TemporaryName(o.Column.Name), o.Column.Comment); err != nil {
2828
return nil, fmt.Errorf("failed to add comment to column: %w", err)
2929
}
3030
}
3131

3232
if !o.Column.IsNullable() && o.Column.Default == nil {
33-
if err := addNotNullConstraint(ctx, conn, o.Table, o.Column.Name, TemporaryName(o.Column.Name)); err != nil {
33+
if err := addNotNullConstraint(ctx, conn, table.Name, o.Column.Name, TemporaryName(o.Column.Name)); err != nil {
3434
return nil, fmt.Errorf("failed to add not null constraint: %w", err)
3535
}
3636
}
3737

3838
if o.Column.Check != nil {
39-
if err := o.addCheckConstraint(ctx, conn); err != nil {
39+
if err := o.addCheckConstraint(ctx, table.Name, conn); err != nil {
4040
return nil, fmt.Errorf("failed to add check constraint: %w", err)
4141
}
4242
}
@@ -231,9 +231,9 @@ func addNotNullConstraint(ctx context.Context, conn db.DB, table, column, physic
231231
return err
232232
}
233233

234-
func (o *OpAddColumn) addCheckConstraint(ctx context.Context, conn db.DB) error {
234+
func (o *OpAddColumn) addCheckConstraint(ctx context.Context, tableName string, conn db.DB) error {
235235
_, err := conn.ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT %s CHECK (%s) NOT VALID",
236-
pq.QuoteIdentifier(o.Table),
236+
pq.QuoteIdentifier(tableName),
237237
pq.QuoteIdentifier(o.Column.Check.Name),
238238
rewriteCheckExpression(o.Column.Check.Constraint, o.Column.Name, TemporaryName(o.Column.Name)),
239239
))

pkg/migrations/op_add_column_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,74 @@ func TestAddColumnDefaultTransformation(t *testing.T) {
14941494
}, roll.WithSQLTransformer(sqlTransformer))
14951495
}
14961496

1497+
func TestAddColumnToATableCreatedInTheSameMigration(t *testing.T) {
1498+
t.Parallel()
1499+
1500+
ExecuteTests(t, TestCases{
1501+
{
1502+
name: "add column to newly created table",
1503+
migrations: []migrations.Migration{
1504+
{
1505+
Name: "01_add_table",
1506+
Operations: migrations.Operations{
1507+
&migrations.OpCreateTable{
1508+
Name: "users",
1509+
Columns: []migrations.Column{
1510+
{
1511+
Name: "id",
1512+
Type: "serial",
1513+
Pk: ptr(true),
1514+
},
1515+
{
1516+
Name: "name",
1517+
Type: "varchar(255)",
1518+
},
1519+
},
1520+
},
1521+
&migrations.OpAddColumn{
1522+
Table: "users",
1523+
Column: migrations.Column{
1524+
Name: "age",
1525+
Type: "integer",
1526+
Nullable: ptr(false),
1527+
Check: &migrations.CheckConstraint{
1528+
Name: "age_check",
1529+
Constraint: "age >= 18",
1530+
},
1531+
Comment: ptr("the age of the user"),
1532+
},
1533+
},
1534+
},
1535+
},
1536+
},
1537+
afterStart: func(t *testing.T, db *sql.DB, schema string) {
1538+
// Inserting into the new column on the new table works.
1539+
MustInsert(t, db, schema, "01_add_table", "users", map[string]string{
1540+
"name": "Alice", "age": "30",
1541+
})
1542+
1543+
// Inserting a value that doesn't meet the check constraint fails.
1544+
MustNotInsert(t, db, schema, "01_add_table", "users", map[string]string{
1545+
"name": "Bob", "age": "8",
1546+
}, testutils.CheckViolationErrorCode)
1547+
},
1548+
afterRollback: func(t *testing.T, db *sql.DB, schema string) {
1549+
},
1550+
afterComplete: func(t *testing.T, db *sql.DB, schema string) {
1551+
// Inserting into the new column on the new table works.
1552+
MustInsert(t, db, schema, "01_add_table", "users", map[string]string{
1553+
"name": "Bob", "age": "31",
1554+
})
1555+
1556+
// Inserting a value that doesn't meet the check constraint fails.
1557+
MustNotInsert(t, db, schema, "01_add_table", "users", map[string]string{
1558+
"name": "Carl", "age": "8",
1559+
}, testutils.CheckViolationErrorCode)
1560+
},
1561+
},
1562+
}, roll.WithSkipValidation(true)) // TODO: remove once this migration can be validated
1563+
}
1564+
14971565
func TestAddColumnInvalidNameLength(t *testing.T) {
14981566
t.Parallel()
14991567

0 commit comments

Comments
 (0)