diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f2959af526..be5b037aa0 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1,4 +1,4 @@
-
+
@@ -1536,11 +1536,7 @@
bool
- True if this is a table and it has (or recently had) any
- indexes. This is set by CREATE INDEX, but
- not cleared immediately by DROP INDEX.
- VACUUM clears relhasindex> if it finds the
- table has no indexes
+ True if this is a table and it has (or recently had) any indexes
@@ -1617,6 +1613,17 @@
+
+ relhasexclusion
+ bool
+
+
+ For a table, true if the table has (or once had) any exclusion
+ constraints; for an index, true if the index supports an exclusion
+ constraint
+
+
+
relhasrulesbool
@@ -1680,6 +1687,17 @@
+
+
+ Several of the boolean flags in pg_class> are maintained
+ lazily: they are guaranteed to be true if that's the correct state, but
+ may not be reset to false immediately when the condition is no longer
+ true. For example, relhasindex> is set by
+ CREATE INDEX, but it is never cleared by
+ DROP INDEX. Instead, VACUUM clears
+ relhasindex> if it finds the table has no indexes. This
+ arrangement avoids race conditions and improves concurrency.
+
@@ -1690,11 +1708,12 @@
- The catalog pg_constraint stores check, primary key, unique, and foreign
- key constraints on tables. (Column constraints are not treated
- specially. Every column constraint is equivalent to some table
- constraint.) Not-null constraints are represented in the
- pg_attribute> catalog.
+ The catalog pg_constraint stores check, primary
+ key, unique, foreign key, and exclusion constraints on tables.
+ (Column constraints are not treated specially. Every column constraint is
+ equivalent to some table constraint.)
+ Not-null constraints are represented in the pg_attribute>
+ catalog, not here.
@@ -1739,7 +1758,8 @@
c> = check constraint,
f> = foreign key constraint,
p> = primary key constraint,
- u> = unique constraint
+ u> = unique constraint,
+ x> = exclusion constraint
@@ -1776,7 +1796,7 @@
oidpg_class.oidThe index supporting this constraint, if it's a unique, primary
- key, or foreign key constraint; else 0
+ key, foreign key, or exclusion constraint; else 0
@@ -1828,7 +1848,7 @@
bool
- This constraint is defined locally in the relation. Note that a
+ This constraint is defined locally for the relation. Note that a
constraint can be locally defined and inherited simultaneously
@@ -1838,7 +1858,8 @@
int4
- The number of direct ancestors this constraint has. A constraint with
+ The number of direct inheritance ancestors this constraint has.
+ A constraint with
a nonzero number of ancestors cannot be dropped nor renamed
@@ -1878,6 +1899,13 @@
If a foreign key, list of the equality operators for FK = FK comparisons
+
+ conexclop
+ oid[]
+ pg_operator.oid>
+ If an exclusion constraint, list of the per-column exclusion operators
+
+
conbintext
@@ -1895,6 +1923,16 @@
+
+ In the case of an exclusion constraint, conkey
+ is only useful for constraint elements that are simple column references.
+ For other cases, a zero appears in conkey
+ and the associated index must be consulted to discover the expression
+ that is constrained. (conkey thus has the
+ same contents as pg_index>.indkey> for the
+ index.)
+
+
consrc is not updated when referenced objects
@@ -1908,7 +1946,8 @@
pg_class.relchecks needs to agree with the
number of check-constraint entries found in this table for each
- relation.
+ relation. Also, pg_class.relhasexclusion must
+ be true if there are any exclusion-constraint entries for the relation.
diff --git a/doc/src/sgml/errcodes.sgml b/doc/src/sgml/errcodes.sgml
index e5597f2004..5819004f48 100644
--- a/doc/src/sgml/errcodes.sgml
+++ b/doc/src/sgml/errcodes.sgml
@@ -1,4 +1,4 @@
-
+
PostgreSQL Error Codes
@@ -640,6 +640,12 @@
check_violation
+
+23P01
+EXCLUSION VIOLATION
+exclusion_violation
+
+
Class 24 — Invalid Cursor State>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 8e4b1bbc31..e315843187 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1,5 +1,5 @@
@@ -24,7 +24,7 @@ PostgreSQL documentation
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name ( [
{ column_namedata_type [ DEFAULT default_expr ] [ column_constraint [ ... ] ]
| table_constraint
- | LIKE parent_table [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } ] ... }
+ | LIKE parent_table [ like_option ... ] }
[, ... ]
] )
[ INHERITS ( parent_table [, ... ] ) ]
@@ -37,9 +37,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE constraint_name ]
{ NOT NULL |
NULL |
+ CHECK ( expression ) |
UNIQUE index_parameters |
PRIMARY KEY index_parameters |
- CHECK ( expression ) |
REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
[ ON DELETE action ] [ ON UPDATE action ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -47,17 +47,26 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE table_constraint is:
[ CONSTRAINT constraint_name ]
-{ UNIQUE ( column_name [, ... ] ) index_parameters |
+{ CHECK ( expression ) |
+ UNIQUE ( column_name [, ... ] ) index_parameters |
PRIMARY KEY ( column_name [, ... ] ) index_parameters |
- CHECK ( expression ) |
+ EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] |
FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE action ] [ ON UPDATE action ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
-index_parameters in UNIQUE and PRIMARY KEY constraints are:
+and like_option is:
+
+{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+
+index_parameters in UNIQUE, PRIMARY KEY, and EXCLUDE constraints are:
[ WITH ( storage_parameter [= value] [, ... ] ) ]
[ USING INDEX TABLESPACE tablespace ]
+
+exclude_element in an EXCLUDE constraint is:
+
+{ column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
@@ -251,7 +260,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE
+
+ CHECK ( expression )
+
+
+ The CHECK> clause specifies an expression producing a
+ Boolean result which new or updated rows must satisfy for an
+ insert or update operation to succeed. Expressions evaluating
+ to TRUE or UNKNOWN succeed. Should any row of an insert or
+ update operation produce a FALSE result an error exception is
+ raised and the insert or update does not alter the database. A
+ check constraint specified as a column constraint should
+ reference that column's value only, while an expression
+ appearing in a table constraint can reference multiple columns.
+
+
+
+ Currently, CHECK expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row.
+
+
+
+
UNIQUE> (column constraint)UNIQUE ( column_name [, ... ] )> (table constraint)
@@ -406,29 +438,54 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE expression )
+ EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ]
- The CHECK> clause specifies an expression producing a
- Boolean result which new or updated rows must satisfy for an
- insert or update operation to succeed. Expressions evaluating
- to TRUE or UNKNOWN succeed. Should any row of an insert or
- update operation produce a FALSE result an error exception is
- raised and the insert or update does not alter the database. A
- check constraint specified as a column constraint should
- reference that column's value only, while an expression
- appearing in a table constraint can reference multiple columns.
+ The EXCLUDE> clause defines an exclusion
+ constraint, which guarantees that if
+ any two rows are compared on the specified column(s) or
+ expression(s) using the specified operator(s), not all of these
+ comparisons will return TRUE>. If all of the
+ specified operators test for equality, this is equivalent to a
+ UNIQUE> constraint, although an ordinary unique constraint
+ will be faster. However, exclusion constraints can specify
+ constraints that are more general than simple equality.
+ For example, you can specify a constraint that
+ no two rows in the table contain overlapping circles
+ (see ) by using the
+ &&> operator.
- Currently, CHECK expressions cannot contain
- subqueries nor refer to variables other than columns of the
- current row.
+ Exclusion constraints are implemented using
+ an index, so each specified operator must be associated with an
+ appropriate operator class
+ (see ) for the index access
+ method index_method>.
+ The operators are required to be commutative.
+ Each exclude_element
+ can optionally specify an operator class and/or ordering options;
+ these are described fully under
+ .
+
+
+
+ The access method must support amgettuple> (see ); at present this means GIN>
+ cannot be used. Although it's allowed, there is little point in using
+ btree or hash indexes with an exclusion constraint, because this
+ does nothing that an ordinary unique constraint doesn't do better.
+ So in practice the access method will always be GiST>.
+
+
+
+ The predicate> allows you to specify an
+ exclusion constraint on a subset of the table; internally this creates a
+ partial index. Note that parentheses are required around the predicate.
-
REFERENCES reftable [ ( refcolumn ) ] [ MATCH matchtype ] [ ON DELETE action ] [ ON UPDATE action ] (column constraint)
@@ -557,7 +614,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE command).
NOT DEFERRABLE is the default.
- Currently, only UNIQUE>, PRIMARY KEY>, and
+ Currently, only UNIQUE>, PRIMARY KEY>,
+ EXCLUDE>, and
REFERENCES> (foreign key) constraints accept this
clause. NOT NULL> and CHECK> constraints are not
deferrable.
@@ -695,8 +753,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE is consulted, or
if the table is temporary.
@@ -715,8 +773,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE . The storage parameters currently
available for tables are listed below. For each parameter, unless noted,
@@ -1099,6 +1158,18 @@ WITH (fillfactor=70);
+
+ Create table circles> with an exclusion
+ constraint that prevents any two circles from overlapping:
+
+
+CREATE TABLE circles (
+ c circle,
+ EXCLUDE USING gist (c WITH &&)
+);
+
+
+
Create table cinemas> in tablespace diskvol1>:
@@ -1194,6 +1265,15 @@ CREATE TABLE cinemas (
+
+ EXCLUDE Constraint
+
+
+ The EXCLUDE> constraint type is a
+ PostgreSQL extension.
+
+
+
NULLConstraint
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 860e576603..f07996a3d4 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/index/genam.c,v 1.76 2009/08/01 20:59:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/index/genam.c,v 1.77 2009/12/07 05:22:21 tgl Exp $
*
* NOTES
* many of the old access method routines have been turned into
@@ -137,21 +137,18 @@ IndexScanEnd(IndexScanDesc scan)
*
* Construct a string describing the contents of an index entry, in the
* form "(key_name, ...)=(key_value, ...)". This is currently used
- * only for building unique-constraint error messages, but we don't want
- * to hardwire the spelling of the messages here.
+ * for building unique-constraint and exclusion-constraint error messages.
+ *
+ * The passed-in values/nulls arrays are the "raw" input to the index AM,
+ * e.g. results of FormIndexDatum --- this is not necessarily what is stored
+ * in the index, but it's what the user perceives to be stored.
*/
char *
BuildIndexValueDescription(Relation indexRelation,
Datum *values, bool *isnull)
{
- /*
- * XXX for the moment we use the index's tupdesc as a guide to the
- * datatypes of the values. This is okay for btree indexes but is in
- * fact the wrong thing in general. This will have to be fixed if we
- * are ever to support non-btree unique indexes.
- */
- TupleDesc tupdesc = RelationGetDescr(indexRelation);
StringInfoData buf;
+ int natts = indexRelation->rd_rel->relnatts;
int i;
initStringInfo(&buf);
@@ -159,7 +156,7 @@ BuildIndexValueDescription(Relation indexRelation,
pg_get_indexdef_columns(RelationGetRelid(indexRelation),
true));
- for (i = 0; i < tupdesc->natts; i++)
+ for (i = 0; i < natts; i++)
{
char *val;
@@ -170,7 +167,17 @@ BuildIndexValueDescription(Relation indexRelation,
Oid foutoid;
bool typisvarlena;
- getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
+ /*
+ * The provided data is not necessarily of the type stored in
+ * the index; rather it is of the index opclass's input type.
+ * So look at rd_opcintype not the index tupdesc.
+ *
+ * Note: this is a bit shaky for opclasses that have pseudotype
+ * input types such as ANYARRAY or RECORD. Currently, the
+ * typoutput functions associated with the pseudotypes will
+ * work okay, but we might have to try harder in future.
+ */
+ getTypeOutputInfo(indexRelation->rd_opcintype[i],
&foutoid, &typisvarlena);
val = OidOutputFunctionCall(foutoid, values[i]);
}
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 9d6f854d12..18affeb098 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.100 2009/10/05 19:24:34 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.101 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -267,7 +267,7 @@ Boot_DeclareIndexStmt:
$8,
NULL,
$10,
- NULL, NIL,
+ NULL, NIL, NIL,
false, false, false, false, false,
false, false, true, false, false);
do_end();
@@ -285,7 +285,7 @@ Boot_DeclareUniqueIndexStmt:
$9,
NULL,
$11,
- NULL, NIL,
+ NULL, NIL, NIL,
true, false, false, false, false,
false, false, true, false, false);
do_end();
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 89991ea2fb..5fa07d6c2a 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.253 2009/09/27 01:32:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.254 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1100,6 +1100,10 @@ index_register(Oid heap,
newind->il_info->ii_Predicate = (List *)
copyObject(indexInfo->ii_Predicate);
newind->il_info->ii_PredicateState = NIL;
+ /* no exclusion constraints at bootstrap time, so no need to copy */
+ Assert(indexInfo->ii_ExclusionOps == NULL);
+ Assert(indexInfo->ii_ExclusionProcs == NULL);
+ Assert(indexInfo->ii_ExclusionStrats == NULL);
newind->il_next = ILHead;
ILHead = newind;
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a013306284..8b8fcea734 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.360 2009/10/05 19:24:35 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.361 2009/12/07 05:22:21 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -678,6 +678,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
+ values[Anum_pg_class_relhasexclusion - 1] = BoolGetDatum(rd_rel->relhasexclusion);
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
@@ -1748,9 +1749,10 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
' ',
' ',
' ',
- expr, /* Tree form check constraint */
- ccbin, /* Binary form check constraint */
- ccsrc, /* Source form check constraint */
+ NULL, /* not an exclusion constraint */
+ expr, /* Tree form of check constraint */
+ ccbin, /* Binary form of check constraint */
+ ccsrc, /* Source form of check constraint */
is_local, /* conislocal */
inhcount); /* coninhcount */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 482d40542d..6564c05c9e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.324 2009/11/20 20:38:09 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.325 2009/12/07 05:22:21 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -93,8 +93,12 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
bool primary,
bool immediate,
bool isvalid);
-static void index_update_stats(Relation rel, bool hasindex, bool isprimary,
+static void index_update_stats(Relation rel,
+ bool hasindex, bool isprimary, bool hasexclusion,
Oid reltoastidxid, double reltuples);
+static void IndexCheckExclusion(Relation heapRelation,
+ Relation indexRelation,
+ IndexInfo *indexInfo);
static bool validate_index_callback(ItemPointer itemptr, void *opaque);
static void validate_index_heapscan(Relation heapRelation,
Relation indexRelation,
@@ -505,7 +509,7 @@ UpdateIndexRelation(Oid indexoid,
* will be marked "invalid" and the caller must take additional steps
* to fix it up.
*
- * Returns OID of the created index.
+ * Returns the OID of the created index.
*/
Oid
index_create(Oid heapRelationId,
@@ -530,9 +534,12 @@ index_create(Oid heapRelationId,
Relation indexRelation;
TupleDesc indexTupDesc;
bool shared_relation;
+ bool is_exclusion;
Oid namespaceId;
int i;
+ is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
+
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
/*
@@ -573,6 +580,15 @@ index_create(Oid heapRelationId,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("concurrent index creation on system catalog tables is not supported")));
+ /*
+ * This case is currently not supported, but there's no way to ask for
+ * it in the grammar anyway, so it can't happen.
+ */
+ if (concurrent && is_exclusion)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg_internal("concurrent index creation for exclusion constraints is not supported")));
+
/*
* We cannot allow indexing a shared relation after initdb (because
* there's no way to make the entry in other databases' pg_class).
@@ -658,6 +674,7 @@ index_create(Oid heapRelationId,
indexRelation->rd_rel->relam = accessMethodObjectId;
indexRelation->rd_rel->relkind = RELKIND_INDEX;
indexRelation->rd_rel->relhasoids = false;
+ indexRelation->rd_rel->relhasexclusion = is_exclusion;
/*
* store index's pg_class entry
@@ -728,14 +745,17 @@ index_create(Oid heapRelationId,
constraintType = CONSTRAINT_PRIMARY;
else if (indexInfo->ii_Unique)
constraintType = CONSTRAINT_UNIQUE;
+ else if (is_exclusion)
+ constraintType = CONSTRAINT_EXCLUSION;
else
{
- elog(ERROR, "constraint must be PRIMARY or UNIQUE");
+ elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE");
constraintType = 0; /* keep compiler quiet */
}
- /* Shouldn't have any expressions */
- if (indexInfo->ii_Expressions)
+ /* primary/unique constraints shouldn't have any expressions */
+ if (indexInfo->ii_Expressions &&
+ constraintType != CONSTRAINT_EXCLUSION)
elog(ERROR, "constraints cannot have index expressions");
conOid = CreateConstraintEntry(indexRelationName,
@@ -757,6 +777,7 @@ index_create(Oid heapRelationId,
' ',
' ',
' ',
+ indexInfo->ii_ExclusionOps,
NULL, /* no check constraint */
NULL,
NULL,
@@ -925,6 +946,7 @@ index_create(Oid heapRelationId,
index_update_stats(heapRelation,
true,
isprimary,
+ is_exclusion,
InvalidOid,
heapRelation->rd_rel->reltuples);
/* Make the above update visible */
@@ -1080,6 +1102,21 @@ BuildIndexInfo(Relation index)
ii->ii_Predicate = RelationGetIndexPredicate(index);
ii->ii_PredicateState = NIL;
+ /* fetch exclusion constraint info if any */
+ if (index->rd_rel->relhasexclusion)
+ {
+ RelationGetExclusionInfo(index,
+ &ii->ii_ExclusionOps,
+ &ii->ii_ExclusionProcs,
+ &ii->ii_ExclusionStrats);
+ }
+ else
+ {
+ ii->ii_ExclusionOps = NULL;
+ ii->ii_ExclusionProcs = NULL;
+ ii->ii_ExclusionStrats = NULL;
+ }
+
/* other info */
ii->ii_Unique = indexStruct->indisunique;
ii->ii_ReadyForInserts = indexStruct->indisready;
@@ -1177,6 +1214,7 @@ FormIndexDatum(IndexInfo *indexInfo,
*
* hasindex: set relhasindex to this value
* isprimary: if true, set relhaspkey true; else no change
+ * hasexclusion: if true, set relhasexclusion true; else no change
* reltoastidxid: if not InvalidOid, set reltoastidxid to this value;
* else no change
* reltuples: set reltuples to this value
@@ -1192,7 +1230,8 @@ FormIndexDatum(IndexInfo *indexInfo,
* expect a relcache flush to occur after REINDEX.
*/
static void
-index_update_stats(Relation rel, bool hasindex, bool isprimary,
+index_update_stats(Relation rel,
+ bool hasindex, bool isprimary, bool hasexclusion,
Oid reltoastidxid, double reltuples)
{
BlockNumber relpages = RelationGetNumberOfBlocks(rel);
@@ -1231,8 +1270,9 @@ index_update_stats(Relation rel, bool hasindex, bool isprimary,
* It is safe to use a non-transactional update even though our
* transaction could still fail before committing. Setting relhasindex
* true is safe even if there are no indexes (VACUUM will eventually fix
- * it), and of course the relpages and reltuples counts are correct (or at
- * least more so than the old values) regardless.
+ * it), likewise for relhaspkey and relhasexclusion. And of course the
+ * relpages and reltuples counts are correct (or at least more so than the
+ * old values) regardless.
*/
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
@@ -1287,6 +1327,14 @@ index_update_stats(Relation rel, bool hasindex, bool isprimary,
dirty = true;
}
}
+ if (hasexclusion)
+ {
+ if (!rd_rel->relhasexclusion)
+ {
+ rd_rel->relhasexclusion = true;
+ dirty = true;
+ }
+ }
if (OidIsValid(reltoastidxid))
{
Assert(rd_rel->relkind == RELKIND_TOASTVALUE);
@@ -1461,6 +1509,13 @@ index_build(Relation heapRelation,
PointerGetDatum(indexInfo)));
Assert(PointerIsValid(stats));
+ /*
+ * If it's for an exclusion constraint, make a second pass over the
+ * heap to verify that the constraint is satisfied.
+ */
+ if (indexInfo->ii_ExclusionOps != NULL)
+ IndexCheckExclusion(heapRelation, indexRelation, indexInfo);
+
/* Restore userid */
SetUserIdAndContext(save_userid, save_secdefcxt);
@@ -1499,11 +1554,13 @@ index_build(Relation heapRelation,
index_update_stats(heapRelation,
true,
isprimary,
+ (indexInfo->ii_ExclusionOps != NULL),
(heapRelation->rd_rel->relkind == RELKIND_TOASTVALUE) ?
RelationGetRelid(indexRelation) : InvalidOid,
stats->heap_tuples);
index_update_stats(indexRelation,
+ false,
false,
false,
InvalidOid,
@@ -1522,15 +1579,14 @@ index_build(Relation heapRelation,
* is scanned to find tuples that should be entered into the index. Each
* such tuple is passed to the AM's callback routine, which does the right
* things to add it to the new index. After we return, the AM's index
- * build procedure does whatever cleanup is needed; in particular, it should
- * close the heap and index relations.
+ * build procedure does whatever cleanup it needs.
*
* The total count of heap tuples is returned. This is for updating pg_class
- * statistics. (It's annoying not to be able to do that here, but we can't
- * do it until after the relation is closed.) Note that the index AM itself
- * must keep track of the number of index tuples; we don't do so here because
- * the AM might reject some of the tuples for its own reasons, such as being
- * unable to store NULLs.
+ * statistics. (It's annoying not to be able to do that here, but we want
+ * to merge that update with others; see index_update_stats.) Note that the
+ * index AM itself must keep track of the number of index tuples; we don't do
+ * so here because the AM might reject some of the tuples for its own reasons,
+ * such as being unable to store NULLs.
*
* A side effect is to set indexInfo->ii_BrokenHotChain to true if we detect
* any potentially broken HOT chains. Currently, we set this if there are
@@ -1898,6 +1954,106 @@ IndexBuildHeapScan(Relation heapRelation,
}
+/*
+ * IndexCheckExclusion - verify that a new exclusion constraint is satisfied
+ *
+ * When creating an exclusion constraint, we first build the index normally
+ * and then rescan the heap to check for conflicts. We assume that we only
+ * need to validate tuples that are live according to SnapshotNow, and that
+ * these were correctly indexed even in the presence of broken HOT chains.
+ * This should be OK since we are holding at least ShareLock on the table,
+ * meaning there can be no uncommitted updates from other transactions.
+ * (Note: that wouldn't necessarily work for system catalogs, since many
+ * operations release write lock early on the system catalogs.)
+ */
+static void
+IndexCheckExclusion(Relation heapRelation,
+ Relation indexRelation,
+ IndexInfo *indexInfo)
+{
+ HeapScanDesc scan;
+ HeapTuple heapTuple;
+ Datum values[INDEX_MAX_KEYS];
+ bool isnull[INDEX_MAX_KEYS];
+ List *predicate;
+ TupleTableSlot *slot;
+ EState *estate;
+ ExprContext *econtext;
+
+ /*
+ * Need an EState for evaluation of index expressions and partial-index
+ * predicates. Also a slot to hold the current tuple.
+ */
+ estate = CreateExecutorState();
+ econtext = GetPerTupleExprContext(estate);
+ slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /* Set up execution state for predicate, if any. */
+ predicate = (List *)
+ ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
+ estate);
+
+ /*
+ * Scan all live tuples in the base relation.
+ */
+ scan = heap_beginscan_strat(heapRelation, /* relation */
+ SnapshotNow, /* snapshot */
+ 0, /* number of keys */
+ NULL, /* scan key */
+ true, /* buffer access strategy OK */
+ true); /* syncscan OK */
+
+ while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ CHECK_FOR_INTERRUPTS();
+
+ MemoryContextReset(econtext->ecxt_per_tuple_memory);
+
+ /* Set up for predicate or expression evaluation */
+ ExecStoreTuple(heapTuple, slot, InvalidBuffer, false);
+
+ /*
+ * In a partial index, ignore tuples that don't satisfy the predicate.
+ */
+ if (predicate != NIL)
+ {
+ if (!ExecQual(predicate, econtext, false))
+ continue;
+ }
+
+ /*
+ * Extract index column values, including computing expressions.
+ */
+ FormIndexDatum(indexInfo,
+ slot,
+ estate,
+ values,
+ isnull);
+
+ /*
+ * Check that this tuple has no conflicts.
+ */
+ check_exclusion_constraint(heapRelation,
+ indexRelation, indexInfo,
+ &(heapTuple->t_self), values, isnull,
+ estate, true, false);
+ }
+
+ heap_endscan(scan);
+
+ ExecDropSingleTupleTableSlot(slot);
+
+ FreeExecutorState(estate);
+
+ /* These may have been pointing to the now-gone estate */
+ indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_PredicateState = NIL;
+}
+
+
/*
* validate_index - support code for concurrent index builds
*
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 65fe6d5efd..cfe95cf226 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.118 2009/07/29 20:56:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.119 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -30,7 +30,8 @@
* In the current implementation, we share code for opening/closing the
* indexes with execUtils.c. But we do not use ExecInsertIndexTuples,
* because we don't want to create an EState. This implies that we
- * do not support partial or expressional indexes on system catalogs.
+ * do not support partial or expressional indexes on system catalogs,
+ * nor can we support generalized exclusion constraints.
* This could be fixed with localized changes here if we wanted to pay
* the extra overhead of building an EState.
*/
@@ -111,10 +112,12 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
/*
* Expressional and partial indexes on system catalogs are not
- * supported
+ * supported, nor exclusion constraints, nor deferred uniqueness
*/
Assert(indexInfo->ii_Expressions == NIL);
Assert(indexInfo->ii_Predicate == NIL);
+ Assert(indexInfo->ii_ExclusionOps == NULL);
+ Assert(relationDescs[i]->rd_index->indimmediate);
/*
* FormIndexDatum fills in its values and isnull parameters with the
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index e973b0abc6..78532f52bb 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -4,7 +4,7 @@
*
* Copyright (c) 2003-2009, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.59 2009/12/05 21:43:35 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.60 2009/12/07 05:22:21 tgl Exp $
*/
/*
@@ -1666,6 +1666,7 @@ CREATE VIEW table_constraints AS
WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
AND c.conrelid = r.oid
+ AND c.contype <> 'x' -- ignore nonstandard exclusion constraints
AND r.relkind = 'r'
AND (NOT pg_is_other_temp_schema(nr.oid))
AND (pg_has_role(r.relowner, 'USAGE')
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index d92bbf84cc..02cf5f1400 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.49 2009/10/13 00:53:07 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.50 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -59,6 +59,7 @@ CreateConstraintEntry(const char *constraintName,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
+ const Oid *exclOp,
Node *conExpr,
const char *conBin,
const char *conSrc,
@@ -75,6 +76,7 @@ CreateConstraintEntry(const char *constraintName,
ArrayType *conpfeqopArray;
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
+ ArrayType *conexclopArray;
NameData cname;
int i;
ObjectAddress conobject;
@@ -130,6 +132,19 @@ CreateConstraintEntry(const char *constraintName,
conffeqopArray = NULL;
}
+ if (exclOp != NULL)
+ {
+ Datum *opdatums;
+
+ opdatums = (Datum *) palloc(constraintNKeys * sizeof(Datum));
+ for (i = 0; i < constraintNKeys; i++)
+ opdatums[i] = ObjectIdGetDatum(exclOp[i]);
+ conexclopArray = construct_array(opdatums, constraintNKeys,
+ OIDOID, sizeof(Oid), true, 'i');
+ }
+ else
+ conexclopArray = NULL;
+
/* initialize nulls and values */
for (i = 0; i < Natts_pg_constraint; i++)
{
@@ -177,6 +192,11 @@ CreateConstraintEntry(const char *constraintName,
else
nulls[Anum_pg_constraint_conffeqop - 1] = true;
+ if (conexclopArray)
+ values[Anum_pg_constraint_conexclop - 1] = PointerGetDatum(conexclopArray);
+ else
+ nulls[Anum_pg_constraint_conexclop - 1] = true;
+
/*
* initialize the binary form of the check constraint.
*/
@@ -321,6 +341,14 @@ CreateConstraintEntry(const char *constraintName,
}
}
+ /*
+ * We don't bother to register dependencies on the exclusion operators
+ * of an exclusion constraint. We assume they are members of the opclass
+ * supporting the index, so there's an indirect dependency via that.
+ * (This would be pretty dicey for cross-type operators, but exclusion
+ * operators can never be cross-type.)
+ */
+
if (conExpr != NULL)
{
/*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index dfc178724e..05f3bb4969 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.20 2009/10/05 19:24:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.21 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -239,6 +239,9 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_Predicate = NIL;
indexInfo->ii_PredicateState = NIL;
+ indexInfo->ii_ExclusionOps = NULL;
+ indexInfo->ii_ExclusionProcs = NULL;
+ indexInfo->ii_ExclusionStrats = NULL;
indexInfo->ii_Unique = true;
indexInfo->ii_ReadyForInserts = true;
indexInfo->ii_Concurrent = false;
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index 42d4d4e1f9..41adf87147 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/constraint.c,v 1.1 2009/07/29 20:56:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/constraint.c,v 1.2 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -23,9 +23,12 @@
/*
* unique_key_recheck - trigger function to do a deferred uniqueness check.
*
+ * This now also does deferred exclusion-constraint checks, so the name is
+ * somewhat historical.
+ *
* This is invoked as an AFTER ROW trigger for both INSERT and UPDATE,
* for any rows recorded as potentially violating a deferrable unique
- * constraint.
+ * or exclusion constraint.
*
* This may be an end-of-statement check, a commit-time check, or a
* check triggered by a SET CONSTRAINTS command.
@@ -85,7 +88,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
* because this trigger gets queued only in response to index insertions;
* which means it does not get queued for HOT updates. The row we are
* called for might now be dead, but have a live HOT child, in which case
- * we still need to make the uniqueness check. Therefore we have to use
+ * we still need to make the check. Therefore we have to use
* heap_hot_search, not just HeapTupleSatisfiesVisibility as is done in
* the comparable test in RI_FKey_check.
*
@@ -123,9 +126,11 @@ unique_key_recheck(PG_FUNCTION_ARGS)
/*
* Typically the index won't have expressions, but if it does we need
- * an EState to evaluate them.
+ * an EState to evaluate them. We need it for exclusion constraints
+ * too, even if they are just on simple columns.
*/
- if (indexInfo->ii_Expressions != NIL)
+ if (indexInfo->ii_Expressions != NIL ||
+ indexInfo->ii_ExclusionOps != NULL)
{
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
@@ -141,19 +146,37 @@ unique_key_recheck(PG_FUNCTION_ARGS)
* Note: if the index uses functions that are not as immutable as they
* are supposed to be, this could produce an index tuple different from
* the original. The index AM can catch such errors by verifying that
- * it finds a matching index entry with the tuple's TID.
+ * it finds a matching index entry with the tuple's TID. For exclusion
+ * constraints we check this in check_exclusion_constraint().
*/
FormIndexDatum(indexInfo, slot, estate, values, isnull);
/*
- * Now do the uniqueness check. This is not a real insert; it is a
- * check that the index entry that has already been inserted is unique.
+ * Now do the appropriate check.
*/
- index_insert(indexRel, values, isnull, &(new_row->t_self),
- trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
+ if (indexInfo->ii_ExclusionOps == NULL)
+ {
+ /*
+ * Note: this is not a real insert; it is a check that the index entry
+ * that has already been inserted is unique.
+ */
+ index_insert(indexRel, values, isnull, &(new_row->t_self),
+ trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
+ }
+ else
+ {
+ /*
+ * For exclusion constraints we just do the normal check, but now
+ * it's okay to throw error.
+ */
+ check_exclusion_constraint(trigdata->tg_relation, indexRel, indexInfo,
+ &(new_row->t_self), values, isnull,
+ estate, false, false);
+ }
/*
- * If that worked, then this index entry is unique, and we are done.
+ * If that worked, then this index entry is unique or non-excluded,
+ * and we are done.
*/
if (estate != NULL)
FreeExecutorState(estate);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 96272ab998..00786442b3 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.187 2009/07/29 20:56:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.188 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -25,6 +25,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_opfamily.h"
#include "catalog/pg_tablespace.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
@@ -36,6 +37,7 @@
#include "optimizer/clauses.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
+#include "parser/parse_oper.h"
#include "parser/parsetree.h"
#include "storage/lmgr.h"
#include "storage/proc.h"
@@ -58,6 +60,7 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
Oid *classOidP,
int16 *colOptionP,
List *attList,
+ List *exclusionOpNames,
Oid relId,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
@@ -83,6 +86,8 @@ static bool relationHasPrimaryKey(Relation rel);
* to index on.
* 'predicate': the partial-index condition, or NULL if none.
* 'options': reloptions from WITH (in list-of-DefElem form).
+ * 'exclusionOpNames': list of names of exclusion-constraint operators,
+ * or NIL if not an exclusion constraint.
* 'unique': make the index enforce uniqueness.
* 'primary': mark the index as a primary key in the catalogs.
* 'isconstraint': index is for a PRIMARY KEY or UNIQUE constraint,
@@ -106,6 +111,7 @@ DefineIndex(RangeVar *heapRelation,
List *attributeList,
Expr *predicate,
List *options,
+ List *exclusionOpNames,
bool unique,
bool primary,
bool isconstraint,
@@ -247,10 +253,21 @@ DefineIndex(RangeVar *heapRelation,
if (indexRelationName == NULL)
{
if (primary)
+ {
indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
NULL,
"pkey",
namespaceId);
+ }
+ else if (exclusionOpNames != NIL)
+ {
+ IndexElem *iparam = (IndexElem *) linitial(attributeList);
+
+ indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ iparam->name,
+ "exclusion",
+ namespaceId);
+ }
else
{
IndexElem *iparam = (IndexElem *) linitial(attributeList);
@@ -303,6 +320,11 @@ DefineIndex(RangeVar *heapRelation,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support multicolumn indexes",
accessMethodName)));
+ if (exclusionOpNames != NIL && !OidIsValid(accessMethodForm->amgettuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("access method \"%s\" does not support exclusion constraints",
+ accessMethodName)));
amcanorder = accessMethodForm->amcanorder;
amoptions = accessMethodForm->amoptions;
@@ -418,6 +440,9 @@ DefineIndex(RangeVar *heapRelation,
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_Predicate = make_ands_implicit(predicate);
indexInfo->ii_PredicateState = NIL;
+ indexInfo->ii_ExclusionOps = NULL;
+ indexInfo->ii_ExclusionProcs = NULL;
+ indexInfo->ii_ExclusionStrats = NULL;
indexInfo->ii_Unique = unique;
/* In a concurrent build, mark it not-ready-for-inserts */
indexInfo->ii_ReadyForInserts = !concurrent;
@@ -427,7 +452,8 @@ DefineIndex(RangeVar *heapRelation,
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
ComputeIndexAttrs(indexInfo, classObjectId, coloptions, attributeList,
- relationId, accessMethodName, accessMethodId,
+ exclusionOpNames, relationId,
+ accessMethodName, accessMethodId,
amcanorder, isconstraint);
/*
@@ -435,11 +461,27 @@ DefineIndex(RangeVar *heapRelation,
* error checks)
*/
if (isconstraint && !quiet)
+ {
+ const char *constraint_type;
+
+ if (primary)
+ constraint_type = "PRIMARY KEY";
+ else if (unique)
+ constraint_type = "UNIQUE";
+ else if (exclusionOpNames != NIL)
+ constraint_type = "EXCLUDE";
+ else
+ {
+ elog(ERROR, "unknown constraint type");
+ constraint_type = NULL; /* keep compiler quiet */
+ }
+
ereport(NOTICE,
(errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
- primary ? "PRIMARY KEY" : "UNIQUE",
+ constraint_type,
indexRelationName, RelationGetRelationName(rel))));
+ }
/* save lockrelid and locktag for below, then close rel */
heaprelid = rel->rd_lockInfo.lockRelId;
@@ -799,21 +841,38 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
Oid *classOidP,
int16 *colOptionP,
List *attList, /* list of IndexElem's */
+ List *exclusionOpNames,
Oid relId,
char *accessMethodName,
Oid accessMethodId,
bool amcanorder,
bool isconstraint)
{
- ListCell *rest;
- int attn = 0;
+ ListCell *nextExclOp;
+ ListCell *lc;
+ int attn;
+
+ /* Allocate space for exclusion operator info, if needed */
+ if (exclusionOpNames)
+ {
+ int ncols = list_length(attList);
+
+ Assert(list_length(exclusionOpNames) == ncols);
+ indexInfo->ii_ExclusionOps = (Oid *) palloc(sizeof(Oid) * ncols);
+ indexInfo->ii_ExclusionProcs = (Oid *) palloc(sizeof(Oid) * ncols);
+ indexInfo->ii_ExclusionStrats = (uint16 *) palloc(sizeof(uint16) * ncols);
+ nextExclOp = list_head(exclusionOpNames);
+ }
+ else
+ nextExclOp = NULL;
/*
* process attributeList
*/
- foreach(rest, attList)
+ attn = 0;
+ foreach(lc, attList)
{
- IndexElem *attribute = (IndexElem *) lfirst(rest);
+ IndexElem *attribute = (IndexElem *) lfirst(lc);
Oid atttype;
/*
@@ -897,6 +956,71 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
accessMethodName,
accessMethodId);
+ /*
+ * Identify the exclusion operator, if any.
+ */
+ if (nextExclOp)
+ {
+ List *opname = (List *) lfirst(nextExclOp);
+ Oid opid;
+ Oid opfamily;
+ int strat;
+
+ /*
+ * Find the operator --- it must accept the column datatype
+ * without runtime coercion (but binary compatibility is OK)
+ */
+ opid = compatible_oper_opid(opname, atttype, atttype, false);
+
+ /*
+ * Only allow commutative operators to be used in exclusion
+ * constraints. If X conflicts with Y, but Y does not conflict
+ * with X, bad things will happen.
+ */
+ if (get_commutator(opid) != opid)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("operator %s is not commutative",
+ format_operator(opid)),
+ errdetail("Only commutative operators can be used in exclusion constraints.")));
+
+ /*
+ * Operator must be a member of the right opfamily, too
+ */
+ opfamily = get_opclass_family(classOidP[attn]);
+ strat = get_op_opfamily_strategy(opid, opfamily);
+ if (strat == 0)
+ {
+ HeapTuple opftuple;
+ Form_pg_opfamily opfform;
+
+ /*
+ * attribute->opclass might not explicitly name the opfamily,
+ * so fetch the name of the selected opfamily for use in the
+ * error message.
+ */
+ opftuple = SearchSysCache(OPFAMILYOID,
+ ObjectIdGetDatum(opfamily),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(opftuple))
+ elog(ERROR, "cache lookup failed for opfamily %u",
+ opfamily);
+ opfform = (Form_pg_opfamily) GETSTRUCT(opftuple);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("operator %s is not a member of operator family \"%s\"",
+ format_operator(opid),
+ NameStr(opfform->opfname)),
+ errdetail("The exclusion operator must be related to the index operator class for the constraint.")));
+ }
+
+ indexInfo->ii_ExclusionOps[attn] = opid;
+ indexInfo->ii_ExclusionProcs[attn] = get_opcode(opid);
+ indexInfo->ii_ExclusionStrats[attn] = strat;
+ nextExclOp = lnext(nextExclOp);
+ }
+
/*
* Set up the per-column options (indoption field). For now, this is
* zero for any un-ordered index, while ordered indexes have DESC and
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1de1950453..d10a7490b7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.306 2009/11/20 20:38:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.307 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -4603,6 +4603,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->options,
+ stmt->excludeOpNames,
stmt->unique,
stmt->primary,
stmt->isconstraint,
@@ -5035,6 +5036,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkconstraint->fk_matchtype,
+ NULL, /* no exclusion constraint */
NULL, /* no check constraint */
NULL,
NULL,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 4f997a0dce..54d8220cb3 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.138 2009/10/08 02:39:19 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.139 2009/12/07 05:22:21 tgl Exp $
*
* DESCRIPTION
* The "DefineFoo" routines take the parse tree and pick out the
@@ -984,6 +984,12 @@ DefineDomain(CreateDomainStmt *stmt)
errmsg("primary key constraints not possible for domains")));
break;
+ case CONSTR_EXCLUSION:
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("exclusion constraints not possible for domains")));
+ break;
+
case CONSTR_FOREIGN:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -1868,6 +1874,12 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
errmsg("primary key constraints not possible for domains")));
break;
+ case CONSTR_EXCLUSION:
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("exclusion constraints not possible for domains")));
+ break;
+
case CONSTR_FOREIGN:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -2297,9 +2309,10 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
' ',
' ',
' ',
- expr, /* Tree form check constraint */
- ccbin, /* Binary form check constraint */
- ccsrc, /* Source form check constraint */
+ NULL, /* not an exclusion constraint */
+ expr, /* Tree form of check constraint */
+ ccbin, /* Binary form of check constraint */
+ ccsrc, /* Source form of check constraint */
true, /* is local */
0); /* inhcount */
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index eeee7654e0..83b8c18e8e 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.396 2009/11/16 21:32:06 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.397 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -763,7 +763,8 @@ vac_update_relstats(Relation relation,
/*
* If we have discovered that there are no indexes, then there's no
- * primary key either. This could be done more thoroughly...
+ * primary key either, nor any exclusion constraints. This could be done
+ * more thoroughly...
*/
if (!hasindex)
{
@@ -772,6 +773,11 @@ vac_update_relstats(Relation relation,
pgcform->relhaspkey = false;
dirty = true;
}
+ if (pgcform->relhasexclusion && pgcform->relkind != RELKIND_INDEX)
+ {
+ pgcform->relhasexclusion = false;
+ dirty = true;
+ }
}
/* We also clear relhasrules and relhastriggers if needed */
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index e1aa8ce8ac..ac993465a5 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.166 2009/11/20 20:38:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.167 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -44,16 +44,22 @@
#include "access/genam.h"
#include "access/heapam.h"
+#include "access/relscan.h"
+#include "access/transam.h"
#include "catalog/index.h"
#include "executor/execdebug.h"
#include "nodes/nodeFuncs.h"
#include "parser/parsetree.h"
+#include "storage/lmgr.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
#include "utils/tqual.h"
static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
+static bool index_recheck_constraint(Relation index, Oid *constr_procs,
+ Datum *existing_values, bool *existing_isnull,
+ Datum *new_values);
static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
@@ -959,8 +965,8 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
* doesn't provide the functionality needed by the
* executor.. -cim 9/27/89
*
- * This returns a list of OIDs for any unique indexes
- * whose constraint check was deferred and which had
+ * This returns a list of index OIDs for any unique or exclusion
+ * constraints that are deferred and that had
* potential (unconfirmed) conflicts.
*
* CAUTION: this must not be called for a HOT update.
@@ -1011,7 +1017,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
Relation indexRelation = relationDescs[i];
IndexInfo *indexInfo;
IndexUniqueCheck checkUnique;
- bool isUnique;
+ bool satisfiesConstraint;
if (indexRelation == NULL)
continue;
@@ -1056,7 +1062,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
isnull);
/*
- * The index AM does the rest, including uniqueness checking.
+ * The index AM does the actual insertion, plus uniqueness checking.
*
* For an immediate-mode unique index, we just tell the index AM to
* throw error if not unique.
@@ -1076,7 +1082,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
else
checkUnique = UNIQUE_CHECK_PARTIAL;
- isUnique =
+ satisfiesConstraint =
index_insert(indexRelation, /* index relation */
values, /* array of index Datums */
isnull, /* null flags */
@@ -1084,11 +1090,36 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
heapRelation, /* heap relation */
checkUnique); /* type of uniqueness check to do */
- if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
+ /*
+ * If the index has an associated exclusion constraint, check that.
+ * This is simpler than the process for uniqueness checks since we
+ * always insert first and then check. If the constraint is deferred,
+ * we check now anyway, but don't throw error on violation; instead
+ * we'll queue a recheck event.
+ *
+ * An index for an exclusion constraint can't also be UNIQUE (not an
+ * essential property, we just don't allow it in the grammar), so no
+ * need to preserve the prior state of satisfiesConstraint.
+ */
+ if (indexInfo->ii_ExclusionOps != NULL)
+ {
+ bool errorOK = !indexRelation->rd_index->indimmediate;
+
+ satisfiesConstraint =
+ check_exclusion_constraint(heapRelation,
+ indexRelation, indexInfo,
+ tupleid, values, isnull,
+ estate, false, errorOK);
+ }
+
+ if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
+ indexInfo->ii_ExclusionOps != NULL) &&
+ !satisfiesConstraint)
{
/*
- * The tuple potentially violates the uniqueness constraint,
- * so make a note of the index so that we can re-check it later.
+ * The tuple potentially violates the uniqueness or exclusion
+ * constraint, so make a note of the index so that we can re-check
+ * it later.
*/
result = lappend_oid(result, RelationGetRelid(indexRelation));
}
@@ -1097,6 +1128,221 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
return result;
}
+/*
+ * Check for violation of an exclusion constraint
+ *
+ * heap: the table containing the new tuple
+ * index: the index supporting the exclusion constraint
+ * indexInfo: info about the index, including the exclusion properties
+ * tupleid: heap TID of the new tuple we have just inserted
+ * values, isnull: the *index* column values computed for the new tuple
+ * estate: an EState we can do evaluation in
+ * newIndex: if true, we are trying to build a new index (this affects
+ * only the wording of error messages)
+ * errorOK: if true, don't throw error for violation
+ *
+ * Returns true if OK, false if actual or potential violation
+ *
+ * When errorOK is true, we report violation without waiting to see if any
+ * concurrent transaction has committed or not; so the violation is only
+ * potential, and the caller must recheck sometime later. This behavior
+ * is convenient for deferred exclusion checks; we need not bother queuing
+ * a deferred event if there is definitely no conflict at insertion time.
+ *
+ * When errorOK is false, we'll throw error on violation, so a false result
+ * is impossible.
+ */
+bool
+check_exclusion_constraint(Relation heap, Relation index, IndexInfo *indexInfo,
+ ItemPointer tupleid, Datum *values, bool *isnull,
+ EState *estate, bool newIndex, bool errorOK)
+{
+ Oid *constr_procs = indexInfo->ii_ExclusionProcs;
+ uint16 *constr_strats = indexInfo->ii_ExclusionStrats;
+ int index_natts = index->rd_index->indnatts;
+ IndexScanDesc index_scan;
+ HeapTuple tup;
+ ScanKeyData scankeys[INDEX_MAX_KEYS];
+ SnapshotData DirtySnapshot;
+ int i;
+ bool conflict;
+ bool found_self;
+ TupleTableSlot *existing_slot;
+
+ /*
+ * If any of the input values are NULL, the constraint check is assumed
+ * to pass (i.e., we assume the operators are strict).
+ */
+ for (i = 0; i < index_natts; i++)
+ {
+ if (isnull[i])
+ return true;
+ }
+
+ /*
+ * Search the tuples that are in the index for any violations,
+ * including tuples that aren't visible yet.
+ */
+ InitDirtySnapshot(DirtySnapshot);
+
+ for (i = 0; i < index_natts; i++)
+ {
+ ScanKeyInit(&scankeys[i],
+ i + 1,
+ constr_strats[i],
+ constr_procs[i],
+ values[i]);
+ }
+
+ /* Need a TupleTableSlot to put existing tuples in */
+ existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+
+ /*
+ * May have to restart scan from this point if a potential
+ * conflict is found.
+ */
+retry:
+ conflict = false;
+ found_self = false;
+ index_scan = index_beginscan(heap, index, &DirtySnapshot,
+ index_natts, scankeys);
+
+ while ((tup = index_getnext(index_scan,
+ ForwardScanDirection)) != NULL)
+ {
+ TransactionId xwait;
+ Datum existing_values[INDEX_MAX_KEYS];
+ bool existing_isnull[INDEX_MAX_KEYS];
+ char *error_new;
+ char *error_existing;
+
+ /*
+ * Ignore the entry for the tuple we're trying to check.
+ */
+ if (ItemPointerEquals(tupleid, &tup->t_self))
+ {
+ if (found_self) /* should not happen */
+ elog(ERROR, "found self tuple multiple times in index \"%s\"",
+ RelationGetRelationName(index));
+ found_self = true;
+ continue;
+ }
+
+ /*
+ * Extract the index column values and isnull flags from the existing
+ * tuple.
+ */
+ ExecStoreTuple(tup, existing_slot, InvalidBuffer, false);
+ FormIndexDatum(indexInfo, existing_slot, estate,
+ existing_values, existing_isnull);
+
+ /* If lossy indexscan, must recheck the condition */
+ if (index_scan->xs_recheck)
+ {
+ if (!index_recheck_constraint(index,
+ constr_procs,
+ existing_values,
+ existing_isnull,
+ values))
+ continue; /* tuple doesn't actually match, so no conflict */
+ }
+
+ /*
+ * At this point we have either a conflict or a potential conflict.
+ * If we're not supposed to raise error, just return the fact of the
+ * potential conflict without waiting to see if it's real.
+ */
+ if (errorOK)
+ {
+ conflict = true;
+ break;
+ }
+
+ /*
+ * If an in-progress transaction is affecting the visibility of this
+ * tuple, we need to wait for it to complete and then recheck. For
+ * simplicity we do rechecking by just restarting the whole scan ---
+ * this case probably doesn't happen often enough to be worth trying
+ * harder, and anyway we don't want to hold any index internal locks
+ * while waiting.
+ */
+ xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ DirtySnapshot.xmin : DirtySnapshot.xmax;
+
+ if (TransactionIdIsValid(xwait))
+ {
+ index_endscan(index_scan);
+ XactLockTableWait(xwait);
+ goto retry;
+ }
+
+ /*
+ * We have a definite conflict. Report it.
+ */
+ error_new = BuildIndexValueDescription(index, values, isnull);
+ error_existing = BuildIndexValueDescription(index, existing_values,
+ existing_isnull);
+ if (newIndex)
+ ereport(ERROR,
+ (errcode(ERRCODE_EXCLUSION_VIOLATION),
+ errmsg("could not create exclusion constraint \"%s\"",
+ RelationGetRelationName(index)),
+ errdetail("Key %s conflicts with key %s.",
+ error_new, error_existing)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_EXCLUSION_VIOLATION),
+ errmsg("conflicting key value violates exclusion constraint \"%s\"",
+ RelationGetRelationName(index)),
+ errdetail("Key %s conflicts with existing key %s.",
+ error_new, error_existing)));
+ }
+
+ index_endscan(index_scan);
+
+ /*
+ * We should have found our tuple in the index, unless we exited the
+ * loop early because of conflict. Complain if not.
+ */
+ if (!found_self && !conflict)
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("failed to re-find tuple within index \"%s\"",
+ RelationGetRelationName(index)),
+ errhint("This may be because of a non-immutable index expression.")));
+
+ ExecDropSingleTupleTableSlot(existing_slot);
+
+ return !conflict;
+}
+
+/*
+ * Check existing tuple's index values to see if it really matches the
+ * exclusion condition against the new_values. Returns true if conflict.
+ */
+static bool
+index_recheck_constraint(Relation index, Oid *constr_procs,
+ Datum *existing_values, bool *existing_isnull,
+ Datum *new_values)
+{
+ int index_natts = index->rd_index->indnatts;
+ int i;
+
+ for (i = 0; i < index_natts; i++)
+ {
+ /* Assume the exclusion operators are strict */
+ if (existing_isnull[i])
+ return false;
+
+ if (!DatumGetBool(OidFunctionCall2(constr_procs[i],
+ existing_values[i],
+ new_values[i])))
+ return false;
+ }
+
+ return true;
+}
+
/*
* UpdateChangedParamSet
* Add changed parameters to a plan node's chgParam set
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 35656d8656..c0bb01f141 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.452 2009/11/20 20:38:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.453 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2157,8 +2157,11 @@ _copyConstraint(Constraint *from)
COPY_NODE_FIELD(raw_expr);
COPY_STRING_FIELD(cooked_expr);
COPY_NODE_FIELD(keys);
+ COPY_NODE_FIELD(exclusions);
COPY_NODE_FIELD(options);
COPY_STRING_FIELD(indexspace);
+ COPY_STRING_FIELD(access_method);
+ COPY_NODE_FIELD(where_clause);
COPY_NODE_FIELD(pktable);
COPY_NODE_FIELD(fk_attrs);
COPY_NODE_FIELD(pk_attrs);
@@ -2595,6 +2598,7 @@ _copyIndexStmt(IndexStmt *from)
COPY_NODE_FIELD(indexParams);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(whereClause);
+ COPY_NODE_FIELD(excludeOpNames);
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index eeda5799ee..7a8bedf1cb 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -22,7 +22,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.374 2009/11/20 20:38:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.375 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1179,6 +1179,7 @@ _equalIndexStmt(IndexStmt *a, IndexStmt *b)
COMPARE_NODE_FIELD(indexParams);
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(whereClause);
+ COMPARE_NODE_FIELD(excludeOpNames);
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
@@ -2103,8 +2104,11 @@ _equalConstraint(Constraint *a, Constraint *b)
COMPARE_NODE_FIELD(raw_expr);
COMPARE_STRING_FIELD(cooked_expr);
COMPARE_NODE_FIELD(keys);
+ COMPARE_NODE_FIELD(exclusions);
COMPARE_NODE_FIELD(options);
COMPARE_STRING_FIELD(indexspace);
+ COMPARE_STRING_FIELD(access_method);
+ COMPARE_NODE_FIELD(where_clause);
COMPARE_NODE_FIELD(pktable);
COMPARE_NODE_FIELD(fk_attrs);
COMPARE_NODE_FIELD(pk_attrs);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6909675950..80a2fa7640 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.373 2009/11/28 00:46:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.374 2009/12/07 05:22:22 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@@ -1798,6 +1798,7 @@ _outIndexStmt(StringInfo str, IndexStmt *node)
WRITE_NODE_FIELD(indexParams);
WRITE_NODE_FIELD(options);
WRITE_NODE_FIELD(whereClause);
+ WRITE_NODE_FIELD(excludeOpNames);
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
@@ -2378,6 +2379,7 @@ _outConstraint(StringInfo str, Constraint *node)
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
+ /* access_method and where_clause not currently used */
break;
case CONSTR_UNIQUE:
@@ -2385,6 +2387,16 @@ _outConstraint(StringInfo str, Constraint *node)
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
+ /* access_method and where_clause not currently used */
+ break;
+
+ case CONSTR_EXCLUSION:
+ appendStringInfo(str, "EXCLUSION");
+ WRITE_NODE_FIELD(exclusions);
+ WRITE_NODE_FIELD(options);
+ WRITE_STRING_FIELD(indexspace);
+ WRITE_STRING_FIELD(access_method);
+ WRITE_NODE_FIELD(where_clause);
break;
case CONSTR_FOREIGN:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index cab6d915c8..8dca257360 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.694 2009/11/20 20:38:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.695 2009/12/07 05:22:22 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -352,6 +352,8 @@ static TypeName *TableFuncTypeName(List *columns);
%type def_arg columnElem where_clause where_or_current_clause
a_expr b_expr c_expr func_expr AexprConst indirection_el
columnref in_expr having_clause func_table array_expr
+ ExclusionWhereClause
+%type ExclusionConstraintList ExclusionConstraintElem
%type func_arg_list
%type func_arg_expr
%type row type_list array_expr_list
@@ -475,7 +477,7 @@ static TypeName *TableFuncTypeName(List *columns);
DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
- EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
+ EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
@@ -1591,8 +1593,16 @@ alter_table_cmds:
;
alter_table_cmd:
- /* ALTER TABLE ADD [COLUMN] */
- ADD_P opt_column columnDef
+ /* ALTER TABLE ADD */
+ ADD_P columnDef
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_AddColumn;
+ n->def = $2;
+ $$ = (Node *)n;
+ }
+ /* ALTER TABLE ADD COLUMN */
+ | ADD_P COLUMN columnDef
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_AddColumn;
@@ -2487,6 +2497,22 @@ ConstraintElem:
n->initdeferred = ($8 & 2) != 0;
$$ = (Node *)n;
}
+ | EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
+ opt_definition OptConsTableSpace ExclusionWhereClause
+ ConstraintAttributeSpec
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_EXCLUSION;
+ n->location = @1;
+ n->access_method = $2;
+ n->exclusions = $4;
+ n->options = $6;
+ n->indexspace = $7;
+ n->where_clause = $8;
+ n->deferrable = ($9 & 1) != 0;
+ n->initdeferred = ($9 & 2) != 0;
+ $$ = (Node *)n;
+ }
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
opt_column_list key_match key_actions ConstraintAttributeSpec
{
@@ -2544,6 +2570,28 @@ key_match: MATCH FULL
}
;
+ExclusionConstraintList:
+ ExclusionConstraintElem { $$ = list_make1($1); }
+ | ExclusionConstraintList ',' ExclusionConstraintElem
+ { $$ = lappend($1, $3); }
+ ;
+
+ExclusionConstraintElem: index_elem WITH any_operator
+ {
+ $$ = list_make2($1, $3);
+ }
+ /* allow OPERATOR() decoration for the benefit of ruleutils.c */
+ | index_elem WITH OPERATOR '(' any_operator ')'
+ {
+ $$ = list_make2($1, $5);
+ }
+ ;
+
+ExclusionWhereClause:
+ WHERE '(' a_expr ')' { $$ = $3; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
/*
* We combine the update and delete actions into one value temporarily
* for simplicity of parsing, and then break them down again in the
@@ -10619,6 +10667,7 @@ unreserved_keyword:
| ENCRYPTED
| ENUM_P
| ESCAPE
+ | EXCLUDE
| EXCLUDING
| EXCLUSIVE
| EXECUTE
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 18f7e5c556..f21fcb6104 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.30 2009/11/13 23:49:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.31 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -35,6 +35,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "commands/comment.h"
#include "commands/defrem.h"
@@ -456,6 +457,10 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
saw_default = true;
break;
+ case CONSTR_CHECK:
+ cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
+ break;
+
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
if (constraint->keys == NIL)
@@ -463,8 +468,9 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
- case CONSTR_CHECK:
- cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
+ case CONSTR_EXCLUSION:
+ /* grammar does not allow EXCLUDE as a column constraint */
+ elog(ERROR, "column exclusion constraints are not supported");
break;
case CONSTR_FOREIGN:
@@ -503,6 +509,7 @@ transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
{
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
+ case CONSTR_EXCLUSION:
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -814,7 +821,7 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
/*
* chooseIndexName
*
- * Set name to unnamed index. See also the same logic in DefineIndex.
+ * Set name for unnamed index. See also the same logic in DefineIndex.
*/
static char *
chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt)
@@ -828,6 +835,13 @@ chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt)
return ChooseRelationName(relation->relname, NULL,
"pkey", namespaceId);
}
+ else if (index_stmt->excludeOpNames != NIL)
+ {
+ IndexElem *iparam = (IndexElem *) linitial(index_stmt->indexParams);
+
+ return ChooseRelationName(relation->relname, iparam->name,
+ "exclusion", namespaceId);
+ }
else
{
IndexElem *iparam = (IndexElem *) linitial(index_stmt->indexParams);
@@ -880,7 +894,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
/* Fetch pg_am tuple for source index from relcache entry */
amrec = source_idx->rd_am;
- /* Must get indclass the hard way, since it's not stored in relcache */
+ /* Extract indclass from the pg_index tuple */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
Anum_pg_index_indclass, &isnull);
Assert(!isnull);
@@ -905,12 +919,12 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
index->idxname = NULL;
/*
- * If the index is marked PRIMARY, it's certainly from a constraint; else,
- * 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 the index is marked PRIMARY or has an exclusion condition, it's
+ * certainly from a constraint; else, if it's not marked UNIQUE, it
+ * certainly isn't. If it is or might be from a constraint, we have to
+ * fetch the pg_constraint record.
*/
- if (index->primary || index->unique)
+ if (index->primary || index->unique || idxrelrec->relhasexclusion)
{
Oid constraintId = get_index_constraint(source_relid);
@@ -931,6 +945,53 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
index->deferrable = conrec->condeferrable;
index->initdeferred = conrec->condeferred;
+ /* If it's an exclusion constraint, we need the operator names */
+ if (idxrelrec->relhasexclusion)
+ {
+ Datum *elems;
+ int nElems;
+ int i;
+
+ Assert(conrec->contype == CONSTRAINT_EXCLUSION);
+ /* Extract operator OIDs from the pg_constraint tuple */
+ datum = SysCacheGetAttr(CONSTROID, ht_constr,
+ Anum_pg_constraint_conexclop,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null conexclop for constraint %u",
+ constraintId);
+
+ deconstruct_array(DatumGetArrayTypeP(datum),
+ OIDOID, sizeof(Oid), true, 'i',
+ &elems, NULL, &nElems);
+
+ for (i = 0; i < nElems; i++)
+ {
+ Oid operid = DatumGetObjectId(elems[i]);
+ HeapTuple opertup;
+ Form_pg_operator operform;
+ char *oprname;
+ char *nspname;
+ List *namelist;
+
+ opertup = SearchSysCache(OPEROID,
+ ObjectIdGetDatum(operid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(opertup))
+ elog(ERROR, "cache lookup failed for operator %u",
+ operid);
+ operform = (Form_pg_operator) GETSTRUCT(opertup);
+ oprname = pstrdup(NameStr(operform->oprname));
+ /* For simplicity we always schema-qualify the op name */
+ nspname = get_namespace_name(operform->oprnamespace);
+ namelist = list_make2(makeString(nspname),
+ makeString(oprname));
+ index->excludeOpNames = lappend(index->excludeOpNames,
+ namelist);
+ ReleaseSysCache(opertup);
+ }
+ }
+
ReleaseSysCache(ht_constr);
}
else
@@ -1087,7 +1148,7 @@ get_opclass(Oid opclass, Oid actual_datatype)
/*
* transformIndexConstraints
- * Handle UNIQUE and PRIMARY KEY constraints, which create indexes.
+ * Handle UNIQUE, PRIMARY KEY, EXCLUDE constraints, which create indexes.
* We also merge in any index definitions arising from
* LIKE ... INCLUDING INDEXES.
*/
@@ -1100,8 +1161,9 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
/*
* 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.
+ * KEY, mark each column as NOT NULL and create an index. For UNIQUE or
+ * EXCLUDE, create an index as for PRIMARY KEY, but do not insist on NOT
+ * NULL.
*/
foreach(lc, cxt->ixconstraints)
{
@@ -1109,7 +1171,8 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
Assert(IsA(constraint, Constraint));
Assert(constraint->contype == CONSTR_PRIMARY ||
- constraint->contype == CONSTR_UNIQUE);
+ constraint->contype == CONSTR_UNIQUE ||
+ constraint->contype == CONSTR_EXCLUSION);
index = transformIndexConstraint(constraint, cxt);
@@ -1167,6 +1230,7 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
if (equal(index->indexParams, priorindex->indexParams) &&
equal(index->whereClause, priorindex->whereClause) &&
+ equal(index->excludeOpNames, priorindex->excludeOpNames) &&
strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
index->deferrable == priorindex->deferrable &&
index->initdeferred == priorindex->initdeferred)
@@ -1193,19 +1257,18 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
/*
* transformIndexConstraint
- * Transform one UNIQUE or PRIMARY KEY constraint for
+ * Transform one UNIQUE, PRIMARY KEY, or EXCLUDE constraint for
* transformIndexConstraints.
*/
static IndexStmt *
transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
{
IndexStmt *index;
- ListCell *keys;
- IndexElem *iparam;
+ ListCell *lc;
index = makeNode(IndexStmt);
- index->unique = true;
+ index->unique = (constraint->contype != CONSTR_EXCLUSION);
index->primary = (constraint->contype == CONSTR_PRIMARY);
if (index->primary)
{
@@ -1231,25 +1294,55 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->idxname = NULL; /* DefineIndex will choose name */
index->relation = cxt->relation;
- index->accessMethod = DEFAULT_INDEX_TYPE;
+ index->accessMethod = constraint->access_method ? constraint->access_method : DEFAULT_INDEX_TYPE;
index->options = constraint->options;
index->tableSpace = constraint->indexspace;
+ index->whereClause = constraint->where_clause;
index->indexParams = NIL;
- index->whereClause = NULL;
+ index->excludeOpNames = NIL;
index->concurrent = false;
/*
+ * If it's an EXCLUDE constraint, the grammar returns a list of pairs
+ * of IndexElems and operator names. We have to break that apart into
+ * separate lists.
+ */
+ if (constraint->contype == CONSTR_EXCLUSION)
+ {
+ foreach(lc, constraint->exclusions)
+ {
+ List *pair = (List *) lfirst(lc);
+ IndexElem *elem;
+ List *opname;
+
+ Assert(list_length(pair) == 2);
+ elem = (IndexElem *) linitial(pair);
+ Assert(IsA(elem, IndexElem));
+ opname = (List *) lsecond(pair);
+ Assert(IsA(opname, List));
+
+ index->indexParams = lappend(index->indexParams, elem);
+ index->excludeOpNames = lappend(index->excludeOpNames, opname);
+ }
+
+ return index;
+ }
+
+ /*
+ * For UNIQUE and PRIMARY KEY, we just have a list of column names.
+ *
* Make sure referenced keys exist. If we are making a PRIMARY KEY index,
* also make sure they are NOT NULL, if possible. (Although we could leave
* it to DefineIndex to mark the columns NOT NULL, it's more efficient to
* get it right the first time.)
*/
- foreach(keys, constraint->keys)
+ foreach(lc, constraint->keys)
{
- char *key = strVal(lfirst(keys));
+ char *key = strVal(lfirst(lc));
bool found = false;
ColumnDef *column = NULL;
ListCell *columns;
+ IndexElem *iparam;
foreach(columns, cxt->columns)
{
@@ -2000,6 +2093,7 @@ transformConstraintAttrs(ParseState *pstate, List *constraintList)
((node) != NULL && \
((node)->contype == CONSTR_PRIMARY || \
(node)->contype == CONSTR_UNIQUE || \
+ (node)->contype == CONSTR_EXCLUSION || \
(node)->contype == CONSTR_FOREIGN))
foreach(clist, constraintList)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f4263d39e6..1a7640b350 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.320 2009/12/01 02:31:12 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.321 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -797,6 +797,7 @@ ProcessUtility(Node *parsetree,
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->options,
+ stmt->excludeOpNames,
stmt->unique,
stmt->primary,
stmt->isconstraint,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fc5885e68e..71e614d700 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.315 2009/11/20 20:38:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.316 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -144,6 +144,7 @@ static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
+ const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
@@ -705,6 +706,7 @@ pg_get_indexdef(PG_FUNCTION_ARGS)
Oid indexrelid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, 0,
+ NULL,
false, false, 0)));
}
@@ -718,6 +720,7 @@ pg_get_indexdef_ext(PG_FUNCTION_ARGS)
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, colno,
+ NULL,
colno != 0,
false,
prettyFlags)));
@@ -727,7 +730,7 @@ pg_get_indexdef_ext(PG_FUNCTION_ARGS)
char *
pg_get_indexdef_string(Oid indexrelid)
{
- return pg_get_indexdef_worker(indexrelid, 0, false, true, 0);
+ return pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, 0);
}
/* Internal version that just reports the column definitions */
@@ -737,14 +740,23 @@ pg_get_indexdef_columns(Oid indexrelid, bool pretty)
int prettyFlags;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
- return pg_get_indexdef_worker(indexrelid, 0, true, false, prettyFlags);
+ return pg_get_indexdef_worker(indexrelid, 0, NULL, true, false, prettyFlags);
}
+/*
+ * Internal workhorse to decompile an index definition.
+ *
+ * This is now used for exclusion constraints as well: if excludeOps is not
+ * NULL then it points to an array of exclusion operator OIDs.
+ */
static char *
pg_get_indexdef_worker(Oid indexrelid, int colno,
+ const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags)
{
+ /* might want a separate isConstraint parameter later */
+ bool isConstraint = (excludeOps != NULL);
HeapTuple ht_idx;
HeapTuple ht_idxrel;
HeapTuple ht_am;
@@ -842,11 +854,17 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
initStringInfo(&buf);
if (!attrsOnly)
- appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
- idxrec->indisunique ? "UNIQUE " : "",
- quote_identifier(NameStr(idxrelrec->relname)),
- generate_relation_name(indrelid, NIL),
- quote_identifier(NameStr(amrec->amname)));
+ {
+ if (!isConstraint)
+ appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
+ idxrec->indisunique ? "UNIQUE " : "",
+ quote_identifier(NameStr(idxrelrec->relname)),
+ generate_relation_name(indrelid, NIL),
+ quote_identifier(NameStr(amrec->amname)));
+ else /* currently, must be EXCLUDE constraint */
+ appendStringInfo(&buf, "EXCLUDE USING %s (",
+ quote_identifier(NameStr(amrec->amname)));
+ }
/*
* Report the indexed attributes
@@ -917,6 +935,13 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
appendStringInfo(&buf, " NULLS FIRST");
}
}
+
+ /* Add the exclusion operator if relevant */
+ if (excludeOps != NULL)
+ appendStringInfo(&buf, " WITH %s",
+ generate_operator_name(excludeOps[keyno],
+ keycoltype,
+ keycoltype));
}
}
@@ -943,8 +968,12 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
tblspc = get_rel_tablespace(indexrelid);
if (OidIsValid(tblspc))
+ {
+ if (isConstraint)
+ appendStringInfoString(&buf, " USING INDEX");
appendStringInfo(&buf, " TABLESPACE %s",
quote_identifier(get_tablespace_name(tblspc)));
+ }
}
/*
@@ -968,7 +997,10 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
/* Deparse */
str = deparse_expression_pretty(node, context, false, false,
prettyFlags, 0);
- appendStringInfo(&buf, " WHERE %s", str);
+ if (isConstraint)
+ appendStringInfo(&buf, " WHERE (%s)", str);
+ else
+ appendStringInfo(&buf, " WHERE %s", str);
}
}
@@ -1244,6 +1276,43 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
break;
}
+ case CONSTRAINT_EXCLUSION:
+ {
+ Oid indexOid = conForm->conindid;
+ Datum val;
+ bool isnull;
+ Datum *elems;
+ int nElems;
+ int i;
+ Oid *operators;
+
+ /* Extract operator OIDs from the pg_constraint tuple */
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conexclop,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null conexclop for constraint %u",
+ constraintId);
+
+ deconstruct_array(DatumGetArrayTypeP(val),
+ OIDOID, sizeof(Oid), true, 'i',
+ &elems, NULL, &nElems);
+
+ operators = (Oid *) palloc(nElems * sizeof(Oid));
+ for (i = 0; i < nElems; i++)
+ operators[i] = DatumGetObjectId(elems[i]);
+
+ /* pg_get_indexdef_worker does the rest */
+ /* suppress tablespace because pg_dump wants it that way */
+ appendStringInfoString(&buf,
+ pg_get_indexdef_worker(indexOid,
+ 0,
+ operators,
+ false,
+ false,
+ prettyFlags));
+ break;
+ }
default:
elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
break;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3a66e2aa8e..82c4bbc01a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.292 2009/09/26 23:08:22 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.293 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -60,9 +60,11 @@
#include "storage/fd.h"
#include "storage/lmgr.h"
#include "storage/smgr.h"
+#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
#include "utils/resowner.h"
@@ -1079,10 +1081,13 @@ RelationInitIndexAccessInfo(Relation relation)
memcpy(relation->rd_indoption, indoption->values, natts * sizeof(int16));
/*
- * expressions and predicate cache will be filled later
+ * expressions, predicate, exclusion caches will be filled later
*/
relation->rd_indexprs = NIL;
relation->rd_indpred = NIL;
+ relation->rd_exclops = NULL;
+ relation->rd_exclprocs = NULL;
+ relation->rd_exclstrats = NULL;
relation->rd_amcache = NULL;
}
@@ -3453,6 +3458,130 @@ RelationGetIndexAttrBitmap(Relation relation)
return indexattrs;
}
+/*
+ * RelationGetExclusionInfo -- get info about index's exclusion constraint
+ *
+ * This should be called only for an index that is known to have an
+ * associated exclusion constraint. It returns arrays (palloc'd in caller's
+ * context) of the exclusion operator OIDs, their underlying functions'
+ * OIDs, and their strategy numbers in the index's opclasses. We cache
+ * all this information since it requires a fair amount of work to get.
+ */
+void
+RelationGetExclusionInfo(Relation indexRelation,
+ Oid **operators,
+ Oid **procs,
+ uint16 **strategies)
+{
+ int ncols = indexRelation->rd_rel->relnatts;
+ Oid *ops;
+ Oid *funcs;
+ uint16 *strats;
+ Relation conrel;
+ SysScanDesc conscan;
+ ScanKeyData skey[1];
+ HeapTuple htup;
+ bool found;
+ MemoryContext oldcxt;
+ int i;
+
+ /* Allocate result space in caller context */
+ *operators = ops = (Oid *) palloc(sizeof(Oid) * ncols);
+ *procs = funcs = (Oid *) palloc(sizeof(Oid) * ncols);
+ *strategies = strats = (uint16 *) palloc(sizeof(uint16) * ncols);
+
+ /* Quick exit if we have the data cached already */
+ if (indexRelation->rd_exclstrats != NULL)
+ {
+ memcpy(ops, indexRelation->rd_exclops, sizeof(Oid) * ncols);
+ memcpy(funcs, indexRelation->rd_exclprocs, sizeof(Oid) * ncols);
+ memcpy(strats, indexRelation->rd_exclstrats, sizeof(uint16) * ncols);
+ return;
+ }
+
+ /*
+ * Search pg_constraint for the constraint associated with the index.
+ * To make this not too painfully slow, we use the index on conrelid;
+ * that will hold the parent relation's OID not the index's own OID.
+ */
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(indexRelation->rd_index->indrelid));
+
+ conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ SnapshotNow, 1, skey);
+ found = false;
+
+ while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ {
+ Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(htup);
+ Datum val;
+ bool isnull;
+ ArrayType *arr;
+ int nelem;
+
+ /* We want the exclusion constraint owning the index */
+ if (conform->contype != CONSTRAINT_EXCLUSION ||
+ conform->conindid != RelationGetRelid(indexRelation))
+ continue;
+
+ /* There should be only one */
+ if (found)
+ elog(ERROR, "unexpected exclusion constraint record found for rel %s",
+ RelationGetRelationName(indexRelation));
+ found = true;
+
+ /* Extract the operator OIDS from conexclop */
+ val = fastgetattr(htup,
+ Anum_pg_constraint_conexclop,
+ conrel->rd_att, &isnull);
+ if (isnull)
+ elog(ERROR, "null conexclop for rel %s",
+ RelationGetRelationName(indexRelation));
+
+ arr = DatumGetArrayTypeP(val); /* ensure not toasted */
+ nelem = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ nelem != ncols ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != OIDOID)
+ elog(ERROR, "conexclop is not a 1-D Oid array");
+
+ memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * ncols);
+ }
+
+ systable_endscan(conscan);
+ heap_close(conrel, AccessShareLock);
+
+ if (!found)
+ elog(ERROR, "exclusion constraint record missing for rel %s",
+ RelationGetRelationName(indexRelation));
+
+ /* We need the func OIDs and strategy numbers too */
+ for (i = 0; i < ncols; i++)
+ {
+ funcs[i] = get_opcode(ops[i]);
+ strats[i] = get_op_opfamily_strategy(ops[i],
+ indexRelation->rd_opfamily[i]);
+ /* shouldn't fail, since it was checked at index creation */
+ if (strats[i] == InvalidStrategy)
+ elog(ERROR, "could not find strategy for operator %u in family %u",
+ ops[i], indexRelation->rd_opfamily[i]);
+ }
+
+ /* Save a copy of the results in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(indexRelation->rd_indexcxt);
+ indexRelation->rd_exclops = (Oid *) palloc(sizeof(Oid) * ncols);
+ indexRelation->rd_exclprocs = (Oid *) palloc(sizeof(Oid) * ncols);
+ indexRelation->rd_exclstrats = (uint16 *) palloc(sizeof(uint16) * ncols);
+ memcpy(indexRelation->rd_exclops, ops, sizeof(Oid) * ncols);
+ memcpy(indexRelation->rd_exclprocs, funcs, sizeof(Oid) * ncols);
+ memcpy(indexRelation->rd_exclstrats, strats, sizeof(uint16) * ncols);
+ MemoryContextSwitchTo(oldcxt);
+}
+
/*
* load_relcache_init_file, write_relcache_init_file
@@ -3768,13 +3897,16 @@ load_relcache_init_file(bool shared)
* format is complex and subject to change). They must be rebuilt if
* needed by RelationCacheInitializePhase3. This is not expected to
* be a big performance hit since few system catalogs have such. Ditto
- * for index expressions and predicates.
+ * for index expressions, predicates, and exclusion info.
*/
rel->rd_rules = NULL;
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
+ rel->rd_exclops = NULL;
+ rel->rd_exclprocs = NULL;
+ rel->rd_exclstrats = NULL;
/*
* Reset transient-state fields in the relcache entry
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 5b3b36757d..a753976139 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12,7 +12,7 @@
* by PostgreSQL
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.553 2009/11/20 20:38:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.554 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -3680,6 +3680,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
i_condeferred,
i_contableoid,
i_conoid,
+ i_condef,
i_tablespace,
i_options;
int ntups;
@@ -3710,7 +3711,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
* assume an index won't have more than one internal dependency.
*/
resetPQExpBuffer(query);
- if (g_fout->remoteVersion >= 80200)
+ if (g_fout->remoteVersion >= 80500)
{
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
@@ -3722,6 +3723,35 @@ getIndexes(TableInfo tblinfo[], int numTables)
"c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
+ "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
+ "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
+ "array_to_string(t.reloptions, ', ') AS options "
+ "FROM pg_catalog.pg_index i "
+ "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
+ "LEFT JOIN pg_catalog.pg_depend d "
+ "ON (d.classid = t.tableoid "
+ "AND d.objid = t.oid "
+ "AND d.deptype = 'i') "
+ "LEFT JOIN pg_catalog.pg_constraint c "
+ "ON (d.refclassid = c.tableoid "
+ "AND d.refobjid = c.oid) "
+ "WHERE i.indrelid = '%u'::pg_catalog.oid "
+ "ORDER BY indexname",
+ tbinfo->dobj.catId.oid);
+ }
+ else if (g_fout->remoteVersion >= 80200)
+ {
+ appendPQExpBuffer(query,
+ "SELECT t.tableoid, t.oid, "
+ "t.relname AS indexname, "
+ "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+ "t.relnatts AS indnkeys, "
+ "i.indkey, i.indisclustered, "
+ "c.contype, c.conname, "
+ "c.condeferrable, c.condeferred, "
+ "c.tableoid AS contableoid, "
+ "c.oid AS conoid, "
+ "null AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
"array_to_string(t.reloptions, ', ') AS options "
"FROM pg_catalog.pg_index i "
@@ -3749,6 +3779,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
"c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
+ "null AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
"null AS options "
"FROM pg_catalog.pg_index i "
@@ -3776,6 +3807,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
"c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
+ "null AS condef, "
"NULL AS tablespace, "
"null AS options "
"FROM pg_catalog.pg_index i "
@@ -3806,6 +3838,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
"false AS condeferred, "
"0::oid AS contableoid, "
"t.oid AS conoid, "
+ "null AS condef, "
"NULL AS tablespace, "
"null AS options "
"FROM pg_index i, pg_class t "
@@ -3831,6 +3864,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
"false AS condeferred, "
"0::oid AS contableoid, "
"t.oid AS conoid, "
+ "null AS condef, "
"NULL AS tablespace, "
"null AS options "
"FROM pg_index i, pg_class t "
@@ -3858,6 +3892,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
i_condeferred = PQfnumber(res, "condeferred");
i_contableoid = PQfnumber(res, "contableoid");
i_conoid = PQfnumber(res, "conoid");
+ i_condef = PQfnumber(res, "condef");
i_tablespace = PQfnumber(res, "tablespace");
i_options = PQfnumber(res, "options");
@@ -3895,7 +3930,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
contype = *(PQgetvalue(res, j, i_contype));
- if (contype == 'p' || contype == 'u')
+ if (contype == 'p' || contype == 'u' || contype == 'x')
{
/*
* If we found a constraint matching the index, create an
@@ -3913,7 +3948,10 @@ getIndexes(TableInfo tblinfo[], int numTables)
constrinfo[j].contable = tbinfo;
constrinfo[j].condomain = NULL;
constrinfo[j].contype = contype;
- constrinfo[j].condef = NULL;
+ if (contype == 'x')
+ constrinfo[j].condef = strdup(PQgetvalue(res, j, i_condef));
+ else
+ constrinfo[j].condef = NULL;
constrinfo[j].confrelid = InvalidOid;
constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
@@ -10728,7 +10766,9 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
q = createPQExpBuffer();
delq = createPQExpBuffer();
- if (coninfo->contype == 'p' || coninfo->contype == 'u')
+ if (coninfo->contype == 'p' ||
+ coninfo->contype == 'u' ||
+ coninfo->contype == 'x')
{
/* Index-related constraint */
IndxInfo *indxinfo;
@@ -10745,37 +10785,46 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
fmtId(tbinfo->dobj.name));
- appendPQExpBuffer(q, " ADD CONSTRAINT %s %s (",
- fmtId(coninfo->dobj.name),
- coninfo->contype == 'p' ? "PRIMARY KEY" : "UNIQUE");
+ appendPQExpBuffer(q, " ADD CONSTRAINT %s ",
+ fmtId(coninfo->dobj.name));
- for (k = 0; k < indxinfo->indnkeys; k++)
+ if (coninfo->condef)
{
- int indkey = (int) indxinfo->indkeys[k];
- const char *attname;
-
- if (indkey == InvalidAttrNumber)
- break;
- attname = getAttrName(indkey, tbinfo);
-
- appendPQExpBuffer(q, "%s%s",
- (k == 0) ? "" : ", ",
- fmtId(attname));
+ /* pg_get_constraintdef should have provided everything */
+ appendPQExpBuffer(q, "%s;\n", coninfo->condef);
}
-
- appendPQExpBuffer(q, ")");
-
- if (indxinfo->options && strlen(indxinfo->options) > 0)
- appendPQExpBuffer(q, " WITH (%s)", indxinfo->options);
-
- if (coninfo->condeferrable)
+ else
{
- appendPQExpBuffer(q, " DEFERRABLE");
- if (coninfo->condeferred)
- appendPQExpBuffer(q, " INITIALLY DEFERRED");
- }
+ appendPQExpBuffer(q, "%s (",
+ coninfo->contype == 'p' ? "PRIMARY KEY" : "UNIQUE");
+ for (k = 0; k < indxinfo->indnkeys; k++)
+ {
+ int indkey = (int) indxinfo->indkeys[k];
+ const char *attname;
- appendPQExpBuffer(q, ";\n");
+ if (indkey == InvalidAttrNumber)
+ break;
+ attname = getAttrName(indkey, tbinfo);
+
+ appendPQExpBuffer(q, "%s%s",
+ (k == 0) ? "" : ", ",
+ fmtId(attname));
+ }
+
+ appendPQExpBuffer(q, ")");
+
+ if (indxinfo->options && strlen(indxinfo->options) > 0)
+ appendPQExpBuffer(q, " WITH (%s)", indxinfo->options);
+
+ if (coninfo->condeferrable)
+ {
+ appendPQExpBuffer(q, " DEFERRABLE");
+ if (coninfo->condeferred)
+ appendPQExpBuffer(q, " INITIALLY DEFERRED");
+ }
+
+ appendPQExpBuffer(q, ";\n");
+ }
/* If the index is clustered, we need to record that. */
if (indxinfo->indisclustered)
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8fd456fe34..8ca21dfc0d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -8,7 +8,7 @@
*
* Copyright (c) 2000-2009, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.231 2009/11/11 21:07:41 petere Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.232 2009/12/07 05:22:23 tgl Exp $
*/
#include "postgres_fe.h"
@@ -1105,6 +1105,7 @@ describeOneTableDetails(const char *schemaname,
bool hasrules;
bool hastriggers;
bool hasoids;
+ bool hasexclusion;
Oid tablespace;
char *reloptions;
} tableinfo;
@@ -1121,7 +1122,22 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
- if (pset.sversion >= 80400)
+ if (pset.sversion >= 80500)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+ "c.relhastriggers, c.relhasoids, "
+ "%s, c.reltablespace, c.relhasexclusion\n"
+ "FROM pg_catalog.pg_class c\n "
+ "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+ "WHERE c.oid = '%s'\n",
+ (verbose ?
+ "pg_catalog.array_to_string(c.reloptions || "
+ "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+ : "''"),
+ oid);
+ }
+ else if (pset.sversion >= 80400)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
@@ -1185,10 +1201,12 @@ describeOneTableDetails(const char *schemaname,
tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0;
tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
- tableinfo.reloptions = pset.sversion >= 80200 ?
+ tableinfo.reloptions = (pset.sversion >= 80200) ?
strdup(PQgetvalue(res, 0, 6)) : 0;
tableinfo.tablespace = (pset.sversion >= 80000) ?
atooid(PQgetvalue(res, 0, 7)) : 0;
+ tableinfo.hasexclusion = (pset.sversion >= 80500) ?
+ strcmp(PQgetvalue(res, 0, 8), "t") == 0 : false;
PQclear(res);
res = NULL;
@@ -1642,6 +1660,38 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
+ /* print exclusion constraints */
+ if (tableinfo.hasexclusion)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT r.conname, "
+ "pg_catalog.pg_get_constraintdef(r.oid, true)\n"
+ "FROM pg_catalog.pg_constraint r\n"
+ "WHERE r.conrelid = '%s' AND r.contype = 'x'\n"
+ "ORDER BY 1",
+ oid);
+ result = PSQLexec(buf.data, false);
+ if (!result)
+ goto error_return;
+ else
+ tuples = PQntuples(result);
+
+ if (tuples > 0)
+ {
+ printTableAddFooter(&cont, _("Exclusion constraints:"));
+ for (i = 0; i < tuples; i++)
+ {
+ /* untranslated contraint name and def */
+ printfPQExpBuffer(&buf, " \"%s\" %s",
+ PQgetvalue(result, i, 0),
+ PQgetvalue(result, i, 1));
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ }
+ PQclear(result);
+ }
+
/* print foreign-key constraints (there are none if no triggers) */
if (tableinfo.hastriggers)
{
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 83ba592aa3..9b93572097 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.555 2009/12/05 21:43:35 petere Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.556 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200912051
+#define CATALOG_VERSION_NO 200912071
#endif
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index f5a737f9f0..8eae51f098 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.154 2009/10/07 22:14:25 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.155 2009/12/07 05:22:23 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@@ -426,12 +426,13 @@ DATA(insert ( 1249 tableoid 26 0 0 4 -7 0 -1 -1 t p i t f f t 0 _null_));
{ 1259, {"relchecks"}, 21, -1, 0, 2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhasoids"}, 16, -1, 0, 1, 18, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhaspkey"}, 16, -1, 0, 1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
-{ 1259, {"relhasrules"}, 16, -1, 0, 1, 20, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
-{ 1259, {"relhastriggers"},16, -1, 0, 1, 21, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
-{ 1259, {"relhassubclass"},16, -1, 0, 1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
-{ 1259, {"relfrozenxid"}, 28, -1, 0, 4, 23, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
-{ 1259, {"relacl"}, 1034, -1, 0, -1, 24, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
-{ 1259, {"reloptions"}, 1009, -1, 0, -1, 25, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
+{ 1259, {"relhasexclusion"},16, -1, 0, 1, 20, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relhasrules"}, 16, -1, 0, 1, 21, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relhastriggers"},16, -1, 0, 1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relhassubclass"},16, -1, 0, 1, 23, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relfrozenxid"}, 28, -1, 0, 4, 24, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relacl"}, 1034, -1, 0, -1, 25, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1259, {"reloptions"}, 1009, -1, 0, -1, 26, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
DATA(insert ( 1259 relname 19 -1 0 NAMEDATALEN 1 0 -1 -1 f p c t f f t 0 _null_));
DATA(insert ( 1259 relnamespace 26 -1 0 4 2 0 -1 -1 t p i t f f t 0 _null_));
@@ -452,12 +453,13 @@ DATA(insert ( 1259 relnatts 21 -1 0 2 16 0 -1 -1 t p s t f f t 0 _null_));
DATA(insert ( 1259 relchecks 21 -1 0 2 17 0 -1 -1 t p s t f f t 0 _null_));
DATA(insert ( 1259 relhasoids 16 -1 0 1 18 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relhaspkey 16 -1 0 1 19 0 -1 -1 t p c t f f t 0 _null_));
-DATA(insert ( 1259 relhasrules 16 -1 0 1 20 0 -1 -1 t p c t f f t 0 _null_));
-DATA(insert ( 1259 relhastriggers 16 -1 0 1 21 0 -1 -1 t p c t f f t 0 _null_));
-DATA(insert ( 1259 relhassubclass 16 -1 0 1 22 0 -1 -1 t p c t f f t 0 _null_));
-DATA(insert ( 1259 relfrozenxid 28 -1 0 4 23 0 -1 -1 t p i t f f t 0 _null_));
-DATA(insert ( 1259 relacl 1034 -1 0 -1 24 1 -1 -1 f x i f f f t 0 _null_));
-DATA(insert ( 1259 reloptions 1009 -1 0 -1 25 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1259 relhasexclusion 16 -1 0 1 20 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relhasrules 16 -1 0 1 21 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relhastriggers 16 -1 0 1 22 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relhassubclass 16 -1 0 1 23 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relfrozenxid 28 -1 0 4 24 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 relacl 1034 -1 0 -1 25 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1259 reloptions 1009 -1 0 -1 26 1 -1 -1 f x i f f f t 0 _null_));
DATA(insert ( 1259 ctid 27 0 0 6 -1 0 -1 -1 f p s t f f t 0 _null_));
DATA(insert ( 1259 oid 26 0 0 4 -2 0 -1 -1 t p i t f f t 0 _null_));
DATA(insert ( 1259 xmin 28 0 0 4 -3 0 -1 -1 t p i t f f t 0 _null_));
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 2ab4e9fda0..5746e447e4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.116 2009/09/26 22:42:02 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.117 2009/12/07 05:22:23 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@@ -56,6 +56,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
int2 relchecks; /* # of CHECK constraints for class */
bool relhasoids; /* T if we generate OIDs for rows of rel */
bool relhaspkey; /* has (or has had) PRIMARY KEY index */
+ bool relhasexclusion; /* has (or has had) exclusion constraint */
bool relhasrules; /* has (or has had) any rules */
bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */
@@ -87,7 +88,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 25
+#define Natts_pg_class 26
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -107,12 +108,13 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relchecks 17
#define Anum_pg_class_relhasoids 18
#define Anum_pg_class_relhaspkey 19
-#define Anum_pg_class_relhasrules 20
-#define Anum_pg_class_relhastriggers 21
-#define Anum_pg_class_relhassubclass 22
-#define Anum_pg_class_relfrozenxid 23
-#define Anum_pg_class_relacl 24
-#define Anum_pg_class_reloptions 25
+#define Anum_pg_class_relhasexclusion 20
+#define Anum_pg_class_relhasrules 21
+#define Anum_pg_class_relhastriggers 22
+#define Anum_pg_class_relhassubclass 23
+#define Anum_pg_class_relfrozenxid 24
+#define Anum_pg_class_relacl 25
+#define Anum_pg_class_reloptions 26
/* ----------------
* initial contents of pg_class
@@ -124,13 +126,13 @@ typedef FormData_pg_class *Form_pg_class;
*/
/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f f r 28 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 25 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
#define RELKIND_INDEX 'i' /* secondary index */
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9822fc8c08..87258a231a 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.33 2009/10/12 19:49:24 adunstan Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.34 2009/12/07 05:22:23 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@@ -119,6 +119,12 @@ CATALOG(pg_constraint,2606)
*/
Oid conffeqop[1];
+ /*
+ * If an exclusion constraint, the OIDs of the exclusion operators for
+ * each column of the constraint
+ */
+ Oid conexclop[1];
+
/*
* If a check constraint, nodeToString representation of expression
*/
@@ -141,7 +147,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 21
+#define Natts_pg_constraint 22
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
@@ -161,8 +167,9 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define Anum_pg_constraint_conpfeqop 17
#define Anum_pg_constraint_conppeqop 18
#define Anum_pg_constraint_conffeqop 19
-#define Anum_pg_constraint_conbin 20
-#define Anum_pg_constraint_consrc 21
+#define Anum_pg_constraint_conexclop 20
+#define Anum_pg_constraint_conbin 21
+#define Anum_pg_constraint_consrc 22
/* Valid values for contype */
@@ -170,6 +177,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define CONSTRAINT_FOREIGN 'f'
#define CONSTRAINT_PRIMARY 'p'
#define CONSTRAINT_UNIQUE 'u'
+#define CONSTRAINT_EXCLUSION 'x'
/*
* Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
@@ -209,6 +217,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
+ const Oid *exclOp,
Node *conExpr,
const char *conBin,
const char *conSrc,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 89bb227a90..1b665ff855 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.97 2009/09/22 23:43:41 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.98 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -26,6 +26,7 @@ extern void DefineIndex(RangeVar *heapRelation,
List *attributeList,
Expr *predicate,
List *options,
+ List *exclusionOpNames,
bool unique,
bool primary,
bool isconstraint,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ba2f42d686..76c075f75f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.163 2009/10/26 02:26:41 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.164 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -321,6 +321,12 @@ extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum_full);
+extern bool check_exclusion_constraint(Relation heap, Relation index,
+ IndexInfo *indexInfo,
+ ItemPointer tupleid,
+ Datum *values, bool *isnull,
+ EState *estate,
+ bool newIndex, bool errorOK);
extern void RegisterExprContextCallback(ExprContext *econtext,
ExprContextCallbackFunction function,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 3bff0b5a6d..7e4cfe9d71 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.212 2009/11/20 20:38:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.213 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -40,6 +40,9 @@
* ExpressionsState exec state for expressions, or NIL if none
* Predicate partial-index predicate, or NIL if none
* PredicateState exec state for predicate, or NIL if none
+ * ExclusionOps Per-column exclusion operators, or NULL if none
+ * ExclusionProcs Underlying function OIDs for ExclusionOps
+ * ExclusionStrats Opclass strategy numbers for ExclusionOps
* Unique is it a unique index?
* ReadyForInserts is it valid for inserts?
* Concurrent are we doing a concurrent index build?
@@ -58,6 +61,9 @@ typedef struct IndexInfo
List *ii_ExpressionsState; /* list of ExprState */
List *ii_Predicate; /* list of Expr */
List *ii_PredicateState; /* list of ExprState */
+ Oid *ii_ExclusionOps; /* array with one entry per column */
+ Oid *ii_ExclusionProcs; /* array with one entry per column */
+ uint16 *ii_ExclusionStrats; /* array with one entry per column */
bool ii_Unique;
bool ii_ReadyForInserts;
bool ii_Concurrent;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ad95ac9bb4..a791223e6f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.416 2009/11/20 20:38:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.417 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1395,6 +1395,7 @@ typedef enum ConstrType /* types of constraints */
CONSTR_CHECK,
CONSTR_PRIMARY,
CONSTR_UNIQUE,
+ CONSTR_EXCLUSION,
CONSTR_FOREIGN,
CONSTR_ATTR_DEFERRABLE, /* attributes for previous constraint node */
CONSTR_ATTR_NOT_DEFERRABLE,
@@ -1429,10 +1430,18 @@ typedef struct Constraint
Node *raw_expr; /* expr, as untransformed parse tree */
char *cooked_expr; /* expr, as nodeToString representation */
- /* Fields used for index constraints (UNIQUE and PRIMARY KEY): */
+ /* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
List *keys; /* String nodes naming referenced column(s) */
+
+ /* Fields used for EXCLUSION constraints: */
+ List *exclusions; /* list of (IndexElem, operator name) pairs */
+
+ /* Fields used for index constraints (UNIQUE, PRIMARY KEY, EXCLUSION): */
List *options; /* options from WITH clause */
char *indexspace; /* index tablespace; NULL for default */
+ /* These could be, but currently are not, used for UNIQUE/PKEY: */
+ char *access_method; /* index access method; NULL for default */
+ Node *where_clause; /* partial index predicate */
/* Fields used for FOREIGN KEY constraints: */
RangeVar *pktable; /* Primary key table */
@@ -1880,6 +1889,7 @@ typedef struct IndexStmt
List *indexParams; /* a list of IndexElem */
List *options; /* options from WITH clause */
Node *whereClause; /* qualification (partial-index predicate) */
+ List *excludeOpNames; /* exclusion operator names, or NIL if none */
bool unique; /* is index unique? */
bool primary; /* is index on primary key? */
bool isconstraint; /* is it from a CONSTRAINT clause? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 42087742fa..f07057c430 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -11,7 +11,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.6 2009/11/05 23:24:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.7 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -143,6 +143,7 @@ PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
+PG_KEYWORD("exclude", EXCLUDE, UNRESERVED_KEYWORD)
PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h
index 6524b8c5fe..91f39b7983 100644
--- a/src/include/utils/errcodes.h
+++ b/src/include/utils/errcodes.h
@@ -11,7 +11,7 @@
*
* Copyright (c) 2003-2009, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.29 2009/03/04 10:55:00 petere Exp $
+ * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.30 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -167,6 +167,7 @@
#define ERRCODE_FOREIGN_KEY_VIOLATION MAKE_SQLSTATE('2','3', '5','0','3')
#define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3', '5','0','5')
#define ERRCODE_CHECK_VIOLATION MAKE_SQLSTATE('2','3', '5','1','4')
+#define ERRCODE_EXCLUSION_VIOLATION MAKE_SQLSTATE('2','3', 'P','0','1')
/* Class 24 - Invalid Cursor State */
#define ERRCODE_INVALID_CURSOR_STATE MAKE_SQLSTATE('2','4', '0','0','0')
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0ef6642f98..b41f87f255 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.116 2009/11/20 20:38:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.117 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -196,6 +196,9 @@ typedef struct RelationData
int16 *rd_indoption; /* per-column AM-specific flags */
List *rd_indexprs; /* index expression trees, if any */
List *rd_indpred; /* index predicate tree, if any */
+ Oid *rd_exclops; /* OIDs of exclusion operators, if any */
+ Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */
+ uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */
void *rd_amcache; /* available for use by index AM */
/*
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 007532fb0e..4207e7ee4b 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/relcache.h,v 1.64 2009/08/12 20:53:31 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/relcache.h,v 1.65 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -43,6 +43,10 @@ extern Oid RelationGetOidIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+extern void RelationGetExclusionInfo(Relation indexRelation,
+ Oid **operators,
+ Oid **procs,
+ uint16 **strategies);
extern void RelationSetIndexList(Relation relation,
List *indexIds, Oid oidIndex);
diff --git a/src/pl/plpgsql/src/plerrcodes.h b/src/pl/plpgsql/src/plerrcodes.h
index a2de61d03e..2fdab2ef3b 100644
--- a/src/pl/plpgsql/src/plerrcodes.h
+++ b/src/pl/plpgsql/src/plerrcodes.h
@@ -9,7 +9,7 @@
*
* Copyright (c) 2003-2009, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.18 2009/03/04 10:55:00 petere Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.19 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -303,6 +303,10 @@
"check_violation", ERRCODE_CHECK_VIOLATION
},
+{
+ "exclusion_violation", ERRCODE_EXCLUSION_VIOLATION
+},
+
{
"invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE
},
diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source
index 178b159c70..ee396f3703 100644
--- a/src/test/regress/input/constraints.source
+++ b/src/test/regress/input/constraints.source
@@ -5,6 +5,7 @@
-- - CHECK clauses
-- - PRIMARY KEY clauses
-- - UNIQUE clauses
+-- - EXCLUDE clauses
--
--
@@ -366,3 +367,61 @@ COMMIT;
SELECT * FROM unique_tbl;
DROP TABLE unique_tbl;
+
+--
+-- EXCLUDE constraints
+--
+
+CREATE TABLE circles (
+ c1 CIRCLE,
+ c2 TEXT,
+ EXCLUDE USING gist
+ (c1 WITH &&, (c2::circle) WITH ~=)
+ WHERE (circle_center(c1) <> '(0,0)')
+);
+
+-- these should succeed because they don't match the index predicate
+INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
+INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
+
+-- succeed
+INSERT INTO circles VALUES('<(10,10), 10>', '<(0,0), 5>');
+-- fail, overlaps
+INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+-- succeed because c1 doesn't overlap
+INSERT INTO circles VALUES('<(20,20), 1>', '<(0,0), 5>');
+-- succeed because c2 is not the same
+INSERT INTO circles VALUES('<(20,20), 10>', '<(1,1), 5>');
+
+-- should fail on existing data without the WHERE clause
+ALTER TABLE circles ADD EXCLUDE USING gist
+ (c1 WITH &&, (c2::circle) WITH ~=);
+
+DROP TABLE circles;
+
+-- Check deferred exclusion constraint
+
+CREATE TABLE deferred_excl (
+ f1 int,
+ CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED
+);
+
+INSERT INTO deferred_excl VALUES(1);
+INSERT INTO deferred_excl VALUES(2);
+INSERT INTO deferred_excl VALUES(1); -- fail
+BEGIN;
+INSERT INTO deferred_excl VALUES(2); -- no fail here
+COMMIT; -- should fail here
+BEGIN;
+INSERT INTO deferred_excl VALUES(3);
+INSERT INTO deferred_excl VALUES(3); -- no fail here
+COMMIT; -- should fail here
+
+ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con;
+
+-- This should fail, but worth testing because of HOT updates
+UPDATE deferred_excl SET f1 = 3;
+
+ALTER TABLE deferred_excl ADD EXCLUDE (f1 WITH =);
+
+DROP TABLE deferred_excl;
diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source
index 56dbf7a055..8928ca8beb 100644
--- a/src/test/regress/output/constraints.source
+++ b/src/test/regress/output/constraints.source
@@ -5,6 +5,7 @@
-- - CHECK clauses
-- - PRIMARY KEY clauses
-- - UNIQUE clauses
+-- - EXCLUDE clauses
--
--
-- DEFAULT syntax
@@ -512,3 +513,64 @@ SELECT * FROM unique_tbl;
(5 rows)
DROP TABLE unique_tbl;
+--
+-- EXCLUDE constraints
+--
+CREATE TABLE circles (
+ c1 CIRCLE,
+ c2 TEXT,
+ EXCLUDE USING gist
+ (c1 WITH &&, (c2::circle) WITH ~=)
+ WHERE (circle_center(c1) <> '(0,0)')
+);
+NOTICE: CREATE TABLE / EXCLUDE will create implicit index "circles_c1_exclusion" for table "circles"
+-- these should succeed because they don't match the index predicate
+INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
+INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
+-- succeed
+INSERT INTO circles VALUES('<(10,10), 10>', '<(0,0), 5>');
+-- fail, overlaps
+INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ERROR: conflicting key value violates exclusion constraint "circles_c1_exclusion"
+DETAIL: Key (c1, (c2::circle))=(<(20,20),10>, <(0,0),5>) conflicts with existing key (c1, (c2::circle))=(<(10,10),10>, <(0,0),5>).
+-- succeed because c1 doesn't overlap
+INSERT INTO circles VALUES('<(20,20), 1>', '<(0,0), 5>');
+-- succeed because c2 is not the same
+INSERT INTO circles VALUES('<(20,20), 10>', '<(1,1), 5>');
+-- should fail on existing data without the WHERE clause
+ALTER TABLE circles ADD EXCLUDE USING gist
+ (c1 WITH &&, (c2::circle) WITH ~=);
+NOTICE: ALTER TABLE / ADD EXCLUDE will create implicit index "circles_c1_exclusion1" for table "circles"
+ERROR: could not create exclusion constraint "circles_c1_exclusion1"
+DETAIL: Key (c1, (c2::circle))=(<(0,0),5>, <(0,0),5>) conflicts with key (c1, (c2::circle))=(<(0,0),5>, <(0,0),5>).
+DROP TABLE circles;
+-- Check deferred exclusion constraint
+CREATE TABLE deferred_excl (
+ f1 int,
+ CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED
+);
+NOTICE: CREATE TABLE / EXCLUDE will create implicit index "deferred_excl_con" for table "deferred_excl"
+INSERT INTO deferred_excl VALUES(1);
+INSERT INTO deferred_excl VALUES(2);
+INSERT INTO deferred_excl VALUES(1); -- fail
+ERROR: conflicting key value violates exclusion constraint "deferred_excl_con"
+DETAIL: Key (f1)=(1) conflicts with existing key (f1)=(1).
+BEGIN;
+INSERT INTO deferred_excl VALUES(2); -- no fail here
+COMMIT; -- should fail here
+ERROR: conflicting key value violates exclusion constraint "deferred_excl_con"
+DETAIL: Key (f1)=(2) conflicts with existing key (f1)=(2).
+BEGIN;
+INSERT INTO deferred_excl VALUES(3);
+INSERT INTO deferred_excl VALUES(3); -- no fail here
+COMMIT; -- should fail here
+ERROR: conflicting key value violates exclusion constraint "deferred_excl_con"
+DETAIL: Key (f1)=(3) conflicts with existing key (f1)=(3).
+ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con;
+-- This should fail, but worth testing because of HOT updates
+UPDATE deferred_excl SET f1 = 3;
+ALTER TABLE deferred_excl ADD EXCLUDE (f1 WITH =);
+NOTICE: ALTER TABLE / ADD EXCLUDE will create implicit index "deferred_excl_f1_exclusion" for table "deferred_excl"
+ERROR: could not create exclusion constraint "deferred_excl_f1_exclusion"
+DETAIL: Key (f1)=(3) conflicts with key (f1)=(3).
+DROP TABLE deferred_excl;