mirror of https://github.com/postgres/postgres
Completed FOREIGN KEY syntax.
Added functionality for automatic trigger creation during CREATE TABLE. Added ON DELETE RESTRICT and some others. Jan
This commit is contained in:
parent
1d8ce77233
commit
b8ef7e7f82
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
| FOREIGN KEY '(' columnList ')' REFERENCES ColId opt_column_list key_match key_actions ConstraintAttributeSpec
|
||||
{
|
||||
elog(NOTICE,"CREATE TABLE/FOREIGN KEY clause ignored; not yet implemented");
|
||||
$$ = NULL;
|
||||
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,34 +1496,44 @@ 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;
|
||||
}
|
||||
| INITIALLY DEFERRED
|
||||
{
|
||||
$$ = 1;
|
||||
}
|
||||
| INITIALLY IMMEDIATE
|
||||
{
|
||||
$$ = 0;
|
||||
}
|
||||
ConstraintDeferrabilitySpec: NOT DEFERRABLE
|
||||
{ $$ = 0; }
|
||||
| DEFERRABLE
|
||||
{ $$ = 1; }
|
||||
;
|
||||
|
||||
ConstraintTimeSpec: INITIALLY IMMEDIATE
|
||||
{ $$ = 0; }
|
||||
| INITIALLY DEFERRED
|
||||
{ $$ = 2; }
|
||||
;
|
||||
|
||||
|
||||
DropTrigStmt: DROP TRIGGER name ON relation_name
|
||||
{
|
||||
DropTrigStmt *n = makeNode(DropTrigStmt);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
* ----------------------
|
||||
|
|
Loading…
Reference in New Issue