aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/trigger.c2
-rw-r--r--src/backend/parser/analyze.c398
-rw-r--r--src/backend/parser/gram.y201
-rw-r--r--src/backend/utils/adt/ri_triggers.c653
-rw-r--r--src/include/nodes/nodes.h5
-rw-r--r--src/include/nodes/parsenodes.h33
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
* ----------------------