-<!-- $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>
-<!-- $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 — Invalid Cursor State</></entry>
<!--
-$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
-->
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> [, ... ] ) ]
[ 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 ]
<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>
</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
</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>
</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>
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.
<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.
<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,
</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>
</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>
*
*
* 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
*
* 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);
pg_get_indexdef_columns(RelationGetRelid(indexRelation),
true));
- for (i = 0; i < tupdesc->natts; i++)
+ for (i = 0; i < natts; i++)
{
char *val;
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]);
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
$8,
NULL,
$10,
- NULL, NIL,
+ NULL, NIL, NIL,
false, false, false, false, false,
false, false, true, false, false);
do_end();
$9,
NULL,
$11,
- NULL, NIL,
+ NULL, NIL, NIL,
true, false, false, false, false,
false, false, true, false, false);
do_end();
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
*
*
* 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
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);
' ',
' ',
' ',
- 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 */
*
*
* 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
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,
* 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,
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);
/*
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).
*/
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
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,
' ',
' ',
' ',
+ indexInfo->ii_ExclusionOps,
NULL, /* no check constraint */
NULL,
NULL,
index_update_stats(heapRelation,
true,
isprimary,
+ is_exclusion,
InvalidOid,
heapRelation->rd_rel->reltuples);
/* Make the above update visible */
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;
*
* 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
* 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);
* 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);
dirty = true;
}
}
+ if (hasexclusion)
+ {
+ if (!rd_rel->relhasexclusion)
+ {
+ rd_rel->relhasexclusion = true;
+ dirty = true;
+ }
+ }
if (OidIsValid(reltoastidxid))
{
Assert(rd_rel->relkind == RELKIND_TOASTVALUE);
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);
index_update_stats(heapRelation,
true,
isprimary,
+ (indexInfo->ii_ExclusionOps != NULL),
(heapRelation->rd_rel->relkind == RELKIND_TOASTVALUE) ?
RelationGetRelid(indexRelation) : InvalidOid,
stats->heap_tuples);
index_update_stats(indexRelation,
false,
false,
+ false,
InvalidOid,
stats->index_tuples);
* 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
/*
+ * 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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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.
*/
/*
* 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
*
* 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 $
*/
/*
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')
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
+ const Oid *exclOp,
Node *conExpr,
const char *conBin,
const char *conSrc,
ArrayType *conpfeqopArray;
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
+ ArrayType *conexclopArray;
NameData cname;
int i;
ObjectAddress conobject;
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++)
{
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.
*/
}
}
+ /*
+ * 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)
{
/*
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
* 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.
* 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.
*
/*
* 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);
* 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);
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
#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"
Oid *classOidP,
int16 *colOptionP,
List *attList,
+ List *exclusionOpNames,
Oid relId,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
* 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,
List *attributeList,
Expr *predicate,
List *options,
+ List *exclusionOpNames,
bool unique,
bool primary,
bool isconstraint,
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);
(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;
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;
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);
/*
* 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;
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;
/*
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.
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->options,
+ stmt->excludeOpNames,
stmt->unique,
stmt->primary,
stmt->isconstraint,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkconstraint->fk_matchtype,
+ NULL, /* no exclusion constraint */
NULL, /* no check constraint */
NULL,
NULL,
*
*
* 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
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),
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),
' ',
' ',
' ',
- 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 */
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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)
{
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 */
*
*
* 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);
* 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.
Relation indexRelation = relationDescs[i];
IndexInfo *indexInfo;
IndexUniqueCheck checkUnique;
- bool isUnique;
+ bool satisfiesConstraint;
if (indexRelation == NULL)
continue;
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.
else
checkUnique = UNIQUE_CHECK_PARTIAL;
- isUnique =
+ satisfiesConstraint =
index_insert(indexRelation, /* index relation */
values, /* array of index Datums */
isnull, /* null flags */
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));
}
}
/*
+ * 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
*/
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
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);
*
*
* 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*
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);
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
+ /* access_method and where_clause not currently used */
break;
case CONSTR_UNIQUE:
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:
*
*
* 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
%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
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
;
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;
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
{
}
;
+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
| ENCRYPTED
| ENUM_P
| ESCAPE
+ | EXCLUDE
| EXCLUDING
| EXCLUSIVE
| EXECUTE
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
saw_default = true;
break;
+ case CONSTR_CHECK:
+ cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
+ break;
+
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
if (constraint->keys == NIL)
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:
{
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
+ case CONSTR_EXCLUSION:
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
/*
* 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)
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);
/* 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);
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);
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
/*
* 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.
*/
/*
* 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)
{
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);
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)
/*
* 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)
{
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)
{
((node) != NULL && \
((node)->contype == CONSTR_PRIMARY || \
(node)->contype == CONSTR_UNIQUE || \
+ (node)->contype == CONSTR_EXCLUSION || \
(node)->contype == CONSTR_FOREIGN))
foreach(clist, constraintList)
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->options,
+ stmt->excludeOpNames,
stmt->unique,
stmt->primary,
stmt->isconstraint,
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
Oid indexrelid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, 0,
+ NULL,
false, false, 0)));
}
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)));
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 */
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;
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
appendStringInfo(&buf, " NULLS FIRST");
}
}
+
+ /* Add the exclusion operator if relevant */
+ if (excludeOps != NULL)
+ appendStringInfo(&buf, " WITH %s",
+ generate_operator_name(excludeOps[keyno],
+ keycoltype,
+ keycoltype));
}
}
tblspc = get_rel_tablespace(indexrelid);
if (OidIsValid(tblspc))
+ {
+ if (isConstraint)
+ appendStringInfoString(&buf, " USING INDEX");
appendStringInfo(&buf, " TABLESPACE %s",
quote_identifier(get_tablespace_name(tblspc)));
+ }
}
/*
/* 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);
}
}
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;
*
*
* 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"
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;
}
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
* 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
* 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 $
*
*-------------------------------------------------------------------------
*/
i_condeferred,
i_contableoid,
i_conoid,
+ i_condef,
i_tablespace,
i_options;
int ntups;
* 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, "
"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 "
"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 "
"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 "
"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 "
"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 "
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");
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
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';
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;
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)
*
* 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"
bool hasrules;
bool hastriggers;
bool hasoids;
+ bool hasexclusion;
Oid tablespace;
char *reloptions;
} tableinfo;
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, "
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;
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)
{
* 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 $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200912051
+#define CATALOG_VERSION_NO 200912071
#endif
* 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
{ 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_));
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_));
* 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
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 */
* ----------------
*/
-#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
#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
*/
/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f f r 28 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 25 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
#define RELKIND_INDEX 'i' /* secondary index */
* 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
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;
* 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
#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 */
#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
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
+ const Oid *exclOp,
Node *conExpr,
const char *conBin,
const char *conSrc,
* 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 $
*
*-------------------------------------------------------------------------
*/
List *attributeList,
Expr *predicate,
List *options,
+ List *exclusionOpNames,
bool unique,
bool primary,
bool isconstraint,
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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?
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
CONSTR_CHECK,
CONSTR_PRIMARY,
CONSTR_UNIQUE,
+ CONSTR_EXCLUSION,
CONSTR_FOREIGN,
CONSTR_ATTR_DEFERRABLE, /* attributes for previous constraint node */
CONSTR_ATTR_NOT_DEFERRABLE,
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 */
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? */
* 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 $
*
*-------------------------------------------------------------------------
*/
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)
*
* 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')
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
/*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
*
* 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
},
-- - CHECK clauses
-- - PRIMARY KEY clauses
-- - UNIQUE clauses
+-- - EXCLUDE clauses
--
--
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;
-- - CHECK clauses
-- - PRIMARY KEY clauses
-- - UNIQUE clauses
+-- - EXCLUDE clauses
--
--
-- DEFAULT syntax
(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;