Skip to content

Commit d697304

Browse files
Improve validation of create_table and add_column operations (#666)
Improve validation of `create_table` and `add_column` operations. Previously, these operations would fail at migration `Start` if one of the column definitions was invalid due to having a missing `name` or `type` field. This PR adds extra validation checks to both operations to ensure that such migrations fail at the validation stage instead.
1 parent cad53be commit d697304

File tree

6 files changed

+120
-0
lines changed

6 files changed

+120
-0
lines changed

pkg/migrations/column.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,15 @@ func (c *Column) HasImplicitDefault() bool {
2626
return false
2727
}
2828
}
29+
30+
// Validate returns true iff the column contains all fields required to create
31+
// the column
32+
func (c *Column) Validate() bool {
33+
if c.Name == "" {
34+
return false
35+
}
36+
if c.Type == "" {
37+
return false
38+
}
39+
return true
40+
}

pkg/migrations/errors.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ type ColumnAlreadyExistsError struct {
4141
Name string
4242
}
4343

44+
type ColumnIsInvalidError struct {
45+
Table string
46+
Name string
47+
}
48+
49+
func (e ColumnIsInvalidError) Error() string {
50+
return fmt.Sprintf(`column %q in table %q is missing one or more required fields - "name" or "type"`,
51+
e.Name, e.Table)
52+
}
53+
4454
func (e ColumnAlreadyExistsError) Error() string {
4555
return fmt.Sprintf("column %q already exists in table %q", e.Name, e.Table)
4656
}

pkg/migrations/op_add_column.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ func (o *OpAddColumn) Validate(ctx context.Context, s *schema.Schema) error {
148148
return err
149149
}
150150

151+
// Validate that the column contains all required fields
152+
if !o.Column.Validate() {
153+
return ColumnIsInvalidError{Table: o.Table, Name: o.Column.Name}
154+
}
155+
151156
table := s.GetTable(o.Table)
152157
if table == nil {
153158
return TableDoesNotExistError{Name: o.Table}

pkg/migrations/op_add_column_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,44 @@ func TestAddColumnValidation(t *testing.T) {
13221322
},
13231323
wantStartErr: migrations.ColumnAlreadyExistsError{Table: "users", Name: "name"},
13241324
},
1325+
{
1326+
name: "column must be valid (not missing its type)",
1327+
migrations: []migrations.Migration{
1328+
addTableMigration,
1329+
{
1330+
Name: "02_add_column",
1331+
Operations: migrations.Operations{
1332+
&migrations.OpAddColumn{
1333+
Table: "users",
1334+
Column: migrations.Column{
1335+
Name: "description",
1336+
// Missing type
1337+
},
1338+
},
1339+
},
1340+
},
1341+
},
1342+
wantStartErr: migrations.ColumnIsInvalidError{Table: "users", Name: "description"},
1343+
},
1344+
{
1345+
name: "column must be valid (not missing its name)",
1346+
migrations: []migrations.Migration{
1347+
addTableMigration,
1348+
{
1349+
Name: "02_add_column",
1350+
Operations: migrations.Operations{
1351+
&migrations.OpAddColumn{
1352+
Table: "users",
1353+
Column: migrations.Column{
1354+
// Missing name
1355+
Type: "text",
1356+
},
1357+
},
1358+
},
1359+
},
1360+
},
1361+
wantStartErr: migrations.ColumnIsInvalidError{Table: "users", Name: ""},
1362+
},
13251363
{
13261364
name: "up SQL is mandatory when adding a NOT NULL column with no DEFAULT",
13271365
migrations: []migrations.Migration{

pkg/migrations/op_create_table.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ func (o *OpCreateTable) Validate(ctx context.Context, s *schema.Schema) error {
8989
return fmt.Errorf("invalid column: %w", err)
9090
}
9191

92+
// Validate that the column contains all required fields
93+
if !col.Validate() {
94+
return ColumnIsInvalidError{Table: o.Name, Name: col.Name}
95+
}
96+
9297
// Ensure that any foreign key references are valid, ie. the referenced
9398
// table and column exist.
9499
if col.References != nil {

pkg/migrations/op_create_table_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,56 @@ func TestCreateTableValidation(t *testing.T) {
13231323
},
13241324
wantStartErr: migrations.InvalidIdentifierLengthError{Name: invalidName},
13251325
},
1326+
{
1327+
name: "column definition missing a name",
1328+
migrations: []migrations.Migration{
1329+
{
1330+
Name: "01_create_table",
1331+
Operations: migrations.Operations{
1332+
&migrations.OpCreateTable{
1333+
Name: "orders",
1334+
Columns: []migrations.Column{
1335+
{
1336+
Name: "id",
1337+
Type: "serial",
1338+
Pk: true,
1339+
},
1340+
{
1341+
// missing name
1342+
Type: "varchar(255)",
1343+
},
1344+
},
1345+
},
1346+
},
1347+
},
1348+
},
1349+
wantStartErr: migrations.ColumnIsInvalidError{Table: "orders", Name: ""},
1350+
},
1351+
{
1352+
name: "column definition missing a type",
1353+
migrations: []migrations.Migration{
1354+
{
1355+
Name: "01_create_table",
1356+
Operations: migrations.Operations{
1357+
&migrations.OpCreateTable{
1358+
Name: "orders",
1359+
Columns: []migrations.Column{
1360+
{
1361+
Name: "id",
1362+
Type: "serial",
1363+
Pk: true,
1364+
},
1365+
{
1366+
Name: "name",
1367+
// missing type
1368+
},
1369+
},
1370+
},
1371+
},
1372+
},
1373+
},
1374+
wantStartErr: migrations.ColumnIsInvalidError{Table: "orders", Name: "name"},
1375+
},
13261376
{
13271377
name: "invalid column name",
13281378
migrations: []migrations.Migration{

0 commit comments

Comments
 (0)