*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,57 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE column_name [, ... ] ) index_parameters |
CHECK ( expression ) |
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:
--- 51,58 ----
PRIMARY KEY ( column_name [, ... ] ) index_parameters |
CHECK ( expression ) |
FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
! [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE action ] [ ON UPDATE action ] |
! EXCLUSION [ USING index_method ] ( expression CHECK WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
index_parameters in UNIQUE and PRIMARY KEY constraints are:
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE index_method ] ( expression CHECK WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ]
+
+
+ The EXCLUSION> clause specifies an operator exclusion
+ constraint. An operator exclusion constraint is more general
+ than a UNIQUE constraint, and can use an
+ arbitrary operator to detect conflicts. For instance, you can
+ specify the constraint that no two tuples in the table contain
+ overlapping circles (see ) by
+ using the && operator.
+
+
+
+ The constraint specifies the conflict condition, so the operator
+ should return TRUE when applied to two
+ conflicting values. Also, the operator specified must be
+ commutative (that is, the commutator of the operator must be the
+ operator itself), must be a boolean operator, and must be
+ associated with an operator class
+ (see ) using
+ index_method. The
+ constraint is violated if, and only if, there exist two tuples
+ where all corresponding expressions between the tuples conflict
+ according
+ to operator
+ (i.e. the operator returns TRUE).
+
+
+
+ Internally, operator exclusion constraints use an index to
+ perform a search looking for conflicting values, and handle
+ concurrent operations similar to a UNIQUE
+ constraint. If all of the operators are specified as the
+ equality operator (usually =), this
+ constraint behaves identically to a UNIQUE
+ constraint. However, it may exhibit slightly worse performance
+ than specifying UNIQUE, because operator
+ exclusion constraints require one additional index search. The
+ advantage of operator exclusion constraints is the ability to
+ specify more general constraints (like a non-overlapping
+ constraint for circles), and also the ability to use index
+ methods other than btree, such
+ as GiST (see ).
+
+
+
+ The index_parameters
+ are the same as for a UNIQUE constraint. The predicate
+ allows you to specify the constraint on a subset of the table
+ (note the reqiuired parentheses around the predicate
+ expression), internally using a partial index
+ (see ). The expression
+ is normally just a column name, but can also be an expression or
+ function call and the constraint will check the result (similar
+ to creating a unique index over an expression).
+
+
+
+
+
DEFERRABLE
NOT DEFERRABLE
***************
*** 1110,1115 **** CREATE TABLE cinemas (
--- 1171,1188 ----
+
+ Create table circles> with an operator exclusion
+ constraint that prevents overlapping circles within it:
+
+
+ CREATE TABLE circles (
+ c circle,
+ EXCLUSION USING gist (c CHECK WITH &&)
+ );
+
+
+
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
* index_vacuum_cleanup - post-deletion cleanup of an index
* index_getprocid - get a support procedure OID
* index_getprocinfo - get a support procedure's lookup info
+ * index_check_constraint - check operator exclusion constraints
*
* NOTES
* This file contains the index_ routines which used
***************
*** 64,72 ****
--- 65,77 ----
#include "access/relscan.h"
#include "access/transam.h"
+ #include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
+ #include "storage/lwlock.h"
+ #include "storage/procarray.h"
+ #include "utils/lsyscache.h"
#include "utils/relcache.h"
#include "utils/snapmgr.h"
#include "utils/tqual.h"
***************
*** 116,122 **** do { \
static IndexScanDesc index_beginscan_internal(Relation indexRelation,
int nkeys, ScanKey key);
-
/* ----------------------------------------------------------------
* index_ interface functions
* ----------------------------------------------------------------
--- 121,126 ----
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
$8,
NULL,
$10,
! NULL, NIL,
false, false, false, false, false,
false, false, true, false, false);
do_end();
--- 267,273 ----
$8,
NULL,
$10,
! NULL, NIL, NULL,
false, false, false, false, false,
false, false, true, false, false);
do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
$9,
NULL,
$11,
! NULL, NIL,
true, false, false, false, false,
false, false, true, false, false);
do_end();
--- 285,291 ----
$9,
NULL,
$11,
! NULL, NIL, NULL,
true, false, false, false, false,
false, false, true, false, false);
do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1109 ----
copyObject(indexInfo->ii_Predicate);
newind->il_info->ii_PredicateState = NIL;
+ /* no operator exclusion constraints exist at bootstrap time */
+ newind->il_info->ii_ExclusionConstraint = NULL;
+
newind->il_next = ILHead;
ILHead = newind;
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
' ',
' ',
' ',
+ NULL,
expr, /* Tree form check constraint */
ccbin, /* Binary form check constraint */
ccsrc, /* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
constraintType = CONSTRAINT_PRIMARY;
else if (indexInfo->ii_Unique)
constraintType = CONSTRAINT_UNIQUE;
else
{
! elog(ERROR, "constraint must be PRIMARY or UNIQUE");
constraintType = 0; /* keep compiler quiet */
}
/* Shouldn't have any expressions */
! if (indexInfo->ii_Expressions)
elog(ERROR, "constraints cannot have index expressions");
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
--- 728,750 ----
constraintType = CONSTRAINT_PRIMARY;
else if (indexInfo->ii_Unique)
constraintType = CONSTRAINT_UNIQUE;
+ else if (indexInfo->ii_ExclusionConstraint != NULL)
+ constraintType = CONSTRAINT_OPX;
else
{
! elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUSION");
constraintType = 0; /* keep compiler quiet */
}
/* Shouldn't have any expressions */
! if (indexInfo->ii_Expressions &&
! constraintType != CONSTRAINT_OPX)
elog(ERROR, "constraints cannot have index expressions");
+ if (constraintType == CONSTRAINT_OPX && concurrent)
+ elog(ERROR, "concurrent index builds not supported for "
+ "operator exclusion constraints");
+
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
' ',
' ',
' ',
+ indexInfo->ii_ExclusionConstraint,
NULL, /* no check constraint */
NULL,
NULL,
***************
*** 803,808 **** index_create(Oid heapRelationId,
--- 811,874 ----
"Unique_ConstraintTrigger",
false);
}
+
+ CommandCounterIncrement();
+
+ /* Increment pg_class.relopxconstraints for the heap and index. */
+ if (constraintType == CONSTRAINT_OPX)
+ {
+ Relation pgrel;
+ Form_pg_class heap_class;
+ HeapTuple relTup;
+ HeapTuple idxTup;
+
+ pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* Increment the count for the heap. */
+ relTup = SearchSysCacheCopy(RELOID,
+ ObjectIdGetDatum(heapRelationId),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(relTup))
+ elog(ERROR, "cache lookup failed for relation %u",
+ heapRelationId);
+ heap_class = (Form_pg_class) GETSTRUCT(relTup);
+
+ if (heap_class->relopxconstraints < 0)
+ elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ RelationGetRelationName(heapRelation),
+ heap_class->relopxconstraints);
+
+ heap_class->relopxconstraints++;
+
+ simple_heap_update(pgrel, &relTup->t_self, relTup);
+
+ CatalogUpdateIndexes(pgrel, relTup);
+
+ heap_freetuple(relTup);
+
+ /* Increment the count for the index. */
+ idxTup = SearchSysCacheCopy(RELOID,
+ ObjectIdGetDatum(indexRelationId),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(idxTup))
+ elog(ERROR, "cache lookup failed for relation %u",
+ indexRelationId);
+ heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+
+ if (heap_class->relopxconstraints != 0)
+ elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ indexRelationName, heap_class->relopxconstraints);
+
+ heap_class->relopxconstraints++;
+
+ simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+
+ CatalogUpdateIndexes(pgrel, idxTup);
+
+ heap_freetuple(idxTup);
+
+ heap_close(pgrel, RowExclusiveLock);
+ }
}
else
{
***************
*** 1082,1087 **** BuildIndexInfo(Relation index)
--- 1148,1157 ----
/* other info */
ii->ii_Unique = indexStruct->indisunique;
ii->ii_ReadyForInserts = indexStruct->indisready;
+ if (index->rd_rel->relopxconstraints > 0)
+ ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ else
+ ii->ii_ExclusionConstraint = NULL;
/* initialize index-build state to default */
ii->ii_Concurrent = false;
***************
*** 1893,1898 **** IndexBuildHeapScan(Relation heapRelation,
--- 1963,1971 ----
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_PredicateState = NIL;
+ /* operator exclusion constraints aren't supported at index build time. */
+ indexInfo->ii_ExclusionConstraint = NULL;
+
return reltuples;
}
***************
*** 2267,2272 **** validate_index_heapscan(Relation heapRelation,
--- 2340,2348 ----
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_PredicateState = NIL;
+
+ /* operator exclusion constraints aren't supported at index build time. */
+ indexInfo->ii_ExclusionConstraint = NULL;
}
***************
*** 2522,2524 **** reindex_relation(Oid relid, bool toast_too)
--- 2598,2601 ----
return result;
}
+
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
AND c.conrelid = r.oid
+ AND c.contype IN ('c','f','p','u')
AND r.relkind = 'r'
AND (NOT pg_is_other_temp_schema(nr.oid))
AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
+ const int16 *exclusion_constraint,
Node *conExpr,
const char *conBin,
const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
ArrayType *conpfeqopArray;
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
+ ArrayType *constrategiesArray = NULL;
NameData cname;
int i;
ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
conffeqopArray = NULL;
}
+ if (exclusion_constraint != NULL)
+ {
+ Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+
+ for (i = 0; i < constraintNKeys; i++)
+ strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ constrategiesArray = construct_array(strategyDatums,
+ constraintNKeys,
+ INT2OID,
+ sizeof(int16), true, 's');
+ }
+
/* initialize nulls and values */
for (i = 0; i < Natts_pg_constraint; i++)
{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
else
nulls[Anum_pg_constraint_conffeqop - 1] = true;
+ if (constrategiesArray)
+ values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ else
+ nulls[Anum_pg_constraint_constrategies - 1] = true;
+
/*
* initialize the binary form of the check constraint.
*/
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
found = true;
break;
}
+ else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ {
+ found = true;
+ break;
+ }
}
systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
* being dropped. This update will force backends to rebuild relcache
* entries when we commit.
*/
! if (con->contype == CONSTRAINT_CHECK)
{
Relation pgrel;
HeapTuple relTup;
--- 548,555 ----
* being dropped. This update will force backends to rebuild relcache
* entries when we commit.
*/
! if (con->contype == CONSTRAINT_CHECK ||
! con->contype == CONSTRAINT_OPX)
{
Relation pgrel;
HeapTuple relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
con->conrelid);
classForm = (Form_pg_class) GETSTRUCT(relTup);
! if (classForm->relchecks == 0) /* should not happen */
! elog(ERROR, "relation \"%s\" has relchecks = 0",
! RelationGetRelationName(rel));
! classForm->relchecks--;
simple_heap_update(pgrel, &relTup->t_self, relTup);
--- 564,583 ----
con->conrelid);
classForm = (Form_pg_class) GETSTRUCT(relTup);
! if (con->contype == CONSTRAINT_CHECK)
! {
! if (classForm->relchecks == 0) /* should not happen */
! elog(ERROR, "relation \"%s\" has relchecks = 0",
! RelationGetRelationName(rel));
! classForm->relchecks--;
! }
! else
! {
! if (classForm->relopxconstraints == 0) /* should not happen */
! elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! RelationGetRelationName(rel));
! classForm->relopxconstraints--;
! }
simple_heap_update(pgrel, &relTup->t_self, relTup);
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,252 ----
indexInfo->ii_Concurrent = false;
indexInfo->ii_BrokenHotChain = false;
+ /* toast tables don't have operator exclusion constraints */
+ indexInfo->ii_ExclusionConstraint = NULL;
+
classObjectId[0] = OID_BTREE_OPS_OID;
classObjectId[1] = INT4_BTREE_OPS_OID;
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
Relation indexRel;
IndexInfo *indexInfo;
EState *estate;
! ExprContext *econtext;
TupleTableSlot *slot;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
--- 40,46 ----
Relation indexRel;
IndexInfo *indexInfo;
EState *estate;
! ExprContext *econtext = NULL;
TupleTableSlot *slot;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
* Typically the index won't have expressions, but if it does we need
* an EState to evaluate them.
*/
! if (indexInfo->ii_Expressions != NIL)
{
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
* Typically the index won't have expressions, but if it does we need
* an EState to evaluate them.
*/
! if (indexInfo->ii_Expressions != NIL ||
! indexInfo->ii_ExclusionConstraint != NULL)
{
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
* 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.
*/
! index_insert(indexRel, values, isnull, &(new_row->t_self),
! trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
/*
* If that worked, then this index entry is unique, and we are done.
--- 150,168 ----
* 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.
*/
! if (indexInfo->ii_ExclusionConstraint == NULL)
! {
! index_insert(indexRel, values, isnull, &(new_row->t_self),
! trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! }
! else
! {
! index_check_constraint(trigdata->tg_relation, indexRel,
! slot, &(new_row->t_self), values, isnull,
! indexInfo->ii_ExclusionConstraint,
! indexInfo->ii_ExpressionsState,
! econtext, false);
! }
/*
* If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static bool relationHasPrimaryKey(Relation rel);
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
* 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
* 'concurrent': avoid blocking writers to the table while building.
*/
! void
DefineIndex(RangeVar *heapRelation,
char *indexRelationName,
Oid indexRelationId,
--- 95,101 ----
* 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
* 'concurrent': avoid blocking writers to the table while building.
*/
! Oid
DefineIndex(RangeVar *heapRelation,
char *indexRelationName,
Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
List *attributeList,
Expr *predicate,
List *options,
+ int16 *exclusion_constraint,
bool unique,
bool primary,
bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
if (indexRelationName == NULL)
{
if (primary)
+ {
indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
NULL,
"pkey",
namespaceId);
+ }
+ else if (exclusion_constraint != NULL)
+ {
+ IndexElem *iparam = (IndexElem *) linitial(attributeList);
+
+ indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ iparam->name,
+ "exclusion",
+ namespaceId);
+ }
else
{
IndexElem *iparam = (IndexElem *) linitial(attributeList);
***************
*** 424,429 **** DefineIndex(RangeVar *heapRelation,
--- 434,442 ----
indexInfo->ii_Concurrent = concurrent;
indexInfo->ii_BrokenHotChain = false;
+ /* operator exclusion constraints aren't supported at index build time. */
+ indexInfo->ii_ExclusionConstraint = exclusion_constraint;
+
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
ComputeIndexAttrs(indexInfo, classObjectId, coloptions, attributeList,
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
* error checks)
*/
if (isconstraint && !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",
indexRelationName, RelationGetRelationName(rel))));
/* save lockrelid and locktag for below, then close rel */
heaprelid = rel->rd_lockInfo.lockRelId;
--- 448,471 ----
* error checks)
*/
if (isconstraint && !quiet)
+ {
+ char *constraint_type = NULL;
+
+ if (primary)
+ constraint_type = "PRIMARY KEY";
+ else if (unique)
+ constraint_type = "UNIQUE";
+ else if (exclusion_constraint != NULL)
+ constraint_type = "EXCLUSION";
+ else
+ elog(ERROR, "unknown constraint type");
+
ereport(NOTICE,
(errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! constraint_type,
indexRelationName, RelationGetRelationName(rel))));
+ }
/* save lockrelid and locktag for below, then close rel */
heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
isconstraint, deferrable, initdeferred,
allowSystemTableMods, skip_build, concurrent);
! return; /* We're done, in the standard case */
}
/*
--- 481,487 ----
isconstraint, deferrable, initdeferred,
allowSystemTableMods, skip_build, concurrent);
! return indexRelationId; /* We're done, in the standard case */
}
/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 776,783 ----
* Last thing to do is release the session-level lock on the parent table.
*/
UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+
+ return indexRelationId;
}
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
! static Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
--- 967,973 ----
/*
* Resolve possibly-defaulted operator class specification
*/
! Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
Oid refrelid; /* PK rel, if FOREIGN */
Oid refindid; /* OID of PK's index, if FOREIGN */
Oid conid; /* OID of pg_constraint entry, if FOREIGN */
! Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */
List *qualstate; /* Execution state for CHECK */
} NewConstraint;
--- 155,162 ----
Oid refrelid; /* PK rel, if FOREIGN */
Oid refindid; /* OID of PK's index, if FOREIGN */
Oid conid; /* OID of pg_constraint entry, if FOREIGN */
! Oid conindid; /* OID of constraint index, if EXCLUSION */
! Node *qual; /* Check expr if CHECK else Constraint */
List *qualstate; /* Execution state for CHECK */
} NewConstraint;
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
bool recurse, bool recursing);
static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ Relation rel,
+ Constraint *constraint);
static void ATExecDropConstraint(Relation rel, const char *constrName,
DropBehavior behavior,
bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
int i;
ListCell *l;
EState *estate;
+ List *opxList = NIL;
+ int max_index_atts = 0;
/*
* Open the relation(s). We have surely already locked the existing
***************
*** 3077,3082 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3083,3091 ----
switch (con->contype)
{
+ Relation indexRelation = NULL;
+ IndexInfo *indexInfo = NULL;
+
case CONSTR_CHECK:
needscan = true;
con->qualstate = (List *)
***************
*** 3085,3090 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3094,3127 ----
case CONSTR_FOREIGN:
/* Nothing to do here */
break;
+ case CONSTR_OPERATOR_EXCLUSION:
+ needscan = true;
+
+ if (newrel != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot rewrite table while adding "
+ "operator exclusion constraint")));
+
+ indexRelation = index_open(con->conindid, AccessShareLock);
+
+ indexInfo = BuildIndexInfo(indexRelation);
+ indexInfo->ii_PredicateState = (List *)
+ ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+
+ opxList = lappend(opxList,
+ list_make2(indexRelation, indexInfo));
+
+ /*
+ * Keep track of the greatest number of index
+ * attributes for any operator exclusion constraint so
+ * that we can preallocate the idxvals/idxnulls
+ * arrays.
+ */
+ if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ max_index_atts = indexInfo->ii_NumIndexAttrs;
+
+ break;
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) con->contype);
***************
*** 3119,3134 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
if (newrel || needscan)
{
! ExprContext *econtext;
! Datum *values;
! bool *isnull;
! TupleTableSlot *oldslot;
! TupleTableSlot *newslot;
! HeapScanDesc scan;
! HeapTuple tuple;
! MemoryContext oldCxt;
! List *dropped_attrs = NIL;
! ListCell *lc;
econtext = GetPerTupleExprContext(estate);
--- 3156,3173 ----
if (newrel || needscan)
{
! ExprContext *econtext;
! Datum *values;
! bool *isnull;
! TupleTableSlot *oldslot;
! TupleTableSlot *newslot;
! HeapScanDesc scan;
! HeapTuple tuple;
! MemoryContext oldCxt;
! List *dropped_attrs = NIL;
! ListCell *lc;
! Datum *idxvals = NULL;
! bool *idxnulls = NULL;
econtext = GetPerTupleExprContext(estate);
***************
*** 3147,3152 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3186,3200 ----
memset(values, 0, i * sizeof(Datum));
memset(isnull, true, i * sizeof(bool));
+ /* Preallocate idxvals/idxnulls arrays */
+ if (opxList != NIL)
+ {
+ idxvals = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ memset(idxnulls, true, max_index_atts * sizeof(bool));
+ }
+
/*
* Any attributes that are dropped according to the new tuple
* descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3172,3177 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3220,3226 ----
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+
if (newrel)
{
Oid tupOid = InvalidOid;
***************
*** 3242,3247 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3291,3297 ----
con->name)));
break;
case CONSTR_FOREIGN:
+ case CONSTR_OPERATOR_EXCLUSION:
/* Nothing to do here */
break;
default:
***************
*** 3250,3255 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3300,3325 ----
}
}
+ foreach (l, opxList)
+ {
+ List *pair = (List *) lfirst(l);
+ Relation indexRelation = (Relation) linitial(pair);
+ IndexInfo *indexInfo = (IndexInfo *) lsecond(pair);
+
+ FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+
+ /* ignore tuples that don't match the constraint predicate */
+ if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ continue;
+
+ /* check operator exclusion constraint */
+ index_check_constraint(oldrel, indexRelation, newslot,
+ &tuple->t_self, idxvals, idxnulls,
+ indexInfo->ii_ExclusionConstraint,
+ indexInfo->ii_ExpressionsState,
+ econtext, false);
+ }
+
/* Write the tuple out to the new relation */
if (newrel)
simple_heap_insert(newrel, tuple);
***************
*** 3264,3269 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3334,3352 ----
ExecDropSingleTupleTableSlot(oldslot);
ExecDropSingleTupleTableSlot(newslot);
+
+ if (idxvals != NULL)
+ pfree(idxvals);
+ if (idxnulls != NULL)
+ pfree(idxnulls);
+
+ foreach (l, opxList)
+ {
+ List *pair = (List *) lfirst(l);
+ Relation indexRelation = (Relation) linitial(pair);
+
+ index_close(indexRelation, NoLock);
+ }
}
FreeExecutorState(estate);
***************
*** 3271,3276 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3354,3360 ----
heap_close(oldrel, NoLock);
if (newrel)
heap_close(newrel, NoLock);
+
}
/*
***************
*** 4569,4574 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4653,4659 ----
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->options,
+ NULL,
stmt->unique,
stmt->primary,
stmt->isconstraint,
***************
*** 4632,4637 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4717,4757 ----
ATAddForeignKeyConstraint(tab, rel, newConstraint);
break;
+ case CONSTR_OPERATOR_EXCLUSION:
+ /*
+ * We don't recurse for operator exclusion constraints, either.
+ */
+ if (newConstraint->conname)
+ {
+ if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ RelationGetRelid(rel),
+ RelationGetNamespace(rel),
+ newConstraint->conname))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ newConstraint->conname,
+ RelationGetRelationName(rel))));
+ }
+ else
+ {
+ char *choose_name2 = "";
+ IndexElem *ie = linitial(newConstraint->operator_exclusion);
+
+ if (ie->name != NULL)
+ choose_name2 = ie->name;
+
+ newConstraint->conname =
+ ChooseConstraintName(RelationGetRelationName(rel),
+ choose_name2,
+ "exclusion",
+ RelationGetNamespace(rel),
+ NIL);
+ }
+
+ ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ break;
+
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) newConstraint->contype);
***************
*** 5001,5006 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5121,5127 ----
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkconstraint->fk_matchtype,
+ NULL,
NULL, /* no check constraint */
NULL,
NULL,
***************
*** 5037,5042 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5158,5322 ----
heap_close(pkrel, NoLock);
}
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ Constraint *constraint)
+ {
+ int natts;
+ ListCell *lc;
+ HeapTuple tup;
+ Oid methodOid;
+ List *indexElems = NIL;
+ int16 *exclusion_constraint;
+ Oid gettupleOid;
+ Oid index_oid = InvalidOid;
+ Form_pg_am am;
+ RangeVar *rv;
+ int i;
+
+ /*
+ * Find access method oid, and make sure it supports gettuple.
+ */
+ tup = SearchSysCache(AMNAME,
+ CStringGetDatum(constraint->using_method),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("access method \"%s\" does not exist",
+ constraint->using_method)));
+
+ methodOid = HeapTupleGetOid(tup);
+ am = (Form_pg_am) GETSTRUCT(tup);
+ gettupleOid = am->amgettuple;
+
+ ReleaseSysCache(tup);
+
+ if (!OidIsValid(gettupleOid))
+ ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("method \"%s\" does not support gettuple",
+ constraint->using_method)));
+
+ natts = list_length(constraint->operator_exclusion);
+
+ exclusion_constraint = palloc(sizeof(int16) * natts);
+
+ /*
+ * Create the strategies array from the input (IndexElem, Operator)
+ * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ */
+ i = 0;
+ foreach (lc, constraint->operator_exclusion)
+ {
+ List *pair = lfirst(lc);
+ List *opname;
+ IndexElem *elem;
+ Oid opfamily;
+ Oid opclassid;
+ Oid typoid;
+ Oid opid;
+
+ Assert(list_length(pair) == 2);
+
+ elem = linitial(pair);
+ Assert(IsA(elem, IndexElem));
+ opname = lsecond(pair);
+ Assert(IsA(opname, List));
+
+ indexElems = lappend(indexElems, elem);
+
+ if (elem->name != NULL)
+ {
+ AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ if (heapatt < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" does not exist", elem->name),
+ errhint("Cannot specify system column.")));
+
+ typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ }
+ else
+ typoid = exprType(elem->expr);
+
+ opid = LookupOperName(NULL, opname, typoid, typoid, false, -1);
+
+ opclassid = GetIndexOpClass(elem->opclass, typoid,
+ constraint->using_method, methodOid);
+
+ opfamily = get_opclass_family(opclassid);
+
+ /*
+ * Only allow commutative operators to be used for operator
+ * 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 for exclusion constraint must be "
+ "commutative", quote_identifier(get_opname(opid))),
+ errdetail("Set the operator's COMMUTATOR to be itself, "
+ "or choose a different operator.")
+ ));
+ }
+
+ exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+
+ if (exclusion_constraint[i] == InvalidStrategy)
+ elog(ERROR, "no strategy found for operator %d "
+ "in operator family %d", opid, opfamily);
+
+ i++;
+ }
+
+ rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ pstrdup(RelationGetRelationName(rel)),
+ -1);
+ /*
+ * Build index to enforce the constraint. Set isconstraint to
+ * false because we're building the constraint entry ourselves.
+ */
+ index_oid = DefineIndex(rv, /* relation range var */
+ NULL, /* index name */
+ InvalidOid, /* predefined OID */
+ constraint->using_method, /* am name */
+ constraint->indexspace, /* index tablespace */
+ indexElems, /* parameters */
+ (Expr *) constraint->where_clause, /* where */
+ constraint->options, /* options */
+ exclusion_constraint, /* exclusion constraint */
+ false, /* unique */
+ false, /* primary */
+ true, /* is constraint? */
+ constraint->deferrable, /* deferrable */
+ constraint->initdeferred, /* init deferred */
+ true, /* is_alter_table? */
+ true, /* check rights? */
+ false, /* skip build? */
+ false, /* quiet? */
+ false); /* concurrent? */
+
+ /*
+ * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ * (we can skip this during table creation).
+ */
+ if (!constraint->skip_validation)
+ {
+ NewConstraint *newcon;
+
+ newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ newcon->name = constraint->conname;
+ newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ newcon->conindid = index_oid;
+ newcon->qual = (Node *) constraint;
+
+ tab->constraints = lappend(tab->constraints, newcon);
+ }
+
+
+ }
/*
* transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
' ',
' ',
' ',
+ NULL,
expr, /* Tree form check constraint */
ccbin, /* Binary form check constraint */
ccsrc, /* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
#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/lsyscache.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
#include "utils/tqual.h"
***************
*** 55,61 ****
static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
!
/* ----------------------------------------------------------------
* Executor state and memory management functions
--- 59,68 ----
static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! ExprContext *econtext, List *index_exprs,
! Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
/* ----------------------------------------------------------------
* Executor state and memory management functions
***************
*** 1010,1016 **** ExecInsertIndexTuples(TupleTableSlot *slot,
Relation indexRelation = relationDescs[i];
IndexInfo *indexInfo;
IndexUniqueCheck checkUnique;
! bool isUnique;
if (indexRelation == NULL)
continue;
--- 1017,1023 ----
Relation indexRelation = relationDescs[i];
IndexInfo *indexInfo;
IndexUniqueCheck checkUnique;
! bool satisfiesConstraint;
if (indexRelation == NULL)
continue;
***************
*** 1075,1081 **** ExecInsertIndexTuples(TupleTableSlot *slot,
else
checkUnique = UNIQUE_CHECK_PARTIAL;
! isUnique =
index_insert(indexRelation, /* index relation */
values, /* array of index Datums */
isnull, /* null flags */
--- 1082,1088 ----
else
checkUnique = UNIQUE_CHECK_PARTIAL;
! satisfiesConstraint =
index_insert(indexRelation, /* index relation */
values, /* array of index Datums */
isnull, /* null flags */
***************
*** 1083,1089 **** ExecInsertIndexTuples(TupleTableSlot *slot,
heapRelation, /* heap relation */
checkUnique); /* type of uniqueness check to do */
! if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
{
/*
* The tuple potentially violates the uniqueness constraint,
--- 1090,1118 ----
heapRelation, /* heap relation */
checkUnique); /* type of uniqueness check to do */
! /*
! * Operator exclusion constraint check is simpler, because the
! * check is separated from the index insert.
! */
! if (indexInfo->ii_ExclusionConstraint != NULL)
! {
! bool errorOK = !indexRelation->rd_index->indimmediate;
!
! /*
! * An index for an operator exclusion constraint can't
! * also be UNIQUE.
! */
! satisfiesConstraint =
! index_check_constraint(heapRelation, indexRelation,
! slot, tupleid, values, isnull,
! indexInfo->ii_ExclusionConstraint,
! indexInfo->ii_ExpressionsState,
! econtext, errorOK);
! }
!
! if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! indexInfo->ii_ExclusionConstraint != NULL) &&
! !satisfiesConstraint)
{
/*
* The tuple potentially violates the uniqueness constraint,
***************
*** 1217,1219 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1246,1496 ----
MemoryContextSwitchTo(oldcontext);
}
+
+ bool
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ ItemPointer tupleid, Datum *values, bool *isnull,
+ int16 *exclusion_constraint, List *index_exprs,
+ ExprContext *econtext, bool errorOK)
+ {
+ IndexScanDesc index_scan;
+ HeapTuple tup;
+ ScanKeyData *scankeys;
+ int2 index_natts = index->rd_index->indnatts;
+ Oid *constr_procs;
+ SnapshotData DirtySnapshot;
+ int nkeys = 0;
+ int i;
+ bool found_self;
+ bool conflict = false;
+ TupleTableSlot *existing_slot;
+
+ /*
+ * If any of the input values are NULL, the constraint check must
+ * pass.
+ */
+ for (i = 0; i < index_natts; i++)
+ if (isnull[i])
+ return true;
+
+ /*
+ * Find the function that tests for a conflict based on the
+ * strategy number, operator family, and types.
+ */
+ constr_procs = palloc(sizeof(Oid) * index_natts);
+ for (i = 0; i < index_natts; i++)
+ {
+ /*
+ * Find the procedure implementing the strategy for the
+ * index for two arguments both with the type of the
+ * indexed attribute.
+ */
+ Oid oper;
+ Oid opfamily = index->rd_opfamily[i];
+ Oid typoid = index->rd_opcintype[i];
+ StrategyNumber strategy = exclusion_constraint[i];
+
+ if (strategy == InvalidStrategy)
+ continue;
+
+ oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+
+ if(OidIsValid(oper))
+ constr_procs[i] = get_opcode(oper);
+ else
+ elog(ERROR, "cannot determine operator for type %d and "
+ "strategy %d", typoid, strategy);
+ }
+
+ /*
+ * Now search the tuples that are actually in the index for
+ * any violations.
+ */
+
+ scankeys = palloc(index_natts * sizeof(ScanKeyData));
+
+ for (i = 0; i < index_natts; i++)
+ {
+ Datum key_datum;
+
+ key_datum = values[i];
+
+ if (exclusion_constraint[i] == InvalidStrategy)
+ continue;
+
+ ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ constr_procs[i], key_datum);
+ nkeys++;
+ }
+
+ existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+
+ /*
+ * We have to find all tuples, even those not visible yet.
+ */
+ InitDirtySnapshot(DirtySnapshot);
+
+ retry:
+ found_self = false;
+ index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ scankeys);
+ while((tup = index_getnext(index_scan,
+ ForwardScanDirection)) != NULL)
+ {
+ TransactionId xwait;
+
+ if(ItemPointerEquals(tupleid, &tup->t_self))
+ {
+ Assert(!found_self);
+ found_self = true;
+ continue;
+ }
+
+ ExecStoreTuple(tup, existing_slot, index_scan->xs_cbuf, false);
+
+ if (index_scan->xs_recheck)
+ {
+ bool matches;
+
+ matches = index_recheck_constraint(
+ index, existing_slot, econtext, index_exprs, values,
+ constr_procs);
+
+ if (!matches)
+ continue; /* tuple doesn't actually match, so no conflict */
+ }
+
+ /*
+ * At this point we have either a conflict or a potential
+ * conflict.
+ */
+
+ 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
+ * restart the scan.
+ */
+ xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ DirtySnapshot.xmin : DirtySnapshot.xmax;
+
+ if (TransactionIdIsValid(xwait))
+ {
+ index_endscan(index_scan);
+ XactLockTableWait(xwait);
+ goto retry;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION),
+ errmsg("operator exclusion constraint violation detected: "
+ "\"%s\"", RelationGetRelationName(index)),
+ errdetail("Tuple \"%s\" conflicts with existing tuple "
+ "\"%s\".", tuple_as_string(new_slot),
+ tuple_as_string(existing_slot))));
+ }
+
+ Assert(conflict || found_self);
+
+ ExecDropSingleTupleTableSlot(existing_slot);
+
+ index_endscan(index_scan);
+
+ pfree(scankeys);
+
+ pfree(constr_procs);
+
+ return !conflict;
+ }
+
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ ExprContext *econtext, List *index_exprs,
+ Datum *new_values, Oid *constr_procs)
+ {
+ int index_natts = index->rd_index->indnatts;
+ int2 *index_keys = index->rd_index->indkey.values;
+ ListCell *lc = list_head(index_exprs);
+ int i;
+
+ for (i = 0; i < index_natts; i++)
+ {
+ Datum old_value;
+ bool isnull;
+
+ if (index_keys[i] == 0)
+ {
+ ExprState *exprstate;
+
+ Assert(lc != NULL);
+ exprstate = (ExprState *) lfirst(lc);
+
+ old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ lc = lnext(lc);
+ }
+ else
+ {
+ old_value = slot_getattr(slot, index_keys[i], &isnull);
+
+ /*
+ * Any null should cause the constraint to pass, so this
+ * recheck should immediately return false. Note: This
+ * isn't consistent in the case where the index search
+ * does match NULLs.
+ */
+ if (isnull)
+ return false;
+ }
+
+ if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ new_values[i])))
+ return false;
+ }
+
+ return true;
+ }
+
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ TupleDesc tupdesc = slot->tts_tupleDescriptor;
+ StringInfoData buf;
+ int i;
+
+ initStringInfo(&buf);
+ appendStringInfoString(&buf, "(");
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ char *strval;
+ Datum value;
+ bool isnull;
+
+ value = slot_getattr(slot, i + 1, &isnull);
+
+ if (isnull)
+ strval = "null";
+ else
+ {
+ Oid foutoid;
+ bool typisvarlena;
+
+ getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ &typisvarlena);
+ strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ }
+
+ if (i > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfoString(&buf, strval);
+ }
+
+ appendStringInfoChar(&buf, ')');
+
+ return buf.data;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2158,2163 **** _copyConstraint(Constraint *from)
--- 2158,2166 ----
COPY_NODE_FIELD(keys);
COPY_NODE_FIELD(options);
COPY_STRING_FIELD(indexspace);
+ COPY_STRING_FIELD(using_method);
+ COPY_NODE_FIELD(operator_exclusion);
+ COPY_NODE_FIELD(where_clause);
COPY_NODE_FIELD(pktable);
COPY_NODE_FIELD(fk_attrs);
COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2106,2111 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2106,2114 ----
COMPARE_NODE_FIELD(keys);
COMPARE_NODE_FIELD(options);
COMPARE_STRING_FIELD(indexspace);
+ COMPARE_STRING_FIELD(using_method);
+ COMPARE_NODE_FIELD(operator_exclusion);
+ COMPARE_NODE_FIELD(where_clause);
COMPARE_NODE_FIELD(pktable);
COMPARE_NODE_FIELD(fk_attrs);
COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2394,2399 **** _outConstraint(StringInfo str, Constraint *node)
--- 2394,2407 ----
WRITE_BOOL_FIELD(skip_validation);
break;
+ case CONSTR_OPERATOR_EXCLUSION:
+ appendStringInfo(str, "OPERATOR_EXCLUSION");
+ WRITE_STRING_FIELD(indexspace);
+ WRITE_STRING_FIELD(using_method);
+ WRITE_NODE_FIELD(operator_exclusion);
+ WRITE_NODE_FIELD(where_clause);
+ break;
+
case CONSTR_ATTR_DEFERRABLE:
appendStringInfo(str, "ATTR_DEFERRABLE");
break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 355,360 **** static TypeName *TableFuncTypeName(List *columns);
--- 355,361 ----
%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
+ exclusion_where_clause
%type func_arg_list
%type func_arg_expr
%type row type_list array_expr_list
***************
*** 435,440 **** static TypeName *TableFuncTypeName(List *columns);
--- 436,442 ----
%type opt_existing_window_name
%type opt_frame_clause frame_extent frame_bound
+ %type ExclusionConstraintList ExclusionConstraintElem
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
***************
*** 478,484 **** 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
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
--- 480,486 ----
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 EXCLUSION EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
***************
*** 2506,2511 **** ConstraintElem:
--- 2508,2528 ----
n->initdeferred = ($11 & 2) != 0;
$$ = (Node *)n;
}
+ | EXCLUSION access_method_clause '(' ExclusionConstraintList ')'
+ opt_definition OptConsTableSpace exclusion_where_clause
+ ConstraintAttributeSpec
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_OPERATOR_EXCLUSION;
+ n->using_method = $2;
+ n->operator_exclusion = $4;
+ n->options = $6;
+ n->indexspace = $7;
+ n->where_clause = $8;
+ n->deferrable = ($9 & 1) != 0;
+ n->initdeferred = ($9 & 2) != 0;
+ $$ = (Node *)n;
+ }
;
opt_column_list:
***************
*** 2546,2551 **** key_match: MATCH FULL
--- 2563,2585 ----
}
;
+ ExclusionConstraintList:
+ ExclusionConstraintElem { $$ = list_make1($1); }
+ | ExclusionConstraintList ',' ExclusionConstraintElem
+ { $$ = lappend($1, $3); }
+ ;
+
+ ExclusionConstraintElem: index_elem CHECK WITH any_operator
+ {
+ $$ = list_make2($1, $4);
+ }
+ ;
+
+ exclusion_where_clause:
+ 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
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 70,76 **** typedef struct
List *columns; /* ColumnDef items */
List *ckconstraints; /* CHECK constraints */
List *fkconstraints; /* FOREIGN KEY constraints */
! List *ixconstraints; /* index-creating constraints */
List *inh_indexes; /* cloned indexes from INCLUDING INDEXES */
List *blist; /* "before list" of things to do before
* creating the table */
--- 70,77 ----
List *columns; /* ColumnDef items */
List *ckconstraints; /* CHECK constraints */
List *fkconstraints; /* FOREIGN KEY constraints */
! List *idxconstraints; /* index-creating constraints */
! List *opxconstraints; /* operator exclusion constraints */
List *inh_indexes; /* cloned indexes from INCLUDING INDEXES */
List *blist; /* "before list" of things to do before
* creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
static void transformColumnType(ParseState *pstate, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void preprocessOpExConstraints(ParseState *pstate,
+ CreateStmtContext *cxt,
+ RangeVar *relation,
+ Constraint *constraint);
/*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
List *result;
List *save_alist;
ListCell *elements;
+ ListCell *lc;
+ List *opxlist = NIL;
/*
* We must not scribble on the passed-in CreateStmt, so copy it. (This is
***************
*** 174,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
! cxt.ixconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
--- 181,188 ----
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
! cxt.idxconstraints = NIL;
! cxt.opxconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
transformFKConstraints(pstate, &cxt, true, false);
/*
+ * Transform operator exclusion constraints into an
+ * AlterTableStmt.
+ */
+ if (cxt.opxconstraints != NIL)
+ {
+ AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+
+ alterstmt->relation = cxt.relation;
+ alterstmt->cmds = NIL;
+ alterstmt->relkind = OBJECT_TABLE;
+
+ foreach (lc, cxt.opxconstraints)
+ {
+ Constraint *constraint = (Constraint *) lfirst(lc);
+ AlterTableCmd *altercmd = makeNode(AlterTableCmd);
+
+ Assert(IsA(constraint, Constraint));
+ Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+
+ /*
+ * Don't need to validate against existing rows during
+ * creation.
+ */
+ constraint->skip_validation = true;
+
+ altercmd->subtype = AT_AddConstraint;
+ altercmd->name = NULL;
+ altercmd->def = (Node *) constraint;
+ alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ }
+
+ opxlist = list_make1(alterstmt);
+ }
+
+ /*
* Output results.
*/
stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
result = list_concat(result, save_alist);
+ result = list_concat(result, opxlist);
return result;
}
***************
*** 460,466 **** transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
case CONSTR_UNIQUE:
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
! cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
case CONSTR_CHECK:
--- 504,510 ----
case CONSTR_UNIQUE:
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
! cxt->idxconstraints = lappend(cxt->idxconstraints, constraint);
break;
case CONSTR_CHECK:
***************
*** 503,509 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
{
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
! cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
case CONSTR_CHECK:
--- 547,553 ----
{
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
! cxt->idxconstraints = lappend(cxt->idxconstraints, constraint);
break;
case CONSTR_CHECK:
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
+ case CONSTR_OPERATOR_EXCLUSION:
+ cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ break;
+
case CONSTR_NULL:
case CONSTR_NOTNULL:
case CONSTR_DEFAULT:
***************
*** 730,735 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 778,789 ----
/* Build CREATE INDEX statement to recreate the parent_index */
index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
+ if (index_stmt == NULL)
+ {
+ index_close(parent_index, AccessShareLock);
+ continue;
+ }
+
/* Copy comment on index */
if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
{
***************
*** 868,873 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 922,937 ----
elog(ERROR, "cache lookup failed for relation %u", source_relid);
idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+ /*
+ * Skip indexes for operator exclusion constraints, those should
+ * not be copied when INCLUDING INDEXES is specified.
+ */
+ if (idxrelrec->relopxconstraints != 0)
+ {
+ ReleaseSysCache(ht_idxrel);
+ return NULL;
+ }
+
/* Fetch pg_index tuple for source index from relcache entry */
ht_idx = source_idx->rd_indextuple;
idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1099,1105 **** transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
* KEY, mark each column as NOT NULL and create an index. For UNIQUE,
* create an index as for PRIMARY KEY, but do not insist on NOT NULL.
*/
! foreach(lc, cxt->ixconstraints)
{
Constraint *constraint = (Constraint *) lfirst(lc);
--- 1163,1169 ----
* KEY, mark each column as NOT NULL and create an index. For UNIQUE,
* create an index as for PRIMARY KEY, but do not insist on NOT NULL.
*/
! foreach(lc, cxt->idxconstraints)
{
Constraint *constraint = (Constraint *) lfirst(lc);
***************
*** 1837,1843 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
! cxt.ixconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
--- 1901,1908 ----
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
! cxt.idxconstraints = NIL;
! cxt.opxconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
***************
*** 1885,1890 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1950,1958 ----
*/
if (IsA(cmd->def, Constraint))
{
+ preprocessOpExConstraints(pstate, &cxt, stmt->relation,
+ (Constraint *) cmd->def);
+
transformTableConstraint(pstate, &cxt,
(Constraint *) cmd->def);
if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1943,1949 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
}
cxt.alist = NIL;
! /* Append any CHECK or FK constraints to the commands list */
foreach(l, cxt.ckconstraints)
{
newcmd = makeNode(AlterTableCmd);
--- 2011,2020 ----
}
cxt.alist = NIL;
! /*
! * Append any CHECK, FK or operator exclusion constraints to the
! * commands list
! */
foreach(l, cxt.ckconstraints)
{
newcmd = makeNode(AlterTableCmd);
***************
*** 1958,1963 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2029,2041 ----
newcmd->def = (Node *) lfirst(l);
newcmds = lappend(newcmds, newcmd);
}
+ foreach(l, cxt.opxconstraints)
+ {
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_AddConstraint;
+ newcmd->def = (Node *) lfirst(l);
+ newcmds = lappend(newcmds, newcmd);
+ }
/* Close rel but keep lock */
relation_close(rel, NoLock);
***************
*** 2250,2252 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2328,2381 ----
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+ static void
+ preprocessOpExConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ RangeVar *relation, Constraint *constraint)
+ {
+ ListCell *lc;
+ RangeTblEntry *rte;
+
+ /*
+ * Put the parent table into the rtable so that the expressions can refer
+ * to its fields without qualification.
+ */
+ rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+
+ addRTEtoQuery(pstate, rte, false, true, true);
+
+ /* preprocess index expressions */
+ foreach(lc, constraint->operator_exclusion)
+ {
+ List *pair = lfirst(lc);
+ IndexElem *ielem;
+
+ Assert(list_length(pair) == 2);
+
+ ielem = linitial(pair);
+ Assert(IsA(ielem, IndexElem));
+
+ if (ielem->expr)
+ {
+ ielem->expr = transformExpr(pstate, ielem->expr);
+
+ /*
+ * We check only that the result type is legitimate; this
+ * is for consistency with what transformWhereClause()
+ * checks for the predicate. DefineIndex() will make more
+ * checks.
+ */
+ if (expression_returns_set(ielem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("index expression cannot return a set")
+ ));
+ }
+ }
+
+ /* preprocess index predicate */
+ if (constraint->where_clause)
+ constraint->where_clause = transformWhereClause(
+ pstate, constraint->where_clause, "WHERE");
+ }
+
*** a/src/backend/storage/ipc/ipci.c
--- b/src/backend/storage/ipc/ipci.c
***************
*** 15,20 ****
--- 15,21 ----
#include "postgres.h"
#include "access/clog.h"
+ #include "access/genam.h"
#include "access/heapam.h"
#include "access/multixact.h"
#include "access/nbtree.h"
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->options,
+ NULL,
stmt->unique,
stmt->primary,
stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 142,147 **** static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
--- 142,149 ----
static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
+ static void decompile_column_strategy_array(Datum column_strategy_array,
+ Oid indexOid, StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
bool attrsOnly, bool showTblSpc,
***************
*** 1193,1198 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1195,1221 ----
break;
}
+ case CONSTRAINT_OPX:
+ {
+ Datum val;
+ bool isnull;
+ Oid indexOid = conForm->conindid;
+
+ /* Fetch constraint expression in parsetree form */
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_constrategies,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null conbin for constraint %u",
+ constraintId);
+
+ appendStringInfo(&buf, "(");
+ decompile_column_strategy_array(val, indexOid, &buf);
+ appendStringInfo(&buf, ") USING INDEX %s",
+ quote_identifier(get_rel_name(indexOid)));
+
+ break;
+ }
default:
elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
break;
***************
*** 1240,1245 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1263,1316 ----
}
}
+ /*
+ * Convert an int16[] Datum into a comma-separated list of column
+ * names and operators for the indicated index; append the list to
+ * buf.
+ */
+ static void
+ decompile_column_strategy_array(Datum column_strategy_array, Oid indexOid,
+ StringInfo buf)
+ {
+ Datum *keys;
+ int nKeys;
+ int j;
+ Relation indexRelation;
+
+ /* Extract data from array of int16 */
+ deconstruct_array(DatumGetArrayTypeP(column_strategy_array),
+ INT2OID, 2, true, 's',
+ &keys, NULL, &nKeys);
+
+ indexRelation = relation_open(indexOid, AccessShareLock);
+
+ for (j = 0; j < nKeys; j++)
+ {
+ Oid opid;
+ char *opName;
+ Oid opfamily = indexRelation->rd_opfamily[j];
+ char *colName = get_relid_attribute_name(indexOid, j + 1);
+ Oid colType = indexRelation->rd_opcintype[j];
+ int16 strategy = DatumGetInt16(keys[j]);
+
+ opid = get_opfamily_member(opfamily, colType, colType, strategy);
+ opName = get_opname(opid);
+
+ if (colName == NULL || opName == NULL)
+ elog(ERROR, "unexpected error: cannot determine column and "
+ "operator names");
+
+ if (j == 0)
+ appendStringInfo(buf, "%s %s", quote_identifier(colName),
+ quote_identifier(opName));
+ else
+ appendStringInfo(buf, ", %s %s", quote_identifier(colName),
+ quote_identifier(opName));
+ }
+
+ relation_close(indexRelation, NoLock);
+ }
+
/* ----------
* get_expr - Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
#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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3039,3119 ----
}
/*
+ * Load any operator exclusion constraints for the relation.
+ */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ Relation conrel;
+ SysScanDesc conscan;
+ ScanKeyData skey[1];
+ HeapTuple htup;
+ Datum val;
+ bool isnull;
+ bool found = false;
+ int16 *constraints = NULL;
+ Oid relid = indexRelation->rd_index->indrelid;
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+
+ conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ SnapshotNow, 1, skey);
+
+ while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ {
+ Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(htup);
+ ArrayType *arr;
+ int nelem;
+
+ /* We want check constraints only */
+ if (conform->contype != CONSTRAINT_OPX)
+ continue;
+
+ if (conform->conindid != indexRelation->rd_id)
+ continue;
+
+ if (found)
+ elog(ERROR, "unexpected operator exclusion constraint record "
+ "found for rel %s", RelationGetRelationName(indexRelation));
+
+ val = fastgetattr(htup,
+ Anum_pg_constraint_constrategies,
+ conrel->rd_att, &isnull);
+ if (isnull)
+ elog(ERROR, "null constrategies for rel %s",
+ RelationGetRelationName(indexRelation));
+
+ arr = DatumGetArrayTypeP(val); /* ensure not toasted */
+ nelem = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ nelem != indexRelation->rd_rel->relnatts ||
+ nelem > INDEX_MAX_KEYS ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != INT2OID)
+ elog(ERROR, "constrategies is not a 1-D smallint array");
+ constraints = palloc(sizeof(int16) * nelem);
+ memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ if ((Pointer) arr != DatumGetPointer(val))
+ pfree(arr); /* free de-toasted copy, if any */
+
+ found = true;
+ }
+
+ systable_endscan(conscan);
+ heap_close(conrel, AccessShareLock);
+
+ if (!found)
+ elog(ERROR, "constraint record missing for rel %s",
+ RelationGetRelationName(indexRelation));
+
+ return constraints;
+ }
+
+ /*
* RelationGetIndexList -- get a list of OIDs of indexes on this relation
*
* The index list is created only if someone requests it. We scan pg_index
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
struct
{
int16 checks;
+ int16 opxconstraints;
char relkind;
bool hasindex;
bool hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
! if (pset.sversion >= 80400)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
initPQExpBuffer(&tmpbuf);
/* Get general table info */
! if (pset.sversion >= 80500)
! {
! printfPQExpBuffer(&buf,
! "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! "c.relhastriggers, c.relhasoids, "
! "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
strdup(PQgetvalue(res, 0, 6)) : 0;
tableinfo.tablespace = (pset.sversion >= 80000) ?
atooid(PQgetvalue(res, 0, 7)) : 0;
+ tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ atoi(PQgetvalue(res, 0, 8)) : 0;
PQclear(res);
res = NULL;
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
PQclear(result);
}
+ /* print operator exclusion constraints */
+ if (tableinfo.opxconstraints)
+ {
+ 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,
+ _("Operator 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)
{
*** a/src/include/access/genam.h
--- b/src/include/access/genam.h
***************
*** 16,21 ****
--- 16,23 ----
#include "access/sdir.h"
#include "access/skey.h"
+ #include "access/xact.h"
+ #include "executor/tuptable.h"
#include "nodes/tidbitmap.h"
#include "storage/buf.h"
#include "storage/lock.h"
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid 26 0 0 4 -7 0 -1 -1 t p i t f f t 0 _null_));
{ 1259, {"relkind"}, 18, -1, 0, 1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relnatts"}, 21, -1, 0, 2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
{ 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 } }
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_));
--- 424,438 ----
{ 1259, {"relkind"}, 18, -1, 0, 1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relnatts"}, 21, -1, 0, 2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relchecks"}, 21, -1, 0, 2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"}, 21, -1, 0, 2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"}, 16, -1, 0, 1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"}, 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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp 16 -1 0 1 14 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relkind 18 -1 0 1 15 0 -1 -1 t p c t f f t 0 _null_));
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 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_));
--- 451,465 ----
DATA(insert ( 1259 relkind 18 -1 0 1 15 0 -1 -1 t p c t f f t 0 _null_));
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 relopxconstraints 21 -1 0 2 18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids 16 -1 0 1 19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey 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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
* contain entries with negative attnums for system attributes.
*/
int2 relchecks; /* # of CHECK constraints for class */
+ int2 relopxconstraints; /* # of opx constraints for class */
bool relhasoids; /* T if we generate OIDs for rows of rel */
bool relhaspkey; /* has (or has had) PRIMARY KEY index */
bool relhasrules; /* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
! #define Natts_pg_class 25
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
--- 88,94 ----
* ----------------
*/
! #define Natts_pg_class 26
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relkind 15
#define Anum_pg_class_relnatts 16
#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
/* ----------------
* initial contents of pg_class
--- 106,120 ----
#define Anum_pg_class_relkind 15
#define Anum_pg_class_relnatts 16
#define Anum_pg_class_relchecks 17
! #define Anum_pg_class_relopxconstraints 18
! #define Anum_pg_class_relhasoids 19
! #define Anum_pg_class_relhaspkey 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,136 **** 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_ ));
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_ ));
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_ ));
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_ ));
DESCR("");
#define RELKIND_INDEX 'i' /* secondary index */
--- 126,138 ----
*/
/* 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 0 t 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 0 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 0 t 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 26 0 0 t f f f f 3 _null_ _null_ ));
DESCR("");
#define RELKIND_INDEX 'i' /* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
Oid conffeqop[1];
/*
+ * If constraint is an operator exclusion constraint, these are
+ * the strategy numbers used for constraint. The size of the array
+ * is equal to the number of attributes in the index referenced by
+ * conindid.
+ */
+ int2 constrategies[1];
+
+ /*
* If a check constraint, nodeToString representation of expression
*/
text conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
! #define Natts_pg_constraint 21
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
--- 149,155 ----
* compiler constants for pg_constraint
* ----------------
*/
! #define Natts_pg_constraint 22
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
***************
*** 161,168 **** 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
/* Valid values for contype */
--- 169,177 ----
#define Anum_pg_constraint_conpfeqop 17
#define Anum_pg_constraint_conppeqop 18
#define Anum_pg_constraint_conffeqop 19
! #define Anum_pg_constraint_constrategies 20
! #define Anum_pg_constraint_conbin 21
! #define Anum_pg_constraint_consrc 22
/* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
#define CONSTRAINT_FOREIGN 'f'
#define CONSTRAINT_PRIMARY 'p'
#define CONSTRAINT_UNIQUE 'u'
+ #define CONSTRAINT_OPX 'x'
/*
* Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
+ const int16 *exclusion_constraint,
Node *conExpr,
const char *conBin,
const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
/* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
char *indexRelationName,
Oid indexRelationId,
char *accessMethodName,
--- 18,24 ----
/* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
char *indexRelationName,
Oid indexRelationId,
char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
List *attributeList,
Expr *predicate,
List *options,
+ int16 *exclusion_constraint,
bool unique,
bool primary,
bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
extern char *ChooseRelationName(const char *name1, const char *name2,
const char *label, Oid namespaceid);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,338 ----
extern void UnregisterExprContextCallback(ExprContext *econtext,
ExprContextCallbackFunction function,
Datum arg);
+ extern bool index_check_constraint(Relation heap, Relation index,
+ TupleTableSlot *new_slot,
+ ItemPointer tupleid, Datum *values,
+ bool *isnull, int16 *exclusion_constraint,
+ List *index_exprs, ExprContext *econtext,
+ bool errorOK);
#endif /* EXECUTOR_H */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
List *ii_ExpressionsState; /* list of ExprState */
List *ii_Predicate; /* list of Expr */
List *ii_PredicateState; /* list of ExprState */
+ int16 *ii_ExclusionConstraint;
bool ii_Unique;
bool ii_ReadyForInserts;
bool ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1388,1393 **** typedef enum ConstrType /* types of constraints */
--- 1388,1394 ----
CONSTR_CHECK,
CONSTR_PRIMARY,
CONSTR_UNIQUE,
+ CONSTR_OPERATOR_EXCLUSION,
CONSTR_FOREIGN,
CONSTR_ATTR_DEFERRABLE, /* attributes for previous constraint node */
CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1422,1432 **** 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): */
List *keys; /* String nodes naming referenced column(s) */
List *options; /* options from WITH clause */
char *indexspace; /* index tablespace; NULL for default */
/* Fields used for FOREIGN KEY constraints: */
RangeVar *pktable; /* Primary key table */
List *fk_attrs; /* Attributes of foreign key */
--- 1423,1438 ----
Node *raw_expr; /* expr, as untransformed parse tree */
char *cooked_expr; /* expr, as nodeToString representation */
! /* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
List *keys; /* String nodes naming referenced column(s) */
List *options; /* options from WITH clause */
char *indexspace; /* index tablespace; NULL for default */
+ /* Fields used for index constraints: */
+ List *operator_exclusion; /* list of (colname, operator) pairs */
+ char *using_method; /* access method for this constraint */
+ Node *where_clause; /* predicate for exclusion constraint */
+
/* Fields used for FOREIGN KEY constraints: */
RangeVar *pktable; /* Primary key table */
List *fk_attrs; /* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 144,149 **** PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
--- 144,150 ----
PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
+ PG_KEYWORD("exclusion", EXCLUSION, UNRESERVED_KEYWORD)
PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD)
*** a/src/include/storage/lwlock.h
--- b/src/include/storage/lwlock.h
***************
*** 67,72 **** typedef enum LWLockId
--- 67,73 ----
AutovacuumLock,
AutovacuumScheduleLock,
SyncScanLock,
+ IndexConstraintLock,
/* Individual lock IDs end here */
FirstBufMappingLock,
FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 15,20 ****
--- 15,21 ----
#define RELCACHE_H
#include "access/tupdesc.h"
+ #include "access/skey.h"
#include "nodes/bitmapset.h"
#include "nodes/pg_list.h"
***************
*** 43,48 **** extern Oid RelationGetOidIndex(Relation relation);
--- 44,50 ----
extern List *RelationGetIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
extern void RelationSetIndexList(Relation relation,
List *indexIds, Oid oidIndex);
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
SELECT * FROM unique_tbl;
DROP TABLE unique_tbl;
+
+ CREATE TABLE circles (
+ c1 CIRCLE,
+ c2 TEXT,
+ EXCLUSION USING gist
+ (c1 CHECK WITH &&, (c2::circle) CHECK 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>');
+ -- should fail
+ 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 EXCLUSION USING gist
+ (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+
+ DROP TABLE circles;
+
+
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 267,273 **** SELECT * FROM INSERT_TBL;
CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
CONSTRAINT COPY_CON
CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
! COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
SELECT '' AS two, * FROM COPY_TBL;
two | x | y | z
-----+---+---------------+---
--- 267,273 ----
CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
CONSTRAINT COPY_CON
CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
! COPY COPY_TBL FROM '/home/jdavis/wd/git/postgresql/src/test/regress/data/constro.data';
SELECT '' AS two, * FROM COPY_TBL;
two | x | y | z
-----+---+---------------+---
***************
*** 275,281 **** SELECT '' AS two, * FROM COPY_TBL;
| 6 | OK | 4
(2 rows)
! COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
ERROR: new row for relation "copy_tbl" violates check constraint "copy_con"
CONTEXT: COPY copy_tbl, line 2: "7 check failed 6"
SELECT * FROM COPY_TBL;
--- 275,281 ----
| 6 | OK | 4
(2 rows)
! COPY COPY_TBL FROM '/home/jdavis/wd/git/postgresql/src/test/regress/data/constrf.data';
ERROR: new row for relation "copy_tbl" violates check constraint "copy_con"
CONTEXT: COPY copy_tbl, line 2: "7 check failed 6"
SELECT * FROM COPY_TBL;
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
(5 rows)
DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+ c1 CIRCLE,
+ c2 TEXT,
+ EXCLUSION USING gist
+ (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=)
+ WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE: ALTER TABLE / ADD EXCLUSION 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR: operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL: Tuple "(<(20,20),10>, <(0,0), 5>)" conflicts with existing tuple "(<(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 EXCLUSION USING gist
+ (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ NOTICE: ALTER TABLE / ADD EXCLUSION will create implicit index "circles_c1_exclusion1" for table "circles"
+ ERROR: operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL: Tuple "(<(0,0),5>, <(0,0), 5>)" conflicts with existing tuple "(<(0,0),5>, <(0,0), 5>)".
+ DROP TABLE circles;