diff options
Diffstat (limited to 'src/backend/parser/analyze.c')
-rw-r--r-- | src/backend/parser/analyze.c | 1844 |
1 files changed, 902 insertions, 942 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index d4af6f68619..94e5e1602eb 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.200 2001/10/03 20:54:20 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.201 2001/10/12 00:07:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -36,11 +36,33 @@ #include "utils/numeric.h" #include "utils/relcache.h" #include "utils/syscache.h" +#include "utils/temprel.h" #ifdef MULTIBYTE #include "mb/pg_wchar.h" #endif + +/* State shared by transformCreateStmt and its subroutines */ +typedef struct +{ + const char *stmtType; /* "CREATE TABLE" or "ALTER TABLE" */ + char *relname; /* name of relation */ + List *inhRelnames; /* names of relations to inherit from */ + bool istemp; /* is it to be a temp relation? */ + Oid relOid; /* OID of table, if ALTER TABLE case */ + List *columns; /* ColumnDef items */ + List *ckconstraints; /* CHECK constraints */ + List *fkconstraints; /* FOREIGN KEY constraints */ + List *ixconstraints; /* index-creating constraints */ + List *blist; /* "before list" of things to do before + * creating the table */ + List *alist; /* "after list" of things to do after + * creating the table */ + IndexStmt *pkey; /* PRIMARY KEY index, if any */ +} CreateStmtContext; + + static Query *transformStmt(ParseState *pstate, Node *stmt); static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt); @@ -52,17 +74,28 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt); static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt); +static void transformColumnDefinition(ParseState *pstate, + CreateStmtContext *cxt, + ColumnDef *column); +static void transformTableConstraint(ParseState *pstate, + CreateStmtContext *cxt, + Constraint *constraint); +static void transformIndexConstraints(ParseState *pstate, + CreateStmtContext *cxt); +static void transformFKConstraints(ParseState *pstate, + CreateStmtContext *cxt); static Node *transformTypeRefs(ParseState *pstate, Node *stmt); static void transformTypeRefsList(ParseState *pstate, List *l); static void transformTypeRef(ParseState *pstate, TypeName *tn); static List *getSetColTypes(ParseState *pstate, Node *node); static void transformForUpdate(Query *qry, List *forUpdate); -static void transformFkeyGetPrimaryKey(FkConstraint *fkconstraint, Oid *pktypoid); static void transformConstraintAttrs(List *constraintList); static void transformColumnType(ParseState *pstate, ColumnDef *column); static void transformFkeyCheckAttrs(FkConstraint *fkconstraint, Oid *pktypoid); - +static void transformFkeyGetPrimaryKey(FkConstraint *fkconstraint, Oid *pktypoid); +static bool relationHasPrimaryKey(char *relname); +static Oid transformFkeyGetColType(CreateStmtContext *cxt, char *colname); static void release_pstate_resources(ParseState *pstate); static FromExpr *makeFromExpr(List *fromlist, Node *quals); @@ -679,296 +712,341 @@ CreateIndexName(char *table_name, char *column_name, static Query * transformCreateStmt(ParseState *pstate, CreateStmt *stmt) { + CreateStmtContext cxt; 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; - bool saw_nullable; - bool is_serial; - q = makeNode(Query); - q->commandType = CMD_UTILITY; - - fkconstraints = NIL; - constraints = stmt->constraints; - columns = NIL; - dlist = NIL; + cxt.stmtType = "CREATE TABLE"; + cxt.relname = stmt->relname; + cxt.inhRelnames = stmt->inhRelnames; + cxt.istemp = stmt->istemp; + cxt.relOid = InvalidOid; + cxt.columns = NIL; + cxt.ckconstraints = NIL; + cxt.fkconstraints = NIL; + cxt.ixconstraints = NIL; + cxt.blist = NIL; + cxt.alist = NIL; + cxt.pkey = NULL; /* - * Run through each primary element in the table creation clause + * Run through each primary element in the table creation clause. + * Separate column defs from constraints, and do preliminary analysis. */ foreach(elements, stmt->tableElts) { - element = lfirst(elements); + Node *element = lfirst(elements); + switch (nodeTag(element)) { case T_ColumnDef: - column = (ColumnDef *) element; - columns = lappend(columns, column); + transformColumnDefinition(pstate, &cxt, + (ColumnDef *) element); + break; - /* Check for SERIAL pseudo-types */ - is_serial = false; - if (strcmp(column->typename->name, "serial") == 0 || - strcmp(column->typename->name, "serial4") == 0) - { - is_serial = true; - column->typename->name = pstrdup("int4"); - } - else if (strcmp(column->typename->name, "bigserial") == 0 || - strcmp(column->typename->name, "serial8") == 0) - { - is_serial = true; - column->typename->name = pstrdup("int8"); - } + case T_Constraint: + transformTableConstraint(pstate, &cxt, + (Constraint *) element); + break; - /* Do necessary work on the column type declaration */ - transformColumnType(pstate, column); + case T_FkConstraint: + /* No pre-transformation needed */ + cxt.fkconstraints = lappend(cxt.fkconstraints, element); + break; - /* Special actions for SERIAL pseudo-types */ - if (is_serial) - { - char *sname; - char *qstring; - A_Const *snamenode; - FuncCall *funccallnode; - CreateSeqStmt *sequence; + default: + elog(ERROR, "parser: unrecognized node (internal error)"); + } + } - /* - * Create appropriate constraints for SERIAL. We do - * this in full, rather than shortcutting, so that we - * will detect any conflicting constraints the user - * wrote (like a different DEFAULT). - */ - sname = makeObjectName(stmt->relname, column->colname, - "seq"); + Assert(stmt->constraints == NIL); - /* - * Create an expression tree representing the function - * call nextval('"sequencename"') - */ - qstring = palloc(strlen(sname) + 2 + 1); - sprintf(qstring, "\"%s\"", sname); - snamenode = makeNode(A_Const); - snamenode->val.type = T_String; - snamenode->val.val.str = qstring; - funccallnode = makeNode(FuncCall); - funccallnode->funcname = "nextval"; - funccallnode->args = makeList1(snamenode); - funccallnode->agg_star = false; - funccallnode->agg_distinct = false; - - constraint = makeNode(Constraint); - constraint->contype = CONSTR_DEFAULT; - constraint->name = sname; - constraint->raw_expr = (Node *) funccallnode; - constraint->cooked_expr = NULL; - constraint->keys = NIL; - column->constraints = lappend(column->constraints, - constraint); - - constraint = makeNode(Constraint); - constraint->contype = CONSTR_UNIQUE; - constraint->name = NULL; /* assign later */ - column->constraints = lappend(column->constraints, - constraint); - - constraint = makeNode(Constraint); - constraint->contype = CONSTR_NOTNULL; - column->constraints = lappend(column->constraints, - constraint); + /* + * Postprocess constraints that give rise to index definitions. + */ + transformIndexConstraints(pstate, &cxt); - /* - * Build a CREATE SEQUENCE command to create the - * sequence object, and add it to the list of things - * to be done before this CREATE TABLE. - */ - sequence = makeNode(CreateSeqStmt); - sequence->seqname = pstrdup(sname); - sequence->istemp = stmt->istemp; - sequence->options = NIL; + /* + * Postprocess foreign-key constraints. + */ + transformFKConstraints(pstate, &cxt); + + /* + * Output results. + */ + q = makeNode(Query); + q->commandType = CMD_UTILITY; + q->utilityStmt = (Node *) stmt; + stmt->tableElts = cxt.columns; + stmt->constraints = cxt.ckconstraints; + extras_before = cxt.blist; + extras_after = cxt.alist; - elog(NOTICE, "CREATE TABLE will create implicit sequence '%s' for SERIAL column '%s.%s'", - sequence->seqname, stmt->relname, column->colname); + return q; +} - blist = lappend(blist, sequence); - } +static void +transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt, + ColumnDef *column) +{ + bool is_serial; + bool saw_nullable; + Constraint *constraint; + List *clist; + Ident *key; - /* Process column constraints, if any... */ - transformConstraintAttrs(column->constraints); + cxt->columns = lappend(cxt->columns, column); - saw_nullable = false; + /* Check for SERIAL pseudo-types */ + is_serial = false; + if (strcmp(column->typename->name, "serial") == 0 || + strcmp(column->typename->name, "serial4") == 0) + { + is_serial = true; + column->typename->name = pstrdup("int4"); + } + else if (strcmp(column->typename->name, "bigserial") == 0 || + strcmp(column->typename->name, "serial8") == 0) + { + is_serial = true; + column->typename->name = pstrdup("int8"); + } - foreach(clist, column->constraints) - { - constraint = lfirst(clist); + /* Do necessary work on the column type declaration */ + transformColumnType(pstate, column); - /* - * 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 (IsA(constraint, FkConstraint)) - { - Ident *id = makeNode(Ident); + /* Special actions for SERIAL pseudo-types */ + if (is_serial) + { + char *sname; + char *qstring; + A_Const *snamenode; + FuncCall *funccallnode; + CreateSeqStmt *sequence; - id->name = column->colname; - id->indirection = NIL; - id->isRel = false; + /* + * Create appropriate constraints for SERIAL. We do + * this in full, rather than shortcutting, so that we + * will detect any conflicting constraints the user + * wrote (like a different DEFAULT). + */ + sname = makeObjectName(cxt->relname, column->colname, "seq"); - fkconstraint = (FkConstraint *) constraint; - fkconstraint->fk_attrs = makeList1(id); + /* + * Create an expression tree representing the function + * call nextval('"sequencename"') + */ + qstring = palloc(strlen(sname) + 2 + 1); + sprintf(qstring, "\"%s\"", sname); + snamenode = makeNode(A_Const); + snamenode->val.type = T_String; + snamenode->val.val.str = qstring; + funccallnode = makeNode(FuncCall); + funccallnode->funcname = "nextval"; + funccallnode->args = makeList1(snamenode); + funccallnode->agg_star = false; + funccallnode->agg_distinct = false; + + constraint = makeNode(Constraint); + constraint->contype = CONSTR_DEFAULT; + constraint->name = sname; + constraint->raw_expr = (Node *) funccallnode; + constraint->cooked_expr = NULL; + constraint->keys = NIL; + column->constraints = lappend(column->constraints, constraint); + + constraint = makeNode(Constraint); + constraint->contype = CONSTR_UNIQUE; + constraint->name = NULL; /* assign later */ + column->constraints = lappend(column->constraints, constraint); + + constraint = makeNode(Constraint); + constraint->contype = CONSTR_NOTNULL; + column->constraints = lappend(column->constraints, constraint); - fkconstraints = lappend(fkconstraints, constraint); - continue; - } + /* + * Build a CREATE SEQUENCE command to create the + * sequence object, and add it to the list of things + * to be done before this CREATE/ALTER TABLE. + */ + sequence = makeNode(CreateSeqStmt); + sequence->seqname = pstrdup(sname); + sequence->istemp = cxt->istemp; + sequence->options = NIL; - switch (constraint->contype) - { - case CONSTR_NULL: - if (saw_nullable && column->is_not_null) - elog(ERROR, "CREATE TABLE/(NOT) NULL conflicting declaration" - " for '%s.%s'", stmt->relname, column->colname); - column->is_not_null = FALSE; - saw_nullable = true; - break; + elog(NOTICE, "%s will create implicit sequence '%s' for SERIAL column '%s.%s'", + cxt->stmtType, sequence->seqname, cxt->relname, column->colname); - case CONSTR_NOTNULL: - if (saw_nullable && !column->is_not_null) - elog(ERROR, "CREATE TABLE/(NOT) NULL conflicting declaration" - " for '%s.%s'", stmt->relname, column->colname); - column->is_not_null = TRUE; - saw_nullable = true; - break; + cxt->blist = lappend(cxt->blist, sequence); + } - case CONSTR_DEFAULT: - if (column->raw_default != NULL) - elog(ERROR, "CREATE TABLE/DEFAULT multiple values specified" - " for '%s.%s'", stmt->relname, column->colname); - column->raw_default = constraint->raw_expr; - Assert(constraint->cooked_expr == NULL); - break; + /* Process column constraints, if any... */ + transformConstraintAttrs(column->constraints); - case CONSTR_PRIMARY: - if (constraint->name == NULL) - constraint->name = makeObjectName(stmt->relname, NULL, "pkey"); - if (constraint->keys == NIL) - { - key = makeNode(Ident); - key->name = pstrdup(column->colname); - constraint->keys = makeList1(key); - } - dlist = lappend(dlist, constraint); - break; + saw_nullable = false; - case CONSTR_UNIQUE: - if (constraint->name == NULL) - constraint->name = makeObjectName(stmt->relname, column->colname, "key"); - if (constraint->keys == NIL) - { - key = makeNode(Ident); - key->name = pstrdup(column->colname); - constraint->keys = makeList1(key); - } - dlist = lappend(dlist, constraint); - break; + foreach(clist, column->constraints) + { + constraint = lfirst(clist); - case CONSTR_CHECK: - if (constraint->name == NULL) - constraint->name = makeObjectName(stmt->relname, column->colname, NULL); - constraints = lappend(constraints, constraint); - break; + /* + * 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 (IsA(constraint, FkConstraint)) + { + FkConstraint *fkconstraint = (FkConstraint *) constraint; + Ident *id = makeNode(Ident); - case CONSTR_ATTR_DEFERRABLE: - case CONSTR_ATTR_NOT_DEFERRABLE: - case CONSTR_ATTR_DEFERRED: - case CONSTR_ATTR_IMMEDIATE: - /* transformConstraintAttrs took care of these */ - break; + id->name = column->colname; + id->indirection = NIL; + id->isRel = false; - default: - elog(ERROR, "parser: unrecognized constraint (internal error)"); - break; - } - } - break; + fkconstraint->fk_attrs = makeList1(id); - case T_Constraint: - constraint = (Constraint *) element; - switch (constraint->contype) - { - case CONSTR_PRIMARY: - if (constraint->name == NULL) - constraint->name = makeObjectName(stmt->relname, NULL, "pkey"); - dlist = lappend(dlist, constraint); - break; + cxt->fkconstraints = lappend(cxt->fkconstraints, fkconstraint); + continue; + } - case CONSTR_UNIQUE: - dlist = lappend(dlist, constraint); - break; + Assert(IsA(constraint, Constraint)); - case CONSTR_CHECK: - constraints = lappend(constraints, constraint); - break; + switch (constraint->contype) + { + case CONSTR_NULL: + if (saw_nullable && column->is_not_null) + elog(ERROR, "%s/(NOT) NULL conflicting declaration for '%s.%s'", + cxt->stmtType, cxt->relname, column->colname); + column->is_not_null = FALSE; + saw_nullable = true; + break; - case CONSTR_NULL: - case CONSTR_NOTNULL: - case CONSTR_DEFAULT: - case CONSTR_ATTR_DEFERRABLE: - case CONSTR_ATTR_NOT_DEFERRABLE: - case CONSTR_ATTR_DEFERRED: - case CONSTR_ATTR_IMMEDIATE: - elog(ERROR, "parser: illegal context for constraint (internal error)"); - break; + case CONSTR_NOTNULL: + if (saw_nullable && !column->is_not_null) + elog(ERROR, "%s/(NOT) NULL conflicting declaration for '%s.%s'", + cxt->stmtType, cxt->relname, column->colname); + column->is_not_null = TRUE; + saw_nullable = true; + break; - default: - elog(ERROR, "parser: unrecognized constraint (internal error)"); - break; + case CONSTR_DEFAULT: + if (column->raw_default != NULL) + elog(ERROR, "%s/DEFAULT multiple values specified for '%s.%s'", + cxt->stmtType, cxt->relname, column->colname); + column->raw_default = constraint->raw_expr; + Assert(constraint->cooked_expr == NULL); + break; + + case CONSTR_PRIMARY: + if (constraint->name == NULL) + constraint->name = makeObjectName(cxt->relname, + NULL, + "pkey"); + if (constraint->keys == NIL) + { + key = makeNode(Ident); + key->name = pstrdup(column->colname); + constraint->keys = makeList1(key); } + cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); break; - case T_FkConstraint: + case CONSTR_UNIQUE: + if (constraint->name == NULL) + constraint->name = makeObjectName(cxt->relname, + column->colname, + "key"); + if (constraint->keys == NIL) + { + key = makeNode(Ident); + key->name = pstrdup(column->colname); + constraint->keys = makeList1(key); + } + cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); + break; - /* - * Table level FOREIGN KEY constraints are already - * complete. Just remember for later. - */ - fkconstraints = lappend(fkconstraints, element); + case CONSTR_CHECK: + if (constraint->name == NULL) + constraint->name = makeObjectName(cxt->relname, + column->colname, + NULL); + cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); + break; + + case CONSTR_ATTR_DEFERRABLE: + case CONSTR_ATTR_NOT_DEFERRABLE: + case CONSTR_ATTR_DEFERRED: + case CONSTR_ATTR_IMMEDIATE: + /* transformConstraintAttrs took care of these */ break; default: - elog(ERROR, "parser: unrecognized node (internal error)"); + elog(ERROR, "parser: unrecognized constraint (internal error)"); + break; } } +} - stmt->tableElts = columns; - stmt->constraints = constraints; +static void +transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt, + Constraint *constraint) +{ + switch (constraint->contype) + { + case CONSTR_PRIMARY: + if (constraint->name == NULL) + constraint->name = makeObjectName(cxt->relname, + NULL, + "pkey"); + cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); + break; -/* Now run through the "deferred list" to complete the query transformation. - * For PRIMARY KEY, mark each column as NOT NULL and create an index. - * For UNIQUE, create an index as for PRIMARY KEY, but do not insist on - * NOT NULL. - */ - while (dlist != NIL) + case CONSTR_UNIQUE: + cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); + break; + + case CONSTR_CHECK: + cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); + break; + + case CONSTR_NULL: + case CONSTR_NOTNULL: + case CONSTR_DEFAULT: + case CONSTR_ATTR_DEFERRABLE: + case CONSTR_ATTR_NOT_DEFERRABLE: + case CONSTR_ATTR_DEFERRED: + case CONSTR_ATTR_IMMEDIATE: + elog(ERROR, "parser: illegal context for constraint (internal error)"); + break; + + default: + elog(ERROR, "parser: unrecognized constraint (internal error)"); + break; + } +} + +static void +transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt) +{ + List *listptr; + List *keys; + IndexStmt *index; + IndexElem *iparam; + ColumnDef *column; + List *columns; + List *indexlist = NIL; + + /* + * Run through the constraints that need to generate an index. + * For PRIMARY KEY, mark each column as NOT NULL and create an index. + * For UNIQUE, create an index as for PRIMARY KEY, but do not insist on + * NOT NULL. + */ + foreach(listptr, cxt->ixconstraints) { - constraint = lfirst(dlist); + Constraint *constraint = lfirst(listptr); + Assert(IsA(constraint, Constraint)); Assert((constraint->contype == CONSTR_PRIMARY) || (constraint->contype == CONSTR_UNIQUE)); @@ -979,32 +1057,40 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) index->primary = (constraint->contype == CONSTR_PRIMARY); if (index->primary) { - if (pkey != NULL) - elog(ERROR, "CREATE TABLE/PRIMARY KEY multiple primary keys" - " for table '%s' are not allowed", stmt->relname); - pkey = index; + /* In ALTER TABLE case, a primary index might already exist */ + if (cxt->pkey != NULL || + (OidIsValid(cxt->relOid) && + relationHasPrimaryKey(cxt->relname))) + elog(ERROR, "%s/PRIMARY KEY multiple primary keys" + " for table '%s' are not allowed", + cxt->stmtType, cxt->relname); + cxt->pkey = index; } if (constraint->name != NULL) index->idxname = pstrdup(constraint->name); else if (constraint->contype == CONSTR_PRIMARY) - index->idxname = makeObjectName(stmt->relname, NULL, "pkey"); + index->idxname = makeObjectName(cxt->relname, NULL, "pkey"); else index->idxname = NULL; /* will set it later */ - index->relname = stmt->relname; + index->relname = cxt->relname; index->accessMethod = "btree"; index->indexParams = NIL; index->whereClause = NULL; + /* + * Make sure referenced keys exist. If we are making a + * PRIMARY KEY index, also make sure they are NOT NULL. + */ foreach(keys, constraint->keys) { + Ident *key = (Ident *) lfirst(keys); bool found = false; - key = (Ident *) lfirst(keys); Assert(IsA(key, Ident)); column = NULL; - foreach(columns, stmt->tableElts) + foreach(columns, cxt->columns) { column = lfirst(columns); Assert(IsA(column, ColumnDef)); @@ -1020,13 +1106,12 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) if (constraint->contype == CONSTR_PRIMARY) column->is_not_null = TRUE; } - else + else if (cxt->inhRelnames) { /* try inherited tables */ - List *inhRelnames = stmt->inhRelnames; List *inher; - foreach(inher, inhRelnames) + foreach(inher, cxt->inhRelnames) { Value *inh = lfirst(inher); Relation rel; @@ -1072,18 +1157,41 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) break; } } + else if (OidIsValid(cxt->relOid)) + { + /* ALTER TABLE case: does column already exist? */ + HeapTuple atttuple; + + atttuple = SearchSysCache(ATTNAME, + ObjectIdGetDatum(cxt->relOid), + PointerGetDatum(key->name), + 0, 0); + if (HeapTupleIsValid(atttuple)) + { + found = true; + /* + * We require pre-existing column to be already marked + * NOT NULL. + */ + if (constraint->contype == CONSTR_PRIMARY && + !((Form_pg_attribute) GETSTRUCT(atttuple))->attnotnull) + elog(ERROR, "Existing attribute \"%s\" cannot be a PRIMARY KEY because it is not marked NOT NULL", + key->name); + ReleaseSysCache(atttuple); + } + } if (!found) - elog(ERROR, "CREATE TABLE: column \"%s\" named in key does not exist", - key->name); + elog(ERROR, "%s: column \"%s\" named in key does not exist", + cxt->stmtType, key->name); /* Check for PRIMARY KEY(foo, foo) */ foreach(columns, index->indexParams) { iparam = (IndexElem *) lfirst(columns); if (strcmp(key->name, iparam->name) == 0) - elog(ERROR, "CREATE TABLE: column \"%s\" appears twice in %s constraint", - key->name, + elog(ERROR, "%s: column \"%s\" appears twice in %s constraint", + cxt->stmtType, key->name, index->primary ? "PRIMARY KEY" : "UNIQUE"); } @@ -1095,8 +1203,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) index->indexParams = lappend(index->indexParams, iparam); } - ilist = lappend(ilist, index); - dlist = lnext(dlist); + indexlist = lappend(indexlist, index); } /* @@ -1105,31 +1212,32 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) * KEY or SERIAL UNIQUE. A strict reading of SQL92 would suggest * raising an error instead, but that strikes me as too * anal-retentive. - tgl 2001-02-14 + * + * XXX in ALTER TABLE case, it'd be nice to look for duplicate + * pre-existing indexes, too. */ - dlist = ilist; - ilist = NIL; - if (pkey != NULL) + cxt->alist = NIL; + if (cxt->pkey != NULL) { /* Make sure we keep the PKEY index in preference to others... */ - ilist = makeList1(pkey); + cxt->alist = makeList1(cxt->pkey); } - while (dlist != NIL) + while (indexlist != NIL) { - index = lfirst(dlist); + index = lfirst(indexlist); - /* if it's pkey, it's already in ilist */ - if (index != pkey) + /* if it's pkey, it's already in cxt->alist */ + if (index != cxt->pkey) { bool keep = true; List *priorlist; - foreach(priorlist, ilist) + foreach(priorlist, cxt->alist) { IndexStmt *priorindex = lfirst(priorlist); if (equal(index->indexParams, priorindex->indexParams)) { - /* * If the prior index is as yet unnamed, and this one * is named, then transfer the name to the prior @@ -1145,502 +1253,411 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt) } if (keep) - ilist = lappend(ilist, index); + cxt->alist = lappend(cxt->alist, index); } - dlist = lnext(dlist); + indexlist = lnext(indexlist); } /* * Finally, select unique names for all not-previously-named indices, * and display notice messages. + * + * XXX in ALTER TABLE case, we fail to consider name collisions + * against pre-existing indexes. */ - dlist = ilist; - while (dlist != NIL) + foreach(indexlist, cxt->alist) { - index = lfirst(dlist); + index = lfirst(indexlist); if (index->idxname == NULL && index->indexParams != NIL) { iparam = lfirst(index->indexParams); - index->idxname = CreateIndexName(stmt->relname, iparam->name, - "key", ilist); + index->idxname = CreateIndexName(cxt->relname, iparam->name, + "key", cxt->alist); } if (index->idxname == NULL) /* should not happen */ - elog(ERROR, "CREATE TABLE: failed to make implicit index name"); + elog(ERROR, "%s: failed to make implicit index name", + cxt->stmtType); - elog(NOTICE, "CREATE TABLE/%s will create implicit index '%s' for table '%s'", - (index->primary ? "PRIMARY KEY" : "UNIQUE"), - index->idxname, stmt->relname); - - dlist = lnext(dlist); + elog(NOTICE, "%s/%s will create implicit index '%s' for table '%s'", + cxt->stmtType, (index->primary ? "PRIMARY KEY" : "UNIQUE"), + index->idxname, cxt->relname); } +} - q->utilityStmt = (Node *) stmt; - extras_before = blist; - extras_after = ilist; +static void +transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt) +{ + CreateTrigStmt *fk_trigger; + List *fkactions = NIL; + List *fkclist; + List *fk_attr; + List *pk_attr; + Ident *id; + Oid pktypoid[INDEX_MAX_KEYS]; + Oid fktypoid[INDEX_MAX_KEYS]; + int i; + + if (cxt->fkconstraints == NIL) + return; - /* - * Now process the FOREIGN KEY constraints and add appropriate queries - * to the extras_after statements list. - */ - if (fkconstraints != NIL) + elog(NOTICE, "%s will create implicit trigger(s) for FOREIGN KEY check(s)", + cxt->stmtType); + + foreach(fkclist, cxt->fkconstraints) { - CreateTrigStmt *fk_trigger; - List *fk_attr; - List *pk_attr; - Ident *id; - Oid pktypoid[INDEX_MAX_KEYS]; - Oid fktypoid[INDEX_MAX_KEYS]; - int i; + FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist); + int attnum; + List *fkattrs; - for (i=0; i<INDEX_MAX_KEYS; i++) { - pktypoid[i]=fktypoid[i]=0; - } - elog(NOTICE, "CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)"); + /* + * If the constraint has no name, set it to <unnamed> + */ + if (fkconstraint->constr_name == NULL) + fkconstraint->constr_name = "<unnamed>"; + + for (attnum=0; attnum<INDEX_MAX_KEYS; attnum++) + pktypoid[attnum] = fktypoid[attnum] = InvalidOid; - foreach(fkclist, fkconstraints) + /* + * Look up the referencing attributes to make sure they exist + * (or will exist) in this table, and remember their type OIDs. + */ + attnum = 0; + foreach(fkattrs, fkconstraint->fk_attrs) { - fkconstraint = (FkConstraint *) lfirst(fkclist); + Ident *fkattr = lfirst(fkattrs); - /* - * If the constraint has no name, set it to <unnamed> - * - */ - if (fkconstraint->constr_name == NULL) - fkconstraint->constr_name = "<unnamed>"; + if (attnum >= INDEX_MAX_KEYS) + elog(ERROR, "Can only have %d keys in a foreign key", + INDEX_MAX_KEYS); + fktypoid[attnum++] = transformFkeyGetColType(cxt, + fkattr->name); + } - /* - * Check to see if the attributes mentioned by the constraint - * actually exist on this table. - */ - if (fkconstraint->fk_attrs != NIL) + /* + * If the attribute list for the referenced table was omitted, + * lookup the definition of the primary key. + */ + if (fkconstraint->pk_attrs == NIL) + { + if (strcmp(fkconstraint->pktable_name, cxt->relname) != 0) + transformFkeyGetPrimaryKey(fkconstraint, pktypoid); + else if (cxt->pkey != NULL) { - int found = 0; - int attnum=0; - List *cols; - List *fkattrs; - Ident *fkattr = NULL; - ColumnDef *col; - - foreach(fkattrs, fkconstraint->fk_attrs) - { - found = 0; - fkattr = lfirst(fkattrs); - foreach(cols, stmt->tableElts) - { - col = lfirst(cols); - if (strcmp(col->colname, fkattr->name) == 0) - { - char *buff=TypeNameToInternalName(col->typename); - Oid type=typenameTypeId(buff); - if (!OidIsValid(type)) { - elog(ERROR, "Unable to lookup type %s", col->typename->name); - } - fktypoid[attnum++]=type; - found = 1; - break; - } - } - if (!found) { - List *inher; - List *inhRelnames = stmt->inhRelnames; - Relation rel; + /* Use the to-be-created primary key */ + List *attr; - foreach(inher, inhRelnames) - { - Value *inh = lfirst(inher); - int count; - - Assert(IsA(inh, String)); - rel = heap_openr(strVal(inh), AccessShareLock); - if (rel->rd_rel->relkind != RELKIND_RELATION) - elog(ERROR, "inherited table \"%s\" is not a relation", - strVal(inh)); - for (count = 0; count < rel->rd_att->natts; count++) - { - char *name = NameStr(rel->rd_att->attrs[count]->attname); - - if (strcmp(fkattr->name, name) == 0) - { - fktypoid[attnum++]=rel->rd_att->attrs[count]->atttypid; - found = 1; - break; - } - } - heap_close(rel, NoLock); - if (found) - break; - } - } - if (!found) - break; + attnum=0; + foreach(attr, cxt->pkey->indexParams) + { + IndexElem *ielem = lfirst(attr); + Ident *pkattr = (Ident *) makeNode(Ident); + + pkattr->name = pstrdup(ielem->name); + pkattr->indirection = NIL; + pkattr->isRel = false; + fkconstraint->pk_attrs = lappend(fkconstraint->pk_attrs, + pkattr); + if (attnum >= INDEX_MAX_KEYS) + elog(ERROR, "Can only have %d keys in a foreign key", + INDEX_MAX_KEYS); + pktypoid[attnum++] = transformFkeyGetColType(cxt, + ielem->name); } - if (!found) - elog(ERROR, "columns referenced in foreign key constraint not found."); } - - /* - * If the attribute list for the referenced table was omitted, - * lookup for the definition of the primary key. If the - * referenced table is this table, use the definition we found - * above, rather than looking to the system tables. - * - */ - if (fkconstraint->fk_attrs != NIL && fkconstraint->pk_attrs == NIL) + else { - if (strcmp(fkconstraint->pktable_name, stmt->relname) != 0) + /* In ALTER TABLE case, primary key may already exist */ + if (OidIsValid(cxt->relOid)) transformFkeyGetPrimaryKey(fkconstraint, pktypoid); - else if (pkey != NULL) - { - List *pkey_attr = pkey->indexParams; - List *attr; - List *findattr; - IndexElem *ielem; - Ident *pkattr; - int attnum=0; - ColumnDef *col; - - foreach(attr, pkey_attr) - { - ielem = lfirst(attr); - pkattr = (Ident *) makeNode(Ident); - pkattr->name = pstrdup(ielem->name); - pkattr->indirection = NIL; - pkattr->isRel = false; - fkconstraint->pk_attrs = lappend(fkconstraint->pk_attrs, pkattr); - foreach (findattr, stmt->tableElts) { - col=lfirst(findattr); - if (strcmp(col->colname, ielem->name)==0) { - char *buff=TypeNameToInternalName(col->typename); - Oid type=typenameTypeId(buff); - if (!OidIsValid(type)) { - elog(ERROR, "Unable to lookup type %s", col->typename->name); - } - pktypoid[attnum++]=type; /* need to convert typename */ - break; - } - } - } - } else - { elog(ERROR, "PRIMARY KEY for referenced table \"%s\" not found", fkconstraint->pktable_name); - } } + } + else + { + /* Validate the specified referenced key list */ + if (strcmp(fkconstraint->pktable_name, cxt->relname) != 0) + transformFkeyCheckAttrs(fkconstraint, pktypoid); else { - if (strcmp(fkconstraint->pktable_name, stmt->relname) != 0) - transformFkeyCheckAttrs(fkconstraint, pktypoid); - else + /* Look for a matching new unique/primary constraint */ + List *index; + bool found = false; + + foreach(index, cxt->alist) { - /* Get a unique/pk constraint from above */ - List *index; - int found = 0; + IndexStmt *ind = lfirst(index); + List *pkattrs; - foreach(index, ilist) + if (!ind->unique) + continue; + if (length(ind->indexParams) != + length(fkconstraint->pk_attrs)) + continue; + attnum=0; + foreach(pkattrs, fkconstraint->pk_attrs) { - IndexStmt *ind = lfirst(index); - IndexElem *indparm; + Ident *pkattr = lfirst(pkattrs); List *indparms; - List *pkattrs; - List *findattr; - ColumnDef *col; - Ident *pkattr; - if (ind->unique) + found = false; + foreach(indparms, ind->indexParams) { - int count = 0; - int attnum=0; - - foreach(indparms, ind->indexParams) - count++; - if (count != length(fkconstraint->pk_attrs)) - found = 0; - else + IndexElem *indparm = lfirst(indparms); + + if (strcmp(indparm->name, pkattr->name) == 0) { - foreach(pkattrs, fkconstraint->pk_attrs) - { - found = 0; - pkattr = lfirst(pkattrs); - foreach(indparms, ind->indexParams) - { - indparm = lfirst(indparms); - if (strcmp(indparm->name, pkattr->name) == 0) - { - foreach (findattr, stmt->tableElts) { - col=lfirst(findattr); - if (strcmp(col->colname, indparm->name)==0) { - char *buff=TypeNameToInternalName(col->typename); - Oid type=typenameTypeId(buff); - if (!OidIsValid(type)) { - elog(ERROR, "Unable to lookup type %s", col->typename->name); - } - pktypoid[attnum++]=type; - found=1; - break; - } - } - if (!found) { - List *inher; - List *inhRelnames=stmt->inhRelnames; - Relation rel; - foreach (inher, inhRelnames) { - Value *inh=lfirst(inher); - int count; - Assert(IsA(inh, String)); - rel=heap_openr(strVal(inh), AccessShareLock); - if (rel->rd_rel->relkind!=RELKIND_RELATION) - elog(ERROR, "inherited table \"%s\" is not a relation", strVal(inh)); - for (count=0; count<rel->rd_att->natts; count++) { - char *name=NameStr(rel->rd_att->attrs[count]->attname); - if (strcmp(pkattr->name, name)==0) { - pktypoid[attnum++]=rel->rd_att->attrs[count]->atttypid; - found=1; - break; - } - } - heap_close(rel, NoLock); - if (found) - break; - } - } - break; - } - } - if (!found) - break; - } + found = true; + break; } } - if (found) + if (!found) break; + if (attnum >= INDEX_MAX_KEYS) + elog(ERROR, "Can only have %d keys in a foreign key", + INDEX_MAX_KEYS); + pktypoid[attnum++] = transformFkeyGetColType(cxt, + pkattr->name); } - if (!found) + if (found) + break; + } + if (!found) + { + /* In ALTER TABLE case, such an index may already exist */ + if (OidIsValid(cxt->relOid)) + transformFkeyCheckAttrs(fkconstraint, pktypoid); + else elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found", fkconstraint->pktable_name); } } + } - for (i = 0; i < INDEX_MAX_KEYS && fktypoid[i] != 0; i++) { - /* - * fktypoid[i] is the foreign key table's i'th element's type oid - * pktypoid[i] is the primary key table's i'th element's type oid - * We let oper() do our work for us, including elog(ERROR) if the - * types don't compare with = - */ - Operator o=oper("=", fktypoid[i], pktypoid[i], false); - ReleaseSysCache(o); - } + /* Be sure referencing and referenced column types are comparable */ + for (i = 0; i < INDEX_MAX_KEYS && fktypoid[i] != 0; i++) + { /* - * Build a CREATE CONSTRAINT TRIGGER statement for the CHECK - * action. - * + * fktypoid[i] is the foreign key table's i'th element's type oid + * pktypoid[i] is the primary key table's i'th element's type oid + * We let oper() do our work for us, including elog(ERROR) if the + * types don't compare with = */ - 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, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(stmt->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable_name)); - fk_trigger->args = lappend(fk_trigger->args, + Operator o=oper("=", fktypoid[i], pktypoid[i], false); + ReleaseSysCache(o); + } + + /* + * Build a CREATE CONSTRAINT TRIGGER statement for the CHECK + * action. + */ + fk_trigger = (CreateTrigStmt *) makeNode(CreateTrigStmt); + fk_trigger->trigname = fkconstraint->constr_name; + fk_trigger->relname = cxt->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, + makeString(fkconstraint->constr_name)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(cxt->relname)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkconstraint->pktable_name)); + fk_trigger->args = lappend(fk_trigger->args, makeString(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, - makeString(id->name)); + fk_attr = fkconstraint->fk_attrs; + pk_attr = fkconstraint->pk_attrs; + if (length(fk_attr) != length(pk_attr)) + elog(ERROR, "number of key attributes in referenced table must be equal to foreign key" + "\n\tIllegal FOREIGN KEY definition references \"%s\"", + fkconstraint->pktable_name); + + while (fk_attr != NIL) + { + id = (Ident *) lfirst(fk_attr); + fk_trigger->args = lappend(fk_trigger->args, + makeString(id->name)); - id = (Ident *) lfirst(pk_attr); - fk_trigger->args = lappend(fk_trigger->args, - makeString(id->name)); + id = (Ident *) lfirst(pk_attr); + fk_trigger->args = lappend(fk_trigger->args, + makeString(id->name)); - fk_attr = lnext(fk_attr); - pk_attr = lnext(pk_attr); - } + fk_attr = lnext(fk_attr); + pk_attr = lnext(pk_attr); + } - extras_after = lappend(extras_after, (Node *) fk_trigger); + fkactions = lappend(fkactions, (Node *) fk_trigger); - /* - * 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; - 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; - switch ((fkconstraint->actions & FKCONSTR_ON_DELETE_MASK) - >> FKCONSTR_ON_DELETE_SHIFT) - { - case FKCONSTR_ON_KEY_NOACTION: - fk_trigger->funcname = "RI_FKey_noaction_del"; - break; - case FKCONSTR_ON_KEY_RESTRICT: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; - 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; - } + /* + * 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; + 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 = cxt->relname; + switch ((fkconstraint->actions & FKCONSTR_ON_DELETE_MASK) + >> FKCONSTR_ON_DELETE_SHIFT) + { + case FKCONSTR_ON_KEY_NOACTION: + fk_trigger->funcname = "RI_FKey_noaction_del"; + break; + case FKCONSTR_ON_KEY_RESTRICT: + fk_trigger->deferrable = false; + fk_trigger->initdeferred = false; + 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->args = NIL; - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(stmt->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable_name)); - fk_trigger->args = lappend(fk_trigger->args, + fk_trigger->args = NIL; + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkconstraint->constr_name)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(cxt->relname)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkconstraint->pktable_name)); + fk_trigger->args = lappend(fk_trigger->args, makeString(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, - makeString(id->name)); + 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, + makeString(id->name)); - id = (Ident *) lfirst(pk_attr); - fk_trigger->args = lappend(fk_trigger->args, - makeString(id->name)); + id = (Ident *) lfirst(pk_attr); + fk_trigger->args = lappend(fk_trigger->args, + makeString(id->name)); - fk_attr = lnext(fk_attr); - pk_attr = lnext(pk_attr); - } + fk_attr = lnext(fk_attr); + pk_attr = lnext(pk_attr); + } - extras_after = lappend(extras_after, (Node *) fk_trigger); + fkactions = lappend(fkactions, (Node *) fk_trigger); - /* - * 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; - 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; - switch ((fkconstraint->actions & FKCONSTR_ON_UPDATE_MASK) - >> FKCONSTR_ON_UPDATE_SHIFT) - { - case FKCONSTR_ON_KEY_NOACTION: - fk_trigger->funcname = "RI_FKey_noaction_upd"; - break; - case FKCONSTR_ON_KEY_RESTRICT: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; - 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; - } + /* + * 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; + 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 = cxt->relname; + switch ((fkconstraint->actions & FKCONSTR_ON_UPDATE_MASK) + >> FKCONSTR_ON_UPDATE_SHIFT) + { + case FKCONSTR_ON_KEY_NOACTION: + fk_trigger->funcname = "RI_FKey_noaction_upd"; + break; + case FKCONSTR_ON_KEY_RESTRICT: + fk_trigger->deferrable = false; + fk_trigger->initdeferred = false; + 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->args = NIL; - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(stmt->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable_name)); - fk_trigger->args = lappend(fk_trigger->args, + fk_trigger->args = NIL; + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkconstraint->constr_name)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(cxt->relname)); + fk_trigger->args = lappend(fk_trigger->args, + makeString(fkconstraint->pktable_name)); + fk_trigger->args = lappend(fk_trigger->args, makeString(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, - makeString(id->name)); - - id = (Ident *) lfirst(pk_attr); - fk_trigger->args = lappend(fk_trigger->args, - makeString(id->name)); + 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, + makeString(id->name)); - fk_attr = lnext(fk_attr); - pk_attr = lnext(pk_attr); - } + id = (Ident *) lfirst(pk_attr); + fk_trigger->args = lappend(fk_trigger->args, + makeString(id->name)); - extras_after = lappend(extras_after, (Node *) fk_trigger); + fk_attr = lnext(fk_attr); + pk_attr = lnext(pk_attr); } - } - return q; -} /* transformCreateStmt() */ + fkactions = lappend(fkactions, (Node *) fk_trigger); + } + /* + * Attach completed list of extra actions to cxt->alist. We cannot + * do this earlier, because we assume above that cxt->alist still + * holds only IndexStmts. + */ + cxt->alist = nconc(cxt->alist, fkactions); +} /* * transformIndexStmt - @@ -2481,262 +2498,89 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) /* * tranformAlterTableStmt - * transform an Alter Table Statement - * */ static Query * transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt) { + CreateStmtContext cxt; Query *qry; - qry = makeNode(Query); - qry->commandType = CMD_UTILITY; - /* - * The only subtypes that currently have special handling are 'A'dd - * column and Add 'C'onstraint. In addition, right now only Foreign - * Key 'C'onstraints have a special transformation. - * + * The only subtypes that currently require parse transformation + * handling are 'A'dd column and Add 'C'onstraint. These largely + * re-use code from CREATE TABLE. */ switch (stmt->subtype) { case 'A': - transformColumnType(pstate, (ColumnDef *) stmt->def); + cxt.stmtType = "ALTER TABLE"; + cxt.relname = stmt->relname; + cxt.inhRelnames = NIL; + cxt.istemp = is_temp_rel_name(stmt->relname); + cxt.relOid = GetSysCacheOid(RELNAME, + PointerGetDatum(stmt->relname), + 0, 0, 0); + cxt.columns = NIL; + cxt.ckconstraints = NIL; + cxt.fkconstraints = NIL; + cxt.ixconstraints = NIL; + cxt.blist = NIL; + cxt.alist = NIL; + cxt.pkey = NULL; + + Assert(IsA(stmt->def, ColumnDef)); + transformColumnDefinition(pstate, &cxt, + (ColumnDef *) stmt->def); + + transformIndexConstraints(pstate, &cxt); + transformFKConstraints(pstate, &cxt); + + ((ColumnDef *) stmt->def)->constraints = cxt.ckconstraints; + extras_before = cxt.blist; + extras_after = cxt.alist; break; - case 'C': - if (stmt->def && IsA(stmt->def, FkConstraint)) - { - CreateTrigStmt *fk_trigger; - List *fk_attr; - List *pk_attr; - Ident *id; - FkConstraint *fkconstraint; - - extras_after = NIL; - elog(NOTICE, "ALTER TABLE ... ADD CONSTRAINT will create implicit trigger(s) for FOREIGN KEY check(s)"); - - fkconstraint = (FkConstraint *) stmt->def; - - /* - * 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) { - Oid pktypoid[INDEX_MAX_KEYS]; - transformFkeyGetPrimaryKey(fkconstraint, pktypoid); - } - - /* - * 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, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(stmt->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(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, - makeString(id->name)); - - id = (Ident *) lfirst(pk_attr); - fk_trigger->args = lappend(fk_trigger->args, - makeString(id->name)); - - fk_attr = lnext(fk_attr); - pk_attr = lnext(pk_attr); - } - - extras_after = lappend(extras_after, (Node *) fk_trigger); - - /* - * 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_NOACTION: - fk_trigger->funcname = "RI_FKey_noaction_del"; - break; - 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, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(stmt->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(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, - makeString(id->name)); - - id = (Ident *) lfirst(pk_attr); - fk_trigger->args = lappend(fk_trigger->args, - makeString(id->name)); - - fk_attr = lnext(fk_attr); - pk_attr = lnext(pk_attr); - } - extras_after = lappend(extras_after, (Node *) fk_trigger); - - /* - * 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_NOACTION: - fk_trigger->funcname = "RI_FKey_noaction_upd"; - break; - 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, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(stmt->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(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, - makeString(id->name)); - - id = (Ident *) lfirst(pk_attr); - fk_trigger->args = lappend(fk_trigger->args, - makeString(id->name)); + case 'C': + cxt.stmtType = "ALTER TABLE"; + cxt.relname = stmt->relname; + cxt.inhRelnames = NIL; + cxt.istemp = is_temp_rel_name(stmt->relname); + cxt.relOid = GetSysCacheOid(RELNAME, + PointerGetDatum(stmt->relname), + 0, 0, 0); + cxt.columns = NIL; + cxt.ckconstraints = NIL; + cxt.fkconstraints = NIL; + cxt.ixconstraints = NIL; + cxt.blist = NIL; + cxt.alist = NIL; + cxt.pkey = NULL; + + if (IsA(stmt->def, Constraint)) + transformTableConstraint(pstate, &cxt, + (Constraint *) stmt->def); + else if (IsA(stmt->def, FkConstraint)) + cxt.fkconstraints = lappend(cxt.fkconstraints, stmt->def); + else + elog(ERROR, "Unexpected node type in ALTER TABLE ADD CONSTRAINT"); - fk_attr = lnext(fk_attr); - pk_attr = lnext(pk_attr); - } + transformIndexConstraints(pstate, &cxt); + transformFKConstraints(pstate, &cxt); - extras_after = lappend(extras_after, (Node *) fk_trigger); - } + Assert(cxt.columns == NIL); + stmt->def = (Node *) nconc(cxt.ckconstraints, cxt.fkconstraints); + extras_before = cxt.blist; + extras_after = cxt.alist; break; + default: break; } + + qry = makeNode(Query); + qry->commandType = CMD_UTILITY; qry->utilityStmt = (Node *) stmt; + return qry; } @@ -2931,9 +2775,8 @@ transformForUpdate(Query *qry, List *forUpdate) /* * transformFkeyCheckAttrs - * - * Try to make sure that the attributes of a referenced table + * Make sure that the attributes of a referenced table * belong to a unique (or primary key) constraint. - * */ static void transformFkeyCheckAttrs(FkConstraint *fkconstraint, Oid *pktypoid) @@ -2949,9 +2792,6 @@ transformFkeyCheckAttrs(FkConstraint *fkconstraint, Oid *pktypoid) * 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; /* @@ -2971,7 +2811,7 @@ transformFkeyCheckAttrs(FkConstraint *fkconstraint, Oid *pktypoid) ObjectIdGetDatum(indexoid), 0, 0, 0); if (!HeapTupleIsValid(indexTuple)) - elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found", + elog(ERROR, "transformFkeyCheckAttrs: index %u not found", indexoid); indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); @@ -2980,7 +2820,8 @@ transformFkeyCheckAttrs(FkConstraint *fkconstraint, Oid *pktypoid) List *attrl; int attnum=0; - for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++); + for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++) + ; if (i != length(fkconstraint->pk_attrs)) found = false; else @@ -3030,7 +2871,6 @@ transformFkeyCheckAttrs(FkConstraint *fkconstraint, Oid *pktypoid) * * 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, Oid *pktypoid) @@ -3048,9 +2888,6 @@ transformFkeyGetPrimaryKey(FkConstraint *fkconstraint, Oid *pktypoid) * 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; /* @@ -3095,11 +2932,10 @@ transformFkeyGetPrimaryKey(FkConstraint *fkconstraint, Oid *pktypoid) int pkattno = indexStruct->indkey[i]; Ident *pkattr = makeNode(Ident); - pkattr->name = DatumGetCString(DirectFunctionCall1(nameout, - NameGetDatum(&(pkrel_attrs[pkattno - 1]->attname)))); + pkattr->name = pstrdup(NameStr(pkrel_attrs[pkattno-1]->attname)); pkattr->indirection = NIL; pkattr->isRel = false; - pktypoid[attnum++]=pkrel_attrs[pkattno-1]->atttypid; + pktypoid[attnum++] = pkrel_attrs[pkattno-1]->atttypid; fkconstraint->pk_attrs = lappend(fkconstraint->pk_attrs, pkattr); } @@ -3110,6 +2946,130 @@ transformFkeyGetPrimaryKey(FkConstraint *fkconstraint, Oid *pktypoid) } /* + * relationHasPrimaryKey - + * + * See whether an existing relation has a primary key. + */ +static bool +relationHasPrimaryKey(char *relname) +{ + bool result = false; + Relation rel; + List *indexoidlist, + *indexoidscan; + + rel = heap_openr(relname, AccessShareLock); + + /* + * Get the list of index OIDs for the table from the relcache, and + * look up each one in the pg_index syscache until we find one marked + * primary key (hopefully there isn't more than one such). + */ + indexoidlist = RelationGetIndexList(rel); + + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsti(indexoidscan); + HeapTuple indexTuple; + + indexTuple = SearchSysCache(INDEXRELID, + ObjectIdGetDatum(indexoid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "relationHasPrimaryKey: index %u not found", + indexoid); + result = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary; + ReleaseSysCache(indexTuple); + if (result) + break; + } + + freeList(indexoidlist); + + heap_close(rel, AccessShareLock); + + return result; +} + +/* + * transformFkeyGetColType - + * + * Find a referencing column by name, and return its type OID. + * Error if it can't be found. + */ +static Oid +transformFkeyGetColType(CreateStmtContext *cxt, char *colname) +{ + List *cols; + List *inher; + Oid result; + + /* First look for column among the newly-created columns */ + foreach(cols, cxt->columns) + { + ColumnDef *col = lfirst(cols); + + if (strcmp(col->colname, colname) == 0) + { + char *buff = TypeNameToInternalName(col->typename); + + result = typenameTypeId(buff); + if (!OidIsValid(result)) + elog(ERROR, "Unable to lookup type %s", + col->typename->name); + return result; + } + } + /* Look for column among inherited columns (if CREATE TABLE case) */ + foreach(inher, cxt->inhRelnames) + { + Value *inh = lfirst(inher); + Relation rel; + int count; + + Assert(IsA(inh, String)); + rel = heap_openr(strVal(inh), AccessShareLock); + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "inherited table \"%s\" is not a relation", + strVal(inh)); + for (count = 0; count < rel->rd_att->natts; count++) + { + char *name = NameStr(rel->rd_att->attrs[count]->attname); + + if (strcmp(name, colname) == 0) + { + result = rel->rd_att->attrs[count]->atttypid; + + heap_close(rel, NoLock); + return result; + } + } + heap_close(rel, NoLock); + } + /* Look for column among existing columns (if ALTER TABLE case) */ + if (OidIsValid(cxt->relOid)) + { + HeapTuple atttuple; + + atttuple = SearchSysCache(ATTNAME, + ObjectIdGetDatum(cxt->relOid), + PointerGetDatum(colname), + 0, 0); + if (HeapTupleIsValid(atttuple)) + { + result = ((Form_pg_attribute) GETSTRUCT(atttuple))->atttypid; + + ReleaseSysCache(atttuple); + return result; + } + } + + elog(ERROR, "%s: column \"%s\" referenced in foreign key constraint does not exist", + cxt->stmtType, colname); + return InvalidOid; /* keep compiler quiet */ +} + +/* * Preprocess a list of column constraint clauses * to attach constraint attributes to their primary constraint nodes * and detect inconsistent/misplaced constraint attributes. |