diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2009-07-29 20:56:21 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2009-07-29 20:56:21 +0000 |
commit | 25d9bf2e3e66ee2e546c5c523d148ecab6ee1dcc (patch) | |
tree | b0dee0f1d6111fd6658d432ec30e5ddb88adc02f /src/backend/parser | |
parent | 850490579318ff52097eec92ce535357dd0c7a3a (diff) | |
download | postgresql-25d9bf2e3e66ee2e546c5c523d148ecab6ee1dcc.tar.gz |
Support deferrable uniqueness constraints.
The current implementation fires an AFTER ROW trigger for each tuple that
looks like it might be non-unique according to the index contents at the
time of insertion. This works well as long as there aren't many conflicts,
but won't scale to massive unique-key reassignments. Improving that case
is a TODO item.
Dean Rasheed
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/gram.y | 29 | ||||
-rw-r--r-- | src/backend/parser/parse_utilcmd.c | 137 |
2 files changed, 130 insertions, 36 deletions
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c44bbe75c3..0547b64a6e 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.673 2009/07/26 23:34:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.674 2009/07/29 20:56:19 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -2220,6 +2220,8 @@ ColConstraintElem: n->cooked_expr = NULL; n->keys = NULL; n->indexspace = NULL; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | NULL_P @@ -2231,6 +2233,8 @@ ColConstraintElem: n->cooked_expr = NULL; n->keys = NULL; n->indexspace = NULL; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | UNIQUE opt_definition OptConsTableSpace @@ -2243,6 +2247,8 @@ ColConstraintElem: n->keys = NULL; n->options = $2; n->indexspace = $3; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | PRIMARY KEY opt_definition OptConsTableSpace @@ -2255,6 +2261,8 @@ ColConstraintElem: n->keys = NULL; n->options = $3; n->indexspace = $4; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | CHECK '(' a_expr ')' @@ -2266,6 +2274,8 @@ ColConstraintElem: n->cooked_expr = NULL; n->keys = NULL; n->indexspace = NULL; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | DEFAULT b_expr @@ -2277,6 +2287,8 @@ ColConstraintElem: n->cooked_expr = NULL; n->keys = NULL; n->indexspace = NULL; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | REFERENCES qualified_name opt_column_list key_match key_actions @@ -2398,7 +2410,7 @@ TableConstraint: ; ConstraintElem: - CHECK '(' a_expr ')' + CHECK '(' a_expr ')' ConstraintAttributeSpec { Constraint *n = makeNode(Constraint); n->contype = CONSTR_CHECK; @@ -2406,9 +2418,17 @@ ConstraintElem: n->raw_expr = $3; n->cooked_expr = NULL; n->indexspace = NULL; + if ($5 != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CHECK constraints cannot be deferred"), + parser_errposition(@5))); + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | UNIQUE '(' columnList ')' opt_definition OptConsTableSpace + ConstraintAttributeSpec { Constraint *n = makeNode(Constraint); n->contype = CONSTR_UNIQUE; @@ -2418,9 +2438,12 @@ ConstraintElem: n->keys = $3; n->options = $5; n->indexspace = $6; + n->deferrable = ($7 & 1) != 0; + n->initdeferred = ($7 & 2) != 0; $$ = (Node *)n; } | PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace + ConstraintAttributeSpec { Constraint *n = makeNode(Constraint); n->contype = CONSTR_PRIMARY; @@ -2430,6 +2453,8 @@ ConstraintElem: n->keys = $4; n->options = $6; n->indexspace = $7; + n->deferrable = ($8 & 1) != 0; + n->initdeferred = ($8 & 2) != 0; $$ = (Node *)n; } | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index a5d805aa98..94c8c5977b 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -19,7 +19,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.23 2009/07/16 06:33:43 petere Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.24 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -33,6 +33,7 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/namespace.h" +#include "catalog/pg_constraint.h" #include "catalog/pg_opclass.h" #include "catalog/pg_type.h" #include "commands/defrem.h" @@ -801,15 +802,38 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx, /* * If the index is marked PRIMARY, it's certainly from a constraint; else, - * if it's not marked UNIQUE, it certainly isn't; else, we have to search - * pg_depend to see if there's an associated unique constraint. + * if it's not marked UNIQUE, it certainly isn't. If it is or might be + * from a constraint, we have to fetch the constraint to check for + * deferrability attributes. */ - if (index->primary) - index->isconstraint = true; - else if (!index->unique) - index->isconstraint = false; + if (index->primary || index->unique) + { + Oid constraintId = get_index_constraint(source_relid); + + if (OidIsValid(constraintId)) + { + HeapTuple ht_constr; + Form_pg_constraint conrec; + + ht_constr = SearchSysCache(CONSTROID, + ObjectIdGetDatum(constraintId), + 0, 0, 0); + if (!HeapTupleIsValid(ht_constr)) + elog(ERROR, "cache lookup failed for constraint %u", + constraintId); + conrec = (Form_pg_constraint) GETSTRUCT(ht_constr); + + index->isconstraint = true; + index->deferrable = conrec->condeferrable; + index->initdeferred = conrec->condeferred; + + ReleaseSysCache(ht_constr); + } + else + index->isconstraint = false; + } else - index->isconstraint = OidIsValid(get_index_constraint(source_relid)); + index->isconstraint = false; /* Get the index expressions, if any */ datum = SysCacheGetAttr(INDEXRELID, ht_idx, @@ -1039,7 +1063,9 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt) if (equal(index->indexParams, priorindex->indexParams) && equal(index->whereClause, priorindex->whereClause) && - strcmp(index->accessMethod, priorindex->accessMethod) == 0) + strcmp(index->accessMethod, priorindex->accessMethod) == 0 && + index->deferrable == priorindex->deferrable && + index->initdeferred == priorindex->initdeferred) { priorindex->unique |= index->unique; @@ -1092,6 +1118,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) */ } index->isconstraint = true; + index->deferrable = constraint->deferrable; + index->initdeferred = constraint->initdeferred; if (constraint->name != NULL) index->idxname = pstrdup(constraint->name); @@ -1853,8 +1881,9 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) * to attach constraint attributes to their primary constraint nodes * and detect inconsistent/misplaced constraint attributes. * - * NOTE: currently, attributes are only supported for FOREIGN KEY primary - * constraints, but someday they ought to be supported for other constraints. + * NOTE: currently, attributes are only supported for FOREIGN KEY, UNIQUE, + * and PRIMARY KEY constraints, but someday they ought to be supported + * for other constraint types. */ static void transformConstraintAttrs(List *constraintList) @@ -1864,6 +1893,13 @@ transformConstraintAttrs(List *constraintList) bool saw_initially = false; ListCell *clist; +#define SUPPORTS_ATTRS(node) \ + ((node) != NULL && \ + (IsA((node), FkConstraint) || \ + (IsA((node), Constraint) && \ + (((Constraint *) (node))->contype == CONSTR_PRIMARY || \ + ((Constraint *) (node))->contype == CONSTR_UNIQUE)))) + foreach(clist, constraintList) { Node *node = lfirst(clist); @@ -1882,8 +1918,7 @@ transformConstraintAttrs(List *constraintList) switch (con->contype) { case CONSTR_ATTR_DEFERRABLE: - if (lastprimarynode == NULL || - !IsA(lastprimarynode, FkConstraint)) + if (!SUPPORTS_ATTRS(lastprimarynode)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced DEFERRABLE clause"))); @@ -1892,11 +1927,14 @@ transformConstraintAttrs(List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"))); saw_deferrability = true; - ((FkConstraint *) lastprimarynode)->deferrable = true; + if (IsA(lastprimarynode, FkConstraint)) + ((FkConstraint *) lastprimarynode)->deferrable = true; + else + ((Constraint *) lastprimarynode)->deferrable = true; break; + case CONSTR_ATTR_NOT_DEFERRABLE: - if (lastprimarynode == NULL || - !IsA(lastprimarynode, FkConstraint)) + if (!SUPPORTS_ATTRS(lastprimarynode)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced NOT DEFERRABLE clause"))); @@ -1905,16 +1943,28 @@ transformConstraintAttrs(List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"))); saw_deferrability = true; - ((FkConstraint *) lastprimarynode)->deferrable = false; - if (saw_initially && - ((FkConstraint *) lastprimarynode)->initdeferred) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + if (IsA(lastprimarynode, FkConstraint)) + { + ((FkConstraint *) lastprimarynode)->deferrable = false; + if (saw_initially && + ((FkConstraint *) lastprimarynode)->initdeferred) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + } + else + { + ((Constraint *) lastprimarynode)->deferrable = false; + if (saw_initially && + ((Constraint *) lastprimarynode)->initdeferred) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + } break; + case CONSTR_ATTR_DEFERRED: - if (lastprimarynode == NULL || - !IsA(lastprimarynode, FkConstraint)) + if (!SUPPORTS_ATTRS(lastprimarynode)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced INITIALLY DEFERRED clause"))); @@ -1923,21 +1973,36 @@ transformConstraintAttrs(List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"))); saw_initially = true; - ((FkConstraint *) lastprimarynode)->initdeferred = true; /* * If only INITIALLY DEFERRED appears, assume DEFERRABLE */ - if (!saw_deferrability) - ((FkConstraint *) lastprimarynode)->deferrable = true; - else if (!((FkConstraint *) lastprimarynode)->deferrable) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + if (IsA(lastprimarynode, FkConstraint)) + { + ((FkConstraint *) lastprimarynode)->initdeferred = true; + + if (!saw_deferrability) + ((FkConstraint *) lastprimarynode)->deferrable = true; + else if (!((FkConstraint *) lastprimarynode)->deferrable) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + } + else + { + ((Constraint *) lastprimarynode)->initdeferred = true; + + if (!saw_deferrability) + ((Constraint *) lastprimarynode)->deferrable = true; + else if (!((Constraint *) lastprimarynode)->deferrable) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + } break; + case CONSTR_ATTR_IMMEDIATE: - if (lastprimarynode == NULL || - !IsA(lastprimarynode, FkConstraint)) + if (!SUPPORTS_ATTRS(lastprimarynode)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced INITIALLY IMMEDIATE clause"))); @@ -1946,8 +2011,12 @@ transformConstraintAttrs(List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"))); saw_initially = true; - ((FkConstraint *) lastprimarynode)->initdeferred = false; + if (IsA(lastprimarynode, FkConstraint)) + ((FkConstraint *) lastprimarynode)->initdeferred = false; + else + ((Constraint *) lastprimarynode)->initdeferred = false; break; + default: /* Otherwise it's not an attribute */ lastprimarynode = node; |