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