*** a/doc/src/sgml/ref/create_table.sgml --- b/doc/src/sgml/ref/create_table.sgml *************** *** 51,63 **** 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: [ WITH ( storage_parameter [= value] [, ... ] ) ] [ USING INDEX TABLESPACE tablespace ] --- 51,69 ---- 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 ] | ! EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] } [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] index_parameters in UNIQUE and PRIMARY KEY constraints are: [ WITH ( storage_parameter [= value] [, ... ] ) ] [ USING INDEX TABLESPACE tablespace ] + + and exclude_element in an EXCLUDE constraint is: + + { column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] + *************** *** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] + + + The EXCLUDE clause specifies an operator exclusion + constraint. An operator exclusion constraint guarantees that if + any two tuples are compared on the specified columns or + expressions using the specified operators, at least one such + comparison will return FALSE. If all of the + specified operators test for equality, it is equivalent to a + UNIQUE constraint, although an ordinary unique constraint will + normally be faster. However, operator exclusion constraints can + use index methods other than btree (such as GiST + (see ), and can specify more general + constraints. For instance, you can specify the constraint that + no two tuples in the table contain overlapping circles + (see ) by using the + && operator. + + + + Operator exclusion constraints are implemented internally using + an index, so the specified operators must be associated with an + appropriate operator class + (see ) for access + method index_method, and the access method must + support amgettuple (see for details). + The operators are also required to be their own commutators + (see ). + + + + The predicate allows you to + specify a constraint on a subset of the table, internally using + a partial index. Note the require perentheses around the + predicate. + + + + + DEFERRABLE NOT DEFERRABLE *************** *** 1111,1116 **** CREATE TABLE cinemas ( --- 1157,1174 ---- + + Create table circles with an operator exclusion + constraint that prevents overlapping circles within it: + + + CREATE TABLE circles ( + c circle, + EXCLUDE USING gist (c WITH &&) + ); + + + *** a/src/backend/access/index/genam.c --- b/src/backend/access/index/genam.c *************** *** 144,155 **** 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 i; --- 144,149 ---- *************** *** 170,176 **** BuildIndexValueDescription(Relation indexRelation, Oid foutoid; bool typisvarlena; ! getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid, &typisvarlena); val = OidOutputFunctionCall(foutoid, values[i]); } --- 164,174 ---- Oid foutoid; bool typisvarlena; ! /* ! * Don't use the tupdesc for the type information, because ! * that might be different for non-btree opclasses. ! */ ! getTypeOutputInfo(indexRelation->rd_opcintype[i], &foutoid, &typisvarlena); val = OidOutputFunctionCall(foutoid, values[i]); } *** 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 *** 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,1111 ---- copyObject(indexInfo->ii_Predicate); newind->il_info->ii_PredicateState = NIL; + /* no operator exclusion constraints exist at bootstrap time */ + newind->il_info->ii_ExclusionConstraintOps = NULL; + newind->il_info->ii_ExclusionConstraintProcs = NULL; + newind->il_info->ii_ExclusionConstraintStrats = 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_ExclusionConstraintOps != NULL) + constraintType = CONSTRAINT_OPX; else { ! elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE"); 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_ExclusionConstraintOps, NULL, /* no check constraint */ NULL, NULL, *************** *** 804,809 **** index_create(Oid heapRelationId, --- 812,875 ---- "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 { *************** *** 1084,1089 **** BuildIndexInfo(Relation index) --- 1150,1169 ---- ii->ii_Unique = indexStruct->indisunique; ii->ii_ReadyForInserts = indexStruct->indisready; + if (index->rd_rel->relopxconstraints > 0) + { + RelationGetOpExclusionConstraints(index, + &ii->ii_ExclusionConstraintOps, + &ii->ii_ExclusionConstraintProcs, + &ii->ii_ExclusionConstraintStrats); + } + else + { + ii->ii_ExclusionConstraintOps = NULL; + ii->ii_ExclusionConstraintProcs = NULL; + ii->ii_ExclusionConstraintStrats = NULL; + } + /* initialize index-build state to default */ ii->ii_Concurrent = false; ii->ii_BrokenHotChain = false; *************** *** 1894,1899 **** IndexBuildHeapScan(Relation heapRelation, --- 1974,1984 ---- indexInfo->ii_ExpressionsState = NIL; indexInfo->ii_PredicateState = NIL; + /* operator exclusion constraints aren't checked at index build time */ + indexInfo->ii_ExclusionConstraintOps = NULL; + indexInfo->ii_ExclusionConstraintProcs = NULL; + indexInfo->ii_ExclusionConstraintStrats = NULL; + return reltuples; } *************** *** 2268,2273 **** validate_index_heapscan(Relation heapRelation, --- 2353,2366 ---- /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; indexInfo->ii_PredicateState = NIL; + + /* + * Operator exclusion constraints aren't supported for concurrent + * index builds. + */ + indexInfo->ii_ExclusionConstraintOps = NULL; + indexInfo->ii_ExclusionConstraintProcs = NULL; + indexInfo->ii_ExclusionConstraintStrats = NULL; } *** 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 Oid *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 *conoperatorsArray = NULL; NameData cname; int i; ObjectAddress conobject; *************** *** 130,135 **** CreateConstraintEntry(const char *constraintName, --- 132,148 ---- conffeqopArray = NULL; } + if (exclusion_constraint != NULL) + { + Datum *opDatums = palloc(sizeof(Datum) * constraintNKeys); + + for (i = 0; i < constraintNKeys; i++) + opDatums[i] = ObjectIdGetDatum(exclusion_constraint[i]); + conoperatorsArray = construct_array(opDatums, + constraintNKeys, + OIDOID, sizeof(Oid), true, 'i'); + } + /* initialize nulls and values */ for (i = 0; i < Natts_pg_constraint; i++) { *************** *** 177,182 **** CreateConstraintEntry(const char *constraintName, --- 190,200 ---- else nulls[Anum_pg_constraint_conffeqop - 1] = true; + if (conoperatorsArray) + values[Anum_pg_constraint_conoperators - 1] = PointerGetDatum(conoperatorsArray); + else + nulls[Anum_pg_constraint_conoperators - 1] = true; + /* * initialize the binary form of the check constraint. */ *************** *** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId, --- 407,417 ---- 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; --- 547,554 ---- * 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); --- 563,582 ---- 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,254 ---- indexInfo->ii_Concurrent = false; indexInfo->ii_BrokenHotChain = false; + /* toast tables don't have operator exclusion constraints */ + indexInfo->ii_ExclusionConstraintOps = NULL; + indexInfo->ii_ExclusionConstraintProcs = NULL; + indexInfo->ii_ExclusionConstraintStrats = 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_ExclusionConstraintOps != 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,166 ---- * 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_ExclusionConstraintOps == NULL) ! { ! index_insert(indexRel, values, isnull, &(new_row->t_self), ! trigdata->tg_relation, UNIQUE_CHECK_EXISTING); ! } ! else ! { ! index_exclusion_constraint(trigdata->tg_relation, indexRel, ! slot, &(new_row->t_self), values, isnull, ! indexInfo, estate, 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, + Oid *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); *************** *** 423,428 **** DefineIndex(RangeVar *heapRelation, --- 433,441 ---- indexInfo->ii_ReadyForInserts = !concurrent; indexInfo->ii_Concurrent = concurrent; indexInfo->ii_BrokenHotChain = false; + indexInfo->ii_ExclusionConstraintOps = exclusion_constraint; + indexInfo->ii_ExclusionConstraintProcs = NULL; /* not needed */ + indexInfo->ii_ExclusionConstraintStrats = NULL; /* not needed */ classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16)); *************** *** 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 = "EXCLUDE"; + 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 EXCLUDE */ ! 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; CommandId mycid; BulkInsertState bistate; int hi_options; *************** *** 3103,3108 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) --- 3109,3117 ---- switch (con->contype) { + Relation indexRelation = NULL; + IndexInfo *indexInfo = NULL; + case CONSTR_CHECK: needscan = true; con->qualstate = (List *) *************** *** 3111,3116 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) --- 3120,3153 ---- 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); *************** *** 3155,3160 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) --- 3192,3199 ---- MemoryContext oldCxt; List *dropped_attrs = NIL; ListCell *lc; + Datum *idxvals = NULL; + bool *idxnulls = NULL; econtext = GetPerTupleExprContext(estate); *************** *** 3173,3178 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) --- 3212,3226 ---- 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 *************** *** 3198,3203 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) --- 3246,3252 ---- while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { + if (newrel) { Oid tupOid = InvalidOid; *************** *** 3268,3273 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) --- 3317,3323 ---- con->name))); break; case CONSTR_FOREIGN: + case CONSTR_OPERATOR_EXCLUSION: /* Nothing to do here */ break; default: *************** *** 3276,3281 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) --- 3326,3349 ---- } } + 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_exclusion_constraint(oldrel, indexRelation, newslot, + &tuple->t_self, idxvals, idxnulls, + indexInfo, estate, false); + } + /* Write the tuple out to the new relation */ if (newrel) heap_insert(newrel, tuple, mycid, hi_options, bistate); *************** *** 3290,3295 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) --- 3358,3376 ---- 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); *************** *** 4603,4608 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel, --- 4684,4690 ---- stmt->indexParams, /* parameters */ (Expr *) stmt->whereClause, stmt->options, + NULL, stmt->unique, stmt->primary, stmt->isconstraint, *************** *** 4666,4671 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, --- 4748,4773 ---- 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)))); + } + + ATAddOperatorExclusionConstraint(tab, rel, newConstraint); + break; + default: elog(ERROR, "unrecognized constraint type: %d", (int) newConstraint->contype); *************** *** 5035,5040 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, --- 5137,5143 ---- fkconstraint->fk_upd_action, fkconstraint->fk_del_action, fkconstraint->fk_matchtype, + NULL, NULL, /* no check constraint */ NULL, NULL, *************** *** 5071,5076 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, --- 5174,5366 ---- heap_close(pkrel, NoLock); } + static void + ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel, + Constraint *constraint) + { + int natts; + ListCell *lc; + HeapTuple tup; + Oid methodOid; + List *indexElems = NIL; + Oid *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 operator " + "exclusion constraints", + constraint->using_method), + errdetail("The index access method must support the " + "gettuple() interface to be used with an " + "operator exclusion constraint."))); + + natts = list_length(constraint->operator_exclusion); + + exclusion_constraint = palloc(sizeof(Oid) * 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 = compatible_oper_opid(opname, typoid, typoid, false); + + 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", format_operator(opid)), + errdetail("Set the operator's COMMUTATOR to be itself, " + "or choose a different operator.") + )); + } + + if (!op_in_opfamily(opid, opfamily)) + { + char *opclass_name; + HeapTuple tuple; + Form_pg_opclass opctup; + + tuple = SearchSysCache(CLAOID, + ObjectIdGetDatum(opclassid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("operator class with OID %u does not exist", + opclassid))); + + opctup = (Form_pg_opclass) GETSTRUCT(tuple); + opclass_name = pstrdup(opctup->opcname.data); + ReleaseSysCache(tuple); + + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("no strategy found for operator %s in operator " + "class %s", format_operator(opid), + quote_identifier(opclass_name)), + errdetail("The operator class must provide a strategy " + "number for the given operator.") + )); + } + + exclusion_constraint[i] = opid; + + i++; + } + + rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)), + pstrdup(RelationGetRelationName(rel)), + -1); + /* + * Build index to enforce the constraint. + */ + index_oid = DefineIndex(rv, /* relation range var */ + constraint->conname, /* 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,67 ---- 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, ! EState *estate, List *index_exprs, ! Datum *new_values, Oid *constr_procs); /* ---------------------------------------------------------------- * Executor state and memory management functions *************** *** 1011,1017 **** 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; *************** *** 1076,1082 **** 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 */ *************** *** 1084,1090 **** 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,1116 ---- 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_ExclusionConstraintOps != NULL) ! { ! bool errorOK = !indexRelation->rd_index->indimmediate; ! ! /* ! * An index for an operator exclusion constraint can't ! * also be UNIQUE. ! */ ! satisfiesConstraint = ! index_exclusion_constraint(heapRelation, indexRelation, ! slot, tupleid, values, isnull, ! indexInfo, estate, errorOK); ! } ! ! if ((checkUnique == UNIQUE_CHECK_PARTIAL || ! indexInfo->ii_ExclusionConstraintOps != NULL) && ! !satisfiesConstraint) { /* * The tuple potentially violates the uniqueness constraint, *************** *** 1218,1220 **** ShutdownExprContext(ExprContext *econtext, bool isCommit) --- 1244,1442 ---- MemoryContextSwitchTo(oldcontext); } + + bool + index_exclusion_constraint(Relation heap, Relation index, + TupleTableSlot *new_slot, ItemPointer tupleid, + Datum *values, bool *isnull, IndexInfo *indexInfo, + EState *estate, bool errorOK) + { + IndexScanDesc index_scan; + HeapTuple tup; + ScanKeyData *scankeys; + int2 index_natts = index->rd_index->indnatts; + SnapshotData DirtySnapshot; + int nkeys = 0; + int i; + bool found_self; + bool conflict = false; + TupleTableSlot *existing_slot; + + Oid *constr_procs = indexInfo->ii_ExclusionConstraintProcs; + uint16 *constr_strats = indexInfo->ii_ExclusionConstraintStrats; + List *index_exprs = indexInfo->ii_ExpressionsState; + + /* + * 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; + + /* + * Search the tuples that are in the index for any violations, + * including tuples that aren't visible yet. + */ + + InitDirtySnapshot(DirtySnapshot); + + scankeys = palloc(index_natts * sizeof(ScanKeyData)); + + for (i = 0; i < index_natts; i++) + { + Datum key_datum; + + key_datum = values[i]; + + ScanKeyInit(&scankeys[nkeys], i + 1, constr_strats[i], + constr_procs[i], key_datum); + nkeys++; + } + + existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap)); + + /* + * May have to restart scan from this point if a potential + * conflict is found. + */ + retry: + found_self = false; + index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys, + scankeys); + while((tup = index_getnext(index_scan, + ForwardScanDirection)) != NULL) + { + TransactionId xwait; + char *error_new = NULL; + char *error_existing = NULL; + + 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, estate, 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; + } + + error_new = BuildIndexValueDescription(index, values, isnull); + + { + Datum *existing_values = palloc(sizeof(Datum) * index_natts); + bool *existing_isnull = palloc(sizeof(bool) * index_natts); + ExprContext *econtext = GetPerTupleExprContext(estate); + + /* Going to error soon, so it's OK to change the scan tuple. */ + econtext->ecxt_scantuple = existing_slot; + FormIndexDatum(indexInfo, existing_slot, estate, existing_values, + existing_isnull); + + error_existing = BuildIndexValueDescription(index, existing_values, + existing_isnull); + } + + ereport(ERROR, + (errcode(ERRCODE_EXCLUSION_VIOLATION), + errmsg("conflicting key value violates operator exclusion " + "constraint \"%s\"", RelationGetRelationName(index)), + errdetail("Tuple \"%s\" conflicts with existing tuple " + "\"%s\".", error_new, error_existing))); + } + + Assert(conflict || found_self); + + ExecDropSingleTupleTableSlot(existing_slot); + + index_endscan(index_scan); + + pfree(scankeys); + + return !conflict; + } + + static bool + index_recheck_constraint(Relation index, TupleTableSlot *slot, + EState *estate, 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); + ExprContext *econtext = GetPerTupleExprContext(estate); + 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; + } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 2159,2164 **** _copyConstraint(Constraint *from) --- 2159,2167 ---- 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 *************** *** 2105,2110 **** _equalConstraint(Constraint *a, Constraint *b) --- 2105,2113 ---- 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 *************** *** 2398,2403 **** _outConstraint(StringInfo str, Constraint *node) --- 2398,2411 ---- 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 *************** *** 352,357 **** static TypeName *TableFuncTypeName(List *columns); --- 352,358 ---- %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 *************** *** 432,437 **** static TypeName *TableFuncTypeName(List *columns); --- 433,439 ---- %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. *************** *** 475,481 **** 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 --- 477,483 ---- 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 ! 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,1598 **** alter_table_cmds: ; alter_table_cmd: ! /* ALTER TABLE ADD [COLUMN] */ ! ADD_P opt_column columnDef { AlterTableCmd *n = makeNode(AlterTableCmd); n->subtype = AT_AddColumn; --- 1593,1608 ---- ; alter_table_cmd: ! /* 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; *************** *** 2504,2509 **** ConstraintElem: --- 2514,2534 ---- n->initdeferred = ($11 & 2) != 0; $$ = (Node *)n; } + | EXCLUDE 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: *************** *** 2544,2549 **** key_match: MATCH FULL --- 2569,2591 ---- } ; + ExclusionConstraintList: + ExclusionConstraintElem { $$ = list_make1($1); } + | ExclusionConstraintList ',' ExclusionConstraintElem + { $$ = lappend($1, $3); } + ; + + ExclusionConstraintElem: index_elem WITH any_operator + { + $$ = list_make2($1, $3); + } + ; + + 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 *************** *** 10619,10624 **** unreserved_keyword: --- 10661,10667 ---- | ENCRYPTED | ENUM_P | ESCAPE + | EXCLUDE | EXCLUDING | EXCLUSIVE | EXECUTE *** a/src/backend/parser/parse_utilcmd.c --- b/src/backend/parser/parse_utilcmd.c *************** *** 71,76 **** typedef struct --- 71,77 ---- List *ckconstraints; /* CHECK constraints */ List *fkconstraints; /* FOREIGN KEY constraints */ List *ixconstraints; /* 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 transformOpxConstraints(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 *************** *** 175,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString) --- 182,188 ---- cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = 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; } *************** *** 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: *************** *** 734,739 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, --- 782,793 ---- /* 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) { *************** *** 872,877 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx, --- 926,941 ---- 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); *************** *** 1842,1847 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) --- 1906,1912 ---- cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; + cxt.opxconstraints = NIL; cxt.inh_indexes = NIL; cxt.blist = NIL; cxt.alist = NIL; *************** *** 1889,1894 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) --- 1954,1962 ---- */ if (IsA(cmd->def, Constraint)) { + transformOpxConstraints(pstate, &cxt, stmt->relation, + (Constraint *) cmd->def); + transformTableConstraint(pstate, &cxt, (Constraint *) cmd->def); if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN) *************** *** 1947,1953 **** 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); --- 2015,2024 ---- } cxt.alist = NIL; ! /* ! * Append any CHECK, FK or operator exclusion constraints to the ! * commands list ! */ foreach(l, cxt.ckconstraints) { newcmd = makeNode(AlterTableCmd); *************** *** 1962,1967 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) --- 2033,2045 ---- 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); *************** *** 2254,2256 **** setSchemaName(char *context_schema, char **stmt_schema_name) --- 2332,2385 ---- "different from the one being created (%s)", *stmt_schema_name, context_schema))); } + + static void + transformOpxConstraints(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/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 *************** *** 148,153 **** static char *pg_get_indexdef_worker(Oid indexrelid, int colno, --- 148,155 ---- int prettyFlags); static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, int prettyFlags); + static char *pg_get_opxdef_worker(Oid indexrelid, Oid *operators, + int prettyFlags); static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname, int prettyFlags); static int print_function_arguments(StringInfo buf, HeapTuple proctup, *************** *** 1244,1249 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, --- 1246,1282 ---- break; } + case CONSTRAINT_OPX: + { + bool isnull; + Oid indexOid = conForm->conindid; + Datum val; + Datum *keys; + int nKeys; + int i; + Oid *operators; + + val = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_conoperators, + &isnull); + if (isnull) + elog(ERROR, "null conoperators for constraint %u", + constraintId); + + deconstruct_array(DatumGetArrayTypeP(val), + OIDOID, sizeof(Oid), true, 'i', + &keys, NULL, &nKeys); + + operators = palloc(nKeys * sizeof(Oid)); + for(i = 0; i < nKeys; i++) + operators[i] = DatumGetObjectId(keys[i]); + + appendStringInfo(&buf, pg_get_opxdef_worker(indexOid, + operators, + prettyFlags)); + + break; + } default: elog(ERROR, "invalid constraint type \"%c\"", conForm->contype); break; *************** *** 1291,1296 **** decompile_column_index_array(Datum column_index_array, Oid relId, --- 1324,1558 ---- } } + static char * + pg_get_opxdef_worker(Oid indexrelid, Oid *operators, int prettyFlags) + { + HeapTuple ht_idx; + HeapTuple ht_idxrel; + HeapTuple ht_am; + Form_pg_index idxrec; + Form_pg_class idxrelrec; + Form_pg_am amrec; + List *indexprs; + ListCell *indexpr_item; + List *context; + Oid indrelid; + int keyno; + Oid keycoltype; + Datum indclassDatum; + Datum indoptionDatum; + bool isnull; + oidvector *indclass; + int2vector *indoption; + StringInfoData buf; + char *str; + char *sep; + Oid tblspc; + + /* + * Fetch the pg_index tuple by the Oid of the index + */ + ht_idx = SearchSysCache(INDEXRELID, + ObjectIdGetDatum(indexrelid), + 0, 0, 0); + if (!HeapTupleIsValid(ht_idx)) + elog(ERROR, "cache lookup failed for index %u", indexrelid); + idxrec = (Form_pg_index) GETSTRUCT(ht_idx); + + indrelid = idxrec->indrelid; + Assert(indexrelid == idxrec->indexrelid); + + /* Must get indclass and indoption the hard way */ + indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indclass, &isnull); + Assert(!isnull); + indclass = (oidvector *) DatumGetPointer(indclassDatum); + indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indoption, &isnull); + Assert(!isnull); + indoption = (int2vector *) DatumGetPointer(indoptionDatum); + + /* + * Fetch the pg_class tuple of the index relation + */ + ht_idxrel = SearchSysCache(RELOID, + ObjectIdGetDatum(indexrelid), + 0, 0, 0); + if (!HeapTupleIsValid(ht_idxrel)) + elog(ERROR, "cache lookup failed for relation %u", indexrelid); + idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel); + + /* + * Fetch the pg_am tuple of the index' access method + */ + ht_am = SearchSysCache(AMOID, + ObjectIdGetDatum(idxrelrec->relam), + 0, 0, 0); + if (!HeapTupleIsValid(ht_am)) + elog(ERROR, "cache lookup failed for access method %u", + idxrelrec->relam); + amrec = (Form_pg_am) GETSTRUCT(ht_am); + + /* + * Get the index expressions, if any. (NOTE: we do not use the relcache + * versions of the expressions and predicate, because we want to display + * non-const-folded expressions.) + */ + if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs)) + { + Datum exprsDatum; + bool isnull; + char *exprsString; + + exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indexprs, &isnull); + Assert(!isnull); + exprsString = TextDatumGetCString(exprsDatum); + indexprs = (List *) stringToNode(exprsString); + pfree(exprsString); + } + else + indexprs = NIL; + + indexpr_item = list_head(indexprs); + + context = deparse_context_for(get_rel_name(indrelid), indrelid); + + /* + * Start the index definition. Note that the index's name should never be + * schema-qualified, but the indexed rel's name may be. + */ + initStringInfo(&buf); + + appendStringInfo(&buf, "EXCLUDE USING %s (", + quote_identifier(NameStr(amrec->amname))); + + /* + * Report the indexed attributes + */ + sep = ""; + for (keyno = 0; keyno < idxrec->indnatts; keyno++) + { + AttrNumber attnum = idxrec->indkey.values[keyno]; + int16 opt = indoption->values[keyno]; + char *opName; + + appendStringInfoString(&buf, sep); + sep = ", "; + + if (attnum != 0) + { + /* Simple index column */ + char *attname; + + attname = get_relid_attribute_name(indrelid, attnum); + appendStringInfoString(&buf, quote_identifier(attname)); + keycoltype = get_atttype(indrelid, attnum); + } + else + { + /* expressional index */ + Node *indexkey; + + if (indexpr_item == NULL) + elog(ERROR, "too few entries in indexprs list"); + indexkey = (Node *) lfirst(indexpr_item); + indexpr_item = lnext(indexpr_item); + /* Deparse */ + str = deparse_expression_pretty(indexkey, context, false, false, + prettyFlags, 0); + + /* Need parens if it's not a bare function call */ + if (indexkey && IsA(indexkey, FuncExpr) && + ((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL) + appendStringInfoString(&buf, str); + else + appendStringInfo(&buf, "(%s)", str); + + keycoltype = exprType(indexkey); + } + + /* Add the operator class name, if not default */ + get_opclass_name(indclass->values[keyno], keycoltype, &buf); + + /* Add options if relevant */ + if (amrec->amcanorder) + { + /* if it supports sort ordering, report DESC and NULLS opts */ + if (opt & INDOPTION_DESC) + { + appendStringInfo(&buf, " DESC"); + /* NULLS FIRST is the default in this case */ + if (!(opt & INDOPTION_NULLS_FIRST)) + appendStringInfo(&buf, " NULLS LAST"); + } + else + { + if (opt & INDOPTION_NULLS_FIRST) + appendStringInfo(&buf, " NULLS FIRST"); + } + } + + /* Add operator exclusion constraint */ + appendStringInfo(&buf, " WITH "); + + opName = generate_operator_name(operators[keyno], keycoltype, + keycoltype); + + appendStringInfo(&buf, "%s", opName); + } + + appendStringInfoChar(&buf, ')'); + + /* + * If it has options, append "WITH (options)" + */ + str = flatten_reloptions(indexrelid); + if (str) + { + appendStringInfo(&buf, " WITH (%s)", str); + pfree(str); + } + + /* + * If it's in a nondefault tablespace, say so, but only if requested + */ + tblspc = get_rel_tablespace(indexrelid); + if (OidIsValid(tblspc)) + appendStringInfo(&buf, " USING INDEX TABLESPACE %s", + quote_identifier(get_tablespace_name(tblspc))); + + /* + * If it's a partial index, decompile and append the predicate + */ + if (!heap_attisnull(ht_idx, Anum_pg_index_indpred)) + { + Node *node; + Datum predDatum; + bool isnull; + char *predString; + + /* Convert text string to node tree */ + predDatum = SysCacheGetAttr(INDEXRELID, ht_idx, + Anum_pg_index_indpred, &isnull); + Assert(!isnull); + predString = TextDatumGetCString(predDatum); + node = (Node *) stringToNode(predString); + pfree(predString); + + /* Deparse */ + str = deparse_expression_pretty(node, context, false, false, + prettyFlags, 0); + appendStringInfo(&buf, " WHERE (%s)", str); + } + + /* Clean up */ + ReleaseSysCache(ht_idx); + ReleaseSysCache(ht_idxrel); + ReleaseSysCache(ht_am); + + return buf.data; + } /* ---------- * get_expr - Decompile an expression tree *** a/src/backend/utils/cache/relcache.c --- b/src/backend/utils/cache/relcache.c *************** *** 60,68 **** --- 60,70 ---- #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" *************** *** 3038,3043 **** CheckConstraintFetch(Relation relation) --- 3040,3147 ---- } /* + * Load any operator exclusion constraints for the relation. + */ + void + RelationGetOpExclusionConstraints(Relation indexRelation, Oid **operators, + Oid **procs, uint16 **strategies) + { + Relation conrel; + SysScanDesc conscan; + ScanKeyData skey[1]; + HeapTuple htup; + Datum val; + bool isnull; + bool found = false; + Oid relid = indexRelation->rd_index->indrelid; + Oid *ops = NULL; + Oid *funcs = NULL; + uint16 *strats = NULL; + + 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; + int i; + + /* 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_conoperators, + conrel->rd_att, &isnull); + if (isnull) + elog(ERROR, "null conoperators 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) != OIDOID) + elog(ERROR, "conoperators is not a 1-D Oid array"); + + ops = palloc(sizeof(Oid) * nelem); + funcs = palloc(sizeof(Oid) * nelem); + strats = palloc(sizeof(uint16) * nelem); + + memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * nelem); + + for (i = 0; i < nelem; i++) + { + funcs[i] = get_opcode(ops[i]); + if (!OidIsValid(funcs[i])) + elog(ERROR, "could not find function for operator: %d", + ops[i]); + + strats[i] = get_op_opfamily_strategy( + ops[i], indexRelation->rd_opfamily[i]); + if (strats[i] == InvalidStrategy) + elog(ERROR, "could not find strategy for operator %d in " + "family %d", ops[i], indexRelation->rd_opfamily[i]); + } + + 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)); + + *operators = ops; + *procs = funcs; + *strategies = strats; + + return; + } + + /* * 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/pg_dump/pg_backup_archiver.c --- b/src/bin/pg_dump/pg_backup_archiver.c *************** *** 2855,2860 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat --- 2855,2861 ---- strcmp(te->desc, "CONSTRAINT") == 0 || strcmp(te->desc, "DEFAULT") == 0 || strcmp(te->desc, "FK CONSTRAINT") == 0 || + strcmp(te->desc, "EXCLUSION CONSTRAINT") == 0 || strcmp(te->desc, "INDEX") == 0 || strcmp(te->desc, "RULE") == 0 || strcmp(te->desc, "TRIGGER") == 0 || *** a/src/bin/pg_dump/pg_dump.c --- b/src/bin/pg_dump/pg_dump.c *************** *** 3680,3685 **** getIndexes(TableInfo tblinfo[], int numTables) --- 3680,3686 ---- i_condeferred, i_contableoid, i_conoid, + i_condef, i_tablespace, i_options; int ntups; *************** *** 3710,3716 **** getIndexes(TableInfo tblinfo[], int numTables) * assume an index won't have more than one internal dependency. */ resetPQExpBuffer(query); ! if (g_fout->remoteVersion >= 80200) { appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, " --- 3711,3745 ---- * assume an index won't have more than one internal dependency. */ resetPQExpBuffer(query); ! if (g_fout->remoteVersion >= 80500) ! { ! 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, " ! "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " ! "c.oid AS conoid, " ! "(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, " *************** *** 3858,3863 **** getIndexes(TableInfo tblinfo[], int numTables) --- 3887,3893 ---- 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,3901 **** 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 we found a constraint matching the index, create an --- 3925,3931 ---- indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't'); contype = *(PQgetvalue(res, j, i_contype)); ! if (contype == 'p' || contype == 'u' || contype == 'x') { /* * If we found a constraint matching the index, create an *************** *** 3913,3919 **** getIndexes(TableInfo tblinfo[], int numTables) constrinfo[j].contable = tbinfo; constrinfo[j].condomain = NULL; constrinfo[j].contype = contype; ! constrinfo[j].condef = NULL; constrinfo[j].confrelid = InvalidOid; constrinfo[j].conindex = indxinfo[j].dobj.dumpId; constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't'; --- 3943,3952 ---- constrinfo[j].contable = tbinfo; constrinfo[j].condomain = NULL; constrinfo[j].contype = contype; ! 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'; *************** *** 10912,10917 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo) --- 10945,10975 ---- NULL, NULL); } } + else if (coninfo->contype == 'x') + { + appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n", + fmtId(tbinfo->dobj.name)); + appendPQExpBuffer(q, " ADD CONSTRAINT %s %s;\n", + fmtId(coninfo->dobj.name), + coninfo->condef); + + appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.", + fmtId(tbinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delq, "%s ", + fmtId(tbinfo->dobj.name)); + appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n", + fmtId(coninfo->dobj.name)); + + ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId, + coninfo->dobj.name, + tbinfo->dobj.namespace->dobj.name, + NULL, + tbinfo->rolname, false, + "EXCLUSION CONSTRAINT", SECTION_POST_DATA, + q->data, delq->data, NULL, + coninfo->dobj.dependencies, coninfo->dobj.nDeps, + NULL, NULL); + } else { write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype); *** 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/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. + */ + Oid conoperators[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_conoperators 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 Oid *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, + Oid *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,337 ---- extern void UnregisterExprContextCallback(ExprContext *econtext, ExprContextCallbackFunction function, Datum arg); + extern bool index_exclusion_constraint(Relation heap, Relation index, + TupleTableSlot *new_slot, + ItemPointer tupleid, Datum *values, + bool *isnull, IndexInfo *indexInfo, + EState *estate, bool errorOK); #endif /* EXECUTOR_H */ *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 58,63 **** typedef struct IndexInfo --- 58,66 ---- List *ii_ExpressionsState; /* list of ExprState */ List *ii_Predicate; /* list of Expr */ List *ii_PredicateState; /* list of ExprState */ + Oid *ii_ExclusionConstraintOps; + Oid *ii_ExclusionConstraintProcs; + uint16 *ii_ExclusionConstraintStrats; bool ii_Unique; bool ii_ReadyForInserts; bool ii_Concurrent; *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 1395,1400 **** typedef enum ConstrType /* types of constraints */ --- 1395,1401 ---- CONSTR_CHECK, CONSTR_PRIMARY, CONSTR_UNIQUE, + CONSTR_OPERATOR_EXCLUSION, CONSTR_FOREIGN, CONSTR_ATTR_DEFERRABLE, /* attributes for previous constraint node */ CONSTR_ATTR_NOT_DEFERRABLE, *************** *** 1429,1439 **** 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 */ --- 1430,1445 ---- 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 *************** *** 143,148 **** PG_KEYWORD("end", END_P, RESERVED_KEYWORD) --- 143,149 ---- 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) *** a/src/include/utils/errcodes.h --- b/src/include/utils/errcodes.h *************** *** 167,172 **** --- 167,173 ---- #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') *** a/src/include/utils/relcache.h --- b/src/include/utils/relcache.h *************** *** 43,48 **** extern Oid RelationGetOidIndex(Relation relation); --- 43,52 ---- extern List *RelationGetIndexExpressions(Relation relation); extern List *RelationGetIndexPredicate(Relation relation); extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation); + extern void RelationGetOpExclusionConstraints(Relation indexRelation, + Oid **operators, + Oid **procs, + uint16 **strategies); extern void RelationSetIndexList(Relation relation, List *indexIds, Oid oidIndex); *** a/src/pl/plpgsql/src/plerrcodes.h --- b/src/pl/plpgsql/src/plerrcodes.h *************** *** 304,309 **** --- 304,313 ---- }, { + "exclusion_violation", ERRCODE_EXCLUSION_VIOLATION + }, + + { "invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE }, *** 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, + 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>'); + -- 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 EXCLUDE USING gist + (c1 WITH &&, (c2::circle) WITH ~=); + + DROP TABLE circles; + + *** a/src/test/regress/output/constraints.source --- b/src/test/regress/output/constraints.source *************** *** 512,514 **** SELECT * FROM unique_tbl; --- 512,542 ---- (5 rows) DROP TABLE unique_tbl; + CREATE TABLE circles ( + c1 CIRCLE, + c2 TEXT, + EXCLUDE USING gist + (c1 WITH &&, (c2::circle) WITH ~=) + WHERE (circle_center(c1) <> '(0,0)') + ); + NOTICE: ALTER TABLE / ADD 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>'); + -- should fail + INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>'); + ERROR: conflicting key value violates operator exclusion constraint "circles_c1_exclusion" + DETAIL: Tuple "(c1, (c2::circle))=(<(20,20),10>, <(0,0),5>)" conflicts with existing tuple "(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: conflicting key value violates operator exclusion constraint "circles_c1_exclusion1" + DETAIL: Tuple "(c1, (c2::circle))=(<(0,0),5>, <(0,0),5>)" conflicts with existing tuple "(c1, (c2::circle))=(<(0,0),5>, <(0,0),5>)". + DROP TABLE circles;