diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index a70c11aa8899d6408706b7682ce8f90703f0ddcc..99aee810da4eb32343e95e2eed9d81a984dd71d8 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.202 2009/07/28 02:56:29 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.203 2009/07/29 20:56:17 tgl Exp $ --> <!-- Documentation of the system catalogs, directed toward PostgreSQL developers --> @@ -2675,6 +2675,14 @@ (<structfield>indisunique</> should always be true when this is true)</entry> </row> + <row> + <entry><structfield>indimmediate</structfield></entry> + <entry><type>bool</type></entry> + <entry></entry> + <entry>If true, the uniqueness check is enforced immediately on insertion + (<structfield>indisunique</> should always be true when this is true)</entry> + </row> + <row> <entry><structfield>indisclustered</structfield></entry> <entry><type>bool</type></entry> diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index 19b3c70814ecf003eebf6bf029b099b36924b519..b81cd27d313c1494efdd88d14b93e4572aedaf00 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/indexam.sgml,v 2.30 2009/03/24 20:17:08 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/indexam.sgml,v 2.31 2009/07/29 20:56:17 tgl Exp $ --> <chapter id="indexam"> <title>Index Access Method Interface Definition</title> @@ -172,20 +172,32 @@ aminsert (Relation indexRelation, bool *isnull, ItemPointer heap_tid, Relation heapRelation, - bool check_uniqueness); + IndexUniqueCheck checkUnique); </programlisting> Insert a new tuple into an existing index. The <literal>values</> and <literal>isnull</> arrays give the key values to be indexed, and <literal>heap_tid</> is the TID to be indexed. If the access method supports unique indexes (its <structname>pg_am</>.<structfield>amcanunique</> flag is true) then - <literal>check_uniqueness</> might be true, in which case the access method - must verify that there is no conflicting row; this is the only situation in - which the access method normally needs the <literal>heapRelation</> - parameter. See <xref linkend="index-unique-checks"> for details. - The result is TRUE if an index entry was inserted, FALSE if not. (A FALSE - result does not denote an error condition, but is used for cases such - as an index method refusing to index a NULL.) + <literal>checkUnique</> indicates the type of uniqueness check to + perform. This varies depending on whether the unique constraint is + deferrable; see <xref linkend="index-unique-checks"> for details. + Normally the access method only needs the <literal>heapRelation</> + parameter when performing uniqueness checking (since then it will have to + look into the heap to verify tuple liveness). + </para> + + <para> + The function's boolean result value is significant only when + <literal>checkUnique</> is <literal>UNIQUE_CHECK_PARTIAL</>. + In this case a TRUE result means the new entry is known unique, whereas + FALSE means it might be non-unique (and a deferred uniqueness check must + be scheduled). For other cases a constant FALSE result is recommended. + </para> + + <para> + Some indexes might not index all tuples. If the tuple is not to be + indexed, <function>aminsert</> should just return without doing anything. </para> <para> @@ -706,10 +718,10 @@ amrestrpos (IndexScanDesc scan); </para> <para> - Furthermore, immediately before raising a uniqueness violation + Furthermore, immediately before reporting a uniqueness violation according to the above rules, the access method must recheck the liveness of the row being inserted. If it is committed dead then - no error should be raised. (This case cannot occur during the + no violation should be reported. (This case cannot occur during the ordinary scenario of inserting a row that's just been created by the current transaction. It can happen during <command>CREATE UNIQUE INDEX CONCURRENTLY</>, however.) @@ -728,8 +740,78 @@ amrestrpos (IndexScanDesc scan); </para> <para> - The main limitation of this scheme is that it has no convenient way - to support deferred uniqueness checks. + If the unique constraint is deferrable, there is additional complexity: + we need to be able to insert an index entry for a new row, but defer any + uniqueness-violation error until end of statement or even later. To + avoid unnecessary repeat searches of the index, the index access method + should do a preliminary uniqueness check during the initial insertion. + If this shows that there is definitely no conflicting live tuple, we + are done. Otherwise, we schedule a recheck to occur when it is time to + enforce the constraint. If, at the time of the recheck, both the inserted + tuple and some other tuple with the same key are live, then the error + must be reported. (Note that for this purpose, <quote>live</> actually + means <quote>any tuple in the index entry's HOT chain is live</>.) + To implement this, the <function>aminsert</> function is passed a + <literal>checkUnique</> parameter having one of the following values: + + <itemizedlist> + <listitem> + <para> + <literal>UNIQUE_CHECK_NO</> indicates that no uniqueness checking + should be done (this is not a unique index). + </para> + </listitem> + <listitem> + <para> + <literal>UNIQUE_CHECK_YES</> indicates that this is a non-deferrable + unique index, and the uniqueness check must be done immediately, as + described above. + </para> + </listitem> + <listitem> + <para> + <literal>UNIQUE_CHECK_PARTIAL</> indicates that the unique + constraint is deferrable. <productname>PostgreSQL</productname> + will use this mode to insert each row's index entry. The access + method must allow duplicate entries into the index, and report any + potential duplicates by returning FALSE from <function>aminsert</>. + For each row for which FALSE is returned, a deferred recheck will + be scheduled. + </para> + + <para> + The access method must identify any rows which might violate the + unique constraint, but it is not an error for it to report false + positives. This allows the check to be done without waiting for other + transactions to finish; conflicts reported here are not treated as + errors and will be rechecked later, by which time they may no longer + be conflicts. + </para> + </listitem> + <listitem> + <para> + <literal>UNIQUE_CHECK_EXISTING</> indicates that this is a deferred + recheck of a row that was reported as a potential uniqueness violation. + Although this is implemented by calling <function>aminsert</>, the + access method must <emphasis>not</> insert a new index entry in this + case. The index entry is already present. Rather, the access method + must check to see if there is another live index entry. If so, and + if the target row is also still live, report error. + </para> + + <para> + It is recommended that in a <literal>UNIQUE_CHECK_EXISTING</> call, + the access method further verify that the target row actually does + have an existing entry in the index, and report error if not. This + is a good idea because the index tuple values passed to + <function>aminsert</> will have been recomputed. If the index + definition involves functions that are not really immutable, we + might be checking the wrong area of the index. Checking that the + target row is found in the recheck verifies that we are scanning + for the same tuple values as were used in the original insertion. + </para> + </listitem> + </itemizedlist> </para> </sect1> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 64971752eb4ed6d061ca329aa33037c56e87bf73..1f986bcd88c71019837138d55e50a6d77037cd3d 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/create_table.sgml,v 1.114 2009/02/12 13:25:33 momjian Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/create_table.sgml,v 1.115 2009/07/29 20:56:17 tgl Exp $ PostgreSQL documentation --> @@ -35,8 +35,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR where <replaceable class="PARAMETER">column_constraint</replaceable> is: [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ] -{ NOT NULL | - NULL | +{ NOT NULL | + NULL | UNIQUE <replaceable class="PARAMETER">index_parameters</replaceable> | PRIMARY KEY <replaceable class="PARAMETER">index_parameters</replaceable> | CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) | @@ -423,11 +423,10 @@ and <replaceable class="PARAMETER">table_constraint</replaceable> is: contain values that match values in the referenced column(s) of some row of the referenced table. If <replaceable class="parameter">refcolumn</replaceable> is omitted, the - primary key of the <replaceable - class="parameter">reftable</replaceable> is used. The - referenced columns must be the columns of a unique or primary - key constraint in the referenced table. Note that foreign key - constraints cannot be defined between temporary tables and + primary key of the <replaceable class="parameter">reftable</replaceable> + is used. The referenced columns must be the columns of a non-deferrable + unique or primary key constraint in the referenced table. Note that + foreign key constraints cannot be defined between temporary tables and permanent tables. </para> @@ -534,9 +533,11 @@ and <replaceable class="PARAMETER">table_constraint</replaceable> is: after every command. Checking of constraints that are deferrable can be postponed until the end of the transaction (using the <xref linkend="sql-set-constraints" endterm="sql-set-constraints-title"> command). - <literal>NOT DEFERRABLE</literal> is the default. Only foreign - key constraints currently accept this clause. All other - constraint types are not deferrable. + <literal>NOT DEFERRABLE</literal> is the default. + Currently, only <literal>UNIQUE</>, <literal>PRIMARY KEY</>, and + <literal>REFERENCES</> (foreign key) constraints accept this + clause. <literal>NOT NULL</> and <literal>CHECK</> constraints are not + deferrable. </para> </listitem> </varlistentry> @@ -1140,6 +1141,23 @@ CREATE TABLE cinemas ( </para> </refsect2> + <refsect2> + <title>Non-deferred Uniqueness Constraints</title> + + <para> + When a <literal>UNIQUE</> or <literal>PRIMARY KEY</> constraint is + not deferrable, <productname>PostgreSQL</productname> checks for + uniqueness immediately whenever a row is inserted or modified. + The SQL standard says that uniqueness should be enforced only at + the end of the statement; this makes a difference when, for example, + a single command updates multiple key values. To obtain + standard-compliant behavior, declare the constraint as + <literal>DEFERRABLE</> but not deferred (i.e., <literal>INITIALLY + IMMEDIATE</>). Be aware that this can be significantly slower than + immediate uniqueness checking. + </para> + </refsect2> + <refsect2> <title>Column Check Constraints</title> diff --git a/doc/src/sgml/ref/set_constraints.sgml b/doc/src/sgml/ref/set_constraints.sgml index 58f64b2437c5be3d1eb5633642b9b1e8fa58a3b2..e03910e2529b4d9d8256f99d6eb711cc0f61171e 100644 --- a/doc/src/sgml/ref/set_constraints.sgml +++ b/doc/src/sgml/ref/set_constraints.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_constraints.sgml,v 1.16 2008/11/14 10:22:47 petere Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_constraints.sgml,v 1.17 2009/07/29 20:56:17 tgl Exp $ --> <refentry id="SQL-SET-CONSTRAINTS"> <refmeta> <refentrytitle id="SQL-SET-CONSTRAINTS-title">SET CONSTRAINTS</refentrytitle> @@ -48,7 +48,7 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ... <command>SET CONSTRAINTS</command> with a list of constraint names changes the mode of just those constraints (which must all be deferrable). The current schema search path is used to find the first matching name if - no schema name is specified. <command>SET CONSTRAINTS ALL</command> + no schema name is specified. <command>SET CONSTRAINTS ALL</command> changes the mode of all deferrable constraints. </para> @@ -66,10 +66,19 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ... </para> <para> - Currently, only foreign key constraints are affected by this - setting. Check and unique constraints are always effectively - not deferrable. Triggers that are declared as <quote>constraint - triggers</> are also affected. + Currently, only <literal>UNIQUE</>, <literal>PRIMARY KEY</>, and + <literal>REFERENCES</> (foreign key) constraints are affected by this + setting. <literal>NOT NULL</> and <literal>CHECK</> constraints are + always checked immediately when a row is inserted or modified + (<emphasis>not</> at the end of the statement). + Uniqueness constraints that have not been declared <literal>DEFERRABLE</> + are also checked immediately. + </para> + + <para> + The firing of triggers that are declared as <quote>constraint triggers</> + is also controlled by this setting — they fire at the same time + that the associated constraint should be checked. </para> </refsect1> @@ -92,7 +101,7 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ... This command complies with the behavior defined in the SQL standard, except for the limitation that, in <productname>PostgreSQL</productname>, it only applies to - foreign-key constraints. + foreign-key and uniqueness constraints. </para> </refsect1> diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c index 2adaed43d48780b3c980275dad175a4bc75dd1b5..d175a5a99e6f35dd99aefb25f9c9baf7d557686e 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.22 2009/06/11 14:48:53 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.23 2009/07/29 20:56:17 tgl Exp $ *------------------------------------------------------------------------- */ @@ -415,12 +415,11 @@ gininsert(PG_FUNCTION_ARGS) #ifdef NOT_USED Relation heapRel = (Relation) PG_GETARG_POINTER(4); - bool checkUnique = PG_GETARG_BOOL(5); + IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5); #endif GinState ginstate; MemoryContext oldCtx; MemoryContext insertCtx; - uint32 res = 0; int i; insertCtx = AllocSetContextCreate(CurrentMemoryContext, @@ -440,7 +439,7 @@ gininsert(PG_FUNCTION_ARGS) memset(&collector, 0, sizeof(GinTupleCollector)); for (i = 0; i < ginstate.origTupdesc->natts; i++) if (!isnull[i]) - res += ginHeapTupleFastCollect(index, &ginstate, &collector, + ginHeapTupleFastCollect(index, &ginstate, &collector, (OffsetNumber) (i + 1), values[i], ht_ctid); ginHeapTupleFastInsert(index, &ginstate, &collector); @@ -449,7 +448,7 @@ gininsert(PG_FUNCTION_ARGS) { for (i = 0; i < ginstate.origTupdesc->natts; i++) if (!isnull[i]) - res += ginHeapTupleInsert(index, &ginstate, + ginHeapTupleInsert(index, &ginstate, (OffsetNumber) (i + 1), values[i], ht_ctid); } @@ -457,5 +456,5 @@ gininsert(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldCtx); MemoryContextDelete(insertCtx); - PG_RETURN_BOOL(res > 0); + PG_RETURN_BOOL(false); } diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 2742969c6af8372a6e6afd84993dea57d00d932c..ef6febef85c25e4c3bc16f2cd7e056ea0a5c0d29 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/gist/gist.c,v 1.156 2009/01/01 17:23:34 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/gist/gist.c,v 1.157 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -225,7 +225,7 @@ gistinsert(PG_FUNCTION_ARGS) #ifdef NOT_USED Relation heapRel = (Relation) PG_GETARG_POINTER(4); - bool checkUnique = PG_GETARG_BOOL(5); + IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5); #endif IndexTuple itup; GISTSTATE giststate; @@ -248,7 +248,7 @@ gistinsert(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldCtx); MemoryContextDelete(insertCtx); - PG_RETURN_BOOL(true); + PG_RETURN_BOOL(false); } diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 49b6594f1ebb73a0bd0ddce05238a48921bd0045..3505683836457e9ab470b454a816e977bd2cefdd 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.112 2009/06/11 14:48:53 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.113 2009/07/29 20:56:18 tgl Exp $ * * NOTES * This file contains only the public interface routines. @@ -165,7 +165,7 @@ hashinsert(PG_FUNCTION_ARGS) #ifdef NOT_USED Relation heapRel = (Relation) PG_GETARG_POINTER(4); - bool checkUnique = PG_GETARG_BOOL(5); + IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5); #endif IndexTuple itup; @@ -192,7 +192,7 @@ hashinsert(PG_FUNCTION_ARGS) pfree(itup); - PG_RETURN_BOOL(true); + PG_RETURN_BOOL(false); } diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index 781bfd2e4862f3439fc2b5680ddebbc8b28f4d16..2fbf8f43bbd5a4f9b9e0ff4da88447b52a203a2f 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.94 2009/07/22 01:21:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.95 2009/07/29 20:56:18 tgl Exp $ * * * INTERFACE ROUTINES @@ -1229,7 +1229,9 @@ toast_save_datum(Relation rel, Datum value, int options) */ index_insert(toastidx, t_values, t_isnull, &(toasttup->t_self), - toastrel, toastidx->rd_index->indisunique); + toastrel, + toastidx->rd_index->indisunique ? + UNIQUE_CHECK_YES : UNIQUE_CHECK_NO); /* * Free memory diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 32623965c78d32afd9cd2490d8feaa9b5be3d45e..f4ffeccd3281b2ca8f2b8a9ad525caf3e5f123f7 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/index/indexam.c,v 1.114 2009/06/11 14:48:54 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/index/indexam.c,v 1.115 2009/07/29 20:56:18 tgl Exp $ * * INTERFACE ROUTINES * index_open - open an index relation by relation OID @@ -185,7 +185,7 @@ index_insert(Relation indexRelation, bool *isnull, ItemPointer heap_t_ctid, Relation heapRelation, - bool check_uniqueness) + IndexUniqueCheck checkUnique) { FmgrInfo *procedure; @@ -201,7 +201,7 @@ index_insert(Relation indexRelation, PointerGetDatum(isnull), PointerGetDatum(heap_t_ctid), PointerGetDatum(heapRelation), - BoolGetDatum(check_uniqueness))); + Int32GetDatum((int32) checkUnique))); } /* diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index a06faa202033c1b55cbddc27e9df05cd6c3732a3..f01371822491c03ec063e2c3b0668455396853fe 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.170 2009/06/11 14:48:54 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.171 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -49,8 +49,9 @@ typedef struct static Buffer _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf); static TransactionId _bt_check_unique(Relation rel, IndexTuple itup, - Relation heapRel, Buffer buf, OffsetNumber ioffset, - ScanKey itup_scankey); + Relation heapRel, Buffer buf, OffsetNumber offset, + ScanKey itup_scankey, + IndexUniqueCheck checkUnique, bool *is_unique); static void _bt_findinsertloc(Relation rel, Buffer *bufptr, OffsetNumber *offsetptr, @@ -85,11 +86,24 @@ static void _bt_vacuum_one_page(Relation rel, Buffer buffer); * * This routine is called by the public interface routines, btbuild * and btinsert. By here, itup is filled in, including the TID. + * + * If checkUnique is UNIQUE_CHECK_NO or UNIQUE_CHECK_PARTIAL, this + * will allow duplicates. Otherwise (UNIQUE_CHECK_YES or + * UNIQUE_CHECK_EXISTING) it will throw error for a duplicate. + * For UNIQUE_CHECK_EXISTING we merely run the duplicate check, and + * don't actually insert. + * + * The result value is only significant for UNIQUE_CHECK_PARTIAL: + * it must be TRUE if the entry is known unique, else FALSE. + * (In the current implementation we'll also return TRUE after a + * successful UNIQUE_CHECK_YES or UNIQUE_CHECK_EXISTING call, but + * that's just a coding artifact.) */ -void +bool _bt_doinsert(Relation rel, IndexTuple itup, - bool index_is_unique, Relation heapRel) + IndexUniqueCheck checkUnique, Relation heapRel) { + bool is_unique = false; int natts = rel->rd_rel->relnatts; ScanKey itup_scankey; BTStack stack; @@ -134,13 +148,18 @@ top: * * If we must wait for another xact, we release the lock while waiting, * and then must start over completely. + * + * For a partial uniqueness check, we don't wait for the other xact. + * Just let the tuple in and return false for possibly non-unique, + * or true for definitely unique. */ - if (index_is_unique) + if (checkUnique != UNIQUE_CHECK_NO) { TransactionId xwait; offset = _bt_binsrch(rel, buf, natts, itup_scankey, false); - xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey); + xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey, + checkUnique, &is_unique); if (TransactionIdIsValid(xwait)) { @@ -153,13 +172,23 @@ top: } } - /* do the insertion */ - _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup); - _bt_insertonpg(rel, buf, stack, itup, offset, false); + if (checkUnique != UNIQUE_CHECK_EXISTING) + { + /* do the insertion */ + _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup); + _bt_insertonpg(rel, buf, stack, itup, offset, false); + } + else + { + /* just release the buffer */ + _bt_relbuf(rel, buf); + } /* be tidy */ _bt_freestack(stack); _bt_freeskey(itup_scankey); + + return is_unique; } /* @@ -172,10 +201,16 @@ top: * Returns InvalidTransactionId if there is no conflict, else an xact ID * we must wait for to see if it commits a conflicting tuple. If an actual * conflict is detected, no return --- just ereport(). + * + * However, if checkUnique == UNIQUE_CHECK_PARTIAL, we always return + * InvalidTransactionId because we don't want to wait. In this case we + * set *is_unique to false if there is a potential conflict, and the + * core code must redo the uniqueness check later. */ static TransactionId _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, - Buffer buf, OffsetNumber offset, ScanKey itup_scankey) + Buffer buf, OffsetNumber offset, ScanKey itup_scankey, + IndexUniqueCheck checkUnique, bool *is_unique) { TupleDesc itupdesc = RelationGetDescr(rel); int natts = rel->rd_rel->relnatts; @@ -184,6 +219,10 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, Page page; BTPageOpaque opaque; Buffer nbuf = InvalidBuffer; + bool found = false; + + /* Assume unique until we find a duplicate */ + *is_unique = true; InitDirtySnapshot(SnapshotDirty); @@ -240,22 +279,49 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, curitup = (IndexTuple) PageGetItem(page, curitemid); htid = curitup->t_tid; + /* + * If we are doing a recheck, we expect to find the tuple we + * are rechecking. It's not a duplicate, but we have to keep + * scanning. + */ + if (checkUnique == UNIQUE_CHECK_EXISTING && + ItemPointerCompare(&htid, &itup->t_tid) == 0) + { + found = true; + } + /* * We check the whole HOT-chain to see if there is any tuple * that satisfies SnapshotDirty. This is necessary because we * have just a single index entry for the entire chain. */ - if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead)) + else if (heap_hot_search(&htid, heapRel, &SnapshotDirty, + &all_dead)) { - /* it is a duplicate */ - TransactionId xwait = - (TransactionIdIsValid(SnapshotDirty.xmin)) ? - SnapshotDirty.xmin : SnapshotDirty.xmax; + TransactionId xwait; + + /* + * It is a duplicate. If we are only doing a partial + * check, then don't bother checking if the tuple is + * being updated in another transaction. Just return + * the fact that it is a potential conflict and leave + * the full check till later. + */ + if (checkUnique == UNIQUE_CHECK_PARTIAL) + { + if (nbuf != InvalidBuffer) + _bt_relbuf(rel, nbuf); + *is_unique = false; + return InvalidTransactionId; + } /* * If this tuple is being updated by other transaction * then we have to wait for its commit/abort. */ + xwait = (TransactionIdIsValid(SnapshotDirty.xmin)) ? + SnapshotDirty.xmin : SnapshotDirty.xmax; + if (TransactionIdIsValid(xwait)) { if (nbuf != InvalidBuffer) @@ -295,6 +361,9 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, break; } + /* + * This is a definite conflict. + */ ereport(ERROR, (errcode(ERRCODE_UNIQUE_VIOLATION), errmsg("duplicate key value violates unique constraint \"%s\"", @@ -349,6 +418,18 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, } } + /* + * If we are doing a recheck then we should have found the tuple we + * are checking. Otherwise there's something very wrong --- probably, + * the index is on a non-immutable expression. + */ + if (checkUnique == UNIQUE_CHECK_EXISTING && !found) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to re-find tuple within index \"%s\"", + RelationGetRelationName(rel)), + errhint("This may be because of a non-immutable index expression."))); + if (nbuf != InvalidBuffer) _bt_relbuf(rel, nbuf); diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 2b76e7cd453725a15c90b13fccd25907018342a5..87a8a225dbf743b1273a81487eb8a591db2c0307 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -12,7 +12,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.171 2009/06/11 14:48:54 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.172 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -217,18 +217,19 @@ btinsert(PG_FUNCTION_ARGS) bool *isnull = (bool *) PG_GETARG_POINTER(2); ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3); Relation heapRel = (Relation) PG_GETARG_POINTER(4); - bool checkUnique = PG_GETARG_BOOL(5); + IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5); + bool result; IndexTuple itup; /* generate an index tuple */ itup = index_form_tuple(RelationGetDescr(rel), values, isnull); itup->t_tid = *ht_ctid; - _bt_doinsert(rel, itup, checkUnique, heapRel); + result = _bt_doinsert(rel, itup, checkUnique, heapRel); pfree(itup); - PG_RETURN_BOOL(true); + PG_RETURN_BOOL(result); } /* diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index 1510614d5ade983a36085cd4e41e63b0d6863e04..1670e462bc3506777b9089ab326841f9c240270c 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.96 2009/01/01 17:23:36 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.97 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -266,7 +266,7 @@ Boot_DeclareIndexStmt: NULL, $10, NULL, NIL, - false, false, false, + false, false, false, false, false, false, false, true, false, false); do_end(); } @@ -284,7 +284,7 @@ Boot_DeclareUniqueIndexStmt: NULL, $11, NULL, NIL, - true, false, false, + true, false, false, false, false, false, false, true, false, false); do_end(); } diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 0199ca673037971ddd5241d05bb3c92f8813a525..73472d1568eba2f58663cdabb662ec8c345b059d 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.319 2009/07/28 02:56:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.320 2009/07/29 20:56:18 tgl Exp $ * * * INTERFACE ROUTINES @@ -40,14 +40,18 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opclass.h" #include "catalog/pg_tablespace.h" +#include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/storage.h" #include "commands/tablecmds.h" +#include "commands/trigger.h" #include "executor/executor.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/var.h" +#include "parser/parser.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/procarray.h" @@ -87,6 +91,7 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid, Oid *classOids, int16 *coloptions, bool primary, + bool immediate, bool isvalid); static void index_update_stats(Relation rel, bool hasindex, bool isprimary, Oid reltoastidxid, double reltuples); @@ -372,6 +377,7 @@ UpdateIndexRelation(Oid indexoid, Oid *classOids, int16 *coloptions, bool primary, + bool immediate, bool isvalid) { int2vector *indkey; @@ -439,6 +445,7 @@ UpdateIndexRelation(Oid indexoid, values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs); values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique); values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary); + values[Anum_pg_index_indimmediate - 1] = BoolGetDatum(immediate); values[Anum_pg_index_indisclustered - 1] = BoolGetDatum(false); values[Anum_pg_index_indisvalid - 1] = BoolGetDatum(isvalid); values[Anum_pg_index_indcheckxmin - 1] = BoolGetDatum(false); @@ -488,6 +495,8 @@ UpdateIndexRelation(Oid indexoid, * reloptions: AM-specific options * isprimary: index is a PRIMARY KEY * isconstraint: index is owned by a PRIMARY KEY or UNIQUE constraint + * deferrable: constraint is DEFERRABLE + * initdeferred: constraint is INITIALLY DEFERRED * allow_system_table_mods: allow table to be a system catalog * skip_build: true to skip the index_build() step for the moment; caller * must do it later (typically via reindex_index()) @@ -509,6 +518,8 @@ index_create(Oid heapRelationId, Datum reloptions, bool isprimary, bool isconstraint, + bool deferrable, + bool initdeferred, bool allow_system_table_mods, bool skip_build, bool concurrent) @@ -679,7 +690,9 @@ index_create(Oid heapRelationId, * ---------------- */ UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo, - classObjectId, coloptions, isprimary, !concurrent); + classObjectId, coloptions, isprimary, + !deferrable, + !concurrent); /* * Register constraint and dependencies for the index. @@ -726,8 +739,8 @@ index_create(Oid heapRelationId, conOid = CreateConstraintEntry(indexRelationName, namespaceId, constraintType, - false, /* isDeferrable */ - false, /* isDeferred */ + deferrable, + initdeferred, heapRelationId, indexInfo->ii_KeyAttrNumbers, indexInfo->ii_NumIndexAttrs, @@ -753,6 +766,40 @@ index_create(Oid heapRelationId, referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL); + + /* + * If the constraint is deferrable, create the deferred uniqueness + * checking trigger. (The trigger will be given an internal + * dependency on the constraint by CreateTrigger, so there's no + * need to do anything more here.) + */ + if (deferrable) + { + RangeVar *heapRel; + CreateTrigStmt *trigger; + + heapRel = makeRangeVar(get_namespace_name(namespaceId), + pstrdup(RelationGetRelationName(heapRelation)), + -1); + + trigger = makeNode(CreateTrigStmt); + trigger->trigname = pstrdup(indexRelationName); + trigger->relation = heapRel; + trigger->funcname = SystemFuncName("unique_key_recheck"); + trigger->args = NIL; + trigger->before = false; + trigger->row = true; + trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE; + trigger->isconstraint = true; + trigger->deferrable = true; + trigger->initdeferred = initdeferred; + trigger->constrrel = NULL; + + (void) CreateTrigger(trigger, conOid, indexRelationId, + isprimary ? "PK_ConstraintTrigger" : + "Unique_ConstraintTrigger", + false); + } } else { @@ -791,6 +838,10 @@ index_create(Oid heapRelationId, recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); } + + /* Non-constraint indexes can't be deferrable */ + Assert(!deferrable); + Assert(!initdeferred); } /* Store dependency on operator classes */ @@ -823,6 +874,13 @@ index_create(Oid heapRelationId, DEPENDENCY_AUTO); } } + else + { + /* Bootstrap mode - assert we weren't asked for constraint support */ + Assert(!isconstraint); + Assert(!deferrable); + Assert(!initdeferred); + } /* * Advance the command counter so that we can see the newly-entered @@ -2190,7 +2248,8 @@ validate_index_heapscan(Relation heapRelation, isnull, &rootTuple, heapRelation, - indexInfo->ii_Unique); + indexInfo->ii_Unique ? + UNIQUE_CHECK_YES : UNIQUE_CHECK_NO); state->tups_inserted += 1; } diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c index b7079722312bb2eb2d10bacdb9eb5715011a26e5..65fe6d5efde41cf67d7bfa777afa43116a314bd5 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.117 2009/01/01 17:23:37 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.118 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -134,7 +134,8 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple) isnull, /* is-null flags */ &(heapTuple->t_self), /* tid of heap tuple */ heapRelation, - relationDescs[i]->rd_index->indisunique); + relationDescs[i]->rd_index->indisunique ? + UNIQUE_CHECK_YES : UNIQUE_CHECK_NO); } ExecDropSingleTupleTableSlot(slot); diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index 3970f88f7ddf2dae853279a5dfc1475408307b1d..37f8f2332be09b65200f078f7d5e380905fc699c 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -289,7 +289,7 @@ F695 Translation support NO F696 Additional translation documentation NO F701 Referential update actions YES F711 ALTER domain YES -F721 Deferrable constraints NO foreign keys only +F721 Deferrable constraints NO foreign and unique keys only F731 INSERT column privileges YES F741 Referential MATCH types NO no partial match yet F751 View CHECK enhancements NO diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index b284cd23aa9a3bc92dc16abf5f63d03cb4b62155..9e2f20e3bf03233ffae03bad2ffd44ed44f84189 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.17 2009/06/11 20:46:11 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.18 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -253,7 +253,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, BTREE_AM_OID, rel->rd_rel->reltablespace, classObjectId, coloptions, (Datum) 0, - true, false, true, false, false); + true, false, false, false, + true, false, false); /* * Store the toast table's OID in the parent relation's pg_class row diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index e455e62c9aa8e69a501ae41c402a27c01abb0d75..29a29ab7f4c6fb235de6b4a587a26464d3a7808d 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -4,7 +4,7 @@ # Makefile for backend/commands # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/commands/Makefile,v 1.39 2008/12/19 16:25:17 petere Exp $ +# $PostgreSQL: pgsql/src/backend/commands/Makefile,v 1.40 2009/07/29 20:56:18 tgl Exp $ # #------------------------------------------------------------------------- @@ -13,7 +13,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ - conversioncmds.o copy.o \ + constraint.o conversioncmds.o copy.o \ dbcommands.o define.o discard.o explain.o foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c new file mode 100644 index 0000000000000000000000000000000000000000..42d4d4e1f9c4727f59d8c59fa1e0fef4a42e5db5 --- /dev/null +++ b/src/backend/commands/constraint.c @@ -0,0 +1,166 @@ +/*------------------------------------------------------------------------- + * + * constraint.c + * PostgreSQL CONSTRAINT support code. + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * 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 $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/index.h" +#include "commands/trigger.h" +#include "executor/executor.h" +#include "utils/builtins.h" +#include "utils/tqual.h" + + +/* + * unique_key_recheck - trigger function to do a deferred uniqueness check. + * + * This is invoked as an AFTER ROW trigger for both INSERT and UPDATE, + * for any rows recorded as potentially violating a deferrable unique + * constraint. + * + * This may be an end-of-statement check, a commit-time check, or a + * check triggered by a SET CONSTRAINTS command. + */ +Datum +unique_key_recheck(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + const char *funcname = "unique_key_recheck"; + HeapTuple new_row; + ItemPointerData tmptid; + Relation indexRel; + IndexInfo *indexInfo; + EState *estate; + ExprContext *econtext; + TupleTableSlot *slot; + Datum values[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + + /* + * Make sure this is being called as an AFTER ROW trigger. Note: + * translatable error strings are shared with ri_triggers.c, so + * resist the temptation to fold the function name into them. + */ + if (!CALLED_AS_TRIGGER(fcinfo)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("function \"%s\" was not called by trigger manager", + funcname))); + + if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || + !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("function \"%s\" must be fired AFTER ROW", + funcname))); + + /* + * Get the new data that was inserted/updated. + */ + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + new_row = trigdata->tg_trigtuple; + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + new_row = trigdata->tg_newtuple; + else + { + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("function \"%s\" must be fired for INSERT or UPDATE", + funcname))); + new_row = NULL; /* keep compiler quiet */ + } + + /* + * If the new_row is now dead (ie, inserted and then deleted within our + * transaction), we can skip the check. However, we have to be careful, + * 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 + * heap_hot_search, not just HeapTupleSatisfiesVisibility as is done in + * the comparable test in RI_FKey_check. + * + * This might look like just an optimization, because the index AM will + * make this identical test before throwing an error. But it's actually + * needed for correctness, because the index AM will also throw an error + * if it doesn't find the index entry for the row. If the row's dead then + * it's possible the index entry has also been marked dead, and even + * removed. + */ + tmptid = new_row->t_self; + if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL)) + { + /* + * All rows in the HOT chain are dead, so skip the check. + */ + return PointerGetDatum(NULL); + } + + /* + * Open the index, acquiring a RowExclusiveLock, just as if we were + * going to update it. (This protects against possible changes of the + * index schema, not against concurrent updates.) + */ + indexRel = index_open(trigdata->tg_trigger->tgconstrindid, + RowExclusiveLock); + indexInfo = BuildIndexInfo(indexRel); + + /* + * The heap tuple must be put into a slot for FormIndexDatum. + */ + slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation)); + + ExecStoreTuple(new_row, slot, InvalidBuffer, false); + + /* + * Typically the index won't have expressions, but if it does we need + * an EState to evaluate them. + */ + if (indexInfo->ii_Expressions != NIL) + { + estate = CreateExecutorState(); + econtext = GetPerTupleExprContext(estate); + econtext->ecxt_scantuple = slot; + } + else + estate = NULL; + + /* + * Form the index values and isnull flags for the index entry that + * we need to check. + * + * 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. + */ + 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. + */ + index_insert(indexRel, values, isnull, &(new_row->t_self), + trigdata->tg_relation, UNIQUE_CHECK_EXISTING); + + /* + * If that worked, then this index entry is unique, and we are done. + */ + if (estate != NULL) + FreeExecutorState(estate); + + ExecDropSingleTupleTableSlot(slot); + + index_close(indexRel, RowExclusiveLock); + + return PointerGetDatum(NULL); +} diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index b41da6385db8c6641e1a436084138cc35387671f..21f7b94d5465cbab2f668c895736465295e068b5 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.315 2009/07/25 17:04:19 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.316 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2130,6 +2130,8 @@ CopyFrom(CopyState cstate) if (!skip_tuple) { + List *recheckIndexes = NIL; + /* Place tuple in tuple slot */ ExecStoreTuple(tuple, slot, InvalidBuffer, false); @@ -2141,10 +2143,12 @@ CopyFrom(CopyState cstate) heap_insert(cstate->rel, tuple, mycid, hi_options, bistate); if (resultRelInfo->ri_NumIndices > 0) - ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); + recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), + estate, false); /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple); + ExecARInsertTriggers(estate, resultRelInfo, tuple, + recheckIndexes); /* * We count only tuples not suppressed by a BEFORE INSERT trigger; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index ef879a3df2ba35a6a6a3de24fa2a953183efc558..96272ab998d08a3187e338b1fdf0014494cd5106 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.186 2009/07/16 06:33:42 petere Exp $ + * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.187 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -87,6 +87,8 @@ static bool relationHasPrimaryKey(Relation rel); * 'primary': mark the index as a primary key in the catalogs. * 'isconstraint': index is for a PRIMARY KEY or UNIQUE constraint, * so build a pg_constraint entry for it. + * 'deferrable': constraint is DEFERRABLE. + * 'initdeferred': constraint is INITIALLY DEFERRED. * 'is_alter_table': this is due to an ALTER rather than a CREATE operation. * 'check_rights': check for CREATE rights in the namespace. (This should * be true except when ALTER is deleting/recreating an index.) @@ -107,6 +109,8 @@ DefineIndex(RangeVar *heapRelation, bool unique, bool primary, bool isconstraint, + bool deferrable, + bool initdeferred, bool is_alter_table, bool check_rights, bool skip_build, @@ -447,7 +451,8 @@ DefineIndex(RangeVar *heapRelation, indexRelationId = index_create(relationId, indexRelationName, indexRelationId, indexInfo, accessMethodId, tablespaceId, classObjectId, - coloptions, reloptions, primary, isconstraint, + coloptions, reloptions, primary, + isconstraint, deferrable, initdeferred, allowSystemTableMods, skip_build, concurrent); return; /* We're done, in the standard case */ @@ -465,7 +470,8 @@ DefineIndex(RangeVar *heapRelation, indexRelationId = index_create(relationId, indexRelationName, indexRelationId, indexInfo, accessMethodId, tablespaceId, classObjectId, - coloptions, reloptions, primary, isconstraint, + coloptions, reloptions, primary, + isconstraint, deferrable, initdeferred, allowSystemTableMods, true, concurrent); /* diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e883e8ed91fd4800a8b98fe24d81ebfd19c9f05f..0d3e3bc1670a9c1b8dfef731dbb19e10ff9b971d 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.292 2009/07/28 02:56:30 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.293 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -4391,6 +4391,8 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel, stmt->unique, stmt->primary, stmt->isconstraint, + stmt->deferrable, + stmt->initdeferred, true, /* is_alter_table */ check_rights, skip_build, @@ -4955,6 +4957,17 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid, indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); if (indexStruct->indisprimary) { + /* + * Refuse to use a deferrable primary key. This is per SQL spec, + * and there would be a lot of interesting semantic problems if + * we tried to allow it. + */ + if (!indexStruct->indimmediate) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot use a deferrable primary key for referenced table \"%s\"", + RelationGetRelationName(pkrel)))); + *indexOid = indexoid; break; } @@ -5040,11 +5053,12 @@ transformFkeyCheckAttrs(Relation pkrel, indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); /* - * Must have the right number of columns; must be unique and not a - * partial index; forget it if there are any expressions, too + * Must have the right number of columns; must be unique (non + * deferrable) and not a partial index; forget it if there are any + * expressions, too */ if (indexStruct->indnatts == numattrs && - indexStruct->indisunique && + indexStruct->indisunique && indexStruct->indimmediate && heap_attisnull(indexTuple, Anum_pg_index_indpred) && heap_attisnull(indexTuple, Anum_pg_index_indexprs)) { @@ -5243,7 +5257,8 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint, fk_trigger->constrrel = fkconstraint->pktable; fk_trigger->args = NIL; - (void) CreateTrigger(fk_trigger, constraintOid, indexOid, false); + (void) CreateTrigger(fk_trigger, constraintOid, indexOid, + "RI_ConstraintTrigger", false); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -5322,7 +5337,8 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, } fk_trigger->args = NIL; - (void) CreateTrigger(fk_trigger, constraintOid, indexOid, false); + (void) CreateTrigger(fk_trigger, constraintOid, indexOid, + "RI_ConstraintTrigger", false); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -5373,7 +5389,8 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, } fk_trigger->args = NIL; - (void) CreateTrigger(fk_trigger, constraintOid, indexOid, false); + (void) CreateTrigger(fk_trigger, constraintOid, indexOid, + "RI_ConstraintTrigger", false); } /* diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 0cc33aae6b60ab463507b53cd36b6a7599fac765..b84731126a1303e2fe9af1b75e0b9852c9ffbe71 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.249 2009/07/28 02:56:30 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.250 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -63,7 +63,8 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, Instrumentation *instr, MemoryContext per_tuple_context); static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, - bool row_trigger, HeapTuple oldtup, HeapTuple newtup); + bool row_trigger, HeapTuple oldtup, HeapTuple newtup, + List *recheckIndexes); /* @@ -77,6 +78,10 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, * indexOid, if nonzero, is the OID of an index associated with the constraint. * We do nothing with this except store it into pg_trigger.tgconstrindid. * + * prefix is NULL for user-created triggers. For internally generated + * constraint triggers, it is a prefix string to use in building the + * trigger name. (stmt->trigname is the constraint name in such cases.) + * * If checkPermissions is true we require ACL_TRIGGER permissions on the * relation. If not, the caller already checked permissions. (This is * currently redundant with constraintOid being zero, but it's clearer to @@ -87,7 +92,7 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, */ Oid CreateTrigger(CreateTrigStmt *stmt, - Oid constraintOid, Oid indexOid, + Oid constraintOid, Oid indexOid, const char *prefix, bool checkPermissions) { int16 tgtype; @@ -216,20 +221,20 @@ CreateTrigger(CreateTrigStmt *stmt, trigoid = GetNewOid(tgrel); /* - * If trigger is for an RI constraint, the passed-in name is the - * constraint name; save that and build a unique trigger name to avoid - * collisions with user-selected trigger names. + * If trigger is for a constraint, stmt->trigname is the constraint + * name; save that and build a unique trigger name based on the supplied + * prefix, to avoid collisions with user-selected trigger names. */ - if (OidIsValid(constraintOid)) + if (prefix != NULL) { snprintf(constrtrigname, sizeof(constrtrigname), - "RI_ConstraintTrigger_%u", trigoid); + "%s_%u", prefix, trigoid); trigname = constrtrigname; constrname = stmt->trigname; } else if (stmt->isconstraint) { - /* constraint trigger: trigger name is also constraint name */ + /* user constraint trigger: trigger name is also constraint name */ trigname = stmt->trigname; constrname = stmt->trigname; } @@ -1650,7 +1655,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo) if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0) AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT, - false, NULL, NULL); + false, NULL, NULL, NIL); } HeapTuple @@ -1706,13 +1711,13 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, void ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, - HeapTuple trigtuple) + HeapTuple trigtuple, List *recheckIndexes) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0) AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT, - true, NULL, trigtuple); + true, NULL, trigtuple, recheckIndexes); } void @@ -1781,7 +1786,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo) if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0) AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE, - false, NULL, NULL); + false, NULL, NULL, NIL); } bool @@ -1858,7 +1863,7 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, tupleid, NULL); AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE, - true, trigtuple, NULL); + true, trigtuple, NULL, NIL); heap_freetuple(trigtuple); } } @@ -1929,7 +1934,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo) if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0) AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE, - false, NULL, NULL); + false, NULL, NULL, NIL); } HeapTuple @@ -1999,7 +2004,8 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo, void ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, - ItemPointer tupleid, HeapTuple newtuple) + ItemPointer tupleid, HeapTuple newtuple, + List *recheckIndexes) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; @@ -2009,7 +2015,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, tupleid, NULL); AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE, - true, trigtuple, newtuple); + true, trigtuple, newtuple, recheckIndexes); heap_freetuple(trigtuple); } } @@ -2080,7 +2086,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo) if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0) AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE, - false, NULL, NULL); + false, NULL, NULL, NIL); } @@ -3793,15 +3799,18 @@ AfterTriggerPendingOnRel(Oid relid) /* ---------- * AfterTriggerSaveEvent() * - * Called by ExecA[RS]...Triggers() to add the event to the queue. + * Called by ExecA[RS]...Triggers() to queue up the triggers that should + * be fired for an event. * - * NOTE: should be called only if we've determined that an event must - * be added to the queue. + * NOTE: this is called whenever there are any triggers associated with + * the event (even if they are disabled). This function decides which + * triggers actually need to be queued. * ---------- */ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, - HeapTuple oldtup, HeapTuple newtup) + HeapTuple oldtup, HeapTuple newtup, + List *recheckIndexes) { Relation rel = relinfo->ri_RelationDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc; @@ -3961,6 +3970,17 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, } } + /* + * If the trigger is a deferred unique constraint check trigger, + * only queue it if the unique constraint was potentially violated, + * which we know from index insertion time. + */ + if (trigger->tgfoid == F_UNIQUE_KEY_RECHECK) + { + if (!list_member_oid(recheckIndexes, trigger->tgconstrindid)) + continue; /* Uniqueness definitely not violated */ + } + /* * Fill in event structure and add it to the current query's queue. */ diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 1fddf10bc9a353002a666767e94153d35de9d001..1bfe48eaac45f1df592b0aa8fc31ed87ded971a9 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.326 2009/06/11 20:46:11 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.327 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1753,6 +1753,7 @@ ExecInsert(TupleTableSlot *slot, ResultRelInfo *resultRelInfo; Relation resultRelationDesc; Oid newId; + List *recheckIndexes = NIL; /* * get the heap tuple out of the tuple table slot, making sure we have a @@ -1834,10 +1835,11 @@ ExecInsert(TupleTableSlot *slot, * insert index entries for tuple */ if (resultRelInfo->ri_NumIndices > 0) - ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); + recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), + estate, false); /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple); + ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes); /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) @@ -1999,6 +2001,7 @@ ExecUpdate(TupleTableSlot *slot, HTSU_Result result; ItemPointerData update_ctid; TransactionId update_xmax; + List *recheckIndexes = NIL; /* * abort the operation if not running transactions @@ -2132,10 +2135,12 @@ lreplace:; * If it's a HOT update, we mustn't insert new index entries. */ if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple)) - ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false); + recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), + estate, false); /* AFTER ROW UPDATE Triggers */ - ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple); + ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple, + recheckIndexes); /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 9411718667f5e2ba9abd71e8354c9c050baa3e69..43c0e54065359ac0847017c2a0098a05f37c517e 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.160 2009/07/18 19:15:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.161 2009/07/29 20:56:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1033,17 +1033,22 @@ 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 + * potential (unconfirmed) conflicts. + * * CAUTION: this must not be called for a HOT update. * We can't defend against that here for lack of info. * Should we change the API to make it safer? * ---------------------------------------------------------------- */ -void +List * ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid, EState *estate, - bool is_vacuum) + bool is_vacuum_full) { + List *result = NIL; ResultRelInfo *resultRelInfo; int i; int numIndices; @@ -1077,9 +1082,12 @@ ExecInsertIndexTuples(TupleTableSlot *slot, */ for (i = 0; i < numIndices; i++) { + Relation indexRelation = relationDescs[i]; IndexInfo *indexInfo; + IndexUniqueCheck checkUnique; + bool isUnique; - if (relationDescs[i] == NULL) + if (indexRelation == NULL) continue; indexInfo = indexInfoArray[i]; @@ -1122,22 +1130,50 @@ ExecInsertIndexTuples(TupleTableSlot *slot, isnull); /* - * The index AM does the rest. Note we suppress unique-index checks - * if we are being called from VACUUM, since VACUUM may need to move - * dead tuples that have the same keys as live ones. + * The index AM does the rest, including uniqueness checking. + * + * For an immediate-mode unique index, we just tell the index AM to + * throw error if not unique. + * + * For a deferrable unique index, we tell the index AM to just detect + * possible non-uniqueness, and we add the index OID to the result + * list if further checking is needed. + * + * Special hack: we suppress unique-index checks if we are being + * called from VACUUM FULL, since VACUUM FULL may need to move dead + * tuples that have the same keys as live ones. */ - index_insert(relationDescs[i], /* index relation */ - values, /* array of index Datums */ - isnull, /* null flags */ - tupleid, /* tid of heap tuple */ - heapRelation, - relationDescs[i]->rd_index->indisunique && !is_vacuum); + if (is_vacuum_full || !indexRelation->rd_index->indisunique) + checkUnique = UNIQUE_CHECK_NO; + else if (indexRelation->rd_index->indimmediate) + checkUnique = UNIQUE_CHECK_YES; + else + checkUnique = UNIQUE_CHECK_PARTIAL; + + isUnique = + index_insert(indexRelation, /* index relation */ + values, /* array of index Datums */ + isnull, /* null flags */ + tupleid, /* tid of heap tuple */ + heapRelation, /* heap relation */ + checkUnique); /* type of uniqueness check to do */ + + if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique) + { + /* + * The tuple potentially violates the uniqueness constraint, + * so make a note of the index so that we can re-check it later. + */ + result = lappend_oid(result, RelationGetRelid(indexRelation)); + } /* * keep track of index inserts for debugging */ IncrIndexInserted(); } + + return result; } /* diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 0752cbfc00eb8ce2a580a0ee34443460f91cbf5b..d10a9eb6cc5e8586095c67749d6ce76979e3116a 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.435 2009/07/26 23:34:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.436 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2091,6 +2091,8 @@ _copyConstraint(Constraint *from) COPY_NODE_FIELD(keys); COPY_NODE_FIELD(options); COPY_STRING_FIELD(indexspace); + COPY_SCALAR_FIELD(deferrable); + COPY_SCALAR_FIELD(initdeferred); return newnode; } @@ -2510,6 +2512,8 @@ _copyIndexStmt(IndexStmt *from) COPY_SCALAR_FIELD(unique); COPY_SCALAR_FIELD(primary); COPY_SCALAR_FIELD(isconstraint); + COPY_SCALAR_FIELD(deferrable); + COPY_SCALAR_FIELD(initdeferred); COPY_SCALAR_FIELD(concurrent); return newnode; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 121fbd0d2eee7682c9d0badbe7035af06aa0e625..6fceab27855df108f677efaca80581349505fec2 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.358 2009/07/26 23:34:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.359 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1160,6 +1160,8 @@ _equalIndexStmt(IndexStmt *a, IndexStmt *b) COMPARE_SCALAR_FIELD(unique); COMPARE_SCALAR_FIELD(primary); COMPARE_SCALAR_FIELD(isconstraint); + COMPARE_SCALAR_FIELD(deferrable); + COMPARE_SCALAR_FIELD(initdeferred); COMPARE_SCALAR_FIELD(concurrent); return true; @@ -2068,6 +2070,8 @@ _equalConstraint(Constraint *a, Constraint *b) COMPARE_NODE_FIELD(keys); COMPARE_NODE_FIELD(options); COMPARE_STRING_FIELD(indexspace); + COMPARE_SCALAR_FIELD(deferrable); + COMPARE_SCALAR_FIELD(initdeferred); return true; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 4e808a5868e3936dd0fb06a69674ab11bbdec2d6..e4de1c5aee2c54c37705a6a63db37fe621c45345 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.361 2009/07/16 06:33:42 petere Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.362 2009/07/29 20:56:19 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -1734,6 +1734,8 @@ _outIndexStmt(StringInfo str, IndexStmt *node) WRITE_BOOL_FIELD(unique); WRITE_BOOL_FIELD(primary); WRITE_BOOL_FIELD(isconstraint); + WRITE_BOOL_FIELD(deferrable); + WRITE_BOOL_FIELD(initdeferred); WRITE_BOOL_FIELD(concurrent); } @@ -2285,6 +2287,8 @@ _outConstraint(StringInfo str, Constraint *node) WRITE_NODE_FIELD(keys); WRITE_NODE_FIELD(options); WRITE_STRING_FIELD(indexspace); + WRITE_BOOL_FIELD(deferrable); + WRITE_BOOL_FIELD(initdeferred); break; case CONSTR_UNIQUE: @@ -2292,6 +2296,8 @@ _outConstraint(StringInfo str, Constraint *node) WRITE_NODE_FIELD(keys); WRITE_NODE_FIELD(options); WRITE_STRING_FIELD(indexspace); + WRITE_BOOL_FIELD(deferrable); + WRITE_BOOL_FIELD(initdeferred); break; case CONSTR_CHECK: diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c44bbe75c3bd427846ce7961cc889b7610b9645b..0547b64a6e76ae3c4c9c28a7b6c791c3a0b9a3be 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.673 2009/07/26 23:34:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.674 2009/07/29 20:56:19 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -2220,6 +2220,8 @@ ColConstraintElem: n->cooked_expr = NULL; n->keys = NULL; n->indexspace = NULL; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | NULL_P @@ -2231,6 +2233,8 @@ ColConstraintElem: n->cooked_expr = NULL; n->keys = NULL; n->indexspace = NULL; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | UNIQUE opt_definition OptConsTableSpace @@ -2243,6 +2247,8 @@ ColConstraintElem: n->keys = NULL; n->options = $2; n->indexspace = $3; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | PRIMARY KEY opt_definition OptConsTableSpace @@ -2255,6 +2261,8 @@ ColConstraintElem: n->keys = NULL; n->options = $3; n->indexspace = $4; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | CHECK '(' a_expr ')' @@ -2266,6 +2274,8 @@ ColConstraintElem: n->cooked_expr = NULL; n->keys = NULL; n->indexspace = NULL; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | DEFAULT b_expr @@ -2277,6 +2287,8 @@ ColConstraintElem: n->cooked_expr = NULL; n->keys = NULL; n->indexspace = NULL; + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | REFERENCES qualified_name opt_column_list key_match key_actions @@ -2398,7 +2410,7 @@ TableConstraint: ; ConstraintElem: - CHECK '(' a_expr ')' + CHECK '(' a_expr ')' ConstraintAttributeSpec { Constraint *n = makeNode(Constraint); n->contype = CONSTR_CHECK; @@ -2406,9 +2418,17 @@ ConstraintElem: n->raw_expr = $3; n->cooked_expr = NULL; n->indexspace = NULL; + if ($5 != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CHECK constraints cannot be deferred"), + parser_errposition(@5))); + n->deferrable = FALSE; + n->initdeferred = FALSE; $$ = (Node *)n; } | UNIQUE '(' columnList ')' opt_definition OptConsTableSpace + ConstraintAttributeSpec { Constraint *n = makeNode(Constraint); n->contype = CONSTR_UNIQUE; @@ -2418,9 +2438,12 @@ ConstraintElem: n->keys = $3; n->options = $5; n->indexspace = $6; + n->deferrable = ($7 & 1) != 0; + n->initdeferred = ($7 & 2) != 0; $$ = (Node *)n; } | PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace + ConstraintAttributeSpec { Constraint *n = makeNode(Constraint); n->contype = CONSTR_PRIMARY; @@ -2430,6 +2453,8 @@ ConstraintElem: n->keys = $4; n->options = $6; n->indexspace = $7; + n->deferrable = ($8 & 1) != 0; + n->initdeferred = ($8 & 2) != 0; $$ = (Node *)n; } | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index a5d805aa98b51cff602133947670e0dca34fd27e..94c8c5977bc5f995bd270022ac4b227c138b681b 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.23 2009/07/16 06:33:43 petere Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.24 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -33,6 +33,7 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/namespace.h" +#include "catalog/pg_constraint.h" #include "catalog/pg_opclass.h" #include "catalog/pg_type.h" #include "commands/defrem.h" @@ -801,15 +802,38 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx, /* * If the index is marked PRIMARY, it's certainly from a constraint; else, - * if it's not marked UNIQUE, it certainly isn't; else, we have to search - * pg_depend to see if there's an associated unique constraint. + * if it's not marked UNIQUE, it certainly isn't. If it is or might be + * from a constraint, we have to fetch the constraint to check for + * deferrability attributes. */ - if (index->primary) - index->isconstraint = true; - else if (!index->unique) - index->isconstraint = false; + if (index->primary || index->unique) + { + Oid constraintId = get_index_constraint(source_relid); + + if (OidIsValid(constraintId)) + { + HeapTuple ht_constr; + Form_pg_constraint conrec; + + ht_constr = SearchSysCache(CONSTROID, + ObjectIdGetDatum(constraintId), + 0, 0, 0); + if (!HeapTupleIsValid(ht_constr)) + elog(ERROR, "cache lookup failed for constraint %u", + constraintId); + conrec = (Form_pg_constraint) GETSTRUCT(ht_constr); + + index->isconstraint = true; + index->deferrable = conrec->condeferrable; + index->initdeferred = conrec->condeferred; + + ReleaseSysCache(ht_constr); + } + else + index->isconstraint = false; + } else - index->isconstraint = OidIsValid(get_index_constraint(source_relid)); + index->isconstraint = false; /* Get the index expressions, if any */ datum = SysCacheGetAttr(INDEXRELID, ht_idx, @@ -1039,7 +1063,9 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt) if (equal(index->indexParams, priorindex->indexParams) && equal(index->whereClause, priorindex->whereClause) && - strcmp(index->accessMethod, priorindex->accessMethod) == 0) + strcmp(index->accessMethod, priorindex->accessMethod) == 0 && + index->deferrable == priorindex->deferrable && + index->initdeferred == priorindex->initdeferred) { priorindex->unique |= index->unique; @@ -1092,6 +1118,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) */ } index->isconstraint = true; + index->deferrable = constraint->deferrable; + index->initdeferred = constraint->initdeferred; if (constraint->name != NULL) index->idxname = pstrdup(constraint->name); @@ -1853,8 +1881,9 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) * to attach constraint attributes to their primary constraint nodes * and detect inconsistent/misplaced constraint attributes. * - * NOTE: currently, attributes are only supported for FOREIGN KEY primary - * constraints, but someday they ought to be supported for other constraints. + * NOTE: currently, attributes are only supported for FOREIGN KEY, UNIQUE, + * and PRIMARY KEY constraints, but someday they ought to be supported + * for other constraint types. */ static void transformConstraintAttrs(List *constraintList) @@ -1864,6 +1893,13 @@ transformConstraintAttrs(List *constraintList) bool saw_initially = false; ListCell *clist; +#define SUPPORTS_ATTRS(node) \ + ((node) != NULL && \ + (IsA((node), FkConstraint) || \ + (IsA((node), Constraint) && \ + (((Constraint *) (node))->contype == CONSTR_PRIMARY || \ + ((Constraint *) (node))->contype == CONSTR_UNIQUE)))) + foreach(clist, constraintList) { Node *node = lfirst(clist); @@ -1882,8 +1918,7 @@ transformConstraintAttrs(List *constraintList) switch (con->contype) { case CONSTR_ATTR_DEFERRABLE: - if (lastprimarynode == NULL || - !IsA(lastprimarynode, FkConstraint)) + if (!SUPPORTS_ATTRS(lastprimarynode)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced DEFERRABLE clause"))); @@ -1892,11 +1927,14 @@ transformConstraintAttrs(List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"))); saw_deferrability = true; - ((FkConstraint *) lastprimarynode)->deferrable = true; + if (IsA(lastprimarynode, FkConstraint)) + ((FkConstraint *) lastprimarynode)->deferrable = true; + else + ((Constraint *) lastprimarynode)->deferrable = true; break; + case CONSTR_ATTR_NOT_DEFERRABLE: - if (lastprimarynode == NULL || - !IsA(lastprimarynode, FkConstraint)) + if (!SUPPORTS_ATTRS(lastprimarynode)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced NOT DEFERRABLE clause"))); @@ -1905,16 +1943,28 @@ transformConstraintAttrs(List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"))); saw_deferrability = true; - ((FkConstraint *) lastprimarynode)->deferrable = false; - if (saw_initially && - ((FkConstraint *) lastprimarynode)->initdeferred) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + if (IsA(lastprimarynode, FkConstraint)) + { + ((FkConstraint *) lastprimarynode)->deferrable = false; + if (saw_initially && + ((FkConstraint *) lastprimarynode)->initdeferred) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + } + else + { + ((Constraint *) lastprimarynode)->deferrable = false; + if (saw_initially && + ((Constraint *) lastprimarynode)->initdeferred) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + } break; + case CONSTR_ATTR_DEFERRED: - if (lastprimarynode == NULL || - !IsA(lastprimarynode, FkConstraint)) + if (!SUPPORTS_ATTRS(lastprimarynode)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced INITIALLY DEFERRED clause"))); @@ -1923,21 +1973,36 @@ transformConstraintAttrs(List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"))); saw_initially = true; - ((FkConstraint *) lastprimarynode)->initdeferred = true; /* * If only INITIALLY DEFERRED appears, assume DEFERRABLE */ - if (!saw_deferrability) - ((FkConstraint *) lastprimarynode)->deferrable = true; - else if (!((FkConstraint *) lastprimarynode)->deferrable) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + if (IsA(lastprimarynode, FkConstraint)) + { + ((FkConstraint *) lastprimarynode)->initdeferred = true; + + if (!saw_deferrability) + ((FkConstraint *) lastprimarynode)->deferrable = true; + else if (!((FkConstraint *) lastprimarynode)->deferrable) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + } + else + { + ((Constraint *) lastprimarynode)->initdeferred = true; + + if (!saw_deferrability) + ((Constraint *) lastprimarynode)->deferrable = true; + else if (!((Constraint *) lastprimarynode)->deferrable) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + } break; + case CONSTR_ATTR_IMMEDIATE: - if (lastprimarynode == NULL || - !IsA(lastprimarynode, FkConstraint)) + if (!SUPPORTS_ATTRS(lastprimarynode)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced INITIALLY IMMEDIATE clause"))); @@ -1946,8 +2011,12 @@ transformConstraintAttrs(List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"))); saw_initially = true; - ((FkConstraint *) lastprimarynode)->initdeferred = false; + if (IsA(lastprimarynode, FkConstraint)) + ((FkConstraint *) lastprimarynode)->initdeferred = false; + else + ((Constraint *) lastprimarynode)->initdeferred = false; break; + default: /* Otherwise it's not an attribute */ lastprimarynode = node; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 126a079f3e473573cd70dbc3fccfd26fd5899fdc..c6fefccfc6f6110298f22f005b9405efb7462355 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.312 2009/07/28 02:56:30 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.313 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -795,6 +795,8 @@ ProcessUtility(Node *parsetree, stmt->unique, stmt->primary, stmt->isconstraint, + stmt->deferrable, + stmt->initdeferred, false, /* is_alter_table */ true, /* check_rights */ false, /* skip_build */ @@ -929,7 +931,7 @@ ProcessUtility(Node *parsetree, case T_CreateTrigStmt: CreateTrigger((CreateTrigStmt *) parsetree, - InvalidOid, InvalidOid, true); + InvalidOid, InvalidOid, NULL, true); break; case T_DropPropertyStmt: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 9767b9f1f508db3c68efe1ee1268ccdac766096e..752e84dbfea0111d2ba25e4bcda62fb7123fb1b1 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.304 2009/07/24 21:08:42 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.305 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1044,11 +1044,6 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, if (string) appendStringInfo(&buf, " ON DELETE %s", string); - if (conForm->condeferrable) - appendStringInfo(&buf, " DEFERRABLE"); - if (conForm->condeferred) - appendStringInfo(&buf, " INITIALLY DEFERRED"); - break; } case CONSTRAINT_PRIMARY: @@ -1150,6 +1145,11 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, break; } + if (conForm->condeferrable) + appendStringInfo(&buf, " DEFERRABLE"); + if (conForm->condeferred) + appendStringInfo(&buf, " INITIALLY DEFERRED"); + /* Cleanup */ ReleaseSysCache(tup); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 775865d5696842faff1d70a82c6a446956d00577..639593743d0f0e4369e548f697a76db42b0ee70c 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.287 2009/06/11 14:49:05 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.288 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2938,7 +2938,7 @@ RelationGetIndexList(Relation relation) /* Check to see if it is a unique, non-partial btree index on OID */ if (index->indnatts == 1 && - index->indisunique && + index->indisunique && index->indimmediate && index->indkey.values[0] == ObjectIdAttributeNumber && index->indclass.values[0] == OID_BTREE_OPS_OID && heap_attisnull(htup, Anum_pg_index_indpred)) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e05bd53d9ca7f10c8467d866bb32c7d4ee892114..b1883efe755c5db3e77f6ee6562c27f8a60b3d03 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.542 2009/07/23 22:59:40 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.543 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -3655,6 +3655,8 @@ getIndexes(TableInfo tblinfo[], int numTables) i_indisclustered, i_contype, i_conname, + i_condeferrable, + i_condeferred, i_contableoid, i_conoid, i_tablespace, @@ -3696,6 +3698,7 @@ getIndexes(TableInfo tblinfo[], int numTables) "t.relnatts AS indnkeys, " "i.indkey, i.indisclustered, " "c.contype, c.conname, " + "c.condeferrable, c.condeferred, " "c.tableoid AS contableoid, " "c.oid AS conoid, " "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, " @@ -3722,6 +3725,7 @@ getIndexes(TableInfo tblinfo[], int numTables) "t.relnatts AS indnkeys, " "i.indkey, i.indisclustered, " "c.contype, c.conname, " + "c.condeferrable, c.condeferred, " "c.tableoid AS contableoid, " "c.oid AS conoid, " "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, " @@ -3748,6 +3752,7 @@ getIndexes(TableInfo tblinfo[], int numTables) "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 tablespace, " @@ -3776,6 +3781,8 @@ getIndexes(TableInfo tblinfo[], int numTables) "CASE WHEN i.indisprimary THEN 'p'::char " "ELSE '0'::char END AS contype, " "t.relname AS conname, " + "false AS condeferrable, " + "false AS condeferred, " "0::oid AS contableoid, " "t.oid AS conoid, " "NULL AS tablespace, " @@ -3799,6 +3806,8 @@ getIndexes(TableInfo tblinfo[], int numTables) "CASE WHEN i.indisprimary THEN 'p'::char " "ELSE '0'::char END AS contype, " "t.relname AS conname, " + "false AS condeferrable, " + "false AS condeferred, " "0::oid AS contableoid, " "t.oid AS conoid, " "NULL AS tablespace, " @@ -3824,6 +3833,8 @@ getIndexes(TableInfo tblinfo[], int numTables) i_indisclustered = PQfnumber(res, "indisclustered"); i_contype = PQfnumber(res, "contype"); i_conname = PQfnumber(res, "conname"); + i_condeferrable = PQfnumber(res, "condeferrable"); + i_condeferred = PQfnumber(res, "condeferred"); i_contableoid = PQfnumber(res, "contableoid"); i_conoid = PQfnumber(res, "conoid"); i_tablespace = PQfnumber(res, "tablespace"); @@ -3884,6 +3895,8 @@ getIndexes(TableInfo tblinfo[], int numTables) constrinfo[j].condef = NULL; constrinfo[j].confrelid = InvalidOid; constrinfo[j].conindex = indxinfo[j].dobj.dumpId; + constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't'; + constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't'; constrinfo[j].conislocal = true; constrinfo[j].separate = true; @@ -3988,6 +4001,8 @@ getConstraints(TableInfo tblinfo[], int numTables) constrinfo[j].condef = strdup(PQgetvalue(res, j, i_condef)); constrinfo[j].confrelid = atooid(PQgetvalue(res, j, i_confrelid)); constrinfo[j].conindex = 0; + constrinfo[j].condeferrable = false; + constrinfo[j].condeferred = false; constrinfo[j].conislocal = true; constrinfo[j].separate = true; } @@ -4072,6 +4087,8 @@ getDomainConstraints(TypeInfo *tinfo) constrinfo[i].condef = strdup(PQgetvalue(res, i, i_consrc)); constrinfo[i].confrelid = InvalidOid; constrinfo[i].conindex = 0; + constrinfo[i].condeferrable = false; + constrinfo[i].condeferred = false; constrinfo[i].conislocal = true; constrinfo[i].separate = false; @@ -5071,6 +5088,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables) constrs[j].condef = strdup(PQgetvalue(res, j, 3)); constrs[j].confrelid = InvalidOid; constrs[j].conindex = 0; + constrs[j].condeferrable = false; + constrs[j].condeferred = false; constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't'); constrs[j].separate = false; @@ -10454,6 +10473,13 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo) 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. */ diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 3339bf75623b943ab6e6150faffc5df9275d2cb7..b6702cf5186009d4d066c64655055644053e7986 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.154 2009/06/11 14:49:07 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.155 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -332,6 +332,9 @@ typedef struct _triggerInfo * struct ConstraintInfo is used for all constraint types. However we * use a different objType for foreign key constraints, to make it easier * to sort them the way we want. + * + * Note: condeferrable and condeferred are currently only valid for + * unique/primary-key constraints. Otherwise that info is in condef. */ typedef struct _constraintInfo { @@ -342,6 +345,8 @@ typedef struct _constraintInfo char *condef; /* definition, if CHECK or FOREIGN KEY */ Oid confrelid; /* referenced table, if FOREIGN KEY */ DumpId conindex; /* identifies associated index if any */ + bool condeferrable; /* TRUE if constraint is DEFERRABLE */ + bool condeferred; /* TRUE if constraint is INITIALLY DEFERRED */ bool conislocal; /* TRUE if constraint has local definition */ bool separate; /* TRUE if must dump as separate item */ } ConstraintInfo; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 968b4218189c03c707e7d09a1fbe08f7cbf20473..6e288da67a88b5e7c0dd92898ca8ea383ed88c5c 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.225 2009/07/20 03:46:45 tgl Exp $ + * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.226 2009/07/29 20:56:19 tgl Exp $ */ #include "postgres_fe.h" @@ -1321,11 +1321,32 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT i.indisunique, i.indisprimary, i.indisclustered, "); if (pset.sversion >= 80200) - appendPQExpBuffer(&buf, "i.indisvalid, "); + appendPQExpBuffer(&buf, "i.indisvalid,\n"); else - appendPQExpBuffer(&buf, "true as indisvalid, "); - appendPQExpBuffer(&buf, "a.amname, c2.relname,\n" - " pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n" + appendPQExpBuffer(&buf, "true AS indisvalid,\n"); + if (pset.sversion >= 80500) + appendPQExpBuffer(&buf, + " (NOT i.indimmediate) AND " + "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, " + "pg_catalog.pg_constraint con WHERE " + "d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass AND " + "d.objid = i.indexrelid AND " + "d.refclassid = 'pg_catalog.pg_constraint'::pg_catalog.regclass AND " + "d.refobjid = con.oid AND d.deptype = 'i' AND " + "con.condeferrable) AS condeferrable,\n" + " (NOT i.indimmediate) AND " + "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, " + "pg_catalog.pg_constraint con WHERE " + "d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass AND " + "d.objid = i.indexrelid AND " + "d.refclassid = 'pg_catalog.pg_constraint'::pg_catalog.regclass AND " + "d.refobjid = con.oid AND d.deptype = 'i' AND " + "con.condeferred) AS condeferred,\n"); + else + appendPQExpBuffer(&buf, + " false AS condeferrable, false AS condeferred,\n"); + appendPQExpBuffer(&buf, " a.amname, c2.relname, " + "pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n" "FROM pg_catalog.pg_index i, pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_am a\n" "WHERE i.indexrelid = c.oid AND c.oid = '%s' AND c.relam = a.oid\n" "AND i.indrelid = c2.oid", @@ -1345,9 +1366,11 @@ describeOneTableDetails(const char *schemaname, char *indisprimary = PQgetvalue(result, 0, 1); char *indisclustered = PQgetvalue(result, 0, 2); char *indisvalid = PQgetvalue(result, 0, 3); - char *indamname = PQgetvalue(result, 0, 4); - char *indtable = PQgetvalue(result, 0, 5); - char *indpred = PQgetvalue(result, 0, 6); + char *deferrable = PQgetvalue(result, 0, 4); + char *deferred = PQgetvalue(result, 0, 5); + char *indamname = PQgetvalue(result, 0, 6); + char *indtable = PQgetvalue(result, 0, 7); + char *indpred = PQgetvalue(result, 0, 8); if (strcmp(indisprimary, "t") == 0) printfPQExpBuffer(&tmpbuf, _("primary key, ")); @@ -1370,6 +1393,12 @@ describeOneTableDetails(const char *schemaname, if (strcmp(indisvalid, "t") != 0) appendPQExpBuffer(&tmpbuf, _(", invalid")); + if (strcmp(deferrable, "t") == 0) + appendPQExpBuffer(&tmpbuf, _(", deferrable")); + + if (strcmp(deferred, "t") == 0) + appendPQExpBuffer(&tmpbuf, _(", initially deferred")); + printTableAddFooter(&cont, tmpbuf.data); add_tablespace_footer(&cont, tableinfo.relkind, tableinfo.tablespace, true); @@ -1431,6 +1460,26 @@ describeOneTableDetails(const char *schemaname, else appendPQExpBuffer(&buf, "true as indisvalid, "); appendPQExpBuffer(&buf, "pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)"); + if (pset.sversion >= 80500) + appendPQExpBuffer(&buf, + ",\n (NOT i.indimmediate) AND " + "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, " + "pg_catalog.pg_constraint con WHERE " + "d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass AND " + "d.objid = i.indexrelid AND " + "d.refclassid = 'pg_catalog.pg_constraint'::pg_catalog.regclass AND " + "d.refobjid = con.oid AND d.deptype = 'i' AND " + "con.condeferrable) AS condeferrable" + ",\n (NOT i.indimmediate) AND " + "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, " + "pg_catalog.pg_constraint con WHERE " + "d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass AND " + "d.objid = i.indexrelid AND " + "d.refclassid = 'pg_catalog.pg_constraint'::pg_catalog.regclass AND " + "d.refobjid = con.oid AND d.deptype = 'i' AND " + "con.condeferred) AS condeferred"); + else + appendPQExpBuffer(&buf, ", false AS condeferrable, false AS condeferred"); if (pset.sversion >= 80000) appendPQExpBuffer(&buf, ", c2.reltablespace"); appendPQExpBuffer(&buf, @@ -1477,12 +1526,18 @@ describeOneTableDetails(const char *schemaname, if (strcmp(PQgetvalue(result, i, 4), "t") != 0) appendPQExpBuffer(&buf, " INVALID"); + if (strcmp(PQgetvalue(result, i, 6), "t") == 0) + appendPQExpBuffer(&buf, " DEFERRABLE"); + + if (strcmp(PQgetvalue(result, i, 7), "t") == 0) + appendPQExpBuffer(&buf, " INITIALLY DEFERRED"); + printTableAddFooter(&cont, buf.data); /* Print tablespace of the index on the same line */ if (pset.sversion >= 80000) add_tablespace_footer(&cont, 'i', - atooid(PQgetvalue(result, i, 6)), + atooid(PQgetvalue(result, i, 8)), false); } } @@ -1677,7 +1732,7 @@ describeOneTableDetails(const char *schemaname, PQclear(result); } - /* print triggers (but ignore foreign-key triggers) */ + /* print triggers (but ignore RI and unique constraint triggers) */ if (tableinfo.hastriggers) { printfPQExpBuffer(&buf, diff --git a/src/include/access/genam.h b/src/include/access/genam.h index bf3fe96e26d5b0039e879a3050e27a69a19bde5f..a8c0e029296fb9dc62df7523a960678bc44458bf 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.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/access/genam.h,v 1.78 2009/06/11 14:49:08 momjian Exp $ + * $PostgreSQL: pgsql/src/include/access/genam.h,v 1.79 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -85,6 +85,34 @@ typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state); typedef struct IndexScanDescData *IndexScanDesc; typedef struct SysScanDescData *SysScanDesc; +/* + * Enumeration specifying the type of uniqueness check to perform in + * index_insert(). + * + * UNIQUE_CHECK_YES is the traditional Postgres immediate check, possibly + * blocking to see if a conflicting transaction commits. + * + * For deferrable unique constraints, UNIQUE_CHECK_PARTIAL is specified at + * insertion time. The index AM should test if the tuple is unique, but + * should not throw error, block, or prevent the insertion if the tuple + * appears not to be unique. We'll recheck later when it is time for the + * constraint to be enforced. The AM must return true if the tuple is + * known unique, false if it is possibly non-unique. In the "true" case + * it is safe to omit the later recheck. + * + * When it is time to recheck the deferred constraint, a pseudo-insertion + * call is made with UNIQUE_CHECK_EXISTING. The tuple is already in the + * index in this case, so it should not be inserted again. Rather, just + * check for conflicting live tuples (possibly blocking). + */ +typedef enum IndexUniqueCheck +{ + UNIQUE_CHECK_NO, /* Don't do any uniqueness checking */ + UNIQUE_CHECK_YES, /* Enforce uniqueness at insertion time */ + UNIQUE_CHECK_PARTIAL, /* Test uniqueness, but no error */ + UNIQUE_CHECK_EXISTING /* Check if existing tuple is unique */ +} IndexUniqueCheck; + /* * generalized index_ interface routines (in indexam.c) @@ -103,7 +131,7 @@ extern bool index_insert(Relation indexRelation, Datum *values, bool *isnull, ItemPointer heap_t_ctid, Relation heapRelation, - bool check_uniqueness); + IndexUniqueCheck checkUnique); extern IndexScanDesc index_beginscan(Relation heapRelation, Relation indexRelation, diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 1d3e42d99bcee5e9cb3195a8ac9b79bd06b276b6..ed5ec57e47fec4b0faee327657b21875f6580905 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.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/access/nbtree.h,v 1.124 2009/06/11 14:49:08 momjian Exp $ + * $PostgreSQL: pgsql/src/include/access/nbtree.h,v 1.125 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -517,8 +517,8 @@ extern Datum btoptions(PG_FUNCTION_ARGS); /* * prototypes for functions in nbtinsert.c */ -extern void _bt_doinsert(Relation rel, IndexTuple itup, - bool index_is_unique, Relation heapRel); +extern bool _bt_doinsert(Relation rel, IndexTuple itup, + IndexUniqueCheck checkUnique, Relation heapRel); extern Buffer _bt_getstackbuf(Relation rel, BTStack stack, int access); extern void _bt_insert_parent(Relation rel, Buffer buf, Buffer rbuf, BTStack stack, bool is_root, bool is_only); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index ecbf1056063d518e6b3950f6ac420d58344824f5..847362ad273ea0cc9da477a722b5240e770ce85f 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.533 2009/07/28 02:56:30 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.534 2009/07/29 20:56:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200907271 +#define CATALOG_VERSION_NO 200907291 #endif diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 18064889aa10ee9dfb05684d602ca883894885f1..a432260058f1308ca11fb878ecc11be9b0f6f223 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.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/catalog/index.h,v 1.77 2009/01/01 17:23:56 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.78 2009/07/29 20:56:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -39,6 +39,8 @@ extern Oid index_create(Oid heapRelationId, Datum reloptions, bool isprimary, bool isconstraint, + bool deferrable, + bool initdeferred, bool allow_system_table_mods, bool skip_build, bool concurrent); diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index b852a28cd51ab97fdb809936658195bac81acf2c..e1c6fc83731b71cd56ac2b8e6463bc503d3c70c0 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.148 2009/06/11 14:49:09 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.149 2009/07/29 20:56:20 tgl Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -469,14 +469,15 @@ DATA(insert ( 1259 tableoid 26 0 4 -7 0 -1 -1 t p i t f f t 0 _null_)); { 0, {"indnatts"}, 21, -1, 2, 3, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \ { 0, {"indisunique"}, 16, -1, 1, 4, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ { 0, {"indisprimary"}, 16, -1, 1, 5, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ -{ 0, {"indisclustered"}, 16, -1, 1, 6, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ -{ 0, {"indisvalid"}, 16, -1, 1, 7, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ -{ 0, {"indcheckxmin"}, 16, -1, 1, 8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ -{ 0, {"indisready"}, 16, -1, 1, 9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ -{ 0, {"indkey"}, 22, -1, -1, 10, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \ -{ 0, {"indclass"}, 30, -1, -1, 11, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \ -{ 0, {"indoption"}, 22, -1, -1, 12, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \ -{ 0, {"indexprs"}, 25, -1, -1, 13, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \ -{ 0, {"indpred"}, 25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } } +{ 0, {"indimmediate"}, 16, -1, 1, 6, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ +{ 0, {"indisclustered"}, 16, -1, 1, 7, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ +{ 0, {"indisvalid"}, 16, -1, 1, 8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ +{ 0, {"indcheckxmin"}, 16, -1, 1, 9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ +{ 0, {"indisready"}, 16, -1, 1, 10, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \ +{ 0, {"indkey"}, 22, -1, -1, 11, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \ +{ 0, {"indclass"}, 30, -1, -1, 12, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \ +{ 0, {"indoption"}, 22, -1, -1, 13, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \ +{ 0, {"indexprs"}, 25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \ +{ 0, {"indpred"}, 25, -1, -1, 15, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } } #endif /* PG_ATTRIBUTE_H */ diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h index 1ec2e49a59a9e7259e50d4c68b20c7ba6a3882cc..36ffef5ed8750dae5245d264d3dcd599e3d6ab76 100644 --- a/src/include/catalog/pg_index.h +++ b/src/include/catalog/pg_index.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_index.h,v 1.47 2009/01/01 17:23:57 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_index.h,v 1.48 2009/07/29 20:56:20 tgl Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -35,6 +35,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS int2 indnatts; /* number of columns in index */ bool indisunique; /* is this a unique index? */ bool indisprimary; /* is this index for primary key? */ + bool indimmediate; /* is uniqueness enforced immediately? */ bool indisclustered; /* is this the index last clustered by? */ bool indisvalid; /* is this index valid for use by queries? */ bool indcheckxmin; /* must we wait for xmin to be old? */ @@ -62,21 +63,22 @@ typedef FormData_pg_index *Form_pg_index; * compiler constants for pg_index * ---------------- */ -#define Natts_pg_index 14 +#define Natts_pg_index 15 #define Anum_pg_index_indexrelid 1 #define Anum_pg_index_indrelid 2 #define Anum_pg_index_indnatts 3 #define Anum_pg_index_indisunique 4 #define Anum_pg_index_indisprimary 5 -#define Anum_pg_index_indisclustered 6 -#define Anum_pg_index_indisvalid 7 -#define Anum_pg_index_indcheckxmin 8 -#define Anum_pg_index_indisready 9 -#define Anum_pg_index_indkey 10 -#define Anum_pg_index_indclass 11 -#define Anum_pg_index_indoption 12 -#define Anum_pg_index_indexprs 13 -#define Anum_pg_index_indpred 14 +#define Anum_pg_index_indimmediate 6 +#define Anum_pg_index_indisclustered 7 +#define Anum_pg_index_indisvalid 8 +#define Anum_pg_index_indcheckxmin 9 +#define Anum_pg_index_indisready 10 +#define Anum_pg_index_indkey 11 +#define Anum_pg_index_indclass 12 +#define Anum_pg_index_indoption 13 +#define Anum_pg_index_indexprs 14 +#define Anum_pg_index_indpred 15 /* * Index AMs that support ordered scans must support these two indoption diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 218ae74176608d37c3b600c58dc50078e1cea30a..d5884faa4a6d42468e92452b4d6c827ac23afea8 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.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/catalog/pg_proc.h,v 1.546 2009/07/07 18:49:16 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.547 2009/07/29 20:56:20 tgl Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -2323,6 +2323,10 @@ DESCR("convert generic options array to name/value table"); DATA(insert OID = 1619 ( pg_typeof PGNSP PGUID 12 1 0 0 f f f f f s 1 0 2206 "2276" _null_ _null_ _null_ _null_ pg_typeof _null_ _null_ _null_ )); DESCR("returns the type of the argument"); +/* Deferrable unique constraint trigger */ +DATA(insert OID = 1250 ( unique_key_recheck PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_recheck _null_ _null_ _null_ )); +DESCR("deferred UNIQUE constraint check"); + /* Generic referential integrity constraint triggers */ DATA(insert OID = 1644 ( RI_FKey_check_ins PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ RI_FKey_check_ins _null_ _null_ _null_ )); DESCR("referential integrity FOREIGN KEY ... REFERENCES"); diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 1d6ab7bbf4f50dad44711c0f522d1d8c2ee76478..4396a497385be58ddbce3ab84c5a9b3a012b5ae4 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.95 2009/07/16 06:33:45 petere Exp $ + * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.96 2009/07/29 20:56:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -29,6 +29,8 @@ extern void DefineIndex(RangeVar *heapRelation, bool unique, bool primary, bool isconstraint, + bool deferrable, + bool initdeferred, bool is_alter_table, bool check_rights, bool skip_build, diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 3e14bbe3baa12b2f770ff3e70394625bd20b7075..c92337a5a1b06e150c76d8e3c10a39b2d9229d08 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,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/trigger.h,v 1.74 2009/07/28 02:56:31 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.75 2009/07/29 20:56:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -105,7 +105,7 @@ extern PGDLLIMPORT int SessionReplicationRole; #define TRIGGER_DISABLED 'D' extern Oid CreateTrigger(CreateTrigStmt *stmt, - Oid constraintOid, Oid indexOid, + Oid constraintOid, Oid indexOid, const char *prefix, bool checkPermissions); extern void DropTrigger(Oid relid, const char *trigname, @@ -132,7 +132,8 @@ extern HeapTuple ExecBRInsertTriggers(EState *estate, HeapTuple trigtuple); extern void ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, - HeapTuple trigtuple); + HeapTuple trigtuple, + List *recheckIndexes); extern void ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo); extern void ExecASDeleteTriggers(EState *estate, @@ -154,7 +155,8 @@ extern HeapTuple ExecBRUpdateTriggers(EState *estate, extern void ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, - HeapTuple newtuple); + HeapTuple newtuple, + List *recheckIndexes); extern void ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo); extern void ExecASTruncateTriggers(EState *estate, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 86148ed1354cf30944505602e274e16890c0096e..2ad1e26b2a1f9e95d53f1d03fd99e56a15d8f963 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.157 2009/07/22 17:00:23 tgl Exp $ + * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.158 2009/07/29 20:56:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -302,8 +302,8 @@ extern void ExecCloseScanRelation(Relation scanrel); extern void ExecOpenIndices(ResultRelInfo *resultRelInfo); extern void ExecCloseIndices(ResultRelInfo *resultRelInfo); -extern void ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid, - EState *estate, bool is_vacuum); +extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid, + EState *estate, bool is_vacuum_full); extern void RegisterExprContextCallback(ExprContext *econtext, ExprContextCallbackFunction function, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 5947c6acc982467e3d0b736bff88216f39e5581c..ae2f087360887092d1cc2bd5f95765a30f47c12a 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.398 2009/07/26 23:34:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.399 2009/07/29 20:56:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1355,7 +1355,7 @@ typedef struct CreateStmt * Constraint attributes (DEFERRABLE etc) are initially represented as * separate Constraint nodes for simplicity of parsing. parse_utilcmd.c makes * a pass through the constraints list to attach the info to the appropriate - * FkConstraint node (and, perhaps, someday to other kinds of constraints). + * Constraint and FkConstraint nodes. * ---------- */ @@ -1385,6 +1385,8 @@ typedef struct Constraint List *options; /* options from WITH clause */ char *indexspace; /* index tablespace for PKEY/UNIQUE * constraints; NULL for default */ + bool deferrable; /* DEFERRABLE */ + bool initdeferred; /* INITIALLY DEFERRED */ } Constraint; /* ---------- @@ -1555,12 +1557,11 @@ typedef struct CreateTrigStmt /* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */ int16 events; /* INSERT/UPDATE/DELETE/TRUNCATE */ - /* The following are used for referential */ - /* integrity constraint triggers */ - bool isconstraint; /* This is an RI trigger */ + /* The following are used for constraint triggers (RI and unique checks) */ + bool isconstraint; /* This is a constraint trigger */ bool deferrable; /* [NOT] DEFERRABLE */ bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */ - RangeVar *constrrel; /* opposite relation */ + RangeVar *constrrel; /* opposite relation, if RI trigger */ } CreateTrigStmt; /* ---------------------- @@ -1864,6 +1865,8 @@ typedef struct IndexStmt bool unique; /* is index unique? */ bool primary; /* is index on primary key? */ bool isconstraint; /* is it from a CONSTRAINT clause? */ + bool deferrable; /* is the constraint DEFERRABLE? */ + bool initdeferred; /* is the constraint INITIALLY DEFERRED? */ bool concurrent; /* should this be a concurrent index build? */ } IndexStmt; diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 5547b6c8c93df0f40671404652e684d79d3fba9a..72b39f70685181b76bfd6020d96de6d6e940b12c 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.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/builtins.h,v 1.334 2009/07/16 06:33:46 petere Exp $ + * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.335 2009/07/29 20:56:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1029,6 +1029,9 @@ extern Datum window_nth_value(PG_FUNCTION_ARGS); /* access/transam/twophase.c */ extern Datum pg_prepared_xact(PG_FUNCTION_ARGS); +/* commands/constraint.c */ +extern Datum unique_key_recheck(PG_FUNCTION_ARGS); + /* commands/prepare.c */ extern Datum pg_prepared_statement(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index c6f1f158ee55cd48471074ac2338a2eff90b7775..7213192d5f33bf10b97482edeb32623d6ffa2cf1 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -164,7 +164,8 @@ FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace WHERE relhasoids AND ((nspname ~ '^pg_') IS NOT FALSE) AND NOT EXISTS (SELECT 1 FROM pg_index i WHERE indrelid = c.oid - AND indkey[0] = -2 AND indnatts = 1 AND indisunique); + AND indkey[0] = -2 AND indnatts = 1 + AND indisunique AND indimmediate); relname | nspname ---------+--------- (0 rows) diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source index 350f29152a2ffc08f4125276ee1f4c2afd1e52ea..178b159c70163eceee3faa1af613d76e09823526 100644 --- a/src/test/regress/input/constraints.source +++ b/src/test/regress/input/constraints.source @@ -259,3 +259,110 @@ SELECT '' AS five, * FROM UNIQUE_TBL; DROP TABLE UNIQUE_TBL; +-- +-- Deferrable unique constraints +-- + +CREATE TABLE unique_tbl (i int UNIQUE DEFERRABLE, t text); + +INSERT INTO unique_tbl VALUES (0, 'one'); +INSERT INTO unique_tbl VALUES (1, 'two'); +INSERT INTO unique_tbl VALUES (2, 'tree'); +INSERT INTO unique_tbl VALUES (3, 'four'); +INSERT INTO unique_tbl VALUES (4, 'five'); + +BEGIN; + +-- default is immediate so this should fail right away +UPDATE unique_tbl SET i = 1 WHERE i = 0; + +ROLLBACK; + +-- check is done at end of statement, so this should succeed +UPDATE unique_tbl SET i = i+1; + +SELECT * FROM unique_tbl; + +-- explicitly defer the constraint +BEGIN; + +SET CONSTRAINTS unique_tbl_i_key DEFERRED; + +INSERT INTO unique_tbl VALUES (3, 'three'); +DELETE FROM unique_tbl WHERE t = 'tree'; -- makes constraint valid again + +COMMIT; -- should succeed + +SELECT * FROM unique_tbl; + +-- try adding an initially deferred constraint +ALTER TABLE unique_tbl DROP CONSTRAINT unique_tbl_i_key; +ALTER TABLE unique_tbl ADD CONSTRAINT unique_tbl_i_key + UNIQUE (i) DEFERRABLE INITIALLY DEFERRED; + +BEGIN; + +INSERT INTO unique_tbl VALUES (1, 'five'); +INSERT INTO unique_tbl VALUES (5, 'one'); +UPDATE unique_tbl SET i = 4 WHERE i = 2; +UPDATE unique_tbl SET i = 2 WHERE i = 4 AND t = 'four'; +DELETE FROM unique_tbl WHERE i = 1 AND t = 'one'; +DELETE FROM unique_tbl WHERE i = 5 AND t = 'five'; + +COMMIT; + +SELECT * FROM unique_tbl; + +-- should fail at commit-time +BEGIN; +INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now +COMMIT; -- should fail + +-- make constraint check immediate +BEGIN; + +SET CONSTRAINTS ALL IMMEDIATE; + +INSERT INTO unique_tbl VALUES (3, 'Three'); -- should fail + +COMMIT; + +-- forced check when SET CONSTRAINTS is called +BEGIN; + +SET CONSTRAINTS ALL DEFERRED; + +INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now + +SET CONSTRAINTS ALL IMMEDIATE; -- should fail + +COMMIT; + +-- test a HOT update that invalidates the conflicting tuple. +-- the trigger should still fire and catch the violation + +BEGIN; + +INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now +UPDATE unique_tbl SET t = 'THREE' WHERE i = 3 AND t = 'Three'; + +COMMIT; -- should fail + +SELECT * FROM unique_tbl; + +-- test a HOT update that modifies the newly inserted tuple, +-- but should succeed because we then remove the other conflicting tuple. + +BEGIN; + +INSERT INTO unique_tbl VALUES(3, 'tree'); -- should succeed for now +UPDATE unique_tbl SET t = 'threex' WHERE t = 'tree'; +DELETE FROM unique_tbl WHERE t = 'three'; + +SELECT * FROM unique_tbl; + +COMMIT; + +SELECT * FROM unique_tbl; + +DROP TABLE unique_tbl; diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source index 654e8d995351348f4b0f442eb1f5ecf1a80e888f..e21aa6b7e16d69dbef53f63142fe42622abda193 100644 --- a/src/test/regress/output/constraints.source +++ b/src/test/regress/output/constraints.source @@ -375,3 +375,132 @@ SELECT '' AS five, * FROM UNIQUE_TBL; (5 rows) DROP TABLE UNIQUE_TBL; +-- +-- Deferrable unique constraints +-- +CREATE TABLE unique_tbl (i int UNIQUE DEFERRABLE, t text); +NOTICE: CREATE TABLE / UNIQUE will create implicit index "unique_tbl_i_key" for table "unique_tbl" +INSERT INTO unique_tbl VALUES (0, 'one'); +INSERT INTO unique_tbl VALUES (1, 'two'); +INSERT INTO unique_tbl VALUES (2, 'tree'); +INSERT INTO unique_tbl VALUES (3, 'four'); +INSERT INTO unique_tbl VALUES (4, 'five'); +BEGIN; +-- default is immediate so this should fail right away +UPDATE unique_tbl SET i = 1 WHERE i = 0; +ERROR: duplicate key value violates unique constraint "unique_tbl_i_key" +ROLLBACK; +-- check is done at end of statement, so this should succeed +UPDATE unique_tbl SET i = i+1; +SELECT * FROM unique_tbl; + i | t +---+------ + 1 | one + 2 | two + 3 | tree + 4 | four + 5 | five +(5 rows) + +-- explicitly defer the constraint +BEGIN; +SET CONSTRAINTS unique_tbl_i_key DEFERRED; +INSERT INTO unique_tbl VALUES (3, 'three'); +DELETE FROM unique_tbl WHERE t = 'tree'; -- makes constraint valid again +COMMIT; -- should succeed +SELECT * FROM unique_tbl; + i | t +---+------- + 1 | one + 2 | two + 4 | four + 5 | five + 3 | three +(5 rows) + +-- try adding an initially deferred constraint +ALTER TABLE unique_tbl DROP CONSTRAINT unique_tbl_i_key; +ALTER TABLE unique_tbl ADD CONSTRAINT unique_tbl_i_key + UNIQUE (i) DEFERRABLE INITIALLY DEFERRED; +NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "unique_tbl_i_key" for table "unique_tbl" +BEGIN; +INSERT INTO unique_tbl VALUES (1, 'five'); +INSERT INTO unique_tbl VALUES (5, 'one'); +UPDATE unique_tbl SET i = 4 WHERE i = 2; +UPDATE unique_tbl SET i = 2 WHERE i = 4 AND t = 'four'; +DELETE FROM unique_tbl WHERE i = 1 AND t = 'one'; +DELETE FROM unique_tbl WHERE i = 5 AND t = 'five'; +COMMIT; +SELECT * FROM unique_tbl; + i | t +---+------- + 3 | three + 1 | five + 5 | one + 4 | two + 2 | four +(5 rows) + +-- should fail at commit-time +BEGIN; +INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now +COMMIT; -- should fail +ERROR: duplicate key value violates unique constraint "unique_tbl_i_key" +-- make constraint check immediate +BEGIN; +SET CONSTRAINTS ALL IMMEDIATE; +INSERT INTO unique_tbl VALUES (3, 'Three'); -- should fail +ERROR: duplicate key value violates unique constraint "unique_tbl_i_key" +COMMIT; +-- forced check when SET CONSTRAINTS is called +BEGIN; +SET CONSTRAINTS ALL DEFERRED; +INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now +SET CONSTRAINTS ALL IMMEDIATE; -- should fail +ERROR: duplicate key value violates unique constraint "unique_tbl_i_key" +COMMIT; +-- test a HOT update that invalidates the conflicting tuple. +-- the trigger should still fire and catch the violation +BEGIN; +INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now +UPDATE unique_tbl SET t = 'THREE' WHERE i = 3 AND t = 'Three'; +COMMIT; -- should fail +ERROR: duplicate key value violates unique constraint "unique_tbl_i_key" +SELECT * FROM unique_tbl; + i | t +---+------- + 3 | three + 1 | five + 5 | one + 4 | two + 2 | four +(5 rows) + +-- test a HOT update that modifies the newly inserted tuple, +-- but should succeed because we then remove the other conflicting tuple. +BEGIN; +INSERT INTO unique_tbl VALUES(3, 'tree'); -- should succeed for now +UPDATE unique_tbl SET t = 'threex' WHERE t = 'tree'; +DELETE FROM unique_tbl WHERE t = 'three'; +SELECT * FROM unique_tbl; + i | t +---+-------- + 1 | five + 5 | one + 4 | two + 2 | four + 3 | threex +(5 rows) + +COMMIT; +SELECT * FROM unique_tbl; + i | t +---+-------- + 1 | five + 5 | one + 4 | two + 2 | four + 3 | threex +(5 rows) + +DROP TABLE unique_tbl; diff --git a/src/test/regress/sql/sanity_check.sql b/src/test/regress/sql/sanity_check.sql index b0d800bc797755aa677f0f36d003c86603fb689f..7ab0c91dfb92722098f35a5f20a970412e028cca 100644 --- a/src/test/regress/sql/sanity_check.sql +++ b/src/test/regress/sql/sanity_check.sql @@ -22,4 +22,5 @@ FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace WHERE relhasoids AND ((nspname ~ '^pg_') IS NOT FALSE) AND NOT EXISTS (SELECT 1 FROM pg_index i WHERE indrelid = c.oid - AND indkey[0] = -2 AND indnatts = 1 AND indisunique); + AND indkey[0] = -2 AND indnatts = 1 + AND indisunique AND indimmediate);