mirror of
https://github.com/postgres/postgres.git
synced 2025-09-08 00:47:37 +03:00
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
This commit is contained in:
@@ -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
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user