From b8ef7e7f82b7c81dd05e6bc167fe4471e5073be5 Mon Sep 17 00:00:00 2001 From: Jan Wieck <JanWieck@Yahoo.com> Date: Mon, 6 Dec 1999 18:02:47 +0000 Subject: [PATCH] Completed FOREIGN KEY syntax. Added functionality for automatic trigger creation during CREATE TABLE. Added ON DELETE RESTRICT and some others. Jan --- src/backend/commands/trigger.c | 2 + src/backend/parser/analyze.c | 398 ++++++++++++++++- src/backend/parser/gram.y | 201 ++++++--- src/backend/utils/adt/ri_triggers.c | 653 +++++++++++++++++++++++++++- src/include/nodes/nodes.h | 5 +- src/include/nodes/parsenodes.h | 33 +- 6 files changed, 1178 insertions(+), 114 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 9d23f255e59..d16d9a55843 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -394,6 +394,8 @@ RelationRemoveTriggers(Relation rel) stmt.relname = pstrdup(RelationGetRelationName(refrel)); stmt.trigname = nameout(&pg_trigger->tgname); + elog(NOTICE, "DROP TABLE implicitly drops referential integrity trigger from table \"%s\"", stmt.relname); + DropTrigger(&stmt); pfree(stmt.relname); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 4ad72439b36..e93bd13d9a1 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -5,7 +5,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: analyze.c,v 1.124 1999/11/15 02:00:09 tgl Exp $ + * $Id: analyze.c,v 1.125 1999/12/06 18:02:42 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -13,6 +13,8 @@ #include "postgres.h" #include "access/heapam.h" +#include "catalog/catname.h" +#include "catalog/pg_index.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "parse.h" @@ -35,6 +37,7 @@ static Query *transformCursorStmt(ParseState *pstate, SelectStmt *stmt); static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt); static void transformForUpdate(Query *qry, List *forUpdate); +static void transformFkeyGetPrimaryKey(FkConstraint *fkconstraint); void CheckSelectForUpdate(Query *qry); /* kluge to return extra info from transformCreateStmt() */ @@ -556,28 +559,32 @@ CreateIndexName(char *table_name, char *column_name, char *label, List *indices) static Query * transformCreateStmt(ParseState *pstate, CreateStmt *stmt) { - Query *q; - List *elements; - Node *element; - List *columns; - List *dlist; - ColumnDef *column; - List *constraints, - *clist; - Constraint *constraint; - List *keys; - Ident *key; - List *blist = NIL; /* "before list" of things to do before - * creating the table */ - List *ilist = NIL; /* "index list" of things to do after - * creating the table */ - IndexStmt *index, - *pkey = NULL; - IndexElem *iparam; + Query *q; + List *elements; + Node *element; + List *columns; + List *dlist; + ColumnDef *column; + List *constraints, + *clist; + Constraint *constraint; + List *fkconstraints, /* List of FOREIGN KEY constraints to */ + *fkclist; /* add finally */ + FkConstraint *fkconstraint; + List *keys; + Ident *key; + List *blist = NIL; /* "before list" of things to do before + * creating the table */ + List *ilist = NIL; /* "index list" of things to do after + * creating the table */ + IndexStmt *index, + *pkey = NULL; + IndexElem *iparam; q = makeNode(Query); q->commandType = CMD_UTILITY; + fkconstraints = NIL; constraints = stmt->constraints; columns = NIL; dlist = NIL; @@ -648,6 +655,28 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) foreach(clist, column->constraints) { constraint = lfirst(clist); + + /* ---------- + * If this column constraint is a FOREIGN KEY + * constraint, then we fill in the current attributes + * name and throw it into the list of FK constraints + * to be processed later. + * ---------- + */ + if (nodeTag(constraint) == T_FkConstraint) + { + Ident *id = makeNode(Ident); + id->name = column->colname; + id->indirection = NIL; + id->isRel = false; + + fkconstraint = (FkConstraint *)constraint; + fkconstraint->fk_attrs = lappend(NIL, id); + + fkconstraints = lappend(fkconstraints, constraint); + continue; + } + switch (constraint->contype) { case CONSTR_NULL: @@ -735,6 +764,15 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) } break; + case T_FkConstraint: + /* ---------- + * Table level FOREIGN KEY constraints are already complete. + * Just remember for later. + * ---------- + */ + fkconstraints = lappend(fkconstraints, element); + break; + default: elog(ERROR, "parser: unrecognized node (internal error)"); } @@ -888,9 +926,235 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) extras_before = blist; extras_after = ilist; + /* + * Now process the FOREIGN KEY constraints and add appropriate + * queries to the extras_after statements list. + * + */ + if (fkconstraints != NIL) + { + CreateTrigStmt *fk_trigger; + List *fk_attr; + List *pk_attr; + Ident *id; + + elog(NOTICE, "CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)"); + + foreach (fkclist, fkconstraints) + { + fkconstraint = (FkConstraint *)lfirst(fkclist); + + /* + * If the constraint has no name, set it to <unnamed> + * + */ + if (fkconstraint->constr_name == NULL) + fkconstraint->constr_name = "<unnamed>"; + + /* + * If the attribute list for the referenced table was + * omitted, lookup for the definition of the primary key + * + */ + if (fkconstraint->fk_attrs != NIL && fkconstraint->pk_attrs == NIL) + transformFkeyGetPrimaryKey(fkconstraint); + + /* + * Build a CREATE CONSTRAINT TRIGGER statement for the CHECK + * action. + * + */ + fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt); + fk_trigger->trigname = fkconstraint->constr_name; + fk_trigger->relname = stmt->relname; + fk_trigger->funcname = "RI_FKey_check_ins"; + fk_trigger->before = false; + fk_trigger->row = true; + fk_trigger->actions[0] = 'i'; + fk_trigger->actions[1] = 'u'; + fk_trigger->actions[2] = '\0'; + fk_trigger->lang = NULL; + fk_trigger->text = NULL; + fk_trigger->attr = NIL; + fk_trigger->when = NULL; + fk_trigger->isconstraint = true; + fk_trigger->deferrable = fkconstraint->deferrable; + fk_trigger->initdeferred = fkconstraint->initdeferred; + fk_trigger->constrrelname = fkconstraint->pktable_name; + + fk_trigger->args = NIL; + fk_trigger->args = lappend(fk_trigger->args, + fkconstraint->constr_name); + fk_trigger->args = lappend(fk_trigger->args, + stmt->relname); + fk_trigger->args = lappend(fk_trigger->args, + fkconstraint->pktable_name); + fk_trigger->args = lappend(fk_trigger->args, + fkconstraint->match_type); + fk_attr = fkconstraint->fk_attrs; + pk_attr = fkconstraint->pk_attrs; + if (length(fk_attr) != length(pk_attr)) + { + elog(NOTICE, "Illegal FOREIGN KEY definition REFERENCES \"%s\"", + fkconstraint->pktable_name); + elog(ERROR, "number of key attributes in referenced table must be equal to foreign key"); + } + while (fk_attr != NIL) + { + id = (Ident *)lfirst(fk_attr); + fk_trigger->args = lappend(fk_trigger->args, id->name); + + id = (Ident *)lfirst(pk_attr); + fk_trigger->args = lappend(fk_trigger->args, id->name); + + fk_attr = lnext(fk_attr); + pk_attr = lnext(pk_attr); + } + + extras_after = lappend(extras_after, (Node *)fk_trigger); + + if ((fkconstraint->actions & FKCONSTR_ON_DELETE_MASK) != 0) + { + /* + * Build a CREATE CONSTRAINT TRIGGER statement for the + * ON DELETE action fired on the PK table !!! + * + */ + fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt); + fk_trigger->trigname = fkconstraint->constr_name; + fk_trigger->relname = fkconstraint->pktable_name; + switch ((fkconstraint->actions & FKCONSTR_ON_DELETE_MASK) + >> FKCONSTR_ON_DELETE_SHIFT) + { + case FKCONSTR_ON_KEY_RESTRICT: + fk_trigger->funcname = "RI_FKey_restrict_del"; + break; + case FKCONSTR_ON_KEY_CASCADE: + fk_trigger->funcname = "RI_FKey_cascade_del"; + break; + case FKCONSTR_ON_KEY_SETNULL: + fk_trigger->funcname = "RI_FKey_setnull_del"; + break; + case FKCONSTR_ON_KEY_SETDEFAULT: + fk_trigger->funcname = "RI_FKey_setdefault_del"; + break; + default: + elog(ERROR, "Only one ON DELETE action can be specified for FOREIGN KEY constraint"); + break; + } + fk_trigger->before = false; + fk_trigger->row = true; + fk_trigger->actions[0] = 'd'; + fk_trigger->actions[1] = '\0'; + fk_trigger->lang = NULL; + fk_trigger->text = NULL; + fk_trigger->attr = NIL; + fk_trigger->when = NULL; + fk_trigger->isconstraint = true; + fk_trigger->deferrable = fkconstraint->deferrable; + fk_trigger->initdeferred = fkconstraint->initdeferred; + fk_trigger->constrrelname = stmt->relname; + + fk_trigger->args = NIL; + fk_trigger->args = lappend(fk_trigger->args, + fkconstraint->constr_name); + fk_trigger->args = lappend(fk_trigger->args, + stmt->relname); + fk_trigger->args = lappend(fk_trigger->args, + fkconstraint->pktable_name); + fk_trigger->args = lappend(fk_trigger->args, + fkconstraint->match_type); + fk_attr = fkconstraint->fk_attrs; + pk_attr = fkconstraint->pk_attrs; + while (fk_attr != NIL) + { + id = (Ident *)lfirst(fk_attr); + fk_trigger->args = lappend(fk_trigger->args, id->name); + + id = (Ident *)lfirst(pk_attr); + fk_trigger->args = lappend(fk_trigger->args, id->name); + + fk_attr = lnext(fk_attr); + pk_attr = lnext(pk_attr); + } + + extras_after = lappend(extras_after, (Node *)fk_trigger); + } + + if ((fkconstraint->actions & FKCONSTR_ON_UPDATE_MASK) != 0) + { + /* + * Build a CREATE CONSTRAINT TRIGGER statement for the + * ON UPDATE action fired on the PK table !!! + * + */ + fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt); + fk_trigger->trigname = fkconstraint->constr_name; + fk_trigger->relname = fkconstraint->pktable_name; + switch ((fkconstraint->actions & FKCONSTR_ON_UPDATE_MASK) + >> FKCONSTR_ON_UPDATE_SHIFT) + { + case FKCONSTR_ON_KEY_RESTRICT: + fk_trigger->funcname = "RI_FKey_restrict_upd"; + break; + case FKCONSTR_ON_KEY_CASCADE: + fk_trigger->funcname = "RI_FKey_cascade_upd"; + break; + case FKCONSTR_ON_KEY_SETNULL: + fk_trigger->funcname = "RI_FKey_setnull_upd"; + break; + case FKCONSTR_ON_KEY_SETDEFAULT: + fk_trigger->funcname = "RI_FKey_setdefault_upd"; + break; + default: + elog(ERROR, "Only one ON UPDATE action can be specified for FOREIGN KEY constraint"); + break; + } + fk_trigger->before = false; + fk_trigger->row = true; + fk_trigger->actions[0] = 'u'; + fk_trigger->actions[1] = '\0'; + fk_trigger->lang = NULL; + fk_trigger->text = NULL; + fk_trigger->attr = NIL; + fk_trigger->when = NULL; + fk_trigger->isconstraint = true; + fk_trigger->deferrable = fkconstraint->deferrable; + fk_trigger->initdeferred = fkconstraint->initdeferred; + fk_trigger->constrrelname = stmt->relname; + + fk_trigger->args = NIL; + fk_trigger->args = lappend(fk_trigger->args, + fkconstraint->constr_name); + fk_trigger->args = lappend(fk_trigger->args, + stmt->relname); + fk_trigger->args = lappend(fk_trigger->args, + fkconstraint->pktable_name); + fk_trigger->args = lappend(fk_trigger->args, + fkconstraint->match_type); + fk_attr = fkconstraint->fk_attrs; + pk_attr = fkconstraint->pk_attrs; + while (fk_attr != NIL) + { + id = (Ident *)lfirst(fk_attr); + fk_trigger->args = lappend(fk_trigger->args, id->name); + + id = (Ident *)lfirst(pk_attr); + fk_trigger->args = lappend(fk_trigger->args, id->name); + + fk_attr = lnext(fk_attr); + pk_attr = lnext(pk_attr); + } + + extras_after = lappend(extras_after, (Node *)fk_trigger); + } + } + } + return q; } /* transformCreateStmt() */ + /* * transformIndexStmt - * transforms the qualification of the index statement @@ -1338,3 +1602,99 @@ transformForUpdate(Query *qry, List *forUpdate) qry->rowMark = rowMark; return; } + + +/* + * transformFkeyGetPrimaryKey - + * + * Try to find the primary key attributes of a referenced table if + * the column list in the REFERENCES specification was omitted. + * + */ +static void +transformFkeyGetPrimaryKey(FkConstraint *fkconstraint) +{ + Relation pkrel; + Form_pg_attribute *pkrel_attrs; + Relation indexRd; + HeapScanDesc indexSd; + ScanKeyData key; + HeapTuple indexTup; + Form_pg_index indexStruct = NULL; + Ident *pkattr; + int pkattno; + int i; + + /* ---------- + * Open the referenced table and get the attributes list + * ---------- + */ + pkrel = heap_openr(fkconstraint->pktable_name, AccessShareLock); + if (pkrel == NULL) + elog(ERROR, "referenced table \"%s\" not found", + fkconstraint->pktable_name); + pkrel_attrs = pkrel->rd_att->attrs; + + /* ---------- + * Open pg_index and begin a scan for all indices defined on + * the referenced table + * ---------- + */ + indexRd = heap_openr(IndexRelationName, AccessShareLock); + ScanKeyEntryInitialize(&key, 0, Anum_pg_index_indrelid, + F_OIDEQ, + ObjectIdGetDatum(pkrel->rd_id)); + indexSd = heap_beginscan(indexRd, /* scan desc */ + false, /* scan backward flag */ + SnapshotNow, /* NOW snapshot */ + 1, /* number scan keys */ + &key); /* scan keys */ + + /* ---------- + * Fetch the index with indisprimary == true + * ---------- + */ + while (HeapTupleIsValid(indexTup = heap_getnext(indexSd, 0))) + { + indexStruct = (Form_pg_index) GETSTRUCT(indexTup); + + if (indexStruct->indisprimary) + { + break; + } + } + + /* ---------- + * Check that we found it + * ---------- + */ + if (!HeapTupleIsValid(indexTup)) + elog(ERROR, "PRIMARY KEY for referenced table \"%s\" not found", + fkconstraint->pktable_name); + + /* ---------- + * Now build the list of PK attributes from the indkey definition + * using the attribute names of the PK relation descriptor + * ---------- + */ + for (i = 0; i < 8 && indexStruct->indkey[i] != 0; i++) + { + pkattno = indexStruct->indkey[i]; + pkattr = (Ident *)makeNode(Ident); + pkattr->name = nameout(&(pkrel_attrs[pkattno - 1]->attname)); + pkattr->indirection = NIL; + pkattr->isRel = false; + + fkconstraint->pk_attrs = lappend(fkconstraint->pk_attrs, pkattr); + } + + /* ---------- + * End index scan and close relations + * ---------- + */ + heap_endscan(indexSd); + heap_close(indexRd, AccessShareLock); + heap_close(pkrel, AccessShareLock); +} + + diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c3d72896f65..ce16f9e9f3c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.116 1999/11/30 03:57:24 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.117 1999/12/06 18:02:43 wieck Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -143,7 +143,6 @@ static Node *doNegate(Node *n); %type <boolean> TriggerActionTime, TriggerForSpec, PLangTrusted -%type <ival> OptConstrTrigDeferrable, OptConstrTrigInitdeferred %type <str> OptConstrFromTable %type <str> TriggerEvents, TriggerFuncArg @@ -249,8 +248,10 @@ static Node *doNegate(Node *n); %type <node> TableConstraint %type <list> ColPrimaryKey, ColQualList, ColQualifier %type <node> ColConstraint, ColConstraintElem -%type <list> key_actions, key_action -%type <str> key_match, key_reference +%type <ival> key_actions, key_action, key_reference +%type <str> key_match +%type <ival> ConstraintAttributeSpec, ConstraintDeferrabilitySpec, + ConstraintTimeSpec %type <list> constraints_set_list %type <list> constraints_set_namelist @@ -976,9 +977,24 @@ ColPrimaryKey: PRIMARY KEY ColConstraint: CONSTRAINT name ColConstraintElem { - Constraint *n = (Constraint *)$3; - if (n != NULL) n->name = $2; - $$ = $3; + switch (nodeTag($3)) + { + case T_Constraint: + { + Constraint *n = (Constraint *)$3; + if (n != NULL) n->name = $2; + } + break; + case T_FkConstraint: + { + FkConstraint *n = (FkConstraint *)$3; + if (n != NULL) n->constr_name = $2; + } + break; + default: + break; + } + $$ = $3; } | ColConstraintElem { $$ = $1; } @@ -1060,10 +1076,25 @@ ColConstraintElem: CHECK '(' a_expr ')' n->keys = NULL; $$ = (Node *)n; } - | REFERENCES ColId opt_column_list key_match key_actions + | REFERENCES ColId opt_column_list key_match key_actions { - elog(NOTICE,"CREATE TABLE/FOREIGN KEY clause ignored; not yet implemented"); - $$ = NULL; + /* XXX + * Need ConstraintAttributeSpec as $6 -- Jan + */ + FkConstraint *n = makeNode(FkConstraint); + n->constr_name = NULL; + n->pktable_name = $2; + n->fk_attrs = NIL; + n->pk_attrs = $3; + n->match_type = $4; + n->actions = $5; + n->deferrable = true; + n->initdeferred = false; + /* + n->deferrable = ($6 & 1) != 0; + n->initdeferred = ($6 & 2) != 0; + */ + $$ = (Node *)n; } ; @@ -1073,9 +1104,24 @@ ColConstraintElem: CHECK '(' a_expr ')' */ TableConstraint: CONSTRAINT name ConstraintElem { - Constraint *n = (Constraint *)$3; - if (n != NULL) n->name = $2; - $$ = $3; + switch (nodeTag($3)) + { + case T_Constraint: + { + Constraint *n = (Constraint *)$3; + if (n != NULL) n->name = $2; + } + break; + case T_FkConstraint: + { + FkConstraint *n = (FkConstraint *)$3; + if (n != NULL) n->constr_name = $2; + } + break; + default: + break; + } + $$ = $3; } | ConstraintElem { $$ = $1; } @@ -1110,31 +1156,51 @@ ConstraintElem: CHECK '(' a_expr ')' n->keys = $4; $$ = (Node *)n; } - | FOREIGN KEY '(' columnList ')' REFERENCES ColId opt_column_list key_match key_actions - { - elog(NOTICE,"CREATE TABLE/FOREIGN KEY clause ignored; not yet implemented"); - $$ = NULL; + | FOREIGN KEY '(' columnList ')' REFERENCES ColId opt_column_list key_match key_actions ConstraintAttributeSpec + { + FkConstraint *n = makeNode(FkConstraint); + n->constr_name = NULL; + n->pktable_name = $7; + n->fk_attrs = $4; + n->pk_attrs = $8; + n->match_type = $9; + n->actions = $10; + n->deferrable = ($11 & 1) != 0; + n->initdeferred = ($11 & 2) != 0; + $$ = (Node *)n; } ; -key_match: MATCH FULL { $$ = NULL; } - | MATCH PARTIAL { $$ = NULL; } - | /*EMPTY*/ { $$ = NULL; } +key_match: MATCH FULL + { + $$ = "FULL"; + } + | MATCH PARTIAL + { + elog(ERROR, "FOREIGN KEY match type PARTIAL not implemented yet"); + $$ = "PARTIAL"; + } + | /*EMPTY*/ + { + elog(ERROR, "FOREIGN KEY match type UNSPECIFIED not implemented yet"); + $$ = "UNSPECIFIED"; + } ; -key_actions: key_action key_action { $$ = NIL; } - | key_action { $$ = NIL; } - | /*EMPTY*/ { $$ = NIL; } +key_actions: key_action key_action { $$ = $1 | $2; } + | key_action { $$ = $1; } + | /*EMPTY*/ { $$ = 0; } ; -key_action: ON DELETE key_reference { $$ = NIL; } - | ON UPDATE key_reference { $$ = NIL; } +key_action: ON DELETE key_reference { $$ = $3 << FKCONSTR_ON_DELETE_SHIFT; } + | ON UPDATE key_reference { $$ = $3 << FKCONSTR_ON_UPDATE_SHIFT; } ; -key_reference: NO ACTION { $$ = NULL; } - | CASCADE { $$ = NULL; } - | SET DEFAULT { $$ = NULL; } - | SET NULL_P { $$ = NULL; } +key_reference: NO ACTION { $$ = FKCONSTR_ON_KEY_NOACTION; } + | RESTRICT { $$ = FKCONSTR_ON_KEY_RESTRICT; } + | CASCADE { $$ = FKCONSTR_ON_KEY_CASCADE; } + | SET NULL_P { $$ = FKCONSTR_ON_KEY_SETNULL; } + | SET DEFAULT { $$ = FKCONSTR_ON_KEY_SETDEFAULT; } ; OptInherit: INHERITS '(' relation_name_list ')' { $$ = $3; } @@ -1329,14 +1395,14 @@ CreateTrigStmt: CREATE TRIGGER name TriggerActionTime TriggerEvents ON } | CREATE CONSTRAINT TRIGGER name AFTER TriggerOneEvent ON relation_name OptConstrFromTable - OptConstrTrigDeferrable OptConstrTrigInitdeferred + ConstraintAttributeSpec FOR EACH ROW EXECUTE PROCEDURE name '(' TriggerFuncArgs ')' { CreateTrigStmt *n = makeNode(CreateTrigStmt); n->trigname = $4; n->relname = $8; - n->funcname = $17; - n->args = $19; + n->funcname = $16; + n->args = $18; n->before = false; n->row = true; n->actions[0] = $6; @@ -1346,22 +1412,9 @@ CreateTrigStmt: CREATE TRIGGER name TriggerActionTime TriggerEvents ON n->attr = NULL; /* unused */ n->when = NULL; /* unused */ - /* - * Check that the DEFERRABLE and INITIALLY combination - * makes sense - */ n->isconstraint = true; - if ($11 == 1) - { - if ($10 == 0) - elog(ERROR, "INITIALLY DEFERRED constraint " - "cannot be NOT DEFERRABLE"); - n->deferrable = true; - n->initdeferred = true; - } else { - n->deferrable = ($10 == 1); - n->initdeferred = false; - } + n->deferrable = ($10 & 1) != 0; + n->initdeferred = ($10 & 2) != 0; n->constrrelname = $9; $$ = (Node *)n; @@ -1443,33 +1496,43 @@ OptConstrFromTable: /* Empty */ } ; -OptConstrTrigDeferrable: /* Empty */ - { - $$ = -1; - } - | DEFERRABLE - { - $$ = 1; - } - | NOT DEFERRABLE - { +ConstraintAttributeSpec: /* Empty */ + { $$ = 0; } + | ConstraintDeferrabilitySpec + { $$ = $1; } + | ConstraintDeferrabilitySpec ConstraintTimeSpec + { + if ($1 == 0 && $2 != 0) + elog(ERROR, "INITIALLY DEFERRED constraint must be DEFERRABLE"); + $$ = $1 | $2; + } + | ConstraintTimeSpec + { + if ($1 != 0) + $$ = 3; + else $$ = 0; - } + } + | ConstraintTimeSpec ConstraintDeferrabilitySpec + { + if ($2 == 0 && $1 != 0) + elog(ERROR, "INITIALLY DEFERRED constraint must be DEFERRABLE"); + $$ = $1 | $2; + } ; -OptConstrTrigInitdeferred: /* Empty */ - { - $$ = -1; - } +ConstraintDeferrabilitySpec: NOT DEFERRABLE + { $$ = 0; } + | DEFERRABLE + { $$ = 1; } + ; + +ConstraintTimeSpec: INITIALLY IMMEDIATE + { $$ = 0; } | INITIALLY DEFERRED - { - $$ = 1; - } - | INITIALLY IMMEDIATE - { - $$ = 0; - } + { $$ = 2; } ; + DropTrigStmt: DROP TRIGGER name ON relation_name { diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index e3b6030541b..6b07cc38245 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -6,11 +6,25 @@ * * 1999 Jan Wieck * - * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.3 1999/11/22 17:56:29 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.4 1999/12/06 18:02:44 wieck Exp $ * * ---------- */ + +/* ---------- + * Internal TODO: + * + * Finish functions for MATCH FULL: + * setnull_del + * setnull_upd + * setdefault_del + * setdefault_upd + * + * Add MATCH PARTIAL logic + * ---------- + */ + #include "postgres.h" #include "fmgr.h" @@ -52,8 +66,12 @@ #define RI_KEYS_NONE_NULL 2 -#define RI_PLAN_TYPE_CHECK_FULL 0 -#define RI_PLAN_TYPE_CASCADE_DEL_FULL 1 +#define RI_PLAN_CHECK_LOOKUPPK_NOCOLS 1 +#define RI_PLAN_CHECK_LOOKUPPK 2 +#define RI_PLAN_CASCADE_DEL_DODELETE 1 +#define RI_PLAN_CASCADE_UPD_DOUPDATE 1 +#define RI_PLAN_RESTRICT_DEL_CHECKREF 1 +#define RI_PLAN_RESTRICT_UPD_CHECKREF 1 /* ---------- @@ -195,7 +213,8 @@ RI_FKey_check (FmgrInfo *proinfo) * ---------- */ if (tgnargs == 4) { - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, 1, + ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, + RI_PLAN_CHECK_LOOKUPPK_NOCOLS, fk_rel, pk_rel, tgnargs, tgargs); @@ -273,9 +292,10 @@ RI_FKey_check (FmgrInfo *proinfo) * ---------- */ case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, 2, - fk_rel, pk_rel, - tgnargs, tgargs); + ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, + RI_PLAN_CHECK_LOOKUPPK, + fk_rel, pk_rel, + tgnargs, tgargs); switch (ri_NullCheck(fk_rel, new_row, &qkey, RI_KEYPAIR_FK_IDX)) { @@ -382,7 +402,7 @@ RI_FKey_check (FmgrInfo *proinfo) else check_nulls[i] = ' '; } - check_nulls[RI_MAX_NUMKEYS] = '\0'; + check_nulls[i] = '\0'; /* ---------- * Now check that foreign key exists in PK table @@ -515,9 +535,10 @@ RI_FKey_cascade_del (FmgrInfo *proinfo) */ case RI_MATCH_TYPE_UNSPECIFIED: case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, 1, - fk_rel, pk_rel, - tgnargs, tgargs); + ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, + RI_PLAN_CASCADE_DEL_DODELETE, + fk_rel, pk_rel, + tgnargs, tgargs); switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) { @@ -601,13 +622,13 @@ RI_FKey_cascade_del (FmgrInfo *proinfo) else del_nulls[i] = ' '; } - del_nulls[RI_MAX_NUMKEYS] = '\0'; + del_nulls[i] = '\0'; /* ---------- * Now delete constraint * ---------- */ - if (SPI_execp(qplan, del_values, del_nulls, 1) != SPI_OK_DELETE) + if (SPI_execp(qplan, del_values, del_nulls, 0) != SPI_OK_DELETE) elog(ERROR, "SPI_execp() failed in RI_FKey_cascade_del()"); if (SPI_finish() != SPI_OK_FINISH) @@ -642,12 +663,220 @@ RI_FKey_cascade_del (FmgrInfo *proinfo) HeapTuple RI_FKey_cascade_upd (FmgrInfo *proinfo) { - TriggerData *trigdata; + TriggerData *trigdata; + int tgnargs; + char **tgargs; + Relation fk_rel; + Relation pk_rel; + HeapTuple new_row; + HeapTuple old_row; + RI_QueryKey qkey; + void *qplan; + Datum upd_values[RI_MAX_NUMKEYS * 2]; + char upd_nulls[RI_MAX_NUMKEYS * 2 + 1]; + bool isnull; + int i; + int j; trigdata = CurrentTriggerData; CurrentTriggerData = NULL; - elog(NOTICE, "RI_FKey_cascade_upd() called\n"); + /* ---------- + * Check that this is a valid trigger call on the right time and event. + * ---------- + */ + if (trigdata == NULL) + elog(ERROR, "RI_FKey_cascade_upd() not fired by trigger manager"); + if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || + !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + elog(ERROR, "RI_FKey_cascade_upd() must be fired AFTER ROW"); + if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + elog(ERROR, "RI_FKey_cascade_upd() must be fired for UPDATE"); + + /* ---------- + * Check for the correct # of call arguments + * ---------- + */ + tgnargs = trigdata->tg_trigger->tgnargs; + tgargs = trigdata->tg_trigger->tgargs; + if (tgnargs < 4 || (tgnargs % 2) != 0) + elog(ERROR, "wrong # of arguments in call to RI_FKey_cascade_upd()"); + if (tgnargs > RI_MAX_ARGUMENTS) + elog(ERROR, "too many keys (%d max) in call to RI_FKey_cascade_upd()", + RI_MAX_NUMKEYS); + + /* ---------- + * Nothing to do if no column names to compare given + * ---------- + */ + if (tgnargs == 4) + return NULL; + + /* ---------- + * Get the relation descriptors of the FK and PK tables and + * the old tuple. + * ---------- + */ + fk_rel = heap_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock); + pk_rel = trigdata->tg_relation; + new_row = trigdata->tg_newtuple; + old_row = trigdata->tg_trigtuple; + + switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + { + /* ---------- + * SQL3 11.9 <referential constraint definition> + * Gereral rules 7) a) i): + * MATCH <unspecified> or MATCH FULL + * ... ON UPDATE CASCADE + * ---------- + */ + case RI_MATCH_TYPE_UNSPECIFIED: + case RI_MATCH_TYPE_FULL: + ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, + RI_PLAN_CASCADE_UPD_DOUPDATE, + fk_rel, pk_rel, + tgnargs, tgargs); + + switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) + { + case RI_KEYS_ALL_NULL: + case RI_KEYS_SOME_NULL: + /* ---------- + * No update - MATCH FULL means there cannot be any + * reference to old key if it contains NULL + * ---------- + */ + heap_close(fk_rel, NoLock); + return NULL; + + case RI_KEYS_NONE_NULL: + /* ---------- + * Have a full qualified key - continue below + * ---------- + */ + break; + } + heap_close(fk_rel, NoLock); + + /* ---------- + * No need to do anything if old and new keys are equal + * ---------- + */ + if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey, + RI_KEYPAIR_PK_IDX)) + return NULL; + + if (SPI_connect() != SPI_OK_CONNECT) + elog(NOTICE, "SPI_connect() failed in RI_FKey_restrict_upd()"); + + /* ---------- + * Fetch or prepare a saved plan for the restrict delete + * lookup for foreign references + * ---------- + */ + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) + { + char buf[256]; + char querystr[8192]; + char qualstr[8192]; + char *querysep; + char *qualsep; + Oid queryoids[RI_MAX_NUMKEYS * 2]; + + /* ---------- + * The query string built is + * UPDATE <fktable> SET fkatt1 = $1 [, ...] + * WHERE fkatt1 = $n [AND ...] + * The type id's for the $ parameters are those of the + * corresponding PK attributes. Thus, SPI_prepare could + * eventually fail if the parser cannot identify some way + * how to compare these two types by '='. + * ---------- + */ + sprintf(querystr, "UPDATE \"%s\" SET", + tgargs[RI_FK_RELNAME_ARGNO]); + qualstr[0] = '\0'; + querysep = ""; + qualsep = "WHERE"; + for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++) + { + sprintf(buf, "%s \"%s\" = $%d", querysep, + tgargs[4 + i * 2], i + 1); + strcat(querystr, buf); + sprintf(buf, " %s \"%s\" = $%d", qualsep, + tgargs[4 + i * 2], j + 1); + strcat(qualstr, buf); + querysep = ","; + qualsep = "AND"; + queryoids[i] = SPI_gettypeid(pk_rel->rd_att, + qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + queryoids[j] = queryoids[i]; + } + strcat(querystr, qualstr); + + /* ---------- + * Prepare, save and remember the new plan. + * ---------- + */ + qplan = SPI_prepare(querystr, qkey.nkeypairs * 2, queryoids); + qplan = SPI_saveplan(qplan); + ri_HashPreparedPlan(&qkey, qplan); + } + + /* ---------- + * We have a plan now. Build up the arguments for SPI_execp() + * from the key values in the updated PK tuple. + * ---------- + */ + for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++) + { + upd_values[i] = SPI_getbinval(new_row, + pk_rel->rd_att, + qkey.keypair[i][RI_KEYPAIR_PK_IDX], + &isnull); + if (isnull) + upd_nulls[i] = 'n'; + else + upd_nulls[i] = ' '; + + upd_values[j] = SPI_getbinval(old_row, + pk_rel->rd_att, + qkey.keypair[i][RI_KEYPAIR_PK_IDX], + &isnull); + if (isnull) + upd_nulls[j] = 'n'; + else + upd_nulls[j] = ' '; + } + upd_nulls[j] = '\0'; + + /* ---------- + * Now update the existing references + * ---------- + */ + if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE) + elog(ERROR, "SPI_execp() failed in RI_FKey_cascade_upd()"); + + if (SPI_finish() != SPI_OK_FINISH) + elog(NOTICE, "SPI_finish() failed in RI_FKey_cascade_upd()"); + + return NULL; + + /* ---------- + * Handle MATCH PARTIAL restrict update. + * ---------- + */ + case RI_MATCH_TYPE_PARTIAL: + elog(ERROR, "MATCH PARTIAL not yet supported"); + return NULL; + } + + /* ---------- + * Never reached + * ---------- + */ + elog(ERROR, "internal error #4 in ri_triggers.c"); return NULL; } @@ -661,12 +890,196 @@ RI_FKey_cascade_upd (FmgrInfo *proinfo) HeapTuple RI_FKey_restrict_del (FmgrInfo *proinfo) { - TriggerData *trigdata; + TriggerData *trigdata; + int tgnargs; + char **tgargs; + Relation fk_rel; + Relation pk_rel; + HeapTuple old_row; + RI_QueryKey qkey; + void *qplan; + Datum del_values[RI_MAX_NUMKEYS]; + char del_nulls[RI_MAX_NUMKEYS + 1]; + bool isnull; + int i; trigdata = CurrentTriggerData; CurrentTriggerData = NULL; - elog(NOTICE, "RI_FKey_restrict_del() called\n"); + /* ---------- + * Check that this is a valid trigger call on the right time and event. + * ---------- + */ + if (trigdata == NULL) + elog(ERROR, "RI_FKey_restrict_del() not fired by trigger manager"); + if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || + !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + elog(ERROR, "RI_FKey_restrict_del() must be fired AFTER ROW"); + if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) + elog(ERROR, "RI_FKey_restrict_del() must be fired for DELETE"); + + /* ---------- + * Check for the correct # of call arguments + * ---------- + */ + tgnargs = trigdata->tg_trigger->tgnargs; + tgargs = trigdata->tg_trigger->tgargs; + if (tgnargs < 4 || (tgnargs % 2) != 0) + elog(ERROR, "wrong # of arguments in call to RI_FKey_restrict_del()"); + if (tgnargs > RI_MAX_ARGUMENTS) + elog(ERROR, "too many keys (%d max) in call to RI_FKey_restrict_del()", + RI_MAX_NUMKEYS); + + /* ---------- + * Nothing to do if no column names to compare given + * ---------- + */ + if (tgnargs == 4) + return NULL; + + /* ---------- + * Get the relation descriptors of the FK and PK tables and + * the old tuple. + * ---------- + */ + fk_rel = heap_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock); + pk_rel = trigdata->tg_relation; + old_row = trigdata->tg_trigtuple; + + switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + { + /* ---------- + * SQL3 11.9 <referential constraint definition> + * Gereral rules 6) a) iv): + * MATCH <unspecified> or MATCH FULL + * ... ON DELETE CASCADE + * ---------- + */ + case RI_MATCH_TYPE_UNSPECIFIED: + case RI_MATCH_TYPE_FULL: + ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, + RI_PLAN_RESTRICT_DEL_CHECKREF, + fk_rel, pk_rel, + tgnargs, tgargs); + + switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) + { + case RI_KEYS_ALL_NULL: + case RI_KEYS_SOME_NULL: + /* ---------- + * No check - MATCH FULL means there cannot be any + * reference to old key if it contains NULL + * ---------- + */ + heap_close(fk_rel, NoLock); + return NULL; + + case RI_KEYS_NONE_NULL: + /* ---------- + * Have a full qualified key - continue below + * ---------- + */ + break; + } + heap_close(fk_rel, NoLock); + + if (SPI_connect() != SPI_OK_CONNECT) + elog(NOTICE, "SPI_connect() failed in RI_FKey_restrict_del()"); + + /* ---------- + * Fetch or prepare a saved plan for the restrict delete + * lookup for foreign references + * ---------- + */ + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) + { + char buf[256]; + char querystr[8192]; + char *querysep; + Oid queryoids[RI_MAX_NUMKEYS]; + + /* ---------- + * The query string built is + * SELECT oid FROM <fktable> WHERE fkatt1 = $1 [AND ...] + * The type id's for the $ parameters are those of the + * corresponding PK attributes. Thus, SPI_prepare could + * eventually fail if the parser cannot identify some way + * how to compare these two types by '='. + * ---------- + */ + sprintf(querystr, "SELECT oid FROM \"%s\"", + tgargs[RI_FK_RELNAME_ARGNO]); + querysep = "WHERE"; + for (i = 0; i < qkey.nkeypairs; i++) + { + sprintf(buf, " %s \"%s\" = $%d", querysep, + tgargs[4 + i * 2], i + 1); + strcat(querystr, buf); + querysep = "AND"; + queryoids[i] = SPI_gettypeid(pk_rel->rd_att, + qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + } + + /* ---------- + * Prepare, save and remember the new plan. + * ---------- + */ + qplan = SPI_prepare(querystr, qkey.nkeypairs, queryoids); + qplan = SPI_saveplan(qplan); + ri_HashPreparedPlan(&qkey, qplan); + } + + /* ---------- + * We have a plan now. Build up the arguments for SPI_execp() + * from the key values in the deleted PK tuple. + * ---------- + */ + for (i = 0; i < qkey.nkeypairs; i++) + { + del_values[i] = SPI_getbinval(old_row, + pk_rel->rd_att, + qkey.keypair[i][RI_KEYPAIR_PK_IDX], + &isnull); + if (isnull) + del_nulls[i] = 'n'; + else + del_nulls[i] = ' '; + } + del_nulls[i] = '\0'; + + /* ---------- + * Now check for existing references + * ---------- + */ + if (SPI_execp(qplan, del_values, del_nulls, 1) != SPI_OK_SELECT) + elog(ERROR, "SPI_execp() failed in RI_FKey_restrict_del()"); + + if (SPI_processed > 0) + elog(ERROR, "%s referential integrity violation - " + "key in %s still referenced from %s", + tgargs[RI_CONSTRAINT_NAME_ARGNO], + tgargs[RI_PK_RELNAME_ARGNO], + tgargs[RI_FK_RELNAME_ARGNO]); + + if (SPI_finish() != SPI_OK_FINISH) + elog(NOTICE, "SPI_finish() failed in RI_FKey_restrict_del()"); + + return NULL; + + /* ---------- + * Handle MATCH PARTIAL restrict delete. + * ---------- + */ + case RI_MATCH_TYPE_PARTIAL: + elog(ERROR, "MATCH PARTIAL not yet supported"); + return NULL; + } + + /* ---------- + * Never reached + * ---------- + */ + elog(ERROR, "internal error #3 in ri_triggers.c"); return NULL; } @@ -680,12 +1093,206 @@ RI_FKey_restrict_del (FmgrInfo *proinfo) HeapTuple RI_FKey_restrict_upd (FmgrInfo *proinfo) { - TriggerData *trigdata; + TriggerData *trigdata; + int tgnargs; + char **tgargs; + Relation fk_rel; + Relation pk_rel; + HeapTuple new_row; + HeapTuple old_row; + RI_QueryKey qkey; + void *qplan; + Datum upd_values[RI_MAX_NUMKEYS]; + char upd_nulls[RI_MAX_NUMKEYS + 1]; + bool isnull; + int i; trigdata = CurrentTriggerData; CurrentTriggerData = NULL; - elog(NOTICE, "RI_FKey_restrict_upd() called\n"); + /* ---------- + * Check that this is a valid trigger call on the right time and event. + * ---------- + */ + if (trigdata == NULL) + elog(ERROR, "RI_FKey_restrict_upd() not fired by trigger manager"); + if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || + !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + elog(ERROR, "RI_FKey_restrict_upd() must be fired AFTER ROW"); + if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + elog(ERROR, "RI_FKey_restrict_upd() must be fired for UPDATE"); + + /* ---------- + * Check for the correct # of call arguments + * ---------- + */ + tgnargs = trigdata->tg_trigger->tgnargs; + tgargs = trigdata->tg_trigger->tgargs; + if (tgnargs < 4 || (tgnargs % 2) != 0) + elog(ERROR, "wrong # of arguments in call to RI_FKey_restrict_upd()"); + if (tgnargs > RI_MAX_ARGUMENTS) + elog(ERROR, "too many keys (%d max) in call to RI_FKey_restrict_upd()", + RI_MAX_NUMKEYS); + + /* ---------- + * Nothing to do if no column names to compare given + * ---------- + */ + if (tgnargs == 4) + return NULL; + + /* ---------- + * Get the relation descriptors of the FK and PK tables and + * the old tuple. + * ---------- + */ + fk_rel = heap_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock); + pk_rel = trigdata->tg_relation; + new_row = trigdata->tg_newtuple; + old_row = trigdata->tg_trigtuple; + + switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) + { + /* ---------- + * SQL3 11.9 <referential constraint definition> + * Gereral rules 6) a) iv): + * MATCH <unspecified> or MATCH FULL + * ... ON DELETE CASCADE + * ---------- + */ + case RI_MATCH_TYPE_UNSPECIFIED: + case RI_MATCH_TYPE_FULL: + ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, + RI_PLAN_RESTRICT_UPD_CHECKREF, + fk_rel, pk_rel, + tgnargs, tgargs); + + switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX)) + { + case RI_KEYS_ALL_NULL: + case RI_KEYS_SOME_NULL: + /* ---------- + * No check - MATCH FULL means there cannot be any + * reference to old key if it contains NULL + * ---------- + */ + heap_close(fk_rel, NoLock); + return NULL; + + case RI_KEYS_NONE_NULL: + /* ---------- + * Have a full qualified key - continue below + * ---------- + */ + break; + } + heap_close(fk_rel, NoLock); + + /* ---------- + * No need to check anything if old and new keys are equal + * ---------- + */ + if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey, + RI_KEYPAIR_PK_IDX)) + return NULL; + + if (SPI_connect() != SPI_OK_CONNECT) + elog(NOTICE, "SPI_connect() failed in RI_FKey_restrict_upd()"); + + /* ---------- + * Fetch or prepare a saved plan for the restrict delete + * lookup for foreign references + * ---------- + */ + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) + { + char buf[256]; + char querystr[8192]; + char *querysep; + Oid queryoids[RI_MAX_NUMKEYS]; + + /* ---------- + * The query string built is + * SELECT oid FROM <fktable> WHERE fkatt1 = $1 [AND ...] + * The type id's for the $ parameters are those of the + * corresponding PK attributes. Thus, SPI_prepare could + * eventually fail if the parser cannot identify some way + * how to compare these two types by '='. + * ---------- + */ + sprintf(querystr, "SELECT oid FROM \"%s\"", + tgargs[RI_FK_RELNAME_ARGNO]); + querysep = "WHERE"; + for (i = 0; i < qkey.nkeypairs; i++) + { + sprintf(buf, " %s \"%s\" = $%d", querysep, + tgargs[4 + i * 2], i + 1); + strcat(querystr, buf); + querysep = "AND"; + queryoids[i] = SPI_gettypeid(pk_rel->rd_att, + qkey.keypair[i][RI_KEYPAIR_PK_IDX]); + } + + /* ---------- + * Prepare, save and remember the new plan. + * ---------- + */ + qplan = SPI_prepare(querystr, qkey.nkeypairs, queryoids); + qplan = SPI_saveplan(qplan); + ri_HashPreparedPlan(&qkey, qplan); + } + + /* ---------- + * We have a plan now. Build up the arguments for SPI_execp() + * from the key values in the updated PK tuple. + * ---------- + */ + for (i = 0; i < qkey.nkeypairs; i++) + { + upd_values[i] = SPI_getbinval(old_row, + pk_rel->rd_att, + qkey.keypair[i][RI_KEYPAIR_PK_IDX], + &isnull); + if (isnull) + upd_nulls[i] = 'n'; + else + upd_nulls[i] = ' '; + } + upd_nulls[i] = '\0'; + + /* ---------- + * Now check for existing references + * ---------- + */ + if (SPI_execp(qplan, upd_values, upd_nulls, 1) != SPI_OK_SELECT) + elog(ERROR, "SPI_execp() failed in RI_FKey_restrict_upd()"); + + if (SPI_processed > 0) + elog(ERROR, "%s referential integrity violation - " + "key in %s still referenced from %s", + tgargs[RI_CONSTRAINT_NAME_ARGNO], + tgargs[RI_PK_RELNAME_ARGNO], + tgargs[RI_FK_RELNAME_ARGNO]); + + if (SPI_finish() != SPI_OK_FINISH) + elog(NOTICE, "SPI_finish() failed in RI_FKey_restrict_upd()"); + + return NULL; + + /* ---------- + * Handle MATCH PARTIAL restrict update. + * ---------- + */ + case RI_MATCH_TYPE_PARTIAL: + elog(ERROR, "MATCH PARTIAL not yet supported"); + return NULL; + } + + /* ---------- + * Never reached + * ---------- + */ + elog(ERROR, "internal error #4 in ri_triggers.c"); return NULL; } @@ -704,7 +1311,7 @@ RI_FKey_setnull_del (FmgrInfo *proinfo) trigdata = CurrentTriggerData; CurrentTriggerData = NULL; - elog(NOTICE, "RI_FKey_setnull_del() called\n"); + elog(ERROR, "RI_FKey_setnull_del() called\n"); return NULL; } @@ -723,7 +1330,7 @@ RI_FKey_setnull_upd (FmgrInfo *proinfo) trigdata = CurrentTriggerData; CurrentTriggerData = NULL; - elog(NOTICE, "RI_FKey_setnull_upd() called\n"); + elog(ERROR, "RI_FKey_setnull_upd() called\n"); return NULL; } @@ -742,7 +1349,7 @@ RI_FKey_setdefault_del (FmgrInfo *proinfo) trigdata = CurrentTriggerData; CurrentTriggerData = NULL; - elog(NOTICE, "RI_FKey_setdefault_del() called\n"); + elog(ERROR, "RI_FKey_setdefault_del() called\n"); return NULL; } @@ -761,7 +1368,7 @@ RI_FKey_setdefault_upd (FmgrInfo *proinfo) trigdata = CurrentTriggerData; CurrentTriggerData = NULL; - elog(NOTICE, "RI_FKey_setdefault_upd() called\n"); + elog(ERROR, "RI_FKey_setdefault_upd() called\n"); return NULL; } diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 4e830d7527c..ffb7ca848fb 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: nodes.h,v 1.56 1999/11/23 20:07:02 momjian Exp $ + * $Id: nodes.h,v 1.57 1999/12/06 18:02:46 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -215,7 +215,8 @@ typedef enum NodeTag T_JoinExpr, T_CaseExpr, T_CaseWhen, - T_RowMark + T_RowMark, + T_FkConstraint } NodeTag; /* diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 04c8c9b5182..542760a2357 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.87 1999/11/30 03:57:29 momjian Exp $ + * $Id: parsenodes.h,v 1.88 1999/12/06 18:02:47 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -172,6 +172,37 @@ typedef struct Constraint List *keys; /* list of primary keys */ } Constraint; + +/* ---------- + * Definitions for FOREIGN KEY constraints in CreateStmt + * ---------- + */ +#define FKCONSTR_ON_KEY_NOACTION 0x0000 +#define FKCONSTR_ON_KEY_RESTRICT 0x0001 +#define FKCONSTR_ON_KEY_CASCADE 0x0002 +#define FKCONSTR_ON_KEY_SETNULL 0x0004 +#define FKCONSTR_ON_KEY_SETDEFAULT 0x0008 + +#define FKCONSTR_ON_DELETE_MASK 0x000F +#define FKCONSTR_ON_DELETE_SHIFT 0 + +#define FKCONSTR_ON_UPDATE_MASK 0x00F0 +#define FKCONSTR_ON_UPDATE_SHIFT 4 + +typedef struct FkConstraint +{ + NodeTag type; + char *constr_name; /* Constraint name */ + char *pktable_name; /* Primary key table name */ + List *fk_attrs; /* Attributes of foreign key */ + List *pk_attrs; /* Corresponding attrs in PK table */ + char *match_type; /* FULL or PARTIAL */ + int32 actions; /* ON DELETE/UPDATE actions */ + bool deferrable; /* DEFERRABLE */ + bool initdeferred; /* INITIALLY DEFERRED */ +} FkConstraint; + + /* ---------------------- * Create/Drop TRIGGER Statements * ---------------------- -- GitLab