Skip to content

Commit e15a153

Browse files
Convert column FOREIGN KEYs in CREATE TABLE
Convert inline `FOREIGN KEY` values in `CREATE TABLE` statements like this: ```sql CREATE TABLE foo(a int REFERENCES bar(b)) ``` into `OpCreateTable` operations like this: ```json [ { "create_table": { "columns": [ { "name": "a", "nullable": true, "references": { "column": "b", "name": "foo_a_fkey", "on_delete": "NO ACTION", "table": "bar" }, "type": "int" } ], "name": "foo" } } ] ```
1 parent facf39f commit e15a153

File tree

3 files changed

+154
-8
lines changed

3 files changed

+154
-8
lines changed

pkg/sql2pgroll/create_table.go

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func convertColumnDef(tableName string, col *pgq.ColumnDef) (*migrations.Column,
9898
// Convert column constraints
9999
var notNull, pk, unique bool
100100
var check *migrations.CheckConstraint
101+
var foreignKey *migrations.ForeignKeyReference
101102
var defaultValue *string
102103
for _, c := range col.GetConstraints() {
103104
switch c.GetConstraint().GetContype() {
@@ -131,20 +132,25 @@ func convertColumnDef(tableName string, col *pgq.ColumnDef) (*migrations.Column,
131132
}
132133
defaultValue = &d
133134
case pgq.ConstrType_CONSTR_FOREIGN:
134-
if !canConvertForeignKeyConstraint(c.GetConstraint()) {
135+
foreignKey, err = convertInlineForeignKeyConstraint(tableName, col.GetColname(), c.GetConstraint())
136+
if err != nil {
137+
return nil, fmt.Errorf("error converting inline foreign key constraint: %w", err)
138+
}
139+
if foreignKey == nil {
135140
return nil, nil
136141
}
137142
}
138143
}
139144

140145
return &migrations.Column{
141-
Name: col.GetColname(),
142-
Type: typeString,
143-
Nullable: !notNull,
144-
Pk: pk,
145-
Check: check,
146-
Default: defaultValue,
147-
Unique: unique,
146+
Name: col.GetColname(),
147+
Type: typeString,
148+
Nullable: !notNull,
149+
Pk: pk,
150+
Check: check,
151+
References: foreignKey,
152+
Default: defaultValue,
153+
Unique: unique,
148154
}, nil
149155
}
150156

@@ -179,6 +185,8 @@ func canConvertPrimaryKeyConstraint(constraint *pgq.Constraint) bool {
179185
}
180186
}
181187

188+
// convertInlineCheckConstraint converts an inline check constraint to a
189+
// `CheckConstraint`.
182190
func convertInlineCheckConstraint(tableName, columnName string, constraint *pgq.Constraint) (*migrations.CheckConstraint, error) {
183191
if !canConvertCheckConstraint(constraint) {
184192
return nil, nil
@@ -199,3 +207,28 @@ func convertInlineCheckConstraint(tableName, columnName string, constraint *pgq.
199207
Constraint: expr,
200208
}, nil
201209
}
210+
211+
// convertInlineForeignKeyConstraint converts an inline foreign key constraint
212+
// to a `ForeignKeyReference`.
213+
func convertInlineForeignKeyConstraint(tableName, columnName string, constraint *pgq.Constraint) (*migrations.ForeignKeyReference, error) {
214+
if !canConvertForeignKeyConstraint(constraint) {
215+
return nil, nil
216+
}
217+
218+
onDelete, err := parseOnDeleteAction(constraint.GetFkDelAction())
219+
if err != nil {
220+
return nil, err
221+
}
222+
223+
name := fmt.Sprintf("%s_%s_fkey", tableName, columnName)
224+
if constraint.GetConname() != "" {
225+
name = constraint.GetConname()
226+
}
227+
228+
return &migrations.ForeignKeyReference{
229+
Name: name,
230+
OnDelete: onDelete,
231+
Column: constraint.GetPkAttrs()[0].GetString_().GetSval(),
232+
Table: getQualifiedRelationName(constraint.GetPktable()),
233+
}, nil
234+
}

pkg/sql2pgroll/create_table_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,34 @@ func TestConvertCreateTableStatements(t *testing.T) {
4848
sql: "CREATE TABLE foo(a timestamptz DEFAULT now())",
4949
expectedOp: expect.CreateTableOp11,
5050
},
51+
{
52+
sql: "CREATE TABLE foo(a int REFERENCES bar(b))",
53+
expectedOp: expect.CreateTableOp12,
54+
},
55+
{
56+
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON UPDATE NO ACTION)",
57+
expectedOp: expect.CreateTableOp12,
58+
},
59+
{
60+
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE NO ACTION)",
61+
expectedOp: expect.CreateTableOp12,
62+
},
63+
{
64+
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE RESTRICT)",
65+
expectedOp: expect.CreateTableOp13,
66+
},
67+
{
68+
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE SET NULL)",
69+
expectedOp: expect.CreateTableOp14,
70+
},
71+
{
72+
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE SET DEFAULT)",
73+
expectedOp: expect.CreateTableOp15,
74+
},
75+
{
76+
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE CASCADE)",
77+
expectedOp: expect.CreateTableOp16,
78+
},
5179
{
5280
sql: "CREATE TABLE foo(a varchar(255))",
5381
expectedOp: expect.CreateTableOp3,

pkg/sql2pgroll/expect/create_table.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,88 @@ var CreateTableOp11 = &migrations.OpCreateTable{
129129
},
130130
},
131131
}
132+
133+
var CreateTableOp12 = &migrations.OpCreateTable{
134+
Name: "foo",
135+
Columns: []migrations.Column{
136+
{
137+
Name: "a",
138+
Type: "int",
139+
Nullable: true,
140+
References: &migrations.ForeignKeyReference{
141+
Name: "foo_a_fkey",
142+
Table: "bar",
143+
Column: "b",
144+
OnDelete: migrations.ForeignKeyReferenceOnDeleteNOACTION,
145+
},
146+
},
147+
},
148+
}
149+
150+
var CreateTableOp13 = &migrations.OpCreateTable{
151+
Name: "foo",
152+
Columns: []migrations.Column{
153+
{
154+
Name: "a",
155+
Type: "int",
156+
Nullable: true,
157+
References: &migrations.ForeignKeyReference{
158+
Name: "foo_a_fkey",
159+
Table: "bar",
160+
Column: "b",
161+
OnDelete: migrations.ForeignKeyReferenceOnDeleteRESTRICT,
162+
},
163+
},
164+
},
165+
}
166+
167+
var CreateTableOp14 = &migrations.OpCreateTable{
168+
Name: "foo",
169+
Columns: []migrations.Column{
170+
{
171+
Name: "a",
172+
Type: "int",
173+
Nullable: true,
174+
References: &migrations.ForeignKeyReference{
175+
Name: "foo_a_fkey",
176+
Table: "bar",
177+
Column: "b",
178+
OnDelete: migrations.ForeignKeyReferenceOnDeleteSETNULL,
179+
},
180+
},
181+
},
182+
}
183+
184+
var CreateTableOp15 = &migrations.OpCreateTable{
185+
Name: "foo",
186+
Columns: []migrations.Column{
187+
{
188+
Name: "a",
189+
Type: "int",
190+
Nullable: true,
191+
References: &migrations.ForeignKeyReference{
192+
Name: "foo_a_fkey",
193+
Table: "bar",
194+
Column: "b",
195+
OnDelete: migrations.ForeignKeyReferenceOnDeleteSETDEFAULT,
196+
},
197+
},
198+
},
199+
}
200+
201+
var CreateTableOp16 = &migrations.OpCreateTable{
202+
Name: "foo",
203+
Columns: []migrations.Column{
204+
{
205+
Name: "a",
206+
Type: "int",
207+
Nullable: true,
208+
References: &migrations.ForeignKeyReference{
209+
Name: "foo_a_fkey",
210+
Table: "bar",
211+
Column: "b",
212+
OnDelete: migrations.ForeignKeyReferenceOnDeleteCASCADE,
213+
},
214+
},
215+
},
216+
}

0 commit comments

Comments
 (0)