Commit 0cb65564 authored by Tom Lane's avatar Tom Lane

Add exclusion constraints, which generalize the concept of uniqueness to

support any indexable commutative operator, not just equality.  Two rows
violate the exclusion constraint if "row1.col OP row2.col" is TRUE for
each of the columns in the constraint.

Jeff Davis, reviewed by Robert Haas
parent 8de7472b
<!-- $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
-->
......@@ -1536,11 +1536,7 @@
<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>
......@@ -1617,6 +1613,17 @@
</entry>
</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>
......@@ -1680,6 +1687,17 @@
</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">
......@@ -1690,11 +1708,12 @@
</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>
......@@ -1739,7 +1758,8 @@
<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>
......@@ -1776,7 +1796,7 @@
<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>
......@@ -1828,7 +1848,7 @@
<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>
......@@ -1838,7 +1858,8 @@
<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>
......@@ -1878,6 +1899,13 @@
<entry>If a foreign key, list of the equality operators for FK = FK comparisons</entry>
</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>
......@@ -1895,6 +1923,16 @@
</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
......@@ -1908,7 +1946,8 @@
<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>
......@@ -640,6 +640,12 @@
<entry>check_violation</entry>
</row>
<row>
<entry><literal>23P01</literal></entry>
<entry>EXCLUSION VIOLATION</entry>
<entry>exclusion_violation</entry>
</row>
<row>
<entry spanname="span13"><emphasis role="bold">Class 24 &mdash; Invalid Cursor State</></entry>
......
This diff is collapsed.
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/index/genam.c,v 1.76 2009/08/01 20:59:17 tgl Exp $
* $PostgreSQL: pgsql/src/backend/access/index/genam.c,v 1.77 2009/12/07 05:22:21 tgl Exp $
*
* NOTES
* many of the old access method routines have been turned into
......@@ -137,21 +137,18 @@ IndexScanEnd(IndexScanDesc scan)
*
* Construct a string describing the contents of an index entry, in the
* form "(key_name, ...)=(key_value, ...)". This is currently used
* only for building unique-constraint error messages, but we don't want
* to hardwire the spelling of the messages here.
* for building unique-constraint and exclusion-constraint error messages.
*
* The passed-in values/nulls arrays are the "raw" input to the index AM,
* e.g. results of FormIndexDatum --- this is not necessarily what is stored
* in the index, but it's what the user perceives to be stored.
*/
char *
BuildIndexValueDescription(Relation indexRelation,
Datum *values, bool *isnull)
{
/*
* XXX for the moment we use the index's tupdesc as a guide to the
* datatypes of the values. This is okay for btree indexes but is in
* fact the wrong thing in general. This will have to be fixed if we
* are ever to support non-btree unique indexes.
*/
TupleDesc tupdesc = RelationGetDescr(indexRelation);
StringInfoData buf;
int natts = indexRelation->rd_rel->relnatts;
int i;
initStringInfo(&buf);
......@@ -159,7 +156,7 @@ BuildIndexValueDescription(Relation indexRelation,
pg_get_indexdef_columns(RelationGetRelid(indexRelation),
true));
for (i = 0; i < tupdesc->natts; i++)
for (i = 0; i < natts; i++)
{
char *val;
......@@ -170,7 +167,17 @@ BuildIndexValueDescription(Relation indexRelation,
Oid foutoid;
bool typisvarlena;
getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
/*
* The provided data is not necessarily of the type stored in
* the index; rather it is of the index opclass's input type.
* So look at rd_opcintype not the index tupdesc.
*
* Note: this is a bit shaky for opclasses that have pseudotype
* input types such as ANYARRAY or RECORD. Currently, the
* typoutput functions associated with the pseudotypes will
* work okay, but we might have to try harder in future.
*/
getTypeOutputInfo(indexRelation->rd_opcintype[i],
&foutoid, &typisvarlena);
val = OidOutputFunctionCall(foutoid, values[i]);
}
......
......@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.100 2009/10/05 19:24:34 tgl Exp $
* $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.101 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -267,7 +267,7 @@ Boot_DeclareIndexStmt:
$8,
NULL,
$10,
NULL, NIL,
NULL, NIL, NIL,
false, false, false, false, false,
false, false, true, false, false);
do_end();
......@@ -285,7 +285,7 @@ Boot_DeclareUniqueIndexStmt:
$9,
NULL,
$11,
NULL, NIL,
NULL, NIL, NIL,
true, false, false, false, false,
false, false, true, false, false);
do_end();
......
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.253 2009/09/27 01:32:11 tgl Exp $
* $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.254 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1100,6 +1100,10 @@ index_register(Oid heap,
newind->il_info->ii_Predicate = (List *)
copyObject(indexInfo->ii_Predicate);
newind->il_info->ii_PredicateState = NIL;
/* no exclusion constraints at bootstrap time, so no need to copy */
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(indexInfo->ii_ExclusionProcs == NULL);
Assert(indexInfo->ii_ExclusionStrats == NULL);
newind->il_next = ILHead;
ILHead = newind;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.360 2009/10/05 19:24:35 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.361 2009/12/07 05:22:21 tgl Exp $
*
*
* INTERFACE ROUTINES
......@@ -678,6 +678,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
values[Anum_pg_class_relhasexclusion - 1] = BoolGetDatum(rd_rel->relhasexclusion);
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
......@@ -1748,9 +1749,10 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
' ',
' ',
' ',
expr, /* Tree form check constraint */
ccbin, /* Binary form check constraint */
ccsrc, /* Source form check constraint */
NULL, /* not an exclusion constraint */
expr, /* Tree form of check constraint */
ccbin, /* Binary form of check constraint */
ccsrc, /* Source form of check constraint */
is_local, /* conislocal */
inhcount); /* coninhcount */
......
This diff is collapsed.
......@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.118 2009/07/29 20:56:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.119 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -30,7 +30,8 @@
* In the current implementation, we share code for opening/closing the
* indexes with execUtils.c. But we do not use ExecInsertIndexTuples,
* because we don't want to create an EState. This implies that we
* do not support partial or expressional indexes on system catalogs.
* do not support partial or expressional indexes on system catalogs,
* nor can we support generalized exclusion constraints.
* This could be fixed with localized changes here if we wanted to pay
* the extra overhead of building an EState.
*/
......@@ -111,10 +112,12 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
/*
* Expressional and partial indexes on system catalogs are not
* supported
* supported, nor exclusion constraints, nor deferred uniqueness
*/
Assert(indexInfo->ii_Expressions == NIL);
Assert(indexInfo->ii_Predicate == NIL);
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(relationDescs[i]->rd_index->indimmediate);
/*
* FormIndexDatum fills in its values and isnull parameters with the
......
......@@ -4,7 +4,7 @@
*
* Copyright (c) 2003-2009, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.59 2009/12/05 21:43:35 petere Exp $
* $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.60 2009/12/07 05:22:21 tgl Exp $
*/
/*
......@@ -1666,6 +1666,7 @@ CREATE VIEW table_constraints AS
WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
AND c.conrelid = r.oid
AND c.contype <> 'x' -- ignore nonstandard exclusion constraints
AND r.relkind = 'r'
AND (NOT pg_is_other_temp_schema(nr.oid))
AND (pg_has_role(r.relowner, 'USAGE')
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.49 2009/10/13 00:53:07 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.50 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -59,6 +59,7 @@ CreateConstraintEntry(const char *constraintName,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
const Oid *exclOp,
Node *conExpr,
const char *conBin,
const char *conSrc,
......@@ -75,6 +76,7 @@ CreateConstraintEntry(const char *constraintName,
ArrayType *conpfeqopArray;
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
ArrayType *conexclopArray;
NameData cname;
int i;
ObjectAddress conobject;
......@@ -130,6 +132,19 @@ CreateConstraintEntry(const char *constraintName,
conffeqopArray = NULL;
}
if (exclOp != NULL)
{
Datum *opdatums;
opdatums = (Datum *) palloc(constraintNKeys * sizeof(Datum));
for (i = 0; i < constraintNKeys; i++)
opdatums[i] = ObjectIdGetDatum(exclOp[i]);
conexclopArray = construct_array(opdatums, constraintNKeys,
OIDOID, sizeof(Oid), true, 'i');
}
else
conexclopArray = NULL;
/* initialize nulls and values */
for (i = 0; i < Natts_pg_constraint; i++)
{
......@@ -177,6 +192,11 @@ CreateConstraintEntry(const char *constraintName,
else
nulls[Anum_pg_constraint_conffeqop - 1] = true;
if (conexclopArray)
values[Anum_pg_constraint_conexclop - 1] = PointerGetDatum(conexclopArray);
else
nulls[Anum_pg_constraint_conexclop - 1] = true;
/*
* initialize the binary form of the check constraint.
*/
......@@ -321,6 +341,14 @@ CreateConstraintEntry(const char *constraintName,
}
}
/*
* We don't bother to register dependencies on the exclusion operators
* of an exclusion constraint. We assume they are members of the opclass
* supporting the index, so there's an indirect dependency via that.
* (This would be pretty dicey for cross-type operators, but exclusion
* operators can never be cross-type.)
*/
if (conExpr != NULL)
{
/*
......
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.20 2009/10/05 19:24:36 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.21 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -239,6 +239,9 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_Predicate = NIL;
indexInfo->ii_PredicateState = NIL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
indexInfo->ii_Unique = true;
indexInfo->ii_ReadyForInserts = true;
indexInfo->ii_Concurrent = false;
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/constraint.c,v 1.1 2009/07/29 20:56:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/constraint.c,v 1.2 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -23,9 +23,12 @@
/*
* unique_key_recheck - trigger function to do a deferred uniqueness check.
*
* This now also does deferred exclusion-constraint checks, so the name is
* somewhat historical.
*
* This is invoked as an AFTER ROW trigger for both INSERT and UPDATE,
* for any rows recorded as potentially violating a deferrable unique
* constraint.
* or exclusion constraint.
*
* This may be an end-of-statement check, a commit-time check, or a
* check triggered by a SET CONSTRAINTS command.
......@@ -85,7 +88,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
* because this trigger gets queued only in response to index insertions;
* which means it does not get queued for HOT updates. The row we are
* called for might now be dead, but have a live HOT child, in which case
* we still need to make the uniqueness check. Therefore we have to use
* we still need to make the check. Therefore we have to use
* heap_hot_search, not just HeapTupleSatisfiesVisibility as is done in
* the comparable test in RI_FKey_check.
*
......@@ -123,9 +126,11 @@ unique_key_recheck(PG_FUNCTION_ARGS)
/*
* Typically the index won't have expressions, but if it does we need
* an EState to evaluate them.
* an EState to evaluate them. We need it for exclusion constraints
* too, even if they are just on simple columns.
*/
if (indexInfo->ii_Expressions != NIL)
if (indexInfo->ii_Expressions != NIL ||
indexInfo->ii_ExclusionOps != NULL)
{
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
......@@ -141,19 +146,37 @@ unique_key_recheck(PG_FUNCTION_ARGS)
* Note: if the index uses functions that are not as immutable as they
* are supposed to be, this could produce an index tuple different from
* the original. The index AM can catch such errors by verifying that
* it finds a matching index entry with the tuple's TID.
* it finds a matching index entry with the tuple's TID. For exclusion
* constraints we check this in check_exclusion_constraint().
*/
FormIndexDatum(indexInfo, slot, estate, values, isnull);
/*
* Now do the uniqueness check. This is not a real insert; it is a
* check that the index entry that has already been inserted is unique.
* Now do the appropriate check.
*/
index_insert(indexRel, values, isnull, &(new_row->t_self),
trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
if (indexInfo->ii_ExclusionOps == NULL)
{
/*
* Note: this is not a real insert; it is a check that the index entry
* that has already been inserted is unique.
*/
index_insert(indexRel, values, isnull, &(new_row->t_self),
trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
}
else
{
/*
* For exclusion constraints we just do the normal check, but now
* it's okay to throw error.
*/
check_exclusion_constraint(trigdata->tg_relation, indexRel, indexInfo,
&(new_row->t_self), values, isnull,
estate, false, false);
}
/*
* If that worked, then this index entry is unique, and we are done.
* If that worked, then this index entry is unique or non-excluded,
* and we are done.
*/
if (estate != NULL)
FreeExecutorState(estate);
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.187 2009/07/29 20:56:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.188 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -25,6 +25,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_tablespace.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
......@@ -36,6 +37,7 @@
#include "optimizer/clauses.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parsetree.h"
#include "storage/lmgr.h"
#include "storage/proc.h"
......@@ -58,6 +60,7 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
Oid *classOidP,
int16 *colOptionP,
List *attList,
List *exclusionOpNames,
Oid relId,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
......@@ -83,6 +86,8 @@ static bool relationHasPrimaryKey(Relation rel);
* to index on.
* 'predicate': the partial-index condition, or NULL if none.
* 'options': reloptions from WITH (in list-of-DefElem form).
* 'exclusionOpNames': list of names of exclusion-constraint operators,
* or NIL if not an exclusion constraint.
* 'unique': make the index enforce uniqueness.
* 'primary': mark the index as a primary key in the catalogs.
* 'isconstraint': index is for a PRIMARY KEY or UNIQUE constraint,
......@@ -106,6 +111,7 @@ DefineIndex(RangeVar *heapRelation,
List *attributeList,
Expr *predicate,
List *options,
List *exclusionOpNames,
bool unique,
bool primary,
bool isconstraint,
......@@ -247,10 +253,21 @@ DefineIndex(RangeVar *heapRelation,
if (indexRelationName == NULL)
{
if (primary)
{
indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
NULL,
"pkey",
namespaceId);
}
else if (exclusionOpNames != NIL)
{
IndexElem *iparam = (IndexElem *) linitial(attributeList);
indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
iparam->name,
"exclusion",
namespaceId);
}
else
{
IndexElem *iparam = (IndexElem *) linitial(attributeList);
......@@ -303,6 +320,11 @@ DefineIndex(RangeVar *heapRelation,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support multicolumn indexes",
accessMethodName)));
if (exclusionOpNames != NIL && !OidIsValid(accessMethodForm->amgettuple))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("access method \"%s\" does not support exclusion constraints",
accessMethodName)));
amcanorder = accessMethodForm->amcanorder;
amoptions = accessMethodForm->amoptions;
......@@ -418,6 +440,9 @@ DefineIndex(RangeVar *heapRelation,
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_Predicate = make_ands_implicit(predicate);
indexInfo->ii_PredicateState = NIL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
indexInfo->ii_Unique = unique;
/* In a concurrent build, mark it not-ready-for-inserts */
indexInfo->ii_ReadyForInserts = !concurrent;
......@@ -427,7 +452,8 @@ DefineIndex(RangeVar *heapRelation,
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
ComputeIndexAttrs(indexInfo, classObjectId, coloptions, attributeList,
relationId, accessMethodName, accessMethodId,
exclusionOpNames, relationId,
accessMethodName, accessMethodId,
amcanorder, isconstraint);
/*
......@@ -435,11 +461,27 @@ DefineIndex(RangeVar *heapRelation,
* error checks)
*/
if (isconstraint && !quiet)
{
const char *constraint_type;
if (primary)
constraint_type = "PRIMARY KEY";
else if (unique)
constraint_type = "UNIQUE";
else if (exclusionOpNames != NIL)
constraint_type = "EXCLUDE";
else
{
elog(ERROR, "unknown constraint type");
constraint_type = NULL; /* keep compiler quiet */
}
ereport(NOTICE,
(errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
primary ? "PRIMARY KEY" : "UNIQUE",
constraint_type,
indexRelationName, RelationGetRelationName(rel))));
}
/* save lockrelid and locktag for below, then close rel */
heaprelid = rel->rd_lockInfo.lockRelId;
......@@ -799,21 +841,38 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
Oid *classOidP,
int16 *colOptionP,
List *attList, /* list of IndexElem's */
List *exclusionOpNames,
Oid relId,
char *accessMethodName,
Oid accessMethodId,
bool amcanorder,
bool isconstraint)
{
ListCell *rest;
int attn = 0;
ListCell *nextExclOp;
ListCell *lc;
int attn;
/* Allocate space for exclusion operator info, if needed */
if (exclusionOpNames)
{
int ncols = list_length(attList);
Assert(list_length(exclusionOpNames) == ncols);
indexInfo->ii_ExclusionOps = (Oid *) palloc(sizeof(Oid) * ncols);
indexInfo->ii_ExclusionProcs = (Oid *) palloc(sizeof(Oid) * ncols);
indexInfo->ii_ExclusionStrats = (uint16 *) palloc(sizeof(uint16) * ncols);
nextExclOp = list_head(exclusionOpNames);
}
else
nextExclOp = NULL;
/*
* process attributeList
*/
foreach(rest, attList)
attn = 0;
foreach(lc, attList)
{
IndexElem *attribute = (IndexElem *) lfirst(rest);
IndexElem *attribute = (IndexElem *) lfirst(lc);
Oid atttype;
/*
......@@ -897,6 +956,71 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
accessMethodName,
accessMethodId);
/*
* Identify the exclusion operator, if any.
*/
if (nextExclOp)
{
List *opname = (List *) lfirst(nextExclOp);
Oid opid;
Oid opfamily;
int strat;
/*
* Find the operator --- it must accept the column datatype
* without runtime coercion (but binary compatibility is OK)
*/
opid = compatible_oper_opid(opname, atttype, atttype, false);
/*
* Only allow commutative operators to be used in exclusion
* constraints. If X conflicts with Y, but Y does not conflict
* with X, bad things will happen.
*/
if (get_commutator(opid) != opid)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("operator %s is not commutative",
format_operator(opid)),
errdetail("Only commutative operators can be used in exclusion constraints.")));
/*
* Operator must be a member of the right opfamily, too
*/
opfamily = get_opclass_family(classOidP[attn]);
strat = get_op_opfamily_strategy(opid, opfamily);
if (strat == 0)
{
HeapTuple opftuple;
Form_pg_opfamily opfform;
/*
* attribute->opclass might not explicitly name the opfamily,
* so fetch the name of the selected opfamily for use in the
* error message.
*/
opftuple = SearchSysCache(OPFAMILYOID,
ObjectIdGetDatum(opfamily),
0, 0, 0);
if (!HeapTupleIsValid(opftuple))
elog(ERROR, "cache lookup failed for opfamily %u",
opfamily);
opfform = (Form_pg_opfamily) GETSTRUCT(opftuple);
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("operator %s is not a member of operator family \"%s\"",
format_operator(opid),
NameStr(opfform->opfname)),
errdetail("The exclusion operator must be related to the index operator class for the constraint.")));
}
indexInfo->ii_ExclusionOps[attn] = opid;
indexInfo->ii_ExclusionProcs[attn] = get_opcode(opid);
indexInfo->ii_ExclusionStrats[attn] = strat;
nextExclOp = lnext(nextExclOp);
}
/*
* Set up the per-column options (indoption field). For now, this is
* zero for any un-ordered index, while ordered indexes have DESC and
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.306 2009/11/20 20:38:10 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.307 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -4603,6 +4603,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->options,
stmt->excludeOpNames,
stmt->unique,
stmt->primary,
stmt->isconstraint,
......@@ -5035,6 +5036,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkconstraint->fk_matchtype,
NULL, /* no exclusion constraint */
NULL, /* no check constraint */
NULL,
NULL,
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.138 2009/10/08 02:39:19 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.139 2009/12/07 05:22:21 tgl Exp $
*
* DESCRIPTION
* The "DefineFoo" routines take the parse tree and pick out the
......@@ -984,6 +984,12 @@ DefineDomain(CreateDomainStmt *stmt)
errmsg("primary key constraints not possible for domains")));
break;
case CONSTR_EXCLUSION:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("exclusion constraints not possible for domains")));
break;
case CONSTR_FOREIGN:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
......@@ -1868,6 +1874,12 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
errmsg("primary key constraints not possible for domains")));
break;
case CONSTR_EXCLUSION:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("exclusion constraints not possible for domains")));
break;
case CONSTR_FOREIGN:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
......@@ -2297,9 +2309,10 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
' ',
' ',
' ',
expr, /* Tree form check constraint */
ccbin, /* Binary form check constraint */
ccsrc, /* Source form check constraint */
NULL, /* not an exclusion constraint */
expr, /* Tree form of check constraint */
ccbin, /* Binary form of check constraint */
ccsrc, /* Source form of check constraint */
true, /* is local */
0); /* inhcount */
......
......@@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.396 2009/11/16 21:32:06 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.397 2009/12/07 05:22:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -763,7 +763,8 @@ vac_update_relstats(Relation relation,
/*
* If we have discovered that there are no indexes, then there's no
* primary key either. This could be done more thoroughly...
* primary key either, nor any exclusion constraints. This could be done
* more thoroughly...
*/
if (!hasindex)
{
......@@ -772,6 +773,11 @@ vac_update_relstats(Relation relation,
pgcform->relhaspkey = false;
dirty = true;
}
if (pgcform->relhasexclusion && pgcform->relkind != RELKIND_INDEX)
{
pgcform->relhasexclusion = false;
dirty = true;
}
}
/* We also clear relhasrules and relhastriggers if needed */
......
This diff is collapsed.
......@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.452 2009/11/20 20:38:10 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.453 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -2157,8 +2157,11 @@ _copyConstraint(Constraint *from)
COPY_NODE_FIELD(raw_expr);
COPY_STRING_FIELD(cooked_expr);
COPY_NODE_FIELD(keys);
COPY_NODE_FIELD(exclusions);
COPY_NODE_FIELD(options);
COPY_STRING_FIELD(indexspace);
COPY_STRING_FIELD(access_method);
COPY_NODE_FIELD(where_clause);
COPY_NODE_FIELD(pktable);
COPY_NODE_FIELD(fk_attrs);
COPY_NODE_FIELD(pk_attrs);
......@@ -2595,6 +2598,7 @@ _copyIndexStmt(IndexStmt *from)
COPY_NODE_FIELD(indexParams);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(whereClause);
COPY_NODE_FIELD(excludeOpNames);
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
......
......@@ -22,7 +22,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.374 2009/11/20 20:38:10 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.375 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1179,6 +1179,7 @@ _equalIndexStmt(IndexStmt *a, IndexStmt *b)
COMPARE_NODE_FIELD(indexParams);
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(whereClause);
COMPARE_NODE_FIELD(excludeOpNames);
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
......@@ -2103,8 +2104,11 @@ _equalConstraint(Constraint *a, Constraint *b)
COMPARE_NODE_FIELD(raw_expr);
COMPARE_STRING_FIELD(cooked_expr);
COMPARE_NODE_FIELD(keys);
COMPARE_NODE_FIELD(exclusions);
COMPARE_NODE_FIELD(options);
COMPARE_STRING_FIELD(indexspace);
COMPARE_STRING_FIELD(access_method);
COMPARE_NODE_FIELD(where_clause);
COMPARE_NODE_FIELD(pktable);
COMPARE_NODE_FIELD(fk_attrs);
COMPARE_NODE_FIELD(pk_attrs);
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.373 2009/11/28 00:46:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.374 2009/12/07 05:22:22 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
......@@ -1798,6 +1798,7 @@ _outIndexStmt(StringInfo str, IndexStmt *node)
WRITE_NODE_FIELD(indexParams);
WRITE_NODE_FIELD(options);
WRITE_NODE_FIELD(whereClause);
WRITE_NODE_FIELD(excludeOpNames);
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
......@@ -2378,6 +2379,7 @@ _outConstraint(StringInfo str, Constraint *node)
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
/* access_method and where_clause not currently used */
break;
case CONSTR_UNIQUE:
......@@ -2385,6 +2387,16 @@ _outConstraint(StringInfo str, Constraint *node)
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
/* access_method and where_clause not currently used */
break;
case CONSTR_EXCLUSION:
appendStringInfo(str, "EXCLUSION");
WRITE_NODE_FIELD(exclusions);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
WRITE_STRING_FIELD(access_method);
WRITE_NODE_FIELD(where_clause);
break;
case CONSTR_FOREIGN:
......
......@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.694 2009/11/20 20:38:10 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.695 2009/12/07 05:22:22 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
......@@ -352,6 +352,8 @@ static TypeName *TableFuncTypeName(List *columns);
%type <node> def_arg columnElem where_clause where_or_current_clause
a_expr b_expr c_expr func_expr AexprConst indirection_el
columnref in_expr having_clause func_table array_expr
ExclusionWhereClause
%type <list> ExclusionConstraintList ExclusionConstraintElem
%type <list> func_arg_list
%type <node> func_arg_expr
%type <list> row type_list array_expr_list
......@@ -475,7 +477,7 @@ static TypeName *TableFuncTypeName(List *columns);
DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION FUNCTIONS
......@@ -1591,8 +1593,16 @@ alter_table_cmds:
;
alter_table_cmd:
/* ALTER TABLE <name> ADD [COLUMN] <coldef> */
ADD_P opt_column columnDef
/* ALTER TABLE <name> ADD <coldef> */
ADD_P columnDef
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_AddColumn;
n->def = $2;
$$ = (Node *)n;
}
/* ALTER TABLE <name> ADD COLUMN <coldef> */
| ADD_P COLUMN columnDef
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_AddColumn;
......@@ -2487,6 +2497,22 @@ ConstraintElem:
n->initdeferred = ($8 & 2) != 0;
$$ = (Node *)n;
}
| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
opt_definition OptConsTableSpace ExclusionWhereClause
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_EXCLUSION;
n->location = @1;
n->access_method = $2;
n->exclusions = $4;
n->options = $6;
n->indexspace = $7;
n->where_clause = $8;
n->deferrable = ($9 & 1) != 0;
n->initdeferred = ($9 & 2) != 0;
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
opt_column_list key_match key_actions ConstraintAttributeSpec
{
......@@ -2544,6 +2570,28 @@ key_match: MATCH FULL
}
;
ExclusionConstraintList:
ExclusionConstraintElem { $$ = list_make1($1); }
| ExclusionConstraintList ',' ExclusionConstraintElem
{ $$ = lappend($1, $3); }
;
ExclusionConstraintElem: index_elem WITH any_operator
{
$$ = list_make2($1, $3);
}
/* allow OPERATOR() decoration for the benefit of ruleutils.c */
| index_elem WITH OPERATOR '(' any_operator ')'
{
$$ = list_make2($1, $5);
}
;
ExclusionWhereClause:
WHERE '(' a_expr ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NULL; }
;
/*
* We combine the update and delete actions into one value temporarily
* for simplicity of parsing, and then break them down again in the
......@@ -10619,6 +10667,7 @@ unreserved_keyword:
| ENCRYPTED
| ENUM_P
| ESCAPE
| EXCLUDE
| EXCLUDING
| EXCLUSIVE
| EXECUTE
......
......@@ -19,7 +19,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.30 2009/11/13 23:49:23 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.31 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -35,6 +35,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "commands/comment.h"
#include "commands/defrem.h"
......@@ -456,6 +457,10 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
saw_default = true;
break;
case CONSTR_CHECK:
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
if (constraint->keys == NIL)
......@@ -463,8 +468,9 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
case CONSTR_CHECK:
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
case CONSTR_EXCLUSION:
/* grammar does not allow EXCLUDE as a column constraint */
elog(ERROR, "column exclusion constraints are not supported");
break;
case CONSTR_FOREIGN:
......@@ -503,6 +509,7 @@ transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
{
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
case CONSTR_EXCLUSION:
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
......@@ -814,7 +821,7 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
/*
* chooseIndexName
*
* Set name to unnamed index. See also the same logic in DefineIndex.
* Set name for unnamed index. See also the same logic in DefineIndex.
*/
static char *
chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt)
......@@ -828,6 +835,13 @@ chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt)
return ChooseRelationName(relation->relname, NULL,
"pkey", namespaceId);
}
else if (index_stmt->excludeOpNames != NIL)
{
IndexElem *iparam = (IndexElem *) linitial(index_stmt->indexParams);
return ChooseRelationName(relation->relname, iparam->name,
"exclusion", namespaceId);
}
else
{
IndexElem *iparam = (IndexElem *) linitial(index_stmt->indexParams);
......@@ -880,7 +894,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
/* Fetch pg_am tuple for source index from relcache entry */
amrec = source_idx->rd_am;
/* Must get indclass the hard way, since it's not stored in relcache */
/* Extract indclass from the pg_index tuple */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
Anum_pg_index_indclass, &isnull);
Assert(!isnull);
......@@ -905,12 +919,12 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
index->idxname = NULL;
/*
* If the index is marked PRIMARY, it's certainly from a constraint; else,
* if it's not marked UNIQUE, it certainly isn't. If it is or might be
* from a constraint, we have to fetch the constraint to check for
* deferrability attributes.
* If the index is marked PRIMARY or has an exclusion condition, it's
* certainly from a constraint; else, if it's not marked UNIQUE, it
* certainly isn't. If it is or might be from a constraint, we have to
* fetch the pg_constraint record.
*/
if (index->primary || index->unique)
if (index->primary || index->unique || idxrelrec->relhasexclusion)
{
Oid constraintId = get_index_constraint(source_relid);
......@@ -931,6 +945,53 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
index->deferrable = conrec->condeferrable;
index->initdeferred = conrec->condeferred;
/* If it's an exclusion constraint, we need the operator names */
if (idxrelrec->relhasexclusion)
{
Datum *elems;
int nElems;
int i;
Assert(conrec->contype == CONSTRAINT_EXCLUSION);
/* Extract operator OIDs from the pg_constraint tuple */
datum = SysCacheGetAttr(CONSTROID, ht_constr,
Anum_pg_constraint_conexclop,
&isnull);
if (isnull)
elog(ERROR, "null conexclop for constraint %u",
constraintId);
deconstruct_array(DatumGetArrayTypeP(datum),
OIDOID, sizeof(Oid), true, 'i',
&elems, NULL, &nElems);
for (i = 0; i < nElems; i++)
{
Oid operid = DatumGetObjectId(elems[i]);
HeapTuple opertup;
Form_pg_operator operform;
char *oprname;
char *nspname;
List *namelist;
opertup = SearchSysCache(OPEROID,
ObjectIdGetDatum(operid),
0, 0, 0);
if (!HeapTupleIsValid(opertup))
elog(ERROR, "cache lookup failed for operator %u",
operid);
operform = (Form_pg_operator) GETSTRUCT(opertup);
oprname = pstrdup(NameStr(operform->oprname));
/* For simplicity we always schema-qualify the op name */
nspname = get_namespace_name(operform->oprnamespace);
namelist = list_make2(makeString(nspname),
makeString(oprname));
index->excludeOpNames = lappend(index->excludeOpNames,
namelist);
ReleaseSysCache(opertup);
}
}
ReleaseSysCache(ht_constr);
}
else
......@@ -1087,7 +1148,7 @@ get_opclass(Oid opclass, Oid actual_datatype)
/*
* transformIndexConstraints
* Handle UNIQUE and PRIMARY KEY constraints, which create indexes.
* Handle UNIQUE, PRIMARY KEY, EXCLUDE constraints, which create indexes.
* We also merge in any index definitions arising from
* LIKE ... INCLUDING INDEXES.
*/
......@@ -1100,8 +1161,9 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
/*
* Run through the constraints that need to generate an index. For PRIMARY
* KEY, mark each column as NOT NULL and create an index. For UNIQUE,
* create an index as for PRIMARY KEY, but do not insist on NOT NULL.
* KEY, mark each column as NOT NULL and create an index. For UNIQUE or
* EXCLUDE, create an index as for PRIMARY KEY, but do not insist on NOT
* NULL.
*/
foreach(lc, cxt->ixconstraints)
{
......@@ -1109,7 +1171,8 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
Assert(IsA(constraint, Constraint));
Assert(constraint->contype == CONSTR_PRIMARY ||
constraint->contype == CONSTR_UNIQUE);
constraint->contype == CONSTR_UNIQUE ||
constraint->contype == CONSTR_EXCLUSION);
index = transformIndexConstraint(constraint, cxt);
......@@ -1167,6 +1230,7 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
if (equal(index->indexParams, priorindex->indexParams) &&
equal(index->whereClause, priorindex->whereClause) &&
equal(index->excludeOpNames, priorindex->excludeOpNames) &&
strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
index->deferrable == priorindex->deferrable &&
index->initdeferred == priorindex->initdeferred)
......@@ -1193,19 +1257,18 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
/*
* transformIndexConstraint
* Transform one UNIQUE or PRIMARY KEY constraint for
* Transform one UNIQUE, PRIMARY KEY, or EXCLUDE constraint for
* transformIndexConstraints.
*/
static IndexStmt *
transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
{
IndexStmt *index;
ListCell *keys;
IndexElem *iparam;
ListCell *lc;
index = makeNode(IndexStmt);
index->unique = true;
index->unique = (constraint->contype != CONSTR_EXCLUSION);
index->primary = (constraint->contype == CONSTR_PRIMARY);
if (index->primary)
{
......@@ -1231,25 +1294,55 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->idxname = NULL; /* DefineIndex will choose name */
index->relation = cxt->relation;
index->accessMethod = DEFAULT_INDEX_TYPE;
index->accessMethod = constraint->access_method ? constraint->access_method : DEFAULT_INDEX_TYPE;
index->options = constraint->options;
index->tableSpace = constraint->indexspace;
index->whereClause = constraint->where_clause;
index->indexParams = NIL;
index->whereClause = NULL;
index->excludeOpNames = NIL;
index->concurrent = false;
/*
* If it's an EXCLUDE constraint, the grammar returns a list of pairs
* of IndexElems and operator names. We have to break that apart into
* separate lists.
*/
if (constraint->contype == CONSTR_EXCLUSION)
{
foreach(lc, constraint->exclusions)
{
List *pair = (List *) lfirst(lc);
IndexElem *elem;
List *opname;
Assert(list_length(pair) == 2);
elem = (IndexElem *) linitial(pair);
Assert(IsA(elem, IndexElem));
opname = (List *) lsecond(pair);
Assert(IsA(opname, List));
index->indexParams = lappend(index->indexParams, elem);
index->excludeOpNames = lappend(index->excludeOpNames, opname);
}
return index;
}
/*
* For UNIQUE and PRIMARY KEY, we just have a list of column names.
*
* Make sure referenced keys exist. If we are making a PRIMARY KEY index,
* also make sure they are NOT NULL, if possible. (Although we could leave
* it to DefineIndex to mark the columns NOT NULL, it's more efficient to
* get it right the first time.)
*/
foreach(keys, constraint->keys)
foreach(lc, constraint->keys)
{
char *key = strVal(lfirst(keys));
char *key = strVal(lfirst(lc));
bool found = false;
ColumnDef *column = NULL;
ListCell *columns;
IndexElem *iparam;
foreach(columns, cxt->columns)
{
......@@ -2000,6 +2093,7 @@ transformConstraintAttrs(ParseState *pstate, List *constraintList)
((node) != NULL && \
((node)->contype == CONSTR_PRIMARY || \
(node)->contype == CONSTR_UNIQUE || \
(node)->contype == CONSTR_EXCLUSION || \
(node)->contype == CONSTR_FOREIGN))
foreach(clist, constraintList)
......
......@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.320 2009/12/01 02:31:12 momjian Exp $
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.321 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -797,6 +797,7 @@ ProcessUtility(Node *parsetree,
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->options,
stmt->excludeOpNames,
stmt->unique,
stmt->primary,
stmt->isconstraint,
......
......@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.315 2009/11/20 20:38:11 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.316 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -144,6 +144,7 @@ static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
......@@ -705,6 +706,7 @@ pg_get_indexdef(PG_FUNCTION_ARGS)
Oid indexrelid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, 0,
NULL,
false, false, 0)));
}
......@@ -718,6 +720,7 @@ pg_get_indexdef_ext(PG_FUNCTION_ARGS)
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, colno,
NULL,
colno != 0,
false,
prettyFlags)));
......@@ -727,7 +730,7 @@ pg_get_indexdef_ext(PG_FUNCTION_ARGS)
char *
pg_get_indexdef_string(Oid indexrelid)
{
return pg_get_indexdef_worker(indexrelid, 0, false, true, 0);
return pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, 0);
}
/* Internal version that just reports the column definitions */
......@@ -737,14 +740,23 @@ pg_get_indexdef_columns(Oid indexrelid, bool pretty)
int prettyFlags;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
return pg_get_indexdef_worker(indexrelid, 0, true, false, prettyFlags);
return pg_get_indexdef_worker(indexrelid, 0, NULL, true, false, prettyFlags);
}
/*
* Internal workhorse to decompile an index definition.
*
* This is now used for exclusion constraints as well: if excludeOps is not
* NULL then it points to an array of exclusion operator OIDs.
*/
static char *
pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags)
{
/* might want a separate isConstraint parameter later */
bool isConstraint = (excludeOps != NULL);
HeapTuple ht_idx;
HeapTuple ht_idxrel;
HeapTuple ht_am;
......@@ -842,11 +854,17 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
initStringInfo(&buf);
if (!attrsOnly)
appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
idxrec->indisunique ? "UNIQUE " : "",
quote_identifier(NameStr(idxrelrec->relname)),
generate_relation_name(indrelid, NIL),
quote_identifier(NameStr(amrec->amname)));
{
if (!isConstraint)
appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
idxrec->indisunique ? "UNIQUE " : "",
quote_identifier(NameStr(idxrelrec->relname)),
generate_relation_name(indrelid, NIL),
quote_identifier(NameStr(amrec->amname)));
else /* currently, must be EXCLUDE constraint */
appendStringInfo(&buf, "EXCLUDE USING %s (",
quote_identifier(NameStr(amrec->amname)));
}
/*
* Report the indexed attributes
......@@ -917,6 +935,13 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
appendStringInfo(&buf, " NULLS FIRST");
}
}
/* Add the exclusion operator if relevant */
if (excludeOps != NULL)
appendStringInfo(&buf, " WITH %s",
generate_operator_name(excludeOps[keyno],
keycoltype,
keycoltype));
}
}
......@@ -943,8 +968,12 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
tblspc = get_rel_tablespace(indexrelid);
if (OidIsValid(tblspc))
{
if (isConstraint)
appendStringInfoString(&buf, " USING INDEX");
appendStringInfo(&buf, " TABLESPACE %s",
quote_identifier(get_tablespace_name(tblspc)));
}
}
/*
......@@ -968,7 +997,10 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
/* Deparse */
str = deparse_expression_pretty(node, context, false, false,
prettyFlags, 0);
appendStringInfo(&buf, " WHERE %s", str);
if (isConstraint)
appendStringInfo(&buf, " WHERE (%s)", str);
else
appendStringInfo(&buf, " WHERE %s", str);
}
}
......@@ -1244,6 +1276,43 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
break;
}
case CONSTRAINT_EXCLUSION:
{
Oid indexOid = conForm->conindid;
Datum val;
bool isnull;
Datum *elems;
int nElems;
int i;
Oid *operators;
/* Extract operator OIDs from the pg_constraint tuple */
val = SysCacheGetAttr(CONSTROID, tup,
Anum_pg_constraint_conexclop,
&isnull);
if (isnull)
elog(ERROR, "null conexclop for constraint %u",
constraintId);
deconstruct_array(DatumGetArrayTypeP(val),
OIDOID, sizeof(Oid), true, 'i',
&elems, NULL, &nElems);
operators = (Oid *) palloc(nElems * sizeof(Oid));
for (i = 0; i < nElems; i++)
operators[i] = DatumGetObjectId(elems[i]);
/* pg_get_indexdef_worker does the rest */
/* suppress tablespace because pg_dump wants it that way */
appendStringInfoString(&buf,
pg_get_indexdef_worker(indexOid,
0,
operators,
false,
false,
prettyFlags));
break;
}
default:
elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
break;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.292 2009/09/26 23:08:22 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.293 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -60,9 +60,11 @@
#include "storage/fd.h"
#include "storage/lmgr.h"
#include "storage/smgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
#include "utils/resowner.h"
......@@ -1079,10 +1081,13 @@ RelationInitIndexAccessInfo(Relation relation)
memcpy(relation->rd_indoption, indoption->values, natts * sizeof(int16));
/*
* expressions and predicate cache will be filled later
* expressions, predicate, exclusion caches will be filled later
*/
relation->rd_indexprs = NIL;
relation->rd_indpred = NIL;
relation->rd_exclops = NULL;
relation->rd_exclprocs = NULL;
relation->rd_exclstrats = NULL;
relation->rd_amcache = NULL;
}
......@@ -3453,6 +3458,130 @@ RelationGetIndexAttrBitmap(Relation relation)
return indexattrs;
}
/*
* RelationGetExclusionInfo -- get info about index's exclusion constraint
*
* This should be called only for an index that is known to have an
* associated exclusion constraint. It returns arrays (palloc'd in caller's
* context) of the exclusion operator OIDs, their underlying functions'
* OIDs, and their strategy numbers in the index's opclasses. We cache
* all this information since it requires a fair amount of work to get.
*/
void
RelationGetExclusionInfo(Relation indexRelation,
Oid **operators,
Oid **procs,
uint16 **strategies)
{
int ncols = indexRelation->rd_rel->relnatts;
Oid *ops;
Oid *funcs;
uint16 *strats;
Relation conrel;
SysScanDesc conscan;
ScanKeyData skey[1];
HeapTuple htup;
bool found;
MemoryContext oldcxt;
int i;
/* Allocate result space in caller context */
*operators = ops = (Oid *) palloc(sizeof(Oid) * ncols);
*procs = funcs = (Oid *) palloc(sizeof(Oid) * ncols);
*strategies = strats = (uint16 *) palloc(sizeof(uint16) * ncols);
/* Quick exit if we have the data cached already */
if (indexRelation->rd_exclstrats != NULL)
{
memcpy(ops, indexRelation->rd_exclops, sizeof(Oid) * ncols);
memcpy(funcs, indexRelation->rd_exclprocs, sizeof(Oid) * ncols);
memcpy(strats, indexRelation->rd_exclstrats, sizeof(uint16) * ncols);
return;
}
/*
* Search pg_constraint for the constraint associated with the index.
* To make this not too painfully slow, we use the index on conrelid;
* that will hold the parent relation's OID not the index's own OID.
*/
ScanKeyInit(&skey[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(indexRelation->rd_index->indrelid));
conrel = heap_open(ConstraintRelationId, AccessShareLock);
conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
SnapshotNow, 1, skey);
found = false;
while (HeapTupleIsValid(htup = systable_getnext(conscan)))
{
Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(htup);
Datum val;
bool isnull;
ArrayType *arr;
int nelem;
/* We want the exclusion constraint owning the index */
if (conform->contype != CONSTRAINT_EXCLUSION ||
conform->conindid != RelationGetRelid(indexRelation))
continue;
/* There should be only one */
if (found)
elog(ERROR, "unexpected exclusion constraint record found for rel %s",
RelationGetRelationName(indexRelation));
found = true;
/* Extract the operator OIDS from conexclop */
val = fastgetattr(htup,
Anum_pg_constraint_conexclop,
conrel->rd_att, &isnull);
if (isnull)
elog(ERROR, "null conexclop for rel %s",
RelationGetRelationName(indexRelation));
arr = DatumGetArrayTypeP(val); /* ensure not toasted */
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem != ncols ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "conexclop is not a 1-D Oid array");
memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * ncols);
}
systable_endscan(conscan);
heap_close(conrel, AccessShareLock);
if (!found)
elog(ERROR, "exclusion constraint record missing for rel %s",
RelationGetRelationName(indexRelation));
/* We need the func OIDs and strategy numbers too */
for (i = 0; i < ncols; i++)
{
funcs[i] = get_opcode(ops[i]);
strats[i] = get_op_opfamily_strategy(ops[i],
indexRelation->rd_opfamily[i]);
/* shouldn't fail, since it was checked at index creation */
if (strats[i] == InvalidStrategy)
elog(ERROR, "could not find strategy for operator %u in family %u",
ops[i], indexRelation->rd_opfamily[i]);
}
/* Save a copy of the results in the relcache entry. */
oldcxt = MemoryContextSwitchTo(indexRelation->rd_indexcxt);
indexRelation->rd_exclops = (Oid *) palloc(sizeof(Oid) * ncols);
indexRelation->rd_exclprocs = (Oid *) palloc(sizeof(Oid) * ncols);
indexRelation->rd_exclstrats = (uint16 *) palloc(sizeof(uint16) * ncols);
memcpy(indexRelation->rd_exclops, ops, sizeof(Oid) * ncols);
memcpy(indexRelation->rd_exclprocs, funcs, sizeof(Oid) * ncols);
memcpy(indexRelation->rd_exclstrats, strats, sizeof(uint16) * ncols);
MemoryContextSwitchTo(oldcxt);
}
/*
* load_relcache_init_file, write_relcache_init_file
......@@ -3768,13 +3897,16 @@ load_relcache_init_file(bool shared)
* format is complex and subject to change). They must be rebuilt if
* needed by RelationCacheInitializePhase3. This is not expected to
* be a big performance hit since few system catalogs have such. Ditto
* for index expressions and predicates.
* for index expressions, predicates, and exclusion info.
*/
rel->rd_rules = NULL;
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
rel->rd_exclprocs = NULL;
rel->rd_exclstrats = NULL;
/*
* Reset transient-state fields in the relcache entry
......
......@@ -12,7 +12,7 @@
* by PostgreSQL
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.553 2009/11/20 20:38:11 tgl Exp $
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.554 2009/12/07 05:22:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -3680,6 +3680,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
i_condeferred,
i_contableoid,
i_conoid,
i_condef,
i_tablespace,
i_options;
int ntups;
......@@ -3710,7 +3711,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
* assume an index won't have more than one internal dependency.
*/
resetPQExpBuffer(query);
if (g_fout->remoteVersion >= 80200)
if (g_fout->remoteVersion >= 80500)
{
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
......@@ -3722,6 +3723,35 @@ getIndexes(TableInfo tblinfo[], int numTables)
"c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
"array_to_string(t.reloptions, ', ') AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
"ON (d.classid = t.tableoid "
"AND d.objid = t.oid "
"AND d.deptype = 'i') "
"LEFT JOIN pg_catalog.pg_constraint c "
"ON (d.refclassid = c.tableoid "
"AND d.refobjid = c.oid) "
"WHERE i.indrelid = '%u'::pg_catalog.oid "
"ORDER BY indexname",
tbinfo->dobj.catId.oid);
}
else if (g_fout->remoteVersion >= 80200)
{
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
"t.relname AS indexname, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
"c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
"array_to_string(t.reloptions, ', ') AS options "
"FROM pg_catalog.pg_index i "
......@@ -3749,6 +3779,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
"c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
"null AS options "
"FROM pg_catalog.pg_index i "
......@@ -3776,6 +3807,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
"c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
"NULL AS tablespace, "
"null AS options "
"FROM pg_catalog.pg_index i "
......@@ -3806,6 +3838,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
"false AS condeferred, "
"0::oid AS contableoid, "
"t.oid AS conoid, "
"null AS condef, "
"NULL AS tablespace, "
"null AS options "
"FROM pg_index i, pg_class t "
......@@ -3831,6 +3864,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
"false AS condeferred, "
"0::oid AS contableoid, "
"t.oid AS conoid, "
"null AS condef, "
"NULL AS tablespace, "
"null AS options "
"FROM pg_index i, pg_class t "
......@@ -3858,6 +3892,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
i_condeferred = PQfnumber(res, "condeferred");
i_contableoid = PQfnumber(res, "contableoid");
i_conoid = PQfnumber(res, "conoid");
i_condef = PQfnumber(res, "condef");
i_tablespace = PQfnumber(res, "tablespace");
i_options = PQfnumber(res, "options");
......@@ -3895,7 +3930,7 @@ getIndexes(TableInfo tblinfo[], int numTables)
indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
contype = *(PQgetvalue(res, j, i_contype));
if (contype == 'p' || contype == 'u')
if (contype == 'p' || contype == 'u' || contype == 'x')
{
/*
* If we found a constraint matching the index, create an
......@@ -3913,7 +3948,10 @@ getIndexes(TableInfo tblinfo[], int numTables)
constrinfo[j].contable = tbinfo;
constrinfo[j].condomain = NULL;
constrinfo[j].contype = contype;
constrinfo[j].condef = NULL;
if (contype == 'x')
constrinfo[j].condef = strdup(PQgetvalue(res, j, i_condef));
else
constrinfo[j].condef = NULL;
constrinfo[j].confrelid = InvalidOid;
constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
......@@ -10728,7 +10766,9 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
q = createPQExpBuffer();
delq = createPQExpBuffer();
if (coninfo->contype == 'p' || coninfo->contype == 'u')
if (coninfo->contype == 'p' ||
coninfo->contype == 'u' ||
coninfo->contype == 'x')
{
/* Index-related constraint */
IndxInfo *indxinfo;
......@@ -10745,37 +10785,46 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
fmtId(tbinfo->dobj.name));
appendPQExpBuffer(q, " ADD CONSTRAINT %s %s (",
fmtId(coninfo->dobj.name),
coninfo->contype == 'p' ? "PRIMARY KEY" : "UNIQUE");
appendPQExpBuffer(q, " ADD CONSTRAINT %s ",
fmtId(coninfo->dobj.name));
for (k = 0; k < indxinfo->indnkeys; k++)
if (coninfo->condef)
{
/* pg_get_constraintdef should have provided everything */
appendPQExpBuffer(q, "%s;\n", coninfo->condef);
}
else
{
int indkey = (int) indxinfo->indkeys[k];
const char *attname;
appendPQExpBuffer(q, "%s (",
coninfo->contype == 'p' ? "PRIMARY KEY" : "UNIQUE");
for (k = 0; k < indxinfo->indnkeys; k++)
{
int indkey = (int) indxinfo->indkeys[k];
const char *attname;
if (indkey == InvalidAttrNumber)
break;
attname = getAttrName(indkey, tbinfo);
if (indkey == InvalidAttrNumber)
break;
attname = getAttrName(indkey, tbinfo);
appendPQExpBuffer(q, "%s%s",
(k == 0) ? "" : ", ",
fmtId(attname));
}
appendPQExpBuffer(q, "%s%s",
(k == 0) ? "" : ", ",
fmtId(attname));
}
appendPQExpBuffer(q, ")");
appendPQExpBuffer(q, ")");
if (indxinfo->options && strlen(indxinfo->options) > 0)
appendPQExpBuffer(q, " WITH (%s)", indxinfo->options);
if (indxinfo->options && strlen(indxinfo->options) > 0)
appendPQExpBuffer(q, " WITH (%s)", indxinfo->options);
if (coninfo->condeferrable)
{
appendPQExpBuffer(q, " DEFERRABLE");
if (coninfo->condeferred)
appendPQExpBuffer(q, " INITIALLY DEFERRED");
}
if (coninfo->condeferrable)
{
appendPQExpBuffer(q, " DEFERRABLE");
if (coninfo->condeferred)
appendPQExpBuffer(q, " INITIALLY DEFERRED");
}
appendPQExpBuffer(q, ";\n");
appendPQExpBuffer(q, ";\n");
}
/* If the index is clustered, we need to record that. */
if (indxinfo->indisclustered)
......
......@@ -8,7 +8,7 @@
*
* Copyright (c) 2000-2009, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.231 2009/11/11 21:07:41 petere Exp $
* $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.232 2009/12/07 05:22:23 tgl Exp $
*/
#include "postgres_fe.h"
......@@ -1105,6 +1105,7 @@ describeOneTableDetails(const char *schemaname,
bool hasrules;
bool hastriggers;
bool hasoids;
bool hasexclusion;
Oid tablespace;
char *reloptions;
} tableinfo;
......@@ -1121,7 +1122,22 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
if (pset.sversion >= 80400)
if (pset.sversion >= 80500)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
"c.relhastriggers, c.relhasoids, "
"%s, c.reltablespace, c.relhasexclusion\n"
"FROM pg_catalog.pg_class c\n "
"LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
"WHERE c.oid = '%s'\n",
(verbose ?
"pg_catalog.array_to_string(c.reloptions || "
"array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
: "''"),
oid);
}
else if (pset.sversion >= 80400)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
......@@ -1185,10 +1201,12 @@ describeOneTableDetails(const char *schemaname,
tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0;
tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
tableinfo.reloptions = pset.sversion >= 80200 ?
tableinfo.reloptions = (pset.sversion >= 80200) ?
strdup(PQgetvalue(res, 0, 6)) : 0;
tableinfo.tablespace = (pset.sversion >= 80000) ?
atooid(PQgetvalue(res, 0, 7)) : 0;
tableinfo.hasexclusion = (pset.sversion >= 80500) ?
strcmp(PQgetvalue(res, 0, 8), "t") == 0 : false;
PQclear(res);
res = NULL;
......@@ -1642,6 +1660,38 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
/* print exclusion constraints */
if (tableinfo.hasexclusion)
{
printfPQExpBuffer(&buf,
"SELECT r.conname, "
"pg_catalog.pg_get_constraintdef(r.oid, true)\n"
"FROM pg_catalog.pg_constraint r\n"
"WHERE r.conrelid = '%s' AND r.contype = 'x'\n"
"ORDER BY 1",
oid);
result = PSQLexec(buf.data, false);
if (!result)
goto error_return;
else
tuples = PQntuples(result);
if (tuples > 0)
{
printTableAddFooter(&cont, _("Exclusion constraints:"));
for (i = 0; i < tuples; i++)
{
/* untranslated contraint name and def */
printfPQExpBuffer(&buf, " \"%s\" %s",
PQgetvalue(result, i, 0),
PQgetvalue(result, i, 1));
printTableAddFooter(&cont, buf.data);
}
}
PQclear(result);
}
/* print foreign-key constraints (there are none if no triggers) */
if (tableinfo.hastriggers)
{
......
......@@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.555 2009/12/05 21:43:35 petere Exp $
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.556 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200912051
#define CATALOG_VERSION_NO 200912071
#endif
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.154 2009/10/07 22:14:25 alvherre Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.155 2009/12/07 05:22:23 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -426,12 +426,13 @@ DATA(insert ( 1249 tableoid 26 0 0 4 -7 0 -1 -1 t p i t f f t 0 _null_));
{ 1259, {"relchecks"}, 21, -1, 0, 2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhasoids"}, 16, -1, 0, 1, 18, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhaspkey"}, 16, -1, 0, 1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhasrules"}, 16, -1, 0, 1, 20, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhastriggers"},16, -1, 0, 1, 21, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhassubclass"},16, -1, 0, 1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relfrozenxid"}, 28, -1, 0, 4, 23, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relacl"}, 1034, -1, 0, -1, 24, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
{ 1259, {"reloptions"}, 1009, -1, 0, -1, 25, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
{ 1259, {"relhasexclusion"},16, -1, 0, 1, 20, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhasrules"}, 16, -1, 0, 1, 21, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhastriggers"},16, -1, 0, 1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relhassubclass"},16, -1, 0, 1, 23, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relfrozenxid"}, 28, -1, 0, 4, 24, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
{ 1259, {"relacl"}, 1034, -1, 0, -1, 25, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
{ 1259, {"reloptions"}, 1009, -1, 0, -1, 26, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
DATA(insert ( 1259 relname 19 -1 0 NAMEDATALEN 1 0 -1 -1 f p c t f f t 0 _null_));
DATA(insert ( 1259 relnamespace 26 -1 0 4 2 0 -1 -1 t p i t f f t 0 _null_));
......@@ -452,12 +453,13 @@ DATA(insert ( 1259 relnatts 21 -1 0 2 16 0 -1 -1 t p s t f f t 0 _null_));
DATA(insert ( 1259 relchecks 21 -1 0 2 17 0 -1 -1 t p s t f f t 0 _null_));
DATA(insert ( 1259 relhasoids 16 -1 0 1 18 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relhaspkey 16 -1 0 1 19 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relhasrules 16 -1 0 1 20 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relhastriggers 16 -1 0 1 21 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relhassubclass 16 -1 0 1 22 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relfrozenxid 28 -1 0 4 23 0 -1 -1 t p i t f f t 0 _null_));
DATA(insert ( 1259 relacl 1034 -1 0 -1 24 1 -1 -1 f x i f f f t 0 _null_));
DATA(insert ( 1259 reloptions 1009 -1 0 -1 25 1 -1 -1 f x i f f f t 0 _null_));
DATA(insert ( 1259 relhasexclusion 16 -1 0 1 20 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relhasrules 16 -1 0 1 21 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relhastriggers 16 -1 0 1 22 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relhassubclass 16 -1 0 1 23 0 -1 -1 t p c t f f t 0 _null_));
DATA(insert ( 1259 relfrozenxid 28 -1 0 4 24 0 -1 -1 t p i t f f t 0 _null_));
DATA(insert ( 1259 relacl 1034 -1 0 -1 25 1 -1 -1 f x i f f f t 0 _null_));
DATA(insert ( 1259 reloptions 1009 -1 0 -1 26 1 -1 -1 f x i f f f t 0 _null_));
DATA(insert ( 1259 ctid 27 0 0 6 -1 0 -1 -1 f p s t f f t 0 _null_));
DATA(insert ( 1259 oid 26 0 0 4 -2 0 -1 -1 t p i t f f t 0 _null_));
DATA(insert ( 1259 xmin 28 0 0 4 -3 0 -1 -1 t p i t f f t 0 _null_));
......
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.116 2009/09/26 22:42:02 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.117 2009/12/07 05:22:23 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -56,6 +56,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
int2 relchecks; /* # of CHECK constraints for class */
bool relhasoids; /* T if we generate OIDs for rows of rel */
bool relhaspkey; /* has (or has had) PRIMARY KEY index */
bool relhasexclusion; /* has (or has had) exclusion constraint */
bool relhasrules; /* has (or has had) any rules */
bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */
......@@ -87,7 +88,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
#define Natts_pg_class 25
#define Natts_pg_class 26
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
......@@ -107,12 +108,13 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relchecks 17
#define Anum_pg_class_relhasoids 18
#define Anum_pg_class_relhaspkey 19
#define Anum_pg_class_relhasrules 20
#define Anum_pg_class_relhastriggers 21
#define Anum_pg_class_relhassubclass 22
#define Anum_pg_class_relfrozenxid 23
#define Anum_pg_class_relacl 24
#define Anum_pg_class_reloptions 25
#define Anum_pg_class_relhasexclusion 20
#define Anum_pg_class_relhasrules 21
#define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23
#define Anum_pg_class_relfrozenxid 24
#define Anum_pg_class_relacl 25
#define Anum_pg_class_reloptions 26
/* ----------------
* initial contents of pg_class
......@@ -124,13 +126,13 @@ typedef FormData_pg_class *Form_pg_class;
*/
/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f f r 28 0 t f f f f 3 _null_ _null_ ));
DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 f f f f f 3 _null_ _null_ ));
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 t f f f f 3 _null_ _null_ ));
DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 25 0 t f f f f 3 _null_ _null_ ));
DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
#define RELKIND_INDEX 'i' /* secondary index */
......
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.33 2009/10/12 19:49:24 adunstan Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.34 2009/12/07 05:22:23 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -119,6 +119,12 @@ CATALOG(pg_constraint,2606)
*/
Oid conffeqop[1];
/*
* If an exclusion constraint, the OIDs of the exclusion operators for
* each column of the constraint
*/
Oid conexclop[1];
/*
* If a check constraint, nodeToString representation of expression
*/
......@@ -141,7 +147,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
#define Natts_pg_constraint 21
#define Natts_pg_constraint 22
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
......@@ -161,8 +167,9 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define Anum_pg_constraint_conpfeqop 17
#define Anum_pg_constraint_conppeqop 18
#define Anum_pg_constraint_conffeqop 19
#define Anum_pg_constraint_conbin 20
#define Anum_pg_constraint_consrc 21
#define Anum_pg_constraint_conexclop 20
#define Anum_pg_constraint_conbin 21
#define Anum_pg_constraint_consrc 22
/* Valid values for contype */
......@@ -170,6 +177,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define CONSTRAINT_FOREIGN 'f'
#define CONSTRAINT_PRIMARY 'p'
#define CONSTRAINT_UNIQUE 'u'
#define CONSTRAINT_EXCLUSION 'x'
/*
* Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
......@@ -209,6 +217,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
const Oid *exclOp,
Node *conExpr,
const char *conBin,
const char *conSrc,
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.97 2009/09/22 23:43:41 tgl Exp $
* $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.98 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -26,6 +26,7 @@ extern void DefineIndex(RangeVar *heapRelation,
List *attributeList,
Expr *predicate,
List *options,
List *exclusionOpNames,
bool unique,
bool primary,
bool isconstraint,
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.163 2009/10/26 02:26:41 tgl Exp $
* $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.164 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -321,6 +321,12 @@ extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum_full);
extern bool check_exclusion_constraint(Relation heap, Relation index,
IndexInfo *indexInfo,
ItemPointer tupleid,
Datum *values, bool *isnull,
EState *estate,
bool newIndex, bool errorOK);
extern void RegisterExprContextCallback(ExprContext *econtext,
ExprContextCallbackFunction function,
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.212 2009/11/20 20:38:11 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.213 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -40,6 +40,9 @@
* ExpressionsState exec state for expressions, or NIL if none
* Predicate partial-index predicate, or NIL if none
* PredicateState exec state for predicate, or NIL if none
* ExclusionOps Per-column exclusion operators, or NULL if none
* ExclusionProcs Underlying function OIDs for ExclusionOps
* ExclusionStrats Opclass strategy numbers for ExclusionOps
* Unique is it a unique index?
* ReadyForInserts is it valid for inserts?
* Concurrent are we doing a concurrent index build?
......@@ -58,6 +61,9 @@ typedef struct IndexInfo
List *ii_ExpressionsState; /* list of ExprState */
List *ii_Predicate; /* list of Expr */
List *ii_PredicateState; /* list of ExprState */
Oid *ii_ExclusionOps; /* array with one entry per column */
Oid *ii_ExclusionProcs; /* array with one entry per column */
uint16 *ii_ExclusionStrats; /* array with one entry per column */
bool ii_Unique;
bool ii_ReadyForInserts;
bool ii_Concurrent;
......
......@@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.416 2009/11/20 20:38:11 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.417 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1395,6 +1395,7 @@ typedef enum ConstrType /* types of constraints */
CONSTR_CHECK,
CONSTR_PRIMARY,
CONSTR_UNIQUE,
CONSTR_EXCLUSION,
CONSTR_FOREIGN,
CONSTR_ATTR_DEFERRABLE, /* attributes for previous constraint node */
CONSTR_ATTR_NOT_DEFERRABLE,
......@@ -1429,10 +1430,18 @@ typedef struct Constraint
Node *raw_expr; /* expr, as untransformed parse tree */
char *cooked_expr; /* expr, as nodeToString representation */
/* Fields used for index constraints (UNIQUE and PRIMARY KEY): */
/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
List *keys; /* String nodes naming referenced column(s) */
/* Fields used for EXCLUSION constraints: */
List *exclusions; /* list of (IndexElem, operator name) pairs */
/* Fields used for index constraints (UNIQUE, PRIMARY KEY, EXCLUSION): */
List *options; /* options from WITH clause */
char *indexspace; /* index tablespace; NULL for default */
/* These could be, but currently are not, used for UNIQUE/PKEY: */
char *access_method; /* index access method; NULL for default */
Node *where_clause; /* partial index predicate */
/* Fields used for FOREIGN KEY constraints: */
RangeVar *pktable; /* Primary key table */
......@@ -1880,6 +1889,7 @@ typedef struct IndexStmt
List *indexParams; /* a list of IndexElem */
List *options; /* options from WITH clause */
Node *whereClause; /* qualification (partial-index predicate) */
List *excludeOpNames; /* exclusion operator names, or NIL if none */
bool unique; /* is index unique? */
bool primary; /* is index on primary key? */
bool isconstraint; /* is it from a CONSTRAINT clause? */
......
......@@ -11,7 +11,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.6 2009/11/05 23:24:27 tgl Exp $
* $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.7 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -143,6 +143,7 @@ PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
PG_KEYWORD("exclude", EXCLUDE, UNRESERVED_KEYWORD)
PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
......
......@@ -11,7 +11,7 @@
*
* Copyright (c) 2003-2009, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.29 2009/03/04 10:55:00 petere Exp $
* $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.30 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -167,6 +167,7 @@
#define ERRCODE_FOREIGN_KEY_VIOLATION MAKE_SQLSTATE('2','3', '5','0','3')
#define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3', '5','0','5')
#define ERRCODE_CHECK_VIOLATION MAKE_SQLSTATE('2','3', '5','1','4')
#define ERRCODE_EXCLUSION_VIOLATION MAKE_SQLSTATE('2','3', 'P','0','1')
/* Class 24 - Invalid Cursor State */
#define ERRCODE_INVALID_CURSOR_STATE MAKE_SQLSTATE('2','4', '0','0','0')
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.116 2009/11/20 20:38:11 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.117 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -196,6 +196,9 @@ typedef struct RelationData
int16 *rd_indoption; /* per-column AM-specific flags */
List *rd_indexprs; /* index expression trees, if any */
List *rd_indpred; /* index predicate tree, if any */
Oid *rd_exclops; /* OIDs of exclusion operators, if any */
Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */
uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */
void *rd_amcache; /* available for use by index AM */
/*
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/relcache.h,v 1.64 2009/08/12 20:53:31 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/relcache.h,v 1.65 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -43,6 +43,10 @@ extern Oid RelationGetOidIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
extern void RelationGetExclusionInfo(Relation indexRelation,
Oid **operators,
Oid **procs,
uint16 **strategies);
extern void RelationSetIndexList(Relation relation,
List *indexIds, Oid oidIndex);
......
......@@ -9,7 +9,7 @@
*
* Copyright (c) 2003-2009, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.18 2009/03/04 10:55:00 petere Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.19 2009/12/07 05:22:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -303,6 +303,10 @@
"check_violation", ERRCODE_CHECK_VIOLATION
},
{
"exclusion_violation", ERRCODE_EXCLUSION_VIOLATION
},
{
"invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE
},
......
......@@ -5,6 +5,7 @@
-- - CHECK clauses
-- - PRIMARY KEY clauses
-- - UNIQUE clauses
-- - EXCLUDE clauses
--
--
......@@ -366,3 +367,61 @@ COMMIT;
SELECT * FROM unique_tbl;
DROP TABLE unique_tbl;
--
-- EXCLUDE constraints
--
CREATE TABLE circles (
c1 CIRCLE,
c2 TEXT,
EXCLUDE USING gist
(c1 WITH &&, (c2::circle) WITH ~=)
WHERE (circle_center(c1) <> '(0,0)')
);
-- these should succeed because they don't match the index predicate
INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
-- succeed
INSERT INTO circles VALUES('<(10,10), 10>', '<(0,0), 5>');
-- fail, overlaps
INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
-- succeed because c1 doesn't overlap
INSERT INTO circles VALUES('<(20,20), 1>', '<(0,0), 5>');
-- succeed because c2 is not the same
INSERT INTO circles VALUES('<(20,20), 10>', '<(1,1), 5>');
-- should fail on existing data without the WHERE clause
ALTER TABLE circles ADD EXCLUDE USING gist
(c1 WITH &&, (c2::circle) WITH ~=);
DROP TABLE circles;
-- Check deferred exclusion constraint
CREATE TABLE deferred_excl (
f1 int,
CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED
);
INSERT INTO deferred_excl VALUES(1);
INSERT INTO deferred_excl VALUES(2);
INSERT INTO deferred_excl VALUES(1); -- fail
BEGIN;
INSERT INTO deferred_excl VALUES(2); -- no fail here
COMMIT; -- should fail here
BEGIN;
INSERT INTO deferred_excl VALUES(3);
INSERT INTO deferred_excl VALUES(3); -- no fail here
COMMIT; -- should fail here
ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con;
-- This should fail, but worth testing because of HOT updates
UPDATE deferred_excl SET f1 = 3;
ALTER TABLE deferred_excl ADD EXCLUDE (f1 WITH =);
DROP TABLE deferred_excl;
......@@ -5,6 +5,7 @@
-- - CHECK clauses
-- - PRIMARY KEY clauses
-- - UNIQUE clauses
-- - EXCLUDE clauses
--
--
-- DEFAULT syntax
......@@ -512,3 +513,64 @@ SELECT * FROM unique_tbl;
(5 rows)
DROP TABLE unique_tbl;
--
-- EXCLUDE constraints
--
CREATE TABLE circles (
c1 CIRCLE,
c2 TEXT,
EXCLUDE USING gist
(c1 WITH &&, (c2::circle) WITH ~=)
WHERE (circle_center(c1) <> '(0,0)')
);
NOTICE: CREATE TABLE / EXCLUDE will create implicit index "circles_c1_exclusion" for table "circles"
-- these should succeed because they don't match the index predicate
INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
INSERT INTO circles VALUES('<(0,0), 5>', '<(0,0), 5>');
-- succeed
INSERT INTO circles VALUES('<(10,10), 10>', '<(0,0), 5>');
-- fail, overlaps
INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
ERROR: conflicting key value violates exclusion constraint "circles_c1_exclusion"
DETAIL: Key (c1, (c2::circle))=(<(20,20),10>, <(0,0),5>) conflicts with existing key (c1, (c2::circle))=(<(10,10),10>, <(0,0),5>).
-- succeed because c1 doesn't overlap
INSERT INTO circles VALUES('<(20,20), 1>', '<(0,0), 5>');
-- succeed because c2 is not the same
INSERT INTO circles VALUES('<(20,20), 10>', '<(1,1), 5>');
-- should fail on existing data without the WHERE clause
ALTER TABLE circles ADD EXCLUDE USING gist
(c1 WITH &&, (c2::circle) WITH ~=);
NOTICE: ALTER TABLE / ADD EXCLUDE will create implicit index "circles_c1_exclusion1" for table "circles"
ERROR: could not create exclusion constraint "circles_c1_exclusion1"
DETAIL: Key (c1, (c2::circle))=(<(0,0),5>, <(0,0),5>) conflicts with key (c1, (c2::circle))=(<(0,0),5>, <(0,0),5>).
DROP TABLE circles;
-- Check deferred exclusion constraint
CREATE TABLE deferred_excl (
f1 int,
CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED
);
NOTICE: CREATE TABLE / EXCLUDE will create implicit index "deferred_excl_con" for table "deferred_excl"
INSERT INTO deferred_excl VALUES(1);
INSERT INTO deferred_excl VALUES(2);
INSERT INTO deferred_excl VALUES(1); -- fail
ERROR: conflicting key value violates exclusion constraint "deferred_excl_con"
DETAIL: Key (f1)=(1) conflicts with existing key (f1)=(1).
BEGIN;
INSERT INTO deferred_excl VALUES(2); -- no fail here
COMMIT; -- should fail here
ERROR: conflicting key value violates exclusion constraint "deferred_excl_con"
DETAIL: Key (f1)=(2) conflicts with existing key (f1)=(2).
BEGIN;
INSERT INTO deferred_excl VALUES(3);
INSERT INTO deferred_excl VALUES(3); -- no fail here
COMMIT; -- should fail here
ERROR: conflicting key value violates exclusion constraint "deferred_excl_con"
DETAIL: Key (f1)=(3) conflicts with existing key (f1)=(3).
ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con;
-- This should fail, but worth testing because of HOT updates
UPDATE deferred_excl SET f1 = 3;
ALTER TABLE deferred_excl ADD EXCLUDE (f1 WITH =);
NOTICE: ALTER TABLE / ADD EXCLUDE will create implicit index "deferred_excl_f1_exclusion" for table "deferred_excl"
ERROR: could not create exclusion constraint "deferred_excl_f1_exclusion"
DETAIL: Key (f1)=(3) conflicts with key (f1)=(3).
DROP TABLE deferred_excl;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment