1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-10 17:42:29 +03:00

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:
Jan Wieck
1999-12-06 18:02:47 +00:00
parent 1d8ce77233
commit b8ef7e7f82
6 changed files with 1178 additions and 114 deletions

View File

@@ -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);
}

View File

@@ -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);