Skip to content

Commit

Permalink
Track foreign key ON DELETE setting in the internal schema represen…
Browse files Browse the repository at this point in the history
…tation (#311)

Add the `ON DELETE` setting of a foreign key to the information stored
about the key in the internal schema representation.

The schema representation for a foreign key now looks like:

```json
{
  "some_table": {
    ...
    "foreignKeys": {
      "fk_users_id": {
        "name": "fk_users_id",
        "columns": [
          "user_id"
        ],
        "onDelete": "NO ACTION",
        "referencedTable": "users",
        "referencedColumns": [
          "id"
        ]
      }
    }
  }
}
```

Fixes #309
  • Loading branch information
andrew-farries authored Mar 7, 2024
1 parent 099a443 commit c88c060
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 3 deletions.
3 changes: 3 additions & 0 deletions pkg/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ type ForeignKey struct {

// The columns in the referenced table that the foreign key references
ReferencedColumns []string `json:"referencedColumns"`

// The ON DELETE behavior of the foreign key
OnDelete string `json:"onDelete"`
}

type CheckConstraint struct {
Expand Down
14 changes: 11 additions & 3 deletions pkg/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,21 +223,29 @@ BEGIN
'name', fk_details.conname,
'columns', fk_details.columns,
'referencedTable', fk_details.referencedTable,
'referencedColumns', fk_details.referencedColumns
'referencedColumns', fk_details.referencedColumns,
'onDelete', fk_details.onDelete
)), '{}'::json)
FROM (
SELECT
fk_constraint.conname,
array_agg(fk_attr.attname ORDER BY fk_constraint.conkey::int[]) AS columns,
fk_cl.relname AS referencedTable,
array_agg(ref_attr.attname ORDER BY fk_constraint.confkey::int[]) AS referencedColumns
array_agg(ref_attr.attname ORDER BY fk_constraint.confkey::int[]) AS referencedColumns,
CASE
WHEN fk_constraint.confdeltype = 'a' THEN 'NO ACTION'
WHEN fk_constraint.confdeltype = 'r' THEN 'RESTRICT'
WHEN fk_constraint.confdeltype = 'c' THEN 'CASCADE'
WHEN fk_constraint.confdeltype = 'd' THEN 'SET DEFAULT'
WHEN fk_constraint.confdeltype = 'n' THEN 'SET NULL'
END as onDelete
FROM pg_constraint AS fk_constraint
INNER JOIN pg_class fk_cl ON fk_constraint.confrelid = fk_cl.oid
INNER JOIN pg_attribute fk_attr ON fk_attr.attrelid = fk_constraint.conrelid AND fk_attr.attnum = ANY(fk_constraint.conkey)
INNER JOIN pg_attribute ref_attr ON ref_attr.attrelid = fk_constraint.confrelid AND ref_attr.attnum = ANY(fk_constraint.confkey)
WHERE fk_constraint.conrelid = t.oid
AND fk_constraint.contype = 'f'
GROUP BY fk_constraint.conname, fk_cl.relname
GROUP BY fk_constraint.conname, fk_cl.relname, fk_constraint.confdeltype
) AS fk_details
)
)), '{}'::json) FROM pg_class AS t
Expand Down
55 changes: 55 additions & 0 deletions pkg/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,61 @@ func TestReadSchema(t *testing.T) {
Columns: []string{"fk"},
ReferencedTable: "table1",
ReferencedColumns: []string{"id"},
OnDelete: "NO ACTION",
},
},
CheckConstraints: map[string]schema.CheckConstraint{},
UniqueConstraints: map[string]schema.UniqueConstraint{},
},
},
},
},
{
name: "foreign key with ON DELETE CASCADE",
createStmt: "CREATE TABLE public.table1 (id int PRIMARY KEY); CREATE TABLE public.table2 (fk int NOT NULL, CONSTRAINT fk_fkey FOREIGN KEY (fk) REFERENCES public.table1 (id) ON DELETE CASCADE)",
wantSchema: &schema.Schema{
Name: "public",
Tables: map[string]schema.Table{
"table1": {
Name: "table1",
Columns: map[string]schema.Column{
"id": {
Name: "id",
Type: "integer",
Nullable: false,
Unique: true,
},
},
PrimaryKey: []string{"id"},
Indexes: map[string]schema.Index{
"table1_pkey": {
Name: "table1_pkey",
Unique: true,
Columns: []string{"id"},
},
},
CheckConstraints: map[string]schema.CheckConstraint{},
UniqueConstraints: map[string]schema.UniqueConstraint{},
ForeignKeys: map[string]schema.ForeignKey{},
},
"table2": {
Name: "table2",
Columns: map[string]schema.Column{
"fk": {
Name: "fk",
Type: "integer",
Nullable: false,
},
},
PrimaryKey: []string{},
Indexes: map[string]schema.Index{},
ForeignKeys: map[string]schema.ForeignKey{
"fk_fkey": {
Name: "fk_fkey",
Columns: []string{"fk"},
ReferencedTable: "table1",
ReferencedColumns: []string{"id"},
OnDelete: "CASCADE",
},
},
CheckConstraints: map[string]schema.CheckConstraint{},
Expand Down

0 comments on commit c88c060

Please sign in to comment.