Skip to content

Commit bbdb2f8

Browse files
authored
Add onUpdate and matchType to the output of analyze/read_schema (#654)
From now on `matchType` and `onUpdate` is exposed in the list of foreign keys: Example output for `fk_sellers` FK: ```json "foreignKeys": { "fk_sellers": { "name": "fk_sellers", "columns": [ "sellers_name", "sellers_zip" ], "referencedTable": "sellers", "referencedColumns": [ "name", "zip" ], "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "matchType": "SIMPLE" } }, ```
1 parent 7478d84 commit bbdb2f8

File tree

3 files changed

+228
-7
lines changed

3 files changed

+228
-7
lines changed

pkg/schema/schema.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ type ForeignKey struct {
133133
OnUpdate string `json:"onUpdate"`
134134

135135
// MatchType is the match type of the foreign key
136-
MatchType string `json:"match_type"`
136+
MatchType string `json:"matchType"`
137137
}
138138

139139
// CheckConstraint represents a check constraint on a table

pkg/state/init.sql

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,16 @@ BEGIN
236236
uc_constraint.conrelid = t.oid
237237
AND uc_constraint.contype = 'u' GROUP BY uc_constraint.oid, uc_constraint.conname) AS uc_details), 'foreignKeys', (
238238
SELECT
239-
COALESCE(json_object_agg(fk_details.conname, json_build_object('name', fk_details.conname, 'columns', fk_details.columns, 'referencedTable', fk_details.referencedTable, 'referencedColumns', fk_details.referencedColumns, 'onDelete', fk_details.onDelete)), '{}'::json)
239+
COALESCE(json_object_agg(fk_details.conname, json_build_object('name', fk_details.conname, 'columns', fk_details.columns, 'referencedTable', fk_details.referencedTable, 'referencedColumns', fk_details.referencedColumns, 'matchType', fk_details.matchType, 'onDelete', fk_details.onDelete, 'onUpdate', fk_details.onUpdate)), '{}'::json)
240240
FROM (
241241
SELECT
242-
fk_info.conname AS conname, fk_info.columns AS columns, fk_info.relname AS referencedTable, array_agg(ref_attr.attname ORDER BY ref_attr.attname) AS referencedColumns, CASE WHEN fk_info.confdeltype = 'a' THEN
242+
fk_info.conname AS conname, fk_info.columns AS columns, fk_info.relname AS referencedTable, array_agg(ref_attr.attname ORDER BY ref_attr.attname) AS referencedColumns, CASE WHEN fk_info.confmatchtype = 'f' THEN
243+
'FULL'
244+
WHEN fk_info.confmatchtype = 'p' THEN
245+
'PARTIAL'
246+
WHEN fk_info.confmatchtype = 's' THEN
247+
'SIMPLE'
248+
END AS matchType, CASE WHEN fk_info.confdeltype = 'a' THEN
243249
'NO ACTION'
244250
WHEN fk_info.confdeltype = 'r' THEN
245251
'RESTRICT'
@@ -249,18 +255,28 @@ BEGIN
249255
'SET DEFAULT'
250256
WHEN fk_info.confdeltype = 'n' THEN
251257
'SET NULL'
252-
END AS onDelete FROM (
258+
END AS onDelete, CASE WHEN fk_info.confupdtype = 'a' THEN
259+
'NO ACTION'
260+
WHEN fk_info.confupdtype = 'r' THEN
261+
'RESTRICT'
262+
WHEN fk_info.confupdtype = 'c' THEN
263+
'CASCADE'
264+
WHEN fk_info.confupdtype = 'd' THEN
265+
'SET DEFAULT'
266+
WHEN fk_info.confupdtype = 'n' THEN
267+
'SET NULL'
268+
END AS onUpdate FROM (
253269
SELECT
254-
fk_constraint.conname, fk_constraint.conrelid, fk_constraint.confrelid, fk_constraint.confkey, fk_cl.relname, fk_constraint.confdeltype, array_agg(fk_attr.attname ORDER BY fk_attr.attname) AS columns FROM pg_constraint AS fk_constraint
270+
fk_constraint.conname, fk_constraint.conrelid, fk_constraint.confrelid, fk_constraint.confkey, fk_cl.relname, fk_constraint.confmatchtype, fk_constraint.confdeltype, fk_constraint.confupdtype, array_agg(fk_attr.attname ORDER BY fk_attr.attname) AS columns FROM pg_constraint AS fk_constraint
255271
INNER JOIN pg_class fk_cl ON fk_constraint.confrelid = fk_cl.oid -- join the referenced table
256272
INNER JOIN pg_attribute fk_attr ON fk_attr.attrelid = fk_constraint.conrelid
257273
AND fk_attr.attnum = ANY (fk_constraint.conkey) -- join the columns of the referencing table
258274
WHERE
259275
fk_constraint.conrelid = t.oid
260-
AND fk_constraint.contype = 'f' GROUP BY fk_constraint.conrelid, fk_constraint.conname, fk_constraint.confrelid, fk_cl.relname, fk_constraint.confkey, fk_constraint.confdeltype) AS fk_info
276+
AND fk_constraint.contype = 'f' GROUP BY fk_constraint.conrelid, fk_constraint.conname, fk_constraint.confrelid, fk_cl.relname, fk_constraint.confkey, fk_constraint.confmatchtype, fk_constraint.confdeltype, fk_constraint.confupdtype) AS fk_info
261277
INNER JOIN pg_attribute ref_attr ON ref_attr.attrelid = fk_info.confrelid
262278
AND ref_attr.attnum = ANY (fk_info.confkey) -- join the columns of the referenced table
263-
GROUP BY fk_info.conname, fk_info.conrelid, fk_info.columns, fk_info.confrelid, fk_info.confdeltype, fk_info.relname) AS fk_details))), '{}'::json)
279+
GROUP BY fk_info.conname, fk_info.conrelid, fk_info.columns, fk_info.confrelid, fk_info.confmatchtype, fk_info.confdeltype, fk_info.confupdtype, fk_info.relname) AS fk_details))), '{}'::json)
264280
FROM pg_class AS t
265281
INNER JOIN pg_namespace AS ns ON t.relnamespace = ns.oid
266282
LEFT JOIN pg_description AS descr ON t.oid = descr.objoid

pkg/state/state_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,9 @@ func TestReadSchema(t *testing.T) {
550550
Columns: []string{"fk"},
551551
ReferencedTable: "table1",
552552
ReferencedColumns: []string{"id"},
553+
MatchType: "SIMPLE",
553554
OnDelete: "NO ACTION",
555+
OnUpdate: "NO ACTION",
554556
},
555557
},
556558
CheckConstraints: map[string]*schema.CheckConstraint{},
@@ -608,7 +610,129 @@ func TestReadSchema(t *testing.T) {
608610
Columns: []string{"fk"},
609611
ReferencedTable: "table1",
610612
ReferencedColumns: []string{"id"},
613+
MatchType: "SIMPLE",
611614
OnDelete: "CASCADE",
615+
OnUpdate: "NO ACTION",
616+
},
617+
},
618+
CheckConstraints: map[string]*schema.CheckConstraint{},
619+
UniqueConstraints: map[string]*schema.UniqueConstraint{},
620+
},
621+
},
622+
},
623+
},
624+
{
625+
name: "foreign key with ON DELETE CASCADE ON UPDATE CASCADE",
626+
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 ON UPDATE CASCADE)",
627+
wantSchema: &schema.Schema{
628+
Name: "public",
629+
Tables: map[string]*schema.Table{
630+
"table1": {
631+
Name: "table1",
632+
Columns: map[string]*schema.Column{
633+
"id": {
634+
Name: "id",
635+
Type: "integer",
636+
Nullable: false,
637+
Unique: true,
638+
PostgresType: "base",
639+
},
640+
},
641+
PrimaryKey: []string{"id"},
642+
Indexes: map[string]*schema.Index{
643+
"table1_pkey": {
644+
Name: "table1_pkey",
645+
Unique: true,
646+
Columns: []string{"id"},
647+
Method: string(migrations.OpCreateIndexMethodBtree),
648+
Definition: "CREATE UNIQUE INDEX table1_pkey ON public.table1 USING btree (id)",
649+
},
650+
},
651+
CheckConstraints: map[string]*schema.CheckConstraint{},
652+
UniqueConstraints: map[string]*schema.UniqueConstraint{},
653+
ForeignKeys: map[string]*schema.ForeignKey{},
654+
},
655+
"table2": {
656+
Name: "table2",
657+
Columns: map[string]*schema.Column{
658+
"fk": {
659+
Name: "fk",
660+
Type: "integer",
661+
Nullable: false,
662+
PostgresType: "base",
663+
},
664+
},
665+
PrimaryKey: []string{},
666+
Indexes: map[string]*schema.Index{},
667+
ForeignKeys: map[string]*schema.ForeignKey{
668+
"fk_fkey": {
669+
Name: "fk_fkey",
670+
Columns: []string{"fk"},
671+
ReferencedTable: "table1",
672+
ReferencedColumns: []string{"id"},
673+
MatchType: "SIMPLE",
674+
OnDelete: "CASCADE",
675+
OnUpdate: "CASCADE",
676+
},
677+
},
678+
CheckConstraints: map[string]*schema.CheckConstraint{},
679+
UniqueConstraints: map[string]*schema.UniqueConstraint{},
680+
},
681+
},
682+
},
683+
},
684+
{
685+
name: "foreign key with MATCH full ON DELETE CASCADE",
686+
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) MATCH FULL ON DELETE CASCADE)",
687+
wantSchema: &schema.Schema{
688+
Name: "public",
689+
Tables: map[string]*schema.Table{
690+
"table1": {
691+
Name: "table1",
692+
Columns: map[string]*schema.Column{
693+
"id": {
694+
Name: "id",
695+
Type: "integer",
696+
Nullable: false,
697+
Unique: true,
698+
PostgresType: "base",
699+
},
700+
},
701+
PrimaryKey: []string{"id"},
702+
Indexes: map[string]*schema.Index{
703+
"table1_pkey": {
704+
Name: "table1_pkey",
705+
Unique: true,
706+
Columns: []string{"id"},
707+
Method: string(migrations.OpCreateIndexMethodBtree),
708+
Definition: "CREATE UNIQUE INDEX table1_pkey ON public.table1 USING btree (id)",
709+
},
710+
},
711+
CheckConstraints: map[string]*schema.CheckConstraint{},
712+
UniqueConstraints: map[string]*schema.UniqueConstraint{},
713+
ForeignKeys: map[string]*schema.ForeignKey{},
714+
},
715+
"table2": {
716+
Name: "table2",
717+
Columns: map[string]*schema.Column{
718+
"fk": {
719+
Name: "fk",
720+
Type: "integer",
721+
Nullable: false,
722+
PostgresType: "base",
723+
},
724+
},
725+
PrimaryKey: []string{},
726+
Indexes: map[string]*schema.Index{},
727+
ForeignKeys: map[string]*schema.ForeignKey{
728+
"fk_fkey": {
729+
Name: "fk_fkey",
730+
Columns: []string{"fk"},
731+
ReferencedTable: "table1",
732+
ReferencedColumns: []string{"id"},
733+
MatchType: "FULL",
734+
OnDelete: "CASCADE",
735+
OnUpdate: "NO ACTION",
612736
},
613737
},
614738
CheckConstraints: map[string]*schema.CheckConstraint{},
@@ -837,7 +961,88 @@ func TestReadSchema(t *testing.T) {
837961
Columns: []string{"customer_id", "product_id"},
838962
ReferencedTable: "products",
839963
ReferencedColumns: []string{"customer_id", "product_id"},
964+
MatchType: "SIMPLE",
965+
OnDelete: "NO ACTION",
966+
OnUpdate: "NO ACTION",
967+
},
968+
},
969+
CheckConstraints: map[string]*schema.CheckConstraint{},
970+
UniqueConstraints: map[string]*schema.UniqueConstraint{},
971+
},
972+
},
973+
},
974+
},
975+
{
976+
name: "multicolumn foreign key constraint with on update action",
977+
createStmt: `CREATE TABLE products(
978+
customer_id INT NOT NULL,
979+
product_id INT NOT NULL,
980+
PRIMARY KEY(customer_id, product_id));
981+
982+
CREATE TABLE orders(
983+
customer_id INT NOT NULL,
984+
product_id INT NOT NULL,
985+
CONSTRAINT fk_customer_product FOREIGN KEY (customer_id, product_id) REFERENCES products (customer_id, product_id) ON UPDATE CASCADE);`,
986+
wantSchema: &schema.Schema{
987+
Name: "public",
988+
Tables: map[string]*schema.Table{
989+
"products": {
990+
Name: "products",
991+
Columns: map[string]*schema.Column{
992+
"customer_id": {
993+
Name: "customer_id",
994+
Type: "integer",
995+
Nullable: false,
996+
PostgresType: "base",
997+
},
998+
"product_id": {
999+
Name: "product_id",
1000+
Type: "integer",
1001+
Nullable: false,
1002+
PostgresType: "base",
1003+
},
1004+
},
1005+
PrimaryKey: []string{"customer_id", "product_id"},
1006+
Indexes: map[string]*schema.Index{
1007+
"products_pkey": {
1008+
Name: "products_pkey",
1009+
Unique: true,
1010+
Columns: []string{"customer_id", "product_id"},
1011+
Method: string(migrations.OpCreateIndexMethodBtree),
1012+
Definition: "CREATE UNIQUE INDEX products_pkey ON public.products USING btree (customer_id, product_id)",
1013+
},
1014+
},
1015+
ForeignKeys: map[string]*schema.ForeignKey{},
1016+
CheckConstraints: map[string]*schema.CheckConstraint{},
1017+
UniqueConstraints: map[string]*schema.UniqueConstraint{},
1018+
},
1019+
"orders": {
1020+
Name: "orders",
1021+
Columns: map[string]*schema.Column{
1022+
"customer_id": {
1023+
Name: "customer_id",
1024+
Type: "integer",
1025+
Nullable: false,
1026+
PostgresType: "base",
1027+
},
1028+
"product_id": {
1029+
Name: "product_id",
1030+
Type: "integer",
1031+
Nullable: false,
1032+
PostgresType: "base",
1033+
},
1034+
},
1035+
PrimaryKey: []string{},
1036+
Indexes: map[string]*schema.Index{},
1037+
ForeignKeys: map[string]*schema.ForeignKey{
1038+
"fk_customer_product": {
1039+
Name: "fk_customer_product",
1040+
Columns: []string{"customer_id", "product_id"},
1041+
ReferencedTable: "products",
1042+
ReferencedColumns: []string{"customer_id", "product_id"},
1043+
MatchType: "SIMPLE",
8401044
OnDelete: "NO ACTION",
1045+
OnUpdate: "CASCADE",
8411046
},
8421047
},
8431048
CheckConstraints: map[string]*schema.CheckConstraint{},

0 commit comments

Comments
 (0)