Skip to content

Commit

Permalink
Convert column FOREIGN KEYs in CREATE TABLE (#554)
Browse files Browse the repository at this point in the history
Convert inline `FOREIGN KEY` constraints 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"
    }
  }
]
```
  • Loading branch information
andrew-farries authored Dec 19, 2024
1 parent 6dd898a commit b65d850
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 8 deletions.
49 changes: 41 additions & 8 deletions pkg/sql2pgroll/create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func convertColumnDef(tableName string, col *pgq.ColumnDef) (*migrations.Column,
// Convert column constraints
var notNull, pk, unique bool
var check *migrations.CheckConstraint
var foreignKey *migrations.ForeignKeyReference
var defaultValue *string
for _, c := range col.GetConstraints() {
switch c.GetConstraint().GetContype() {
Expand Down Expand Up @@ -131,20 +132,25 @@ func convertColumnDef(tableName string, col *pgq.ColumnDef) (*migrations.Column,
}
defaultValue = &d
case pgq.ConstrType_CONSTR_FOREIGN:
if !canConvertForeignKeyConstraint(c.GetConstraint()) {
foreignKey, err = convertInlineForeignKeyConstraint(tableName, col.GetColname(), c.GetConstraint())
if err != nil {
return nil, fmt.Errorf("error converting inline foreign key constraint: %w", err)
}
if foreignKey == nil {
return nil, nil
}
}
}

return &migrations.Column{
Name: col.GetColname(),
Type: typeString,
Nullable: !notNull,
Pk: pk,
Check: check,
Default: defaultValue,
Unique: unique,
Name: col.GetColname(),
Type: typeString,
Nullable: !notNull,
Pk: pk,
Check: check,
References: foreignKey,
Default: defaultValue,
Unique: unique,
}, nil
}

Expand Down Expand Up @@ -179,6 +185,8 @@ func canConvertPrimaryKeyConstraint(constraint *pgq.Constraint) bool {
}
}

// convertInlineCheckConstraint converts an inline check constraint to a
// `CheckConstraint`.
func convertInlineCheckConstraint(tableName, columnName string, constraint *pgq.Constraint) (*migrations.CheckConstraint, error) {
if !canConvertCheckConstraint(constraint) {
return nil, nil
Expand All @@ -199,3 +207,28 @@ func convertInlineCheckConstraint(tableName, columnName string, constraint *pgq.
Constraint: expr,
}, nil
}

// convertInlineForeignKeyConstraint converts an inline foreign key constraint
// to a `ForeignKeyReference`.
func convertInlineForeignKeyConstraint(tableName, columnName string, constraint *pgq.Constraint) (*migrations.ForeignKeyReference, error) {
if !canConvertForeignKeyConstraint(constraint) {
return nil, nil
}

onDelete, err := parseOnDeleteAction(constraint.GetFkDelAction())
if err != nil {
return nil, err
}

name := fmt.Sprintf("%s_%s_fkey", tableName, columnName)
if constraint.GetConname() != "" {
name = constraint.GetConname()
}

return &migrations.ForeignKeyReference{
Name: name,
OnDelete: onDelete,
Column: constraint.GetPkAttrs()[0].GetString_().GetSval(),
Table: getQualifiedRelationName(constraint.GetPktable()),
}, nil
}
28 changes: 28 additions & 0 deletions pkg/sql2pgroll/create_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,34 @@ func TestConvertCreateTableStatements(t *testing.T) {
sql: "CREATE TABLE foo(a timestamptz DEFAULT now())",
expectedOp: expect.CreateTableOp11,
},
{
sql: "CREATE TABLE foo(a int REFERENCES bar(b))",
expectedOp: expect.CreateTableOp12,
},
{
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON UPDATE NO ACTION)",
expectedOp: expect.CreateTableOp12,
},
{
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE NO ACTION)",
expectedOp: expect.CreateTableOp12,
},
{
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE RESTRICT)",
expectedOp: expect.CreateTableOp13,
},
{
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE SET NULL)",
expectedOp: expect.CreateTableOp14,
},
{
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE SET DEFAULT)",
expectedOp: expect.CreateTableOp15,
},
{
sql: "CREATE TABLE foo(a int REFERENCES bar(b) ON DELETE CASCADE)",
expectedOp: expect.CreateTableOp16,
},
{
sql: "CREATE TABLE foo(a varchar(255))",
expectedOp: expect.CreateTableOp3,
Expand Down
85 changes: 85 additions & 0 deletions pkg/sql2pgroll/expect/create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,88 @@ var CreateTableOp11 = &migrations.OpCreateTable{
},
},
}

var CreateTableOp12 = &migrations.OpCreateTable{
Name: "foo",
Columns: []migrations.Column{
{
Name: "a",
Type: "int",
Nullable: true,
References: &migrations.ForeignKeyReference{
Name: "foo_a_fkey",
Table: "bar",
Column: "b",
OnDelete: migrations.ForeignKeyReferenceOnDeleteNOACTION,
},
},
},
}

var CreateTableOp13 = &migrations.OpCreateTable{
Name: "foo",
Columns: []migrations.Column{
{
Name: "a",
Type: "int",
Nullable: true,
References: &migrations.ForeignKeyReference{
Name: "foo_a_fkey",
Table: "bar",
Column: "b",
OnDelete: migrations.ForeignKeyReferenceOnDeleteRESTRICT,
},
},
},
}

var CreateTableOp14 = &migrations.OpCreateTable{
Name: "foo",
Columns: []migrations.Column{
{
Name: "a",
Type: "int",
Nullable: true,
References: &migrations.ForeignKeyReference{
Name: "foo_a_fkey",
Table: "bar",
Column: "b",
OnDelete: migrations.ForeignKeyReferenceOnDeleteSETNULL,
},
},
},
}

var CreateTableOp15 = &migrations.OpCreateTable{
Name: "foo",
Columns: []migrations.Column{
{
Name: "a",
Type: "int",
Nullable: true,
References: &migrations.ForeignKeyReference{
Name: "foo_a_fkey",
Table: "bar",
Column: "b",
OnDelete: migrations.ForeignKeyReferenceOnDeleteSETDEFAULT,
},
},
},
}

var CreateTableOp16 = &migrations.OpCreateTable{
Name: "foo",
Columns: []migrations.Column{
{
Name: "a",
Type: "int",
Nullable: true,
References: &migrations.ForeignKeyReference{
Name: "foo_a_fkey",
Table: "bar",
Column: "b",
OnDelete: migrations.ForeignKeyReferenceOnDeleteCASCADE,
},
},
},
}

0 comments on commit b65d850

Please sign in to comment.