diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c new file mode 100644 index a910f81..19cf77f *** a/src/backend/catalog/heap.c --- b/src/backend/catalog/heap.c *************** static bool MergeWithExistingConstraint( *** 103,111 **** bool allow_merge, bool is_local, bool is_no_inherit); static void SetRelationNumChecks(Relation rel, int numchecks); - static Node *cookConstraint(ParseState *pstate, - Node *raw_constraint, - char *relname); static List *insert_ordered_unique_oid(List *list, Oid datum); --- 103,108 ---- *************** AddRelationNewConstraints(Relation rel, *** 2218,2224 **** * it's valid as a CHECK constraint. */ expr = cookConstraint(pstate, cdef->raw_expr, ! RelationGetRelationName(rel)); } else { --- 2215,2221 ---- * it's valid as a CHECK constraint. */ expr = cookConstraint(pstate, cdef->raw_expr, ! RelationGetRelationName(rel), true); } else { *************** cookDefault(ParseState *pstate, *** 2569,2578 **** * Parse state must be set up to recognize any vars that might appear * in the expression. */ ! static Node * cookConstraint(ParseState *pstate, Node *raw_constraint, ! char *relname) { Node *expr; --- 2566,2576 ---- * Parse state must be set up to recognize any vars that might appear * in the expression. */ ! Node * cookConstraint(ParseState *pstate, Node *raw_constraint, ! char *relname, ! bool is_check) { Node *expr; *************** cookConstraint(ParseState *pstate, *** 2584,2590 **** /* * Make sure it yields a boolean result. */ ! expr = coerce_to_boolean(pstate, expr, "CHECK"); /* * Take care of collations. --- 2582,2588 ---- /* * Make sure it yields a boolean result. */ ! expr = coerce_to_boolean(pstate, expr, is_check ? "CHECK" : "WHERE"); /* * Take care of collations. *************** cookConstraint(ParseState *pstate, *** 2598,2605 **** if (list_length(pstate->p_rtable) != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), ! errmsg("only table \"%s\" can be referenced in check constraint", ! relname))); return expr; } --- 2596,2603 ---- if (list_length(pstate->p_rtable) != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), ! errmsg("only table \"%s\" can be referenced in %s constraint", ! relname, is_check ? "check" : "foreign key"))); return expr; } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c new file mode 100644 index 3483107..261a715 *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** ATAddForeignKeyConstraint(AlteredTableIn *** 5831,5836 **** --- 5831,5840 ---- Oid constrOid; bool old_check_ok; ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop); + Node *expr = NULL; + char *ccbin = NULL; + char *ccsrc = NULL; + /* * Grab an exclusive lock on the pk table, so that someone doesn't delete *************** ATAddForeignKeyConstraint(AlteredTableIn *** 6142,6147 **** --- 6146,6182 ---- ffeqoperators[i] = ffeqop; } + if (fkconstraint->raw_expr) + { + /* FK with WHERE clause, cook it */ + + /* + * Create a dummy ParseState and insert the target relation as its sole + * rangetable entry. We need a ParseState for transformExpr. + */ + ParseState *pstate = make_parsestate(NULL); + RangeTblEntry *rte = addRangeTableEntryForRelation(pstate, + rel, + NULL, + false, + true); + addRTEtoQuery(pstate, rte, true, true, true); + + /* now cook it */ + expr = cookConstraint(pstate, fkconstraint->raw_expr, + RelationGetRelationName(rel), false); + + /* Flatten expression to string form for storage. */ + ccbin = nodeToString(expr); + + /* Also deparse it to form the consrc field. */ + ccsrc = deparse_expression(expr, + deparse_context_for(RelationGetRelationName(rel), + RelationGetRelid(rel)), + false, false); + } + + /* * Record the FK constraint in pg_constraint. */ *************** ATAddForeignKeyConstraint(AlteredTableIn *** 6167,6175 **** fkconstraint->fk_del_action, fkconstraint->fk_matchtype, NULL, /* no exclusion constraint */ ! NULL, /* no check constraint */ ! NULL, ! NULL, true, /* islocal */ 0, /* inhcount */ true, /* isnoinherit */ --- 6202,6210 ---- fkconstraint->fk_del_action, fkconstraint->fk_matchtype, NULL, /* no exclusion constraint */ ! expr, ! ccbin, ! ccsrc, true, /* islocal */ 0, /* inhcount */ true, /* isnoinherit */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y new file mode 100644 index 1922097..b8aaccd *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** ConstraintElem: *** 3111,3117 **** $$ = (Node *)n; } | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name ! opt_column_list key_match key_actions ConstraintAttributeSpec { Constraint *n = makeNode(Constraint); n->contype = CONSTR_FOREIGN; --- 3111,3118 ---- $$ = (Node *)n; } | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name ! opt_column_list ExclusionWhereClause key_match key_actions ! ConstraintAttributeSpec { Constraint *n = makeNode(Constraint); n->contype = CONSTR_FOREIGN; *************** ConstraintElem: *** 3119,3128 **** n->pktable = $7; n->fk_attrs = $4; n->pk_attrs = $8; ! n->fk_matchtype = $9; ! n->fk_upd_action = (char) ($10 >> 8); ! n->fk_del_action = (char) ($10 & 0xFF); ! processCASbits($11, @11, "FOREIGN KEY", &n->deferrable, &n->initdeferred, &n->skip_validation, NULL, yyscanner); --- 3120,3130 ---- n->pktable = $7; n->fk_attrs = $4; n->pk_attrs = $8; ! n->raw_expr = $9; ! n->fk_matchtype = $10; ! n->fk_upd_action = (char) ($11 >> 8); ! n->fk_del_action = (char) ($11 & 0xFF); ! processCASbits($12, @12, "FOREIGN KEY", &n->deferrable, &n->initdeferred, &n->skip_validation, NULL, yyscanner); diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c new file mode 100644 index 917130f..311d33f *** a/src/backend/utils/adt/ri_triggers.c --- b/src/backend/utils/adt/ri_triggers.c *************** *** 40,45 **** --- 40,46 ---- #include "commands/trigger.h" #include "executor/executor.h" #include "executor/spi.h" + #include "optimizer/clauses.h" #include "parser/parse_coerce.h" #include "parser/parse_relation.h" #include "miscadmin.h" *************** typedef struct RI_ConstraintInfo *** 123,128 **** --- 124,131 ---- * PK) */ Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = * FK) */ + char *consrc; /* source of optional where expression */ + char *conbin; /* node tree of optional where expression */ } RI_ConstraintInfo; *************** RI_FKey_check(TriggerData *trigdata) *** 383,388 **** --- 386,435 ---- break; } + if(riinfo->conbin) + { + /* constraint has a where clause, let's evaluate it */ + ExprContext *econtext; + TupleDesc tupDesc; + TupleTableSlot *slot; + bool result; + EState *estate; + List *qual; + List *exprState; + + /* need to create a new executor to evaluate the expression with */ + estate = CreateExecutorState(); + + /* ExecQual needs the expression in this form */ + /* TODO: cache this somewhere? put on the riinfo? */ + qual = make_ands_implicit(stringToNode(riinfo->conbin)); + + exprState = (List *) ExecPrepareExpr((Expr *) qual, estate); + + /* set up tupleslot */ + tupDesc = RelationGetDescr(fk_rel); + econtext = GetPerTupleExprContext(estate); + slot = ExecInitExtraTupleSlot(estate); + ExecSetSlotDescriptor(slot, tupDesc); + ExecStoreTuple(trigdata->tg_trigtuple, slot, InvalidBuffer, false); + econtext->ecxt_scantuple = slot; + + /* evaluate the expression */ + result = ExecQual(exprState, econtext, false); + FreeExecutorState(estate); + ReleaseTupleDesc(tupDesc); + + if (!result) + { + /* + * where clause expression eval returned false, so + * the constraint does not apply to this row + */ + heap_close(pk_rel, RowShareLock); + return PointerGetDatum(NULL); + } + } + if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); *************** ri_restrict_del(TriggerData *trigdata, b *** 754,759 **** --- 801,810 ---- querysep = "AND"; queryoids[i] = pk_type; } + if(riinfo->consrc) + { + appendStringInfo(&querybuf, " AND %s", riinfo->consrc); + } appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); /* Prepare and save the plan */ *************** ri_restrict_upd(TriggerData *trigdata, b *** 977,982 **** --- 1028,1037 ---- querysep = "AND"; queryoids[i] = pk_type; } + if(riinfo->consrc) + { + appendStringInfo(&querybuf, " AND %s", riinfo->consrc); + } appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); /* Prepare and save the plan */ *************** RI_FKey_cascade_del(PG_FUNCTION_ARGS) *** 1133,1138 **** --- 1188,1197 ---- querysep = "AND"; queryoids[i] = pk_type; } + if(riinfo->consrc) + { + appendStringInfo(&querybuf, " AND %s", riinfo->consrc); + } /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, *************** RI_FKey_cascade_upd(PG_FUNCTION_ARGS) *** 1315,1320 **** --- 1374,1383 ---- queryoids[j] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); + if(riinfo->consrc) + { + appendStringInfo(&querybuf, " AND %s", riinfo->consrc); + } /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids, *************** RI_FKey_setnull_del(PG_FUNCTION_ARGS) *** 1480,1485 **** --- 1543,1552 ---- queryoids[i] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); + if(riinfo->consrc) + { + appendStringInfo(&querybuf, " AND %s", riinfo->consrc); + } /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, *************** RI_FKey_setnull_upd(PG_FUNCTION_ARGS) *** 1656,1661 **** --- 1723,1732 ---- queryoids[i] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); + if(riinfo->consrc) + { + appendStringInfo(&querybuf, " AND %s", riinfo->consrc); + } /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, *************** RI_FKey_setdefault_del(PG_FUNCTION_ARGS) *** 1822,1827 **** --- 1893,1902 ---- queryoids[i] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); + if(riinfo->consrc) + { + appendStringInfo(&querybuf, " AND %s", riinfo->consrc); + } /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, *************** RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) *** 2013,2018 **** --- 2088,2097 ---- queryoids[i] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); + if(riinfo->consrc) + { + appendStringInfo(&querybuf, " AND %s", riinfo->consrc); + } /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, *************** ri_LoadConstraintInfo(Oid constraintOid) *** 2788,2793 **** --- 2867,2873 ---- bool isNull; ArrayType *arr; int numkeys; + MemoryContext oldContext; /* * On the first call initialize the hashtable *************** ri_LoadConstraintInfo(Oid constraintOid) *** 2908,2913 **** --- 2988,3005 ---- if ((Pointer) arr != DatumGetPointer(adatum)) pfree(arr); /* free de-toasted copy, if any */ + + /* copy where clause constraint info for partial keys */ + oldContext = MemoryContextSwitchTo(CacheMemoryContext); + adatum = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_consrc, &isNull); + riinfo->consrc = isNull ? NULL : TextDatumGetCString(adatum); + + adatum = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_conbin, &isNull); + riinfo->conbin = isNull ? NULL : TextDatumGetCString(adatum); + MemoryContextSwitchTo(oldContext); + ReleaseSysCache(tup); riinfo->valid = true; diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h new file mode 100644 index b43765b..ae1ecdd *** a/src/include/catalog/heap.h --- b/src/include/catalog/heap.h *************** extern Node *cookDefault(ParseState *pst *** 107,112 **** --- 107,116 ---- Oid atttypid, int32 atttypmod, char *attname); + extern Node *cookConstraint(ParseState *pstate, + Node *raw_constraint, + char *relname, + bool is_check); extern void DeleteRelationTuple(Oid relid); extern void DeleteAttributeTuples(Oid relid); diff --git a/src/test/regress/expected/foreign_key_where.out b/src/test/regress/expected/foreign_key_where.out new file mode 100644 index ...9c63590 *** a/src/test/regress/expected/foreign_key_where.out --- b/src/test/regress/expected/foreign_key_where.out *************** *** 0 **** --- 1,302 ---- + -- + -- Test foreign keys with WHERE clauses + -- + -- + -- Basic setup + -- + create type commentable_entity as enum ('blog', 'event'); + create table blog ( + id int4 primary key + ); + create table event ( + id int4 primary key + ); + create table comment ( + id int4, + parent_entity commentable_entity not null, + parent_id int4 default 66 + ); + -- + -- basic fk insertion / pk deletion / pk update with restrict + -- + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete restrict; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update restrict; + -- data setup + insert into blog values (10); + insert into event values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'event', 20); + -- failing insert + insert into comment values(3, 'blog', 20); -- fails + ERROR: insert or update on table "comment" violates foreign key constraint "comment_blog_fk" + DETAIL: Key (parent_id)=(20) is not present in table "blog". + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | 10 + 2 | event | 20 + (2 rows) + + -- delete of foreign key + delete from blog; -- fails + ERROR: update or delete on table "blog" violates foreign key constraint "comment_blog_fk" on table "comment" + DETAIL: Key (id)=(10) is still referenced from table "comment". + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | 10 + 2 | event | 20 + (2 rows) + + -- update of foreign key + update event set id = 77; -- fails + ERROR: update or delete on table "event" violates foreign key constraint "comment_event_fk" on table "comment" + DETAIL: Key (id)=(20) is still referenced from table "comment". + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | 10 + 2 | event | 20 + (2 rows) + + -- cleanup + delete from comment; + delete from blog; + delete from event; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + -- + -- cascade deletions / updates + -- + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete cascade; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update cascade; + -- data setup + insert into blog values (10); + insert into blog values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'blog', 20); + insert into event values (10); + insert into event values (20); + insert into comment values(3, 'event', 10); + insert into comment values(4, 'event', 20); + -- cascading update + update event set id = 22 where id = 20; + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | 10 + 2 | blog | 20 + 3 | event | 10 + 4 | event | 22 + (4 rows) + + -- cascading delete + delete from blog where id = 10; + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 2 | blog | 20 + 3 | event | 10 + 4 | event | 22 + (3 rows) + + -- cleanup + delete from comment; + delete from event; + delete from blog; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + -- + -- set null + -- + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete set null; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update set null; + -- data setup + insert into blog values (10); + insert into blog values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'blog', 20); + insert into event values (10); + insert into event values (20); + insert into comment values(3, 'event', 10); + insert into comment values(4, 'event', 20); + -- update + update event set id = 22 where id = 20; + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | 10 + 2 | blog | 20 + 3 | event | 10 + 4 | event | + (4 rows) + + -- delete + delete from blog where id = 10; + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | + 2 | blog | 20 + 3 | event | 10 + 4 | event | + (4 rows) + + -- cleanup + delete from comment; + delete from event; + delete from blog; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + -- + -- set default + -- + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete set default; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update set default; + -- data setup + insert into blog values (10); + insert into blog values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'blog', 20); + insert into event values (10); + insert into event values (20); + insert into comment values(3, 'event', 10); + insert into comment values(4, 'event', 20); + -- update + update event set id = 22 where id = 20; -- fails + ERROR: insert or update on table "comment" violates foreign key constraint "comment_event_fk" + DETAIL: Key (parent_id)=(66) is not present in table "event". + insert into event values (66); + update event set id = 22 where id = 20; + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | 10 + 2 | blog | 20 + 3 | event | 10 + 4 | event | 66 + (4 rows) + + -- delete + delete from blog where id = 10; -- fails + ERROR: insert or update on table "comment" violates foreign key constraint "comment_blog_fk" + DETAIL: Key (parent_id)=(66) is not present in table "blog". + insert into blog values (66); + delete from blog where id = 10; + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | 66 + 2 | blog | 20 + 3 | event | 10 + 4 | event | 66 + (4 rows) + + -- cleanup + delete from comment; + delete from event; + delete from blog; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + -- + -- no action with deferred constraints + -- + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete no action + deferrable initially deferred; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update no action + deferrable initially deferred; + -- data setup + insert into blog values (10); + insert into blog values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'blog', 20); + insert into event values (10); + insert into event values (20); + insert into comment values(3, 'event', 10); + insert into comment values(4, 'event', 20); + -- update + begin; + update event set id = 22 where id = 20; + insert into event values (20); + commit; + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | 10 + 2 | blog | 20 + 3 | event | 10 + 4 | event | 20 + (4 rows) + + -- delete + begin; + delete from blog where id = 10; + insert into blog values (10); + commit; + select * from comment order by id; + id | parent_entity | parent_id + ----+---------------+----------- + 1 | blog | 10 + 2 | blog | 20 + 3 | event | 10 + 4 | event | 20 + (4 rows) + + -- cleanup + delete from comment; + delete from event; + delete from blog; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + -- + -- error handling + -- + -- non-existent column + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (foo = 'blog'); + ERROR: column "foo" does not exist + -- wrong type + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 1234); + ERROR: operator does not exist: commentable_entity = integer + HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts. + -- expression not boolean + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity::text); + ERROR: argument of WHERE must be type boolean, not type text + -- + -- validation when table is pre-populated + -- + insert into comment values(1, 'blog', 10); + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog'); -- fails + ERROR: insert or update on table "comment" violates foreign key constraint "comment_blog_fk" + DETAIL: Key (parent_id)=(10) is not present in table "blog". + insert into blog values(10); + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog'); diff --git a/src/test/regress/sql/foreign_key_where.sql b/src/test/regress/sql/foreign_key_where.sql new file mode 100644 index ...ad23ce4 *** a/src/test/regress/sql/foreign_key_where.sql --- b/src/test/regress/sql/foreign_key_where.sql *************** *** 0 **** --- 1,242 ---- + -- + -- Test foreign keys with WHERE clauses + -- + + -- + -- Basic setup + -- + create type commentable_entity as enum ('blog', 'event'); + create table blog ( + id int4 primary key + ); + create table event ( + id int4 primary key + ); + create table comment ( + id int4, + parent_entity commentable_entity not null, + parent_id int4 default 66 + ); + + -- + -- basic fk insertion / pk deletion / pk update with restrict + -- + + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete restrict; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update restrict; + + -- data setup + insert into blog values (10); + insert into event values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'event', 20); + + -- failing insert + insert into comment values(3, 'blog', 20); -- fails + select * from comment order by id; + + -- delete of foreign key + delete from blog; -- fails + select * from comment order by id; + + -- update of foreign key + update event set id = 77; -- fails + select * from comment order by id; + + -- cleanup + delete from comment; + delete from blog; + delete from event; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + + -- + -- cascade deletions / updates + -- + + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete cascade; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update cascade; + + -- data setup + insert into blog values (10); + insert into blog values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'blog', 20); + insert into event values (10); + insert into event values (20); + insert into comment values(3, 'event', 10); + insert into comment values(4, 'event', 20); + + -- cascading update + update event set id = 22 where id = 20; + select * from comment order by id; + + -- cascading delete + delete from blog where id = 10; + select * from comment order by id; + + -- cleanup + delete from comment; + delete from event; + delete from blog; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + + -- + -- set null + -- + + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete set null; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update set null; + + -- data setup + insert into blog values (10); + insert into blog values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'blog', 20); + insert into event values (10); + insert into event values (20); + insert into comment values(3, 'event', 10); + insert into comment values(4, 'event', 20); + + -- update + update event set id = 22 where id = 20; + select * from comment order by id; + + -- delete + delete from blog where id = 10; + select * from comment order by id; + + -- cleanup + delete from comment; + delete from event; + delete from blog; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + + -- + -- set default + -- + + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete set default; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update set default; + + -- data setup + insert into blog values (10); + insert into blog values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'blog', 20); + insert into event values (10); + insert into event values (20); + insert into comment values(3, 'event', 10); + insert into comment values(4, 'event', 20); + + -- update + update event set id = 22 where id = 20; -- fails + insert into event values (66); + update event set id = 22 where id = 20; + select * from comment order by id; + + -- delete + delete from blog where id = 10; -- fails + insert into blog values (66); + delete from blog where id = 10; + select * from comment order by id; + + -- cleanup + delete from comment; + delete from event; + delete from blog; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + + -- + -- no action with deferred constraints + -- + + -- add constraints + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog') + on delete no action + deferrable initially deferred; + alter table comment add constraint comment_event_fk foreign key (parent_id) + references event(id) where (parent_entity = 'event') + on update no action + deferrable initially deferred; + + -- data setup + insert into blog values (10); + insert into blog values (20); + insert into comment values(1, 'blog', 10); + insert into comment values(2, 'blog', 20); + insert into event values (10); + insert into event values (20); + insert into comment values(3, 'event', 10); + insert into comment values(4, 'event', 20); + + -- update + begin; + update event set id = 22 where id = 20; + insert into event values (20); + commit; + select * from comment order by id; + + -- delete + begin; + delete from blog where id = 10; + insert into blog values (10); + commit; + select * from comment order by id; + + -- cleanup + delete from comment; + delete from event; + delete from blog; + alter table comment drop constraint comment_blog_fk; + alter table comment drop constraint comment_event_fk; + + -- + -- error handling + -- + + -- non-existent column + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (foo = 'blog'); + + -- wrong type + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 1234); + -- expression not boolean + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity::text); + + + -- + -- validation when table is pre-populated + -- + insert into comment values(1, 'blog', 10); + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog'); -- fails + insert into blog values(10); + alter table comment add constraint comment_blog_fk foreign key (parent_id) + references blog(id) where (parent_entity = 'blog');