OSDN Git Service

Add exclusion constraints, which generalize the concept of uniqueness to
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 7 Dec 2009 05:22:23 +0000 (05:22 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 7 Dec 2009 05:22:23 +0000 (05:22 +0000)
support any indexable commutative operator, not just equality.  Two rows
violate the exclusion constraint if "row1.col OP row2.col" is TRUE for
each of the columns in the constraint.

Jeff Davis, reviewed by Robert Haas

43 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/errcodes.sgml
doc/src/sgml/ref/create_table.sgml
src/backend/access/index/genam.c
src/backend/bootstrap/bootparse.y
src/backend/bootstrap/bootstrap.c
src/backend/catalog/heap.c
src/backend/catalog/index.c
src/backend/catalog/indexing.c
src/backend/catalog/information_schema.sql
src/backend/catalog/pg_constraint.c
src/backend/catalog/toasting.c
src/backend/commands/constraint.c
src/backend/commands/indexcmds.c
src/backend/commands/tablecmds.c
src/backend/commands/typecmds.c
src/backend/commands/vacuum.c
src/backend/executor/execUtils.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_utilcmd.c
src/backend/tcop/utility.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/cache/relcache.c
src/bin/pg_dump/pg_dump.c
src/bin/psql/describe.c
src/include/catalog/catversion.h
src/include/catalog/pg_attribute.h
src/include/catalog/pg_class.h
src/include/catalog/pg_constraint.h
src/include/commands/defrem.h
src/include/executor/executor.h
src/include/nodes/execnodes.h
src/include/nodes/parsenodes.h
src/include/parser/kwlist.h
src/include/utils/errcodes.h
src/include/utils/rel.h
src/include/utils/relcache.h
src/pl/plpgsql/src/plerrcodes.h
src/test/regress/input/constraints.source
src/test/regress/output/constraints.source

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