WIP: generalized index constraints

Started by Jeff Davisover 16 years ago251 messages
#1Jeff Davis
pgsql@j-davis.com
1 attachment(s)

This is a follow up to my old proposal here:

http://archives.postgresql.org/pgsql-hackers/2008-06/msg00404.php

Top pointed out a few problems here:

http://archives.postgresql.org/pgsql-hackers/2008-06/msg00427.php

Here are my updated answers:

1. Not a problem with the new design, which checks the constraints from
ExecInsertIndexTuples().

2. Not a problem for similar reasons.

3. I don't have an answer here yet, but I have a few thoughts. I see it
as a separate proposal. My hand-waving answer is that it should be just
as possible as before to append index constraint failures to a big list,
and loop through it as long as we're making progress. If I need a more
solid proposal for this problem before my generalized constraints
proposal is considered, let me know.

To try out my patch:

(1) Apply patch to 8.5-devel and Init DB

(2) Install contrib/btree_gist (only necessary for this example, patch
works with Btree and GIN, too).

(3)
=> create table test(i int, c circle);
=> create index test_idx on test using gist(i, c);
=> UPDATE pg_index SET indconstrats = '3 3'
WHERE indexrelid='test_idx'::regclass;

In the above query, 3 is the equality strategy number for the GiST
opclass for integers, and 3 is also the "overlaps" strategy number for
the GiST opclass for circles, so we put a 3 for each attribute. What
this will mean is that it will reject any new tuple when there is
already another tuple in the table with an equal value of i AND an
overlapping value of c. Concurrency should behave identically to UNIQUE
on a btree.

(4) Now, try some inserts (concurrent or otherwise) and see what
happens.

Ultimately, I think the language for this might shape up something like:

CREATE INDEX test_idx ON test USING gist
(i CONSTRAINT =, c CONSTRAINT &&);

which would avoid the need for updating the catalog, of course.

Limitations:

* Still not deferrable, even 'til the end of the command.
* Your constraint must be symmetric (if tuple A conflicts with tuple B,
tuple B must conflict with tuple A).
* The types have to match between the left and right arguments in the
operator class and the type of the column in the table. This is normally
true, but the GIN Array opclass works on type "anyarray", but the table
has a normal type, which causes a problem. Maybe it's possible to be
smarter about this, but the workaround is to just create more opclasses
(I believe).

Any input is appreciated (design problems, implementation, language
ideas, or anything else). I'd like to get it into shape for the July 15
commitfest if no major problems are found.

Regards,
Jeff Davis

Attachments:

index-constraints-20090705.patchtext/x-patch; charset=UTF-8; name=index-constraints-20090705.patchDownload
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 1515d9f..eedb456 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -26,6 +26,7 @@
  *		index_vacuum_cleanup	- post-deletion cleanup of an index
  *		index_getprocid - get a support procedure OID
  *		index_getprocinfo - get a support procedure's lookup info
+ *      index_check_constraint - check index constraints
  *
  * NOTES
  *		This file contains the index_ routines which used
@@ -64,9 +65,13 @@
 
 #include "access/relscan.h"
 #include "access/transam.h"
+#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
+#include "storage/lwlock.h"
+#include "storage/procarray.h"
+#include "utils/lsyscache.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
 #include "utils/tqual.h"
@@ -116,6 +121,19 @@ do { \
 static IndexScanDesc index_beginscan_internal(Relation indexRelation,
 						 int nkeys, ScanKey key);
 
+typedef struct
+{
+	Oid					relid;
+	TransactionId		xid;
+	ItemPointerData		tid;
+} CurrentIndexInsertEntry;
+
+static CurrentIndexInsertEntry *CurrentIndexInsertsTable = NULL;
+
+static bool index_check_constraint_conflict(TupleTableSlot *slot,
+											HeapTuple tup, int2 *heap_attnums,
+											int2 index_natts,
+											Oid *constraint_procs);
 
 /* ----------------------------------------------------------------
  *				   index_ interface functions
@@ -846,3 +864,278 @@ index_getprocinfo(Relation irel,
 
 	return locinfo;
 }
+
+void
+index_check_constraint(Relation heap, Relation index,
+						ItemPointer tid, TupleTableSlot *slot)
+{
+		IndexScanDesc	 index_scan;
+		HeapTuple		 tup;
+		ScanKeyData		*scankeys;
+		int2vector		*constr_strats;
+		Oid				*constr_procs;
+		int				 i;
+		int2			*heap_attnums = index->rd_index->indkey.values;
+		int2			 index_natts  = index->rd_index->indnatts;
+		SnapshotData	 DirtySnapshot;
+		int				 nkeys		  = 0;
+
+		CurrentIndexInsertEntry *MyIndexInsertEntry;
+		CurrentIndexInsertEntry	 potential_conflicts[MaxBackends];
+		int						 n_potential_conflicts = 0;
+
+		/* Find constraint strategy numbers */
+		constr_strats = RelationGetIndexConstraintStrategies(index);
+
+		/* return if no constraint */
+		if (constr_strats == NULL)
+			return;
+
+		/*
+		 * if any of the indexed columns are NULL, the constraint
+		 * is satisfied
+		 */
+		for (i = 0; i < index_natts; i++)
+			if (slot_attisnull(slot, heap_attnums[i]))
+				return;
+
+		/*
+		 * Find the function that tests for a conflict based on the
+		 * strategy number, operator family, and types.
+		 */
+		constr_procs = palloc(sizeof(Oid) * index_natts);
+		for (i = 0; i < index_natts; i++)
+		{
+			/*
+			 * Find the procedure implementing the strategy for the
+			 * index for two arguments both with the type of the
+			 * indexed attribute.
+			 */
+			Oid					oper;
+			Oid					opfamily = index->rd_opfamily[i];
+			StrategyNumber		strategy = constr_strats->values[i];
+			Oid	typeOid	= heap->rd_att->attrs[heap_attnums[i] - 1]->atttypid;
+
+			if (strategy == InvalidStrategy)
+				continue;
+
+			oper = get_opfamily_member(opfamily, typeOid, typeOid, strategy);
+
+			if(OidIsValid(oper))
+				constr_procs[i] = get_opcode(oper);
+			else
+				elog(ERROR, "cannot determine operator for type %d and "
+					 "strategy %d", typeOid, strategy);
+		}
+
+
+		if (CurrentIndexInsertsTable == NULL)
+		{
+			bool found;
+
+			CurrentIndexInsertsTable = (CurrentIndexInsertEntry *)
+				ShmemInitStruct("Current Index Inserts Table",
+								CurrentIndexInsertsShmemSize(), &found);
+			Assert(found);
+		}
+
+		MyIndexInsertEntry = &CurrentIndexInsertsTable[MyBackendId - 1];
+
+		/*
+		 * Check for conflicts with concurrent inserts. These are
+		 * inserts that are actually in-progress now, and have not
+		 * actually been put in the index yet.
+		 */
+
+		/* TODO: this lock should be partitioned by heap relid hash. */
+		LWLockAcquire(IndexConstraintLock, LW_EXCLUSIVE);
+
+		for (i = 0; i < MaxBackends; i++)
+		{
+			CurrentIndexInsertEntry entry = CurrentIndexInsertsTable[i];
+
+			if (RelationGetRelid(heap) == entry.relid &&
+				!TransactionIdIsCurrentTransactionId(entry.xid))
+			{
+				if (TransactionIdIsInProgress(entry.xid))
+				{
+					/* build a list of potential conflicts */
+					potential_conflicts[n_potential_conflicts++] = entry;
+				}
+				else
+					entry.relid = InvalidOid;
+			}
+		}
+
+		MyIndexInsertEntry->relid = heap->rd_id;
+		MyIndexInsertEntry->xid	  = GetCurrentTransactionId();
+		MyIndexInsertEntry->tid	  = *tid;
+
+		LWLockRelease(IndexConstraintLock);
+
+		InitDirtySnapshot(DirtySnapshot);
+
+		for (i = 0; i < n_potential_conflicts; i++)
+		{
+			bool				does_conflict = true;
+			HeapTupleData		tup;
+			Buffer				buffer;
+
+			tup.t_self = potential_conflicts[i].tid;
+			if (!heap_fetch(heap, &DirtySnapshot, &tup, &buffer, false, NULL))
+				continue;
+
+			does_conflict = index_check_constraint_conflict(
+				slot, &tup, heap_attnums, index_natts, constr_procs);
+
+			ReleaseBuffer(buffer);
+
+			if (does_conflict)
+			{
+				CurrentIndexInsertEntry conflict = potential_conflicts[i];
+				if (TransactionIdIsCurrentTransactionId(conflict.xid))
+					elog(ERROR, "conflict detected 1");
+
+				XactLockTableWait(conflict.xid);
+				if (TransactionIdDidCommit(conflict.xid))
+					elog(ERROR, "conflict detected 2");
+			}
+		}
+
+		/*
+		 * Now search the tuples that are actually in the index for
+		 * any violations.
+		 */
+
+		scankeys = palloc(index_natts * sizeof(ScanKeyData));
+		for (i = 0; i < index_natts; i++)
+		{
+			Datum	key_datum;
+			bool	isnull;
+
+			key_datum = slot_getattr(slot, heap_attnums[i], &isnull);
+			Assert(!isnull); /* already checked above */
+
+			if (constr_strats->values[i] == InvalidStrategy)
+				continue;
+
+			ScanKeyInit(&scankeys[nkeys], i + 1, constr_strats->values[i],
+						constr_procs[i], key_datum);
+			nkeys++;
+		}
+
+		/*
+		 * We have to find all tuples, even those not visible
+		 * yet. Other transactions may have inserted many tuples (or
+		 * the transaction might be a prepared transaction), so there
+		 * may be some tuples that are not in the shared memory
+		 * structure and not visible.
+		 */
+		index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+									 scankeys);
+		while((tup = index_getnext(index_scan, ForwardScanDirection)) != NULL)
+		{
+			if (index_scan->xs_recheck)
+			{
+				if (!index_check_constraint_conflict(
+						slot, tup, heap_attnums, index_natts, constr_procs))
+					continue;
+			}
+
+			/* If the in-progress inserting transaction aborts, proceed. */
+			if (TransactionIdIsValid(DirtySnapshot.xmin))
+			{
+				XactLockTableWait(DirtySnapshot.xmin);
+				if (TransactionIdDidAbort(DirtySnapshot.xmin))
+					continue;
+			}
+
+			/* If the in-progress deleting transaction commits, proceed. */
+			if (TransactionIdIsValid(DirtySnapshot.xmax))
+			{
+				XactLockTableWait(DirtySnapshot.xmax);
+				if (TransactionIdDidCommit(DirtySnapshot.xmax))
+					continue;
+			}
+
+			elog(ERROR, "conflict detected 3");
+		}
+
+		index_endscan(index_scan);
+		pfree(scankeys);
+
+		return;
+}
+
+/*
+ * For each attribute of the index, check for a conflict between the
+ * slot's tuple's value and the tuple's value. Only return true if all
+ * values conflict.
+ */
+static bool
+index_check_constraint_conflict(TupleTableSlot *slot, HeapTuple tup,
+								int2 *heap_attnums, int2 index_natts,
+								Oid* constraint_procs)
+{
+	int i;
+
+	for (i = 0; i < index_natts; i++)
+	{
+		Datum	input_datum;
+		Datum	existing_datum;
+		bool	isnull;
+
+		if (!OidIsValid(constraint_procs[i]))
+			continue;
+
+		input_datum = slot_getattr(slot, heap_attnums[i], &isnull);
+		if (isnull)
+			return false;
+
+		existing_datum = heap_getattr(
+			tup, heap_attnums[i], slot->tts_tupleDescriptor, &isnull);
+		if (isnull)
+			return false;
+
+		if (!DatumGetBool(OidFunctionCall2(constraint_procs[i],
+										   input_datum, existing_datum)))
+			return false;
+	}
+	return true;
+}
+
+/*
+ * GistShmemSize --- report amount of shared memory space needed
+ */
+Size
+CurrentIndexInsertsShmemSize(void)
+{
+	return (sizeof(CurrentIndexInsertEntry) * MaxBackends);
+}
+
+/*
+ * GistShmemInit --- initialize this module's shared memory
+ */
+void
+CurrentIndexInsertsShmemInit(void)
+{
+	int							 i;
+	bool						 found;
+	CurrentIndexInsertEntry		*current_inserts;
+
+	current_inserts = (CurrentIndexInsertEntry *)
+		ShmemInitStruct("Current Index Inserts Table",
+						CurrentIndexInsertsShmemSize(),
+						&found);
+
+	if (!IsUnderPostmaster)
+	{
+		/* Initialize shared memory area */
+		Assert(!found);
+
+		for (i = 0; i < MaxBackends; i++)
+			current_inserts[i].relid = InvalidOid;
+	}
+	else
+		Assert(found);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c4e4cab..8a136bb 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -444,6 +444,7 @@ UpdateIndexRelation(Oid indexoid,
 	values[Anum_pg_index_indcheckxmin - 1] = BoolGetDatum(false);
 	/* we set isvalid and isready the same way */
 	values[Anum_pg_index_indisready - 1] = BoolGetDatum(isvalid);
+	nulls[Anum_pg_index_indconstrats - 1] = true;
 	values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey);
 	values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass);
 	values[Anum_pg_index_indoption - 1] = PointerGetDatum(indoption);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 0ccd862..e8d704c 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1067,6 +1067,15 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 	econtext->ecxt_scantuple = slot;
 
 	/*
+	 * before actually inserting, check index constraints for each index
+	 */
+	for (i = 0; i < numIndices; i++)
+	{
+		index_check_constraint(heapRelation, relationDescs[i],
+							   tupleid, slot);
+	}
+
+	/*
 	 * for each index, form and insert the index tuple
 	 */
 	for (i = 0; i < numIndices; i++)
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 3022867..8c04940 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/clog.h"
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/nbtree.h"
@@ -115,6 +116,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 		size = add_size(size, BgWriterShmemSize());
 		size = add_size(size, AutoVacuumShmemSize());
 		size = add_size(size, BTreeShmemSize());
+		size = add_size(size, CurrentIndexInsertsShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
@@ -215,6 +217,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	 * Set up other modules that need some shared memory space
 	 */
 	BTreeShmemInit();
+	CurrentIndexInsertsShmemInit();
 	SyncScanShmemInit();
 
 #ifdef EXEC_BACKEND
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 29976e7..24583d3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3284,6 +3284,22 @@ RelationGetIndexAttrBitmap(Relation relation)
 	return indexattrs;
 }
 
+int2vector *
+RelationGetIndexConstraintStrategies(Relation relation)
+{
+	bool		isnull;
+	Datum		constraint_strategies;
+
+	constraint_strategies = heap_getattr(relation->rd_indextuple,
+										 Anum_pg_index_indconstrats,
+										 GetPgIndexDescriptor(),
+										 &isnull);
+	if (isnull)
+		return NULL;
+
+	return (int2vector *) DatumGetPointer(constraint_strategies);
+
+}
 
 /*
  *	load_relcache_init_file, write_relcache_init_file
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index a6ac5db..2aad8b4 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -16,6 +16,8 @@
 
 #include "access/sdir.h"
 #include "access/skey.h"
+#include "access/xact.h"
+#include "executor/tuptable.h"
 #include "nodes/tidbitmap.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -129,6 +131,13 @@ extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
 				uint16 procnum);
 extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
 				  uint16 procnum);
+extern void index_check_constraint(Relation heapRelation,
+								   Relation indexRelation,
+								   ItemPointer tid,
+								   TupleTableSlot *slot);
+
+extern Size CurrentIndexInsertsShmemSize(void);
+extern void CurrentIndexInsertsShmemInit(void);
 
 /*
  * index access method support routines (in genam.c)
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index eaa405f..413cf0a 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -477,6 +477,7 @@ DATA(insert ( 1259 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
 { 0, {"indclass"},			30, -1, -1, 11, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
 { 0, {"indoption"},			22, -1, -1, 12, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
 { 0, {"indexprs"},			25, -1, -1, 13, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
-{ 0, {"indpred"},			25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
+{ 0, {"indpred"},			25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 0, {"indconstrats"},		22, -1, -1, 15, 1, -1, -1, false, 'p', 'i', false, false, false, true, 0, { 0 } }
 
 #endif   /* PG_ATTRIBUTE_H */
diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h
index 19069db..9ab905c 100644
--- a/src/include/catalog/pg_index.h
+++ b/src/include/catalog/pg_index.h
@@ -49,6 +49,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS
 								 * each zero entry in indkey[] */
 	text		indpred;		/* expression tree for predicate, if a partial
 								 * index; else NULL */
+	int2vector	indconstrats;	/* index constraint strategies */
 } FormData_pg_index;
 
 /* ----------------
@@ -62,7 +63,7 @@ typedef FormData_pg_index *Form_pg_index;
  *		compiler constants for pg_index
  * ----------------
  */
-#define Natts_pg_index					14
+#define Natts_pg_index					15
 #define Anum_pg_index_indexrelid		1
 #define Anum_pg_index_indrelid			2
 #define Anum_pg_index_indnatts			3
@@ -77,6 +78,7 @@ typedef FormData_pg_index *Form_pg_index;
 #define Anum_pg_index_indoption			12
 #define Anum_pg_index_indexprs			13
 #define Anum_pg_index_indpred			14
+#define Anum_pg_index_indconstrats		15
 
 /*
  * Index AMs that support ordered scans must support these two indoption
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index e389c61..9e6f93e 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -63,6 +63,7 @@ typedef enum LWLockId
 	TwoPhaseStateLock,
 	TablespaceCreateLock,
 	BtreeVacuumLock,
+	IndexConstraintLock,
 	AddinShmemInitLock,
 	AutovacuumLock,
 	AutovacuumScheduleLock,
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 7d4d914..305d1d2 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -43,6 +43,7 @@ extern Oid	RelationGetOidIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+extern int2vector *RelationGetIndexConstraintStrategies(Relation relation);
 
 extern void RelationSetIndexList(Relation relation,
 					 List *indexIds, Oid oidIndex);
#2Simon Riggs
simon@2ndQuadrant.com
In reply to: Jeff Davis (#1)
Re: WIP: generalized index constraints

On Sun, 2009-07-05 at 17:28 -0700, Jeff Davis wrote:

This is a follow up to my old proposal here:

http://archives.postgresql.org/pgsql-hackers/2008-06/msg00404.php

Any input is appreciated (design problems, implementation, language
ideas, or anything else). I'd like to get it into shape for the July
15 commitfest if no major problems are found.

I was concerned that your definition of concurrently inserted didn't
seem to match the size of the shared memory array required.

How will you cope with a large COPY? Surely there can be more than one
concurrent insert from any backend?

It would be useful to see a real example of what this can be used for.

I think it will be useful to separate the concepts of a constraint from
the concept of an index. It seems possible to have a UNIQUE constraint
that doesn't help at all in locating rows, just in proving that the rows
are unique.

--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Training, Services and Support

#3Greg Stark
gsstark@mit.edu
In reply to: Simon Riggs (#2)
Re: WIP: generalized index constraints

On Mon, Jul 6, 2009 at 11:56 AM, Simon Riggs<simon@2ndquadrant.com> wrote:

How will you cope with a large COPY? Surely there can be more than one
concurrent insert from any backend?

He only needs to handle inserts for the period they're actively being
inserted into the index. Once they're in the index he'll find them
using the index scan. In other words this is all a proxy for the way
btree locks index pages while it looks for a unique key violation.

I'm a bit concerned about the use of tid. You might have to look at a
lot of heap pages to check for conflicts. I suppose they're almost
certainly all in shared memory though. Also, it sounds like you're
anticipating the possibility of dead entries in the array but if you
do then you need to store the xmin also to protect against a tuple
that's been vacuumed and had its line pointer reused since. But I
don't see the necessity for that anyways since you can just clean up
the entry on abort.

--
greg
http://mit.edu/~gsstark/resume.pdf

#4David Fetter
david@fetter.org
In reply to: Simon Riggs (#2)
Re: WIP: generalized index constraints

On Mon, Jul 06, 2009 at 11:56:41AM +0100, Simon Riggs wrote:

On Sun, 2009-07-05 at 17:28 -0700, Jeff Davis wrote:

This is a follow up to my old proposal here:

http://archives.postgresql.org/pgsql-hackers/2008-06/msg00404.php

Any input is appreciated (design problems, implementation,
language ideas, or anything else). I'd like to get it into shape
for the July 15 commitfest if no major problems are found.

I was concerned that your definition of concurrently inserted didn't
seem to match the size of the shared memory array required.

How will you cope with a large COPY? Surely there can be more than
one concurrent insert from any backend?

It would be useful to see a real example of what this can be used
for.

Constraints like "these intervals can't overlap" would be one. It's
handy in calendaring applications, for example.

I think it will be useful to separate the concepts of a constraint
from the concept of an index. It seems possible to have a UNIQUE
constraint that doesn't help at all in locating rows, just in
proving that the rows are unique.

Interesting idea. Are you thinking of this in terms of things the
planner can do once it knows a set is all distinct values, or...?

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#5Jeff Davis
pgsql@j-davis.com
In reply to: Greg Stark (#3)
Re: WIP: generalized index constraints

On Mon, 2009-07-06 at 12:28 +0100, Greg Stark wrote:

He only needs to handle inserts for the period they're actively being
inserted into the index. Once they're in the index he'll find them
using the index scan. In other words this is all a proxy for the way
btree locks index pages while it looks for a unique key violation.

Exactly, that was my design:

/*
* We have to find all tuples, even those not visible
* yet. Other transactions may have inserted many tuples (or
* the transaction might be a prepared transaction), so there
* may be some tuples that are not in the shared memory
* structure and not visible.
*/

I'm a bit concerned about the use of tid. You might have to look at a
lot of heap pages to check for conflicts. I suppose they're almost
certainly all in shared memory though.

That was my hope.

The 8.4 bulk insert code might defeat that to some degree, however.
Maybe that could be disabled when inserting into an index with
constraints? I didn't think about it before, but the bulk insert buffer
ring would affect unique btrees, too, right?

Also, it sounds like you're
anticipating the possibility of dead entries in the array but if you
do then you need to store the xmin also to protect against a tuple
that's been vacuumed and had its line pointer reused since. But I
don't see the necessity for that anyways since you can just clean up
the entry on abort.

Can you tell me a little more specifically the problem you're worried
about? If the tuple has been VACUUMed and removed, then the TID search
will either find a tuple, and do a spurious constraint check against it;
or not find a tuple, and just move on.

I could have the commit and abort paths clear the entry, which might
optimize away some of the TransactionIdIsInProgress() calls for
transactions that ended normally. But that didn't strike me as a big
cost compared to the index scan.

Regards,
Jeff Davis

#6Jeff Davis
pgsql@j-davis.com
In reply to: Simon Riggs (#2)
Re: WIP: generalized index constraints

On Mon, 2009-07-06 at 11:56 +0100, Simon Riggs wrote:

I think it will be useful to separate the concepts of a constraint from
the concept of an index. It seems possible to have a UNIQUE constraint
that doesn't help at all in locating rows, just in proving that the rows
are unique.

That would be interesting. Do you have a use case? Checking the
constraint would surely be slower in a lot of cases.

I could imagine different constraint-checking schemes that could be fast
against a heap. For instance, if it's greater than the max or less than
the min value, that would be cheap to check. That might be an
interesting way to handle the constraint for a sequence-generated
column, or timestamp column that is always ascending.

However, the problem is I don't see a lot of room for a practical use
case. In the above situations, you'd almost certainly want indexes
anyway: what's the point of a sequence number unless you're going to do
lookups? And if you have an ascending timestamp column, I would think
that you might do range lookups occasionally (which will be even better
because the heap will be clustered).

Regards,
Jeff Davis

#7Jeff Davis
pgsql@j-davis.com
In reply to: David Fetter (#4)
Re: WIP: generalized index constraints

On Mon, 2009-07-06 at 07:30 -0700, David Fetter wrote:

It would be useful to see a real example of what this can be used
for.

Constraints like "these intervals can't overlap" would be one. It's
handy in calendaring applications, for example.

Exactly, you already know my use case ;) My goal is a "temporal key",
where you can't have overlapping intervals of time, e.g. the constraint
"nobody can be two places at the same time".

I think it will be useful to separate the concepts of a constraint
from the concept of an index. It seems possible to have a UNIQUE
constraint that doesn't help at all in locating rows, just in
proving that the rows are unique.

Interesting idea. Are you thinking of this in terms of things the
planner can do once it knows a set is all distinct values, or...?

I think that's an orthogonal idea.

It's a good idea though, I would like the planner to be smarter about
those kinds of things. A simple example is that if a table has a
non-partial unique constraint anywhere, then "select * from foo union
select * from foo" can be transformed into "select * from
foo" (eliminating the expensive union).

Regards,
Jeff Davis

#8Greg Stark
gsstark@mit.edu
In reply to: Jeff Davis (#7)
Re: WIP: generalized index constraints

On Mon, Jul 6, 2009 at 4:57 PM, Jeff Davis<pgsql@j-davis.com> wrote:

Exactly, you already know my use case ;) My goal is a "temporal key",
where you can't have overlapping intervals of time, e.g. the constraint
"nobody can be two places at the same time".

Incidentally to handle non-overlapping ranges you don't need GIST, you
can actually use a plain btree. Since there are no overlapping ranges
the ranges have a complete ordering and you can get that by just
sorting by either endpoint. To enforce the constraint you only have to
compare with the previous and following element in the btree.

--
greg
http://mit.edu/~gsstark/resume.pdf

#9Jeff Davis
pgsql@j-davis.com
In reply to: Greg Stark (#8)
Re: WIP: generalized index constraints

On Mon, 2009-07-06 at 17:02 +0100, Greg Stark wrote:

On Mon, Jul 6, 2009 at 4:57 PM, Jeff Davis<pgsql@j-davis.com> wrote:

Exactly, you already know my use case ;) My goal is a "temporal key",
where you can't have overlapping intervals of time, e.g. the constraint
"nobody can be two places at the same time".

Incidentally to handle non-overlapping ranges you don't need GIST, you
can actually use a plain btree. Since there are no overlapping ranges
the ranges have a complete ordering and you can get that by just
sorting by either endpoint. To enforce the constraint you only have to
compare with the previous and following element in the btree.

What if you have an entire index full of overlapping dead tuples, and a
few live ones? How would search work?

Regards,
Jeff Davis

#10Simon Riggs
simon@2ndQuadrant.com
In reply to: Jeff Davis (#6)
Re: WIP: generalized index constraints

On Mon, 2009-07-06 at 08:50 -0700, Jeff Davis wrote:

On Mon, 2009-07-06 at 11:56 +0100, Simon Riggs wrote:

I think it will be useful to separate the concepts of a constraint from
the concept of an index. It seems possible to have a UNIQUE constraint
that doesn't help at all in locating rows, just in proving that the rows
are unique.

That would be interesting. Do you have a use case? Checking the
constraint would surely be slower in a lot of cases.

I could imagine different constraint-checking schemes that could be fast
against a heap. For instance, if it's greater than the max or less than
the min value, that would be cheap to check. That might be an
interesting way to handle the constraint for a sequence-generated
column, or timestamp column that is always ascending.

Yes.

However, the problem is I don't see a lot of room for a practical use
case. In the above situations, you'd almost certainly want indexes
anyway: what's the point of a sequence number unless you're going to do
lookups? And if you have an ascending timestamp column, I would think
that you might do range lookups occasionally (which will be even better
because the heap will be clustered).

In many cases, people add unique indexes solely to allow replication to
work correctly. The index itself may never be used, especially in high
volume applications.

How do you handle uniqueness within a stream? Presumably it is possible
and useful to have a stream of data that can be guaranteed unique, yet a
stream would never be uniquely targeted for lookups because of the
volume of data involved.

--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Training, Services and Support

#11Greg Stark
gsstark@mit.edu
In reply to: Jeff Davis (#9)
Re: WIP: generalized index constraints

On Mon, Jul 6, 2009 at 6:20 PM, Jeff Davis<pgsql@j-davis.com> wrote:

On Mon, 2009-07-06 at 17:02 +0100, Greg Stark wrote:

On Mon, Jul 6, 2009 at 4:57 PM, Jeff Davis<pgsql@j-davis.com> wrote:

Exactly, you already know my use case ;) My goal is a "temporal key",
where you can't have overlapping intervals of time, e.g. the constraint
"nobody can be two places at the same time".

Incidentally to handle non-overlapping ranges you don't need GIST, you
can actually use a plain btree. Since there are no overlapping ranges
the ranges have a complete ordering and you can get that by just
sorting by either endpoint. To enforce the constraint you only have to
compare with the previous and following element in the btree.

What if you have an entire index full of overlapping dead tuples, and a
few live ones? How would search work?

I should clarify I didn't mean you could implement it in SQL using
Postgres btrees. I just meant that a tree data structure was
sufficient, you don't need the power of GIST. It's probably easier to
implement it in GIST in Postgres since it's there though.

So it would work just like regular btrees, you only consider it a
conflict if there's a live value that conflicts.

--
greg
http://mit.edu/~gsstark/resume.pdf

#12Jeff Davis
pgsql@j-davis.com
In reply to: Simon Riggs (#10)
Re: WIP: generalized index constraints

On Mon, 2009-07-06 at 18:27 +0100, Simon Riggs wrote:

In many cases, people add unique indexes solely to allow replication to
work correctly. The index itself may never be used, especially in high
volume applications.

Interesting. Maybe we should at least try to leave room for this feature
to be added later. I agree that, from a theoretical perspective,
requiring a UNIQUE constraint to use an index is wrong. For one thing,
you can't ensure the uniqueness without defining some total order
(although you can define an arbitrary total order for cases with no
meaningful total order).

How do you handle uniqueness within a stream? Presumably it is possible
and useful to have a stream of data that can be guaranteed unique, yet a
stream would never be uniquely targeted for lookups because of the
volume of data involved.

[ Simon is asking me because I work for Truviso, but my response is not
officially from Truviso ]

There are a few cases worth mentioning here. First, if you have a stream
that's backed by a table, you can use a table constraint. Second, you
might choose to have an "in-order" constraint (not necessary, the system
can fix out-of-order data), which could be a unique constraint that's
very cheap to test.

Additionally, this is not strictly a constraint, but if you have
downstream operators, like COUNT(DISTINCT...), that can be seen as being
similar to a constraint. These will often be over a limited span of
time, say, a minute or an hour, and we can keep the necessary state. If
there are a huge number of distinct values there, then it's a challenge
to avoid keeping a lot of state.

There are a few other specialized methods that we can use for specific
use-cases.

Regards,
Jeff Davis

#13Teodor Sigaev
teodor@sigaev.ru
In reply to: Jeff Davis (#1)
Re: WIP: generalized index constraints

CREATE INDEX test_idx ON test USING gist
(i CONSTRAINT =, c CONSTRAINT &&);

which would avoid the need for updating the catalog, of course.

Hmm, looks like "index"-fied table's constrains

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#12)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Mon, 2009-07-06 at 18:27 +0100, Simon Riggs wrote:

In many cases, people add unique indexes solely to allow replication to
work correctly. The index itself may never be used, especially in high
volume applications.

Interesting. Maybe we should at least try to leave room for this feature
to be added later. I agree that, from a theoretical perspective,
requiring a UNIQUE constraint to use an index is wrong. For one thing,
you can't ensure the uniqueness without defining some total order
(although you can define an arbitrary total order for cases with no
meaningful total order).

This seems a bit pointless. There is certainly not any use case for a
constraint without an enforcement mechanism (or at least none the PG
community is likely to consider legitimate ;-)). And it's not very
realistic to suppose that you'd check a constraint by doing a seqscan
every time. Therefore there has to be an index underlying the
constraint somehow. Jeff's complaint about total order is not an
argument against having an index, it's just pointing out that btree is
not the only possible type of index. It's perfectly legitimate to
imagine using a hash index to enforce uniqueness, for example. If hash
indexes had better performance we'd probably already have been looking
for a way to do that, and wanting some outside-the-AM mechanism for it
so we didn't have to duplicate code from btree.

Also, if hash indexes were a realistic alternative to btree for this,
we'd already have come up against the problem that the CONSTRAINT syntax
doesn't provide any way to specify what kind of index you want to use
underneath the constraint. I think it might be interesting to turn
around Jeff's syntax sketch and provide a way to say that a CONSTRAINT
declaration should depend on some previously added index, eg
something like

ALTER TABLE tab ADD CONSTRAINT UNIQUE (col1, col2) USING index

Not sure how that squares exactly with the question of variant
definitions of uniqueness semantics (as opposed to purely implementation
decisions like hash vs btree). But it's a different way to come at it.

regards, tom lane

#15Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#14)
Re: WIP: generalized index constraints

On Tue, 2009-07-07 at 13:22 -0400, Tom Lane wrote:

ALTER TABLE tab ADD CONSTRAINT UNIQUE (col1, col2) USING index

This would be very useful, though perhaps only because we do not have
REINDEX CONCURRENTLY.

It is likely to be useful in the future to allow an index with N
columns, yet which can provide uniqueness with < N of those columns.
This capability is known as covered indexes and will be important if
Heikki writes his index-only scan code.

--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Training, Services and Support

#16Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#14)
Re: WIP: generalized index constraints

On Tue, 2009-07-07 at 13:22 -0400, Tom Lane wrote:

Jeff Davis <pgsql@j-davis.com> writes:

On Mon, 2009-07-06 at 18:27 +0100, Simon Riggs wrote:

In many cases, people add unique indexes solely to allow replication to
work correctly. The index itself may never be used, especially in high
volume applications.

Interesting. Maybe we should at least try to leave room for this feature
to be added later. I agree that, from a theoretical perspective,
requiring a UNIQUE constraint to use an index is wrong. For one thing,
you can't ensure the uniqueness without defining some total order
(although you can define an arbitrary total order for cases with no
meaningful total order).

This seems a bit pointless. There is certainly not any use case for a
constraint without an enforcement mechanism (or at least none the PG
community is likely to consider legitimate ;-)). And it's not very
realistic to suppose that you'd check a constraint by doing a seqscan
every time. Therefore there has to be an index underlying the
constraint somehow.

I think the idea has been misconstrued.

Obviously a constraint requires an enforcement mechanism. That doesn't
imply that the enforcement mechanism must be fully usable as an index.

The example being discussed was enforcing uniqueness on monotonically
increasing columns. If we knew that a column value was GENERATED ALWAYS
using a sequence, then we could simply skip the uniqueness check
altogether. No index, yet an enforced unique constraint.

Yes, we would need to understand the relationship between the sequence
and the table and throw an error in certain sequence update cases (and
we may need to check those with a seq scan). But that seems a small
price to pay for the avoidance of a potentially very large index that
may have no purpose.

--
Simon Riggs www.2ndQuadrant.com
PostgreSQL Training, Services and Support

#17Greg Stark
gsstark@mit.edu
In reply to: Tom Lane (#14)
Re: WIP: generalized index constraints

On Tue, Jul 7, 2009 at 6:22 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

This seems a bit pointless.  There is certainly not any use case for a
constraint without an enforcement mechanism (or at least none the PG
community is likely to consider legitimate ;-)).  And it's not very
realistic to suppose that you'd check a constraint by doing a seqscan
every time.  Therefore there has to be an index underlying the
constraint somehow.

I'm not entirely convinced that running a full scan to enforce
constraints is necessarily such a crazy idea. It may well be the most
efficient approach after a major bulk load. And consider a read-only
database where the only purpose of the constraint is to inform the
optimizer that it can trust the property to hold.

That said this seems like an orthogonal issue to me.

Jeff's complaint about total order is not an
argument against having an index, it's just pointing out that btree is
not the only possible type of index.  It's perfectly legitimate to
imagine using a hash index to enforce uniqueness, for example.  If hash
indexes had better performance we'd probably already have been looking
for a way to do that, and wanting some outside-the-AM mechanism for it
so we didn't have to duplicate code from btree.

I'm a bit at a loss why we need this extra data structure though. The
whole duplicated code issue seems to me to be one largely of code
structure. If we hoisted the heap-value rechecking code out of the
btree AM then the hash AM could reuse it just fine.

Both the hash and btree AMs would have to implement some kind of
"insert-unique-key" operation which would hold some kind of lock
preventing duplicate unique keys from being inserted but both btree
and hash could implement that efficiently by locking one page or one
hash value.

GIST would need something like this "store the key value or tid in
shared memory" mechanism. But that could be implemented as an external
facility which GIST then made use of -- just the way every part of the
system makes use of other parts. It doesn't mean we have to make
"prevent concurrent unique inserts" not the responsibility of the AM
which knows best how to handle that.

--
greg
http://mit.edu/~gsstark/resume.pdf

#18Jeff Davis
pgsql@j-davis.com
In reply to: Simon Riggs (#15)
Re: WIP: generalized index constraints

On Tue, 2009-07-07 at 18:36 +0100, Simon Riggs wrote:

On Tue, 2009-07-07 at 13:22 -0400, Tom Lane wrote:
It is likely to be useful in the future to allow an index with N
columns, yet which can provide uniqueness with < N of those columns.
This capability is known as covered indexes and will be important if
Heikki writes his index-only scan code.

My patch offers this capability, and the language I suggested would
support it.

In the current version of the patch, just use InvalidStrategy (0)
instead of, say, BTEqualStrategyNumber (3) for the attributes that you
don't want to be a part of the constraint. Some of the proper error
checking is not done yet, but it will work.

Regards,
Jeff Davis

#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#18)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Tue, 2009-07-07 at 18:36 +0100, Simon Riggs wrote:

On Tue, 2009-07-07 at 13:22 -0400, Tom Lane wrote:
It is likely to be useful in the future to allow an index with N
columns, yet which can provide uniqueness with < N of those columns.
This capability is known as covered indexes and will be important if
Heikki writes his index-only scan code.

My patch offers this capability, and the language I suggested would
support it.

In the current version of the patch, just use InvalidStrategy (0)
instead of, say, BTEqualStrategyNumber (3) for the attributes that you
don't want to be a part of the constraint. Some of the proper error
checking is not done yet, but it will work.

I don't think this even approximates the need --- in particular it's not
clear what the semantics of combination across different index columns
are. I assume you've hot-wired it so that several BTEqualStrategyNumber
columns will work like a normal multicolumn uniqueness constraint (IOW
it's okay as long as at least one column is NULL or isn't equal). But
I'm not at all sure that's what I'd want for some other operator type.

Also, what happens if you want to use the same index to support more
than one logical constraint? This is impossible if you put the
information into pg_index, I think.

regards, tom lane

#20Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#19)
Re: WIP: generalized index constraints

On Tue, 2009-07-07 at 14:57 -0400, Tom Lane wrote:

I don't think this even approximates the need --- in particular it's not
clear what the semantics of combination across different index columns
are. I assume you've hot-wired it so that several BTEqualStrategyNumber
columns will work like a normal multicolumn uniqueness constraint (IOW
it's okay as long as at least one column is NULL or isn't equal). But
I'm not at all sure that's what I'd want for some other operator type.

If any input attributes are NULL, the constraint automatically succeeds
(I think that follows the SQL philosophy). Perhaps we should be able to
override this behavior somehow?

Now, for each attribute where a constraint is defined, it becomes a new
scan key used in the index lookup to enforce the constraint. So, the
more attributes in the constraint, the more permissive the constraint
(just like UNIQUE).

I can't think of another good interpretation of the constraint. If a
constraint on (x,y) meant "x is unique, and y is unique", it would be
hard to scan the index for y's uniqueness. If you want to say that both
are independently unique, I believe that requires two indexes, and so it
would probably be best to just require the constraints to be specified
separately. Thoughts?

Also, what happens if you want to use the same index to support more
than one logical constraint? This is impossible if you put the
information into pg_index, I think.

I like the idea to store the information outside of pg_index. It means
that I don't have to build the index and check the constraint at the
same time. It would be more like adding a CHECK constraint.

Regards,
Jeff Davis

#21Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#14)
Re: WIP: generalized index constraints

On Tue, 2009-07-07 at 13:22 -0400, Tom Lane wrote:

Also, if hash indexes were a realistic alternative to btree for this,
we'd already have come up against the problem that the CONSTRAINT syntax
doesn't provide any way to specify what kind of index you want to use
underneath the constraint. I think it might be interesting to turn
around Jeff's syntax sketch and provide a way to say that a CONSTRAINT
declaration should depend on some previously added index, eg
something like

ALTER TABLE tab ADD CONSTRAINT UNIQUE (col1, col2) USING index

How about:

ALTER TABLE tab ADD INDEX CONSTRAINT [name]
(col1 [op], col2 [op]) USING index

And then just validate the constraint at creation time, and store the
information in pg_constraint.

Regards,
Jeff Davis

#22Dean Rasheed
dean.a.rasheed@googlemail.com
In reply to: Tom Lane (#14)
Re: WIP: generalized index constraints

Tom Lane wrote:

... I think it might be interesting to turn
around Jeff's syntax sketch and provide a way to say that a CONSTRAINT
declaration should depend on some previously added index, eg
something like

ALTER TABLE tab ADD CONSTRAINT UNIQUE (col1, col2) USING index

Is there any reason to limit UNIQUE constraints to lists of table
columns? If you can build a unique index on an expression, why not a
unique constraint?

A quick test defining an index and manually adding catalog entries for
the constraint and depends showed that it appears to work fine (and it's
compatible with my deferrable unique constraints patch :-) )

- Dean

#23Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#1)
Re: WIP: generalized index constraints

Right now this patch does not support GIN because GIN doesn't support
amgettuple.

It could be made to support GIN by doing a bitmap index scan, manually
fetching the next tuple (or, if it's lossy, the next one on the page),
checking it against the snapshot, and then rechecking it to make sure it
still matches.

The API I'm looking for is essentially the same as index_getnext(),
which makes the most sense for finding constraint violations.

Is it possible to re-add amgettuple to GIN, and just set the cost high
so it's not chosen by the planner? Or is there some reason this is
fundamentally a bad idea (or won't work at all)?

I know we removed it in 8.4:
http://archives.postgresql.org/pgsql-hackers/2009-02/msg01123.php
http://archives.postgresql.org/pgsql-hackers/2009-02/msg00532.php

but the reasoning seemed mostly because:
1. GIN could not support amgettuple efficiently (lots of rechecking)
2. Normal index scans did not fit a common use case for GIN, anyway

However, if my feature needs to perform this check anyway (to support
GIN, that is), it seems like it could be re-added. There was also some
resistance to removing it in the first place (e.g. for anti-joins), so
perhaps it can be made to be efficient again during the 8.5 cycle.

Regards,
Jeff Davis

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#23)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

Is it possible to re-add amgettuple to GIN, and just set the cost high
so it's not chosen by the planner? Or is there some reason this is
fundamentally a bad idea (or won't work at all)?

We wouldn't have deleted it if it were practical to make it work.

regards, tom lane

#25Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#24)
Re: WIP: generalized index constraints

On Sat, 2009-07-11 at 19:06 -0400, Tom Lane wrote:

Is it possible to re-add amgettuple to GIN, and just set the cost high

We wouldn't have deleted it if it were practical to make it work.

Can you elaborate a little?

Following the thread, I see:

http://archives.postgresql.org/pgsql-hackers/2009-02/msg00532.php

"What would be wrong with letting it degrade to lossy? I suppose the
reason it's trying to avoid that is to avoid having to recheck all the
rows on that page when it comes time to do the index insertion; but
surely having to do that is better than having arbitrary, unpredictable
failure conditions."

And the next message refers to:

http://archives.postgresql.org/message-id/4974B002.3040202@sigaev.ru

"amgettuple interface hasn't possibility to work with page-wide result
instead of exact ItemPointer. amgettuple can not return just a block
number as amgetbitmap can."

I see why it's impractical to make it efficient, but the way I see it we
can make gingettuple just a wrapper around gingetbitmap, which just
iterates through the bitmap. It would not have any performance benefit
over gingetbitmap, obviously. But if my index constraints patch is going
to support GIN (which seems like an interesting application), I would
need to implement a function which does this anyway (efficiently or
not).

Regards,
Jeff Davis

#26Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#1)
1 attachment(s)
Re: WIP: generalized index constraints

Updated patch attached.

Changes:
* Added syntax support:
CREATE INDEX foo_idx ON foo ... (a CONSTRAINT =, b CONSTRAINT &&);
* More aggressively clear the shared memory entries to avoid
unnecessary checks
* Code cleanup

TODO:
* When adding constraint to table with data already in it, verify that
existing data satisfies constraint.
* Clean up error messages a little
* Docs

The following are possible TODO items, but I'd like to get some feedback
first:
* It seems like an alternative language would be better:
ALTER TABLE foo ADD INDEX CONSTRAINT optional_name (a =, b &&)
USING foo_idx;
This language would be more like a table constraint that happens to
use an index. I think it's better because it allows multiple
constraints to be enforced by the same index.
* Right now it only supports index AMs that offer amgettuple, which
excludes GIN. Consider adding a crude implementation of gingettuple
that just calls gingetbitmap internally (obviously providing no
performance advantage over gingetbitmap).

Regards,
Jeff Davis

Attachments:

index-constraints-20090714.patchtext/x-patch; charset=UTF-8; name=index-constraints-20090714.patchDownload
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 1515d9f..d88387b 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -26,6 +26,7 @@
  *		index_vacuum_cleanup	- post-deletion cleanup of an index
  *		index_getprocid - get a support procedure OID
  *		index_getprocinfo - get a support procedure's lookup info
+ *      index_check_constraint - check index constraints
  *
  * NOTES
  *		This file contains the index_ routines which used
@@ -64,9 +65,13 @@
 
 #include "access/relscan.h"
 #include "access/transam.h"
+#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
+#include "storage/lwlock.h"
+#include "storage/procarray.h"
+#include "utils/lsyscache.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
 #include "utils/tqual.h"
@@ -116,6 +121,19 @@ do { \
 static IndexScanDesc index_beginscan_internal(Relation indexRelation,
 						 int nkeys, ScanKey key);
 
+typedef struct
+{
+	Oid					relid;
+	TransactionId		xid;
+	ItemPointerData		tid;
+} CurrentIndexInsertEntry;
+
+static CurrentIndexInsertEntry *CurrentIndexInsertsTable = NULL;
+
+static bool index_check_constraint_conflict(TupleTableSlot *slot,
+											HeapTuple tup, int2 *heap_attnums,
+											int2 index_natts,
+											Oid *constraint_procs);
 
 /* ----------------------------------------------------------------
  *				   index_ interface functions
@@ -846,3 +864,303 @@ index_getprocinfo(Relation irel,
 
 	return locinfo;
 }
+
+void
+index_check_constraint(Relation heap, Relation index,
+						ItemPointer tid, TupleTableSlot *slot)
+{
+		IndexScanDesc	 index_scan;
+		HeapTuple		 tup;
+		ScanKeyData		*scankeys;
+		int2vector		*constr_strats;
+		Oid				*constr_procs;
+		int				 i;
+		int2			*heap_attnums = index->rd_index->indkey.values;
+		int2			 index_natts  = index->rd_index->indnatts;
+		SnapshotData	 DirtySnapshot;
+		int				 nkeys		  = 0;
+
+		CurrentIndexInsertEntry	 potential_conflicts[MaxBackends];
+		int						 n_potential_conflicts = 0;
+
+		/* Find constraint strategy numbers */
+		constr_strats = RelationGetIndexConstraintStrategies(index);
+
+		/* return if no constraint */
+		if (constr_strats == NULL)
+			return;
+
+		/*
+		 * if any of the indexed columns are NULL, the constraint
+		 * is satisfied
+		 */
+		for (i = 0; i < index_natts; i++)
+			if (slot_attisnull(slot, heap_attnums[i]))
+				return;
+
+		/*
+		 * Find the function that tests for a conflict based on the
+		 * strategy number, operator family, and types.
+		 */
+		constr_procs = palloc(sizeof(Oid) * index_natts);
+		for (i = 0; i < index_natts; i++)
+		{
+			/*
+			 * Find the procedure implementing the strategy for the
+			 * index for two arguments both with the type of the
+			 * indexed attribute.
+			 */
+			Oid					oper;
+			Oid					opfamily = index->rd_opfamily[i];
+			StrategyNumber		strategy = constr_strats->values[i];
+			Oid	typeOid	= heap->rd_att->attrs[heap_attnums[i] - 1]->atttypid;
+
+			if (strategy == InvalidStrategy)
+				continue;
+
+			oper = get_opfamily_member(opfamily, typeOid, typeOid, strategy);
+
+			if(OidIsValid(oper))
+				constr_procs[i] = get_opcode(oper);
+			else
+				elog(ERROR, "cannot determine operator for type %d and "
+					 "strategy %d", typeOid, strategy);
+		}
+
+		/*
+		 * Check for conflicts with concurrent inserts. These are
+		 * inserts that are actually in-progress now, and have not
+		 * actually been put in the index yet.
+		 */
+
+		Assert (CurrentIndexInsertsTable != NULL);
+
+		LWLockAcquire(IndexConstraintLock, LW_SHARED);
+
+		for (i = 0; i < MaxBackends; i++)
+		{
+			CurrentIndexInsertEntry entry = CurrentIndexInsertsTable[i];
+
+			if (RelationGetRelid(heap) == entry.relid &&
+				!TransactionIdIsCurrentTransactionId(entry.xid) &&
+				TransactionIdIsInProgress(entry.xid))
+			{
+					potential_conflicts[n_potential_conflicts++] = entry;
+			}
+		}
+
+		LWLockRelease(IndexConstraintLock);
+
+		InitDirtySnapshot(DirtySnapshot);
+
+		for (i = 0; i < n_potential_conflicts; i++)
+		{
+			bool				does_conflict = true;
+			HeapTupleData		tup;
+			Buffer				buffer;
+
+			tup.t_self = potential_conflicts[i].tid;
+			if (!heap_fetch(heap, &DirtySnapshot, &tup, &buffer, false, NULL))
+				continue;
+
+			does_conflict = index_check_constraint_conflict(
+				slot, &tup, heap_attnums, index_natts, constr_procs);
+
+			ReleaseBuffer(buffer);
+
+			if (does_conflict)
+			{
+				CurrentIndexInsertEntry conflict = potential_conflicts[i];
+				if (TransactionIdIsCurrentTransactionId(conflict.xid))
+					elog(ERROR, "conflict detected 1");
+
+				XactLockTableWait(conflict.xid);
+				if (TransactionIdDidCommit(conflict.xid))
+					elog(ERROR, "conflict detected 2");
+			}
+		}
+
+		/*
+		 * Now search the tuples that are actually in the index for
+		 * any violations.
+		 */
+
+		scankeys = palloc(index_natts * sizeof(ScanKeyData));
+		for (i = 0; i < index_natts; i++)
+		{
+			Datum	key_datum;
+			bool	isnull;
+
+			key_datum = slot_getattr(slot, heap_attnums[i], &isnull);
+			Assert(!isnull); /* already checked above */
+
+			if (constr_strats->values[i] == InvalidStrategy)
+				continue;
+
+			ScanKeyInit(&scankeys[nkeys], i + 1, constr_strats->values[i],
+						constr_procs[i], key_datum);
+			nkeys++;
+		}
+
+		/*
+		 * We have to find all tuples, even those not visible
+		 * yet. Other transactions may have inserted many tuples (or
+		 * the transaction might be a prepared transaction), so there
+		 * may be some tuples that are not in the shared memory
+		 * structure and not visible.
+		 */
+		index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+									 scankeys);
+		while((tup = index_getnext(index_scan, ForwardScanDirection)) != NULL)
+		{
+			if (index_scan->xs_recheck)
+			{
+				if (!index_check_constraint_conflict(
+						slot, tup, heap_attnums, index_natts, constr_procs))
+					continue;
+			}
+
+			/* If the in-progress inserting transaction aborts, proceed. */
+			if (TransactionIdIsValid(DirtySnapshot.xmin))
+			{
+				XactLockTableWait(DirtySnapshot.xmin);
+				if (TransactionIdDidAbort(DirtySnapshot.xmin))
+					continue;
+			}
+
+			/* If the in-progress deleting transaction commits, proceed. */
+			if (TransactionIdIsValid(DirtySnapshot.xmax))
+			{
+				XactLockTableWait(DirtySnapshot.xmax);
+				if (TransactionIdDidCommit(DirtySnapshot.xmax))
+					continue;
+			}
+
+			elog(ERROR, "conflict detected 3");
+		}
+
+		index_endscan(index_scan);
+		pfree(scankeys);
+
+		return;
+}
+
+/*
+ * For each attribute of the index, check for a conflict between the
+ * slot's tuple's value and the tuple's value. Only return true if all
+ * values conflict.
+ */
+static bool
+index_check_constraint_conflict(TupleTableSlot *slot, HeapTuple tup,
+								int2 *heap_attnums, int2 index_natts,
+								Oid* constraint_procs)
+{
+	int i;
+
+	for (i = 0; i < index_natts; i++)
+	{
+		Datum	input_datum;
+		Datum	existing_datum;
+		bool	isnull;
+
+		if (!OidIsValid(constraint_procs[i]))
+			continue;
+
+		input_datum = slot_getattr(slot, heap_attnums[i], &isnull);
+		if (isnull)
+			return false;
+
+		existing_datum = heap_getattr(
+			tup, heap_attnums[i], slot->tts_tupleDescriptor, &isnull);
+		if (isnull)
+			return false;
+
+		if (!DatumGetBool(OidFunctionCall2(constraint_procs[i],
+										   input_datum, existing_datum)))
+			return false;
+	}
+	return true;
+}
+
+void
+index_set_current_insert(Oid relid, ItemPointer tupleid)
+{
+	if (CurrentIndexInsertsTable == NULL)
+	{
+		bool found;
+
+		CurrentIndexInsertsTable = (CurrentIndexInsertEntry *)
+			ShmemInitStruct("Current Index Inserts Table",
+							CurrentIndexInsertsShmemSize(), &found);
+		Assert(found);
+	}
+
+	LWLockAcquire(IndexConstraintLock, LW_EXCLUSIVE);
+
+	CurrentIndexInsertsTable[MyBackendId - 1].relid = relid;
+	CurrentIndexInsertsTable[MyBackendId - 1].xid = GetCurrentTransactionId();
+	CurrentIndexInsertsTable[MyBackendId - 1].tid = *tupleid;
+
+	LWLockRelease(IndexConstraintLock);
+}
+
+void
+index_unset_current_insert(void)
+{
+	Assert(CurrentIndexInsertsTable != NULL);
+
+	LWLockAcquire(IndexConstraintLock, LW_EXCLUSIVE);
+
+	CurrentIndexInsertsTable[MyBackendId - 1].relid = InvalidOid;
+
+	LWLockRelease(IndexConstraintLock);
+}
+
+/*
+ * GistShmemSize --- report amount of shared memory space needed
+ */
+Size
+CurrentIndexInsertsShmemSize(void)
+{
+	return (sizeof(CurrentIndexInsertEntry) * MaxBackends);
+}
+
+/*
+ * GistShmemInit --- initialize this module's shared memory
+ */
+void
+CurrentIndexInsertsShmemInit(void)
+{
+	int							 i;
+	bool						 found;
+	CurrentIndexInsertEntry		*current_inserts;
+
+	current_inserts = (CurrentIndexInsertEntry *)
+		ShmemInitStruct("Current Index Inserts Table",
+						CurrentIndexInsertsShmemSize(),
+						&found);
+
+	if (!IsUnderPostmaster)
+	{
+		/* Initialize shared memory area */
+		Assert(!found);
+
+		for (i = 0; i < MaxBackends; i++)
+			current_inserts[i].relid = InvalidOid;
+	}
+	else
+		Assert(found);
+}
+
+void
+AtEOXact_IndexConstraints(void)
+{
+	if (CurrentIndexInsertsTable != NULL)
+	{
+		LWLockAcquire(IndexConstraintLock, LW_EXCLUSIVE);
+
+		CurrentIndexInsertsTable[MyBackendId - 1].relid = InvalidOid;
+
+		LWLockRelease(IndexConstraintLock);
+	}
+}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 117fdab..2716141 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1707,6 +1707,7 @@ CommitTransaction(void)
 	AtEOXact_HashTables(true);
 	AtEOXact_PgStat(true);
 	AtEOXact_Snapshot(true);
+	AtEOXact_IndexConstraints();
 	pgstat_report_xact_timestamp(0);
 
 	CurrentResourceOwner = NULL;
@@ -1942,6 +1943,7 @@ PrepareTransaction(void)
 	AtEOXact_HashTables(true);
 	/* don't call AtEOXact_PgStat here */
 	AtEOXact_Snapshot(true);
+	AtEOXact_IndexConstraints();
 
 	CurrentResourceOwner = NULL;
 	ResourceOwnerDelete(TopTransactionResourceOwner);
@@ -2085,6 +2087,7 @@ AbortTransaction(void)
 	AtEOXact_HashTables(false);
 	AtEOXact_PgStat(false);
 	AtEOXact_Snapshot(false);
+	AtEOXact_IndexConstraints();
 	pgstat_report_xact_timestamp(0);
 
 	/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c4e4cab..d285a78 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -86,6 +86,7 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 					IndexInfo *indexInfo,
 					Oid *classOids,
 					int16 *coloptions,
+					StrategyNumber *constrats,
 					bool primary,
 					bool isvalid);
 static void index_update_stats(Relation rel, bool hasindex, bool isprimary,
@@ -371,12 +372,14 @@ UpdateIndexRelation(Oid indexoid,
 					IndexInfo *indexInfo,
 					Oid *classOids,
 					int16 *coloptions,
+					StrategyNumber *constrats,
 					bool primary,
 					bool isvalid)
 {
 	int2vector *indkey;
 	oidvector  *indclass;
 	int2vector *indoption;
+	int2vector *indconstrats;
 	Datum		exprsDatum;
 	Datum		predDatum;
 	Datum		values[Natts_pg_index];
@@ -394,6 +397,8 @@ UpdateIndexRelation(Oid indexoid,
 		indkey->values[i] = indexInfo->ii_KeyAttrNumbers[i];
 	indclass = buildoidvector(classOids, indexInfo->ii_NumIndexAttrs);
 	indoption = buildint2vector(coloptions, indexInfo->ii_NumIndexAttrs);
+	indconstrats = buildint2vector((int2 *) constrats,
+								   indexInfo->ii_NumIndexAttrs);
 
 	/*
 	 * Convert the index expressions (if any) to a text datum
@@ -447,6 +452,12 @@ UpdateIndexRelation(Oid indexoid,
 	values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey);
 	values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass);
 	values[Anum_pg_index_indoption - 1] = PointerGetDatum(indoption);
+
+	if (constrats != NULL)
+		values[Anum_pg_index_indconstrats - 1] = PointerGetDatum(indconstrats);
+	else
+		nulls[Anum_pg_index_indconstrats - 1] = true;
+
 	values[Anum_pg_index_indexprs - 1] = exprsDatum;
 	if (exprsDatum == (Datum) 0)
 		nulls[Anum_pg_index_indexprs - 1] = true;
@@ -506,6 +517,7 @@ index_create(Oid heapRelationId,
 			 Oid tableSpaceId,
 			 Oid *classObjectId,
 			 int16 *coloptions,
+			 StrategyNumber *constrats,
 			 Datum reloptions,
 			 bool isprimary,
 			 bool isconstraint,
@@ -679,7 +691,8 @@ index_create(Oid heapRelationId,
 	 * ----------------
 	 */
 	UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
-						classObjectId, coloptions, isprimary, !concurrent);
+						classObjectId, coloptions, constrats, isprimary,
+						!concurrent);
 
 	/*
 	 * Register constraint and dependencies for the index.
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 6e7b5cf..1ab1a2b 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -252,7 +252,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 							   indexInfo,
 							   BTREE_AM_OID,
 							   rel->rd_rel->reltablespace,
-							   classObjectId, coloptions, (Datum) 0,
+							   classObjectId, coloptions, NULL, (Datum) 0,
 							   true, false, true, false, false);
 
 	/*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 99ab0e5..7a9300f 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -36,6 +36,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"
@@ -57,6 +58,8 @@ static void CheckPredicate(Expr *predicate);
 static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  Oid *classOidP,
 				  int16 *colOptionP,
+				  StrategyNumber *constrats,
+				  bool *has_constraints,
 				  List *attList,
 				  Oid relId,
 				  char *accessMethodName, Oid accessMethodId,
@@ -126,6 +129,8 @@ DefineIndex(RangeVar *heapRelation,
 	RegProcedure amoptions;
 	Datum		reloptions;
 	int16	   *coloptions;
+	StrategyNumber	   *constrats;
+	bool		has_constraints;
 	IndexInfo  *indexInfo;
 	int			numberOfAttributes;
 	VirtualTransactionId *old_lockholders;
@@ -422,9 +427,15 @@ DefineIndex(RangeVar *heapRelation,
 
 	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
 	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
-	ComputeIndexAttrs(indexInfo, classObjectId, coloptions, attributeList,
-					  relationId, accessMethodName, accessMethodId,
-					  amcanorder, isconstraint);
+	constrats = (StrategyNumber *) palloc(
+		numberOfAttributes * sizeof(StrategyNumber));
+	ComputeIndexAttrs(indexInfo, classObjectId, coloptions, constrats,
+					  &has_constraints, attributeList, relationId,
+					  accessMethodName, accessMethodId, amcanorder,
+					  isconstraint);
+
+	if (!has_constraints)
+		constrats = NULL;
 
 	/*
 	 * Report index creation if appropriate (delay this till after most of the
@@ -447,8 +458,9 @@ DefineIndex(RangeVar *heapRelation,
 		indexRelationId =
 			index_create(relationId, indexRelationName, indexRelationId,
 					  indexInfo, accessMethodId, tablespaceId, classObjectId,
-						 coloptions, reloptions, primary, isconstraint,
-						 allowSystemTableMods, skip_build, concurrent);
+						 coloptions, constrats, reloptions, primary,
+						 isconstraint, allowSystemTableMods, skip_build,
+						 concurrent);
 
 		return;					/* We're done, in the standard case */
 	}
@@ -465,7 +477,7 @@ DefineIndex(RangeVar *heapRelation,
 	indexRelationId =
 		index_create(relationId, indexRelationName, indexRelationId,
 					 indexInfo, accessMethodId, tablespaceId, classObjectId,
-					 coloptions, reloptions, primary, isconstraint,
+					 coloptions, constrats, reloptions, primary, isconstraint,
 					 allowSystemTableMods, true, concurrent);
 
 	/*
@@ -792,6 +804,8 @@ static void
 ComputeIndexAttrs(IndexInfo *indexInfo,
 				  Oid *classOidP,
 				  int16 *colOptionP,
+				  StrategyNumber *constrats, /* constraint strategies */
+				  bool *has_constraints,
 				  List *attList,	/* list of IndexElem's */
 				  Oid relId,
 				  char *accessMethodName,
@@ -802,6 +816,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 	ListCell   *rest;
 	int			attn = 0;
 
+	*has_constraints = false;
+
 	/*
 	 * process attributeList
 	 */
@@ -891,6 +907,21 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 										  accessMethodName,
 										  accessMethodId);
 
+		if (attribute->constraint_oper != NULL)
+		{
+			Oid opfamily = get_opclass_family(classOidP[attn]);
+			Oid opid = LookupOperName(NULL, attribute->constraint_oper,
+									  atttype, atttype, false, -1);
+			constrats[attn] = get_op_opfamily_strategy(opid, opfamily);
+			if (constrats[attn] == InvalidStrategy)
+				elog(ERROR, "no strategy found for operator %d "
+					 "in operator class %d", opid, classOidP[attn]);
+			*has_constraints = true;
+		}
+		else
+			constrats[attn] = InvalidStrategy;
+
+
 		/*
 		 * Set up the per-column options (indoption field).  For now, this is
 		 * zero for any un-ordered index, while ordered indexes have DESC and
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 0ccd862..7e0cb18 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1067,6 +1067,14 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 	econtext->ecxt_scantuple = slot;
 
 	/*
+	 * before inserting, check index constraints for each index
+	 */
+	index_set_current_insert(RelationGetRelid(heapRelation), tupleid);
+	for (i = 0; i < numIndices; i++)
+		index_check_constraint(heapRelation, relationDescs[i],
+							   tupleid, slot);
+
+	/*
 	 * for each index, form and insert the index tuple
 	 */
 	for (i = 0; i < numIndices; i++)
@@ -1132,6 +1140,8 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 		 */
 		IncrIndexInserted();
 	}
+
+	index_unset_current_insert();
 }
 
 /*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2c7d481..c575d18 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2058,6 +2058,7 @@ _copyIndexElem(IndexElem *from)
 	COPY_NODE_FIELD(opclass);
 	COPY_SCALAR_FIELD(ordering);
 	COPY_SCALAR_FIELD(nulls_ordering);
+	COPY_NODE_FIELD(constraint_oper);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c61de97..be8463f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2039,6 +2039,7 @@ _equalIndexElem(IndexElem *a, IndexElem *b)
 	COMPARE_NODE_FIELD(opclass);
 	COMPARE_SCALAR_FIELD(ordering);
 	COMPARE_SCALAR_FIELD(nulls_ordering);
+	COMPARE_NODE_FIELD(constraint_oper);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 49f74d7..a5f7e38 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1876,6 +1876,7 @@ _outIndexElem(StringInfo str, IndexElem *node)
 	WRITE_NODE_FIELD(opclass);
 	WRITE_ENUM_FIELD(ordering, SortByDir);
 	WRITE_ENUM_FIELD(nulls_ordering, SortByNulls);
+	WRITE_NODE_FIELD(constraint_oper);
 }
 
 static void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 20ab0ba..eb7d950 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -280,7 +280,7 @@ static TypeName *TableFuncTypeName(List *columns);
 				sort_clause opt_sort_clause sortby_list index_params
 				name_list from_clause from_list opt_array_bounds
 				qualified_name_list any_name any_name_list
-				any_operator expr_list attrs
+				any_operator expr_list attrs opt_index_constraint
 				target_list insert_column_list set_target_list
 				set_clause_list set_clause multiple_set_clause
 				ctext_expr_list ctext_row def_list indirection opt_indirection
@@ -4560,7 +4560,7 @@ index_params:	index_elem							{ $$ = list_make1($1); }
  * expressions in parens.  For backwards-compatibility reasons, we allow
  * an expression that's just a function call to be written without parens.
  */
-index_elem:	ColId opt_class opt_asc_desc opt_nulls_order
+index_elem:	ColId opt_class opt_asc_desc opt_nulls_order opt_index_constraint
 				{
 					$$ = makeNode(IndexElem);
 					$$->name = $1;
@@ -4568,8 +4568,9 @@ index_elem:	ColId opt_class opt_asc_desc opt_nulls_order
 					$$->opclass = $2;
 					$$->ordering = $3;
 					$$->nulls_ordering = $4;
+					$$->constraint_oper = $5;
 				}
-			| func_expr opt_class opt_asc_desc opt_nulls_order
+			| func_expr opt_class opt_asc_desc opt_nulls_order opt_index_constraint
 				{
 					$$ = makeNode(IndexElem);
 					$$->name = NULL;
@@ -4577,8 +4578,9 @@ index_elem:	ColId opt_class opt_asc_desc opt_nulls_order
 					$$->opclass = $2;
 					$$->ordering = $3;
 					$$->nulls_ordering = $4;
+					$$->constraint_oper = $5;
 				}
-			| '(' a_expr ')' opt_class opt_asc_desc opt_nulls_order
+			| '(' a_expr ')' opt_class opt_asc_desc opt_nulls_order opt_index_constraint
 				{
 					$$ = makeNode(IndexElem);
 					$$->name = NULL;
@@ -4586,6 +4588,7 @@ index_elem:	ColId opt_class opt_asc_desc opt_nulls_order
 					$$->opclass = $4;
 					$$->ordering = $5;
 					$$->nulls_ordering = $6;
+					$$->constraint_oper = $7;
 				}
 		;
 
@@ -4604,6 +4607,10 @@ opt_nulls_order: NULLS_FIRST				{ $$ = SORTBY_NULLS_FIRST; }
 			| /*EMPTY*/						{ $$ = SORTBY_NULLS_DEFAULT; }
 		;
 
+opt_index_constraint: CONSTRAINT any_operator		{ $$ = $2; }
+			| /*EMPTY*/								{ $$ = NULL; }
+		;
+
 
 /*****************************************************************************
  *
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 3022867..8c04940 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/clog.h"
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/nbtree.h"
@@ -115,6 +116,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 		size = add_size(size, BgWriterShmemSize());
 		size = add_size(size, AutoVacuumShmemSize());
 		size = add_size(size, BTreeShmemSize());
+		size = add_size(size, CurrentIndexInsertsShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
@@ -215,6 +217,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	 * Set up other modules that need some shared memory space
 	 */
 	BTreeShmemInit();
+	CurrentIndexInsertsShmemInit();
 	SyncScanShmemInit();
 
 #ifdef EXEC_BACKEND
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 29976e7..24583d3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3284,6 +3284,22 @@ RelationGetIndexAttrBitmap(Relation relation)
 	return indexattrs;
 }
 
+int2vector *
+RelationGetIndexConstraintStrategies(Relation relation)
+{
+	bool		isnull;
+	Datum		constraint_strategies;
+
+	constraint_strategies = heap_getattr(relation->rd_indextuple,
+										 Anum_pg_index_indconstrats,
+										 GetPgIndexDescriptor(),
+										 &isnull);
+	if (isnull)
+		return NULL;
+
+	return (int2vector *) DatumGetPointer(constraint_strategies);
+
+}
 
 /*
  *	load_relcache_init_file, write_relcache_init_file
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index a6ac5db..af4346b 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -16,6 +16,8 @@
 
 #include "access/sdir.h"
 #include "access/skey.h"
+#include "access/xact.h"
+#include "executor/tuptable.h"
 #include "nodes/tidbitmap.h"
 #include "storage/buf.h"
 #include "storage/lock.h"
@@ -129,6 +131,16 @@ extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
 				uint16 procnum);
 extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
 				  uint16 procnum);
+extern void index_set_current_insert(Oid relid, ItemPointer tid);
+extern void index_unset_current_insert(void);
+extern void index_check_constraint(Relation heapRelation,
+								   Relation indexRelation,
+								   ItemPointer tid,
+								   TupleTableSlot *slot);
+
+extern Size CurrentIndexInsertsShmemSize(void);
+extern void CurrentIndexInsertsShmemInit(void);
+extern void AtEOXact_IndexConstraints(void);
 
 /*
  * index access method support routines (in genam.c)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 7275641..bf14c3a 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -36,6 +36,7 @@ extern Oid index_create(Oid heapRelationId,
 			 Oid tableSpaceId,
 			 Oid *classObjectId,
 			 int16 *coloptions,
+			 StrategyNumber *constrats,
 			 Datum reloptions,
 			 bool isprimary,
 			 bool isconstraint,
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index eaa405f..413cf0a 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -477,6 +477,7 @@ DATA(insert ( 1259 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
 { 0, {"indclass"},			30, -1, -1, 11, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
 { 0, {"indoption"},			22, -1, -1, 12, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
 { 0, {"indexprs"},			25, -1, -1, 13, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
-{ 0, {"indpred"},			25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
+{ 0, {"indpred"},			25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 0, {"indconstrats"},		22, -1, -1, 15, 1, -1, -1, false, 'p', 'i', false, false, false, true, 0, { 0 } }
 
 #endif   /* PG_ATTRIBUTE_H */
diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h
index 19069db..9ab905c 100644
--- a/src/include/catalog/pg_index.h
+++ b/src/include/catalog/pg_index.h
@@ -49,6 +49,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS
 								 * each zero entry in indkey[] */
 	text		indpred;		/* expression tree for predicate, if a partial
 								 * index; else NULL */
+	int2vector	indconstrats;	/* index constraint strategies */
 } FormData_pg_index;
 
 /* ----------------
@@ -62,7 +63,7 @@ typedef FormData_pg_index *Form_pg_index;
  *		compiler constants for pg_index
  * ----------------
  */
-#define Natts_pg_index					14
+#define Natts_pg_index					15
 #define Anum_pg_index_indexrelid		1
 #define Anum_pg_index_indrelid			2
 #define Anum_pg_index_indnatts			3
@@ -77,6 +78,7 @@ typedef FormData_pg_index *Form_pg_index;
 #define Anum_pg_index_indoption			12
 #define Anum_pg_index_indexprs			13
 #define Anum_pg_index_indpred			14
+#define Anum_pg_index_indconstrats		15
 
 /*
  * Index AMs that support ordered scans must support these two indoption
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a108b80..acfcdc0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -501,6 +501,7 @@ typedef struct IndexElem
 	List	   *opclass;		/* name of desired opclass; NIL = default */
 	SortByDir	ordering;		/* ASC/DESC/default */
 	SortByNulls nulls_ordering; /* FIRST/LAST/default */
+	List	   *constraint_oper;	/* name of constraint operator */
 } IndexElem;
 
 /*
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index e389c61..9e6f93e 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -63,6 +63,7 @@ typedef enum LWLockId
 	TwoPhaseStateLock,
 	TablespaceCreateLock,
 	BtreeVacuumLock,
+	IndexConstraintLock,
 	AddinShmemInitLock,
 	AutovacuumLock,
 	AutovacuumScheduleLock,
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 7d4d914..305d1d2 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -43,6 +43,7 @@ extern Oid	RelationGetOidIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+extern int2vector *RelationGetIndexConstraintStrategies(Relation relation);
 
 extern void RelationSetIndexList(Relation relation,
 					 List *indexIds, Oid oidIndex);
#27Brendan Jurd
direvus@gmail.com
In reply to: Jeff Davis (#26)
Re: WIP: generalized index constraints

2009/7/15 Jeff Davis <pgsql@j-davis.com>:

Updated patch attached.

Changes:
 * Added syntax support:
    CREATE INDEX foo_idx ON foo ... (a CONSTRAINT =, b CONSTRAINT &&);
 * More aggressively clear the shared memory entries to avoid
  unnecessary checks
 * Code cleanup

TODO:
 * When adding constraint to table with data already in it, verify that
  existing data satisfies constraint.
 * Clean up error messages a little
 * Docs

Hi Jeff,

I've been assigned to do an initial review of your patch. Because
this patch is still WIP, there's not a lot for me to do. I see from
the thread that there are still some design questions that remain
open, but I won't be addressing those. The internals of indexes and
constraints is not an area of the code I have any particular insight
about.

The patch applies cleanly, builds successfully and passes regression
tests. I read through the code changes, and didn't notice any code
convention violations.

I had a play around with the feature in psql. I think the syntax is
okay, but using "ALTER TABLE ... ADD" as you mentioned upthread could
be a better option.

I noticed that there's no change to the output of \d in psql to show
the constraint, so when I do a \d on my test table, I can see that
there's a gist index there, but I can't tell that there is also a
constraint on it. This seems like a pretty significant shortcoming.
Essentially once you've created one of these index constraints, it
vanishes into the catalogs and becomes invisible to the user. This
might call for a modification of pg_get_indexdef()?

You've already noted this as a TODO, but clearly the error messages
need some serious help. If I deliberately violate an index constraint
I get:

ERROR: conflict detected 3

At minimum the message should use the term "constraint" and give the
name of the index that has detected the conflict. I think something
like this would be okay:

ERROR: new record violates constraint on index "circle_idx"

I also noticed that the patch does not include any changes or
additions to the regression test suite. Perhaps it ought to?

That's all the feedback I have for the moment. I hope you find my
comments constructive.

Cheers,
BJ

#28Jeff Davis
pgsql@j-davis.com
In reply to: Brendan Jurd (#27)
Re: WIP: generalized index constraints

On Thu, 2009-07-16 at 15:22 +1000, Brendan Jurd wrote:

I had a play around with the feature in psql. I think the syntax is
okay, but using "ALTER TABLE ... ADD" as you mentioned upthread could
be a better option.

Ok, I think we're pretty much settled on that option then.

Another idea that I thought about is that:

ALTER TABLE foo ADD UNIQUE (a, b) USING foo_idx;

could be a shorthand for:

ALTER TABLE foo ADD INDEX CONSTRAINT (a =, b =) USING foo_idx;

The benefit is that it could go over GiST indexes or hash indexes, not
just btrees. The syntax could also be useful to turn an existing btree
into a unique btree.

I noticed that there's no change to the output of \d in psql to show
the constraint, so when I do a \d on my test table, I can see that
there's a gist index there, but I can't tell that there is also a
constraint on it. This seems like a pretty significant shortcoming.
Essentially once you've created one of these index constraints, it
vanishes into the catalogs and becomes invisible to the user. This
might call for a modification of pg_get_indexdef()?

I agree, that's important. Psql support, regression tests, and docs are
all intertwined somewhat with the syntax, so I held off on that work
until I got a little feedback. I will get to work and see if I can put
together a more complete version in the next few days.

If you happen to have time, you can see if you can break my current
patch. I expect the basic algorithm to remain about the same for my next
version, so if you see any problems with that, please let me know. Also,
if you see any possible improvements that could make it useful for more
situations, that would be helpful, too.

But I think I have enough information to move forward, so if you want to
move on to a more complete patch, feel free.

Thanks for the review!

Regards,
Jeff Davis

#29Brendan Jurd
direvus@gmail.com
In reply to: Jeff Davis (#28)
Re: WIP: generalized index constraints

2009/7/17 Jeff Davis <pgsql@j-davis.com>:

Another idea that I thought about is that:

  ALTER TABLE foo ADD UNIQUE (a, b) USING foo_idx;

could be a shorthand for:

  ALTER TABLE foo ADD INDEX CONSTRAINT (a =, b =) USING foo_idx;

The benefit is that it could go over GiST indexes or hash indexes, not
just btrees. The syntax could also be useful to turn an existing btree
into a unique btree.

I like that idea ... although how would this interact (if at all) with
the existing pg_index.isunique flag? Would it become deprecated in
favour of using indconstrats, or would you actually look at switching
isunique to TRUE if somebody applies a constraint which is made up
entirely of equality ops?

Cheers,
BJ

#30Jeff Davis
pgsql@j-davis.com
In reply to: Brendan Jurd (#29)
Re: WIP: generalized index constraints

On Fri, 2009-07-17 at 09:51 +1000, Brendan Jurd wrote:

I like that idea ... although how would this interact (if at all) with
the existing pg_index.isunique flag? Would it become deprecated in
favour of using indconstrats, or would you actually look at switching
isunique to TRUE if somebody applies a constraint which is made up
entirely of equality ops?

If this ALTER TABLE ADD UNIQUE ... USING syntax is really shorthand for
my special index constraints, it would probably have to use the general
mechanism. Otherwise there would be no way to use the general mechanism
over a btree, which I think should be possible (if nothing else it would
be good to allow apples-to-apples performance testing of my patch).

But I guess it doesn't have to be directly shorthand, ALTER TABLE ADD
UNIQUE ... USING could choose to turn an existing index unique when
possible (e.g. btree), otherwise use the general mechanism.

Regards,
Jeff Davis

#31Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#1)
Re: WIP: generalized index constraints

I'm going to try to get this patch ready for the 9-15 commitfest. Here
are a few design updates:

(1) Language:

I think that the new language should be a table constraint, and I think
there's a consensus on that. The specific language I have in mind is:

CREATE TABLE (
...,
INDEX CONSTRAINT (<attname> <op>, ...) USING INDEX <indexname>
);

ALTER TABLE ADD INDEX CONSTRAINT (<attname> <op>, ...)
USING INDEX <indexname>;

Where <op> is the constraint operator. For example, if all <op>s are
"=" (or whatever the operator for BTEqualStragey is for that type), that
would be semantically identical to a UNIQUE constraint.

(2) Enforce while creating constraint:

To enforce the constraint while adding it to a table with existing data,
I'd treat it somewhat similar to a CHECK constraint: exclusive lock on
the table, and check every tuple.

Note that this is going to be O( n * log n ), assuming a search time of
O( log n ) on the index. A CHECK constraint is obviously just O(n).

(3) Catalog change:

I'll need to add a column pg_constraint.constrategies of type
int2vector. This will hold the strategy numbers of the constraint
operators. For instance, if there's a 2-column index constraint on a
BTree index, and both constraint operators are "=", it will be a vector
containing [BTEqualStrategy, BTEqualStrategy].

I will add a column pg_class.relindconstraints which will be similar to
relchecks (see below).

Also, introduce a new "contype 'i'" meaning that it's an index
constraint.

The patch relies on the existence of pg_constraint.conindid, which is
already committed for 8.5.

(4) Enforce on insert procedure:

This is mostly the same as the previous patch. However, I think I need
to avoid catalog lookups in the index insert path, which is a problem
Tom pointed out in the deferrable unique constraints discussion.

My plan is to make it a part of ResultRelInfo and initialize it in a way
similar to a CHECK constraint (in ExecRelCheck() if it's not already
initialized).

I would use relindconstraints to prevent going into that path during the
common case where there is no index constraint (in particular, when
bootstrapping).

Comments, suggestions, ideas?

Regards,
Jeff Davis

#32Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Jeff Davis (#31)
Re: WIP: generalized index constraints

Jeff Davis wrote:

I'm going to try to get this patch ready for the 9-15 commitfest. Here
are a few design updates:

(1) Language:

I think that the new language should be a table constraint, and I think
there's a consensus on that. The specific language I have in mind is:

CREATE TABLE (
...,
INDEX CONSTRAINT (<attname> <op>, ...) USING INDEX <indexname>
);

That sounds like the constraint is based on an existing index, but there
can't be any existing indexes on a table that hasn't been created yet.
If this creates the index, then the syntax needs to support specifying
index access method and an opclass for all the columns.

ALTER TABLE ADD INDEX CONSTRAINT (<attname> <op>, ...)
USING INDEX <indexname>;

Where <op> is the constraint operator. For example, if all <op>s are
"=" (or whatever the operator for BTEqualStragey is for that type), that
would be semantically identical to a UNIQUE constraint.

This makes more sense.

It would be nice to have syntax to create the index and constraint in
one command, so that the constraint can be checked while the index is
being created. Otherwise you need an extra heap scan to check it.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#33Jeff Davis
pgsql@j-davis.com
In reply to: Heikki Linnakangas (#32)
Re: WIP: generalized index constraints

On Thu, 2009-08-20 at 11:47 +0300, Heikki Linnakangas wrote:

That sounds like the constraint is based on an existing index, but there
can't be any existing indexes on a table that hasn't been created yet.
If this creates the index, then the syntax needs to support specifying
index access method and an opclass for all the columns.

Of course, thanks for pointing that out. To make it work at CREATE TABLE
time, the language would have to specify the index access method, and
the index name should be optional. Do you think it's worthwhile adjust
the syntax for that, or would it just bloat the CREATE TABLE syntax for
no reason?

I'm leaning toward not allowing it at CREATE TABLE time.

It would be nice to have syntax to create the index and constraint in
one command, so that the constraint can be checked while the index is
being created. Otherwise you need an extra heap scan to check it.

I can leave the old syntax in:

CREATE INDEX <indexname> ON <tablename> USING <method>
(a CONSTRAINT <op>, b CONSTRAINT <op>) ...;

and allow both.

However, I'm not sure if it's very easy to provide support for
concurrent index building. Should I block it, or is it worth
investigating further?

Regards,
Jeff Davis

#34Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Jeff Davis (#33)
Re: WIP: generalized index constraints

Jeff Davis wrote:

On Thu, 2009-08-20 at 11:47 +0300, Heikki Linnakangas wrote:

That sounds like the constraint is based on an existing index, but there
can't be any existing indexes on a table that hasn't been created yet.
If this creates the index, then the syntax needs to support specifying
index access method and an opclass for all the columns.

Of course, thanks for pointing that out. To make it work at CREATE TABLE
time, the language would have to specify the index access method, and
the index name should be optional. Do you think it's worthwhile adjust
the syntax for that, or would it just bloat the CREATE TABLE syntax for
no reason?

I'm leaning toward not allowing it at CREATE TABLE time.

Seems reasonable to me too.

However, I'm not sure if it's very easy to provide support for
concurrent index building. Should I block it, or is it worth
investigating further?

Dunno. It sure would be nice, but it's not a showstopper.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#35Brendan Jurd
direvus@gmail.com
In reply to: Heikki Linnakangas (#34)
Re: WIP: generalized index constraints

2009/8/21 Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>:

Jeff Davis wrote:

I'm leaning toward not allowing it at CREATE TABLE time.

Seems reasonable to me too.

+1

There are plenty of other things to do with tables that you can't mix
directly into a CREATE TABLE statement (grant permissions, create
triggers, change owner, to name a few) so this would not be a surprise
-- or a hardship -- for users IMO.

As an aside, Jeff, have you considered how this feature would interact
with CREATE TABLE ... LIKE parent_table [ { INCLUDING | EXCLUDING } {
DEFAULTS | CONSTRAINTS | INDEXES } ] ... }? What if someone asks to
include indexes but not constraints? Vice-versa? Will these cases be
handled gracefully?

Cheers,
BJ

#36Jeff Davis
pgsql@j-davis.com
In reply to: Brendan Jurd (#35)
Re: WIP: generalized index constraints

On Fri, 2009-08-21 at 11:14 +1000, Brendan Jurd wrote:

As an aside, Jeff, have you considered how this feature would interact
with CREATE TABLE ... LIKE parent_table [ { INCLUDING | EXCLUDING } {
DEFAULTS | CONSTRAINTS | INDEXES } ] ... }? What if someone asks to
include indexes but not constraints? Vice-versa? Will these cases be
handled gracefully?

I hadn't considered that yet, thanks for bringing it to my attention.

From the docs on CREATE TABLE (... LIKE ...):

"Not-null constraints are always copied to the new table. CHECK
constraints will only be copied if INCLUDING CONSTRAINTS is specified;
other types of constraints will never be copied."

If they include constraints and not indexes, nothing special.

If they include indexes and not constraints, I think we should follow
the same policy as unique constraints, and create the index and the
constraint.

The behavior seems a little strange to me, but that's the current
behavior for unique indexes.

Regards,
Jeff Davis

#37Brendan Jurd
direvus@gmail.com
In reply to: Jeff Davis (#36)
Re: WIP: generalized index constraints

2009/8/21 Jeff Davis <pgsql@j-davis.com>:

If they include indexes and not constraints, I think we should follow
the same policy as unique constraints, and create the index and the
constraint.

The behavior seems a little strange to me, but that's the current
behavior for unique indexes.

This may be an opportunity to fix it.

The current behaviour seems to be predicated on the unique constraint
being an integral part of the index itself. While this might be true
from a system catalog point of view (pg_index.indisunique), if a user
says that they want to copy a table's structure INCLUDING INDEXES
EXCLUDING CONSTRAINTS then IMO they've made their intention perfectly
clear. They'd expect it to create an index sans the unique
constraint. Ignoring the user's intention and copying the index as-is
(including the unique constraint) would be unfriendly.

Unless the SQL spec demands that we do so?

Cheers,
BJ

#38Greg Stark
gsstark@mit.edu
In reply to: Brendan Jurd (#37)
Re: WIP: generalized index constraints

On Fri, Aug 21, 2009 at 3:23 AM, Brendan Jurd<direvus@gmail.com> wrote:

They'd expect it to create an index sans the unique
constraint.  Ignoring the user's intention and copying the index as-is
(including the unique constraint) would be unfriendly.

Unless the SQL spec demands that we do so?

There are no indexes in the SQL spec.

--
greg
http://mit.edu/~gsstark/resume.pdf

#39Jeff Davis
pgsql@j-davis.com
In reply to: Brendan Jurd (#37)
Re: WIP: generalized index constraints

On Fri, 2009-08-21 at 12:23 +1000, Brendan Jurd wrote:

This may be an opportunity to fix it.

The current behaviour seems to be predicated on the unique constraint
being an integral part of the index itself. While this might be true
from a system catalog point of view (pg_index.indisunique), if a user
says that they want to copy a table's structure INCLUDING INDEXES
EXCLUDING CONSTRAINTS then IMO they've made their intention perfectly
clear. They'd expect it to create an index sans the unique
constraint. Ignoring the user's intention and copying the index as-is
(including the unique constraint) would be unfriendly.

I don't have strong feelings either way. I think that's probably a
separate patch, and a fairly small patch.

Using the principle of least surprise, if a user specified one of
INDEXES or CONSTRAINTS, but not both, and there is a unique index, we
should raise an ERROR (or at least a WARNING).

There is not much of a problem with backwards compatibility. LIKE is
shorthand (not stored in catalogs), so it doesn't affect
pg_dump/restore. And hopefully there aren't a lot of apps out there
creating tables dynamically using the LIKE syntax.

Regards,
Jeff Davis

#40Brendan Jurd
direvus@gmail.com
In reply to: Jeff Davis (#39)
Re: WIP: generalized index constraints

2009/8/21 Jeff Davis <pgsql@j-davis.com>:

On Fri, 2009-08-21 at 12:23 +1000, Brendan Jurd wrote:

The current behaviour seems to be predicated on the unique constraint
being an integral part of the index itself.  While this might be true
from a system catalog point of view (pg_index.indisunique), if a user
says that they want to copy a table's structure INCLUDING INDEXES
EXCLUDING CONSTRAINTS then IMO they've made their intention perfectly
clear.  They'd expect it to create an index sans the unique
constraint.  Ignoring the user's intention and copying the index as-is
(including the unique constraint) would be unfriendly.

I don't have strong feelings either way. I think that's probably a
separate patch, and a fairly small patch.

Yeah, as I was writing the above I was thinking that it might end up a
separate patch. However I was also concerned that it might be less
disruptive if we implement your patch with the less-astonishing
behaviour and fix the unique index case in passing, than to commit
your patch with the bad behavior and then fix both.

Up to you.

Using the principle of least surprise, if a user specified one of
INDEXES or CONSTRAINTS, but not both, and there is a unique index, we
should raise an ERROR (or at least a WARNING).

Actually for what it's worth I would expect INCLUDING INDEXES (with no
mention of what to do about constraints) to go ahead and include
constraints on indexes. I only have strong feelings where the user
has gone to the trouble of explicitly stating that they want indexes
but *not* constraints and we include the constraints anyway.

I would be fine with a NOTICE in the former case, so something like
this would be cool

# CREATE TABLE foo (LIKE bar INCLUDING INDEXES);
NOTICE: INCLUDING INDEXES will also include any constraints on those indexes.
HINT: Specify EXCLUDING CONSTRAINTS to omit them.

To my mind the severity is similar to such notices as "NOTICE: CREATE
TABLE / UNIQUE will create implicit index ...".

i.e., "this is probably what you wanted us to do, but just in case you
weren't expecting this side-effect, we're letting you know about it".

There is not much of a problem with backwards compatibility. LIKE is
shorthand (not stored in catalogs), so it doesn't affect
pg_dump/restore. And hopefully there aren't a lot of apps out there
creating tables dynamically using the LIKE syntax.

Well, it wouldn't surprise me if people are using LIKE to produce
tables for partitioning arrangements.

Cheers,
BJ

#41Dimitri Fontaine
dfontaine@hi-media.com
In reply to: Jeff Davis (#39)
Re: WIP: generalized index constraints

Hi,

Le 21 août 09 à 06:04, Jeff Davis a écrit :

There is not much of a problem with backwards compatibility. LIKE is
shorthand (not stored in catalogs), so it doesn't affect
pg_dump/restore. And hopefully there aren't a lot of apps out there
creating tables dynamically using the LIKE syntax.

I for one use this a lot, every time I'm doing partitioning. What I do
is a plpgsql function creating partitions for a given period
(create_parts(date, date) and default interval with
create_parts(date)), and the function will EXECUTE something like this:

CREATE TABLE schema.partition_YYYYMM (
LIKE schema.parent INCLUDING DEFAULTS INCLUDING INDEXES INCLUDING
CONSTRAINTS,
CHECK ( partition check expression )
)
INHERITS( schema.parent );

The reason to do this is that inherits won't care at all about the
indexes, defaults and constraints. The drawback to doing it this way
is the cheer number of NOTICEs you get back at inherits time when PG
is so verbose about finding that child already has all the parents
columns. From 8.3 onwards it's possible to trick the system though:

CREATE FUNCTION ... ()
RETURNS ...
LANGUAGE plpgsql
SET client_min_messages TO warning
AS $$
$$;

Regards,
--
dim

#42Alvaro Herrera
alvherre@commandprompt.com
In reply to: Brendan Jurd (#40)
Re: WIP: generalized index constraints

Brendan Jurd escribi�:

I would be fine with a NOTICE in the former case, so something like
this would be cool

# CREATE TABLE foo (LIKE bar INCLUDING INDEXES);
NOTICE: INCLUDING INDEXES will also include any constraints on those indexes.
HINT: Specify EXCLUDING CONSTRAINTS to omit them.

To my mind the severity is similar to such notices as "NOTICE: CREATE
TABLE / UNIQUE will create implicit index ...".

i.e., "this is probably what you wanted us to do, but just in case you
weren't expecting this side-effect, we're letting you know about it".

NOTICEs is what we do with index creation on primary key, unique
indexes, and sequences on serial columns, and I think they are seen as
just noise by everyone except novices. Do we want to add more?

Maybe they should be INFO, so that they are shown to the client but not
sent to the server log.

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

#43Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#42)
Re: WIP: generalized index constraints

Alvaro Herrera <alvherre@commandprompt.com> writes:

NOTICEs is what we do with index creation on primary key, unique
indexes, and sequences on serial columns, and I think they are seen as
just noise by everyone except novices. Do we want to add more?

Maybe they should be INFO, so that they are shown to the client but not
sent to the server log.

There was some discussion awhile back of creating a new NOVICE message
level. INFO strikes me as a completely bad idea here, because it would
actually make it harder for non-novices to suppress the messages.

regards, tom lane

#44David Fetter
david@fetter.org
In reply to: Brendan Jurd (#37)
Re: WIP: generalized index constraints

On Fri, Aug 21, 2009 at 12:23:15PM +1000, Brendan Jurd wrote:

2009/8/21 Jeff Davis <pgsql@j-davis.com>:

If they include indexes and not constraints, I think we should
follow the same policy as unique constraints, and create the index
and the constraint.

The behavior seems a little strange to me, but that's the current
behavior for unique indexes.

This may be an opportunity to fix it.

The current behaviour seems to be predicated on the unique
constraint being an integral part of the index itself. While this
might be true from a system catalog point of view
(pg_index.indisunique), if a user says that they want to copy a
table's structure INCLUDING INDEXES EXCLUDING CONSTRAINTS then IMO
they've made their intention perfectly clear. They'd expect it to
create an index sans the unique constraint. Ignoring the user's
intention and copying the index as-is (including the unique
constraint) would be unfriendly.

Unless the SQL spec demands that we do so?

SQL:2008, like its predecessors, does not mention indexing at all.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#45Brendan Jurd
direvus@gmail.com
In reply to: Brendan Jurd (#40)
Re: WIP: generalized index constraints

2009/8/21 Brendan Jurd <direvus@gmail.com>:

2009/8/21 Jeff Davis <pgsql@j-davis.com>:

On Fri, 2009-08-21 at 12:23 +1000, Brendan Jurd wrote:

The current behaviour seems to be predicated on the unique constraint
being an integral part of the index itself.  While this might be true
from a system catalog point of view (pg_index.indisunique), if a user
says that they want to copy a table's structure INCLUDING INDEXES
EXCLUDING CONSTRAINTS then IMO they've made their intention perfectly
clear.  They'd expect it to create an index sans the unique
constraint.  Ignoring the user's intention and copying the index as-is
(including the unique constraint) would be unfriendly.

I don't have strong feelings either way. I think that's probably a
separate patch, and a fairly small patch.

Yeah, as I was writing the above I was thinking that it might end up a
separate patch.  However I was also concerned that it might be less
disruptive if we implement your patch with the less-astonishing
behaviour and fix the unique index case in passing, than to commit
your patch with the bad behavior and then fix both.

Up to you.

Hi Jeff,

Any update on this patch? The discussion appeared to trail off around
21 Aug with some inconclusive thoughts about handling the corner cases
in CREATE TABLE LIKE.

The September CF starts in a couple of days, so this patch is in
danger of missing the boat.

The unresolved points seem to be:

* What to do about INCLUDING INDEXES EXCLUDING CONSTRAINTS --
Postgres gets this wrong for unique indexes currently. Should we
persist with the existing behaviour or fix it as part of this patch?
My personal feeling was +1 for fixing it in this patch.

* Should we emit some sort of message when the user specifies
INCLUDING INDEXES or INCLUDING CONSTRAINTS but not both? I didn't
have strong feelings about this one but there was some differing
thoughts about what log level to use. I thought NOTICE but Alvaro
reckons we've got too many of those already. Tom mentioned the
suggested (but unimplemented) NOVICE level, which seems like a good
move but doesn't resolve the problem of what to do in this patch. One
option would be to add a message at the NOTICE level with a TODO to
downgrade it to NOVICE if/when that becomes available.

Cheers,
BJ

#46Jeff Davis
pgsql@j-davis.com
In reply to: Brendan Jurd (#45)
Re: WIP: generalized index constraints

On Sun, 2009-09-13 at 19:08 +1000, Brendan Jurd wrote:

The September CF starts in a couple of days, so this patch is in
danger of missing the boat.

Thanks for keeping track. I accomplished a significant amount today, so
there's still hope for 9/15.

I will most likely just focus on the core functionality so that I have
something complete and reviewable.

The unresolved points seem to be:

* What to do about INCLUDING INDEXES EXCLUDING CONSTRAINTS --
Postgres gets this wrong for unique indexes currently. Should we
persist with the existing behaviour or fix it as part of this patch?
My personal feeling was +1 for fixing it in this patch.

I don't think that it should make a difference whether "EXCLUDING
CONSTRAINTS" is specified or omitted. There is no "[INCLUDING|EXCLUDING]
CONSTRAINTS" option in the standard, but for the other LIKE options,
EXCLUDING is implied when INCLUDING is not specified.

So, I think we have to make a decision:
1. If INCLUDING CONSTRAINTS is specified, but not INCLUDING INDEXES,
do we: copy the indexes silently; or emit a nice message; or throw
an ERROR?
2. What if INCLUDING INDEXES is specified, but not INCLUDING
CONSTRAINTS?

* Should we emit some sort of message when the user specifies
INCLUDING INDEXES or INCLUDING CONSTRAINTS but not both? I didn't
have strong feelings about this one but there was some differing
thoughts about what log level to use. I thought NOTICE but Alvaro
reckons we've got too many of those already. Tom mentioned the
suggested (but unimplemented) NOVICE level, which seems like a good
move but doesn't resolve the problem of what to do in this patch. One
option would be to add a message at the NOTICE level with a TODO to
downgrade it to NOVICE if/when that becomes available.

I don't think either of these things are a huge amount of work; they are
mostly just decisions that need to be made. I'll start off implementing
whatever is easiest/cleanest, and we'll continue the discussion.

Regards,
Jeff Davis

#47Jeff Davis
pgsql@j-davis.com
In reply to: Brendan Jurd (#45)
1 attachment(s)
Re: WIP: generalized index constraints

On Sun, 2009-09-13 at 19:08 +1000, Brendan Jurd wrote:

Any update on this patch?

Attached is the latest version.

Changes:

* Merged with HEAD
* Changed from storing the information in pg_index to pg_constraint.
This required rewriting a large portion of the patch, so it's not a
clean diff from the previous patch.
* Implemented the language using ALTER TABLE to add the constraint, as
discussed (with a slight change to avoid the extra "INDEX" keyword).
* Added docs
* Added tests
* All relations with relindconstraints == 0 do not pass through the
index constraints enforcement code at all. I did this a little
differently than what I laid out in the design, but the idea is the
same and it should avoid any problems.

That's the good news. The bad news:

* No pg_dump support yet (shouldn't be too hard)
* Creating a new constraint does not check the existing data for
conflicts.
* Doesn't work like the new deferrable unique constraints yet (also
shouldn't be too hard).
* I didn't make any changes to the behavior of LIKE (also not hard).
* Can't be specified at CREATE INDEX time. I don't think this is a
showstopper, and it will take some significant effort. The advantage
of allowing this is that the constraints can be checked more quickly
(in theory) while building the index, and it also might be handy
shorthand. However, it suffers from a number of problems:
1. Extra syntax that is almost entirely redundant.
2. More work.
3. The performance gains will probably be pretty marginal. We have
to do N index scans anyway; the savings would only be due to
the better caching impact and the fact that the index in the
process of being built is smaller than an already-built index.
So, right now I'm not in a hurry to fix this last point.

I realize that some of the things missing make the patch uncomittable in
its current form. However, I would still appreciate a review of what I
have ready.

Regards,
Jeff Davis

Attachments:

generalized-index-constraints-20090915.patch.gzapplication/x-gzip; name=generalized-index-constraints-20090915.patch.gzDownload
#48Brendan Jurd
direvus@gmail.com
In reply to: Jeff Davis (#47)
Re: WIP: generalized index constraints

2009/9/15 Jeff Davis <pgsql@j-davis.com>:

Attached is the latest version.

Hi Jeff,

I'm just getting started reviewing this version now. I noticed that
your patch seems to have been generated by git. Are you hosting this
work on a public repo somewhere that I can pull from? Also I think
the committers generally prefer context diffs (pipe it through
"filterdiff --format=context --strip=1") in submissions.

Regarding the documentation updates, I think you might want to add
some commentary to Chapter 11: Indexes -- perhaps add a new section
after 11.6 Unique Indexes to talk about general index constraints,
and/or update the wording of 11.6 to reflect your changes.

The paragraph explaining index_constraints in ALTER TABLE may be a
little bit confusing.

<quote>
ADD index_constraint

This form adds a new index constraint to the table which is
enforced by the given index. The operator provided must be usable as a
search strategy for the index, and it also must detect conflicts
symmetrically. The semantics are similar to a unique index but it
opens up new possibilities. A unique index can only detect conflicts
when the two values are equal, and that behavior is equivalent to an
index constraint where all operators are the equality operator.

However, an index constraint allows you to use other operators as
well, such as the overlaps operator provided for the circle data type.
The index constraint will ensure that no two circles overlap. See
example below.
</quote>

My eyes started to cross in the second sentence. "Detect conflicts
symmetrically"? I have actually *used* this feature successfully in
testing the patch, and I still don't know quite what to make of that
phrase. You might need to dumb it down.

It might also be good to be a bit more explicit about the way the
choice of operators works. It is the inverse of the logic used to
express an ordinary value constraint. E.g., when you use the equality
operator in an index constraint you are in effect saying that new rows
MUST NOT satisfy this operator for any existing rows.

I'll continue reviewing and post more comments on the code itself shortly.

Cheers,
BJ

#49Brendan Jurd
direvus@gmail.com
In reply to: Jeff Davis (#47)
Re: WIP: generalized index constraints

2009/9/15 Jeff Davis <pgsql@j-davis.com>:

Attached is the latest version.

The new error message for a conflict is:

ERROR: index constraint violation detected
DETAIL: tuple conflicts with existing data

How about also including the name of the constraint (or index) that
was violated? I could imagine this error message being frustrating
for someone who had a table with multiple index constraints, as they
wouldn't know which one had raised the conflict.

Also, the DETAIL part should be written as a full sentence with
leading capital and full stop [1]http://www.postgresql.org/docs/current/static/error-style-guide.html, see

I deliberately tried to create an index constraint using a bogus
operator, to see what would happen:

postgres=# alter table circles add constraint circles_overlap (c <->)
using index circle_idx;
ERROR: no strategy found for operator 1520 in operator family 2595

The error message is pretty unfriendly, but I'm ambivalent about
whether it's worth doing anything about this particular case.

One of the comments I made in my original review [2]http://archives.postgresql.org/message-id/37ed240d0907152222w7ccfc13i8ce8d11a0c517e8@mail.gmail.com was that "\d" in
psql should show the constraint. I don't think you've addressed this
in the current version.

Cheers,
BJ

[1]: http://www.postgresql.org/docs/current/static/error-style-guide.html
[2]: http://archives.postgresql.org/message-id/37ed240d0907152222w7ccfc13i8ce8d11a0c517e8@mail.gmail.com

#50Joshua Tolley
eggyknap@gmail.com
In reply to: Brendan Jurd (#49)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 11:21:14PM +1000, Brendan Jurd wrote:

2009/9/15 Jeff Davis <pgsql@j-davis.com>:

Attached is the latest version.

The new error message for a conflict is:

ERROR: index constraint violation detected
DETAIL: tuple conflicts with existing data

How about also including the name of the constraint (or index) that
was violated? I could imagine this error message being frustrating
for someone who had a table with multiple index constraints, as they
wouldn't know which one had raised the conflict.

Perhaps the tuple that caused the violation as well, like UNIQUE index
violations already do? Even if we know what constraint has been tripped, we
might not know what value did it.

josh@josh# create table a (a integer);
josh@josh*# create unique index a_unique on a (a);
josh@josh*# insert into a values (1), (2), (3);
josh@josh*# insert into a values (8), (3), (4);
ERROR: duplicate key value violates unique constraint "a_unique"
DETAIL: Key (a)=(3) already exists.

--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com

#51Jeff Davis
pgsql@j-davis.com
In reply to: Brendan Jurd (#48)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 22:52 +1000, Brendan Jurd wrote:

I'm just getting started reviewing this version now. I noticed that
your patch seems to have been generated by git. Are you hosting this
work on a public repo somewhere that I can pull from?

I just requested a public repo. I will publish there as soon as its
approved.

Also I think
the committers generally prefer context diffs (pipe it through
"filterdiff --format=context --strip=1") in submissions.

Thanks, I will do that for my future patch submissions.

Regarding the documentation updates, I think you might want to add
some commentary to Chapter 11: Indexes -- perhaps add a new section
after 11.6 Unique Indexes to talk about general index constraints,
and/or update the wording of 11.6 to reflect your changes.

Will do.

My eyes started to cross in the second sentence. "Detect conflicts
symmetrically"? I have actually *used* this feature successfully in
testing the patch, and I still don't know quite what to make of that
phrase. You might need to dumb it down.

Will do.

It might also be good to be a bit more explicit about the way the
choice of operators works. It is the inverse of the logic used to
express an ordinary value constraint. E.g., when you use the equality
operator in an index constraint you are in effect saying that new rows
MUST NOT satisfy this operator for any existing rows.

I'll include that, thanks.

I appreciate the quick feedback; I'll make these changes tonight.

Regards,
Jeff Davis

#52Jeff Davis
pgsql@j-davis.com
In reply to: Brendan Jurd (#49)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 23:21 +1000, Brendan Jurd wrote:

How about also including the name of the constraint (or index) that
was violated? I could imagine this error message being frustrating
for someone who had a table with multiple index constraints, as they
wouldn't know which one had raised the conflict.

Yes, that makes sense. As Joshua Tolley mentions, I'll also include the
tuples that caused the conflict.

Also, the DETAIL part should be written as a full sentence with
leading capital and full stop [1], see

Oh, I haven't seen that document before. Thanks.

postgres=# alter table circles add constraint circles_overlap (c <->)
using index circle_idx;
ERROR: no strategy found for operator 1520 in operator family 2595

The error message is pretty unfriendly, but I'm ambivalent about
whether it's worth doing anything about this particular case.

I think I could make that error a little better by providing a detail
message explaining what the operator should be able to do.

One of the comments I made in my original review [2] was that "\d" in
psql should show the constraint. I don't think you've addressed this
in the current version.

I have psql on my list along with pg_dump, but unfortunately I haven't
done either yet. I don't think it will take too much work, so I'll fix
this as soon as I can.

Regards,
Jeff Davis

#53Jeff Davis
pgsql@j-davis.com
In reply to: Joshua Tolley (#50)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 08:08 -0600, Joshua Tolley wrote:

Perhaps the tuple that caused the violation as well, like UNIQUE index
violations already do? Even if we know what constraint has been tripped, we
might not know what value did it.

Or, even better, include both tuples. With these new constraints the
conflicting tuples aren't necessarily equal.

Regards,
Jeff Davis

#54Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#51)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 12:18 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Tue, 2009-09-15 at 22:52 +1000, Brendan Jurd wrote:

I'm just getting started reviewing this version now.  I noticed that
your patch seems to have been generated by git.  Are you hosting this
work on a public repo somewhere that I can pull from?

I just requested a public repo. I will publish there as soon as its
approved.

Also I think
the committers generally prefer context diffs (pipe it through
"filterdiff --format=context --strip=1") in submissions.

Thanks, I will do that for my future patch submissions.

Regarding the documentation updates, I think you might want to add
some commentary to Chapter 11: Indexes -- perhaps add a new section
after 11.6 Unique Indexes to talk about general index constraints,
and/or update the wording of 11.6 to reflect your changes.

Will do.

My eyes started to cross in the second sentence.  "Detect conflicts
symmetrically"?  I have actually *used* this feature successfully in
testing the patch, and I still don't know quite what to make of that
phrase.  You might need to dumb it down.

Will do.

It might also be good to be a bit more explicit about the way the
choice of operators works.  It is the inverse of the logic used to
express an ordinary value constraint.  E.g., when you use the equality
operator in an index constraint you are in effect saying that new rows
MUST NOT satisfy this operator for any existing rows.

I'll include that, thanks.

I appreciate the quick feedback; I'll make these changes tonight.

Instead of calling these generalized index constraints, I wonder if we
oughtn't to be calling them something like "don't-overlap constraints"
(that's a bad name, but something along those lines). They're not
really general at all, except compared to uniqueness constraints (and
they aren't called generalized unique-index constraints, just
generalized index constraints).

I didn't realize understand what this was all for until I read Brendan's review.

...Robert

#55Brendan Jurd
direvus@gmail.com
In reply to: Robert Haas (#54)
Re: WIP: generalized index constraints

2009/9/16 Robert Haas <robertmhaas@gmail.com>:

Instead of calling these generalized index constraints, I wonder if we
oughtn't to be calling them something like "don't-overlap constraints"
(that's a bad name, but something along those lines).  They're not
really general at all, except compared to uniqueness constraints (and
they aren't called generalized unique-index constraints, just
generalized index constraints).

Well "generalized index constraints" is what we're calling the patch,
but I don't think they are called by that name anywhere in the
proposed documentation changes. In the extension to ALTER TABLE
syntax, they are simply referred to as "index_constraint".

Cheers,
BJ

#56Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#54)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 12:37 -0400, Robert Haas wrote:

Instead of calling these generalized index constraints, I wonder if we
oughtn't to be calling them something like "don't-overlap constraints"
(that's a bad name, but something along those lines). They're not
really general at all, except compared to uniqueness constraints (and
they aren't called generalized unique-index constraints, just
generalized index constraints).

What would you like to be able to enforce using an index that can't be
solved by this patch? It only works for constraints entirely within a
single table, can you think of a way to express that better?

In the code/docs, mostly I call them just "index constraints" or some
variation thereof. But for the lists, I think that might be too vague.

I don't want to call them "don't overlap constraints", because it's not
limited to a non-overlapping constraint. I also don't think "generalized
unique-index constraints" is a good name: it's confusing and it makes it
sound like it is some new way to use a unique index.

Regards,
Jeff Davis

#57Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#56)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 12:54 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Tue, 2009-09-15 at 12:37 -0400, Robert Haas wrote:

Instead of calling these generalized index constraints, I wonder if we
oughtn't to be calling them something like "don't-overlap constraints"
(that's a bad name, but something along those lines).  They're not
really general at all, except compared to uniqueness constraints (and
they aren't called generalized unique-index constraints, just
generalized index constraints).

What would you like to be able to enforce using an index that can't be
solved by this patch? It only works for constraints entirely within a
single table, can you think of a way to express that better?

In the code/docs, mostly I call them just "index constraints" or some
variation thereof. But for the lists, I think that might be too vague.

I don't want to call them "don't overlap constraints", because it's not
limited to a non-overlapping constraint.

Oh. What else can you do with it?

I also don't think "generalized
unique-index constraints" is a good name: it's confusing and it makes it
sound like it is some new way to use a unique index.

I agree.

...Robert

#58Brendan Jurd
direvus@gmail.com
In reply to: Robert Haas (#57)
Re: WIP: generalized index constraints

2009/9/16 Robert Haas <robertmhaas@gmail.com>:

On Tue, Sep 15, 2009 at 12:54 PM, Jeff Davis <pgsql@j-davis.com> wrote:

I don't want to call them "don't overlap constraints", because it's not
limited to a non-overlapping constraint.

Oh.  What else can you do with it?

Anything that there is an operator for.

Cheers,
BJ

#59Robert Haas
robertmhaas@gmail.com
In reply to: Brendan Jurd (#58)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 1:14 PM, Brendan Jurd <direvus@gmail.com> wrote:

2009/9/16 Robert Haas <robertmhaas@gmail.com>:

On Tue, Sep 15, 2009 at 12:54 PM, Jeff Davis <pgsql@j-davis.com> wrote:

I don't want to call them "don't overlap constraints", because it's not
limited to a non-overlapping constraint.

Oh.  What else can you do with it?

Anything that there is an operator for.

Uhh.... so what happens if I create an index constraint using the
+(integer, integer) operator?

...Robert

#60Brendan Jurd
direvus@gmail.com
In reply to: Robert Haas (#59)
Re: WIP: generalized index constraints

2009/9/16 Robert Haas <robertmhaas@gmail.com>:

On Tue, Sep 15, 2009 at 1:14 PM, Brendan Jurd <direvus@gmail.com> wrote:

2009/9/16 Robert Haas <robertmhaas@gmail.com>:

On Tue, Sep 15, 2009 at 12:54 PM, Jeff Davis <pgsql@j-davis.com> wrote:

I don't want to call them "don't overlap constraints", because it's not
limited to a non-overlapping constraint.

Oh.  What else can you do with it?

Anything that there is an operator for.

Uhh.... so what happens if I create an index constraint using the
+(integer, integer) operator?

Okay, so my first answer was a simplification. You can use any
operator that has an appropriate index strategy entry.

#61Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#59)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 13:16 -0400, Robert Haas wrote:

Uhh.... so what happens if I create an index constraint using the
+(integer, integer) operator?

You can use any operator that has an index search strategy. Overlaps is
probably the most useful, but you could imagine other operators, like a
bi-directional containment operator (either LHS is contained in RHS, or
vice-versa).

You can also get creative and have a "similarity" operator that
determines whether two tuples are "too similar". As long as it is
symmetric, the feature will work.

Or just use wrap random() in an operator and see what happens ;)

Regards,
Jeff Davis

#62Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#61)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 1:28 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Tue, 2009-09-15 at 13:16 -0400, Robert Haas wrote:

Uhh.... so what happens if I create an index constraint using the
+(integer, integer) operator?

You can use any operator that has an index search strategy. Overlaps is
probably the most useful, but you could imagine other operators, like a
bi-directional containment operator (either LHS is contained in RHS, or
vice-versa).

You can also get creative and have a "similarity" operator that
determines whether two tuples are "too similar". As long as it is
symmetric, the feature will work.

So it allows us to create constraints of the following form?

For all A in the index, there exists no B in the index such that the
given operator (which must be a binary operator returning boolean)
holds of A and B.

If that's correct, I think we should definitely at least mention the
word "overlap" somewhere in the documentation, because that's what
people are going to want to use it for, and it's hard to conceptualize
without examples, at least for me. You may already be doing this, I
haven't read the patch.

Also, there are certainly other things you could want to do that can't
be handled by this approach. Perhaps you'd like to create a
constraint that a given value can appear at most twice, or a two
column index (A, B) such that for any A the smallest value of B is
less than A. These are certainly less common requirements than what
you're talking about here, and I don't think it's important to try to
support them - at least not at this point - but the word "generalized"
doesn't give me a clue that I won't be able to do those things but I
will be able to make an index that prevents my users from handing out
duplicate IP blocks.

...Robert

#63Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#62)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 13:48 -0400, Robert Haas wrote:

So it allows us to create constraints of the following form?

For all A in the index, there exists no B in the index such that the
given operator (which must be a binary operator returning boolean)
holds of A and B.

Yes. And it's slightly more complicated for multi-column constraints:

For all tuples A in the index with attributes 1 to N, there exists no
tuple B such that:
A1 op1 B1 AND
A2 op2 B2 AND
...
AN op2 BN

If all operators are "=", and the index implements searching on
equality, it's semantically equivalent to a unique index.

If that's correct, I think we should definitely at least mention the
word "overlap" somewhere in the documentation, because that's what
people are going to want to use it for, and it's hard to conceptualize
without examples, at least for me. You may already be doing this, I
haven't read the patch.

My current example uses "overlaps", but I will expand the documentation
to provide more clarity.

Also, there are certainly other things you could want to do that can't
be handled by this approach. Perhaps you'd like to create a
constraint that a given value can appear at most twice, or a two
column index (A, B) such that for any A the smallest value of B is
less than A.

The first is a good example, and actually I think that could be an
add-on to my patch without much difficulty.

The second can't be enforced with an index in nearly the same way
because deleting a tuple could violate the constraint. Also, it seems
like it would be hard to express that kind of constraint. But I agree
that, in principle, it is an index-enforceable constraint.

These are certainly less common requirements than what
you're talking about here, and I don't think it's important to try to
support them - at least not at this point - but the word "generalized"
doesn't give me a clue that I won't be able to do those things but I
will be able to make an index that prevents my users from handing out
duplicate IP blocks.

As far as the name goes, the best I've got so far are "index
constraints" and "generalized index constraints". I'm happy to change
the name if you have a reasonable suggestion.

I don't think the word "generalized" implies that it can do absolutely
anything possible. For the list discussion, I think it's appropriate to
use the term "generalized", because my patch generalizes index
constraints. However, I agree that we shouldn't use that too much in the
code/docs because someone might think of something more general later.

Regards,
Jeff Davis

#64Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#61)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Tue, 2009-09-15 at 13:16 -0400, Robert Haas wrote:

Uhh.... so what happens if I create an index constraint using the
+(integer, integer) operator?

You can use any operator that has an index search strategy. Overlaps is
probably the most useful, but you could imagine other operators, like a
bi-directional containment operator (either LHS is contained in RHS, or
vice-versa).

Does it behave sanely for operators that are non-commutative, such
as '>'? (I'm not even very sure that I know what "sanely" would be
in such a case.)

regards, tom lane

#65David Fetter
david@fetter.org
In reply to: Jeff Davis (#63)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 11:31:48AM -0700, Jeff Davis wrote:

On Tue, 2009-09-15 at 13:48 -0400, Robert Haas wrote:

So it allows us to create constraints of the following form?

For all A in the index, there exists no B in the index such that the
given operator (which must be a binary operator returning boolean)
holds of A and B.

Yes. And it's slightly more complicated for multi-column constraints:

For all tuples A in the index with attributes 1 to N, there exists no
tuple B such that:
A1 op1 B1 AND
A2 op2 B2 AND
...
AN op2 BN

If all operators are "=", and the index implements searching on
equality, it's semantically equivalent to a unique index.

Interesting :) I take it op1..opN (it's opN, not op2, right?) need to
commute?

These are certainly less common requirements than what you're
talking about here, and I don't think it's important to try to
support them - at least not at this point - but the word
"generalized" doesn't give me a clue that I won't be able to do
those things but I will be able to make an index that prevents my
users from handing out duplicate IP blocks.

As far as the name goes, the best I've got so far are "index
constraints" and "generalized index constraints". I'm happy to change
the name if you have a reasonable suggestion.

Here's a couple:

* "generalized-uniqueness constraints"
the hyphen disambiguates

* "operator-based constraints"
A little math-ier, but talks about the API rather than details of
the server implementation.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#66Robert Haas
robertmhaas@gmail.com
In reply to: David Fetter (#65)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 3:03 PM, David Fetter <david@fetter.org> wrote:

* "operator-based constraints"
   A little math-ier, but talks about the API rather than details of
   the server implementation.

Or operator-exclusion constraints? Operator-based exclusion constraints?

I'm feeling exclusive.

...Robert

#67Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#64)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 14:49 -0400, Tom Lane wrote:

Does it behave sanely for operators that are non-commutative, such
as '>'? (I'm not even very sure that I know what "sanely" would be
in such a case.)

One of the requirements is commutativity (I called it "symmetry" in the
docs, for some reason, I will change that).

I haven't explored in too much detail, but using "x >" (or maybe its
"<" ?) would basically mean that you can only insert increasing values
of x. There most likely be some serious problems there, for instance, if
you HOT update an old tuple's "y" attribute, everything is fine; if you
cold update it you would get an error.

Not exactly intuitive, but if you have lots of other requirements about
how things are updated, then I suppose it might be useful to someone.

If you try it, my current patch won't stop you. Maybe I should detect
the fact that the commutator of an operator is not the operator itself,
and throw an ERROR? Probably would be a good idea.

Regards,
Jeff Davis

#68Jeff Davis
pgsql@j-davis.com
In reply to: David Fetter (#65)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 12:03 -0700, David Fetter wrote:

Interesting :) I take it op1..opN (it's opN, not op2, right?) need to
commute?

Yeah, it's opN.

And they should commute, but my current patch won't stop you. I think I
should stop that though, it's pretty difficult to think of a good
use-case for that and there is all kinds of danger.

* "generalized-uniqueness constraints"
the hyphen disambiguates

I don't like using the word "unique" in the description, I think it only
adds to the confusion.

* "operator-based constraints"
A little math-ier, but talks about the API rather than details of
the server implementation.

I like this much better. Maybe "index operator constraints" or "operator
index constraints"?

Regards,
Jeff Davis

#69Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#67)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Tue, 2009-09-15 at 14:49 -0400, Tom Lane wrote:

Does it behave sanely for operators that are non-commutative, such
as '>'? (I'm not even very sure that I know what "sanely" would be
in such a case.)

If you try it, my current patch won't stop you. Maybe I should detect
the fact that the commutator of an operator is not the operator itself,
and throw an ERROR? Probably would be a good idea.

+1. Otherwise people *will* try it, and then send us bug reports when
it doesn't behave sanely.

regards, tom lane

#70David Fetter
david@fetter.org
In reply to: Jeff Davis (#68)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 12:22:46PM -0700, Jeff Davis wrote:

On Tue, 2009-09-15 at 12:03 -0700, David Fetter wrote:

* "operator-based constraints"
A little math-ier, but talks about the API rather than details of
the server implementation.

I like this much better. Maybe "index operator constraints" or "operator
index constraints"?

The word, "index" goes to implementation details, which may change.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#71Jeff Davis
pgsql@j-davis.com
In reply to: David Fetter (#70)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 12:49 -0700, David Fetter wrote:

I like this much better. Maybe "index operator constraints" or "operator
index constraints"?

The word, "index" goes to implementation details, which may change.

Ok, let's vote on a name then:

operator constraints
operator exclusion constraints
operator conflict constraints
conflict operator constraints
operator index constraints
index constraints
generalized index constraints
something else?

Right now, I like "conflict operator constraints" for the long name
(e.g. feature title, long description in docs), and "operator
constraints" for short (e.g. in the code and some places in the docs).

Regards,
Jeff Davis

#72Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#71)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 14:42 -0700, Jeff Davis wrote:

operator constraints
operator exclusion constraints
operator conflict constraints
conflict operator constraints
operator index constraints
index constraints
generalized index constraints
something else?

Just to add a couple more permutations of Robert Haas's suggestions:

exclusion operator constraints
exclusive operator constraints

I also like those.

I think that using the word "operator" first makes it sound like the
operator is the thing being excluded, and adding "-based" makes it more
clear but it is too verbose.

Regards,
Jeff Davis

#73Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#72)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Tue, 2009-09-15 at 14:42 -0700, Jeff Davis wrote:

operator constraints
operator exclusion constraints
operator conflict constraints
conflict operator constraints
operator index constraints
index constraints
generalized index constraints
something else?

Just to add a couple more permutations of Robert Haas's suggestions:

exclusion operator constraints
exclusive operator constraints

To my ear, "operator exclusion constraints" or "exclusive operator
constraints" seem reasonable; the other permutations of that phrase
simply aren't good English.

I'm not tremendously happy with any of them though...

regards, tom lane

#74Joshua Tolley
eggyknap@gmail.com
In reply to: Tom Lane (#73)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 05:52:35PM -0400, Tom Lane wrote:

Jeff Davis <pgsql@j-davis.com> writes:

On Tue, 2009-09-15 at 14:42 -0700, Jeff Davis wrote:

operator constraints
operator exclusion constraints
operator conflict constraints
conflict operator constraints
operator index constraints
index constraints
generalized index constraints
something else?

Just to add a couple more permutations of Robert Haas's suggestions:

exclusion operator constraints
exclusive operator constraints

To my ear, "operator exclusion constraints" or "exclusive operator
constraints" seem reasonable; the other permutations of that phrase
simply aren't good English.

I was having a hard time coming up with a name that was adequately
short-and-sweet, and still conveyed the idea of both "operator" and "index",
which seems important so as to designate between these and the constraints
we've had all along. Perhaps "indexed operator constraints"?

--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com

#75Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#54)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 12:37 -0400, Robert Haas wrote:

Instead of calling these generalized index constraints, I wonder if we
oughtn't to be calling them something like "don't-overlap constraints"
(that's a bad name, but something along those lines). They're not
really general at all, except compared to uniqueness constraints (and
they aren't called generalized unique-index constraints, just
generalized index constraints).

What they should be called is generalized unique constraints, without
reference to "index". Because what they generalize is the operator by
which uniqueness is determined.

Don't all of these have the same effect at the end?

CREATE TABLE data (a int UNIQUE);

CREATE TABLE data (a int);
CREATE UNIQUE INDEX data_a_idx ON data USING btree (a);

CREATE TABLE data (a int);
CREATE INDEX data_a_idx ON data USING btree (a);
ALTER TABLE data ADD CONSTRAINT a_unique (a =) USING INDEX data_a_idx;

Which brings me to two concerns about the syntax.

First, we have so far been fairly consistent to document that unique
indexes are an implementation detail of unique constraints and should
usually not be used directly. This new approach basically reverses that
and forces you to define your constraints by means of implementation
details rather than a logical description. There is nothing in this
feature that makes it strikingly different from the existing constraint
types in a way that would prevent a reasonable syntax for defining the
constraint at table definition time. Another problem this would lead to
is that a say dump of a table definition wouldn't actually contain all
the constraints that apply to the table anymore, because there might be
additional stuff such as this that can't be expressed that way.

If you look at the example from the documentation,

CREATE TABLE circles(c circle);
CREATE INDEX circles_idx ON circles USING gist (c);
ALTER TABLE circles ADD CONSTRAINT circles_idx_constr (c &&) USING INDEX
circles_idx;

the only variable pieces of information that need to be provided to the
table are the index type and the operator. So why not just write it
like this

CREATE TABLE circles (c circle UNIQUE ON gist &&);

and let that create the required index.

And then traditional unique constraints would fit into this as well:

CREATE TABLE data (a int UNIQUE ON btree =);

The other problem I see with the syntax is that

ALTER TABLE circles ADD CONSTRAINT circles_idx_constr (c &&) USING INDEX
circles_idx;

doesn't seem very intuitive about what is actually being constrained.
For a while I was thinking that it was constraining the table to values
that are in the index or something. So using a word such as UNIQUE
would help explain what is going on.

#76Peter Eisentraut
peter_e@gmx.net
In reply to: Jeff Davis (#68)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 12:22 -0700, Jeff Davis wrote:

I don't like using the word "unique" in the description, I think it
only
adds to the confusion.

It would emphasize that a unique constraint is a common special case of
the feature.

#77Noname
tomas@tuxteam.de
In reply to: Jeff Davis (#61)
Re: WIP: generalized index constraints

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Tue, Sep 15, 2009 at 10:28:28AM -0700, Jeff Davis wrote:

On Tue, 2009-09-15 at 13:16 -0400, Robert Haas wrote:

Uhh.... so what happens if I create an index constraint using the
+(integer, integer) operator?

You can use any operator that has an index search strategy. Overlaps is
probably the most useful, but you could imagine other operators, like a
bi-directional containment operator (either LHS is contained in RHS, or
vice-versa).

You can also get creative and have a "similarity" operator that
determines whether two tuples are "too similar". As long as it is
symmetric, the feature will work.

One question: does the operator have to be reflexive? I.e. "A op A holds
for all A"?

I am thinking "proximity" or as you state above "similarity". May be
this is a good metaphor, leading to a good name.

Regards
- -- tomás
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)

iD8DBQFKsOPyBcgs9XrR2kYRAhsUAJkBICYUMK0tDrycPbctiGF7YKI/9gCeLQzq
DPyAAkMgZJFn8BZ7P8119/g=
=lVz1
-----END PGP SIGNATURE-----

#78Robert Haas
robertmhaas@gmail.com
In reply to: Joshua Tolley (#74)
Re: WIP: generalized index constraints

On Tue, Sep 15, 2009 at 7:02 PM, Joshua Tolley <eggyknap@gmail.com> wrote:

On Tue, Sep 15, 2009 at 05:52:35PM -0400, Tom Lane wrote:

Jeff Davis <pgsql@j-davis.com> writes:

On Tue, 2009-09-15 at 14:42 -0700, Jeff Davis wrote:

operator constraints
operator exclusion constraints
operator conflict constraints
conflict operator constraints
operator index constraints
index constraints
generalized index constraints
something else?

Just to add a couple more permutations of Robert Haas's suggestions:

 exclusion operator constraints
 exclusive operator constraints

To my ear, "operator exclusion constraints" or "exclusive operator
constraints" seem reasonable; the other permutations of that phrase
simply aren't good English.

I was having a hard time coming up with a name that was adequately
short-and-sweet, and still conveyed the idea of both "operator" and "index",
which seems important so as to designate between these and the constraints
we've had all along. Perhaps "indexed operator constraints"?

Really I suppose they are indexed operator exclusion constraints, but
that may be too wordy.

...Robert

#79Jeff Davis
pgsql@j-davis.com
In reply to: Noname (#77)
Re: WIP: generalized index constraints

On Wed, 2009-09-16 at 15:11 +0200, tomas@tuxteam.de wrote:

One question: does the operator have to be reflexive? I.e. "A op A holds
for all A"?

I don't think that reflexivity is a strict requirement. You could make
this a constraint over a boolean attribute such that false conflicts
with true and true conflicts with false. That would mean that your table
would have to consist of either all false or all true.

I am thinking "proximity" or as you state above "similarity". May be
this is a good metaphor, leading to a good name.

That's an interesting idea: "proximity constraint". I like it because
(a) "proximity" might reasonably be considered a more general form of
the word "unique", which might satisfy Peter's argument; (b) it conveys
the use case; and (c) it sounds good.

There are a couple bizarre cases where "proximity" doesn't quite fit,
like my boolean example above, but I'm OK with that.

Regards,
Jeff Davis

#80Jeff Davis
pgsql@j-davis.com
In reply to: Peter Eisentraut (#75)
Re: WIP: generalized index constraints

On Wed, 2009-09-16 at 10:14 +0300, Peter Eisentraut wrote:

What they should be called is generalized unique constraints, without
reference to "index". Because what they generalize is the operator by
which uniqueness is determined.

How about GUC, for short? ;-)

Do you think that Tomás's suggestion of "proximity constraints" would
satisfy your requirement on the basis that "proximity" is a more general
form of the word "unique"?

First, we have so far been fairly consistent to document that unique
indexes are an implementation detail of unique constraints and should
usually not be used directly. This new approach basically reverses that
and forces you to define your constraints by means of implementation
details rather than a logical description. There is nothing in this
feature that makes it strikingly different from the existing constraint
types in a way that would prevent a reasonable syntax for defining the
constraint at table definition time. Another problem this would lead to
is that a say dump of a table definition wouldn't actually contain all
the constraints that apply to the table anymore, because there might be
additional stuff such as this that can't be expressed that way.

Those are all good points. I think ultimately we need to support this at
table creation time and make index specification optional.

We do need to allow the user to specify the index, however. An important
use case of this feature is defining an index on (A, B, C) and using it
to enforce UNIQUE(A,B) and UNIQUE(A,C).

CREATE TABLE circles (c circle UNIQUE ON gist &&);

Should we use USING instead of ON to be consistent with CREATE INDEX?

Also, right now a UNIQUE by itself creates a btree. Let's say the user
has btree_gist installed and they declare (a =, b =) without specifying
the AM. Which one do we choose?

I suppose we can come up with a default order, like btree, hash, gist,
gin; and just choose the first one with a matching opclass.

Also, we should decide whether UNIQUE(a,b) should be shorthand for (a =,
b =), or whether they should be treated differently somehow. If so,
we'll need to come up with some kind of rules for how it determines that
UNIQUE chooses to use a btree with indisunique enforcement; and we need
to come up with a way to force it to choose the new enforcement
mechanism in my patch if the user wants to.

CREATE TABLE data (a int UNIQUE ON btree =);

I think we should provide some way for the user to specify what
enforcement mechanism is used when multiple options are possible. In
this example should it use the existing mechanism or the new mechanism?

ALTER TABLE circles ADD CONSTRAINT circles_idx_constr (c &&) USING INDEX
circles_idx;

doesn't seem very intuitive about what is actually being constrained.
For a while I was thinking that it was constraining the table to values
that are in the index or something. So using a word such as UNIQUE
would help explain what is going on.

I'm still uncomfortable using the word UNIQUE. I see that you are taking
an approach similar to ORDER BY ... USING. However, I'm concerned
because we're using a special case word to describe the general feature,
and I think that's confusing.

On the other hand, it's hard to argue that (a &&, b =) is intuitive, so
I'll acquiesce if you get some agreement from someone else.

Regards,
Jeff Davis

#81Jeff Davis
pgsql@j-davis.com
In reply to: Brendan Jurd (#48)
Re: WIP: generalized index constraints

On Tue, 2009-09-15 at 22:52 +1000, Brendan Jurd wrote:

I'm just getting started reviewing this version now. I noticed that
your patch seems to have been generated by git.

Ok, I now have a public git repo on git.postgresql.org, and I rebased my
patch before I pushed it.

See updates in my "generalized-constraints" branch. I have psql support
now, so it might be slightly easier to review.

The language and name of the feature are going through a little turmoil
right now, as you can see, so I'm trying to keep up with that. As that
settles down I'll improve the docs.

Regards,
Jeff Davis

#82Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#75)
Re: WIP: generalized index constraints

On Wed, Sep 16, 2009 at 3:14 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

On Tue, 2009-09-15 at 12:37 -0400, Robert Haas wrote:

Instead of calling these generalized index constraints, I wonder if we
oughtn't to be calling them something like "don't-overlap constraints"
(that's a bad name, but something along those lines).  They're not
really general at all, except compared to uniqueness constraints (and
they aren't called generalized unique-index constraints, just
generalized index constraints).

What they should be called is generalized unique constraints, without
reference to "index".  Because what they generalize is the operator by
which uniqueness is determined.

Well, it should eventually be possible to use this feature to create
an index which excludes overlapping ranges in fact, unless I
misunderstand, that's the principle likely use case. Which is not
unique-ness at all.

...Robert

#83Noname
tomas@tuxteam.de
In reply to: Jeff Davis (#79)
Re: WIP: generalized index constraints

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Wed, Sep 16, 2009 at 09:45:52AM -0700, Jeff Davis wrote:

On Wed, 2009-09-16 at 15:11 +0200, tomas@tuxteam.de wrote:

One question: does the operator have to be reflexive? I.e. "A op A holds
for all A"?

I don't think that reflexivity is a strict requirement. You could make
this a constraint over a boolean attribute such that false conflicts
with true and true conflicts with false. That would mean that your table
would have to consist of either all false or all true.

Let me see whether I've understood this: more in general, op could be
"not equal" -- i.e. <>. Then, once one value was inserted into the
column, all other values would have to be equal.

[...]

That's an interesting idea: "proximity constraint". I like it because
(a) "proximity" might reasonably be considered a more general form of
the word "unique", which might satisfy Peter's argument; (b) it conveys
the use case; and (c) it sounds good.

Yes, "riding" on this geometric metaphor (distance), I was trying to
visualize relations which are symmetric but not refexive and came up
with things like

"point X is at least d1 far, at most d2 far from Y"

i.e. Y are all points which are whithin a ring centered on Y, with inner
radius d1 and outer radius d2. Special cases would be d1=0, d2>0 (that's
conventional proximity) -- but there are weirder cases, as your example
above (d2 possibly infinite, d1 small, giving a "punctured space").

There are a couple bizarre cases where "proximity" doesn't quite fit,
like my boolean example above, but I'm OK with that.

Hmmm.

Regards
- -- tomás
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)

iD8DBQFKsT88Bcgs9XrR2kYRAlmcAJ9+rP7AkimXRPoKGaBoJkthX2LzggCfTWst
KF/XMRouhlEbQcORaeoIbc0=
=BBEF
-----END PGP SIGNATURE-----

#84Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#1)
Re: WIP: generalized index constraints

I think we have a reasonable consensus around the name "operator
exclusion constraints", Robert Haas's suggestion. I am OK with that
name, and it got support from David Fetter and Tom Lane. As David Fetter
said, it's useful for the name to hint at the API.

Peter had some reasonable objections to that name, but the word "unique"
just doesn't cut it for this feature. My feature allows constraints
which are more restrictive than a unique constraint; but the final straw
was after a discussion with Tomás in which we determined that you can
also define constraints which are the opposite of unique: all values
must be the same (by using <> as the operator*).

I agree with Peter that we should support creating these constraints at
table creation time. This can be supported with the following syntax:

CONSTRAINT foo_constr (a <op>, ...)
{ USING INDEX foo_idx | USING method }

and it's also a more declarative syntax for the ALTER TABLE case, and
prevents a series of other problems that Peter pointed out.

There's an important unresolved question with this patch that I need to
address, which just came to light: what about functional/expression
indexes?

Say you have a table foo(a text, b text) and an index on:
((a||b)::circle)

You could define an operator constraint like:
((a||b)::circle &&)

and that would be sane. But I suppose I should also allow any expression
with the same signature, like:
((b||a)::circle &&)

[ not a very realistic example, but it seems like it may be useful ]

Does that make sense? Does someone have a better idea? Am I missing
other issues here?

How do I test if two functions/expressions:
a. are identical?
b. have matching signatures?

Regards,
Jeff Davis

*: Understandably, there is no strategy for <> for most data types.
However, if your constraint is that all values must be the same, it's
quite reasonable to add one and be able to use an index to quickly find
values that are different.

#85Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#84)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

There's an important unresolved question with this patch that I need to
address, which just came to light: what about functional/expression
indexes?

What about them? It's not clear why you think this requires anything
special.

regards, tom lane

#86Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#85)
Re: WIP: generalized index constraints

On Sat, 2009-09-19 at 14:05 -0400, Tom Lane wrote:

What about them? It's not clear why you think this requires anything
special.

From a syntax standpoint, I need to represent one operator for every

index column involved in the constraint. So, if there's a functional
index on ((a||b)::circle), I clearly can't have an exclusion constraint
like (a =, b =).

I see two options:

1. (<expr> <op>), where <expr> is an expression over table attributes
that must have the exact signature as the expression for the index.
2. (<index_col> <op>), and then read the expression from the index

and in either case, use that expression for the extra checking that I
need to do: I need to check whether the input heap tuple conflicts with
concurrently inserting heap tuples, and I also need to do a recheck
step.

#1 seems like extra work and complexity, because I need to test for the
correct signature (maybe that's not difficult), and that extra
flexibility is pretty marginal -- I can't think of an obvious case where
you'd want different expressions. Also, it complicates the simple case
of wanting the expressions to match.

#2 is awkward because the expression columns of an index have generated
names, and you would have to write things like (pg_expression_1 &&).
Also, it makes the constraint too tied to the index, which is a valid
complaint Peter had.

Perhaps you can point me in the right direction to see if two
expressions/functions have matching signatures? Or, if that is too much
of a pain, perhaps I should just test for equal expressions?

Regards,
Jeff Davis

#87Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#86)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Sat, 2009-09-19 at 14:05 -0400, Tom Lane wrote:

What about them? It's not clear why you think this requires anything
special.

From a syntax standpoint, I need to represent one operator for every

index column involved in the constraint. So, if there's a functional
index on ((a||b)::circle), I clearly can't have an exclusion constraint
like (a =, b =).

I see two options:

1. (<expr> <op>), where <expr> is an expression over table attributes
that must have the exact signature as the expression for the index.
2. (<index_col> <op>), and then read the expression from the index

You need to do (1), I think, because (2) seems to require using the
index column name. We have generally felt that the names assigned
to index columns are implementation artifacts that the user ought not
rely on in SQL commands.

and in either case, use that expression for the extra checking that I
need to do: I need to check whether the input heap tuple conflicts with
concurrently inserting heap tuples, and I also need to do a recheck
step.

I haven't read the patch, but this whole discussion sounds to me like
it means you're trying to plug things in at the wrong level. Indexes
generally don't care where the values they are storing came from ---
whether it's a simple column or a expression result, it's all the same
to the index. I don't see why that shouldn't be true for exclusion
constraints too.

BTW, further betraying that I've not read the patch: what exactly are
you doing about the information_schema views? If we are treating these
things as SQL constraints, one would expect them to show up in
information_schema; but I don't see how to represent them there in any
adequate fashion, even without the expression-index angle. On the whole
I think we'd be a lot better off to NOT consider them to be constraints,
but just another option for CREATE INDEX.

regards, tom lane

#88Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#87)
Re: WIP: generalized index constraints

On Sat, 2009-09-19 at 15:26 -0400, Tom Lane wrote:

I haven't read the patch, but this whole discussion sounds to me like
it means you're trying to plug things in at the wrong level. Indexes
generally don't care where the values they are storing came from ---
whether it's a simple column or a expression result, it's all the same
to the index. I don't see why that shouldn't be true for exclusion
constraints too.

The design is that one backend needs to be able to see values being
inserted by other backends before commit. There are two ways I can see
to do this:

(a) have all concurrent inserters serialize doing something like:
1. acquire exclusive LWLock
2. search index for conflicts with dirty snapshot and recheck if
necessary
3. insert into index
4. release exclusive LWLock

(b) do what I do now, which is to:
1. acquire exlusive LWLock
2. put self in table of concurrent inserters, along with TID of heap
tuple I'm inserting
3. release exclusive LWLock
4. acquire shared LWLock
5. copy potential conflicts to local memory
6. release shared LWLock
7. test for real conflicts between my heap tuple and the potentially
conflicting heap tuple (which can be found by TID).
8. search index with dirty snapshot for conflicts and recheck if
necessary
9. insert tuple into index
10. acquire exclusive LWLock
11. remove self from table of concurrent inserters
12. release exclusive LWLock

Design (b) offers better concurrency because all conflict testing, index
searching, and index insertion take place without a lock at all. So, I
chose design (b). This has been out there for quite a long time[1]http://archives.postgresql.org/pgsql-hackers/2008-06/msg00404.php[2]http://archives.postgresql.org/pgsql-hackers/2009-07/msg00302.php,
and if it is an unacceptable design I need to know soon in order for
this feature to make it.

However, the consequence of (b) is that ExecInsertIndexTuples needs to
know about the translation from a heap tuple to an index tuple so that
the conflicts can be checked.

BTW, further betraying that I've not read the patch: what exactly are
you doing about the information_schema views? If we are treating these
things as SQL constraints, one would expect them to show up in
information_schema; but I don't see how to represent them there in any
adequate fashion, even without the expression-index angle.

Nothing right now. I think they should just be omitted from
information_schema, which can only (almost by definition) represent the
lowest common denominator features.

On the whole
I think we'd be a lot better off to NOT consider them to be constraints,
but just another option for CREATE INDEX.

You suggested allowing an ALTER TABLE representation[3]http://archives.postgresql.org/pgsql-hackers/2009-07/msg00406.php and that design
has floated around for quite some time as well.

ALTER TABLE also has a major advantage: multiple constraints can use the
same index. For instance, an index on (a, b, c) can be used to enforce
both (a =, b =) and (a =, c =). You can't do that with btree, and it
could be a powerful feature that might cause some people to choose my
mechanism for a regular UNIQUE constraint over btree's existing
uniqueness mechanism.

So, I actually switched over the ALTER TABLE as my primary syntactic
representation, and dropped the CREATE INDEX variant (I think that would
be worthwhile to bring back as an extra option, but I haven't yet). If I
need to drop ALTER TABLE, I need to know soon.

Regards,
Jeff Davis

[1]: http://archives.postgresql.org/pgsql-hackers/2008-06/msg00404.php
[2]: http://archives.postgresql.org/pgsql-hackers/2009-07/msg00302.php
[3]: http://archives.postgresql.org/pgsql-hackers/2009-07/msg00406.php

#89Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#84)
operator exclusion constraints [was: generalized index constraints]

On Sat, 2009-09-19 at 10:48 -0700, Jeff Davis wrote:

CONSTRAINT foo_constr (a <op>, ...)
{ USING INDEX foo_idx | USING method }

I am updating the syntax to be:

CONSTRAINT foo_constr
EXCLUSION (a <op>, ...) { USING method | INDEX foo_idx };

First, I think EXCLUSION makes a perfect noun to fit in that place (like
"FOREIGN KEY").

Second, this makes it possible to avoid specifying the index, and the
system can create one for you by knowing the access method. That makes
the feature a little more declarative.

However, it still doesn't provide a way to express two constraints using
one index all within CREATE TABLE, because the index would need to be
defined before the constraints in that case. I don't see that as a
problem, but Peter had the following concern:

"Another problem this would lead to is that a say dump of a table
definition wouldn't actually contain all the constraints that apply to
the table anymore, because there might be additional stuff such as this
that can't be expressed that way." [1]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01018.php

I don't think that's a serious problem, I just need to ensure that
indexes referenced by a constraint are dumped before the constraint
itself. Then, I can dump the operator exclusion constraints (OXCs) as
ALTER TABLEs. The "-t" option to pg_dump appears to already dump
constraints as separate ALTER TABLEs. Is there something that I'm
missing?

Regards,
Jeff Davis

[1]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01018.php

#90Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#88)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

The design is that one backend needs to be able to see values being
inserted by other backends before commit.

I don't understand why this isn't handled exactly the way unique
constraints are done now. Frankly, the amount of added complexity you
propose below is enough to make me want to reject the patch forthwith;
given that it's going to be a relatively little-used feature, the bugs
are never going to be out of it completely if we do it like this.

regards, tom lane

#91Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#90)
Re: WIP: generalized index constraints

On Sat, 2009-09-19 at 16:43 -0400, Tom Lane wrote:

I don't understand why this isn't handled exactly the way unique
constraints are done now. Frankly, the amount of added complexity you
propose below is enough to make me want to reject the patch forthwith;
given that it's going to be a relatively little-used feature, the bugs
are never going to be out of it completely if we do it like this.

Unique constraints lock the index page while the insert is happening.
How am I supposed to do that, when the conflicting values might be
anywhere in the index (circles have no total order)?

It may sound complex, but it basically boils down to a two stage
process:
1. test for conflicts with concurrently-inserting backends
2. test for conflicts that already exist in the index (dirty or not)

I don't think that it's ridiculously complex. In fact, I think there are
relatively few scenarios that will make any real difference, and those
scenarios can be tested with gdb pretty thoroughly.

Regards,
Jeff Davis

#92Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#91)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Sat, 2009-09-19 at 16:43 -0400, Tom Lane wrote:

I don't understand why this isn't handled exactly the way unique
constraints are done now. Frankly, the amount of added complexity you
propose below is enough to make me want to reject the patch forthwith;
given that it's going to be a relatively little-used feature, the bugs
are never going to be out of it completely if we do it like this.

Unique constraints lock the index page while the insert is happening.
How am I supposed to do that, when the conflicting values might be
anywhere in the index (circles have no total order)?

Well, you can't do it *exactly* the same way btree does, but what
I would envision is first insert the index tuple and then do a
dirty-snapshot search for conflicting tuples. The interlock against
conflicting concurrent inserts doesn't need all this new infrastructure
you propose; just wait to see if conflicting transactions commit, same
as we do now. And I do maintain that that sort of code has a high risk
of undetected bugs.

regards, tom lane

#93Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#89)
Re: operator exclusion constraints [was: generalized index constraints]

Jeff Davis <pgsql@j-davis.com> writes:

I am updating the syntax to be:

CONSTRAINT foo_constr
EXCLUSION (a <op>, ...) { USING method | INDEX foo_idx };

I'm still acutely uncomfortable with using CONSTRAINT syntax for this.
It is not a constraint per standard, because it's not going to be
displayable in information_schema. Furthermore, by extending
standardized syntax you run the risk of being blindsided by future
additions to the standard.

... Peter had the following concern:

"Another problem this would lead to is that a say dump of a table
definition wouldn't actually contain all the constraints that apply to
the table anymore, because there might be additional stuff such as this
that can't be expressed that way." [1]

I don't think that's a serious problem,

That objection is completely bogus. pg_dump does not, and AFAIR never
has, promised to emit everything in the CREATE TABLE command. It's far
more efficient and practical to emit indexes and constraints as separate
commands later, after the data has been loaded. In the case of say
foreign key constraints, it's absolutely necessary to do it separately,
else you can't implement circular constraint references. Besides, we
already have many cases where indexes have to be emitted separately
because they don't fit into the CONSTRAINT syntax: expression indexes
and nondefault opclasses to name two.

The point about being able to support multiple constraints with one
index is kind of interesting, but I don't actually think that that's
so useful that it should override all other considerations about what
syntax we should pick. I think we should drop the whole thing and
just treat this as an extension to the CREATE INDEX syntax.

regards, tom lane

#94Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#92)
Re: WIP: generalized index constraints

On Sat, 2009-09-19 at 18:00 -0400, Tom Lane wrote:

Well, you can't do it *exactly* the same way btree does, but what
I would envision is first insert the index tuple and then do a
dirty-snapshot search for conflicting tuples. The interlock against
conflicting concurrent inserts doesn't need all this new infrastructure
you propose; just wait to see if conflicting transactions commit, same
as we do now. And I do maintain that that sort of code has a high risk
of undetected bugs.

How do you prevent deadlocks in the following case?

T1: inserts into index
T2: inserts into index
T1: checks index for conflicts, finds T2
T2: checks index for conflicts, finds T1

We can't say "only wait if your xid is higher" because xid 200 may both
insert and check the index before xid 100 even inserts.

The way I solve this in my current patch is by assigning a sequence
number in a shared memory table for each insert. The sequence number
works because a higher sequence number will always be able to see a
lower sequence number's tuple, so we can safely say "only wait if you
have a higher sequence number".

I can tack the same solution onto your idea, but I would need to keep my
shared memory table and probably some other infrastructure. It may be
less complex than it is currently, however. Simpler ideas welcome.

And to clarify the syntax issue, I assume this means that:
((a||b)::circle &&)

would look for the column in the index that matches that expression, and
then use that attribute number when scanning the index? I'm OK with
that; I don't see a lot of obvious value in having separate expressions
for the constraint and the index (even if it did have value, it would
take some real creativity to find it ;)

Regards,
Jeff Davis

#95Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#93)
Re: operator exclusion constraints [was: generalized index constraints]

On Sat, 2009-09-19 at 18:35 -0400, Tom Lane wrote:

I'm still acutely uncomfortable with using CONSTRAINT syntax for this.
It is not a constraint per standard, because it's not going to be
displayable in information_schema. Furthermore, by extending
standardized syntax you run the risk of being blindsided by future
additions to the standard.

Ok.

The point about being able to support multiple constraints with one
index is kind of interesting, but I don't actually think that that's
so useful that it should override all other considerations about what
syntax we should pick. I think we should drop the whole thing and
just treat this as an extension to the CREATE INDEX syntax.

Perhaps ALTER INDEX ADD EXCLUSION CONSTRAINT or some other command? And
CREATE INDEX can offer the ability as a shorthand?

I would still really like to decouple this from CREATE INDEX because of
two reasons:
1. Cannot support multiple constraints per index very easily. I think
this is a significant feature.
2. Must decide to make constraint at the same time as making the
index, and once it's there, you can't remove it without dropping
the index.

I think either of these still tie the concept to implementation, because
creating the index is always explicit. Peter seemed concerned about
that, and I think that concern is valid, but I can live with it. If we
really want them to be declarative, we could invent a new command.

Regards,
Jeff Davis

#96David Fetter
david@fetter.org
In reply to: Jeff Davis (#95)
Re: operator exclusion constraints [was: generalized index constraints]

On Sat, Sep 19, 2009 at 04:40:19PM -0700, Jeff Davis wrote:

On Sat, 2009-09-19 at 18:35 -0400, Tom Lane wrote:

I'm still acutely uncomfortable with using CONSTRAINT syntax for this.
It is not a constraint per standard, because it's not going to be
displayable in information_schema. Furthermore, by extending
standardized syntax you run the risk of being blindsided by future
additions to the standard.

Ok.

It just occurred to me that SQL:2008 ASSERTION might already fit this
feature. :)

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#97Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#86)
Re: WIP: generalized index constraints

On Sat, Sep 19, 2009 at 2:51 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Sat, 2009-09-19 at 14:05 -0400, Tom Lane wrote:

What about them?  It's not clear why you think this requires anything
special.

From a syntax standpoint, I need to represent one operator for every

index column involved in the constraint. So, if there's a functional
index on ((a||b)::circle), I clearly can't have an exclusion constraint
like (a =, b =).

I see two options:

 1. (<expr> <op>), where <expr> is an expression over table attributes
   that must have the exact signature as the expression for the index.
 2. (<index_col> <op>), and then read the expression from the index

I was wondering if we couldn't introduce a dummy tuple name similar to
OLD and NEW, called, say, OTHER. Then instead of writing a =, you
could write a = OTHER.a ... or perhaps a = OTHER.b ... although that
might also open the door to more things than you want to support at
this point.

...Robert

#98Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#97)
Re: WIP: generalized index constraints

On Sat, 2009-09-19 at 23:15 -0400, Robert Haas wrote:

I was wondering if we couldn't introduce a dummy tuple name similar to
OLD and NEW, called, say, OTHER. Then instead of writing a =, you
could write a = OTHER.a ... or perhaps a = OTHER.b ... although that
might also open the door to more things than you want to support at
this point.

Interesting idea. At this point though, there is enough disagreement
over the language that I just want to take the least-invasive route that
has the lowest chance of causing a problem. It looks like ALTER INDEX
might be the path of least resistance.

Regards,
Jeff Davis

#99Jeff Davis
pgsql@j-davis.com
In reply to: David Fetter (#96)
Re: operator exclusion constraints [was: generalized index constraints]

On Sat, 2009-09-19 at 19:23 -0700, David Fetter wrote:

It just occurred to me that SQL:2008 ASSERTION might already fit this
feature. :)

I think I would only be able to enforce very specific types of
assertions that match the template. As I said to Robert, I think I'm
going to use ALTER INDEX for the syntax because it appears to be the
path of least resistance.

Regards,
Jeff Davis

#100Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#94)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Sat, 2009-09-19 at 18:00 -0400, Tom Lane wrote:

Well, you can't do it *exactly* the same way btree does, but what
I would envision is first insert the index tuple and then do a
dirty-snapshot search for conflicting tuples. The interlock against
conflicting concurrent inserts doesn't need all this new infrastructure
you propose; just wait to see if conflicting transactions commit, same
as we do now. And I do maintain that that sort of code has a high risk
of undetected bugs.

How do you prevent deadlocks in the following case?

T1: inserts into index
T2: inserts into index
T1: checks index for conflicts, finds T2
T2: checks index for conflicts, finds T1

You get a deadlock failure, because both transactions will wait for each
other. So what? It's an error in any case, and you can get a reported
deadlock in constraint-enforcement scenarios today (conflicting FK
changes, for instance).

regards, tom lane

#101Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#100)
Re: WIP: generalized index constraints

On Sun, 2009-09-20 at 12:31 -0400, Tom Lane wrote:

T1: inserts into index
T2: inserts into index
T1: checks index for conflicts, finds T2
T2: checks index for conflicts, finds T1

You get a deadlock failure, because both transactions will wait for each
other. So what? It's an error in any case, and you can get a reported
deadlock in constraint-enforcement scenarios today (conflicting FK
changes, for instance).

Well, in theory, one of the transactions may have been destined to be
aborted later anyway, and the deadlock detector might kill the wrong
one. But I agree, perhaps I'm over-thinking this one.

Aside: I just realized that my solution to the deadlock problem won't
work with your simpler idea anyway. When reading the index we've long
since lost the information about the specific insert of the specific
command of the other transaction.

I'll make the change.

Regards,
Jeff Davis

#102Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#95)
Re: operator exclusion constraints [was: generalized index constraints]

Jeff Davis <pgsql@j-davis.com> writes:

I would still really like to decouple this from CREATE INDEX because of
two reasons:
1. Cannot support multiple constraints per index very easily. I think
this is a significant feature.
2. Must decide to make constraint at the same time as making the
index, and once it's there, you can't remove it without dropping
the index.

I don't actually find either of those arguments to be credible in the
least. I don't think that people will find it useful to enforce
multiple constraints with one index, and I don't believe that they'll
design an index without knowledge of the constraint they will enforce
with it. The closest precedent we have is the UNIQUE constraint.
How often have we had requests to add or drop UNIQUE in an existing
index? Maybe there were more than zero, but not by a lot.

As an example of why I don't believe the first item, consider something
like
create index ... (a = , b = )
(or whatever the syntax is to exclude equality on each column
separately). Yeah, it will work, but have you considered the efficiency
implications? Searching such an index for b, independently of a, is
going to suck to such an extent that you'd be *far* better off building
two separate indexes. We do not have, and are unlikely ever to have,
index types in which a search that doesn't constrain the first index
column is efficient.

regards, tom lane

#103Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#101)
Re: WIP: generalized index constraints

BTW, I just thought of an issue that might change some of these
conclusions: what about supporting deferred constraint checking,
as we just recently managed to do for straight UNIQUE constraints?
I don't say that this has to be in the first cut, but it's something
we ought to try to leave room for down the road.

The current infrastructure for deferred uniqueness requires that the
thing actually be a constraint, with an entry in pg_constraint that
can carry the deferrability options. So unless we want to rethink
that, this might be a sufficient reason to override my arguments
about not wanting to use CONSTRAINT syntax.

As far as implementation goes, I think there would be very little
choice but to use the insert-the-index-entry-first, check-later
approach; so your ideas involving extra state in shared memory
seem to fall to the ground anyhow.

regards, tom lane

#104Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#102)
Re: operator exclusion constraints [was: generalized index constraints]

On Sun, 2009-09-20 at 12:45 -0400, Tom Lane wrote:

How often have we had requests to add or drop UNIQUE in an existing
index? Maybe there were more than zero, but not by a lot.

Ok.

As an example of why I don't believe the first item, consider something
like
create index ... (a = , b = )
(or whatever the syntax is to exclude equality on each column
separately). Yeah, it will work, but have you considered the efficiency
implications? Searching such an index for b, independently of a, is
going to suck to such an extent that you'd be *far* better off building
two separate indexes. We do not have, and are unlikely ever to have,
index types in which a search that doesn't constrain the first index
column is efficient.

My use case was something else:

An index on (a, b, c) enforcing the constraints UNIQUE(a, b) and
UNIQUE(a, c).

UNIQUE(a, b) can be enforced efficiently. UNIQUE(a, c) might be less
efficient depending on the selectivity of "a", but as long as "a" is
selective I think it's useful. The alternative is updating two indices
on every insert.

You may still think this use case is too marginal to bother supporting,
but I never made an argument for the use case you described above.

If we move away from multiple constraints per index, are you suggesting
that I also move the constraints out of pg_constraint and back into
pg_index?

Regards,
Jeff Davis

#105Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#103)
Re: WIP: generalized index constraints

On Sun, 2009-09-20 at 13:01 -0400, Tom Lane wrote:

The current infrastructure for deferred uniqueness requires that the
thing actually be a constraint, with an entry in pg_constraint that
can carry the deferrability options. So unless we want to rethink
that, this might be a sufficient reason to override my arguments
about not wanting to use CONSTRAINT syntax.

Ok. Using the word EXCLUSION would hopefully guard us against future
changes to SQL, but you know more about the subtle dangers of language
changes than I do.

So, do I still omit it from information_schema?

As far as implementation goes, I think there would be very little
choice but to use the insert-the-index-entry-first, check-later
approach; so your ideas involving extra state in shared memory
seem to fall to the ground anyhow.

True.

Regards,
Jeff Davis

#106Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#104)
Re: operator exclusion constraints [was: generalized index constraints]

Jeff Davis <pgsql@j-davis.com> writes:

My use case was something else:

An index on (a, b, c) enforcing the constraints UNIQUE(a, b) and
UNIQUE(a, c).

UNIQUE(a, b) can be enforced efficiently. UNIQUE(a, c) might be less
efficient depending on the selectivity of "a", but as long as "a" is
selective I think it's useful. The alternative is updating two indices
on every insert.

You may still think this use case is too marginal to bother supporting,
but I never made an argument for the use case you described above.

You're right, it still seems remarkably marginal. I'm rethinking
my position on use of CONSTRAINT syntax because of the deferrability
issue, but I'm still unconvinced that we need to allow the constraints
to be decoupled from the indexes.

regards, tom lane

#107Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#105)
Re: WIP: generalized index constraints

Jeff Davis <pgsql@j-davis.com> writes:

So, do I still omit it from information_schema?

My thought is yes --- any representation of it within information_schema
would be so inaccurate/incomplete as to be worse than useless, IMO.
Peter might have a different idea though ...

regards, tom lane

#108Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#106)
Re: operator exclusion constraints [was: generalized index constraints]

On Sun, 2009-09-20 at 13:13 -0400, Tom Lane wrote:

You're right, it still seems remarkably marginal. I'm rethinking
my position on use of CONSTRAINT syntax because of the deferrability
issue, but I'm still unconvinced that we need to allow the constraints
to be decoupled from the indexes.

Ok, should I explicitly disallow multiple constraints on one index then?

Regards,
Jeff Davis

#109Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#108)
Re: operator exclusion constraints [was: generalized index constraints]

Jeff Davis <pgsql@j-davis.com> writes:

On Sun, 2009-09-20 at 13:13 -0400, Tom Lane wrote:

You're right, it still seems remarkably marginal. I'm rethinking
my position on use of CONSTRAINT syntax because of the deferrability
issue, but I'm still unconvinced that we need to allow the constraints
to be decoupled from the indexes.

Ok, should I explicitly disallow multiple constraints on one index then?

What I'm arguing for is a syntax in which the question doesn't even
arise, ie, a CONSTRAINT doesn't reference an existing index at all.
If that's not possible for whatever reason, then I think that
disallowing multiple references isn't going to buy any simplicity.

regards, tom lane

#110Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#109)
Re: operator exclusion constraints [was: generalized index constraints]

On Sun, 2009-09-20 at 13:28 -0400, Tom Lane wrote:

What I'm arguing for is a syntax in which the question doesn't even
arise, ie, a CONSTRAINT doesn't reference an existing index at all.
If that's not possible for whatever reason, then I think that
disallowing multiple references isn't going to buy any simplicity.

I believe that syntax is possible by specifying the index access method,
e.g.:

CONSTRAINT <name> EXCLUSION (a =, b &&) USING gist;

versus:

CONSTRAINT <name> EXCLUSION (a =, b &&) INDEX <indexname>;

And the former could build the index implicitly. I haven't written the
code yet, but I don't see any major problems.

So, should I eliminate the latter syntax and only support the former, or
should I support both?

Regards,
Jeff Davis

#111Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#110)
Re: operator exclusion constraints [was: generalized index constraints]

Jeff Davis <pgsql@j-davis.com> writes:

I believe that syntax is possible by specifying the index access method,
e.g.:

CONSTRAINT <name> EXCLUSION (a =, b &&) USING gist;

versus:

CONSTRAINT <name> EXCLUSION (a =, b &&) INDEX <indexname>;

And the former could build the index implicitly. I haven't written the
code yet, but I don't see any major problems.

So, should I eliminate the latter syntax and only support the former, or
should I support both?

I'd vote for only supporting the former.

What worries me more about that syntax is the postfix-operator ambiguity
--- I think it'll be hard to expand it to expressions.  It might be
better to put the operator at the front; or maybe you need an extra
keyword in there.

regards, tom lane

#112Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#111)
Re: operator exclusion constraints [was: generalized index constraints]

On Sun, 2009-09-20 at 13:49 -0400, Tom Lane wrote:

I'd vote for only supporting the former.

Ok.

I just did some brief non-scientific in-memory benchmarks. I think it
has promise, but for now I think it can safely be set aside.

Results appended.

What worries me more about that syntax is the postfix-operator ambiguity
--- I think it'll be hard to expand it to expressions.  It might be
better to put the operator at the front; or maybe you need an extra
keyword in there.

How about "OPERATOR", like:

CONSTRAINT <name>
EXCLUSION (<expr> OPERATOR <op>, ...)
USING <method>;

I like it because it connects back to the name "operator exclusion
constraint".

Regards,
Jeff Davis

---------------------------
Results (oversimplified benchmark):

As a control, two unique btrees (using old uniqueness mechanism) takes
37s.

DDL (old syntax, haven't changed it yet):

create table one(a int, b int, c int);
create index one_a_b_c_idx on one(a,b,c);
alter table one add constraint one_a_b_constr
exclusion (a =, b =) using one_a_b_c_idx;
alter table one add constraint one_a_c_constr
exclusion (a =, c =) index one_a_b_c_idx;

create table two(a int, b int, c int);
create index two_a_b_idx on two(a,b);
create index two_a_c_idx on two(a,c);
alter table two add constraint two_a_c_constr
exclusion (a =, c =) index two_a_c_idx;
alter table two add constraint two_a_b_constr
exclusion (a =, b =) index two_a_b_idx;

Tests are of the form:

-- test inserting into table with one big index with 10 "b"
-- values per "a" value
insert into one select g1, g2, g2
from generate_series(1,100000) g1, generate_series(1,10) g2;

n: number of "a" values per "b" value
t1: results for one-index solution
t2: results for two-index solution

n t1 t2
-------+------+-------
1000 | 105s | 57s
100 | 47s | 54s
10 | 44s | 53s
1 | 42s | 56s

So, the one-index solution shows about 10-20% benefit over the two-index
solution when the number of "b" values per "a" value drops to around
100. Not bad, but nothing to write home about, because it's still
outperformed by the existing btree enforcement mechinism. I think it has
promise for some situations though; such as larger key size, leaf pages
not in memory, etc.

#113Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#1)
operator exclusion constraints [was: generalized index constraints]

Update on operator exclusion constraints (OXC for short):

After a lot of discussion, I think a lot of progress has been made. Here
is my summary, please let me know if I've left anything out or not
addressed some concern.

1. Constraint syntax, part of CREATE/ALTER TABLE:

[CONSTRAINT <name>] EXCLUSION (<expr> OPERATOR <op>, ...)
USING <method>
[ DEFERRABLE | NOT DEFERRABLE ]
[ INITIALLY DEFERRED | INITIALLY IMMEDIATE ];

Table constraint syntax was chosen because of the ability to support
DEFERRABLE[1]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01352.php and the interest in a more declarative syntax[2]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01018.php.

We omitted the [INDEX <indexname>] clause because the usefulness of
defining multiple constraints using one index or defining the constraint
separately from the index was judged to be too marginal[3]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01348.php[4]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01355.php[5]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01360.php. Some
brief benchmarks showed some promise[6]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01369.php, perhaps interesting to explore
later.

Also, we introduce the OPERATOR keyword in between the expression and
the operator to disambiguate the syntax[5]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01360.php. Nobody has affirmed the use
of OPERATOR for the disambiguation, but it seems like the obvious choice
to me.

2. information_schema

We omit operator exclusion constraints from the information schema, on
the grounds that there is no way to represent them usefully there[7]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01310.php[8]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01356.php.

3. Simplify the constraint checking procedure itself

Tom suggested a simpler constraint-checking procedure[9]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01315.php. It introduces
the rare possibility of deadlocks[10]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01317.php, but that possibility exists for
other constraints anyway[11]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01347.php. My scheme for avoiding deadlocks was
significantly more complex, and would become even more complex for
deferrable constraints.

4. <expr> is an expression over the table's attributes and will be used
to generate a functional index with the same expression to enforce the
constraint.

5. We reject non-symmetric operators[12]http://archives.postgresql.org/pgsql-hackers/2009-09/msg00977.php, like >, but allow
non-reflexive operators[13]http://archives.postgresql.org/pgsql-hackers/2009-09/msg01039.php like <>.

6. Semantics of constraint[14]http://archives.postgresql.org/pgsql-hackers/2009-09/msg00971.php are such that for any two tuples A and B,
and for a constraint:

EXCLUSION (e1 OPERATOR <op1>, ..., eN OPERATOR <opN>)

the constraint is violated if:

A.e1 <op1> B.e1 AND
... AND
A.eN <opN> B.eN

7. LIKE is still unresolved. I don't have a strong opinion here.

When INCLUDING CONSTRAINTS and INCLUDING INDEXES are both specified:
a. copy all OXCs and indexes
b. copy no OXCs or indexes
When INCLUDING CONSTRAINTS is specified but not INCLUDING INDEXES:
a. copy all OXCs and indexes
b. copy no OXCs or indexes
When INCLUDING INDEXES is specified but not INCLUDING CONSTRAINTS:
a. copy all OXCs, including indexes
b. copy all indexes created implicitly for OXCs, but not the
constraints themselves
c. copy no OXCs or indexes

We can also emit various types of messages if we think the user is
making a mistake.

UNIQUE behavior here doesn't provide a good cue, because the constraint
is implemented inside the index, so copying the index does copy the
constraint. Brendan made a strong argument[15]http://archives.postgresql.org/pgsql-hackers/2009-09/msg00755.php that the behavior of LIKE
with UNIQUE is wrong, but I don't know if we want to try to fix that
now. I'd like some more input before I actually take care of this item.

The rest of the issues were mostly non-controversial. I will start
making some of these changes and post an updated patch and TODO list.

Regards,
Jeff Davis

[1]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01352.php
[2]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01018.php
[3]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01348.php
[4]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01355.php
[5]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01360.php
[6]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01369.php
[7]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01310.php
[8]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01356.php
[9]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01315.php
[10]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01317.php
[11]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01347.php
[12]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg00977.php
[13]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg01039.php
[14]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg00971.php
[15]: http://archives.postgresql.org/pgsql-hackers/2009-09/msg00755.php

#114Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#113)
Re: operator exclusion constraints [was: generalized index constraints]

Jeff Davis <pgsql@j-davis.com> writes:

1. Constraint syntax, part of CREATE/ALTER TABLE:

[CONSTRAINT <name>] EXCLUSION (<expr> OPERATOR <op>, ...)

Have you actually built this grammar? I don't think it avoids the
problem, because OPERATOR is possible within a_expr.

Also, don't forget the possibility of wanting a nondefault opclass.
(I'm wondering a bit if anyone will want a WHERE clause, too, though
adding that later shouldn't pose any big syntactic obstacles.)

Brendan made a strong argument[15] that the behavior of LIKE
with UNIQUE is wrong, but I don't know if we want to try to fix that
now. I'd like some more input before I actually take care of this item.

That's really a separate issue, but I think we need to do something to
make it more consistent. My first thought is that anything made
via CONSTRAINT syntax ought to be copied by LIKE INCLUDING CONSTRAINTS,
while LIKE INCLUDING INDEXES should copy anything you made via CREATE
INDEX. But note this assumes that there is a clear distinction between
the two. The constraint-depending-on-index design that you started
with would not permit such a rule, or at least it would mean that
INCLUDING CONSTRAINTS EXCLUDING INDEXES would have failure cases.

regards, tom lane

#115Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#114)
Re: operator exclusion constraints [was: generalized index constraints]

On Sun, 2009-09-20 at 17:54 -0400, Tom Lane wrote:

Jeff Davis <pgsql@j-davis.com> writes:

1. Constraint syntax, part of CREATE/ALTER TABLE:

[CONSTRAINT <name>] EXCLUSION (<expr> OPERATOR <op>, ...)

Have you actually built this grammar? I don't think it avoids the
problem, because OPERATOR is possible within a_expr.

Also, don't forget the possibility of wanting a nondefault opclass.
(I'm wondering a bit if anyone will want a WHERE clause, too, though
adding that later shouldn't pose any big syntactic obstacles.)

I suppose I should just allow any index_elem. The only way I was able to
make the grammar for that work is by using a reserved keyword. The
possibilities that make the most sense to me are:

index_elem WITH any_operator
index_elem WITH OPERATOR any_operator
index_elem CHECK any_operator
index_elem CHECK OPERATOR any_operator

Do any of these look acceptable?

Also, I should allow for a tablespace, as well. Because it's specified
with UNIQUE as "USING INDEX TABLESPACE foo", to be consistent I need to
move the "USING method" ahead like so:

CONSTRAINT <name> EXCLUSION [USING method]
(<index_elem> CHECK <op>, ...)
[USING INDEX TABLESPACE <tablespacename>]
[DEFERRABLE | NOT DEFERRABLE ]
[ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]

Having the method before the attribute list makes it more consistent
with CREATE INDEX, as well.

That's really a separate issue, but I think we need to do something to
make it more consistent. My first thought is that anything made
via CONSTRAINT syntax ought to be copied by LIKE INCLUDING CONSTRAINTS,
while LIKE INCLUDING INDEXES should copy anything you made via CREATE
INDEX.

Works for me.

But note this assumes that there is a clear distinction between
the two. The constraint-depending-on-index design that you started
with would not permit such a rule, or at least it would mean that
INCLUDING CONSTRAINTS EXCLUDING INDEXES would have failure cases.

Sounds reasonable. If we decide to support that kind of thing in the
future, we can handle that case somehow (an error seems reasonable to
me).

Regards,
Jeff Davis

#116Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#115)
Re: operator exclusion constraints [was: generalized index constraints]

Jeff Davis <pgsql@j-davis.com> writes:

I suppose I should just allow any index_elem. The only way I was able to
make the grammar for that work is by using a reserved keyword. The
possibilities that make the most sense to me are:

index_elem WITH any_operator
index_elem WITH OPERATOR any_operator
index_elem CHECK any_operator
index_elem CHECK OPERATOR any_operator

Do any of these look acceptable?

I'd vote for "CHECK", out of that list. WITH has no mnemonic value
whatever.

I'm not that thrilled with CHECK either, mainly because it seems like
it ought to check that the operator condition *does* hold, whereas
you're going to check that it *doesn't* hold. But perhaps the EXCLUSION
up front will be enough to set people straight.

BTW, are you sure EXCLUSION doesn't have to become a reserved word for
this? I notice that FOREIGN, CHECK, and UNIQUE all are, which makes me
suspicious ...

regards, tom lane

#117Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#116)
Re: operator exclusion constraints [was: generalized index constraints]

On Sun, 2009-09-20 at 19:42 -0400, Tom Lane wrote:

BTW, are you sure EXCLUSION doesn't have to become a reserved word for
this? I notice that FOREIGN, CHECK, and UNIQUE all are, which makes me
suspicious ...

All of those (except FOREIGN) can be used as a column constraint as
well, and that might be necessary for a reason similar to the reason I
need to use a reserved word (i.e. they can come after a DEFAULT
expression). Is it possible that FOREIGN doesn't really have to be a
reserved word, but was just included because the others were?

I'm not an expert on the matter, but it does appear to compile and
recognize the grammar with EXCLUSION as an unreserved keyword. I'm in
the middle of changing a lot of things around, so I can't say that it
works beyond that.

Regards,
Jeff Davis

#118Peter Eisentraut
peter_e@gmx.net
In reply to: Jeff Davis (#105)
Re: WIP: generalized index constraints

On Sun, 2009-09-20 at 10:08 -0700, Jeff Davis wrote:

On Sun, 2009-09-20 at 13:01 -0400, Tom Lane wrote:

The current infrastructure for deferred uniqueness requires that the
thing actually be a constraint, with an entry in pg_constraint that
can carry the deferrability options. So unless we want to rethink
that, this might be a sufficient reason to override my arguments
about not wanting to use CONSTRAINT syntax.

Ok. Using the word EXCLUSION would hopefully guard us against future
changes to SQL, but you know more about the subtle dangers of language
changes than I do.

So, do I still omit it from information_schema?

I would say yes.

Overall, I think this terminology is pretty good now. We could say,
PostgreSQL has a new constraint type, exclusion constraint. It shares
common properties with other constraint types, e.g., deferrability (in
the future), ADD/DROP CONSTRAINT, etc. But because the standard does
not describe exclusion constraints, they are not listed in the
information schema.

#119Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#116)
Re: operator exclusion constraints [was: generalized index constraints]

On Sun, 2009-09-20 at 19:42 -0400, Tom Lane wrote:

Jeff Davis <pgsql@j-davis.com> writes:

I suppose I should just allow any index_elem. The only way I was able to
make the grammar for that work is by using a reserved keyword. The
possibilities that make the most sense to me are:

index_elem WITH any_operator
index_elem WITH OPERATOR any_operator
index_elem CHECK any_operator
index_elem CHECK OPERATOR any_operator

Do any of these look acceptable?

I'd vote for "CHECK", out of that list. WITH has no mnemonic value
whatever.

Using CHECK as part of the syntax of an EXCLUSION constraint will surely
confuse the whole thing with CHECK constraints.

USING OPERATOR is available, I think.

#120Jeff Davis
pgsql@j-davis.com
In reply to: Peter Eisentraut (#119)
Re: operator exclusion constraints [was: generalized index constraints]

On Wed, 2009-09-23 at 15:10 +0300, Peter Eisentraut wrote:

Using CHECK as part of the syntax of an EXCLUSION constraint will surely
confuse the whole thing with CHECK constraints.

USING OPERATOR is available, I think.

USING won't work because one of the ways to specify the opclass in an
index_elem is something like:

CREATE INDEX foo_idx on foo (i USING int4_ops);

which appears to be undocumented, and it's not obvious to me why that is
useful. The normal way is just:

CREATE INDEX foo_idx on foo (i int4_ops);

Because I am allowing any index_elem for exclusion constraints, that
conflicts with the word USING.

We can either eliminate the USING variant from opt_class (unless it's
necessary for some reason or I missed it in the documentation), or we
can use another word (e.g. WITH or WITH OPERATOR) if you don't like
CHECK.

Regards,
Jeff Davis

#121Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#120)
Re: operator exclusion constraints [was: generalized index constraints]

Jeff Davis <pgsql@j-davis.com> writes:

We can either eliminate the USING variant from opt_class (unless it's
necessary for some reason or I missed it in the documentation), or we
can use another word (e.g. WITH or WITH OPERATOR) if you don't like
CHECK.

Hmm ... we don't seem to have documented the USING noise-word, so it
probably would be safe to remove it; but why take a chance? I don't
particularly agree with Peter's objection to CHECK. There are plenty
of examples in SQL of the same keyword being used for different purposes
in nearby places. Indeed you could make about the same argument to
object to USING, since it'd still be there in "USING access_method"
elsewhere in the same command.

I think that USING is just about as content-free as WITH in this
particular example --- it doesn't give you any hint about what the
purpose of the operator is.

regards, tom lane

#122Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#121)
Re: operator exclusion constraints [was: generalized index constraints]

On Wed, Sep 23, 2009 at 1:47 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Jeff Davis <pgsql@j-davis.com> writes:

We can either eliminate the USING variant from opt_class (unless it's
necessary for some reason or I missed it in the documentation), or we
can use another word (e.g. WITH or WITH OPERATOR) if you don't like
CHECK.

Hmm ... we don't seem to have documented the USING noise-word, so it
probably would be safe to remove it; but why take a chance?  I don't
particularly agree with Peter's objection to CHECK.  There are plenty
of examples in SQL of the same keyword being used for different purposes
in nearby places.  Indeed you could make about the same argument to
object to USING, since it'd still be there in "USING access_method"
elsewhere in the same command.

I think that USING is just about as content-free as WITH in this
particular example --- it doesn't give you any hint about what the
purpose of the operator is.

USING might be just as content-free as WITH, but USING OPERATOR seems
clearly better, at least IMO.

Also, this patch has not been updated in a week, and the clock is
ticking: if we don't have an updated version RSN, we need to move this
to Returned with Feedback and wait until next CommitFest. That would
be too bad; this is an awesome feature.

Thanks,

...Robert

#123Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#122)
Re: operator exclusion constraints [was: generalized index constraints]

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Sep 23, 2009 at 1:47 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I think that USING is just about as content-free as WITH in this
particular example --- it doesn't give you any hint about what the
purpose of the operator is.

USING might be just as content-free as WITH, but USING OPERATOR seems
clearly better, at least IMO.

It's not enough better to justify the conflict with USING opclass, IMO.

An idea that just struck me is CHECK WITH, ie

EXCLUSION (expr CHECK WITH operator)

regards, tom lane

#124Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#123)
Re: operator exclusion constraints [was: generalized index constraints]

On Sun, Sep 27, 2009 at 1:08 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Sep 23, 2009 at 1:47 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I think that USING is just about as content-free as WITH in this
particular example --- it doesn't give you any hint about what the
purpose of the operator is.

USING might be just as content-free as WITH, but USING OPERATOR seems
clearly better, at least IMO.

It's not enough better to justify the conflict with USING opclass, IMO.

An idea that just struck me is CHECK WITH, ie

       EXCLUSION (expr CHECK WITH operator)

I don't like that as well as USING OPERATOR, but I like it far better
than any of the single-word choices, so maybe it's a reasonable
compromise.

...Robert

#125Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#113)
2 attachment(s)
Re: operator exclusion constraints

Attached is a new patch. I ran it through filterdiff, but in case that
didn't work for some reason, I attached a gzipped version of the
original unified diff produced by git.

* Changed underlying algorithm to match Tom's suggestion: do the second
index lookup after already inserting into the first.
* Language changes, including the latest "<expression> CHECK WITH <op>"
idea from Tom, seconded by Robert Haas.
* automatically builds index for you, no need to create it separately,
just specify the index AM (or let it default to btree)
* Only one constraint per index is allowed, and the index is treated
entirely as an internal implementation detail.
* Support for predicates (partial constraints/partial index)
* Support for expressions
* Support for other index options, like WITH list and USING INDEX
TABLESPACE
* Docs updated and improved
* Tests updated
* Full recheck support (the previous recheck mechanism didn't work for
expressions)
* Make information_schema ignore operator exclusion constraints
* error message improvements

When testing/reviewing, use the documentation from CREATE TABLE, but use
the ALTER TABLE variant instead. Right now the CREATE TABLE variant
doesn't work (see below).

There is still a significant TODO list:

* CREATE TABLE -- right now, it only works for ALTER TABLE, and the
docs are lying. Coming soon.

* psql - haven't updated it to keep up with the language changes

* pg_dump

* LIKE

* Inheritance

* Enforce on existing tuples when the constraint is created -- This is
intertwined with inheritance, I think, and I am still working on that.
Obviously, this is an important TODO item to get the patch ready for
commit.

* Deferrability (optional for now) -- I need the trigger to be able to
perform the check as well. It looks like it has most of the information
necessary, but I'm trying to determine where would be the cleanest place
to export the constraint checking function so that it can be called by
the trigger as well as ExecInsertIndexTuples and the bulk checker (that
checks existing tuples at the time the constraint is added).

* GIN support (optional for now) -- I need to create a gingettuple
method. It would have to be a wrapper around gingetbitmap, and would not
be any more efficient than gingetbitmap, but it would allow my patch to
work for GIN indexes.

I think I've made some progress this commitfest, both in terms of
decisions made (thanks to everyone who provided input and direction),
and also in terms of code. I would still appreciate more review during
this commitfest, but realistically, it will still be at least another
week before I can say that I'm done with all open items.

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20090927.patch.gzapplication/x-gzip; name=operator-exclusion-constraints-20090927.patch.gzDownload
operator-exclusion-constraints-20090927.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20090927.context.patchDownload
*** doc/src/sgml/ref/alter_table.sgml
--- doc/src/sgml/ref/alter_table.sgml
***************
*** 61,66 **** ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
--- 61,73 ----
      NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
      SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+ 
+ and <replaceable class="PARAMETER">index_constraint</replaceable> is:
+ 
+     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+     ( <replaceable class="PARAMETER">column_name operator</replaceable> [, ...] ) USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
+ 
+ 
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 905,911 **** ALTER TABLE distributors SET TABLESPACE fasttablespace;
  ALTER TABLE myschema.distributors SET SCHEMA yourschema;
  </programlisting>
    </para>
- 
   </refsect1>
  
   <refsect1>
--- 912,917 ----
*** doc/src/sgml/ref/create_table.sgml
--- doc/src/sgml/ref/create_table.sgml
***************
*** 51,57 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
--- 51,58 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   EXCLUSION [USING <replaceable class="parameter">index_method</replaceable>] (<replaceable class="parameter">expression</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ...]) <replaceable class="parameter">index_parameters</replaceable> [WHERE <replaceable class="parameter">predicate</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>
***************
*** 524,529 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 525,593 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>EXCLUSION [USING <replaceable class="parameter">index_method</replaceable>] (<replaceable class="parameter">expression</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ...]) <replaceable class="parameter">index_parameters</replaceable> [WHERE <replaceable class="parameter">predicate</replaceable>]</literal></term>
+     <listitem>
+      <para>
+       The <literal>EXCLUSION</> clause specifies an operator exclusion
+       constraint. An operator exclusion constraint is more general
+       than a <literal>UNIQUE</literal> constraint, and can use any
+       operator to detect conflicts. For instance, you can specify the
+       constraint that no two tuples in the table contain overlapping
+       circles (see <xref linkend="datatype-geometric">) by using
+       the <literal>&&</literal> operator.
+      </para>
+ 
+      <para>
+       Note that the constraint is specifying the conflict condition,
+       so the operator should return <literal>TRUE</literal> when
+       applied to two conflicting values.
+      </para>
+ 
+      <para>
+       Internally, operator exclusion constraints use an index to
+       perform a search looking for conflicting values, and handle
+       concurrent operations similar to a <literal>UNIQUE</literal>
+       constraint. The operator specified must be commutative (that is,
+       the commutator of the operator must be the operator itself),
+       must be a boolean operator, and must be associated with an
+       operator class (see <xref linkend="SQL-CREATEOPCLASS">) using
+       <replaceable class="parameter">index_method</replaceable>. The
+       constraint is violated if, and only if, there exist two tuples
+       where all corresponding expressions between the tuples conflict
+       according
+       to <replaceable class="parameter">operator</replaceable>
+       (i.e. the operator returns <literal>TRUE</literal>).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">index_parameters</replaceable>
+       are the same as for a <literal>UNIQUE</literal> constraint, and
+       the <replaceable class="parameter">predicate</replaceable>
+       allows you to specify the constraint on a subset of the table
+       (internally using a partial index;
+       see <xref linkend="SQL-CREATEINDEX">). The <replaceable class="parameter">expression</replaceable>
+       is normally just a column name, but can also be an expression
+       and the constraint will be on the result of the function
+       (similar to creating a unique index over an expression).
+      </para>
+ 
+      <para>
+       If all of the operators are specified as the equality operator
+       (usually <literal>=</literal>), this constraint behaves
+       identically to a <literal>UNIQUE</literal> constraint, but may
+       exhibit slightly worse performance than
+       specifying <literal>UNIQUE</literal> (operator exclusion
+       constraints require one additional index search). The advantage
+       of operator exclusion constraints is the ability to specify more
+       general constraints (like a non-overlapping constraint for
+       circles), and also the ability to use index methods other
+       than <literal>btree</literal>, like <literal>GiST</literal>
+       (see <xref linkend="GiST">).
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1087,1092 **** CREATE TABLE cinemas (
--- 1151,1168 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUSION USING gist (c CHECK WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** src/backend/access/index/indexam.c
--- src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
***************
*** 64,72 ****
--- 65,77 ----
  
  #include "access/relscan.h"
  #include "access/transam.h"
+ #include "miscadmin.h"
  #include "pgstat.h"
  #include "storage/bufmgr.h"
  #include "storage/lmgr.h"
+ #include "storage/lwlock.h"
+ #include "storage/procarray.h"
+ #include "utils/lsyscache.h"
  #include "utils/relcache.h"
  #include "utils/snapmgr.h"
  #include "utils/tqual.h"
***************
*** 116,122 **** do { \
  static IndexScanDesc index_beginscan_internal(Relation indexRelation,
  						 int nkeys, ScanKey key);
  
- 
  /* ----------------------------------------------------------------
   *				   index_ interface functions
   * ----------------------------------------------------------------
--- 121,126 ----
*** src/backend/bootstrap/bootparse.y
--- src/backend/bootstrap/bootparse.y
***************
*** 265,271 **** Boot_DeclareIndexStmt:
  								LexIDStr($8),
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 265,271 ----
  								LexIDStr($8),
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 283,289 **** Boot_DeclareUniqueIndexStmt:
  								LexIDStr($9),
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 283,289 ----
  								LexIDStr($9),
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** src/backend/bootstrap/bootstrap.c
--- src/backend/bootstrap/bootstrap.c
***************
*** 1279,1284 **** index_register(Oid heap,
--- 1279,1287 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraint = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** src/backend/catalog/heap.c
--- src/backend/catalog/heap.c
***************
*** 671,676 **** InsertPgClassTuple(Relation pg_class_desc,
--- 671,677 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1671,1676 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1672,1678 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** src/backend/catalog/index.c
--- src/backend/catalog/index.c
***************
*** 727,742 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 727,749 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraint != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUSION");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 756,761 **** index_create(Oid heapRelationId,
--- 763,769 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraint,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 801,806 **** index_create(Oid heapRelationId,
--- 809,848 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 			/* Increment pg_class.relopxconstraints for the index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1080,1085 **** BuildIndexInfo(Relation index)
--- 1122,1131 ----
  	/* other info */
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 		ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ 	else
+ 		ii->ii_ExclusionConstraint = NULL;
  
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
***************
*** 1891,1896 **** IndexBuildHeapScan(Relation heapRelation,
--- 1937,1945 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't supported at index build time. */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2265,2270 **** validate_index_heapscan(Relation heapRelation,
--- 2314,2322 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/* operator exclusion constraints aren't supported at index build time. */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
  }
  
  
***************
*** 2520,2522 **** reindex_relation(Oid relid, bool toast_too)
--- 2572,2575 ----
  
  	return result;
  }
+ 
*** src/backend/catalog/information_schema.sql
--- src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** src/backend/catalog/pg_constraint.c
--- src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *constrategiesArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ 		constrategiesArray = construct_array(strategyDatums,
+ 											 constraintNKeys,
+ 											 INT2OID,
+ 											 sizeof(int16), true, 's');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (constrategiesArray)
+ 		values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ 	else
+ 		nulls[Anum_pg_constraint_constrategies - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 548,555 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 564,583 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** src/backend/catalog/toasting.c
--- src/backend/catalog/toasting.c
***************
*** 242,247 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 242,250 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** src/backend/commands/indexcmds.c
--- src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 251,257 **** DefineIndex(RangeVar *heapRelation,
  												   NULL,
  												   "pkey",
  												   namespaceId);
! 		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
  
--- 250,256 ----
  												   NULL,
  												   "pkey",
  												   namespaceId);
! 		else if (unique)
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
  
***************
*** 260,265 **** DefineIndex(RangeVar *heapRelation,
--- 259,273 ----
  												   "key",
  												   namespaceId);
  		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  	}
  
  	/*
***************
*** 424,429 **** DefineIndex(RangeVar *heapRelation,
--- 432,440 ----
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* operator exclusion constraints aren't supported at index build time. */
+ 	indexInfo->ii_ExclusionConstraint = exclusion_constraint;
+ 
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
  	ComputeIndexAttrs(indexInfo, classObjectId, coloptions, attributeList,
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 446,468 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type;
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUSION";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 478,484 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 773,780 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 964,970 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** src/backend/commands/tablecmds.c
--- src/backend/commands/tablecmds.c
***************
*** 304,309 **** static void ATAddCheckConstraint(List **wqueue,
--- 304,312 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 4515,4520 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4518,4524 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4578,4583 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4582,4622 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 			else
+ 			{
+ 				char *choose_name2 = "";
+ 				IndexElem *ie = linitial(newConstraint->operator_exclusion);
+ 
+ 				if (ie->name != NULL)
+ 					choose_name2 = ie->name;
+ 
+ 				newConstraint->conname =
+ 					ChooseConstraintName(RelationGetRelationName(rel),
+ 										 choose_name2,
+ 										 "exclusion",
+ 										 RelationGetNamespace(rel),
+ 										 NIL);
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 4947,4952 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 4986,4992 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 4983,4988 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5023,5194 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	Oid			 constrOid;
+ 	Oid			 indexOid;
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	int16		*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support gettuple",
+ 							   constraint->using_method)));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(int16) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = LookupOperName(NULL, opname, typoid, typoid, false, -1);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", quote_identifier(get_opname(opid))),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			elog(ERROR, "no strategy found for operator %d "
+ 				 "in operator family %d", opid, opfamily);
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint. Set isconstraint to
+ 	 * false because we're building the constraint entry ourselves.
+ 	 */
+ 	DefineIndex(rv, /* relation range var */
+ 				NULL,		/* index name */
+ 				InvalidOid,	/* predefined OID */
+ 				constraint->using_method,	/* am name */
+ 				constraint->indexspace, /* index tablespace */
+ 				indexElems,	/* parameters */
+ 				(Expr *) constraint->where_clause, /* where */
+ 				constraint->options, /* options */
+ 				exclusion_constraint, /* exclusion constraint */
+ 				false, /* unique */
+ 				false, /* primary */
+ 				true, /* is constraint? */
+ 				constraint->deferrable, /* deferrable */
+ 				constraint->initdeferred, /* init deferred */
+ 				true,	/* is_alter_table? */
+ 				true,	/* check rights? */
+ 				false,   /* skip build? */
+ 				false,   /* quiet? */
+ 				false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Increment pg_class.relopxconstraints for heap only, the index's
+ 	 * entry should already have been incremented by index_create().
+ 	 */
+ 	{
+ 		Relation			pgrel;
+ 		Form_pg_class		heap_class;
+ 		HeapTuple			relTup;
+ 
+ 		pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 		/* Increment the count for the heap. */
+ 		relTup = SearchSysCacheCopy(RELOID,
+ 									ObjectIdGetDatum(RelationGetRelid(rel)),
+ 									0, 0, 0);
+ 		if (!HeapTupleIsValid(relTup))
+ 			elog(ERROR, "cache lookup failed for relation %u",
+ 				 RelationGetRelid(rel));
+ 		heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 		heap_class->relopxconstraints++;
+ 
+ 		simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 		CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 		heap_freetuple(relTup);
+ 
+ 		heap_close(pgrel, RowExclusiveLock);
+ 	}
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** src/backend/commands/typecmds.c
--- src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** src/backend/executor/execUtils.c
--- src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 69,75 **** int			NIndexTupleProcessed;
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *						statistic functions
--- 73,87 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static void index_check_constraint(Relation heap, Relation index,
! 								   TupleTableSlot *slot, ItemPointer tupleid,
! 								   Datum *values, bool *isnull,
! 								   int16 *exclusion_constraint,
! 								   List *index_exprs, ExprContext *econtext);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 ExprContext *econtext, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
  
  /* ----------------------------------------------------------------
   *						statistic functions
***************
*** 1167,1172 **** ExecInsertIndexTuples(TupleTableSlot *slot,
--- 1179,1191 ----
  			result = lappend_oid(result, RelationGetRelid(indexRelation));
  		}
  
+ 		if (indexInfo->ii_ExclusionConstraint != NULL)
+ 			index_check_constraint(heapRelation, indexRelation, slot,
+ 								   tupleid, values, isnull,
+ 								   indexInfo->ii_ExclusionConstraint,
+ 								   indexInfo->ii_ExpressionsState,
+ 								   econtext);
+ 
  		/*
  		 * keep track of index inserts for debugging
  		 */
***************
*** 1297,1299 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1316,1551 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ static void
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ 					   ItemPointer tupleid, Datum *values, bool *isnull,
+ 					   int16 *exclusion_constraint, List *index_exprs,
+ 					   ExprContext *econtext)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	Oid					*constr_procs;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	for (i = 0; i < index_natts; i++)
+ 		if (isnull[i])
+ 			return;
+ 
+ 	/*
+ 	 * Find the function that tests for a conflict based on the
+ 	 * strategy number, operator family, and types.
+ 	 */
+ 	constr_procs = palloc(sizeof(Oid) * index_natts);
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		/*
+ 		 * Find the procedure implementing the strategy for the
+ 		 * index for two arguments both with the type of the
+ 		 * indexed attribute.
+ 		 */
+ 		Oid				oper;
+ 		Oid				opfamily = index->rd_opfamily[i];
+ 		Oid				typoid = index->rd_opcintype[i];
+ 		StrategyNumber	strategy = exclusion_constraint[i];
+ 
+ 		if (strategy == InvalidStrategy)
+ 			continue;
+ 
+ 		oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+ 
+ 		if(OidIsValid(oper))
+ 			constr_procs[i] = get_opcode(oper);
+ 		else
+ 			elog(ERROR, "cannot determine operator for type %d and "
+ 				 "strategy %d", typoid, strategy);
+ 	}
+ 
+ 	InitDirtySnapshot(DirtySnapshot);
+ 
+ 	/*
+ 	 * Now search the tuples that are actually in the index for
+ 	 * any violations.
+ 	 */
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			continue;
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	/*
+ 	 * We have to find all tuples, even those not visible yet.
+ 	 */
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		if(ItemPointerEquals(tupleid, &tup->t_data->t_ctid))
+ 		{
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, econtext, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/* If the in-progress inserting transaction aborts, proceed. */
+ 		if (TransactionIdIsValid(DirtySnapshot.xmin))
+ 		{
+ 			XactLockTableWait(DirtySnapshot.xmin);
+ 			if (TransactionIdDidAbort(DirtySnapshot.xmin))
+ 				continue;
+ 		}
+ 
+ 		/* If the in-progress deleting transaction commits, proceed. */
+ 		if (TransactionIdIsValid(DirtySnapshot.xmax))
+ 		{
+ 			XactLockTableWait(DirtySnapshot.xmax);
+ 			if (TransactionIdDidCommit(DirtySnapshot.xmax))
+ 				continue;
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Existing tuple \"%s\" conflicts with new tuple "
+ 						   "\"%s\".", tuple_as_string(existing_slot),
+ 						   tuple_as_string(new_slot))));
+ 	}
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	pfree(constr_procs);
+ 
+ 	Assert(found_self);
+ 
+ 	return;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 ExprContext *econtext, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ 	TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+ 	StringInfoData		buf;
+ 	int					i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < tupdesc->natts; i++)
+ 	{
+ 		char	*strval;
+ 		Datum	 value;
+ 		bool	 isnull;
+ 
+ 		value = slot_getattr(slot, i + 1, &isnull);
+ 
+ 		if (isnull)
+ 			strval = "null";
+ 		else
+ 		{
+ 			Oid		foutoid;
+ 			bool	typisvarlena;
+ 
+ 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ 							  &typisvarlena);
+ 			strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, strval);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	return buf.data;
+ }
*** src/backend/nodes/copyfuncs.c
--- src/backend/nodes/copyfuncs.c
***************
*** 2075,2080 **** _copyConstraint(Constraint *from)
--- 2075,2083 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** src/backend/nodes/equalfuncs.c
--- src/backend/nodes/equalfuncs.c
***************
*** 2082,2087 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2082,2090 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** src/backend/nodes/outfuncs.c
--- src/backend/nodes/outfuncs.c
***************
*** 2340,2345 **** _outConstraint(StringInfo str, Constraint *node)
--- 2340,2353 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** src/backend/parser/gram.y
--- src/backend/parser/gram.y
***************
*** 427,432 **** static TypeName *TableFuncTypeName(List *columns);
--- 427,433 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 470,476 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION
--- 471,477 ----
  	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 EXCLUSION EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION
***************
*** 2480,2485 **** ConstraintElem:
--- 2481,2501 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUSION access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace ConstraintAttributeSpec
+ 				where_clause
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->deferrable		  = ($8 & 1) != 0;
+ 					n->initdeferred		  = ($8 & 2) != 0;
+ 					n->where_clause		  = $9;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2520,2525 **** key_match:  MATCH FULL
--- 2536,2552 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem CHECK WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $4);
+ 			}
+ 		;
  /*
   * We combine the update and delete actions into one value temporarily
   * for simplicity of parsing, and then break them down again in the
*** src/backend/parser/parse_utilcmd.c
--- src/backend/parser/parse_utilcmd.c
***************
*** 69,75 **** typedef struct
  	List	   *columns;		/* ColumnDef items */
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
! 	List	   *ixconstraints;	/* index-creating constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
--- 69,76 ----
  	List	   *columns;		/* ColumnDef items */
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
! 	List	   *idxconstraints;	/* index-creating constraints */
! 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 172,178 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.ixconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
--- 173,180 ----
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.idxconstraints = NIL;
! 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 458,464 **** transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
  			case CONSTR_UNIQUE:
  				if (constraint->keys == NIL)
  					constraint->keys = list_make1(makeString(column->colname));
! 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
  				break;
  
  			case CONSTR_CHECK:
--- 460,466 ----
  			case CONSTR_UNIQUE:
  				if (constraint->keys == NIL)
  					constraint->keys = list_make1(makeString(column->colname));
! 				cxt->idxconstraints = lappend(cxt->idxconstraints, constraint);
  				break;
  
  			case CONSTR_CHECK:
***************
*** 497,507 **** static void
  transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
  						 Constraint *constraint)
  {
  	switch (constraint->contype)
  	{
  		case CONSTR_PRIMARY:
  		case CONSTR_UNIQUE:
! 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
  			break;
  
  		case CONSTR_CHECK:
--- 499,511 ----
  transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
  						 Constraint *constraint)
  {
+ 	ListCell *l;
+ 
  	switch (constraint->contype)
  	{
  		case CONSTR_PRIMARY:
  		case CONSTR_UNIQUE:
! 			cxt->idxconstraints = lappend(cxt->idxconstraints, constraint);
  			break;
  
  		case CONSTR_CHECK:
***************
*** 512,517 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 516,557 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/* preprocess index expressions and predicate */
+ 			foreach(l, constraint->operator_exclusion)
+ 			{
+ 				List			*pair = lfirst(l);
+ 				IndexElem		*ielem;
+ 
+ 				Assert(list_length(pair) == 2);
+ 
+ 				ielem = linitial(pair);
+ 				Assert(IsA(ielem, IndexElem));
+ 
+ 				if (ielem->expr)
+ 				{
+ 					ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 					/*
+ 					 * We check only that the result type is
+ 					 * legitimate; this is for consistency with what
+ 					 * transformWhereClause() checks for the
+ 					 * predicate.  DefineIndex() will make more
+ 					 * checks.
+ 					 */
+ 					if (expression_returns_set(ielem->expr))
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 								 errmsg("index expression cannot return a set")
+ 									));
+ 				}
+ 			}
+ 			if (constraint->where_clause)
+ 				constraint->where_clause = transformWhereClause(
+ 					pstate, constraint->where_clause, "WHERE");
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 1000,1006 **** transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
  	 * 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.
  	 */
! 	foreach(lc, cxt->ixconstraints)
  	{
  		Constraint *constraint = (Constraint *) lfirst(lc);
  
--- 1040,1046 ----
  	 * 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.
  	 */
! 	foreach(lc, cxt->idxconstraints)
  	{
  		Constraint *constraint = (Constraint *) lfirst(lc);
  
***************
*** 1738,1744 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.ixconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
--- 1778,1785 ----
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.idxconstraints = NIL;
! 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1752,1757 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1793,1799 ----
  	foreach(lcmd, stmt->cmds)
  	{
  		AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
+ 		RangeTblEntry *rte;
  
  		switch (cmd->subtype)
  		{
***************
*** 1784,1789 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1826,1841 ----
  				/*
  				 * The original AddConstraint cmd node doesn't go to newcmds
  				 */
+ 
+ 				/*
+ 				 * Put the parent table into the rtable so that the
+ 				 * expressions can refer to its fields without
+ 				 * qualification.
+ 				 */
+ 				rte = addRangeTableEntry(pstate, stmt->relation, NULL, false,
+ 										 true);
+ 				addRTEtoQuery(pstate, rte, false, true, true);
+ 
  				if (IsA(cmd->def, Constraint))
  				{
  					transformTableConstraint(pstate, &cxt,
***************
*** 1844,1850 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 1896,1905 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1859,1864 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1914,1926 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
*** src/backend/storage/ipc/ipci.c
--- src/backend/storage/ipc/ipci.c
***************
*** 15,20 ****
--- 15,21 ----
  #include "postgres.h"
  
  #include "access/clog.h"
+ #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/multixact.h"
  #include "access/nbtree.h"
*** src/backend/tcop/utility.c
--- src/backend/tcop/utility.c
***************
*** 792,797 **** ProcessUtility(Node *parsetree,
--- 792,798 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** src/backend/utils/adt/ruleutils.c
--- src/backend/utils/adt/ruleutils.c
***************
*** 141,146 **** static char *deparse_expression_pretty(Node *expr, List *dpcontext,
--- 141,148 ----
  static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
  static void decompile_column_index_array(Datum column_index_array, Oid relId,
  							 StringInfo buf);
+ static void decompile_column_strategy_array(Datum column_strategy_array,
+ 											Oid indexOid, StringInfo buf);
  static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
  static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
  					   bool attrsOnly, bool showTblSpc,
***************
*** 1153,1158 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1155,1181 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				Datum	val;
+ 				bool	isnull;
+ 				Oid		indexOid = conForm->conindid;
+ 
+ 				/* Fetch constraint expression in parsetree form */
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_constrategies,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null conbin for constraint %u",
+ 						 constraintId);
+ 
+ 				appendStringInfo(&buf, "(");
+ 				decompile_column_strategy_array(val, indexOid, &buf);
+ 				appendStringInfo(&buf, ") USING INDEX %s",
+ 								 quote_identifier(get_rel_name(indexOid)));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1200,1205 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1223,1276 ----
  	}
  }
  
+ /*
+  * Convert an int16[] Datum into a comma-separated list of column
+  * names and operators for the indicated index; append the list to
+  * buf.
+  */
+ static void
+ decompile_column_strategy_array(Datum column_strategy_array, Oid indexOid,
+ 								StringInfo buf)
+ {
+ 	Datum	   *keys;
+ 	int			nKeys;
+ 	int			j;
+ 	Relation	indexRelation;
+ 
+ 	/* Extract data from array of int16 */
+ 	deconstruct_array(DatumGetArrayTypeP(column_strategy_array),
+ 					  INT2OID, 2, true, 's',
+ 					  &keys, NULL, &nKeys);
+ 
+ 	indexRelation = relation_open(indexOid, AccessShareLock);
+ 
+ 	for (j = 0; j < nKeys; j++)
+ 	{
+ 		Oid		 opid;
+ 		char	*opName;
+ 		Oid		 opfamily = indexRelation->rd_opfamily[j];
+ 		char	*colName  = get_relid_attribute_name(indexOid, j + 1);
+ 		Oid		 colType  = indexRelation->rd_opcintype[j];
+ 		int16	 strategy = DatumGetInt16(keys[j]);
+ 
+ 		opid = get_opfamily_member(opfamily, colType, colType, strategy);
+ 		opName = get_opname(opid);
+ 
+ 		if (colName == NULL || opName == NULL)
+ 			elog(ERROR, "unexpected error: cannot determine column and "
+ 				 "operator names");
+ 
+ 		if (j == 0)
+ 			appendStringInfo(buf, "%s %s", quote_identifier(colName),
+ 							 quote_identifier(opName));
+ 		else
+ 			appendStringInfo(buf, ", %s %s", quote_identifier(colName),
+ 							 quote_identifier(opName));
+ 	}
+ 
+ 	relation_close(indexRelation, NoLock);
+ }
+ 
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** src/backend/utils/cache/relcache.c
--- src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
  #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"
***************
*** 3029,3034 **** CheckConstraintFetch(Relation relation)
--- 3030,3106 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ 	Relation	conrel;
+ 	SysScanDesc conscan;
+ 	ScanKeyData skey[1];
+ 	HeapTuple	htup;
+ 	Datum		val;
+ 	bool		isnull;
+ 	bool		found = false;
+ 	int16	   *constraints;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conindid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(RelationGetRelid(indexRelation)));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintIndidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_constrategies,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null constrategies for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != INT2OID)
+ 			elog(ERROR, "constrategies is not a 1-D smallint array");
+ 		constraints = palloc(sizeof(int16) * nelem);
+ 		memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	return constraints;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** src/bin/psql/describe.c
--- src/bin/psql/describe.c
***************
*** 1033,1038 **** describeOneTableDetails(const char *schemaname,
--- 1033,1039 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1054,1060 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1055,1076 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1122,1127 **** describeOneTableDetails(const char *schemaname,
--- 1138,1145 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1575,1580 **** describeOneTableDetails(const char *schemaname,
--- 1593,1631 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** src/include/access/genam.h
--- src/include/access/genam.h
***************
*** 16,21 ****
--- 16,23 ----
  
  #include "access/sdir.h"
  #include "access/skey.h"
+ #include "access/xact.h"
+ #include "executor/tuptable.h"
  #include "nodes/tidbitmap.h"
  #include "storage/buf.h"
  #include "storage/lock.h"
*** src/include/catalog/indexing.h
--- src/include/catalog/indexing.h
***************
*** 113,118 **** DECLARE_INDEX(pg_constraint_conname_nsp_index, 2664, on pg_constraint using btre
--- 113,120 ----
  /* This following index is not used for a cache and is not unique */
  DECLARE_INDEX(pg_constraint_conrelid_index, 2665, on pg_constraint using btree(conrelid oid_ops));
  #define ConstraintRelidIndexId	2665
+ DECLARE_INDEX(pg_constraint_conindid_index, 2730, on pg_constraint using btree(conindid oid_ops));
+ #define ConstraintIndidIndexId	2730
  /* This following index is not used for a cache and is not unique */
  DECLARE_INDEX(pg_constraint_contypid_index, 2666, on pg_constraint using btree(contypid oid_ops));
  #define ConstraintTypidIndexId	2666
*** src/include/catalog/pg_attribute.h
--- src/include/catalog/pg_attribute.h
***************
*** 423,436 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 423,437 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 449,462 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 450,464 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** src/include/catalog/pg_class.h
--- src/include/catalog/pg_class.h
***************
*** 53,58 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP
--- 53,59 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 86,92 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 87,93 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 104,117 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 105,119 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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
***************
*** 123,135 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 125,137 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** src/include/catalog/pg_constraint.h
--- src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	int2		constrategies[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_constrategies	20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** src/include/commands/defrem.h
--- src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** src/include/nodes/execnodes.h
--- src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	int16	   *ii_ExclusionConstraint;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** src/include/nodes/parsenodes.h
--- src/include/nodes/parsenodes.h
***************
*** 1377,1382 **** typedef enum ConstrType			/* types of constraints */
--- 1377,1383 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1411,1421 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1412,1427 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** src/include/parser/kwlist.h
--- src/include/parser/kwlist.h
***************
*** 143,148 **** PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
--- 143,149 ----
  PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
  PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
  PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
+ PG_KEYWORD("exclusion", EXCLUSION, UNRESERVED_KEYWORD)
  PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
  PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
  PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD)
*** src/include/storage/lwlock.h
--- src/include/storage/lwlock.h
***************
*** 67,72 **** typedef enum LWLockId
--- 67,73 ----
  	AutovacuumLock,
  	AutovacuumScheduleLock,
  	SyncScanLock,
+ 	IndexConstraintLock,
  	/* Individual lock IDs end here */
  	FirstBufMappingLock,
  	FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
*** src/include/utils/relcache.h
--- src/include/utils/relcache.h
***************
*** 15,20 ****
--- 15,21 ----
  #define RELCACHE_H
  
  #include "access/tupdesc.h"
+ #include "access/skey.h"
  #include "nodes/bitmapset.h"
  #include "nodes/pg_list.h"
  
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 44,50 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** src/test/regress/input/constraints.source
--- src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,392 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ CREATE TABLE circles (c1 CIRCLE, c2 TEXT);
+ 
+ ALTER TABLE circles
+   ADD EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK 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>');
+ -- should fail
+ 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>');
+ 
+ DROP TABLE circles;
+ 
+ 
*** src/test/regress/output/constraints.source
--- src/test/regress/output/constraints.source
***************
*** 267,273 **** SELECT * FROM INSERT_TBL;
  CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
  	CONSTRAINT COPY_CON
  	CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
! COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
  SELECT '' AS two, * FROM COPY_TBL;
   two | x |       y       | z 
  -----+---+---------------+---
--- 267,273 ----
  CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
  	CONSTRAINT COPY_CON
  	CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
! COPY COPY_TBL FROM '/home/jdavis/wd/git/postgresql/src/test/regress/data/constro.data';
  SELECT '' AS two, * FROM COPY_TBL;
   two | x |       y       | z 
  -----+---+---------------+---
***************
*** 275,281 **** SELECT '' AS two, * FROM COPY_TBL;
       | 6 | OK            | 4
  (2 rows)
  
! COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
  ERROR:  new row for relation "copy_tbl" violates check constraint "copy_con"
  CONTEXT:  COPY copy_tbl, line 2: "7	check failed	6"
  SELECT * FROM COPY_TBL;
--- 275,281 ----
       | 6 | OK            | 4
  (2 rows)
  
! COPY COPY_TBL FROM '/home/jdavis/wd/git/postgresql/src/test/regress/data/constrf.data';
  ERROR:  new row for relation "copy_tbl" violates check constraint "copy_con"
  CONTEXT:  COPY copy_tbl, line 2: "7	check failed	6"
  SELECT * FROM COPY_TBL;
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,534 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (c1 CIRCLE, c2 TEXT);
+ ALTER TABLE circles
+   ADD EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=)
+   WHERE circle_center(c1) <> '(0,0)';
+ NOTICE:  ALTER TABLE / ADD EXCLUSION 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Existing tuple "(<(10,10),10>, <(0,0), 5>)" conflicts with new tuple "(<(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>');
+ DROP TABLE circles;
#126Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#125)
Re: operator exclusion constraints

On Sun, Sep 27, 2009 at 5:47 PM, Jeff Davis <pgsql@j-davis.com> wrote:

Attached is a new patch. I ran it through filterdiff, but in case that
didn't work for some reason, I attached a gzipped version of the
original unified diff produced by git.

 * Changed underlying algorithm to match Tom's suggestion: do the second
index lookup after already inserting into the first.
 * Language changes, including the latest "<expression> CHECK WITH <op>"
idea from Tom, seconded by Robert Haas.
 * automatically builds index for you, no need to create it separately,
just specify the index AM (or let it default to btree)
 * Only one constraint per index is allowed, and the index is treated
entirely as an internal implementation detail.
 * Support for predicates (partial constraints/partial index)
 * Support for expressions
 * Support for other index options, like WITH list and USING INDEX
TABLESPACE
 * Docs updated and improved
 * Tests updated
 * Full recheck support (the previous recheck mechanism didn't work for
expressions)
 * Make information_schema ignore operator exclusion constraints
 * error message improvements

When testing/reviewing, use the documentation from CREATE TABLE, but use
the ALTER TABLE variant instead. Right now the CREATE TABLE variant
doesn't work (see below).

There is still a significant TODO list:

 * CREATE TABLE -- right now, it only works for ALTER TABLE, and the
docs are lying. Coming soon.

 * psql - haven't updated it to keep up with the language changes

 * pg_dump

 * LIKE

 * Inheritance

 * Enforce on existing tuples when the constraint is created -- This is
intertwined with inheritance, I think, and I am still working on that.
Obviously, this is an important TODO item to get the patch ready for
commit.

 * Deferrability (optional for now) -- I need the trigger to be able to
perform the check as well. It looks like it has most of the information
necessary, but I'm trying to determine where would be the cleanest place
to export the constraint checking function so that it can be called by
the trigger as well as ExecInsertIndexTuples and the bulk checker (that
checks existing tuples at the time the constraint is added).

 * GIN support (optional for now) -- I need to create a gingettuple
method. It would have to be a wrapper around gingetbitmap, and would not
be any more efficient than gingetbitmap, but it would allow my patch to
work for GIN indexes.

I think I've made some progress this commitfest, both in terms of
decisions made (thanks to everyone who provided input and direction),
and also in terms of code. I would still appreciate more review during
this commitfest, but realistically, it will still be at least another
week before I can say that I'm done with all open items.

In that case, I think we should target this for the next CommitFest.
Especially given the number and complexity of the patches remaining
for this CommitFest, I feel very uncomfortable with the idea of
waiting another week for a new patch version, and then possibly still
needing further changes before it is finally committed. While we
allow patches to be resubmitted for the same CommitFest, this is
intended to be for minor adjustments, not significant rewrites.

So I'm going to mark this Returned with Feedback.

...Robert

#127Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#126)
Re: operator exclusion constraints

On Sun, 2009-09-27 at 21:38 -0400, Robert Haas wrote:

In that case, I think we should target this for the next CommitFest.
Especially given the number and complexity of the patches remaining
for this CommitFest, I feel very uncomfortable with the idea of
waiting another week for a new patch version, and then possibly still
needing further changes before it is finally committed. While we
allow patches to be resubmitted for the same CommitFest, this is
intended to be for minor adjustments, not significant rewrites.

OK, I expected that to be the case. I got significant feedback at the
beginning of this commitfest that required some substantial language
changes. I did find this commitfest extremely productive for my feature.

Right now I'm trying to provide some useful feedback to Paval for his
patch.

Regards,
Jeff Davis

#128Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#127)
Re: operator exclusion constraints

On Sun, Sep 27, 2009 at 10:13 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Sun, 2009-09-27 at 21:38 -0400, Robert Haas wrote:

In that case, I think we should target this for the next CommitFest.
Especially given the number and complexity of the patches remaining
for this CommitFest, I feel very uncomfortable with the idea of
waiting another week for a new patch version, and then possibly still
needing further changes before it is finally committed.   While we
allow patches to be resubmitted for the same CommitFest, this is
intended to be for minor adjustments, not significant rewrites.

OK, I expected that to be the case. I got significant feedback at the
beginning of this commitfest that required some substantial language
changes. I did find this commitfest extremely productive for my feature.

Excellent, glad to hear it.

Right now I'm trying to provide some useful feedback to Paval for his
patch.

Thanks, I deeply appreciate that. I believe that there are 29 people
who submitted patches for this CommitFest, and that 4 of them are
reviewing, yourself included. Furthermore, patches and feature
proposals from people who are not themselves helping with the
CommitFest have continued to roll in during this CommitFest.
Personally, I find this quite objectionable. Apparently, CommitFest
no longer means a time when people put aside their own patches to
review those of others; it seems now to mean a time when 87% of the
patch authors either continue development or ignore the CommitFest
completely.

Fortunately, a number of very competent people who did NOT submit
patches nevertheless volunteered to help review, so we may be OK. But
I am not sure this is a very sustainable solution. If everyone who
submitted a pach for this CF had also reviewed one, every patch would
now have a review and there would even be enough reviewers for major
patches to have two each. Instead, we are still struggling to get
every patch looked at once.

...Robert

#129Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#128)
Re: operator exclusion constraints

On Sun, 2009-09-27 at 22:40 -0400, Robert Haas wrote:

Apparently, CommitFest
no longer means a time when people put aside their own patches to
review those of others; it seems now to mean a time when 87% of the
patch authors either continue development or ignore the CommitFest
completely.

Well, I'm not completely innocent here, either. I also spent time making
progress on my patch, both in terms of code and discussion so that I
would at least have enough information to get it ready during the next
development cycle.

We don't have a clear "design review" stage that allows developers an
opportunity to get thorough feedback during the development cycle, so
the commitfest is also the design review stage by default. I got some
comments when I posted my design on 8/16, but it didn't really get
hashed out until a month later when the commitfest was underway.

The ideal is to propose, design, implement, and then submit for the
commitfest. The reality is that the commitfest is often the first time
the developer gets thorough feedback on the design. So, as a developer,
I'm hesitant to polish a patch before the commitfest because I know
significant changes will be required. Hopefully that didn't waste too
much of Brendan's time.

That's just an observation from my experience with my patch. I know it's
easy to point at little inefficiencies from afar, so I'm not suggesting
we change our process. Overall, real progress is being made for the
project in general and my patch in particular, so I'm more than willing
to set my minor frustrations aside as long as that continues.

Regards,
Jeff Davis

#130Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#129)
Re: operator exclusion constraints

On Sun, Sep 27, 2009 at 11:31 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Sun, 2009-09-27 at 22:40 -0400, Robert Haas wrote:

Apparently, CommitFest
no longer means a time when people put aside their own patches to
review those of others; it seems now to mean a time when 87% of the
patch authors either continue development or ignore the CommitFest
completely.

Well, I'm not completely innocent here, either. I also spent time making
progress on my patch, both in terms of code and discussion so that I
would at least have enough information to get it ready during the next
development cycle.

I don't see any problem with that. As long as everyone is willing to
spend SOME time on their own patch and SOME time reviewing the work of
others, the system works. After all, the time to review a patch is,
IME, far shorter than the time to develop one. But right now we have
a large majority of patch authors who are not contributing ANYTHING to
the CommitFest, and that's not so good.

...Robert

#131Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#114)
Re: operator exclusion constraints [was: generalized index constraints]

On Sun, 2009-09-20 at 17:54 -0400, Tom Lane wrote:

(I'm wondering a bit if anyone will want a WHERE clause, too, though
adding that later shouldn't pose any big syntactic obstacles.)

Where should I put the WHERE clause? My current syntax (with patch) is:

[ CONSTRAINT constraint_name ]
EXCLUSION [USING index_method] (expression CHECK WITH operator [, ...]) index_parameters }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
[ WHERE predicate ]

That's a little awkward to document, because WHERE is only supported for
operator exclusion constraints, so it doesn't just fit along side CHECK
and FOREIGN KEY. My only concern is that it would make the CREATE TABLE
syntax slightly harder to read.

We could allow the WHERE clause to be syntactically correct for all the
other constraints, but throw a "not implemented" error if they try to
use it. I'm not sure if that fits nicely with the spec or not.

I tried to move the WHERE clause right before or after the
index_parameters, but that resulted in shift/reduce conflicts.

Thoughts?

Regards,
Jeff Davis

#132Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#131)
Re: operator exclusion constraints [was: generalized index constraints]

Jeff Davis <pgsql@j-davis.com> writes:

I tried to move the WHERE clause right before or after the
index_parameters, but that resulted in shift/reduce conflicts.

You could avoid the conflicts in at least two ways:

* require parens around the WHERE expression

* stick the WHERE inside the EXCLUSION ( ... ) bit, so that its
expression is terminated by the outer right paren.

Not sure if either of these is less ugly than putting it at the end,
though :-(. They both seem a bit surprising.

regards, tom lane

#133Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#113)
2 attachment(s)
Re: operator exclusion constraints

Here's another WIP patch for operator exclusion constraints (both
patches are the same, but one is a context diff made with filterdiff).

It's almost done, and the only reason I'm posting this now is because I
see additional work being done for scalable deferred unique checks, and
I'd like to make sure that we're not interfering with eachother.

Completed Items:

* support for deferred constraints
* enforce constraint when added to a table with existing data
* change predicate to be before DEFERRABLE clause, and require
perentheses around predicate expression
* doc and test updates
* make LIKE and INHERITS ignore operator exclusion constraints (there
was some discussion that we should make LIKE behave more consistently,
but I think that's for a different patch)

Open Items:

* psql support
* pg_dump support
* prevent altering a column that's part of an exclusion constraint in a
way that might cause the exclusion constraint to be violated
* self-review before RRR

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091025.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091025.context.patchDownload
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 61,66 **** ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
--- 61,73 ----
      NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
      SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+ 
+ and <replaceable class="PARAMETER">index_constraint</replaceable> is:
+ 
+     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+     ( <replaceable class="PARAMETER">column_name operator</replaceable> [, ...] ) USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
+ 
+ 
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 905,911 **** ALTER TABLE distributors SET TABLESPACE fasttablespace;
  ALTER TABLE myschema.distributors SET SCHEMA yourschema;
  </programlisting>
    </para>
- 
   </refsect1>
  
   <refsect1>
--- 912,917 ----
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,57 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
--- 51,58 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   EXCLUSION [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">expression</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</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>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 548,613 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>EXCLUSION [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">expression</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
+     <listitem>
+      <para>
+       The <literal>EXCLUSION</> clause specifies an operator exclusion
+       constraint. An operator exclusion constraint is more general
+       than a <literal>UNIQUE</literal> constraint, and can use an
+       arbitrary operator to detect conflicts. For instance, you can
+       specify the constraint that no two tuples in the table contain
+       overlapping circles (see <xref linkend="datatype-geometric">) by
+       using the <literal>&&</literal> operator.
+      </para>
+ 
+      <para>
+       The constraint specifies the conflict condition, so the operator
+       should return <literal>TRUE</literal> when applied to two
+       conflicting values. Also, the operator specified must be
+       commutative (that is, the commutator of the operator must be the
+       operator itself), must be a boolean operator, and must be
+       associated with an operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) using
+       <replaceable class="parameter">index_method</replaceable>. The
+       constraint is violated if, and only if, there exist two tuples
+       where all corresponding expressions between the tuples conflict
+       according
+       to <replaceable class="parameter">operator</replaceable>
+       (i.e. the operator returns <literal>TRUE</literal>).
+      </para>
+ 
+      <para>
+       Internally, operator exclusion constraints use an index to
+       perform a search looking for conflicting values, and handle
+       concurrent operations similar to a <literal>UNIQUE</literal>
+       constraint. If all of the operators are specified as the
+       equality operator (usually <literal>=</literal>), this
+       constraint behaves identically to a <literal>UNIQUE</literal>
+       constraint. However, it may exhibit slightly worse performance
+       than specifying <literal>UNIQUE</literal>, because operator
+       exclusion constraints require one additional index search. The
+       advantage of operator exclusion constraints is the ability to
+       specify more general constraints (like a non-overlapping
+       constraint for circles), and also the ability to use index
+       methods other than <literal>btree</literal>, such
+       as <literal>GiST</literal> (see <xref linkend="GiST">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">index_parameters</replaceable>
+       are the same as for a <literal>UNIQUE</literal> constraint. The <replaceable class="parameter">predicate</replaceable>
+       allows you to specify the constraint on a subset of the table
+       (note the reqiuired parentheses around the predicate
+       expression), internally using a partial index
+       (see <xref linkend="SQL-CREATEINDEX">). The <replaceable class="parameter">expression</replaceable>
+       is normally just a column name, but can also be an expression or
+       function call and the constraint will check the result (similar
+       to creating a unique index over an expression).
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1110,1115 **** CREATE TABLE cinemas (
--- 1171,1188 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUSION USING gist (c CHECK WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
***************
*** 64,72 ****
--- 65,77 ----
  
  #include "access/relscan.h"
  #include "access/transam.h"
+ #include "miscadmin.h"
  #include "pgstat.h"
  #include "storage/bufmgr.h"
  #include "storage/lmgr.h"
+ #include "storage/lwlock.h"
+ #include "storage/procarray.h"
+ #include "utils/lsyscache.h"
  #include "utils/relcache.h"
  #include "utils/snapmgr.h"
  #include "utils/tqual.h"
***************
*** 116,122 **** do { \
  static IndexScanDesc index_beginscan_internal(Relation indexRelation,
  						 int nkeys, ScanKey key);
  
- 
  /* ----------------------------------------------------------------
   *				   index_ interface functions
   * ----------------------------------------------------------------
--- 121,126 ----
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1109 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraint = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraint != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUSION");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraint,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 803,808 **** index_create(Oid heapRelationId,
--- 811,874 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1082,1087 **** BuildIndexInfo(Relation index)
--- 1148,1157 ----
  	/* other info */
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 		ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ 	else
+ 		ii->ii_ExclusionConstraint = NULL;
  
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
***************
*** 1893,1898 **** IndexBuildHeapScan(Relation heapRelation,
--- 1963,1971 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't supported at index build time. */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2267,2272 **** validate_index_heapscan(Relation heapRelation,
--- 2340,2348 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/* operator exclusion constraints aren't supported at index build time. */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
  }
  
  
***************
*** 2522,2524 **** reindex_relation(Oid relid, bool toast_too)
--- 2598,2601 ----
  
  	return result;
  }
+ 
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *constrategiesArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ 		constrategiesArray = construct_array(strategyDatums,
+ 											 constraintNKeys,
+ 											 INT2OID,
+ 											 sizeof(int16), true, 's');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (constrategiesArray)
+ 		values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ 	else
+ 		nulls[Anum_pg_constraint_constrategies - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 548,555 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 564,583 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,252 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 149,167 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraint == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_check_constraint(trigdata->tg_relation, indexRel,
! 							   slot, &(new_row->t_self), values, isnull,
! 							   indexInfo->ii_ExclusionConstraint,
! 							   indexInfo->ii_ExpressionsState,
! 							   econtext, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 424,429 **** DefineIndex(RangeVar *heapRelation,
--- 434,442 ----
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* operator exclusion constraints aren't supported at index build time. */
+ 	indexInfo->ii_ExclusionConstraint = exclusion_constraint;
+ 
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
  	ComputeIndexAttrs(indexInfo, classObjectId, coloptions, attributeList,
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 448,470 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type;
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUSION";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 480,486 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 775,782 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 966,972 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUSION */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  
  	/*
  	 * Open the relation(s).  We have surely already locked the existing
***************
*** 3077,3082 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3083,3091 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3085,3090 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3094,3127 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3119,3134 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
  
  	if (newrel || needscan)
  	{
! 		ExprContext *econtext;
! 		Datum	   *values;
! 		bool	   *isnull;
! 		TupleTableSlot *oldslot;
! 		TupleTableSlot *newslot;
! 		HeapScanDesc scan;
! 		HeapTuple	tuple;
! 		MemoryContext oldCxt;
! 		List	   *dropped_attrs = NIL;
! 		ListCell   *lc;
  
  		econtext = GetPerTupleExprContext(estate);
  
--- 3156,3173 ----
  
  	if (newrel || needscan)
  	{
! 		ExprContext		 *econtext;
! 		Datum			 *values;
! 		bool			 *isnull;
! 		TupleTableSlot	 *oldslot;
! 		TupleTableSlot	 *newslot;
! 		HeapScanDesc	  scan;
! 		HeapTuple		  tuple;
! 		MemoryContext	  oldCxt;
! 		List			 *dropped_attrs	 = NIL;
! 		ListCell		 *lc;
! 		Datum			 *idxvals		 = NULL;
! 		bool			 *idxnulls		 = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3147,3152 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3186,3200 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3172,3177 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3220,3226 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3242,3247 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3291,3297 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3250,3255 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3300,3325 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_check_constraint(oldrel, indexRelation, newslot,
+ 									   &tuple->t_self, idxvals, idxnulls,
+ 									   indexInfo->ii_ExclusionConstraint,
+ 									   indexInfo->ii_ExpressionsState,
+ 									   econtext, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				simple_heap_insert(newrel, tuple);
***************
*** 3264,3269 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3334,3352 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 3271,3276 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3354,3360 ----
  	heap_close(oldrel, NoLock);
  	if (newrel)
  		heap_close(newrel, NoLock);
+ 
  }
  
  /*
***************
*** 4569,4574 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4653,4659 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4632,4637 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4717,4757 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 			else
+ 			{
+ 				char *choose_name2 = "";
+ 				IndexElem *ie = linitial(newConstraint->operator_exclusion);
+ 
+ 				if (ie->name != NULL)
+ 					choose_name2 = ie->name;
+ 
+ 				newConstraint->conname =
+ 					ChooseConstraintName(RelationGetRelationName(rel),
+ 										 choose_name2,
+ 										 "exclusion",
+ 										 RelationGetNamespace(rel),
+ 										 NIL);
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5001,5006 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5121,5127 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5037,5042 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5158,5322 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	int16		*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support gettuple",
+ 							   constraint->using_method)));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(int16) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = LookupOperName(NULL, opname, typoid, typoid, false, -1);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", quote_identifier(get_opname(opid))),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			elog(ERROR, "no strategy found for operator %d "
+ 				 "in operator family %d", opid, opfamily);
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint. Set isconstraint to
+ 	 * false because we're building the constraint entry ourselves.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							NULL,		/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,68 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 ExprContext *econtext, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1010,1016 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1017,1023 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1075,1081 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1082,1088 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1083,1089 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1090,1118 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraint != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_check_constraint(heapRelation, indexRelation,
! 									   slot, tupleid, values, isnull,
! 									   indexInfo->ii_ExclusionConstraint,
! 									   indexInfo->ii_ExpressionsState,
! 									   econtext, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraint != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1217,1219 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1246,1492 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ 					   ItemPointer tupleid, Datum *values, bool *isnull,
+ 					   int16 *exclusion_constraint, List *index_exprs,
+ 					   ExprContext *econtext, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	Oid					*constr_procs;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self	  = false;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	for (i = 0; i < index_natts; i++)
+ 		if (isnull[i])
+ 			return true;
+ 
+ 	/*
+ 	 * Find the function that tests for a conflict based on the
+ 	 * strategy number, operator family, and types.
+ 	 */
+ 	constr_procs = palloc(sizeof(Oid) * index_natts);
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		/*
+ 		 * Find the procedure implementing the strategy for the
+ 		 * index for two arguments both with the type of the
+ 		 * indexed attribute.
+ 		 */
+ 		Oid				oper;
+ 		Oid				opfamily = index->rd_opfamily[i];
+ 		Oid				typoid = index->rd_opcintype[i];
+ 		StrategyNumber	strategy = exclusion_constraint[i];
+ 
+ 		if (strategy == InvalidStrategy)
+ 			continue;
+ 
+ 		oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+ 
+ 		if(OidIsValid(oper))
+ 			constr_procs[i] = get_opcode(oper);
+ 		else
+ 			elog(ERROR, "cannot determine operator for type %d and "
+ 				 "strategy %d", typoid, strategy);
+ 	}
+ 
+ 	InitDirtySnapshot(DirtySnapshot);
+ 
+ 	/*
+ 	 * Now search the tuples that are actually in the index for
+ 	 * any violations.
+ 	 */
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			continue;
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	/*
+ 	 * We have to find all tuples, even those not visible yet.
+ 	 */
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		if(ItemPointerEquals(tupleid, &tup->t_data->t_ctid))
+ 		{
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, econtext, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		if (errorOK)
+ 		{
+ 			conflict = true;
+ 			break;
+ 		}
+ 
+ 		/* If the in-progress inserting transaction aborts, proceed. */
+ 		if (TransactionIdIsValid(DirtySnapshot.xmin))
+ 		{
+ 			XactLockTableWait(DirtySnapshot.xmin);
+ 			if (TransactionIdDidAbort(DirtySnapshot.xmin))
+ 				continue;
+ 		}
+ 
+ 		/* If the in-progress deleting transaction commits, proceed. */
+ 		if (TransactionIdIsValid(DirtySnapshot.xmax))
+ 		{
+ 			XactLockTableWait(DirtySnapshot.xmax);
+ 			if (TransactionIdDidCommit(DirtySnapshot.xmax))
+ 				continue;
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", tuple_as_string(new_slot),
+ 						   tuple_as_string(existing_slot))));
+ 	}
+ 
+ 	if (!conflict)
+ 		Assert(found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	pfree(constr_procs);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 ExprContext *econtext, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ 	TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+ 	StringInfoData		buf;
+ 	int					i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < tupdesc->natts; i++)
+ 	{
+ 		char	*strval;
+ 		Datum	 value;
+ 		bool	 isnull;
+ 
+ 		value = slot_getattr(slot, i + 1, &isnull);
+ 
+ 		if (isnull)
+ 			strval = "null";
+ 		else
+ 		{
+ 			Oid		foutoid;
+ 			bool	typisvarlena;
+ 
+ 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ 							  &typisvarlena);
+ 			strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, strval);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	return buf.data;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2138,2143 **** _copyConstraint(Constraint *from)
--- 2138,2146 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2106,2111 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2106,2114 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2379,2384 **** _outConstraint(StringInfo str, Constraint *node)
--- 2379,2392 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 355,360 **** static TypeName *TableFuncTypeName(List *columns);
--- 355,361 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 435,440 **** static TypeName *TableFuncTypeName(List *columns);
--- 436,442 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 478,484 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 480,486 ----
  	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 EXCLUSION EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
***************
*** 2506,2511 **** ConstraintElem:
--- 2508,2528 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUSION access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2546,2551 **** key_match:  MATCH FULL
--- 2563,2585 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem CHECK WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $4);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 70,76 **** typedef struct
  	List	   *columns;		/* ColumnDef items */
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
! 	List	   *ixconstraints;	/* index-creating constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
--- 70,77 ----
  	List	   *columns;		/* ColumnDef items */
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
! 	List	   *idxconstraints;	/* index-creating constraints */
! 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void preprocessOpExConstraints(ParseState *pstate,
+ 									  CreateStmtContext *cxt,
+ 									  RangeVar *relation,
+ 									  Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 174,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.ixconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
--- 181,188 ----
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.idxconstraints = NIL;
! 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 460,466 **** transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
  			case CONSTR_UNIQUE:
  				if (constraint->keys == NIL)
  					constraint->keys = list_make1(makeString(column->colname));
! 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
  				break;
  
  			case CONSTR_CHECK:
--- 504,510 ----
  			case CONSTR_UNIQUE:
  				if (constraint->keys == NIL)
  					constraint->keys = list_make1(makeString(column->colname));
! 				cxt->idxconstraints = lappend(cxt->idxconstraints, constraint);
  				break;
  
  			case CONSTR_CHECK:
***************
*** 503,509 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
  	{
  		case CONSTR_PRIMARY:
  		case CONSTR_UNIQUE:
! 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
  			break;
  
  		case CONSTR_CHECK:
--- 547,553 ----
  	{
  		case CONSTR_PRIMARY:
  		case CONSTR_UNIQUE:
! 			cxt->idxconstraints = lappend(cxt->idxconstraints, constraint);
  			break;
  
  		case CONSTR_CHECK:
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 730,735 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 778,789 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 868,873 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 922,937 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1099,1105 **** transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
  	 * 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.
  	 */
! 	foreach(lc, cxt->ixconstraints)
  	{
  		Constraint *constraint = (Constraint *) lfirst(lc);
  
--- 1163,1169 ----
  	 * 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.
  	 */
! 	foreach(lc, cxt->idxconstraints)
  	{
  		Constraint *constraint = (Constraint *) lfirst(lc);
  
***************
*** 1837,1843 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.ixconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
--- 1901,1908 ----
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.idxconstraints = NIL;
! 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1885,1890 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1950,1958 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					preprocessOpExConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1943,1949 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2011,2020 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1958,1963 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2029,2041 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2250,2252 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2328,2381 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ preprocessOpExConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						  RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/storage/ipc/ipci.c
--- b/src/backend/storage/ipc/ipci.c
***************
*** 15,20 ****
--- 15,21 ----
  #include "postgres.h"
  
  #include "access/clog.h"
+ #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/multixact.h"
  #include "access/nbtree.h"
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 142,147 **** static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
--- 142,149 ----
  static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
  static void decompile_column_index_array(Datum column_index_array, Oid relId,
  							 StringInfo buf);
+ static void decompile_column_strategy_array(Datum column_strategy_array,
+ 											Oid indexOid, StringInfo buf);
  static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
  static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
  					   bool attrsOnly, bool showTblSpc,
***************
*** 1193,1198 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1195,1221 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				Datum	val;
+ 				bool	isnull;
+ 				Oid		indexOid = conForm->conindid;
+ 
+ 				/* Fetch constraint expression in parsetree form */
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_constrategies,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null conbin for constraint %u",
+ 						 constraintId);
+ 
+ 				appendStringInfo(&buf, "(");
+ 				decompile_column_strategy_array(val, indexOid, &buf);
+ 				appendStringInfo(&buf, ") USING INDEX %s",
+ 								 quote_identifier(get_rel_name(indexOid)));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1240,1245 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1263,1316 ----
  	}
  }
  
+ /*
+  * Convert an int16[] Datum into a comma-separated list of column
+  * names and operators for the indicated index; append the list to
+  * buf.
+  */
+ static void
+ decompile_column_strategy_array(Datum column_strategy_array, Oid indexOid,
+ 								StringInfo buf)
+ {
+ 	Datum	   *keys;
+ 	int			nKeys;
+ 	int			j;
+ 	Relation	indexRelation;
+ 
+ 	/* Extract data from array of int16 */
+ 	deconstruct_array(DatumGetArrayTypeP(column_strategy_array),
+ 					  INT2OID, 2, true, 's',
+ 					  &keys, NULL, &nKeys);
+ 
+ 	indexRelation = relation_open(indexOid, AccessShareLock);
+ 
+ 	for (j = 0; j < nKeys; j++)
+ 	{
+ 		Oid		 opid;
+ 		char	*opName;
+ 		Oid		 opfamily = indexRelation->rd_opfamily[j];
+ 		char	*colName  = get_relid_attribute_name(indexOid, j + 1);
+ 		Oid		 colType  = indexRelation->rd_opcintype[j];
+ 		int16	 strategy = DatumGetInt16(keys[j]);
+ 
+ 		opid = get_opfamily_member(opfamily, colType, colType, strategy);
+ 		opName = get_opname(opid);
+ 
+ 		if (colName == NULL || opName == NULL)
+ 			elog(ERROR, "unexpected error: cannot determine column and "
+ 				 "operator names");
+ 
+ 		if (j == 0)
+ 			appendStringInfo(buf, "%s %s", quote_identifier(colName),
+ 							 quote_identifier(opName));
+ 		else
+ 			appendStringInfo(buf, ", %s %s", quote_identifier(colName),
+ 							 quote_identifier(opName));
+ 	}
+ 
+ 	relation_close(indexRelation, NoLock);
+ }
+ 
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3039,3119 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ 	Relation	conrel;
+ 	SysScanDesc conscan;
+ 	ScanKeyData skey[1];
+ 	HeapTuple	htup;
+ 	Datum		val;
+ 	bool		isnull;
+ 	bool		found = false;
+ 	int16	   *constraints;
+ 	Oid			relid = indexRelation->rd_index->indrelid;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_constrategies,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null constrategies for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != INT2OID)
+ 			elog(ERROR, "constrategies is not a 1-D smallint array");
+ 		constraints = palloc(sizeof(int16) * nelem);
+ 		memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	return constraints;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/access/genam.h
--- b/src/include/access/genam.h
***************
*** 16,21 ****
--- 16,23 ----
  
  #include "access/sdir.h"
  #include "access/skey.h"
+ #include "access/xact.h"
+ #include "executor/tuptable.h"
  #include "nodes/tidbitmap.h"
  #include "storage/buf.h"
  #include "storage/lock.h"
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	int2		constrategies[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_constrategies	20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 318,322 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 318,328 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_check_constraint(Relation heap, Relation index,
+ 								   TupleTableSlot *new_slot,
+ 								   ItemPointer tupleid, Datum *values,
+ 								   bool *isnull, int16 *exclusion_constraint,
+ 								   List *index_exprs, ExprContext *econtext,
+ 								   bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	int16	   *ii_ExclusionConstraint;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1399,1404 **** typedef enum ConstrType			/* types of constraints */
--- 1399,1405 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1433,1443 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1434,1449 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 144,149 **** PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
--- 144,150 ----
  PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
  PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
  PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
+ PG_KEYWORD("exclusion", EXCLUSION, UNRESERVED_KEYWORD)
  PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
  PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
  PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD)
*** a/src/include/storage/lwlock.h
--- b/src/include/storage/lwlock.h
***************
*** 67,72 **** typedef enum LWLockId
--- 67,73 ----
  	AutovacuumLock,
  	AutovacuumScheduleLock,
  	SyncScanLock,
+ 	IndexConstraintLock,
  	/* Individual lock IDs end here */
  	FirstBufMappingLock,
  	FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 15,20 ****
--- 15,21 ----
  #define RELCACHE_H
  
  #include "access/tupdesc.h"
+ #include "access/skey.h"
  #include "nodes/bitmapset.h"
  #include "nodes/pg_list.h"
  
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 44,50 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUSION USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK 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>');
+ -- should fail
+ 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 EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ 
+ DROP TABLE circles;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 267,273 **** SELECT * FROM INSERT_TBL;
  CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
  	CONSTRAINT COPY_CON
  	CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
! COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
  SELECT '' AS two, * FROM COPY_TBL;
   two | x |       y       | z 
  -----+---+---------------+---
--- 267,273 ----
  CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
  	CONSTRAINT COPY_CON
  	CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
! COPY COPY_TBL FROM '/home/jdavis/wd/git/postgresql/src/test/regress/data/constro.data';
  SELECT '' AS two, * FROM COPY_TBL;
   two | x |       y       | z 
  -----+---+---------------+---
***************
*** 275,281 **** SELECT '' AS two, * FROM COPY_TBL;
       | 6 | OK            | 4
  (2 rows)
  
! COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
  ERROR:  new row for relation "copy_tbl" violates check constraint "copy_con"
  CONTEXT:  COPY copy_tbl, line 2: "7	check failed	6"
  SELECT * FROM COPY_TBL;
--- 275,281 ----
       | 6 | OK            | 4
  (2 rows)
  
! COPY COPY_TBL FROM '/home/jdavis/wd/git/postgresql/src/test/regress/data/constrf.data';
  ERROR:  new row for relation "copy_tbl" violates check constraint "copy_con"
  CONTEXT:  COPY copy_tbl, line 2: "7	check failed	6"
  SELECT * FROM COPY_TBL;
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUSION USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD EXCLUSION 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Tuple "(<(20,20),10>, <(0,0), 5>)" conflicts with existing tuple "(<(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 EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ NOTICE:  ALTER TABLE / ADD EXCLUSION will create implicit index "circles_c1_exclusion1" for table "circles"
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL:  Tuple "(<(0,0),5>, <(0,0), 5>)" conflicts with existing tuple "(<(0,0),5>, <(0,0), 5>)".
+ DROP TABLE circles;
operator-exclusion-constraints-20091025.patch.gzapplication/x-gzip; name=operator-exclusion-constraints-20091025.patch.gzDownload
#134Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#133)
2 attachment(s)
Re: operator exclusion constraints

New patch attached (again, both attachments are the same, but one was
passed through filterdiff to get a context diff).

Done:
* fixed the problem Dean Rasheed found using his test script
* some cleanup

Items remaining:
* psql
* pg_dump
* self-review before RRR
* performance testing

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091027.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091027.context.patchDownload
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,57 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
--- 51,58 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   EXCLUSION [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">expression</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</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>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 548,613 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>EXCLUSION [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">expression</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
+     <listitem>
+      <para>
+       The <literal>EXCLUSION</> clause specifies an operator exclusion
+       constraint. An operator exclusion constraint is more general
+       than a <literal>UNIQUE</literal> constraint, and can use an
+       arbitrary operator to detect conflicts. For instance, you can
+       specify the constraint that no two tuples in the table contain
+       overlapping circles (see <xref linkend="datatype-geometric">) by
+       using the <literal>&&</literal> operator.
+      </para>
+ 
+      <para>
+       The constraint specifies the conflict condition, so the operator
+       should return <literal>TRUE</literal> when applied to two
+       conflicting values. Also, the operator specified must be
+       commutative (that is, the commutator of the operator must be the
+       operator itself), must be a boolean operator, and must be
+       associated with an operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) using
+       <replaceable class="parameter">index_method</replaceable>. The
+       constraint is violated if, and only if, there exist two tuples
+       where all corresponding expressions between the tuples conflict
+       according
+       to <replaceable class="parameter">operator</replaceable>
+       (i.e. the operator returns <literal>TRUE</literal>).
+      </para>
+ 
+      <para>
+       Internally, operator exclusion constraints use an index to
+       perform a search looking for conflicting values, and handle
+       concurrent operations similar to a <literal>UNIQUE</literal>
+       constraint. If all of the operators are specified as the
+       equality operator (usually <literal>=</literal>), this
+       constraint behaves identically to a <literal>UNIQUE</literal>
+       constraint. However, it may exhibit slightly worse performance
+       than specifying <literal>UNIQUE</literal>, because operator
+       exclusion constraints require one additional index search. The
+       advantage of operator exclusion constraints is the ability to
+       specify more general constraints (like a non-overlapping
+       constraint for circles), and also the ability to use index
+       methods other than <literal>btree</literal>, such
+       as <literal>GiST</literal> (see <xref linkend="GiST">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">index_parameters</replaceable>
+       are the same as for a <literal>UNIQUE</literal> constraint. The <replaceable class="parameter">predicate</replaceable>
+       allows you to specify the constraint on a subset of the table
+       (note the reqiuired parentheses around the predicate
+       expression), internally using a partial index
+       (see <xref linkend="SQL-CREATEINDEX">). The <replaceable class="parameter">expression</replaceable>
+       is normally just a column name, but can also be an expression or
+       function call and the constraint will check the result (similar
+       to creating a unique index over an expression).
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1110,1115 **** CREATE TABLE cinemas (
--- 1171,1188 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUSION USING gist (c CHECK WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
***************
*** 64,72 ****
--- 65,77 ----
  
  #include "access/relscan.h"
  #include "access/transam.h"
+ #include "miscadmin.h"
  #include "pgstat.h"
  #include "storage/bufmgr.h"
  #include "storage/lmgr.h"
+ #include "storage/lwlock.h"
+ #include "storage/procarray.h"
+ #include "utils/lsyscache.h"
  #include "utils/relcache.h"
  #include "utils/snapmgr.h"
  #include "utils/tqual.h"
***************
*** 116,122 **** do { \
  static IndexScanDesc index_beginscan_internal(Relation indexRelation,
  						 int nkeys, ScanKey key);
  
- 
  /* ----------------------------------------------------------------
   *				   index_ interface functions
   * ----------------------------------------------------------------
--- 121,126 ----
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1109 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraint = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraint != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUSION");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraint,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 803,808 **** index_create(Oid heapRelationId,
--- 811,874 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1082,1087 **** BuildIndexInfo(Relation index)
--- 1148,1157 ----
  	/* other info */
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 		ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ 	else
+ 		ii->ii_ExclusionConstraint = NULL;
  
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
***************
*** 1893,1898 **** IndexBuildHeapScan(Relation heapRelation,
--- 1963,1971 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't supported at index build time. */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2267,2272 **** validate_index_heapscan(Relation heapRelation,
--- 2340,2348 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/* operator exclusion constraints aren't supported at index build time. */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
  }
  
  
***************
*** 2522,2524 **** reindex_relation(Oid relid, bool toast_too)
--- 2598,2601 ----
  
  	return result;
  }
+ 
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *constrategiesArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ 		constrategiesArray = construct_array(strategyDatums,
+ 											 constraintNKeys,
+ 											 INT2OID,
+ 											 sizeof(int16), true, 's');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (constrategiesArray)
+ 		values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ 	else
+ 		nulls[Anum_pg_constraint_constrategies - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 548,555 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 564,583 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,252 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
--- 40,46 ----
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext = NULL;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL ||
! 		indexInfo->ii_ExclusionConstraint != NULL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 150,168 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraint == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_check_constraint(trigdata->tg_relation, indexRel,
! 							   slot, &(new_row->t_self), values, isnull,
! 							   indexInfo->ii_ExclusionConstraint,
! 							   indexInfo->ii_ExpressionsState,
! 							   econtext, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 424,429 **** DefineIndex(RangeVar *heapRelation,
--- 434,442 ----
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* operator exclusion constraints aren't supported at index build time. */
+ 	indexInfo->ii_ExclusionConstraint = exclusion_constraint;
+ 
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
  	ComputeIndexAttrs(indexInfo, classObjectId, coloptions, attributeList,
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 448,471 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type = NULL;
+ 
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUSION";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 481,487 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 776,783 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 967,973 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUSION */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  
  	/*
  	 * Open the relation(s).  We have surely already locked the existing
***************
*** 3077,3082 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3083,3091 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3085,3090 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3094,3127 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3119,3134 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
  
  	if (newrel || needscan)
  	{
! 		ExprContext *econtext;
! 		Datum	   *values;
! 		bool	   *isnull;
! 		TupleTableSlot *oldslot;
! 		TupleTableSlot *newslot;
! 		HeapScanDesc scan;
! 		HeapTuple	tuple;
! 		MemoryContext oldCxt;
! 		List	   *dropped_attrs = NIL;
! 		ListCell   *lc;
  
  		econtext = GetPerTupleExprContext(estate);
  
--- 3156,3173 ----
  
  	if (newrel || needscan)
  	{
! 		ExprContext		 *econtext;
! 		Datum			 *values;
! 		bool			 *isnull;
! 		TupleTableSlot	 *oldslot;
! 		TupleTableSlot	 *newslot;
! 		HeapScanDesc	  scan;
! 		HeapTuple		  tuple;
! 		MemoryContext	  oldCxt;
! 		List			 *dropped_attrs	 = NIL;
! 		ListCell		 *lc;
! 		Datum			 *idxvals		 = NULL;
! 		bool			 *idxnulls		 = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3147,3152 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3186,3200 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3172,3177 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3220,3226 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3242,3247 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3291,3297 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3250,3255 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3300,3325 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_check_constraint(oldrel, indexRelation, newslot,
+ 									   &tuple->t_self, idxvals, idxnulls,
+ 									   indexInfo->ii_ExclusionConstraint,
+ 									   indexInfo->ii_ExpressionsState,
+ 									   econtext, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				simple_heap_insert(newrel, tuple);
***************
*** 3264,3269 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3334,3352 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 3271,3276 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3354,3360 ----
  	heap_close(oldrel, NoLock);
  	if (newrel)
  		heap_close(newrel, NoLock);
+ 
  }
  
  /*
***************
*** 4569,4574 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4653,4659 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4632,4637 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4717,4757 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 			else
+ 			{
+ 				char *choose_name2 = "";
+ 				IndexElem *ie = linitial(newConstraint->operator_exclusion);
+ 
+ 				if (ie->name != NULL)
+ 					choose_name2 = ie->name;
+ 
+ 				newConstraint->conname =
+ 					ChooseConstraintName(RelationGetRelationName(rel),
+ 										 choose_name2,
+ 										 "exclusion",
+ 										 RelationGetNamespace(rel),
+ 										 NIL);
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5001,5006 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5121,5127 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5037,5042 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5158,5322 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	int16		*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support gettuple",
+ 							   constraint->using_method)));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(int16) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = LookupOperName(NULL, opname, typoid, typoid, false, -1);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", quote_identifier(get_opname(opid))),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			elog(ERROR, "no strategy found for operator %d "
+ 				 "in operator family %d", opid, opfamily);
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint. Set isconstraint to
+ 	 * false because we're building the constraint entry ourselves.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							NULL,		/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,68 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 ExprContext *econtext, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1010,1016 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1017,1023 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1075,1081 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1082,1088 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1083,1089 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1090,1118 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraint != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_check_constraint(heapRelation, indexRelation,
! 									   slot, tupleid, values, isnull,
! 									   indexInfo->ii_ExclusionConstraint,
! 									   indexInfo->ii_ExpressionsState,
! 									   econtext, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraint != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1217,1219 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1246,1496 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ 					   ItemPointer tupleid, Datum *values, bool *isnull,
+ 					   int16 *exclusion_constraint, List *index_exprs,
+ 					   ExprContext *econtext, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	Oid					*constr_procs;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	for (i = 0; i < index_natts; i++)
+ 		if (isnull[i])
+ 			return true;
+ 
+ 	/*
+ 	 * Find the function that tests for a conflict based on the
+ 	 * strategy number, operator family, and types.
+ 	 */
+ 	constr_procs = palloc(sizeof(Oid) * index_natts);
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		/*
+ 		 * Find the procedure implementing the strategy for the
+ 		 * index for two arguments both with the type of the
+ 		 * indexed attribute.
+ 		 */
+ 		Oid				oper;
+ 		Oid				opfamily = index->rd_opfamily[i];
+ 		Oid				typoid = index->rd_opcintype[i];
+ 		StrategyNumber	strategy = exclusion_constraint[i];
+ 
+ 		if (strategy == InvalidStrategy)
+ 			continue;
+ 
+ 		oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+ 
+ 		if(OidIsValid(oper))
+ 			constr_procs[i] = get_opcode(oper);
+ 		else
+ 			elog(ERROR, "cannot determine operator for type %d and "
+ 				 "strategy %d", typoid, strategy);
+ 	}
+ 
+ 	/*
+ 	 * Now search the tuples that are actually in the index for
+ 	 * any violations.
+ 	 */
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			continue;
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 
+ 	/*
+ 	 * We have to find all tuples, even those not visible yet.
+ 	 */
+ 	InitDirtySnapshot(DirtySnapshot);
+ 
+ retry:
+ 	found_self = false;
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		TransactionId xwait;
+ 
+ 		if(ItemPointerEquals(tupleid, &tup->t_self))
+ 		{
+ 			Assert(!found_self);
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, econtext, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		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
+ 		 * restart the scan.
+ 		 */
+ 		xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ 			DirtySnapshot.xmin : DirtySnapshot.xmax;
+ 
+ 		if (TransactionIdIsValid(xwait))
+ 		{
+ 			index_endscan(index_scan);
+ 			XactLockTableWait(xwait);
+ 			goto retry;
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", tuple_as_string(new_slot),
+ 						   tuple_as_string(existing_slot))));
+ 	}
+ 
+ 	Assert(conflict || found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	pfree(constr_procs);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 ExprContext *econtext, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ 	TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+ 	StringInfoData		buf;
+ 	int					i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < tupdesc->natts; i++)
+ 	{
+ 		char	*strval;
+ 		Datum	 value;
+ 		bool	 isnull;
+ 
+ 		value = slot_getattr(slot, i + 1, &isnull);
+ 
+ 		if (isnull)
+ 			strval = "null";
+ 		else
+ 		{
+ 			Oid		foutoid;
+ 			bool	typisvarlena;
+ 
+ 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ 							  &typisvarlena);
+ 			strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, strval);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	return buf.data;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2158,2163 **** _copyConstraint(Constraint *from)
--- 2158,2166 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2106,2111 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2106,2114 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2394,2399 **** _outConstraint(StringInfo str, Constraint *node)
--- 2394,2407 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 355,360 **** static TypeName *TableFuncTypeName(List *columns);
--- 355,361 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 435,440 **** static TypeName *TableFuncTypeName(List *columns);
--- 436,442 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 478,484 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 480,486 ----
  	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 EXCLUSION EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
***************
*** 2506,2511 **** ConstraintElem:
--- 2508,2528 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUSION access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2546,2551 **** key_match:  MATCH FULL
--- 2563,2585 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem CHECK WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $4);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 70,76 **** typedef struct
  	List	   *columns;		/* ColumnDef items */
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
! 	List	   *ixconstraints;	/* index-creating constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
--- 70,77 ----
  	List	   *columns;		/* ColumnDef items */
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
! 	List	   *idxconstraints;	/* index-creating constraints */
! 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void preprocessOpExConstraints(ParseState *pstate,
+ 									  CreateStmtContext *cxt,
+ 									  RangeVar *relation,
+ 									  Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 174,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.ixconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
--- 181,188 ----
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.idxconstraints = NIL;
! 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 460,466 **** transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
  			case CONSTR_UNIQUE:
  				if (constraint->keys == NIL)
  					constraint->keys = list_make1(makeString(column->colname));
! 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
  				break;
  
  			case CONSTR_CHECK:
--- 504,510 ----
  			case CONSTR_UNIQUE:
  				if (constraint->keys == NIL)
  					constraint->keys = list_make1(makeString(column->colname));
! 				cxt->idxconstraints = lappend(cxt->idxconstraints, constraint);
  				break;
  
  			case CONSTR_CHECK:
***************
*** 503,509 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
  	{
  		case CONSTR_PRIMARY:
  		case CONSTR_UNIQUE:
! 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
  			break;
  
  		case CONSTR_CHECK:
--- 547,553 ----
  	{
  		case CONSTR_PRIMARY:
  		case CONSTR_UNIQUE:
! 			cxt->idxconstraints = lappend(cxt->idxconstraints, constraint);
  			break;
  
  		case CONSTR_CHECK:
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 730,735 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 778,789 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 868,873 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 922,937 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1099,1105 **** transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
  	 * 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.
  	 */
! 	foreach(lc, cxt->ixconstraints)
  	{
  		Constraint *constraint = (Constraint *) lfirst(lc);
  
--- 1163,1169 ----
  	 * 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.
  	 */
! 	foreach(lc, cxt->idxconstraints)
  	{
  		Constraint *constraint = (Constraint *) lfirst(lc);
  
***************
*** 1837,1843 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.ixconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
--- 1901,1908 ----
  	cxt.columns = NIL;
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
! 	cxt.idxconstraints = NIL;
! 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1885,1890 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1950,1958 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					preprocessOpExConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1943,1949 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2011,2020 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1958,1963 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2029,2041 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2250,2252 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2328,2381 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ preprocessOpExConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						  RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/storage/ipc/ipci.c
--- b/src/backend/storage/ipc/ipci.c
***************
*** 15,20 ****
--- 15,21 ----
  #include "postgres.h"
  
  #include "access/clog.h"
+ #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/multixact.h"
  #include "access/nbtree.h"
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 142,147 **** static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
--- 142,149 ----
  static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
  static void decompile_column_index_array(Datum column_index_array, Oid relId,
  							 StringInfo buf);
+ static void decompile_column_strategy_array(Datum column_strategy_array,
+ 											Oid indexOid, StringInfo buf);
  static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
  static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
  					   bool attrsOnly, bool showTblSpc,
***************
*** 1193,1198 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1195,1221 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				Datum	val;
+ 				bool	isnull;
+ 				Oid		indexOid = conForm->conindid;
+ 
+ 				/* Fetch constraint expression in parsetree form */
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_constrategies,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null conbin for constraint %u",
+ 						 constraintId);
+ 
+ 				appendStringInfo(&buf, "(");
+ 				decompile_column_strategy_array(val, indexOid, &buf);
+ 				appendStringInfo(&buf, ") USING INDEX %s",
+ 								 quote_identifier(get_rel_name(indexOid)));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1240,1245 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1263,1316 ----
  	}
  }
  
+ /*
+  * Convert an int16[] Datum into a comma-separated list of column
+  * names and operators for the indicated index; append the list to
+  * buf.
+  */
+ static void
+ decompile_column_strategy_array(Datum column_strategy_array, Oid indexOid,
+ 								StringInfo buf)
+ {
+ 	Datum	   *keys;
+ 	int			nKeys;
+ 	int			j;
+ 	Relation	indexRelation;
+ 
+ 	/* Extract data from array of int16 */
+ 	deconstruct_array(DatumGetArrayTypeP(column_strategy_array),
+ 					  INT2OID, 2, true, 's',
+ 					  &keys, NULL, &nKeys);
+ 
+ 	indexRelation = relation_open(indexOid, AccessShareLock);
+ 
+ 	for (j = 0; j < nKeys; j++)
+ 	{
+ 		Oid		 opid;
+ 		char	*opName;
+ 		Oid		 opfamily = indexRelation->rd_opfamily[j];
+ 		char	*colName  = get_relid_attribute_name(indexOid, j + 1);
+ 		Oid		 colType  = indexRelation->rd_opcintype[j];
+ 		int16	 strategy = DatumGetInt16(keys[j]);
+ 
+ 		opid = get_opfamily_member(opfamily, colType, colType, strategy);
+ 		opName = get_opname(opid);
+ 
+ 		if (colName == NULL || opName == NULL)
+ 			elog(ERROR, "unexpected error: cannot determine column and "
+ 				 "operator names");
+ 
+ 		if (j == 0)
+ 			appendStringInfo(buf, "%s %s", quote_identifier(colName),
+ 							 quote_identifier(opName));
+ 		else
+ 			appendStringInfo(buf, ", %s %s", quote_identifier(colName),
+ 							 quote_identifier(opName));
+ 	}
+ 
+ 	relation_close(indexRelation, NoLock);
+ }
+ 
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3039,3119 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ 	Relation	conrel;
+ 	SysScanDesc conscan;
+ 	ScanKeyData skey[1];
+ 	HeapTuple	htup;
+ 	Datum		val;
+ 	bool		isnull;
+ 	bool		found = false;
+ 	int16	   *constraints = NULL;
+ 	Oid			relid = indexRelation->rd_index->indrelid;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_constrategies,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null constrategies for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != INT2OID)
+ 			elog(ERROR, "constrategies is not a 1-D smallint array");
+ 		constraints = palloc(sizeof(int16) * nelem);
+ 		memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	return constraints;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/access/genam.h
--- b/src/include/access/genam.h
***************
*** 16,21 ****
--- 16,23 ----
  
  #include "access/sdir.h"
  #include "access/skey.h"
+ #include "access/xact.h"
+ #include "executor/tuptable.h"
  #include "nodes/tidbitmap.h"
  #include "storage/buf.h"
  #include "storage/lock.h"
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	int2		constrategies[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_constrategies	20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,338 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_check_constraint(Relation heap, Relation index,
+ 								   TupleTableSlot *new_slot,
+ 								   ItemPointer tupleid, Datum *values,
+ 								   bool *isnull, int16 *exclusion_constraint,
+ 								   List *index_exprs, ExprContext *econtext,
+ 								   bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	int16	   *ii_ExclusionConstraint;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1388,1393 **** typedef enum ConstrType			/* types of constraints */
--- 1388,1394 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1422,1432 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1423,1438 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 144,149 **** PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
--- 144,150 ----
  PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
  PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
  PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
+ PG_KEYWORD("exclusion", EXCLUSION, UNRESERVED_KEYWORD)
  PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
  PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
  PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD)
*** a/src/include/storage/lwlock.h
--- b/src/include/storage/lwlock.h
***************
*** 67,72 **** typedef enum LWLockId
--- 67,73 ----
  	AutovacuumLock,
  	AutovacuumScheduleLock,
  	SyncScanLock,
+ 	IndexConstraintLock,
  	/* Individual lock IDs end here */
  	FirstBufMappingLock,
  	FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 15,20 ****
--- 15,21 ----
  #define RELCACHE_H
  
  #include "access/tupdesc.h"
+ #include "access/skey.h"
  #include "nodes/bitmapset.h"
  #include "nodes/pg_list.h"
  
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 44,50 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUSION USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK 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>');
+ -- should fail
+ 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 EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ 
+ DROP TABLE circles;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 267,273 **** SELECT * FROM INSERT_TBL;
  CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
  	CONSTRAINT COPY_CON
  	CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
! COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
  SELECT '' AS two, * FROM COPY_TBL;
   two | x |       y       | z 
  -----+---+---------------+---
--- 267,273 ----
  CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
  	CONSTRAINT COPY_CON
  	CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
! COPY COPY_TBL FROM '/home/jdavis/wd/git/postgresql/src/test/regress/data/constro.data';
  SELECT '' AS two, * FROM COPY_TBL;
   two | x |       y       | z 
  -----+---+---------------+---
***************
*** 275,281 **** SELECT '' AS two, * FROM COPY_TBL;
       | 6 | OK            | 4
  (2 rows)
  
! COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
  ERROR:  new row for relation "copy_tbl" violates check constraint "copy_con"
  CONTEXT:  COPY copy_tbl, line 2: "7	check failed	6"
  SELECT * FROM COPY_TBL;
--- 275,281 ----
       | 6 | OK            | 4
  (2 rows)
  
! COPY COPY_TBL FROM '/home/jdavis/wd/git/postgresql/src/test/regress/data/constrf.data';
  ERROR:  new row for relation "copy_tbl" violates check constraint "copy_con"
  CONTEXT:  COPY copy_tbl, line 2: "7	check failed	6"
  SELECT * FROM COPY_TBL;
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUSION USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD EXCLUSION 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Tuple "(<(20,20),10>, <(0,0), 5>)" conflicts with existing tuple "(<(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 EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ NOTICE:  ALTER TABLE / ADD EXCLUSION will create implicit index "circles_c1_exclusion1" for table "circles"
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL:  Tuple "(<(0,0),5>, <(0,0), 5>)" conflicts with existing tuple "(<(0,0),5>, <(0,0), 5>)".
+ DROP TABLE circles;
operator-exclusion-constraints-20091027.patch.gzapplication/x-gzip; name=operator-exclusion-constraints-20091027.patch.gzDownload
#135Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#133)
2 attachment(s)
Re: operator exclusion constraints

New patch attached. Again, both attached patches are the same, but one
is the original patch from "git diff", compressed; and the other is the
same patch passed through filterdiff to make it a context diff. Also
available from my git repo (branch "operator-exclusion-constraints"):

http://git.postgresql.org/gitweb?p=users/jdavis/postgres.git;a=shortlog;h=refs/heads/operator-exclusion-constraints

All open issues are closed; ready for review. Comments welcome.

Completed:

* pg_dump support
* psql support
* clean up after brief review

Simple Performance Tests:

I used Dean Rasheed's python script from this email:

http://archives.postgresql.org/pgsql-hackers/2009-10/msg01527.php

modifying it slightly for the various combinations listed below.

All tests with 10 threads and 1000 loops. The constraint is always
uniqueness (with the operator exclusion constraints that means "CHECK
WITH ="). All results are the approximate average of 3 consecutive runs.
All of these are high-contention tests because they are all competing
over 100 unique values.

[ "btree" means the existing constraint enforcement mechanism in btree,
although the exclusion constraints use a btree as well in this test ]

btree/immediate: 14s
exclusion/immediate: 90s

btree/deferrable: 155s
exclusion/deferrable: 150s

The next tests I did were simple no-contention insert tests of 1M
integer values. I only tested with immediate.

btree/immediate: 15s
exclusion/immediate: 19s

So, the performance is marginally worse for no-contention, about the
same for high-contention deferrable, and significantly worse for
high-contention immediate.

I think that the performance for high-contention immediate could be
improved by being a little more clever to avoid restarting the index
scan on every conflict. However, I've already made a mistake in that
area (as Dean pointed out), so I'll leave it simple for now.

Right as the November commitfest starts, I'll be in Japan for about a
week, so I may be slow to respond to comments during that time.

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091101.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091101.context.patchDownload
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,57 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
--- 51,58 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   EXCLUSION [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">expression</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</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>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 548,613 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>EXCLUSION [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">expression</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
+     <listitem>
+      <para>
+       The <literal>EXCLUSION</> clause specifies an operator exclusion
+       constraint. An operator exclusion constraint is more general
+       than a <literal>UNIQUE</literal> constraint, and can use an
+       arbitrary operator to detect conflicts. For instance, you can
+       specify the constraint that no two tuples in the table contain
+       overlapping circles (see <xref linkend="datatype-geometric">) by
+       using the <literal>&&</literal> operator.
+      </para>
+ 
+      <para>
+       The constraint specifies the conflict condition, so the operator
+       should return <literal>TRUE</literal> when applied to two
+       conflicting values. Also, the operator specified must be
+       commutative (that is, the commutator of the operator must be the
+       operator itself), must be a boolean operator, and must be
+       associated with an operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) using
+       <replaceable class="parameter">index_method</replaceable>. The
+       constraint is violated if, and only if, there exist two tuples
+       where all corresponding expressions between the tuples conflict
+       according
+       to <replaceable class="parameter">operator</replaceable>
+       (i.e. the operator returns <literal>TRUE</literal>).
+      </para>
+ 
+      <para>
+       Internally, operator exclusion constraints use an index to
+       perform a search looking for conflicting values, and handle
+       concurrent operations similar to a <literal>UNIQUE</literal>
+       constraint. If all of the operators are specified as the
+       equality operator (usually <literal>=</literal>), this
+       constraint behaves identically to a <literal>UNIQUE</literal>
+       constraint. However, it may exhibit slightly worse performance
+       than specifying <literal>UNIQUE</literal>, because operator
+       exclusion constraints require one additional index search. The
+       advantage of operator exclusion constraints is the ability to
+       specify more general constraints (like a non-overlapping
+       constraint for circles), and also the ability to use index
+       methods other than <literal>btree</literal>, such
+       as <literal>GiST</literal> (see <xref linkend="GiST">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">index_parameters</replaceable>
+       are the same as for a <literal>UNIQUE</literal> constraint. The <replaceable class="parameter">predicate</replaceable>
+       allows you to specify the constraint on a subset of the table
+       (note the reqiuired parentheses around the predicate
+       expression), internally using a partial index
+       (see <xref linkend="SQL-CREATEINDEX">). The <replaceable class="parameter">expression</replaceable>
+       is normally just a column name, but can also be an expression or
+       function call and the constraint will check the result (similar
+       to creating a unique index over an expression).
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1111,1116 **** CREATE TABLE cinemas (
--- 1172,1189 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUSION USING gist (c CHECK WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
***************
*** 64,72 ****
--- 65,77 ----
  
  #include "access/relscan.h"
  #include "access/transam.h"
+ #include "miscadmin.h"
  #include "pgstat.h"
  #include "storage/bufmgr.h"
  #include "storage/lmgr.h"
+ #include "storage/lwlock.h"
+ #include "storage/procarray.h"
+ #include "utils/lsyscache.h"
  #include "utils/relcache.h"
  #include "utils/snapmgr.h"
  #include "utils/tqual.h"
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1109 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraint = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraint != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUSION");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraint,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 803,808 **** index_create(Oid heapRelationId,
--- 811,874 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1082,1087 **** BuildIndexInfo(Relation index)
--- 1148,1157 ----
  	/* other info */
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 		ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ 	else
+ 		ii->ii_ExclusionConstraint = NULL;
  
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
***************
*** 1893,1898 **** IndexBuildHeapScan(Relation heapRelation,
--- 1963,1971 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't checked at index build time */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2267,2272 **** validate_index_heapscan(Relation heapRelation,
--- 2340,2351 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/*
+ 	 * Operator exclusion constraints aren't supported for concurrent
+ 	 * index builds.
+ 	 */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
  }
  
  
***************
*** 2522,2524 **** reindex_relation(Oid relid, bool toast_too)
--- 2601,2604 ----
  
  	return result;
  }
+ 
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *constrategiesArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ 		constrategiesArray = construct_array(strategyDatums,
+ 											 constraintNKeys,
+ 											 INT2OID,
+ 											 sizeof(int16), true, 's');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (constrategiesArray)
+ 		values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ 	else
+ 		nulls[Anum_pg_constraint_constrategies - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 548,555 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 564,583 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,252 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
--- 40,46 ----
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext = NULL;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL ||
! 		indexInfo->ii_ExclusionConstraint != NULL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 150,168 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraint == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_check_constraint(trigdata->tg_relation, indexRel,
! 							   slot, &(new_row->t_self), values, isnull,
! 							   indexInfo->ii_ExclusionConstraint,
! 							   indexInfo->ii_ExpressionsState,
! 							   econtext, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 423,428 **** DefineIndex(RangeVar *heapRelation,
--- 433,439 ----
  	indexInfo->ii_ReadyForInserts = !concurrent;
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
+ 	indexInfo->ii_ExclusionConstraint = exclusion_constraint;
  
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 446,469 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type = NULL;
+ 
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUSION";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 479,485 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 774,781 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 965,971 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUSION */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  
  	/*
  	 * Open the relation(s).  We have surely already locked the existing
***************
*** 3077,3082 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3083,3091 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3085,3090 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3094,3127 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3119,3134 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
  
  	if (newrel || needscan)
  	{
! 		ExprContext *econtext;
! 		Datum	   *values;
! 		bool	   *isnull;
! 		TupleTableSlot *oldslot;
! 		TupleTableSlot *newslot;
! 		HeapScanDesc scan;
! 		HeapTuple	tuple;
! 		MemoryContext oldCxt;
! 		List	   *dropped_attrs = NIL;
! 		ListCell   *lc;
  
  		econtext = GetPerTupleExprContext(estate);
  
--- 3156,3173 ----
  
  	if (newrel || needscan)
  	{
! 		ExprContext		 *econtext;
! 		Datum			 *values;
! 		bool			 *isnull;
! 		TupleTableSlot	 *oldslot;
! 		TupleTableSlot	 *newslot;
! 		HeapScanDesc	  scan;
! 		HeapTuple		  tuple;
! 		MemoryContext	  oldCxt;
! 		List			 *dropped_attrs	 = NIL;
! 		ListCell		 *lc;
! 		Datum			 *idxvals		 = NULL;
! 		bool			 *idxnulls		 = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3147,3152 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3186,3200 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3172,3177 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3220,3226 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3242,3247 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3291,3297 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3250,3255 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3300,3325 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_check_constraint(oldrel, indexRelation, newslot,
+ 									   &tuple->t_self, idxvals, idxnulls,
+ 									   indexInfo->ii_ExclusionConstraint,
+ 									   indexInfo->ii_ExpressionsState,
+ 									   econtext, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				simple_heap_insert(newrel, tuple);
***************
*** 3264,3269 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3334,3352 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 3271,3276 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3354,3360 ----
  	heap_close(oldrel, NoLock);
  	if (newrel)
  		heap_close(newrel, NoLock);
+ 
  }
  
  /*
***************
*** 4569,4574 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4653,4659 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4632,4637 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4717,4757 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 			else
+ 			{
+ 				char *choose_name2 = "";
+ 				IndexElem *ie = linitial(newConstraint->operator_exclusion);
+ 
+ 				if (ie->name != NULL)
+ 					choose_name2 = ie->name;
+ 
+ 				newConstraint->conname =
+ 					ChooseConstraintName(RelationGetRelationName(rel),
+ 										 choose_name2,
+ 										 "exclusion",
+ 										 RelationGetNamespace(rel),
+ 										 NIL);
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5001,5006 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5121,5127 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5037,5042 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5158,5321 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	int16		*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support gettuple",
+ 							   constraint->using_method)));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(int16) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = LookupOperName(NULL, opname, typoid, typoid, false, -1);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", quote_identifier(get_opname(opid))),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			elog(ERROR, "no strategy found for operator %d "
+ 				 "in operator family %d", opid, opfamily);
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							NULL,		/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,68 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 ExprContext *econtext, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1010,1016 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1017,1023 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1075,1081 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1082,1088 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1083,1089 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1090,1118 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraint != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_check_constraint(heapRelation, indexRelation,
! 									   slot, tupleid, values, isnull,
! 									   indexInfo->ii_ExclusionConstraint,
! 									   indexInfo->ii_ExpressionsState,
! 									   econtext, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraint != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1217,1219 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1246,1496 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ 					   ItemPointer tupleid, Datum *values, bool *isnull,
+ 					   int16 *exclusion_constraint, List *index_exprs,
+ 					   ExprContext *econtext, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	Oid					*constr_procs;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	for (i = 0; i < index_natts; i++)
+ 		if (isnull[i])
+ 			return true;
+ 
+ 	/*
+ 	 * Find the function that tests for a conflict based on the
+ 	 * strategy number, operator family, and types.
+ 	 */
+ 	constr_procs = palloc(sizeof(Oid) * index_natts);
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		/*
+ 		 * Find the procedure implementing the strategy for the
+ 		 * index for two arguments both with the type of the
+ 		 * indexed attribute.
+ 		 */
+ 		Oid				oper;
+ 		Oid				opfamily = index->rd_opfamily[i];
+ 		Oid				typoid = index->rd_opcintype[i];
+ 		StrategyNumber	strategy = exclusion_constraint[i];
+ 
+ 		if (strategy == InvalidStrategy)
+ 			continue;
+ 
+ 		oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+ 
+ 		if(OidIsValid(oper))
+ 			constr_procs[i] = get_opcode(oper);
+ 		else
+ 			elog(ERROR, "cannot determine operator for type %d and "
+ 				 "strategy %d", typoid, strategy);
+ 	}
+ 
+ 	/*
+ 	 * Now search the tuples that are actually in the index for
+ 	 * any violations.
+ 	 */
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			continue;
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 
+ 	/*
+ 	 * We have to find all tuples, even those not visible yet.
+ 	 */
+ 	InitDirtySnapshot(DirtySnapshot);
+ 
+ retry:
+ 	found_self = false;
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		TransactionId xwait;
+ 
+ 		if(ItemPointerEquals(tupleid, &tup->t_self))
+ 		{
+ 			Assert(!found_self);
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, econtext, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		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
+ 		 * restart the scan.
+ 		 */
+ 		xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ 			DirtySnapshot.xmin : DirtySnapshot.xmax;
+ 
+ 		if (TransactionIdIsValid(xwait))
+ 		{
+ 			index_endscan(index_scan);
+ 			XactLockTableWait(xwait);
+ 			goto retry;
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", tuple_as_string(new_slot),
+ 						   tuple_as_string(existing_slot))));
+ 	}
+ 
+ 	Assert(conflict || found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	pfree(constr_procs);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 ExprContext *econtext, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ 	TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+ 	StringInfoData		buf;
+ 	int					i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < tupdesc->natts; i++)
+ 	{
+ 		char	*strval;
+ 		Datum	 value;
+ 		bool	 isnull;
+ 
+ 		value = slot_getattr(slot, i + 1, &isnull);
+ 
+ 		if (isnull)
+ 			strval = "null";
+ 		else
+ 		{
+ 			Oid		foutoid;
+ 			bool	typisvarlena;
+ 
+ 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ 							  &typisvarlena);
+ 			strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, strval);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	return buf.data;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2159,2164 **** _copyConstraint(Constraint *from)
--- 2159,2167 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2107,2112 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2107,2115 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2396,2401 **** _outConstraint(StringInfo str, Constraint *node)
--- 2396,2409 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 355,360 **** static TypeName *TableFuncTypeName(List *columns);
--- 355,361 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 435,440 **** static TypeName *TableFuncTypeName(List *columns);
--- 436,442 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 478,484 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 480,486 ----
  	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 EXCLUSION EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
***************
*** 2506,2511 **** ConstraintElem:
--- 2508,2528 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUSION access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2546,2551 **** key_match:  MATCH FULL
--- 2563,2585 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem CHECK WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $4);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 71,76 **** typedef struct
--- 71,77 ----
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
  	List	   *ixconstraints;	/* index-creating constraints */
+ 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void preprocessOpExConstraints(ParseState *pstate,
+ 									  CreateStmtContext *cxt,
+ 									  RangeVar *relation,
+ 									  Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 175,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 182,188 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 730,735 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 778,789 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 868,873 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 922,937 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1838,1843 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1902,1908 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1885,1890 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1950,1958 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					preprocessOpExConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1943,1949 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2011,2020 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1958,1963 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2029,2041 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2250,2252 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2328,2381 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ preprocessOpExConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						  RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/storage/ipc/ipci.c
--- b/src/backend/storage/ipc/ipci.c
***************
*** 15,20 ****
--- 15,21 ----
  #include "postgres.h"
  
  #include "access/clog.h"
+ #include "access/genam.h"
  #include "access/heapam.h"
  #include "access/multixact.h"
  #include "access/nbtree.h"
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 148,153 **** static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
--- 148,155 ----
  					   int prettyFlags);
  static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  							int prettyFlags);
+ static char * pg_get_opxdef_worker(Oid indexrelid, uint16 *strategies,
+ 								   int prettyFlags);
  static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
  				   int prettyFlags);
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
***************
*** 1193,1198 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1195,1231 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				bool	 isnull;
+ 				Oid		 indexOid = conForm->conindid;
+ 				Datum	 val;
+ 				Datum	*keys;
+ 				int		 nKeys;
+ 				int		 i;
+ 				uint16	*strategies;
+ 
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_constrategies,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null constrategies for constraint %u",
+ 						 constraintId);
+ 
+ 				deconstruct_array(DatumGetArrayTypeP(val),
+ 								  INT2OID, 2, true, 's',
+ 								  &keys, NULL, &nKeys);
+ 
+ 				strategies = palloc(nKeys * sizeof(uint16));
+ 				for(i = 0; i < nKeys; i++)
+ 					strategies[i] = DatumGetInt16(keys[i]);
+ 
+ 				appendStringInfo(&buf, pg_get_opxdef_worker(indexOid,
+ 															strategies,
+ 															prettyFlags));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1240,1245 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1273,1510 ----
  	}
  }
  
+ static char *
+ pg_get_opxdef_worker(Oid indexrelid, uint16_t *strategies, int prettyFlags)
+ {
+ 	HeapTuple	ht_idx;
+ 	HeapTuple	ht_idxrel;
+ 	HeapTuple	ht_am;
+ 	Form_pg_index idxrec;
+ 	Form_pg_class idxrelrec;
+ 	Form_pg_am	amrec;
+ 	List	   *indexprs;
+ 	ListCell   *indexpr_item;
+ 	List	   *context;
+ 	Oid			indrelid;
+ 	int			keyno;
+ 	Oid			keycoltype;
+ 	Datum		indclassDatum;
+ 	Datum		indoptionDatum;
+ 	bool		isnull;
+ 	oidvector  *indclass;
+ 	int2vector *indoption;
+ 	StringInfoData buf;
+ 	char	   *str;
+ 	char	   *sep;
+ 	Oid			tblspc;
+ 
+ 	/*
+ 	 * Fetch the pg_index tuple by the Oid of the index
+ 	 */
+ 	ht_idx = SearchSysCache(INDEXRELID,
+ 							ObjectIdGetDatum(indexrelid),
+ 							0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idx))
+ 		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ 	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+ 
+ 	indrelid = idxrec->indrelid;
+ 	Assert(indexrelid == idxrec->indexrelid);
+ 
+ 	/* Must get indclass and indoption the hard way */
+ 	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indclass, &isnull);
+ 	Assert(!isnull);
+ 	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+ 	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indoption, &isnull);
+ 	Assert(!isnull);
+ 	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ 
+ 	/*
+ 	 * Fetch the pg_class tuple of the index relation
+ 	 */
+ 	ht_idxrel = SearchSysCache(RELOID,
+ 							   ObjectIdGetDatum(indexrelid),
+ 							   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idxrel))
+ 		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+ 	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+ 
+ 	/*
+ 	 * Fetch the pg_am tuple of the index' access method
+ 	 */
+ 	ht_am = SearchSysCache(AMOID,
+ 						   ObjectIdGetDatum(idxrelrec->relam),
+ 						   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_am))
+ 		elog(ERROR, "cache lookup failed for access method %u",
+ 			 idxrelrec->relam);
+ 	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+ 
+ 	/*
+ 	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+ 	 * versions of the expressions and predicate, because we want to display
+ 	 * non-const-folded expressions.)
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+ 	{
+ 		Datum		exprsDatum;
+ 		bool		isnull;
+ 		char	   *exprsString;
+ 
+ 		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indexprs, &isnull);
+ 		Assert(!isnull);
+ 		exprsString = TextDatumGetCString(exprsDatum);
+ 		indexprs = (List *) stringToNode(exprsString);
+ 		pfree(exprsString);
+ 	}
+ 	else
+ 		indexprs = NIL;
+ 
+ 	indexpr_item = list_head(indexprs);
+ 
+ 	context = deparse_context_for(get_rel_name(indrelid), indrelid);
+ 
+ 	/*
+ 	 * Start the index definition.	Note that the index's name should never be
+ 	 * schema-qualified, but the indexed rel's name may be.
+ 	 */
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "EXCLUSION USING %s (",
+ 					 quote_identifier(NameStr(amrec->amname)));
+ 
+ 	/*
+ 	 * Report the indexed attributes
+ 	 */
+ 	sep = "";
+ 	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ 	{
+ 		AttrNumber	attnum = idxrec->indkey.values[keyno];
+ 		int16		opt = indoption->values[keyno];
+ 		Oid			opfamily = get_opclass_family(indclass->values[keyno]);
+ 		Oid			opid;
+ 		char	   *opName;
+ 
+ 		appendStringInfoString(&buf, sep);
+ 		sep = ", ";
+ 
+ 		if (attnum != 0)
+ 		{
+ 			/* Simple index column */
+ 			char	   *attname;
+ 
+ 			attname = get_relid_attribute_name(indrelid, attnum);
+ 			appendStringInfoString(&buf, quote_identifier(attname));
+ 			keycoltype = get_atttype(indrelid, attnum);
+ 		}
+ 		else
+ 		{
+ 			/* expressional index */
+ 			Node	   *indexkey;
+ 
+ 			if (indexpr_item == NULL)
+ 				elog(ERROR, "too few entries in indexprs list");
+ 			indexkey = (Node *) lfirst(indexpr_item);
+ 			indexpr_item = lnext(indexpr_item);
+ 			/* Deparse */
+ 			str = deparse_expression_pretty(indexkey, context, false, false,
+ 											prettyFlags, 0);
+ 
+ 			/* Need parens if it's not a bare function call */
+ 			if (indexkey && IsA(indexkey, FuncExpr) &&
+ 				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+ 				appendStringInfoString(&buf, str);
+ 			else
+ 				appendStringInfo(&buf, "(%s)", str);
+ 
+ 			keycoltype = exprType(indexkey);
+ 		}
+ 
+ 		/* Add the operator class name, if not default */
+ 		get_opclass_name(indclass->values[keyno], keycoltype, &buf);
+ 
+ 		/* Add options if relevant */
+ 		if (amrec->amcanorder)
+ 		{
+ 			/* if it supports sort ordering, report DESC and NULLS opts */
+ 			if (opt & INDOPTION_DESC)
+ 			{
+ 				appendStringInfo(&buf, " DESC");
+ 				/* NULLS FIRST is the default in this case */
+ 				if (!(opt & INDOPTION_NULLS_FIRST))
+ 					appendStringInfo(&buf, " NULLS LAST");
+ 			}
+ 			else
+ 			{
+ 				if (opt & INDOPTION_NULLS_FIRST)
+ 					appendStringInfo(&buf, " NULLS FIRST");
+ 			}
+ 		}
+ 
+ 		/* Add operator exclusion constraint */
+ 		appendStringInfo(&buf, " CHECK WITH ");
+ 
+ 		opid = get_opfamily_member(opfamily, keycoltype, keycoltype,
+ 								   strategies[keyno]);
+ 		opName = generate_operator_name(opid, keycoltype, keycoltype);
+ 
+ 		appendStringInfo(&buf, "%s", opName);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	/*
+ 	 * If it has options, append "WITH (options)"
+ 	 */
+ 	str = flatten_reloptions(indexrelid);
+ 	if (str)
+ 	{
+ 		appendStringInfo(&buf, " WITH (%s)", str);
+ 		pfree(str);
+ 	}
+ 
+ 	/*
+ 	 * If it's in a nondefault tablespace, say so, but only if requested
+ 	 */
+ 	tblspc = get_rel_tablespace(indexrelid);
+ 	if (OidIsValid(tblspc))
+ 		appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
+ 						 quote_identifier(get_tablespace_name(tblspc)));
+ 
+ 	/*
+ 	 * If it's a partial index, decompile and append the predicate
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+ 	{
+ 		Node	   *node;
+ 		Datum		predDatum;
+ 		bool		isnull;
+ 		char	   *predString;
+ 
+ 		/* Convert text string to node tree */
+ 		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indpred, &isnull);
+ 		Assert(!isnull);
+ 		predString = TextDatumGetCString(predDatum);
+ 		node = (Node *) stringToNode(predString);
+ 		pfree(predString);
+ 
+ 		/* Deparse */
+ 		str = deparse_expression_pretty(node, context, false, false,
+ 										prettyFlags, 0);
+ 		appendStringInfo(&buf, " WHERE (%s)", str);
+ 	}
+ 
+ 	/* Clean up */
+ 	ReleaseSysCache(ht_idx);
+ 	ReleaseSysCache(ht_idxrel);
+ 	ReleaseSysCache(ht_am);
+ 
+ 	return buf.data;
+ }
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3039,3119 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ 	Relation	conrel;
+ 	SysScanDesc conscan;
+ 	ScanKeyData skey[1];
+ 	HeapTuple	htup;
+ 	Datum		val;
+ 	bool		isnull;
+ 	bool		found = false;
+ 	int16	   *constraints = NULL;
+ 	Oid			relid = indexRelation->rd_index->indrelid;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_constrategies,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null constrategies for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != INT2OID)
+ 			elog(ERROR, "constrategies is not a 1-D smallint array");
+ 		constraints = palloc(sizeof(int16) * nelem);
+ 		memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	return constraints;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2855,2860 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 2855,2861 ----
  				 strcmp(te->desc, "CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "DEFAULT") == 0 ||
  				 strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ 				 strcmp(te->desc, "EXCLUSION CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "INDEX") == 0 ||
  				 strcmp(te->desc, "RULE") == 0 ||
  				 strcmp(te->desc, "TRIGGER") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 3680,3685 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3680,3686 ----
  				i_condeferred,
  				i_contableoid,
  				i_conoid,
+ 				i_condef,
  				i_tablespace,
  				i_options;
  	int			ntups;
***************
*** 3710,3716 **** getIndexes(TableInfo tblinfo[], int numTables)
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80200)
  		{
  			appendPQExpBuffer(query,
  							  "SELECT t.tableoid, t.oid, "
--- 3711,3745 ----
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			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, "
! 					 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
! 							  "c.oid AS conoid, "
! 							  "(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, "
***************
*** 3858,3863 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3887,3893 ----
  		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,3901 **** 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 we found a constraint matching the index, create an
--- 3925,3931 ----
  			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
  			contype = *(PQgetvalue(res, j, i_contype));
  
! 			if (contype == 'p' || contype == 'u' || contype == 'x')
  			{
  				/*
  				 * If we found a constraint matching the index, create an
***************
*** 3913,3919 **** getIndexes(TableInfo tblinfo[], int numTables)
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				constrinfo[j].condef = NULL;
  				constrinfo[j].confrelid = InvalidOid;
  				constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
  				constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
--- 3943,3952 ----
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				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';
***************
*** 10907,10912 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
--- 10940,10970 ----
  						 NULL, NULL);
  		}
  	}
+ 	else if (coninfo->contype == 'x')
+ 	{
+ 		appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(q, "    ADD CONSTRAINT %s %s;\n",
+ 						  fmtId(coninfo->dobj.name),
+ 						  coninfo->condef);
+ 
+ 		appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ 						  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 		appendPQExpBuffer(delq, "%s ",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ 						  fmtId(coninfo->dobj.name));
+ 
+ 		ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ 					 coninfo->dobj.name,
+ 					 tbinfo->dobj.namespace->dobj.name,
+ 					 NULL,
+ 					 tbinfo->rolname, false,
+ 					 "EXCLUSION CONSTRAINT", SECTION_POST_DATA,
+ 					 q->data, delq->data, NULL,
+ 					 coninfo->dobj.dependencies, coninfo->dobj.nDeps,
+ 					 NULL, NULL);
+ 	}
  	else
  	{
  		write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/access/genam.h
--- b/src/include/access/genam.h
***************
*** 16,21 ****
--- 16,23 ----
  
  #include "access/sdir.h"
  #include "access/skey.h"
+ #include "access/xact.h"
+ #include "executor/tuptable.h"
  #include "nodes/tidbitmap.h"
  #include "storage/buf.h"
  #include "storage/lock.h"
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	int2		constrategies[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_constrategies	20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,338 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_check_constraint(Relation heap, Relation index,
+ 								   TupleTableSlot *new_slot,
+ 								   ItemPointer tupleid, Datum *values,
+ 								   bool *isnull, int16 *exclusion_constraint,
+ 								   List *index_exprs, ExprContext *econtext,
+ 								   bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	int16	   *ii_ExclusionConstraint;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1395,1400 **** typedef enum ConstrType			/* types of constraints */
--- 1395,1401 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1429,1439 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1430,1445 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 144,149 **** PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
--- 144,150 ----
  PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
  PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
  PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
+ PG_KEYWORD("exclusion", EXCLUSION, UNRESERVED_KEYWORD)
  PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
  PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
  PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD)
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 15,20 ****
--- 15,21 ----
  #define RELCACHE_H
  
  #include "access/tupdesc.h"
+ #include "access/skey.h"
  #include "nodes/bitmapset.h"
  #include "nodes/pg_list.h"
  
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 44,50 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUSION USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK 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>');
+ -- should fail
+ 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 EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ 
+ DROP TABLE circles;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUSION USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD EXCLUSION 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Tuple "(<(20,20),10>, <(0,0), 5>)" conflicts with existing tuple "(<(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 EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ NOTICE:  ALTER TABLE / ADD EXCLUSION will create implicit index "circles_c1_exclusion1" for table "circles"
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL:  Tuple "(<(0,0),5>, <(0,0), 5>)" conflicts with existing tuple "(<(0,0),5>, <(0,0), 5>)".
+ DROP TABLE circles;
operator-exclusion-constraints-20091101.patch.gzapplication/x-gzip; name=operator-exclusion-constraints-20091101.patch.gzDownload
#136Simon Riggs
simon@2ndQuadrant.com
In reply to: Jeff Davis (#135)
Re: operator exclusion constraints

On Sun, 2009-11-01 at 10:53 -0800, Jeff Davis wrote:

New patch attached.

After reading the docs in the patch I don't believe you're going to all
this trouble to ensure two circles don't overlap. Can you give some
better examples of what you're trying to achieve and why anyone else
would care? (I'm busy, so are others).

I can probably guess, but my feeling is I shouldn't have to. I feel like
this might be a truly great feature, but I'm worried that either it
isn't at all or it is and yet will be overlooked. Does this project link
in with other planned developments in various plugins?

The current patch writes the syntax like this
EXCLUSION USING gist (c CHECK WITH &&)
makes it look like a table constraint, yet it clearly refers to a single
column. That looks very clumsy to read, to my eyes.

The syntax be easier to read if it was stated as a comparison
e.g. in the circle example
CHECK ( NOT (NEW.c && c)) USING GIST
where NEW is the incoming row.
This is similar to the way I would write the constraint if I wanted to
ensure the values in two columns did not match/overlap etc
CHECK ( NOT (col1 && col2))
and is also not such a radical departure from existing SQL Standard
syntax.

We only need the NOT when there isn't a clear negator defined, so in
most cases I would hope to read something like this
CHECK (NEW.pkey != pkey) USING btree
which should be equivalent to the UNIQUE constraint

I don't think its too late to change the syntax.

--
Simon Riggs www.2ndQuadrant.com

#137Nathan Boley
npboley@gmail.com
In reply to: Simon Riggs (#136)
Re: operator exclusion constraints

After reading the docs in the patch I don't believe you're going to all
this trouble to ensure two circles don't overlap. Can you give some
better examples of what you're trying to achieve and why anyone else
would care? (I'm busy, so are others).

Non overlapping time intervals is one use case - think about room
scheduling. I personally want to use it to ensure the consistency of
genomic annotations.

-Nathan

#138Tom Lane
tgl@sss.pgh.pa.us
In reply to: Simon Riggs (#136)
Re: operator exclusion constraints

Simon Riggs <simon@2ndQuadrant.com> writes:

The syntax be easier to read if it was stated as a comparison
e.g. in the circle example
CHECK ( NOT (NEW.c && c)) USING GIST

I don't think this is a good idea at all. NEW is a nonstandard
Postgres-ism, and introducing it into this syntax doesn't seem very
future-proof to me. What's more, the above is not in the least
analogous to a regular CHECK constraint, because there's some implicit
notion of "c" ranging over all other rows, which is not what is meant
by the same column reference in a CHECK constraint.

I agree that the proposed syntax is a bit awkward, but this isn't
better.

regards, tom lane

#139Jeff Davis
pgsql@j-davis.com
In reply to: Simon Riggs (#136)
Re: operator exclusion constraints

On Sun, 2009-11-01 at 22:42 +0000, Simon Riggs wrote:

After reading the docs in the patch I don't believe you're going to all
this trouble to ensure two circles don't overlap. Can you give some
better examples of what you're trying to achieve and why anyone else
would care? (I'm busy, so are others).

Non-overlapping periods of time. I couldn't document that, because the
PERIOD type doesn't exist in core (yet).

I can probably guess, but my feeling is I shouldn't have to. I feel like
this might be a truly great feature, but I'm worried that either it
isn't at all or it is and yet will be overlooked. Does this project link
in with other planned developments in various plugins?

Absolutely:
http://archives.postgresql.org/pgsql-hackers/2009-10/msg01813.php

The current patch writes the syntax like this
EXCLUSION USING gist (c CHECK WITH &&)
makes it look like a table constraint, yet it clearly refers to a single
column. That looks very clumsy to read, to my eyes.

It is a table constraint, and you can specify multiple columns. I don't
see much point in allowing this as a column constraint, because that's
not the typical case.

Most of the time, there will be two columns like:
EXCLUSION(room_number CHECK WITH =, during CHECK WITH &&)

In other words, usually there is both a resource and a period of time
for the reservation. It is of course possible to use it for a column
constraint, and I'll add syntax if there's demand for it.

The syntax be easier to read if it was stated as a comparison
e.g. in the circle example
CHECK ( NOT (NEW.c && c)) USING GIST
where NEW is the incoming row.
This is similar to the way I would write the constraint if I wanted to
ensure the values in two columns did not match/overlap etc
CHECK ( NOT (col1 && col2))
and is also not such a radical departure from existing SQL Standard
syntax.

We've already had very extensive discussion about the syntax. Your idea
is interesting, but I agree with Tom that it's not ideal, either. NEW
might be OK, but Tom's observation about the new meaning of "c" (ranging
over the entire table) is a compelling problem.

Consider:
CHECK ( NOT (NEW.c && c OR c && d))

The right side of the OR could either mean "c overlaps d" or "forall c,
d: c overlaps d". I can't come up with a way to treat "c" consistently
between the left and right side of the OR (put another way, is "c" free
or bound?).

We could allow subselects in CHECK, but it's difficult to infer from
arbitrary queries what I can enforce with an operator exclusion
constraint, and what I can't.

If you want to re-open the syntax discussion, we can (right is better
than soon). However, it is late in the cycle, so I'll need something
very clear quite soon if this is going to make it into 8.5.

Personally I think the current syntax is pretty good.

Regards,
Jeff Davis

#140Simon Riggs
simon@2ndQuadrant.com
In reply to: Jeff Davis (#139)
Re: operator exclusion constraints

On Sun, 2009-11-01 at 15:42 -0800, Jeff Davis wrote:

Most of the time, there will be two columns like:
EXCLUSION(room_number CHECK WITH =, during CHECK WITH &&)

Now that's a great example.

Looks like the classic "don't allow the same room to be booked more than
once at the same time".

It bothers me that we would have completely separate syntax for this
feature as opposed to normal SQL. It also doesn't make it easy to
interpret from the business statement to the implementation. Notice that
the "," above means "AND". How would we use an OR conditional? How would
we express the wish to use a partial index?

How would I express a bidding rule: "Only allow bids that are better
than the highest bid so far"

EXCLUSION (item CHECK WITH =, bid_price CHECK WITH <)

Did I get the ">" the right way around?

How would I specify a tree that has only 2 down branches at any node,
'left' and 'right'?

If you want to re-open the syntax discussion, we can

I don't *want* to and I don't want to derail a good feature. But we'll
be looking at this for years and believe me if you introduce even a
minor bump you'll hear it repeated endlessly.

Personally I think the current syntax is pretty good.

The feature sounds great, regrettably the syntax doesn't seem very
clean, as an objective observer. I apologise if this causes you trouble,
I have no axe to grind here. Not sure that if we submitted this to SQL
Standard committee that it would be accepted as is.

(You made a few other points which I regrettably skimmed over in my
reply).

--
Simon Riggs www.2ndQuadrant.com

#141Peter Eisentraut
peter_e@gmx.net
In reply to: Simon Riggs (#136)
Re: operator exclusion constraints

On Sun, 2009-11-01 at 22:42 +0000, Simon Riggs wrote:

The current patch writes the syntax like this
EXCLUSION USING gist (c CHECK WITH &&)
makes it look like a table constraint, yet it clearly refers to a
single
column. That looks very clumsy to read, to my eyes.

I think the word CHECK should be avoided completely in this syntax, to
avoid confusion with CHECK constraints.

#142Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#138)
Re: operator exclusion constraints

On Sun, 2009-11-01 at 18:07 -0500, Tom Lane wrote:

Simon Riggs <simon@2ndQuadrant.com> writes:

The syntax be easier to read if it was stated as a comparison
e.g. in the circle example
CHECK ( NOT (NEW.c && c)) USING GIST

I don't think this is a good idea at all. NEW is a nonstandard
Postgres-ism, and introducing it into this syntax doesn't seem very
future-proof to me. What's more, the above is not in the least
analogous to a regular CHECK constraint, because there's some implicit
notion of "c" ranging over all other rows, which is not what is meant
by the same column reference in a CHECK constraint.

I agree that the proposed syntax is a bit awkward, but this isn't
better.

Agreed. Just looking for readable, future-proof syntax.

--
Simon Riggs www.2ndQuadrant.com

#143Jeff Davis
pgsql@j-davis.com
In reply to: Simon Riggs (#140)
Re: operator exclusion constraints

On Mon, 2009-11-02 at 07:38 +0000, Simon Riggs wrote:

It bothers me that we would have completely separate syntax for this
feature as opposed to normal SQL. It also doesn't make it easy to
interpret from the business statement to the implementation. Notice that
the "," above means "AND".

Yes, in that way, it's similar to a UNIQUE constraint, e.g.
UNIQUE (a, b). The more columns you add, the more permissive the
constraint.

How would we use an OR conditional?

Specify multiple constraints.

How would
we express the wish to use a partial index?

EXCLUSION (...) WHERE (...)

The perens are actually required around the predicate in this case, due
to syntactic problems.

How would I express a bidding rule: "Only allow bids that are better
than the highest bid so far"

EXCLUSION (item CHECK WITH =, bid_price CHECK WITH <)

That would be a cool feature, unfortunately it won't work in the current
form. This constraint is only enforced on index insert -- imagine what
confusion would be caused when:

UPDATE foo SET third_column = 7 ...

If that's a HOT update, it wouldn't re-check the bid_price. If it turns
into a cold update, it would reject the update because the bid_price is
no longer the highest.

Did I get the ">" the right way around?

The above problem essentially means we only allow commutative operators,
which avoids this source of confusion.

Interestingly, reflexive operators aren't required. So, if <> is
searchable, you can have the opposite of unique: all values must be the
same. That might be interesting for something like:

EXCLUSION(room CHECK WITH =,
during CHECK WITH &&,
student_grade CHECK WITH <>)

To ensure that a shared room isn't shared between students of different
grades. Not the most compelling use case, but I could imagine something
along these lines being useful.

Maybe a better example would involve sheep and wolves ;)

How would I specify a tree that has only 2 down branches at any node,
'left' and 'right'?

I'm not sure I understand this exactly. If the left or right is
explcitly a part of the tuple, I think it can be done with unique.

If not, and you're looking for a maximum of two tuples, you can see my
ill-fated extension to this feature here:

http://archives.postgresql.org/pgsql-hackers/2009-11/msg00016.php

As Tom pointed out, most use cases would not have a constant limit
throughout the table. If you do have such a use case, you can revive the
proposal.

Not sure that if we submitted this to SQL
Standard committee that it would be accepted as is.

There are implementation details bubbling up to the user-visible
behavior, and I share your concern. The SQL committee would never care
about these implementation details, and I wish we didn't have to,
either.

The machinism that I've employed searches (using a dirty snapshot) for
all of the physical tuples that cause a logical conflict with the
physical tuple currently being added. If found, it uses physical
information from those tuples, like visibility information, to determine
whether to wait, and on whom to wait. After waiting it may either
proceed or abort.

If we move closer to a nice, clean query to express the constraint, it
gets very difficult to tell which physical tuple is responsible for the
conflict. If we don't know what physical tuple is causing the conflict,
we have to serialize all writes.

Additionally, the more it looks like a query, the more we have to tell
users "follow this template" -- which will just lead to confusion and
disappointment for users who think we've implemented SQL ASSERT (which
we haven't).

Although the current syntax isn't great, it is declarative, and it does
allow a variety of constraints.

I certainly welcome ideas that will make a better trade-off here. At the
end, I just want a feature that can implement temporal keys.

Regards,
Jeff Davis

#144Jeff Davis
pgsql@j-davis.com
In reply to: Peter Eisentraut (#141)
Re: operator exclusion constraints

On Mon, 2009-11-02 at 08:25 +0000, Peter Eisentraut wrote:

I think the word CHECK should be avoided completely in this syntax, to
avoid confusion with CHECK constraints.

This is an easy change. I don't have a strong opinion, so the only thing
I can think to do is ask for a vote.

Do you have a specific alternative in mind? How about just "WITH"?

Regards,
Jeff Davis

#145Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#144)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Mon, 2009-11-02 at 08:25 +0000, Peter Eisentraut wrote:

I think the word CHECK should be avoided completely in this syntax, to
avoid confusion with CHECK constraints.

This is an easy change. I don't have a strong opinion, so the only thing
I can think to do is ask for a vote.

Do you have a specific alternative in mind? How about just "WITH"?

I think we had that discussion already, and rejected using "WITH" by
itself because it was so totally devoid of suggestion of what it was
the system would do "with" the expression or operator.

If we don't want to introduce a new reserved word it's difficult to
find alternatives :-(. One thing that just came to mind is that we
might be able to do something like

EXCLUSION (expr CHECK NOT operator)
or
EXCLUSION (expr CONSTRAIN NOT operator)

I like the "NOT" here because "CHECK NOT =" seems to convey pretty
clearly what it is you are checking for. Because NOT is reserved and
can't appear as a connective, I think that this approach might allow
a non-reserved leading word, thus possibly the second variant would
work without reserving CONSTRAIN. I have not tested whether bison
agrees with me though ;-). In any case I think "CHECK NOT =" reads
pretty well, and don't feel a strong urge to use some other word there.

regards, tom lane

#146Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#145)
Re: operator exclusion constraints

On Mon, 2009-11-02 at 13:12 -0500, Tom Lane wrote:

Jeff Davis <pgsql@j-davis.com> writes:

On Mon, 2009-11-02 at 08:25 +0000, Peter Eisentraut wrote:

I think the word CHECK should be avoided completely in this syntax, to
avoid confusion with CHECK constraints.

This is an easy change. I don't have a strong opinion, so the only thing
I can think to do is ask for a vote.

Do you have a specific alternative in mind? How about just "WITH"?

I think we had that discussion already, and rejected using "WITH" by
itself because it was so totally devoid of suggestion of what it was
the system would do "with" the expression or operator.

If we don't want to introduce a new reserved word it's difficult to
find alternatives :-(. One thing that just came to mind is that we
might be able to do something like

EXCLUSION (expr CHECK NOT operator)
or
EXCLUSION (expr CONSTRAIN NOT operator)

I like the "NOT" here because "CHECK NOT =" seems to convey pretty
clearly what it is you are checking for. Because NOT is reserved and
can't appear as a connective, I think that this approach might allow
a non-reserved leading word, thus possibly the second variant would
work without reserving CONSTRAIN. I have not tested whether bison
agrees with me though ;-). In any case I think "CHECK NOT =" reads
pretty well, and don't feel a strong urge to use some other word there.

Yep, like the NOT.

Other ideas
EXCLUSION (expr NOT operator)

CONSTRAINT (expr NOT operator ALL ROWS)

--
Simon Riggs www.2ndQuadrant.com

#147Jeff Davis
pgsql@j-davis.com
In reply to: Simon Riggs (#146)
Re: operator exclusion constraints

On Mon, 2009-11-02 at 18:28 +0000, Simon Riggs wrote:

I like the "NOT" here because "CHECK NOT =" seems to convey pretty
clearly what it is you are checking for. Because NOT is reserved and
can't appear as a connective, I think that this approach might allow
a non-reserved leading word, thus possibly the second variant would
work without reserving CONSTRAIN. I have not tested whether bison
agrees with me though ;-). In any case I think "CHECK NOT =" reads
pretty well, and don't feel a strong urge to use some other word there.

Peter, do any of these ideas work for you? It looks like this opens the
door to using a word other than CHECK. CONSTRAIN NOT is a little
awkward, is there another word that might work better?

I'm not excited about using NOT, because I think it has a hint of a
double-negative when combined with EXCLUSION. The original idea was to
specify the way to find tuples mutually exclusive with the new tuple;
and NOT makes that a little less clear, in my opinion. But I'm fine with
it if that's what everyone else thinks is best.

Regards,
Jeff Davis

#148Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#147)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

I'm not excited about using NOT, because I think it has a hint of a
double-negative when combined with EXCLUSION.

Well, the choice of EXCLUSION isn't set in stone either ...

regards, tom lane

#149Dean Rasheed
dean.a.rasheed@googlemail.com
In reply to: Tom Lane (#148)
Re: operator exclusion constraints

2009/11/3 Tom Lane <tgl@sss.pgh.pa.us>:

Jeff Davis <pgsql@j-davis.com> writes:

I'm not excited about using NOT, because I think it has a hint of a
double-negative when combined with EXCLUSION.

Well, the choice of EXCLUSION isn't set in stone either ...

Is this really a generalized uniqueness constraint, extended to
support operators other than = ?
Perhaps sticking with the word UNIQUE might be more suggestive of this:

UNIQUE (room_number WITH = , during WITH &&)

or:

UNIQUE (room_number , during USING && )

- Dean

#150Jeff Davis
pgsql@j-davis.com
In reply to: Dean Rasheed (#149)
Re: operator exclusion constraints

On Tue, 2009-11-03 at 21:31 +0000, Dean Rasheed wrote:

Is this really a generalized uniqueness constraint, extended to
support operators other than = ?

That has been discussed in the past:

http://archives.postgresql.org/message-id/1253119552.24770.203.camel@jdavis
http://archives.postgresql.org/message-id/1253122946.24770.250.camel@jdavis

However, some constraints allowed by this feature are the *opposite* of
unique: consider "<>".

Personally, I don't like to use the word UNIQUE to describe a constraint
that may reject unique values or permit duplicates.

We already have some reasonable agreement around EXCLUSION ... CHECK
WITH. We should stick with the current syntax unless there's a good
consensus around some other specific proposal.

Regards,
Jeff Davis

#151Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#150)
Re: operator exclusion constraints

On Tue, Nov 3, 2009 at 5:05 PM, Jeff Davis <pgsql@j-davis.com> wrote:

We already have some reasonable agreement around EXCLUSION ... CHECK
WITH. We should stick with the current syntax unless there's a good
consensus around some other specific proposal.

Yeah. I don't like the inflexibility of the current syntax, but
that's mostly because I wish the feature itself could be made more
general. It would be nice to be able to write constraints of the
form:

forall <vars> : <expression>

For example, a uniqueness constraint on a column c is: forall x,y : x.c != y.c
And a does-not overlap constraint might look like this: forall x,y :
NOT (x.c && y.c)

Note that an ordinary check constraint is a special case of this where
there is only one rowvar and it is implicit.

HOWEVER, this is probably a lot more work than what you've already
done, and what you've already done is really good, and we shouldn't
hesitate to commit on the grounds that it won't cure diabetes and
balance the budget. I don't think there is any really beautiful
syntax for the feature as it stands, but considering how useful it is
I am not inclined to stand on ceremony...

...Robert

#152Peter Eisentraut
peter_e@gmx.net
In reply to: Jeff Davis (#147)
Re: operator exclusion constraints

On Tue, 2009-11-03 at 08:51 -0800, Jeff Davis wrote:

Peter, do any of these ideas work for you? It looks like this opens the
door to using a word other than CHECK. CONSTRAIN NOT is a little
awkward, is there another word that might work better?

I'm not excited about using NOT, because I think it has a hint of a
double-negative when combined with EXCLUSION. The original idea was to
specify the way to find tuples mutually exclusive with the new tuple;
and NOT makes that a little less clear, in my opinion. But I'm fine with
it if that's what everyone else thinks is best.

I've been thinking how the other constraint types "read", e.g.,

a CHECK (a > 0) means "check that a is > 0"
b PRIMARY KEY means "b is the primary key"
c UNIQUE means "c is unique [in this table]"

That's easy. Whereas

EXCLUSION (a CHECK NOT =) means, er, "check that a is not an exclusion
of =" or something. Huh?

A more readable alternative might be going into the direction of
(written as a column constraint):

a EXCLUSIVE BY =

meaning "a is exclusive [in this table] [as measured] by =". Or as
table constraint

EXCLUSIVE (a, b) BY =

And then you could think of UNIQUE as "EXCLUSIVE BY default-equals-op".

EXCLUSIVE is already a key word, by the way.

#153Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#152)
Re: operator exclusion constraints

On Thu, Nov 5, 2009 at 9:01 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

On Tue, 2009-11-03 at 08:51 -0800, Jeff Davis wrote:

Peter, do any of these ideas work for you? It looks like this opens the
door to using a word other than CHECK. CONSTRAIN NOT is a little
awkward, is there another word that might work better?

I'm not excited about using NOT, because I think it has a hint of a
double-negative when combined with EXCLUSION. The original idea was to
specify the way to find tuples mutually exclusive with the new tuple;
and NOT makes that a little less clear, in my opinion. But I'm fine with
it if that's what everyone else thinks is best.

I've been thinking how the other constraint types "read", e.g.,

a CHECK (a > 0) means "check that a is > 0"
b PRIMARY KEY means "b is the primary key"
c UNIQUE means "c is unique [in this table]"

That's easy.  Whereas

EXCLUSION (a CHECK NOT =) means, er, "check that a is not an exclusion
of =" or something.  Huh?

A more readable alternative might be going into the direction of
(written as a column constraint):

a EXCLUSIVE BY =

meaning "a is exclusive [in this table] [as measured] by =".  Or as
table constraint

EXCLUSIVE (a, b) BY =

And then you could think of UNIQUE as "EXCLUSIVE BY default-equals-op".

EXCLUSIVE is already a key word, by the way.

Ooh, that's kind of neat. But I think you'd need EXCLUSIVE (a, b) BY
(=, =), since it could equally well be EXCLUSIVE (a, b) BY (=, &&).

...Robert

#154Alvaro Herrera
alvherre@commandprompt.com
In reply to: Robert Haas (#153)
Re: operator exclusion constraints

Robert Haas escribi�:

On Thu, Nov 5, 2009 at 9:01 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

Or as table constraint

EXCLUSIVE (a, b) BY =

And then you could think of UNIQUE as "EXCLUSIVE BY default-equals-op".

EXCLUSIVE is already a key word, by the way.

Ooh, that's kind of neat. But I think you'd need EXCLUSIVE (a, b) BY
(=, =), since it could equally well be EXCLUSIVE (a, b) BY (=, &&).

Perhaps EXCLUSIVE (a BY =, b BY =)

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

#155Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#153)
Re: operator exclusion constraints

Robert Haas <robertmhaas@gmail.com> writes:

Ooh, that's kind of neat. But I think you'd need EXCLUSIVE (a, b) BY
(=, =), since it could equally well be EXCLUSIVE (a, b) BY (=, &&).

Yeah, we definitely want some parentheses delimiting the expression.
EXCLUSIVE still feels like the wrong part-of-speech though. How
about EXCLUDING (...) BY ... instead?

regards, tom lane

#156David E. Wheeler
david@kineticode.com
In reply to: Tom Lane (#155)
Re: operator exclusion constraints

On Nov 5, 2009, at 6:56 AM, Tom Lane wrote:

Yeah, we definitely want some parentheses delimiting the expression.
EXCLUSIVE still feels like the wrong part-of-speech though. How
about EXCLUDING (...) BY ... instead?

CHECK is a verb; so what about a verb here?

EXCLUDE (...) BY ...

Best,

David

#157Jeff Davis
pgsql@j-davis.com
In reply to: David E. Wheeler (#156)
Re: operator exclusion constraints

On Thu, 2009-11-05 at 09:25 -0800, David E. Wheeler wrote:

CHECK is a verb; so what about a verb here?

CHECK -- verb or noun
UNIQUE -- adjective
PRIMARY KEY -- noun
FOREIGN KEY -- noun

Any of them can be called an adjective if you put it before the word
"constraint". But that's kind of circular: if we made a new type of
constraint and called it XYZ, we could say XYZ CONSTRAINT, and call XYZ
an adjective.

I'm having trouble finding a clear pattern to follow.

Regards,
Jeff Davis

#158Alvaro Herrera
alvherre@commandprompt.com
In reply to: Jeff Davis (#157)
Re: operator exclusion constraints

Jeff Davis escribi�:

I'm having trouble finding a clear pattern to follow.

Rewrite SQL in Latin?

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

#159David E. Wheeler
david@kineticode.com
In reply to: Jeff Davis (#157)
Re: operator exclusion constraints

On Nov 5, 2009, at 10:13 AM, Jeff Davis wrote:

CHECK -- verb or noun
UNIQUE -- adjective
PRIMARY KEY -- noun
FOREIGN KEY -- noun

Put like that, I'm thinking:

EXCLUSION - noun or adjective

EXCLUSION (...) BY ...

But that doesn't read as well to my eye as:

EXCLUDE (...) BY ...

Or

EXCLUDING (...) BY ...

I think either of those last two work well enough.

Best,

David

#160Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#155)
Re: operator exclusion constraints

On Thu, 2009-11-05 at 09:56 -0500, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes:

Ooh, that's kind of neat. But I think you'd need EXCLUSIVE (a, b) BY
(=, =), since it could equally well be EXCLUSIVE (a, b) BY (=, &&).

Yeah, we definitely want some parentheses delimiting the expression.
EXCLUSIVE still feels like the wrong part-of-speech though. How
about EXCLUDING (...) BY ... instead?

I think EXCLUDING conflicts with the EXCLUDING in LIKE. Also, it becomes
a little more difficult to place the access method clause, because
"EXCLUDING USING gist" doesn't sound great.

Regards,
Jeff Davis

#161David E. Wheeler
david@kineticode.com
In reply to: Jeff Davis (#160)
Re: operator exclusion constraints

On Nov 5, 2009, at 11:09 AM, Jeff Davis wrote:

I think EXCLUDING conflicts with the EXCLUDING in LIKE. Also, it
becomes
a little more difficult to place the access method clause, because
"EXCLUDING USING gist" doesn't sound great.

Well that's clearly a verb. So perhaps "EXCLUDE USING
gist" ("EXCLUDING USING gist" is a little weirder).

Best,

David

#162Jeff Davis
pgsql@j-davis.com
In reply to: David E. Wheeler (#159)
Re: operator exclusion constraints

On Thu, 2009-11-05 at 10:30 -0800, David E. Wheeler wrote:

But that doesn't read as well to my eye as:

EXCLUDE (...) BY ...

I think EXCLUDE might be a little *too* specific. It sounds like
whatever is on the right hand side will be excluded, but that's not
really what happens.

EXCLUSION is vague about what is doing the excluding and what is being
excluded. I think that's good in this case, because the actual meaning
can't easily be expressed with a couple keywords, so suggesting the
behavior is about as close as we can get (unless someone comes up with a
new idea).

EXCLUDING (...) BY ...

I think that's better, but still sounds a little wrong to me.

Regards,
Jeff Davis

#163Jeff Davis
pgsql@j-davis.com
In reply to: David E. Wheeler (#161)
Re: operator exclusion constraints

On Thu, 2009-11-05 at 11:16 -0800, David E. Wheeler wrote:

Well that's clearly a verb. So perhaps "EXCLUDE USING
gist" ("EXCLUDING USING gist" is a little weirder).

That's not bad.

As I just said in my other email, I think the word EXCLUDE is a little
bit too specific, but the other ideas out there aren't perfect, either.

Regards,
Jeff Davis

#164Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#155)
Re: operator exclusion constraints

On Thu, 2009-11-05 at 09:56 -0500, Tom Lane wrote:

Yeah, we definitely want some parentheses delimiting the expression.
EXCLUSIVE still feels like the wrong part-of-speech though. How
about EXCLUDING (...) BY ... instead?

If I put EXCLUSION in the type_func_name keyword list, it works fine.
But I'm having a little trouble trying to use EXCLUDING or EXCLUSIVE,
because if I move them from unreserved to any other keyword list, I get
reduce/reduce conflicts.

Am I doing something wrong? I would assume that making words more
reserved would usually not lead to conflicts.

Regards,
Jeff Davis

#165Jeff Davis
pgsql@j-davis.com
In reply to: David E. Wheeler (#159)
Re: operator exclusion constraints

Out of the ideas in this thread, here are the best and most complete so
far:

Form 1:
WORD1 [ USING index_method ]
( expression WORD2 operator [, ... ] ) index_parameters
[ WHERE ( predicate ) ]

Form 2:
WORD1 [ USING index_method ]
( expression [, ... ] ) WORD2 ( operator [, ...] ) index_parameters
[ WHERE ( predicate ) ]

Word 1:
EXCLUSION
EXCLUDE

Word 2:
CHECK WITH
BY (only works with Form 2)

The current syntax is Form 1 using EXCLUSION/CHECK WITH.

I had trouble making the grammar work using EXCLUDING or EXCLUSIVE.
However, even if we get past that problem, we would want to move the
"USING index_method" clause afterward (otherwise it's too awkward), but
that causes conflicts with the index_parameters (because of the "USING
INDEX TABLESPACE tablespace" clause).

CHECK WITH has a little more meaning than BY, but as Peter says, it
might cause confusion with a regular CHECK constraint. EXCLUDE implies
that the thing on the right hand side is being excluded, when it's
actually specifying what pairs of tuples are mutally exclusive; so I
think EXCLUDE is a little bit too specific.

Right now, I am leaning toward Form 2, and EXCLUSION/BY. So the typical
case would read like:

EXCLUSION USING gist (room, during) BY (=, &&)

Comments, ideas or votes welcome. However, if anyone has any new ideas,
please make an attempt to ensure that the grammar can work and that it's
just as expressive. We've had several productive brainstorming sessions
so far, and I certainly appreciate the input, but I think it's time to
move toward a consensus.

Regards,
Jeff Davis

#166David E. Wheeler
david@kineticode.com
In reply to: Jeff Davis (#165)
Re: operator exclusion constraints

On Nov 6, 2009, at 10:42 AM, Jeff Davis wrote:

Right now, I am leaning toward Form 2, and EXCLUSION/BY. So the
typical
case would read like:

EXCLUSION USING gist (room, during) BY (=, &&)

I like this, but like EXCLUDE better

EXCLUDE USING gist (room, during) BY (=, &&)

Is your objection to EXCLUDE for cases when there is no USING clause?

EXLUDE (room, during) BY (=, &&)

Yes, I can see how that'd be a bit more confusing. So EXCLUSION
probably is best.

BTW, is it the case that room maps to = and during maps to && in this
example? If so, wouldn't it make more sense to combine them?

EXCLUSION (room WITH =, during WITH &&)

Or am I misunderstanding how this works (quite likely, I'm sure).

Best,

David

#167Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#164)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

If I put EXCLUSION in the type_func_name keyword list, it works fine.
But I'm having a little trouble trying to use EXCLUDING or EXCLUSIVE,
because if I move them from unreserved to any other keyword list, I get
reduce/reduce conflicts.

Am I doing something wrong? I would assume that making words more
reserved would usually not lead to conflicts.

Putting any of these at a higher level than unreserved is problematic
because they are not permitted to be reserved words according to the
SQL spec. While we do have some nonstandard reserved words, I think
the bar for adding new ones has to be pretty high, because it will break
applications that (a) worked before and (b) are not violating either the
letter or the spirit of the standard.

I'd be less worried about making a new col_name_keyword, but if it has
to be type_func_name_keyword then it's nearly as bad as fully reserved.

The main advantage of the CHECK WITH syntax in my eyes was that it
avoided the need to create a new reserved word.

regards, tom lane

#168Tom Lane
tgl@sss.pgh.pa.us
In reply to: David E. Wheeler (#166)
Re: operator exclusion constraints

"David E. Wheeler" <david@kineticode.com> writes:

BTW, is it the case that room maps to = and during maps to && in this
example? If so, wouldn't it make more sense to combine them?

EXCLUSION (room WITH =, during WITH &&)

I think so too. Keeping the expression and the associated operator
together seems more readable and less error-prone than having them
separated by other columns.

BTW, where is the optional opclass name going to fit in? ("There
isn't one" is not an acceptable answer.)

regards, tom lane

#169Jeff Davis
pgsql@j-davis.com
In reply to: David E. Wheeler (#166)
Re: operator exclusion constraints

On Fri, 2009-11-06 at 10:50 -0800, David E. Wheeler wrote:

Is your objection to EXCLUDE for cases when there is no USING clause?

EXLUDE (room, during) BY (=, &&)

"Objection" is too strong a word. EXCLUDE is a transitive verb, so it's
slightly confusing in the above case.

BTW, is it the case that room maps to = and during maps to && in this
example? If so, wouldn't it make more sense to combine them?

EXCLUSION (room WITH =, during WITH &&)

That's (close to) the current syntax, which I'm perfectly fine with.
Form 1 with EXCLUSION/CHECK WITH is the current syntax.

It seemed like the winds were shifting towards separating them, but I'm
happy leaving it alone.

Regards,
Jeff Davis

#170Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#168)
Re: operator exclusion constraints

On Fri, 2009-11-06 at 14:05 -0500, Tom Lane wrote:

BTW, where is the optional opclass name going to fit in? ("There
isn't one" is not an acceptable answer.)

"expression" is actually an index_elem, which includes all of the
options including the opclass. I'll make that more clear.

Regards,
Jeff Davis

#171Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#169)
Re: operator exclusion constraints

On Fri, Nov 6, 2009 at 2:11 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Fri, 2009-11-06 at 10:50 -0800, David E. Wheeler wrote:

Is your objection to EXCLUDE for cases when there is no USING clause?

     EXLUDE (room, during) BY (=, &&)

"Objection" is too strong a word. EXCLUDE is a transitive verb, so it's
slightly confusing in the above case.

BTW, is it the case that room maps to = and during maps to && in this
example? If so, wouldn't it make more sense to combine them?

     EXCLUSION (room WITH =, during WITH &&)

That's (close to) the current syntax, which I'm perfectly fine with.
Form 1 with EXCLUSION/CHECK WITH is the current syntax.

It seemed like the winds were shifting towards separating them, but I'm
happy leaving it alone.

I don't favor separating them. Locality of reference is good.

...Robert

#172Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#167)
Re: operator exclusion constraints

On Fri, 2009-11-06 at 14:00 -0500, Tom Lane wrote:

The main advantage of the CHECK WITH syntax in my eyes was that it
avoided the need to create a new reserved word.

It still needs the EXCLUSION keyword, though, and where does that fit
in? If I include it as unreserved, I get shift/reduce conflicts. If I
include it as a type_func_name keyword, it works.

CHECK, FOREIGN, PRIMARY, and UNIQUE are all reserved as well, which
makes sense because it looks like they conflict directly with column
names in the table definition.

Do you see a way to avoid that problem?

Regards,
Jeff Davis

#173Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#172)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Fri, 2009-11-06 at 14:00 -0500, Tom Lane wrote:

The main advantage of the CHECK WITH syntax in my eyes was that it
avoided the need to create a new reserved word.

It still needs the EXCLUSION keyword, though, and where does that fit
in? If I include it as unreserved, I get shift/reduce conflicts. If I
include it as a type_func_name keyword, it works.

If you could get it down to col_name_keyword, I wouldn't complain.

Most of the problems we've had with having to reserve keywords in CREATE
TABLE stem from the fact that they can follow a DEFAULT expression.
If we restrict this thing to being a table constraint, not a column
constraint, it seems like the issue might go away (and in fact I think
you might not even need col_name_keyword). As long as we are explicitly
specifying column names in the exclusion expressions, I don't think it's
very sensible to write it as a column constraint anyway. Have you
tried that approach?

regards, tom lane

#174Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#173)
Re: operator exclusion constraints

On Fri, 2009-11-06 at 14:59 -0500, Tom Lane wrote:

If we restrict this thing to being a table constraint, not a column
constraint, it seems like the issue might go away (and in fact I think
you might not even need col_name_keyword).

I never enabled the syntax for column constraints, so it was always a
part of ConstraintElem.

To make sure I understand what you're saying, you think that:

CREATE TABLE foo
(
exclusion int,
EXCLUSION (exclusion CHECK WITH =)
);

should work? It's not ambiguous, but I'm not an expert on the grammar,
so I don't immediately know a non-invasive way to express that.

Regards,
Jeff Davis

#175Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#174)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

To make sure I understand what you're saying, you think that:

CREATE TABLE foo
(
exclusion int,
EXCLUSION (exclusion CHECK WITH =)
);

should work?

Well, it looks like it should be able to work, because left-paren
can't immediately follow a column name AFAIR. Maybe I'm missing
something. What's your grammar patch exactly, and what does
bison -v finger as being the problem?

regards, tom lane

#176Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#175)
1 attachment(s)
Re: operator exclusion constraints

On Fri, 2009-11-06 at 19:05 -0500, Tom Lane wrote:

CREATE TABLE foo
(
exclusion int,
EXCLUSION (exclusion CHECK WITH =)
);

Well, it looks like it should be able to work, because left-paren
can't immediately follow a column name AFAIR.

I agree; I don't think it's ambiguous. The other possibility is the
optional "USING index_method" clause in between, but USING is already
reserved, so I don't see a problem there either.

Maybe I'm missing
something. What's your grammar patch exactly, and what does
bison -v finger as being the problem?

bison -v doesn't show anything useful beyond saying that there is one
shift/reduce conflict. The gram.output is 10MB, which doesn't help me
much (I'm still trying to make sense of it). I'd offer to send it along,
but I'm sure bison would produce the same thing for you.

Patch attached with EXCLUSION as a col_name_keyword and one shift/reduce
conflict.

Regards,
Jeff Davis

Attachments:

grammar.difftext/x-patch; charset=UTF-8; name=grammar.diffDownload
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index bf9dd33..a0a8601 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -355,6 +355,7 @@ 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
+				exclusion_where_clause
 %type <list>	func_arg_list
 %type <node>	func_arg_expr
 %type <list>	row type_list array_expr_list
@@ -435,6 +436,7 @@ static TypeName *TableFuncTypeName(List *columns);
 %type <str>		opt_existing_window_name
 %type <ival>	opt_frame_clause frame_extent frame_bound
 
+%type <list>	ExclusionConstraintList ExclusionConstraintElem
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -478,7 +480,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
+	EXCLUDING EXCLUSION EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
 
 	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
 	FREEZE FROM FULL FUNCTION FUNCTIONS
@@ -2506,6 +2508,21 @@ ConstraintElem:
 					n->initdeferred		= ($11 & 2) != 0;
 					$$ = (Node *)n;
 				}
+			| EXCLUSION access_method_clause '(' ExclusionConstraintList ')'
+				opt_definition OptConsTableSpace exclusion_where_clause
+				ConstraintAttributeSpec
+				{
+					Constraint *n = makeNode(Constraint);
+					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+					n->using_method		  = $2;
+					n->operator_exclusion = $4;
+					n->options			  = $6;
+					n->indexspace		  = $7;
+					n->where_clause		  = $8;
+					n->deferrable		  = ($9 & 1) != 0;
+					n->initdeferred		  = ($9 & 2) != 0;
+					$$ = (Node *)n;
+				}
 		;
 
 opt_column_list:
@@ -2546,6 +2563,23 @@ key_match:  MATCH FULL
 			}
 		;
 
+ExclusionConstraintList:
+			ExclusionConstraintElem					{ $$ = list_make1($1); }
+			| ExclusionConstraintList ',' ExclusionConstraintElem
+				{ $$ = lappend($1, $3); }
+		;
+
+ExclusionConstraintElem: index_elem CHECK WITH any_operator
+			{
+				$$ = list_make2($1, $4);
+			}
+		;
+
+exclusion_where_clause:
+			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
@@ -10772,6 +10806,7 @@ col_name_keyword:
 			| COALESCE
 			| DEC
 			| DECIMAL_P
+			| EXCLUSION
 			| EXISTS
 			| EXTRACT
 			| FLOAT_P
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index d9181d8..e728d98 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -144,6 +144,7 @@ PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
 PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
 PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("exclusion", EXCLUSION, COL_NAME_KEYWORD)
 PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
 PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD)
#177Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#176)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Fri, 2009-11-06 at 19:05 -0500, Tom Lane wrote:

Maybe I'm missing
something. What's your grammar patch exactly, and what does
bison -v finger as being the problem?

bison -v doesn't show anything useful beyond saying that there is one
shift/reduce conflict. The gram.output is 10MB, which doesn't help me
much (I'm still trying to make sense of it).

Well, you need to learn a bit more about bison I think. The complaint
is

State 1135 conflicts: 1 shift/reduce

so we look at state 1135, which says:

state 1135

241 alter_table_cmd: ADD_P . opt_column columnDef
251 | ADD_P . TableConstraint

CHECK shift, and go to state 1698
COLUMN shift, and go to state 1742
CONSTRAINT shift, and go to state 1699
EXCLUSION shift, and go to state 1700
FOREIGN shift, and go to state 1701
PRIMARY shift, and go to state 1702
UNIQUE shift, and go to state 1703

EXCLUSION [reduce using rule 887 (opt_column)]
$default reduce using rule 887 (opt_column)

TableConstraint go to state 1743
ConstraintElem go to state 1705
opt_column go to state 1744

This is the state immediately after scanning "ADD" in an ALTER TABLE
command, and what it's unhappy about is that it has two different things
to do if the next token is EXCLUSION. (The dot in the productions
indicates "where we are", and the square brackets mark the unreachable
action.) If you check the other mentioned states it becomes clear that
the state-1700 path leads to deciding that EXCLUSION begins a
TableConstraint, while rule 887 is the "empty" alternative for
opt_column, and is what would have to be done next if EXCLUSION is a
column name beginning a ColumnDef. So the difficulty is that it can't
be sure whether EXCLUSION is a column name without looking one token
past EXCLUSION, but it has to decide whether to eliminate COLUMN before
it can look ahead past EXCLUSION.

This is a pretty common difficulty with empty-producing productions.
The usual way around it is to eliminate the empty production by making
the calling production a bit more redundant. In this case, we can fix
it by replacing

alter_table_cmd:
ADD_P opt_column columnDef

with two productions

alter_table_cmd:
ADD_P columnDef
| ADD_P COLUMN columnDef

The reason this fixes it is that now the parser does not have to make
a shift-reduce decision while EXCLUSION is the next token: it's just
going to shift all the time, and it only has to reduce once EXCLUSION
is the current token and it can see the next one as lookahead. (In
which case, it will reduce EXCLUSION to ColId and proceed with the
its-a-ColumnDef path, only if the next token isn't "(" or "USING".)

Another way to think about is is that we are forcing bison to split
this one state into two, but I find it easier to understand how to
fix the problem by looking for ways to postpone the reduce decision.

So IOW, you need the attached patch to the patch.

regards, tom lane

#178Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#177)
Re: operator exclusion constraints

I wrote:

So IOW, you need the attached patch to the patch.

... plus, of course, move EXCLUSION to the unreserved_keyword list.
Or maybe forget about it and go to EXCLUDE or EXCLUDING?

regards, tom lane

#179Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#177)
Re: operator exclusion constraints

Tom Lane wrote:

This is a pretty common difficulty with empty-producing productions.
The usual way around it is to eliminate the empty production by making
the calling production a bit more redundant. In this case, we can fix
it by replacing

alter_table_cmd:
ADD_P opt_column columnDef

with two productions

alter_table_cmd:
ADD_P columnDef
| ADD_P COLUMN columnDef

The reason this fixes it is that now the parser does not have to make
a shift-reduce decision while EXCLUSION is the next token: it's just
going to shift all the time, and it only has to reduce once EXCLUSION
is the current token and it can see the next one as lookahead. (In
which case, it will reduce EXCLUSION to ColId and proceed with the
its-a-ColumnDef path, only if the next token isn't "(" or "USING".)

Another way to think about is is that we are forcing bison to split
this one state into two, but I find it easier to understand how to
fix the problem by looking for ways to postpone the reduce decision.

This is a pretty good short explanation of how to deal with shift/reduce
problems in bison. With your permission I'm going to copy it to the Wiki
- it's amazing given the ubiquity of bison, yacc and friends how little
the mechanics of LALR(1) parsers are understood. We've had many people
puzzling over it over the years.

cheers

andrew

#180Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#179)
Re: operator exclusion constraints

Andrew Dunstan <andrew@dunslane.net> writes:

This is a pretty good short explanation of how to deal with shift/reduce
problems in bison. With your permission I'm going to copy it to the Wiki

If you like, but I think the part about figuring out which production
is the problem seemed to be at least as important for Jeff ...

regards, tom lane

#181Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#177)
2 attachment(s)
Re: operator exclusion constraints

On Fri, 2009-11-06 at 21:20 -0500, Tom Lane wrote:

bison -v doesn't show anything useful beyond saying that there is one
shift/reduce conflict. The gram.output is 10MB, which doesn't help me
much (I'm still trying to make sense of it).

Well, you need to learn a bit more about bison I think.

Yes, I do. Thank you very much for the detailed explanation; it was very
informative.

I have made the suggested changes, and now it works easily with
EXCLUSION, EXCLUDING, EXCLUSIVE, or EXCLUDE. I have also merged with the
latest changes, and I did another cleanup pass on the patch.

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091107.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091107.context.patchDownload
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,63 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
  </synopsis>
  
   </refsynopsisdiv>
--- 51,69 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   EXCLUSION [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">index_element</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
+ 
+ <phrase>and <replaceable class="PARAMETER">index_element</replaceable> in an <literal>EXCLUSION</literal> constraint is:</phrase>
+ 
+ { column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+ 
  </synopsis>
  
   </refsynopsisdiv>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 553,620 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>EXCLUSION [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">index_element</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
+     <listitem>
+      <para>
+       The <literal>EXCLUSION</> clause specifies an operator exclusion
+       constraint. An operator exclusion constraint is more general
+       than a <literal>UNIQUE</literal> constraint, and can use an
+       arbitrary operator to detect conflicts. For instance, you can
+       specify the constraint that no two tuples in the table contain
+       overlapping circles (see <xref linkend="datatype-geometric">) by
+       using the <literal>&&</literal> operator.
+      </para>
+ 
+      <para>
+       The constraint specifies the conflict condition, so the operator
+       should return <literal>TRUE</literal> when applied to two
+       conflicting values. Also, the operator specified must be
+       commutative (that is, the commutator of the operator must be the
+       operator itself), must be a boolean operator, and must be
+       associated with an operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) using
+       <replaceable class="parameter">index_method</replaceable>. The
+       constraint is violated if, and only if, there exist two tuples
+       where all corresponding expressions between the tuples conflict
+       according
+       to <replaceable class="parameter">operator</replaceable>
+       (i.e. the operator returns <literal>TRUE</literal>).
+      </para>
+ 
+      <para>
+       Internally, operator exclusion constraints use an index to
+       perform a search looking for conflicting values, and handle
+       concurrent operations similar to a <literal>UNIQUE</literal>
+       constraint. If all of the operators are specified as the
+       equality operator (usually <literal>=</literal>), this
+       constraint behaves identically to a <literal>UNIQUE</literal>
+       constraint. However, it may exhibit slightly worse performance
+       than specifying <literal>UNIQUE</literal>, because operator
+       exclusion constraints require one additional index search. The
+       advantage of operator exclusion constraints is the ability to
+       specify more general constraints (like a non-overlapping
+       constraint for circles), and also the ability to use index
+       methods other than <literal>btree</literal>, such
+       as <literal>GiST</literal> (see <xref linkend="GiST">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">index_parameters</replaceable>
+       are the same as for a <literal>UNIQUE</literal>
+       constraint. The <replaceable class="parameter">predicate</replaceable>
+       allows you to specify the constraint on a subset of the table
+       (note the reqiuired parentheses around the predicate
+       expression), internally using a partial index
+       (see <xref linkend="SQL-CREATEINDEX">). The <replaceable class="parameter">index_element</replaceable>
+       is normally just a column name, but can also be an expression or
+       function call and the constraint will check the result (similar
+       to creating a unique index over an expression); and it can
+       contain indexing information such as the operator class.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1111,1116 **** CREATE TABLE cinemas (
--- 1179,1196 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUSION USING gist (c CHECK WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1109 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraint = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraint != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUSION");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraint,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 803,808 **** index_create(Oid heapRelationId,
--- 811,874 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1082,1087 **** BuildIndexInfo(Relation index)
--- 1148,1157 ----
  	/* other info */
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 		ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ 	else
+ 		ii->ii_ExclusionConstraint = NULL;
  
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
***************
*** 1893,1898 **** IndexBuildHeapScan(Relation heapRelation,
--- 1963,1971 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't checked at index build time */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2267,2272 **** validate_index_heapscan(Relation heapRelation,
--- 2340,2351 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/*
+ 	 * Operator exclusion constraints aren't supported for concurrent
+ 	 * index builds.
+ 	 */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
  }
  
  
***************
*** 2522,2524 **** reindex_relation(Oid relid, bool toast_too)
--- 2601,2604 ----
  
  	return result;
  }
+ 
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *constrategiesArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ 		constrategiesArray = construct_array(strategyDatums,
+ 											 constraintNKeys,
+ 											 INT2OID,
+ 											 sizeof(int16), true, 's');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (constrategiesArray)
+ 		values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ 	else
+ 		nulls[Anum_pg_constraint_constrategies - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 548,555 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 564,583 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,252 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
--- 40,46 ----
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext = NULL;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL ||
! 		indexInfo->ii_ExclusionConstraint != NULL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 150,168 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraint == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_check_constraint(trigdata->tg_relation, indexRel,
! 							   slot, &(new_row->t_self), values, isnull,
! 							   indexInfo->ii_ExclusionConstraint,
! 							   indexInfo->ii_ExpressionsState,
! 							   econtext, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 423,428 **** DefineIndex(RangeVar *heapRelation,
--- 433,439 ----
  	indexInfo->ii_ReadyForInserts = !concurrent;
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
+ 	indexInfo->ii_ExclusionConstraint = exclusion_constraint;
  
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 446,469 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type = NULL;
+ 
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUSION";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 479,485 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 774,781 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 965,971 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUSION */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  	CommandId	mycid;
  	BulkInsertState bistate;
  	int			hi_options;
***************
*** 3103,3108 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3109,3117 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3111,3116 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3120,3153 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3145,3160 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
  
  	if (newrel || needscan)
  	{
! 		ExprContext *econtext;
! 		Datum	   *values;
! 		bool	   *isnull;
! 		TupleTableSlot *oldslot;
! 		TupleTableSlot *newslot;
! 		HeapScanDesc scan;
! 		HeapTuple	tuple;
! 		MemoryContext oldCxt;
! 		List	   *dropped_attrs = NIL;
! 		ListCell   *lc;
  
  		econtext = GetPerTupleExprContext(estate);
  
--- 3182,3199 ----
  
  	if (newrel || needscan)
  	{
! 		ExprContext		 *econtext;
! 		Datum			 *values;
! 		bool			 *isnull;
! 		TupleTableSlot	 *oldslot;
! 		TupleTableSlot	 *newslot;
! 		HeapScanDesc	  scan;
! 		HeapTuple		  tuple;
! 		MemoryContext	  oldCxt;
! 		List			 *dropped_attrs	 = NIL;
! 		ListCell		 *lc;
! 		Datum			 *idxvals		 = NULL;
! 		bool			 *idxnulls		 = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3173,3178 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3212,3226 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3198,3203 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3246,3252 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3268,3273 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3317,3323 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3276,3281 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3326,3351 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_check_constraint(oldrel, indexRelation, newslot,
+ 									   &tuple->t_self, idxvals, idxnulls,
+ 									   indexInfo->ii_ExclusionConstraint,
+ 									   indexInfo->ii_ExpressionsState,
+ 									   econtext, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				heap_insert(newrel, tuple, mycid, hi_options, bistate);
***************
*** 3290,3295 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3360,3378 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 4603,4608 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4686,4692 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4666,4671 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4750,4790 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 			else
+ 			{
+ 				char *choose_name2 = "";
+ 				IndexElem *ie = linitial(newConstraint->operator_exclusion);
+ 
+ 				if (ie->name != NULL)
+ 					choose_name2 = ie->name;
+ 
+ 				newConstraint->conname =
+ 					ChooseConstraintName(RelationGetRelationName(rel),
+ 										 choose_name2,
+ 										 "exclusion",
+ 										 RelationGetNamespace(rel),
+ 										 NIL);
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5035,5040 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5154,5160 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5071,5076 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5191,5354 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	int16		*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support gettuple",
+ 							   constraint->using_method)));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(int16) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = LookupOperName(NULL, opname, typoid, typoid, false, -1);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", quote_identifier(get_opname(opid))),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			elog(ERROR, "no strategy found for operator %d "
+ 				 "in operator family %d", opid, opfamily);
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							NULL,		/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,68 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 ExprContext *econtext, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1010,1016 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1017,1023 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1075,1081 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1082,1088 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1083,1089 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1090,1118 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraint != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_check_constraint(heapRelation, indexRelation,
! 									   slot, tupleid, values, isnull,
! 									   indexInfo->ii_ExclusionConstraint,
! 									   indexInfo->ii_ExpressionsState,
! 									   econtext, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraint != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1217,1219 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1246,1496 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ 					   ItemPointer tupleid, Datum *values, bool *isnull,
+ 					   int16 *exclusion_constraint, List *index_exprs,
+ 					   ExprContext *econtext, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	Oid					*constr_procs;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	for (i = 0; i < index_natts; i++)
+ 		if (isnull[i])
+ 			return true;
+ 
+ 	/*
+ 	 * Find the function that tests for a conflict based on the
+ 	 * strategy number, operator family, and types.
+ 	 */
+ 	constr_procs = palloc(sizeof(Oid) * index_natts);
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		/*
+ 		 * Find the procedure implementing the strategy for the
+ 		 * index for two arguments both with the type of the
+ 		 * indexed attribute.
+ 		 */
+ 		Oid				oper;
+ 		Oid				opfamily = index->rd_opfamily[i];
+ 		Oid				typoid = index->rd_opcintype[i];
+ 		StrategyNumber	strategy = exclusion_constraint[i];
+ 
+ 		if (strategy == InvalidStrategy)
+ 			continue;
+ 
+ 		oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+ 
+ 		if(OidIsValid(oper))
+ 			constr_procs[i] = get_opcode(oper);
+ 		else
+ 			elog(ERROR, "cannot determine operator for type %d and "
+ 				 "strategy %d", typoid, strategy);
+ 	}
+ 
+ 	/*
+ 	 * Now search the tuples that are actually in the index for
+ 	 * any violations.
+ 	 */
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			continue;
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 
+ 	/*
+ 	 * We have to find all tuples, even those not visible yet.
+ 	 */
+ 	InitDirtySnapshot(DirtySnapshot);
+ 
+ retry:
+ 	found_self = false;
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		TransactionId xwait;
+ 
+ 		if(ItemPointerEquals(tupleid, &tup->t_self))
+ 		{
+ 			Assert(!found_self);
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, econtext, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		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
+ 		 * restart the scan.
+ 		 */
+ 		xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ 			DirtySnapshot.xmin : DirtySnapshot.xmax;
+ 
+ 		if (TransactionIdIsValid(xwait))
+ 		{
+ 			index_endscan(index_scan);
+ 			XactLockTableWait(xwait);
+ 			goto retry;
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", tuple_as_string(new_slot),
+ 						   tuple_as_string(existing_slot))));
+ 	}
+ 
+ 	Assert(conflict || found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	pfree(constr_procs);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 ExprContext *econtext, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ 	TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+ 	StringInfoData		buf;
+ 	int					i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < tupdesc->natts; i++)
+ 	{
+ 		char	*strval;
+ 		Datum	 value;
+ 		bool	 isnull;
+ 
+ 		value = slot_getattr(slot, i + 1, &isnull);
+ 
+ 		if (isnull)
+ 			strval = "null";
+ 		else
+ 		{
+ 			Oid		foutoid;
+ 			bool	typisvarlena;
+ 
+ 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ 							  &typisvarlena);
+ 			strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, strval);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	return buf.data;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2159,2164 **** _copyConstraint(Constraint *from)
--- 2159,2167 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2107,2112 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2107,2115 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2396,2401 **** _outConstraint(StringInfo str, Constraint *node)
--- 2396,2409 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 355,360 **** static TypeName *TableFuncTypeName(List *columns);
--- 355,361 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 435,440 **** static TypeName *TableFuncTypeName(List *columns);
--- 436,442 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 478,484 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 480,486 ----
  	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 EXCLUSION EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
***************
*** 1593,1600 **** alter_table_cmds:
  		;
  
  alter_table_cmd:
! 			/* ALTER TABLE <name> ADD [COLUMN] <coldef> */
! 			ADD_P opt_column columnDef
  				{
  					AlterTableCmd *n = makeNode(AlterTableCmd);
  					n->subtype = AT_AddColumn;
--- 1595,1610 ----
  		;
  
  alter_table_cmd:
! 			/* 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;
***************
*** 2506,2511 **** ConstraintElem:
--- 2516,2536 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUSION access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2546,2551 **** key_match:  MATCH FULL
--- 2571,2593 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem CHECK WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $4);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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
***************
*** 10575,10580 **** unreserved_keyword:
--- 10617,10623 ----
  			| ENUM_P
  			| ESCAPE
  			| EXCLUDING
+ 			| EXCLUSION
  			| EXCLUSIVE
  			| EXECUTE
  			| EXPLAIN
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 71,76 **** typedef struct
--- 71,77 ----
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
  	List	   *ixconstraints;	/* index-creating constraints */
+ 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void preprocessOpExConstraints(ParseState *pstate,
+ 									  CreateStmtContext *cxt,
+ 									  RangeVar *relation,
+ 									  Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 175,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 182,188 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 730,735 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 778,789 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 868,873 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 922,937 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1838,1843 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1902,1908 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1885,1890 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1950,1958 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					preprocessOpExConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1943,1949 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2011,2020 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1958,1963 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2029,2041 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2250,2252 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2328,2381 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ preprocessOpExConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						  RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 148,153 **** static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
--- 148,155 ----
  					   int prettyFlags);
  static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  							int prettyFlags);
+ static char * pg_get_opxdef_worker(Oid indexrelid, uint16 *strategies,
+ 								   int prettyFlags);
  static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
  				   int prettyFlags);
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
***************
*** 1193,1198 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1195,1231 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				bool	 isnull;
+ 				Oid		 indexOid = conForm->conindid;
+ 				Datum	 val;
+ 				Datum	*keys;
+ 				int		 nKeys;
+ 				int		 i;
+ 				uint16	*strategies;
+ 
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_constrategies,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null constrategies for constraint %u",
+ 						 constraintId);
+ 
+ 				deconstruct_array(DatumGetArrayTypeP(val),
+ 								  INT2OID, 2, true, 's',
+ 								  &keys, NULL, &nKeys);
+ 
+ 				strategies = palloc(nKeys * sizeof(uint16));
+ 				for(i = 0; i < nKeys; i++)
+ 					strategies[i] = DatumGetInt16(keys[i]);
+ 
+ 				appendStringInfo(&buf, pg_get_opxdef_worker(indexOid,
+ 															strategies,
+ 															prettyFlags));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1240,1245 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1273,1510 ----
  	}
  }
  
+ static char *
+ pg_get_opxdef_worker(Oid indexrelid, uint16_t *strategies, int prettyFlags)
+ {
+ 	HeapTuple	ht_idx;
+ 	HeapTuple	ht_idxrel;
+ 	HeapTuple	ht_am;
+ 	Form_pg_index idxrec;
+ 	Form_pg_class idxrelrec;
+ 	Form_pg_am	amrec;
+ 	List	   *indexprs;
+ 	ListCell   *indexpr_item;
+ 	List	   *context;
+ 	Oid			indrelid;
+ 	int			keyno;
+ 	Oid			keycoltype;
+ 	Datum		indclassDatum;
+ 	Datum		indoptionDatum;
+ 	bool		isnull;
+ 	oidvector  *indclass;
+ 	int2vector *indoption;
+ 	StringInfoData buf;
+ 	char	   *str;
+ 	char	   *sep;
+ 	Oid			tblspc;
+ 
+ 	/*
+ 	 * Fetch the pg_index tuple by the Oid of the index
+ 	 */
+ 	ht_idx = SearchSysCache(INDEXRELID,
+ 							ObjectIdGetDatum(indexrelid),
+ 							0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idx))
+ 		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ 	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+ 
+ 	indrelid = idxrec->indrelid;
+ 	Assert(indexrelid == idxrec->indexrelid);
+ 
+ 	/* Must get indclass and indoption the hard way */
+ 	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indclass, &isnull);
+ 	Assert(!isnull);
+ 	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+ 	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indoption, &isnull);
+ 	Assert(!isnull);
+ 	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ 
+ 	/*
+ 	 * Fetch the pg_class tuple of the index relation
+ 	 */
+ 	ht_idxrel = SearchSysCache(RELOID,
+ 							   ObjectIdGetDatum(indexrelid),
+ 							   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idxrel))
+ 		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+ 	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+ 
+ 	/*
+ 	 * Fetch the pg_am tuple of the index' access method
+ 	 */
+ 	ht_am = SearchSysCache(AMOID,
+ 						   ObjectIdGetDatum(idxrelrec->relam),
+ 						   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_am))
+ 		elog(ERROR, "cache lookup failed for access method %u",
+ 			 idxrelrec->relam);
+ 	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+ 
+ 	/*
+ 	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+ 	 * versions of the expressions and predicate, because we want to display
+ 	 * non-const-folded expressions.)
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+ 	{
+ 		Datum		exprsDatum;
+ 		bool		isnull;
+ 		char	   *exprsString;
+ 
+ 		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indexprs, &isnull);
+ 		Assert(!isnull);
+ 		exprsString = TextDatumGetCString(exprsDatum);
+ 		indexprs = (List *) stringToNode(exprsString);
+ 		pfree(exprsString);
+ 	}
+ 	else
+ 		indexprs = NIL;
+ 
+ 	indexpr_item = list_head(indexprs);
+ 
+ 	context = deparse_context_for(get_rel_name(indrelid), indrelid);
+ 
+ 	/*
+ 	 * Start the index definition.	Note that the index's name should never be
+ 	 * schema-qualified, but the indexed rel's name may be.
+ 	 */
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "EXCLUSION USING %s (",
+ 					 quote_identifier(NameStr(amrec->amname)));
+ 
+ 	/*
+ 	 * Report the indexed attributes
+ 	 */
+ 	sep = "";
+ 	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ 	{
+ 		AttrNumber	attnum = idxrec->indkey.values[keyno];
+ 		int16		opt = indoption->values[keyno];
+ 		Oid			opfamily = get_opclass_family(indclass->values[keyno]);
+ 		Oid			opid;
+ 		char	   *opName;
+ 
+ 		appendStringInfoString(&buf, sep);
+ 		sep = ", ";
+ 
+ 		if (attnum != 0)
+ 		{
+ 			/* Simple index column */
+ 			char	   *attname;
+ 
+ 			attname = get_relid_attribute_name(indrelid, attnum);
+ 			appendStringInfoString(&buf, quote_identifier(attname));
+ 			keycoltype = get_atttype(indrelid, attnum);
+ 		}
+ 		else
+ 		{
+ 			/* expressional index */
+ 			Node	   *indexkey;
+ 
+ 			if (indexpr_item == NULL)
+ 				elog(ERROR, "too few entries in indexprs list");
+ 			indexkey = (Node *) lfirst(indexpr_item);
+ 			indexpr_item = lnext(indexpr_item);
+ 			/* Deparse */
+ 			str = deparse_expression_pretty(indexkey, context, false, false,
+ 											prettyFlags, 0);
+ 
+ 			/* Need parens if it's not a bare function call */
+ 			if (indexkey && IsA(indexkey, FuncExpr) &&
+ 				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+ 				appendStringInfoString(&buf, str);
+ 			else
+ 				appendStringInfo(&buf, "(%s)", str);
+ 
+ 			keycoltype = exprType(indexkey);
+ 		}
+ 
+ 		/* Add the operator class name, if not default */
+ 		get_opclass_name(indclass->values[keyno], keycoltype, &buf);
+ 
+ 		/* Add options if relevant */
+ 		if (amrec->amcanorder)
+ 		{
+ 			/* if it supports sort ordering, report DESC and NULLS opts */
+ 			if (opt & INDOPTION_DESC)
+ 			{
+ 				appendStringInfo(&buf, " DESC");
+ 				/* NULLS FIRST is the default in this case */
+ 				if (!(opt & INDOPTION_NULLS_FIRST))
+ 					appendStringInfo(&buf, " NULLS LAST");
+ 			}
+ 			else
+ 			{
+ 				if (opt & INDOPTION_NULLS_FIRST)
+ 					appendStringInfo(&buf, " NULLS FIRST");
+ 			}
+ 		}
+ 
+ 		/* Add operator exclusion constraint */
+ 		appendStringInfo(&buf, " CHECK WITH ");
+ 
+ 		opid = get_opfamily_member(opfamily, keycoltype, keycoltype,
+ 								   strategies[keyno]);
+ 		opName = generate_operator_name(opid, keycoltype, keycoltype);
+ 
+ 		appendStringInfo(&buf, "%s", opName);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	/*
+ 	 * If it has options, append "WITH (options)"
+ 	 */
+ 	str = flatten_reloptions(indexrelid);
+ 	if (str)
+ 	{
+ 		appendStringInfo(&buf, " WITH (%s)", str);
+ 		pfree(str);
+ 	}
+ 
+ 	/*
+ 	 * If it's in a nondefault tablespace, say so, but only if requested
+ 	 */
+ 	tblspc = get_rel_tablespace(indexrelid);
+ 	if (OidIsValid(tblspc))
+ 		appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
+ 						 quote_identifier(get_tablespace_name(tblspc)));
+ 
+ 	/*
+ 	 * If it's a partial index, decompile and append the predicate
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+ 	{
+ 		Node	   *node;
+ 		Datum		predDatum;
+ 		bool		isnull;
+ 		char	   *predString;
+ 
+ 		/* Convert text string to node tree */
+ 		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indpred, &isnull);
+ 		Assert(!isnull);
+ 		predString = TextDatumGetCString(predDatum);
+ 		node = (Node *) stringToNode(predString);
+ 		pfree(predString);
+ 
+ 		/* Deparse */
+ 		str = deparse_expression_pretty(node, context, false, false,
+ 										prettyFlags, 0);
+ 		appendStringInfo(&buf, " WHERE (%s)", str);
+ 	}
+ 
+ 	/* Clean up */
+ 	ReleaseSysCache(ht_idx);
+ 	ReleaseSysCache(ht_idxrel);
+ 	ReleaseSysCache(ht_am);
+ 
+ 	return buf.data;
+ }
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3039,3119 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ 	Relation	conrel;
+ 	SysScanDesc conscan;
+ 	ScanKeyData skey[1];
+ 	HeapTuple	htup;
+ 	Datum		val;
+ 	bool		isnull;
+ 	bool		found = false;
+ 	int16	   *constraints = NULL;
+ 	Oid			relid = indexRelation->rd_index->indrelid;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_constrategies,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null constrategies for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != INT2OID)
+ 			elog(ERROR, "constrategies is not a 1-D smallint array");
+ 		constraints = palloc(sizeof(int16) * nelem);
+ 		memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	return constraints;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2855,2860 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 2855,2861 ----
  				 strcmp(te->desc, "CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "DEFAULT") == 0 ||
  				 strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ 				 strcmp(te->desc, "EXCLUSION CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "INDEX") == 0 ||
  				 strcmp(te->desc, "RULE") == 0 ||
  				 strcmp(te->desc, "TRIGGER") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 3680,3685 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3680,3686 ----
  				i_condeferred,
  				i_contableoid,
  				i_conoid,
+ 				i_condef,
  				i_tablespace,
  				i_options;
  	int			ntups;
***************
*** 3710,3716 **** getIndexes(TableInfo tblinfo[], int numTables)
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80200)
  		{
  			appendPQExpBuffer(query,
  							  "SELECT t.tableoid, t.oid, "
--- 3711,3745 ----
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			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, "
! 					 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
! 							  "c.oid AS conoid, "
! 							  "(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, "
***************
*** 3858,3863 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3887,3893 ----
  		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,3901 **** 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 we found a constraint matching the index, create an
--- 3925,3931 ----
  			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
  			contype = *(PQgetvalue(res, j, i_contype));
  
! 			if (contype == 'p' || contype == 'u' || contype == 'x')
  			{
  				/*
  				 * If we found a constraint matching the index, create an
***************
*** 3913,3919 **** getIndexes(TableInfo tblinfo[], int numTables)
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				constrinfo[j].condef = NULL;
  				constrinfo[j].confrelid = InvalidOid;
  				constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
  				constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
--- 3943,3952 ----
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				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';
***************
*** 10907,10912 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
--- 10940,10970 ----
  						 NULL, NULL);
  		}
  	}
+ 	else if (coninfo->contype == 'x')
+ 	{
+ 		appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(q, "    ADD CONSTRAINT %s %s;\n",
+ 						  fmtId(coninfo->dobj.name),
+ 						  coninfo->condef);
+ 
+ 		appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ 						  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 		appendPQExpBuffer(delq, "%s ",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ 						  fmtId(coninfo->dobj.name));
+ 
+ 		ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ 					 coninfo->dobj.name,
+ 					 tbinfo->dobj.namespace->dobj.name,
+ 					 NULL,
+ 					 tbinfo->rolname, false,
+ 					 "EXCLUSION CONSTRAINT", SECTION_POST_DATA,
+ 					 q->data, delq->data, NULL,
+ 					 coninfo->dobj.dependencies, coninfo->dobj.nDeps,
+ 					 NULL, NULL);
+ 	}
  	else
  	{
  		write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	int2		constrategies[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_constrategies	20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,338 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_check_constraint(Relation heap, Relation index,
+ 								   TupleTableSlot *new_slot,
+ 								   ItemPointer tupleid, Datum *values,
+ 								   bool *isnull, int16 *exclusion_constraint,
+ 								   List *index_exprs, ExprContext *econtext,
+ 								   bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	int16	   *ii_ExclusionConstraint;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1395,1400 **** typedef enum ConstrType			/* types of constraints */
--- 1395,1401 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1429,1439 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1430,1445 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 144,149 **** PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
--- 144,150 ----
  PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
  PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
  PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
+ PG_KEYWORD("exclusion", EXCLUSION, UNRESERVED_KEYWORD)
  PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD)
  PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD)
  PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD)
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 43,49 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUSION USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK 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>');
+ -- should fail
+ 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 EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ 
+ DROP TABLE circles;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUSION USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD EXCLUSION 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Tuple "(<(20,20),10>, <(0,0), 5>)" conflicts with existing tuple "(<(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 EXCLUSION USING gist
+   (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ NOTICE:  ALTER TABLE / ADD EXCLUSION will create implicit index "circles_c1_exclusion1" for table "circles"
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL:  Tuple "(<(0,0),5>, <(0,0), 5>)" conflicts with existing tuple "(<(0,0),5>, <(0,0), 5>)".
+ DROP TABLE circles;
operator-exclusion-constraints-20091107.patch.gzapplication/x-gzip; name=operator-exclusion-constraints-20091107.patch.gzDownload
#182Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#178)
Re: operator exclusion constraints

On Fri, 2009-11-06 at 21:23 -0500, Tom Lane wrote:

Or maybe forget about it and go to EXCLUDE or EXCLUDING?

I left it as EXCLUSION for now. "EXCLUDING USING ..." and "EXCLUSIVE
USING ..." both sound a little awkward to me. Either could be improved
by moving the USING clause around, but that just creates more grammar
headaches.

EXCLUDE probably flows most nicely with the optional USING clause or
without. My only complaint was that it's a transitive verb, so it seems
to impart more meaning than it actually can. I doubt anyone would
actually be more confused in practice, though. If a couple of people
agree, I'll change it to EXCLUDE.

Regards,
Jeff Davis

#183Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#182)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

EXCLUDE probably flows most nicely with the optional USING clause or
without. My only complaint was that it's a transitive verb, so it seems
to impart more meaning than it actually can. I doubt anyone would
actually be more confused in practice, though. If a couple of people
agree, I'll change it to EXCLUDE.

EXCLUDE sounds good to me.

regards, tom lane

#184Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#182)
Re: operator exclusion constraints

On Sat, Nov 7, 2009 at 1:56 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Fri, 2009-11-06 at 21:23 -0500, Tom Lane wrote:

Or maybe forget about it and go to EXCLUDE or EXCLUDING?

I left it as EXCLUSION for now. "EXCLUDING USING ..." and "EXCLUSIVE
USING ..." both sound a little awkward to me. Either could be improved
by moving the USING clause around, but that just creates more grammar
headaches.

EXCLUDE probably flows most nicely with the optional USING clause or
without. My only complaint was that it's a transitive verb, so it seems
to impart more meaning than it actually can. I doubt anyone would
actually be more confused in practice, though. If a couple of people
agree, I'll change it to EXCLUDE.

Personally, I think that this is all rather a matter of opinion, and
of course bikeshedding. CHECK is a verb, which might suggest that
EXCLUDE is the best choice, and it has a nice declarative sound to it.
But the other example is FOREIGN KEY, which is not a verb at all,
which seems to me to more closely parallel EXCLUSION or perhaps
EXCLUDING. I think I like EXCLUSIVE the least of the four, but at the
end of the day, I don't think we can really go far wrong.

I also don't think there's anything wrong with EXCLUDING USING, nor
anything more wrong EXCLUSIVE USING than there is with EXCLUSIVE
alone. Nor do I think there's any problem with EXCLUDE being
transitive because, of course, we're going to follow it with a
description of what we want to exclude, which may be thought of as its
direct object. Once again, I don't think we can go far wrong.

Honestly, I'd probably be in favor of breaking the virtual tie in
favor of whichever word is already a keyword, rather than trying to
decide on (IMHO extremely tenuous) grammatical grounds. But I can't
get worked up about that one way or the other either.

...Robert

#185David E. Wheeler
david@kineticode.com
In reply to: Tom Lane (#183)
Re: operator exclusion constraints

On Nov 7, 2009, at 11:08 AM, Tom Lane wrote:

EXCLUDE probably flows most nicely with the optional USING clause or
without. My only complaint was that it's a transitive verb, so it
seems
to impart more meaning than it actually can. I doubt anyone would
actually be more confused in practice, though. If a couple of people
agree, I'll change it to EXCLUDE.

EXCLUDE sounds good to me.

+1

David

#186Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#184)
Re: operator exclusion constraints

On Sat, 2009-11-07 at 14:11 -0500, Robert Haas wrote:

Honestly, I'd probably be in favor of breaking the virtual tie in
favor of whichever word is already a keyword

The ones that are already keywords are EXCLUSIVE and EXCLUDING, which
are also the least desirable, so that rule doesn't work as a
tie-breaker.

I think that EXCLUSION and EXCLUDE are the options still in the running
here.

Regards,
Jeff Davis

#187Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#186)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Sat, 2009-11-07 at 14:11 -0500, Robert Haas wrote:

Honestly, I'd probably be in favor of breaking the virtual tie in
favor of whichever word is already a keyword

The ones that are already keywords are EXCLUSIVE and EXCLUDING, which
are also the least desirable, so that rule doesn't work as a
tie-breaker.

I think it doesn't really matter now that we've succeeded in making the
keyword unreserved.

regards, tom lane

#188Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#182)
2 attachment(s)
Re: operator exclusion constraints

On Sat, 2009-11-07 at 10:56 -0800, Jeff Davis wrote:

EXCLUDE probably flows most nicely with the optional USING clause or
without. My only complaint was that it's a transitive verb, so it seems
to impart more meaning than it actually can. I doubt anyone would
actually be more confused in practice, though. If a couple of people
agree, I'll change it to EXCLUDE.

It looks like EXCLUDE is the winner. Updated patch attached.

The feature is still called "operator exclusion constraints", and the
docs still make reference to that name, but the syntax specification has
been updated.

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091108.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091108.context.patchDownload
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,63 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
  </synopsis>
  
   </refsynopsisdiv>
--- 51,69 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">index_element</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
+ 
+ <phrase>and <replaceable class="PARAMETER">index_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+ 
+ { column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+ 
  </synopsis>
  
   </refsynopsisdiv>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 553,620 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">index_element</replaceable> CHECK WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
+     <listitem>
+      <para>
+       The <literal>EXCLUDE</> clause specifies an operator exclusion
+       constraint. An operator exclusion constraint is more general
+       than a <literal>UNIQUE</literal> constraint, and can use an
+       arbitrary operator to detect conflicts. For instance, you can
+       specify the constraint that no two tuples in the table contain
+       overlapping circles (see <xref linkend="datatype-geometric">) by
+       using the <literal>&&</literal> operator.
+      </para>
+ 
+      <para>
+       The constraint specifies the conflict condition, so the operator
+       should return <literal>TRUE</literal> when applied to two
+       conflicting values. Also, the operator specified must be
+       commutative (that is, the commutator of the operator must be the
+       operator itself), must be a boolean operator, and must be
+       associated with an operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) using
+       <replaceable class="parameter">index_method</replaceable>. The
+       constraint is violated if, and only if, there exist two tuples
+       where all corresponding expressions between the tuples conflict
+       according
+       to <replaceable class="parameter">operator</replaceable>
+       (i.e. the operator returns <literal>TRUE</literal>).
+      </para>
+ 
+      <para>
+       Internally, operator exclusion constraints use an index to
+       perform a search looking for conflicting values, and handle
+       concurrent operations similar to a <literal>UNIQUE</literal>
+       constraint. If all of the operators are specified as the
+       equality operator (usually <literal>=</literal>), this
+       constraint behaves identically to a <literal>UNIQUE</literal>
+       constraint. However, it may exhibit slightly worse performance
+       than specifying <literal>UNIQUE</literal>, because operator
+       exclusion constraints require one additional index search. The
+       advantage of operator exclusion constraints is the ability to
+       specify more general constraints (like a non-overlapping
+       constraint for circles), and also the ability to use index
+       methods other than <literal>btree</literal>, such
+       as <literal>GiST</literal> (see <xref linkend="GiST">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">index_parameters</replaceable>
+       are the same as for a <literal>UNIQUE</literal>
+       constraint. The <replaceable class="parameter">predicate</replaceable>
+       allows you to specify the constraint on a subset of the table
+       (note the reqiuired parentheses around the predicate
+       expression), internally using a partial index
+       (see <xref linkend="SQL-CREATEINDEX">). The <replaceable class="parameter">index_element</replaceable>
+       is normally just a column name, but can also be an expression or
+       function call and the constraint will check the result (similar
+       to creating a unique index over an expression); and it can
+       contain indexing information such as the operator class.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1111,1116 **** CREATE TABLE cinemas (
--- 1179,1196 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUDE USING gist (c CHECK WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1109 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraint = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraint != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraint,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 803,808 **** index_create(Oid heapRelationId,
--- 811,874 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1082,1087 **** BuildIndexInfo(Relation index)
--- 1148,1157 ----
  	/* other info */
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 		ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ 	else
+ 		ii->ii_ExclusionConstraint = NULL;
  
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
***************
*** 1893,1898 **** IndexBuildHeapScan(Relation heapRelation,
--- 1963,1971 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't checked at index build time */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2267,2272 **** validate_index_heapscan(Relation heapRelation,
--- 2340,2351 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/*
+ 	 * Operator exclusion constraints aren't supported for concurrent
+ 	 * index builds.
+ 	 */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
  }
  
  
***************
*** 2522,2524 **** reindex_relation(Oid relid, bool toast_too)
--- 2601,2604 ----
  
  	return result;
  }
+ 
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *constrategiesArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ 		constrategiesArray = construct_array(strategyDatums,
+ 											 constraintNKeys,
+ 											 INT2OID,
+ 											 sizeof(int16), true, 's');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (constrategiesArray)
+ 		values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ 	else
+ 		nulls[Anum_pg_constraint_constrategies - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 548,555 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 564,583 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,252 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
--- 40,46 ----
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext = NULL;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL ||
! 		indexInfo->ii_ExclusionConstraint != NULL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 150,168 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraint == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_check_constraint(trigdata->tg_relation, indexRel,
! 							   slot, &(new_row->t_self), values, isnull,
! 							   indexInfo->ii_ExclusionConstraint,
! 							   indexInfo->ii_ExpressionsState,
! 							   econtext, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 423,428 **** DefineIndex(RangeVar *heapRelation,
--- 433,439 ----
  	indexInfo->ii_ReadyForInserts = !concurrent;
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
+ 	indexInfo->ii_ExclusionConstraint = exclusion_constraint;
  
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 446,469 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type = NULL;
+ 
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUDE";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 479,485 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 774,781 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 965,971 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUDE */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  	CommandId	mycid;
  	BulkInsertState bistate;
  	int			hi_options;
***************
*** 3103,3108 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3109,3117 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3111,3116 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3120,3153 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3145,3160 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
  
  	if (newrel || needscan)
  	{
! 		ExprContext *econtext;
! 		Datum	   *values;
! 		bool	   *isnull;
! 		TupleTableSlot *oldslot;
! 		TupleTableSlot *newslot;
! 		HeapScanDesc scan;
! 		HeapTuple	tuple;
! 		MemoryContext oldCxt;
! 		List	   *dropped_attrs = NIL;
! 		ListCell   *lc;
  
  		econtext = GetPerTupleExprContext(estate);
  
--- 3182,3199 ----
  
  	if (newrel || needscan)
  	{
! 		ExprContext		 *econtext;
! 		Datum			 *values;
! 		bool			 *isnull;
! 		TupleTableSlot	 *oldslot;
! 		TupleTableSlot	 *newslot;
! 		HeapScanDesc	  scan;
! 		HeapTuple		  tuple;
! 		MemoryContext	  oldCxt;
! 		List			 *dropped_attrs	 = NIL;
! 		ListCell		 *lc;
! 		Datum			 *idxvals		 = NULL;
! 		bool			 *idxnulls		 = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3173,3178 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3212,3226 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3198,3203 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3246,3252 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3268,3273 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3317,3323 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3276,3281 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3326,3351 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_check_constraint(oldrel, indexRelation, newslot,
+ 									   &tuple->t_self, idxvals, idxnulls,
+ 									   indexInfo->ii_ExclusionConstraint,
+ 									   indexInfo->ii_ExpressionsState,
+ 									   econtext, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				heap_insert(newrel, tuple, mycid, hi_options, bistate);
***************
*** 3290,3295 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3360,3378 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 4603,4608 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4686,4692 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4666,4671 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4750,4790 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 			else
+ 			{
+ 				char *choose_name2 = "";
+ 				IndexElem *ie = linitial(newConstraint->operator_exclusion);
+ 
+ 				if (ie->name != NULL)
+ 					choose_name2 = ie->name;
+ 
+ 				newConstraint->conname =
+ 					ChooseConstraintName(RelationGetRelationName(rel),
+ 										 choose_name2,
+ 										 "exclusion",
+ 										 RelationGetNamespace(rel),
+ 										 NIL);
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5035,5040 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5154,5160 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5071,5076 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5191,5354 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	int16		*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support gettuple",
+ 							   constraint->using_method)));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(int16) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = LookupOperName(NULL, opname, typoid, typoid, false, -1);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", quote_identifier(get_opname(opid))),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			elog(ERROR, "no strategy found for operator %d "
+ 				 "in operator family %d", opid, opfamily);
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							NULL,		/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,68 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 ExprContext *econtext, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1010,1016 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1017,1023 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1075,1081 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1082,1088 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1083,1089 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1090,1118 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraint != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_check_constraint(heapRelation, indexRelation,
! 									   slot, tupleid, values, isnull,
! 									   indexInfo->ii_ExclusionConstraint,
! 									   indexInfo->ii_ExpressionsState,
! 									   econtext, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraint != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1217,1219 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1246,1496 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ 					   ItemPointer tupleid, Datum *values, bool *isnull,
+ 					   int16 *exclusion_constraint, List *index_exprs,
+ 					   ExprContext *econtext, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	Oid					*constr_procs;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	for (i = 0; i < index_natts; i++)
+ 		if (isnull[i])
+ 			return true;
+ 
+ 	/*
+ 	 * Find the function that tests for a conflict based on the
+ 	 * strategy number, operator family, and types.
+ 	 */
+ 	constr_procs = palloc(sizeof(Oid) * index_natts);
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		/*
+ 		 * Find the procedure implementing the strategy for the
+ 		 * index for two arguments both with the type of the
+ 		 * indexed attribute.
+ 		 */
+ 		Oid				oper;
+ 		Oid				opfamily = index->rd_opfamily[i];
+ 		Oid				typoid = index->rd_opcintype[i];
+ 		StrategyNumber	strategy = exclusion_constraint[i];
+ 
+ 		if (strategy == InvalidStrategy)
+ 			continue;
+ 
+ 		oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+ 
+ 		if(OidIsValid(oper))
+ 			constr_procs[i] = get_opcode(oper);
+ 		else
+ 			elog(ERROR, "cannot determine operator for type %d and "
+ 				 "strategy %d", typoid, strategy);
+ 	}
+ 
+ 	/*
+ 	 * Now search the tuples that are actually in the index for
+ 	 * any violations.
+ 	 */
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			continue;
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 
+ 	/*
+ 	 * We have to find all tuples, even those not visible yet.
+ 	 */
+ 	InitDirtySnapshot(DirtySnapshot);
+ 
+ retry:
+ 	found_self = false;
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		TransactionId xwait;
+ 
+ 		if(ItemPointerEquals(tupleid, &tup->t_self))
+ 		{
+ 			Assert(!found_self);
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, econtext, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		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
+ 		 * restart the scan.
+ 		 */
+ 		xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ 			DirtySnapshot.xmin : DirtySnapshot.xmax;
+ 
+ 		if (TransactionIdIsValid(xwait))
+ 		{
+ 			index_endscan(index_scan);
+ 			XactLockTableWait(xwait);
+ 			goto retry;
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", tuple_as_string(new_slot),
+ 						   tuple_as_string(existing_slot))));
+ 	}
+ 
+ 	Assert(conflict || found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	pfree(constr_procs);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 ExprContext *econtext, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ 	TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+ 	StringInfoData		buf;
+ 	int					i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < tupdesc->natts; i++)
+ 	{
+ 		char	*strval;
+ 		Datum	 value;
+ 		bool	 isnull;
+ 
+ 		value = slot_getattr(slot, i + 1, &isnull);
+ 
+ 		if (isnull)
+ 			strval = "null";
+ 		else
+ 		{
+ 			Oid		foutoid;
+ 			bool	typisvarlena;
+ 
+ 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ 							  &typisvarlena);
+ 			strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, strval);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	return buf.data;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2159,2164 **** _copyConstraint(Constraint *from)
--- 2159,2167 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2107,2112 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2107,2115 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2396,2401 **** _outConstraint(StringInfo str, Constraint *node)
--- 2396,2409 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 355,360 **** static TypeName *TableFuncTypeName(List *columns);
--- 355,361 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 435,440 **** static TypeName *TableFuncTypeName(List *columns);
--- 436,442 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 478,484 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 480,486 ----
  	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
! 	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
***************
*** 1593,1600 **** alter_table_cmds:
  		;
  
  alter_table_cmd:
! 			/* ALTER TABLE <name> ADD [COLUMN] <coldef> */
! 			ADD_P opt_column columnDef
  				{
  					AlterTableCmd *n = makeNode(AlterTableCmd);
  					n->subtype = AT_AddColumn;
--- 1595,1610 ----
  		;
  
  alter_table_cmd:
! 			/* 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;
***************
*** 2506,2511 **** ConstraintElem:
--- 2516,2536 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2546,2551 **** key_match:  MATCH FULL
--- 2571,2593 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem CHECK WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $4);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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
***************
*** 10574,10579 **** unreserved_keyword:
--- 10616,10622 ----
  			| ENCRYPTED
  			| ENUM_P
  			| ESCAPE
+ 			| EXCLUDE
  			| EXCLUDING
  			| EXCLUSIVE
  			| EXECUTE
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 71,76 **** typedef struct
--- 71,77 ----
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
  	List	   *ixconstraints;	/* index-creating constraints */
+ 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void preprocessOpExConstraints(ParseState *pstate,
+ 									  CreateStmtContext *cxt,
+ 									  RangeVar *relation,
+ 									  Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 175,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 182,188 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 730,735 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 778,789 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 868,873 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 922,937 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1838,1843 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1902,1908 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1885,1890 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1950,1958 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					preprocessOpExConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1943,1949 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2011,2020 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1958,1963 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2029,2041 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2250,2252 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2328,2381 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ preprocessOpExConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						  RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 148,153 **** static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
--- 148,155 ----
  					   int prettyFlags);
  static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  							int prettyFlags);
+ static char * pg_get_opxdef_worker(Oid indexrelid, uint16 *strategies,
+ 								   int prettyFlags);
  static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
  				   int prettyFlags);
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
***************
*** 1193,1198 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1195,1231 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				bool	 isnull;
+ 				Oid		 indexOid = conForm->conindid;
+ 				Datum	 val;
+ 				Datum	*keys;
+ 				int		 nKeys;
+ 				int		 i;
+ 				uint16	*strategies;
+ 
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_constrategies,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null constrategies for constraint %u",
+ 						 constraintId);
+ 
+ 				deconstruct_array(DatumGetArrayTypeP(val),
+ 								  INT2OID, 2, true, 's',
+ 								  &keys, NULL, &nKeys);
+ 
+ 				strategies = palloc(nKeys * sizeof(uint16));
+ 				for(i = 0; i < nKeys; i++)
+ 					strategies[i] = DatumGetInt16(keys[i]);
+ 
+ 				appendStringInfo(&buf, pg_get_opxdef_worker(indexOid,
+ 															strategies,
+ 															prettyFlags));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1240,1245 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1273,1510 ----
  	}
  }
  
+ static char *
+ pg_get_opxdef_worker(Oid indexrelid, uint16_t *strategies, int prettyFlags)
+ {
+ 	HeapTuple	ht_idx;
+ 	HeapTuple	ht_idxrel;
+ 	HeapTuple	ht_am;
+ 	Form_pg_index idxrec;
+ 	Form_pg_class idxrelrec;
+ 	Form_pg_am	amrec;
+ 	List	   *indexprs;
+ 	ListCell   *indexpr_item;
+ 	List	   *context;
+ 	Oid			indrelid;
+ 	int			keyno;
+ 	Oid			keycoltype;
+ 	Datum		indclassDatum;
+ 	Datum		indoptionDatum;
+ 	bool		isnull;
+ 	oidvector  *indclass;
+ 	int2vector *indoption;
+ 	StringInfoData buf;
+ 	char	   *str;
+ 	char	   *sep;
+ 	Oid			tblspc;
+ 
+ 	/*
+ 	 * Fetch the pg_index tuple by the Oid of the index
+ 	 */
+ 	ht_idx = SearchSysCache(INDEXRELID,
+ 							ObjectIdGetDatum(indexrelid),
+ 							0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idx))
+ 		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ 	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+ 
+ 	indrelid = idxrec->indrelid;
+ 	Assert(indexrelid == idxrec->indexrelid);
+ 
+ 	/* Must get indclass and indoption the hard way */
+ 	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indclass, &isnull);
+ 	Assert(!isnull);
+ 	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+ 	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indoption, &isnull);
+ 	Assert(!isnull);
+ 	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ 
+ 	/*
+ 	 * Fetch the pg_class tuple of the index relation
+ 	 */
+ 	ht_idxrel = SearchSysCache(RELOID,
+ 							   ObjectIdGetDatum(indexrelid),
+ 							   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idxrel))
+ 		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+ 	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+ 
+ 	/*
+ 	 * Fetch the pg_am tuple of the index' access method
+ 	 */
+ 	ht_am = SearchSysCache(AMOID,
+ 						   ObjectIdGetDatum(idxrelrec->relam),
+ 						   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_am))
+ 		elog(ERROR, "cache lookup failed for access method %u",
+ 			 idxrelrec->relam);
+ 	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+ 
+ 	/*
+ 	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+ 	 * versions of the expressions and predicate, because we want to display
+ 	 * non-const-folded expressions.)
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+ 	{
+ 		Datum		exprsDatum;
+ 		bool		isnull;
+ 		char	   *exprsString;
+ 
+ 		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indexprs, &isnull);
+ 		Assert(!isnull);
+ 		exprsString = TextDatumGetCString(exprsDatum);
+ 		indexprs = (List *) stringToNode(exprsString);
+ 		pfree(exprsString);
+ 	}
+ 	else
+ 		indexprs = NIL;
+ 
+ 	indexpr_item = list_head(indexprs);
+ 
+ 	context = deparse_context_for(get_rel_name(indrelid), indrelid);
+ 
+ 	/*
+ 	 * Start the index definition.	Note that the index's name should never be
+ 	 * schema-qualified, but the indexed rel's name may be.
+ 	 */
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "EXCLUDE USING %s (",
+ 					 quote_identifier(NameStr(amrec->amname)));
+ 
+ 	/*
+ 	 * Report the indexed attributes
+ 	 */
+ 	sep = "";
+ 	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ 	{
+ 		AttrNumber	attnum = idxrec->indkey.values[keyno];
+ 		int16		opt = indoption->values[keyno];
+ 		Oid			opfamily = get_opclass_family(indclass->values[keyno]);
+ 		Oid			opid;
+ 		char	   *opName;
+ 
+ 		appendStringInfoString(&buf, sep);
+ 		sep = ", ";
+ 
+ 		if (attnum != 0)
+ 		{
+ 			/* Simple index column */
+ 			char	   *attname;
+ 
+ 			attname = get_relid_attribute_name(indrelid, attnum);
+ 			appendStringInfoString(&buf, quote_identifier(attname));
+ 			keycoltype = get_atttype(indrelid, attnum);
+ 		}
+ 		else
+ 		{
+ 			/* expressional index */
+ 			Node	   *indexkey;
+ 
+ 			if (indexpr_item == NULL)
+ 				elog(ERROR, "too few entries in indexprs list");
+ 			indexkey = (Node *) lfirst(indexpr_item);
+ 			indexpr_item = lnext(indexpr_item);
+ 			/* Deparse */
+ 			str = deparse_expression_pretty(indexkey, context, false, false,
+ 											prettyFlags, 0);
+ 
+ 			/* Need parens if it's not a bare function call */
+ 			if (indexkey && IsA(indexkey, FuncExpr) &&
+ 				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+ 				appendStringInfoString(&buf, str);
+ 			else
+ 				appendStringInfo(&buf, "(%s)", str);
+ 
+ 			keycoltype = exprType(indexkey);
+ 		}
+ 
+ 		/* Add the operator class name, if not default */
+ 		get_opclass_name(indclass->values[keyno], keycoltype, &buf);
+ 
+ 		/* Add options if relevant */
+ 		if (amrec->amcanorder)
+ 		{
+ 			/* if it supports sort ordering, report DESC and NULLS opts */
+ 			if (opt & INDOPTION_DESC)
+ 			{
+ 				appendStringInfo(&buf, " DESC");
+ 				/* NULLS FIRST is the default in this case */
+ 				if (!(opt & INDOPTION_NULLS_FIRST))
+ 					appendStringInfo(&buf, " NULLS LAST");
+ 			}
+ 			else
+ 			{
+ 				if (opt & INDOPTION_NULLS_FIRST)
+ 					appendStringInfo(&buf, " NULLS FIRST");
+ 			}
+ 		}
+ 
+ 		/* Add operator exclusion constraint */
+ 		appendStringInfo(&buf, " CHECK WITH ");
+ 
+ 		opid = get_opfamily_member(opfamily, keycoltype, keycoltype,
+ 								   strategies[keyno]);
+ 		opName = generate_operator_name(opid, keycoltype, keycoltype);
+ 
+ 		appendStringInfo(&buf, "%s", opName);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	/*
+ 	 * If it has options, append "WITH (options)"
+ 	 */
+ 	str = flatten_reloptions(indexrelid);
+ 	if (str)
+ 	{
+ 		appendStringInfo(&buf, " WITH (%s)", str);
+ 		pfree(str);
+ 	}
+ 
+ 	/*
+ 	 * If it's in a nondefault tablespace, say so, but only if requested
+ 	 */
+ 	tblspc = get_rel_tablespace(indexrelid);
+ 	if (OidIsValid(tblspc))
+ 		appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
+ 						 quote_identifier(get_tablespace_name(tblspc)));
+ 
+ 	/*
+ 	 * If it's a partial index, decompile and append the predicate
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+ 	{
+ 		Node	   *node;
+ 		Datum		predDatum;
+ 		bool		isnull;
+ 		char	   *predString;
+ 
+ 		/* Convert text string to node tree */
+ 		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indpred, &isnull);
+ 		Assert(!isnull);
+ 		predString = TextDatumGetCString(predDatum);
+ 		node = (Node *) stringToNode(predString);
+ 		pfree(predString);
+ 
+ 		/* Deparse */
+ 		str = deparse_expression_pretty(node, context, false, false,
+ 										prettyFlags, 0);
+ 		appendStringInfo(&buf, " WHERE (%s)", str);
+ 	}
+ 
+ 	/* Clean up */
+ 	ReleaseSysCache(ht_idx);
+ 	ReleaseSysCache(ht_idxrel);
+ 	ReleaseSysCache(ht_am);
+ 
+ 	return buf.data;
+ }
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3039,3119 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ 	Relation	conrel;
+ 	SysScanDesc conscan;
+ 	ScanKeyData skey[1];
+ 	HeapTuple	htup;
+ 	Datum		val;
+ 	bool		isnull;
+ 	bool		found = false;
+ 	int16	   *constraints = NULL;
+ 	Oid			relid = indexRelation->rd_index->indrelid;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_constrategies,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null constrategies for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != INT2OID)
+ 			elog(ERROR, "constrategies is not a 1-D smallint array");
+ 		constraints = palloc(sizeof(int16) * nelem);
+ 		memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	return constraints;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2855,2860 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 2855,2861 ----
  				 strcmp(te->desc, "CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "DEFAULT") == 0 ||
  				 strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ 				 strcmp(te->desc, "EXCLUSION CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "INDEX") == 0 ||
  				 strcmp(te->desc, "RULE") == 0 ||
  				 strcmp(te->desc, "TRIGGER") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 3680,3685 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3680,3686 ----
  				i_condeferred,
  				i_contableoid,
  				i_conoid,
+ 				i_condef,
  				i_tablespace,
  				i_options;
  	int			ntups;
***************
*** 3710,3716 **** getIndexes(TableInfo tblinfo[], int numTables)
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80200)
  		{
  			appendPQExpBuffer(query,
  							  "SELECT t.tableoid, t.oid, "
--- 3711,3745 ----
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			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, "
! 					 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
! 							  "c.oid AS conoid, "
! 							  "(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, "
***************
*** 3858,3863 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3887,3893 ----
  		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,3901 **** 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 we found a constraint matching the index, create an
--- 3925,3931 ----
  			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
  			contype = *(PQgetvalue(res, j, i_contype));
  
! 			if (contype == 'p' || contype == 'u' || contype == 'x')
  			{
  				/*
  				 * If we found a constraint matching the index, create an
***************
*** 3913,3919 **** getIndexes(TableInfo tblinfo[], int numTables)
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				constrinfo[j].condef = NULL;
  				constrinfo[j].confrelid = InvalidOid;
  				constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
  				constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
--- 3943,3952 ----
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				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';
***************
*** 10907,10912 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
--- 10940,10970 ----
  						 NULL, NULL);
  		}
  	}
+ 	else if (coninfo->contype == 'x')
+ 	{
+ 		appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(q, "    ADD CONSTRAINT %s %s;\n",
+ 						  fmtId(coninfo->dobj.name),
+ 						  coninfo->condef);
+ 
+ 		appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ 						  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 		appendPQExpBuffer(delq, "%s ",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ 						  fmtId(coninfo->dobj.name));
+ 
+ 		ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ 					 coninfo->dobj.name,
+ 					 tbinfo->dobj.namespace->dobj.name,
+ 					 NULL,
+ 					 tbinfo->rolname, false,
+ 					 "EXCLUSION CONSTRAINT", SECTION_POST_DATA,
+ 					 q->data, delq->data, NULL,
+ 					 coninfo->dobj.dependencies, coninfo->dobj.nDeps,
+ 					 NULL, NULL);
+ 	}
  	else
  	{
  		write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	int2		constrategies[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_constrategies	20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,338 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_check_constraint(Relation heap, Relation index,
+ 								   TupleTableSlot *new_slot,
+ 								   ItemPointer tupleid, Datum *values,
+ 								   bool *isnull, int16 *exclusion_constraint,
+ 								   List *index_exprs, ExprContext *econtext,
+ 								   bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	int16	   *ii_ExclusionConstraint;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1395,1400 **** typedef enum ConstrType			/* types of constraints */
--- 1395,1401 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1429,1439 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1430,1445 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 143,148 **** PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
--- 143,149 ----
  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)
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 43,49 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUDE USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK 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>');
+ -- should fail
+ 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 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ 
+ DROP TABLE circles;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUDE USING gist
+     (c1 CHECK WITH &&, (c2::circle) CHECK WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Tuple "(<(20,20),10>, <(0,0), 5>)" conflicts with existing tuple "(<(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 CHECK WITH &&, (c2::circle) CHECK WITH ~=);
+ NOTICE:  ALTER TABLE / ADD EXCLUDE will create implicit index "circles_c1_exclusion1" for table "circles"
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL:  Tuple "(<(0,0),5>, <(0,0), 5>)" conflicts with existing tuple "(<(0,0),5>, <(0,0), 5>)".
+ DROP TABLE circles;
operator-exclusion-constraints-20091108.patch.gzapplication/x-gzip; name=operator-exclusion-constraints-20091108.patch.gzDownload
�=8�Joperator-exclusion-constraints-20091108.patch�<ks�����_1�:��0`�u�:��Z����IQB�N��J�7������Hv���U7�6h������_3�eO&�R���0�-�<|����c_N�M_�����Ul���m���D���&��Z��-9iND�Vk���W*����/�J������Y�\?%��������]��{ss��}#����|~��������}���w����W7]���s�0%��c���������������C���u����+�s3w�3��8z%��E�Z�	��p#Gs�$����/HT�m���vr���/�����"�y}{������k�����w���`:�*I3�k;!0�iYO���
,��k;o���T~L�^;~����
|�������,�$D"9�2�p��
���u��1|�/��$����s�p��a������5=ka������I7����[o�vo.}#��o��+d�����ooaGl_�T�6���lU�
�s�[������&P��~�����������]�����/�����o���#�/:������C���~�k��	3�1=7}�v�@�����"I|��j�1�1�Y�����p�H��H�{�����5w��Z��Q��������~IK�X��$���`$$��a.��$�$|lLAY
"v����hoN�������>��6
�������h�����%^K��v@�H�D7Y������r���I������0���;v��%���F��/�[��,������,V���p�|�t�g�P�N?����Q�+�zH��iOl�F�\u6�j�����h��w��y�S���G�S�^��L6�x����0��
�2� ���3C>ql3��5t���pMYKo��4^���I���P����g1����B������xO�w���v���}��f�-+��(]���e�F����Tz c�6��b��x�{�!LJ���L��j�+���vbM�t�b�A���1)���.=K��Go�X����O��}J`��$:�;��P�����\�8� ��x��t���-�P�e<~6[��')
$;(�Up��&il
6Fb�}vHgR,G@�{�#j�Z�!6�g��!-�l���mB�W�	���
��������$�G}�m���W�'n�'�s�`{�K�\gI����X���k,��k8�����QM@~�s�1���j_�.���8f�p+�F���2�g����%������n����^�7��@�!��z�9�x��)��o>
��>"�&�+{��v�r��3�n��@|���d�6��<����3{����v3��.�.��0��E�@��s�L�5�r�o,�'����b��%���%��2lZ13� �G�{��gX���G#��1��#��N[RM��Y��/Xd=��b�i8JX��iXO���DAl�-���1�I�viO������������J�!�����T���p����%-�%ily���`�F,���I��16�1�{0Lx�<C� �n[
i�J8��+DPH12�UuZ��t��=���'FN�b�P�l�J"�z!��FM����Q���d�9��k��aG&QE&�	m���y;J�P���*7����qq�#Y�A�l���j��!E���c����,����KU���vA���6z�Gi~T�N�ek�phTWe�,\����7��/^�Dv��AU0�����)#�p)��5V�?�3�l���(��1�t��EVS�<�t-e���8L]��s�z���\O&�����+g����s����wJ��.*a:T�V����Q7�����g%~K�9�\�$���������������T~�_�3�S��D���)Fe3����Sq�T��	VxQJZI](�4��
�\qrgA����kl��������
��1�r��������UM1�Hl���i�5���I��8k�6v@�;�z5��\��4�p������0�����1�������nG����������m�
���l�|���$�)�8���u����0��2���
%T�����X)��svL�B51���-�#�1�B�F?�`v ���dL!N5�o�}��.3���Q�Po�NZ���_4O�+C>�U]��cU8-(�4�����;��R3g�{�����V9~�]���Zy�����
���6�;�.j��:X0bP0ky#X}�i��3Z*}d��@�)w��\�����w�wG��o+�*F�f��d|��V'���U�����6��p�k��'���n�%:�n��<JcN�2���v�������\���N�r�'W>C7t8#{b�{|��D��w|���-�	W
�F�
���1`�����tb�����Tr�+?a_���4,���A!�k�
K2oz�<Z�r�S���Ykl�x����_�of��\3(��99���s��w�FmC�������|:�`nd��Dsm�}�u��������t
����p1+�vW�I�A�
K�
��<4�y���C��������m���H��x��3��aT`�P�?���l*���v\��z���E6�����jU�u���>��2�/�L����g�&�$2�B���O%��2�� ��!$���]�$���i�m�����b�~�!��h��[���Y����km��UFLB��S�h�[��v�����
ex�h�� C�l��v�o�DV^��g)��K.�h�;�������G�@�=�t�b3��N���8.I�kp�����Ye6Mp{�{L)}��ql�iR����8H�X����7�&&���d�:���!�h�xb����*�G)�x�0�h�.l��WL��j@�#�a(��		�2U"���<NF@��� ������,����%����)ZY��K"b�|�N4%���|y�j6�5^�����P�?��B���7�`sBr�%B#���*�����5
���J}��q��6�jEt� �gQWz�e��gt�~�<e�����P&
���R�i���� s��"�
f6	����n�Np�-,��~����QL����S������W���g�����x�@�\����~�7^��P��xu�[$�+od�9f�M�Y�?�\�R��|
�)+�T����p6���!��S��a|����O��]������p#'�������R<���q(�"�l.T�_s$��"
f���`tP�$����m�:��{����(tI�A1
]+�_�l����{���c[&���H�� 1�:21� ���u���� Y2��Yc��2)����D��j2"6"���� T�����������������G+A����^)��K]KY���`�R�3����s79�aY��|�\�#<:/�,�:�<�(�0�@���	��th���-����j���M����]�����,1�����oP^ ����������_TO^y��YNlR�,lF=s�M�����0y3�~�x�3's�V�>���kT�����}}��O1��=���TC&7n����asY;*x�f|u��K�Z��pE"��#. 
HA(�2pq��GM�_�I��yH����U�r
I0_l��#I���i�P�)*r�����wI�u���%G *�n]P�S�h�p-��B��]?��9�N�(cL�6|O�2p�r[jrx���yx�#h'�s���daN��i���3�.c	OUNNk�z9��&�+���ld�$��sP&2�[Ds���o|���+S�b!Y�,����%b1�G�v'>���8�a��JM��zb�b�Y�Q>��Z�L��/uU�I�7<���5��������1"��5�.�R.��G(���:kky����<9o�N�����jg�Sr�m����r�����U�L����������x�u�Y��u��G��Y�(U���p}��W�V��x8�C0I�!P�w�5P�����yX>���~��,"����/����.��;�v0"1
�l��Q`Z�+8�q����|�/���]�cu�0h��[U������
[J)�i���c�Zm�!''��#�g�Vd!��<)��V���
����/Y9�J'��������4yC�;#4�����k�X�U�"���t���(�j�"^�����o��6��9m�7��<S�|"����x��;��;I�-e{q�PNmPGl8�����=�y�1����-}�����Ojd)N����G��a_��c(�'�d��*���#��%=b�6��af!� ����'A�/rE���
6U2�
�iK%����W_��8qse��P�l)��HgWY�2�������	��(E}R|M_)�I����q*����Y.���k���SB���������������(�E����p������D�=pJ��h�w]�����5�o"v9�r�_"%���� u�1[����c��I��i�����j/x�U�;H�:�^+����	��-c��K��J��F���]����K�������4�x���|�,H��Q��<�Ou�w���s�RE2O�r�=%T���gQ,hR���K�-�skU���N��Z#���<�}�I��b�Ba�+�6�������I:{�pC��x��P���;�/_�S��)�g�\T�U�Xl�����J�<��l�t�<-7j;�R��X��RM�
���VL�)P40qB��������3��O�v�-jt�v�o���(x(�$���R�L���KW���w��WPS��V8U����
��{n^L�F���kZ)�������k�c��)��p��2P�;k��@%�zsr�jV����<7�����9@����S*Y��t�����mo�������:z�R6����IY�W�D����#^�����C3pj��������C�����k��!���w�`\#x�\��{9���
���c��xS���o��O�&��y6��8imP�\,9��JrZC7K���+���r9�%�������~g������o�"��k�tuMK��H2������qI�/&p �/�I���v��Hd����/��Q�����(��/F����~��1��X-�`L��Oqn����G����j�E/}R#�ceN�5��4����X�Ki1��-�L$���WZf:.�l�0����:�8v|�e����M�k�O�\�n%�+$d�N�$��x�D_�:����g5��xv�~�{^�y�K	/�rh��������(�0�~��2�E����)�m�a�i2��%CDm��HVBp;W>�|�Y;��
_ v����4tL����������{o0���/�H;�f���@���|3�_��*AB��a
���(5��& ���B��v�a�:]O�j���o�T�����U���?J��Hv�e���Ej��*���W��E��mw=���Y�z��Q�G�7��f�Z��K^46]�C���$���p7{����m�',0w��|��*�a�����~E��Tm����yG/j�,F/������cc���[�O��A���&(�X1��t��p�����9Q3�\��m�;qh2�TD����Fp��3�_�"��p�bS>5���<.����T�����LG=T/D#���:]�qJ_���o������m�B:0�2���P$�����V���������V{~�ws(���}��������e�(��T�����i����|��4(��i��^��kn���*�R���K|���(z�4	����v�[��$Fe��93�����
��)�o�6������<I���PB)*T�G������G����/�j#���[|�(�l$#�V�L���@�fl#
�v����#�V[[+c���gw�-I`�;3��������w��������y^y��z�t}�w������\��&u���i�:�`��,�����>}Q��_R����o�%��R��&�C9xE����fr���)��yh���5]&��EP������5�����&���w]W0���52�l���.�r!������nv����^.��jM~m�t�\L�+`��j��z�k
T��q������\���s�����
�������0d\��ge��w�'YE.Yz�Ax�=�6����gT����m�����iJ4c��n�i���p�����V�I���$��`4��,���>��P��9R�Q��D{��j���T��FB����4Bc7����m��u�$��P���v�K��^j����Vt��:
�p��i����nk�tO�U!^�Q`�
��W4`�����@L�f(�0-"��r���*n��}��d#4�"_�^��8C� E'*�|��E2������f����0
f��x��0~�,�$��`�����	5��!�������k4X!�W(�L��%&��l�������	���������[R�}�%�yOH�`7�1����g�z�m��p7����d����|?�p�kn"��|���������M���X���Uk��|$���w�C(�
��x����L���`�B�
i"��������P�G^(v��[\�K��&:E���j����T:�4*����D��5�D�������(�"Xs��vu��S=&���1Q�3]�a�6������E�O)�=?C����=��gU�-=�;O*y�a���k�����5%�)��T���6����!�l��Q;���kt
���w(��H:G��]^LeV�R��#�R	"�f���7:��X+��M
�]����k�F4,[O���{��g��q��b����&�Fm�}�NHp	v�n����{��2����a����?y���D��2E����w�����r���"6k�>g�������$�6bqc
����f:������P�^;�/^��Vn��R�d}dl@�@Dh�.�NH��S@��K��%���������f�/��Ka��mI,��(7{N(8����'�����f"��Q}��E�g9�V�@2��bB��q���I4��[��!	��d�����~������K������9.|�v��e��=4<�X0<�/�W�uT�T��Z�k>�?0a�2�C4�	�8��?�~�5���\�{��>1���.���:Q'�dc���5�?_�FE�L��v�:�� ���������~y���u��{��w�W]�JyJ2D��y�:�`}��.�2�q0�S��&,:Z��M�	]~��K:?�C8��M��z�tC1<#�XPeET����L+��f���d���s4�)�%����a�@�%��
>U�E���x�5�#��7%
���-{r���&��u|Lm�*{'�����pa���  ��%�@b� ��|��w?�_�v��l�����l�X�Rtq7�"�z�|��?K�M�i���u0���,;6v���bq;��W0�_�_r������((�jV[o�@���.�Og��N�������<�e����N6=�C�<�36C���{1�%;������8gA8T8(��=�(r���f���&�*c4�����\��T��\�vf������=g��/s�gz���y��I��4���g
C�y�{q�J���cS�7z��2y�7���	�������P�Z.���v�������p��+�,8T+@���:6g�,��6����Y�!�T��^
j�/�;eq�
��7+��y��a:����eDp�K�����g��$�-
p��������`u=m-�V��z��^�-�d_m6#�n�I������$j��M�Xp��.
 ��j�$�7����0�`I} ��!+;�<������ZM���E|����J>O�����A��Wy��E����F��'0<
��	��;fA��r�.�|N!y��hk�#������s���M)���es�!u?�*M����������3f�L�X=��)��H"9(�"��c�8���=�Wa�/���*��
-���x�
��T����������cj�&uU{�I�a\�����Le����T���'���F�Ee���^���v����N[��g���5�`O
�%�_���i��%�G	�n�O's�~r�b|L;�t�8,Ya���z}�k��sZ/��_�X�Gx�<��&���p[R��eJ����]��T��,{-�v8^���E�[��J�����Vh��*��<�n_�_�V���M���������Q5e������]",�~��*e��M/���,����������� F�a`�T^�T|�F�i�~�
��Q���X�=�+�#t�a4c(���%Z�CF3����0�a�B+N)�����g�b��k��Dy^%z@	����2u��+��35�j�����R�ssW�\�C��(���G��
Mg��E�I�������'u\�6,��74�������X,=����!�|#+�2�\hg��b#|�*������S����O��9��j�������� ����VC�u%}�n�-��YA.��&��LYE7�]W�`��	J������	�o����,0�U��~u1S7}��G���D>m�L����7YND��^�����w�:KH�����qO��$�h����������F]s�oa<���k3:x5���H}�����(�nW�D�����rq?�G��eV�;���a��Q����8������d�w�m;�O��e����k;����"�`mc"��h��^
��c��\2tE��\c���9�2U9�w�~g�9�Q��)
�S��'.���o0����hG����gkS&�5�p���LA-P��\a�O�
�b�bB32������.��:6���:Z����&��\,����s���a�"�Q�v�%��g�������fN5��X4��)fYg��Y�P,�9"F��qZ/�v0scmu��0:�f��t�.:f�#��E���9m�-�J��������^��o��}��e,�N��t�4c��b� ;�4��F=��<�S]���]	�����=������� ���3	#Z����
�R�[���IH[	S�P#��2��]~��E��u�*8�Kl��w�Z����;���^�p=��5,�Z����(�|�\
~�Cf@9�����d�{H�M���S8��G�%=�G��.������W�������I����h���E4�	a���������.���l��52��pE�n��5��"�4�f�6������@-�a���l�Z.��������S��5���������Z��RNep=w<^��x���U[��
�kR�X�\35���	��������!�=��B@���o����N���{BR�o	��&����Cun������6�w<�>����I�\����VV������F��0oF���
A�0�%��z)EmZ���a���+�So��c�W�o�K�����aHu�����J
F�A*��P�*�f����N����@@����J��8�q�K�36���O5��u��uV��?��
H���`������S(x=��h0��w����v�����w�{���O�	\�N��A��x�S�.���Hs����K;�'�(�G���������_4��cnY��Z)�e�����Cw�=�S�<�	#>�}.�/w*cd��*^}����e^�0&.�~�g�r�7SK��n����gFa�r�1ITU0gL�b�Qa�=�W�)�Q��pR�3�4
u��G�����(��)��8�3�V�4��[�������Mj{��s|&��F���n'��sBo��^[���dt�5��7N��p<,����3����$DL}��0��l;��	����5~7,������
��%0aJ?I��(�6�Sj��0#m����*�
c!8�3��%�N��
��"�f�����G���(%����D�*��R���6��u�����8X��){t�"3,g��t� 3;�/N�:S�� �����M�-PK���7�X)��P����0s�-r������Wl���������h]E������N��������Q3������H8�Ab�;�d���(6=��DFd�df8�o�k��5�&eC�)!a�AyT-�J���MN�-� l�� l)�n1�p�%~�������������c���

��#g����H�������d���iE4\S��Y?��JA�z3h��mMm$��S��m�N!���N�;w�L�p������m4]������F�n?���II�'������Ma����
���������0���$��C��)�c�
���mp8���b��p��pw$��X��v�����+�K}!;�7�����F����c��
(<NJ�o��p@�qQ]��`�&V5/�or&��g\��F������]Z���D��F�o�;R���a����
2�g�.w���w��=�`�����c���	�a�;O�!�0�������.`�|�8,M�iW"�H�M�p;��Q�&;��
e�O�����<N#S'@�2��}�����DK�l�����7���C��{�����87	��}�HK?���rI����3��8��<�����0I����:x�A����y��������3e���O������^��N>HB�tQ2�W..P�Na��e��b�N���l�e������j�	�C�xQ��erJ����B�C����gB�j�K�/��	(�_���VZ�e�}��M�� $�C���`.-�2�1�Vs���3�����T��$�������t������f�h��cT�:����J�W;8p�"]n4���e�]G�������<
�9���Y�c�E�0����|O�v+����c5T��~����.;d����.�����d1����4���T���!� q4�E��=�h/���{���J�t��uw�3H��w���6	x���|��(�	��%(9�5]��O]D)�HG��T\�J���*O����h;�qs�i�<~<�~.��~B�x��E��!�>�-8�/��X�r��C����"����d=�g���.�?���h���&9������X���������W�>�`�(T���6v����!	l��V���^|Gl���&�7���E�g������5���`#0=g��D����p`�
x�	��6�f�+��������3�s&��h��ip��R����T31�'�K��(�����tZ8�j���~_��+��K��lt!�g
b���
?q�Z�f\��oc���A?$	��568��pB:(]T��ir3d�.q-M3�p��!�$�s1�n������I�����;���6YpQ��L\K
���M8\HB~G���M�2}V�t�1�C����6\9\��A���<p��p4Fwt�	���*�����@<������q���rM��[m��z�p����7��ohA�=�x�Y*F.2���$�?���+��w���W�b��#,FEe�,
\l��Sy���n�)H��w�Ox��H&��?���1�A���X����?�(Z��>O�&P�c��{�U���{^�zxI�S�K	������7����=�Y���x��C�-OZ;�2� 3���1�]zE�����z�c��H}�L"��!�C�y#
a8���u�;���;�<�����M�������y
����,��Rt��W_����n���x�'[JTF���9�&�[�4k��lr�,E���\AO�G�f\]DO��"��t��@�bI]���01��pA��Z"������D;b�%��@{��&'W� ���	�5V�����#���5-e�G��E�����-��Z�$����X�����$z��t�C1�R�qdH�t�L(
�C1s0Rw��.+6����
hK����ODo���aVP��&���*�p��!���ww	AI&K���~��s�X��+-������yGl�+z~���|u�����n�������	����s��0���K�����3��h��O�YBC�l�<v��7���J�Fi�������������o�qu�����)�5���\��/�5D�9,�=?���#����O������=�ku��W�o�m�\�]yl�%�o��1]G3j����|p}�s��@^�p��[$O���2����x�V�m@&�3��5������f��4����i����	�I��h�6
���Cs^�BY���AH6��0|����{X>d�����{��(~�"�w�Y�����|`M�$�1jZ/�����<�#EW��u��`�C$��0�=xI�t�	���J@�u�+�s�������EW%�S��e��x2[�2�H�:������������V8)��O��f���'��nDF��b���p��H.ca����V�1����U^d�`B �Fa?��"��Q�(�NY!���l�h�:�l��s(����a��o���������
]U;�������'v���>W�F_G�|5��|�.�?���g>�/o1Rc��i�����i0�� ���hy���	����qW��I��������3FM�"���}���������4}��|�g��q���xt��/}
V�������0�m�L�
v$.�cr$�;n$�k��Z98j6J�z��6�I��l!���,C�9��K?����"�[�����-47�v/�_�y��Y^���qM��@�<��Y�Z�JZ�d/b���O��Is}+S���Dr����b)��y���x�^Gn���Qc<:�J���`�l���,�
1
��=2
P�"���&�w�m�L���;�,��z�H)�B)e�G�d
�$��T�x#�����
��u�b_k:4�V�TWk��`�W��,*q���Z��@?��K���C"�c"O�i�dW�p����v�����M���g�y�n��	2�u4�Z&As��f�K)�9W����N����:������?�1�X�]M3IO<�nW�Y�!F�;�T8n�F�Z�TnU��A+����I����L���~J O(�$Qn�����fi>#��H{�0��?X��S-�"��/�G?my�N*����1��;��v��_�?�J�~0�����*@������[�55����CeX���yw�fjx=}���0�>���yGm��V�{2�P=�knG���l(G_��2_��*���=,����VL�	p�WTl��f���L�_�q��d��R|(hD)�����������
�����@TL����$�����
��]�O�/�
�A0Z��&�nh���j��sJ�	.���q�/��������_���Y�w��]����������{�p��$��N��������:�X���{�
�����9������w���� �z����.	��*03�������O�5��|#�����E�-�r�>��.L`����3j�$`t�������^wz0���W����v�����m�v��{��?�{������t�����S��
�tfF���@��j���E���	+���;�c�E��yI�K_(7��H��0���+���]�x�+�5^�Dp#H�8����j�mh�4����19B� r<������2��{�4x]n~�*���Xlr�������G�}U?��{'�B�c�<xa����	�k;/��R����4/�_����\d>'yi�q#�r9�����z��d�+��������$=`�v�743�����3�!pC��E��H�	iq�K�X�B1kGU����M��`���'���x�9D!a;9�Z��5\Y�kY���A`��W�n���G�++���c��� �����������-���y�>\:�%�~�����et.�r�9W���#��I��R�wm����4&7��A6/E��zF� W4L6^�.�(C�Y�XL%U:z��v%���JQ}_�
��%��Km�EI��u|8��=<�n��&W�U����kQo�������;W�}"�fs���]���w���^�fL�!He;;-||@E��}f�3�R���(`�&{������SQ0[.V����A�e�sr������}�0+](#�BwN5��5��8�7���L��r��j�@Z}F}�����J�o��$C�LM�����7��o
6^W�Gc_��M��������ZM��U�����L����L� %�"|)��8�h��N3t��B��g�v�|6�D
-��������K�[�8Lt~��W�f"�������z���pWN�@b�1�����+�"�:�\�����,I�vo����+>����"���������*��T�5�E;�(��o,?
�������d��Nz=�hM���m�e����X )����u�$}��7�����."&�Z��!�(�I��i���lq�Z�������e��X��$�kh�X��_b��6��*5J��G������9�iV���t\f���WES���.X=����R#���it�>�T��Q����'`�X��ls�������f���`<�f2R��Q���X��
Y=Q��&�i��T������������13z5�~��$��_�,'V������~�h�>b�&!�V�4�����%��|>�H���M�-C�P�
<�����k�{
����OY?���$$��zA�X�h��	i��)z�je��K��I�^��|~����G�an�^x`FT������&�A��<KF!��_s�
0���{��u�X[nX�s�=�!��/�k�E)Ng����D��<F><��@���w:p�S��I�uI�4� �nI�w����+V�0��(G�bo�J��Y�-u�/���dN�����Yzh�|rO$-t�������N�	0�f��R�3g���J���	Bk�A����S��m.jO,Z��eeU�w�A���p�y�.�����r����T�%C*���/�����^�(�O41Q��C����s�u7����n/��Y�u���k�k�V�������v�{��j�'��L8�Sh9��-|$g��#��`�E>Bn�9�d�)����[�NB�vyf�eA����MM��r��8��~[)�r��s��^�$D1!�1(�$S��E5�%�\������7�>�u.z�:�~�&gsiU��F�Z��Oc�C��OF_$ ���b~��b2L<s6?��-�(�,mh���������1�sA�>Zd�F�!s{�}�c6����@I�� K�7�GUE�81����/�����v�F�����s�7�n��b��;u��z�(�E���paL	5�����$����zA_/���3�`��W�>;E'>AA��5��J��}a-���	��f�/1�F�}X��B��D� ���'b�/�i#_K����E��cIr���� �H.��<S�n�s���l��Q({V�4�U4N��:AHT��J������G ���,�(?����|�k��j���l���fe�F���F�&
s^������`��������A�f�u�*^�m�j��*Vj���y	��Ibc
��<a4����;6�������y���m�tl���v��:(�t����a?a��)H�e���G�g4�J5���6�
��������+����q�& �-I#�L��\�5"�[��ztsz�Gi�6i�R�y�3���:G5���v�����(gy9���y������hA[,��(��|�mA�	F�O���#�0��I�024�i�%+���F<D�0� 
K�sf<����h<���D�_pw�zQ���0��|$�G�i0���
Y��c�����Fi���V���4����`b[
���t�)�E/�$���'U�\t>mHD���@��0���xA%�Mh�������=�6�U�`�3�k��l����q�w��gh���'�I,%�Y�
����Y��a���{mD���;�&�s
�z��INB���Ewv���F&I��T���t�s���}�K�.�A��4�U.Zd&Rk��2��!�'�C�>���d�\mT��Ri�h
G�k��c����X�0�������}����4��VnLI����-�E��\z�����-���-���-���-��0Z�`����]*�fj���7��|�`�:.��`�����V���j��<��t,�,�1:hT��z�X}
V��J�����t�6�/L&rB4��E��1
zM����o_���h|7����k?L����t����	S�\%.������!����t!����IO�t�8c���
�����opl\�x�%2`N���Xb}n����jk�A��+������t�:a�KW�W�GK�����@���� �R���y^hh*�	�z2���k������G�i�����%�`�����}�p{~��,�T��!a����wa/3��q�^k��Yn�dt���:r����
�>yb�u�\�B��%&@/x$��J�jip/��J<'�2��j�N��*b�n�+h�
�n<$=��5��Az��L^"y�g��4��
���;w��~-\;�eg��0�N<!b	�������.0�^�t���q�X�Q2�	:	�o���O��/�:'����DN��>���#��&X_�,��OR�A�������c*�o��I�����}5��3y�x�(=�:!����39Y*��3�����
�����.�[��������ua-��`79�Y�1/4~�f�p{�QsD��V����xaZ��^l���f
0&��h��8����i���`�\�]��^�<�S$�o�5���c9�H�y��l��
�E��L�;Kn����N����m�O��N���#���A�,����khE.����:�����t`/*���	�|jZp)Q���*�Y��,����O�rAI�&�<�8N�#3:X(K� �k�-PQyot��C���J��[�f���?R:���A;C,����W��FY��Ll
�k���A��Pp��A�y����/m�<����|�Y����97�����d������g��<����l�x~�L#���0�K:�Tf�K��_��%eVBY_�pL�W�����D�������D����p'5�����C�c�&����94	������b�$��1�f��U*��f��(�� �xw
��;��sb@8JcN���i9*���N���~�#Hn����wr�0;��8�����#]�c/jE�hoAv�I���N��xg��N�l[����H�I"	l�����G�A�M]��Q�G���3����@l�?��P�
���8*�-��M���fK�v��,#������c���;�n@��na'�M��Kz�I�yhtb�����*o.i��wh��&���1/-'�����M�!T���5����I�|��_p�
���7���������b���J5�.�H9��<d<,>^%K�0�>]�b�^�0�x���9����X/|3G�/������;8,�(����Q+;���O�%�y�|8+�{�\���HP���o����L�T�+���,���9�`j���%2{c���t�G���O�.F������z���D!��X���#���v��vx��;������I�,���\�I���y=c�4�2������H
��t�%�a���$��$�p�8p�=D,Z�K/����Wf8��Jq������3\,]�p�
[G�� b����i��A������i�|�M�F���9���XWI�I?kF������h�e�T,K�y�a�I�"�������	�
�H�|�|]w�����4����gHlB3��`�X����,�����!�o*F!slSg��S��p�\c������>G��%�������������@Du�>���T/��af���,%��V���6�e��|������������um���:�#�-�����3�p�@5~�N��,�%0�w�*0��k���f���=��d�Y2A=�g!%������N��B��8�I���.���a���	1�1\�
�S)�^Z��
���G2?w�1-��?�Hz����D���V�O�Aa�y��<O����6��^2Yf���m"m:">����93��5�<�/������IS3)�a0�����,�O"t��X�FyL\I���*��+V�74#? ���sg�b�\���wc#�9��[����O�B��Y6����u`K���bg�q�3C�1�#�xW:��K��SY_��$�pj���MR�Y�C)�=�E����4����G��;M5��U��5��40�x�<
o��[�������	��&�?�Ml�t������Q�8J�Zct�����h&���(���#21����Q'��bi ��'S^�YI)����V�G�yzB�1�����_�M'����_~��M�QA%��G:E?���6va�>P����]t7x�j'*�L	k"�5���;X������cH�Wf<d3I�S����N4I��dQ!H��=n1��N�e�vg��7����S��^�]�{�D1�u�X7��� ����Yr����:'H�A�o�\��������8p�w��:�H��z�Od��b;`����G�>�<�����c��C�xL6c��:�^a��X�
�1�5�����������v$����h�)���\���q�Ak��6h�0�1���[�XO�|'j�(~Yk|xs�3���I��A�8e/P2�����1����3����uSHo���yA.$�V�VrI�6\���xn&�������>�����f��� �4Z�G����&J1�@K1%�t��������q3�q�5b^'���CL��w�h1@I[�d.N���������<�U^a&�/.�
�@*���\5�.xm��
����X���k�����Z��~���F��]9�;o;��[��A�gR��_��V�T�T8L�HYtR�U�g�~j5��A�
����
t�|p�VAH��k�����
��5�+���C�9b�,�XpBq�'~8�����������C"2�69��/���i^X&4M���
���(�cS�����������MBD1JR���\Mg�O�^p����, �
�uh�p��b��jB�_7v�����)j�g#T�7��/��b`��\^CF����j��F�8;�����l�?Q���;�j�q�++�n(�}Y���`X.����j��Hi75����R@L�^�'y9��s��r�D���n����sQ�w��0��f����^�K�y���k�����(�p�p�eO�]�E(#��*,��[�|�F����Q�D��W�������#���b�����\9%7����/~'DUT��[C �f�U-���RkgR���k�Q��E���C�y�b^j�N�h��/��wvm�����@5��Y#��g��(��}f�j������DkF!�$��AL���1gb���b����c2�0���]���@(�����uN�`t�}h�
�QJ�y�#�[t�K����;'�S@`�D����cJ<�g�(���W?�f�Z.���k�5�����]W��k8�O{**�O���]�A����}���&]H��(W.8^J1G����(M���	J!\�S����1���o��QX� >�aI$���_��"eR
B��K��s�w�*x���!�-�I;���R�6�-�������������BQ;�%��J�l�� \���j�����N,CU��{�X[��A�JS�j�����_�J�s�~��d�>y�������fx��_��Y�M�r��35*-n�!�2
@'b����m90G��
n����k�E��bb#��Nk��{���^�t�h�{u��R?�f7���/��	eQA�%h�|D�vs�
33��3����Z����79L��l?���)X��_����xXX�o��;��k��n���Rn��%s�=g�'�����������J���t|���'��@F_��?�c��r���$34��F�/��[����=��7��Ez:@��xFI����{�F�}r���mJ��|���3D��m��)9�NW��A��'�`
�)�&A��y�[�/~�&�O���O�4A"O��|�Qj��!V�Vkf4�A~#)&�)�bF�X�/{���Q�������2�m�z`�H����MJ��.�g���YF�C��Qv>��fe���k�
B��Rnq!��^.�H��]Z������]�+���xa�
$dI2
*���>�J>3��Ws,���(�|���o�Y���c�U��y�@q���U����79t(|��5m�m����fV�%��Q0��Oi����fz�
L�kIZ�t�"m�Bz��XG������?�����w&�7�6.�)�q��j���&���~Q]w(�D��{�#���@�.AB[����u����QWl�g�2��m3u��"�$�3��I�1v��"��pq;��W���{��
���^,����|�����9���#:�`|S�nJ�A��:�e)�buc��[��-sH�<��<.��3\�e�n(0{(�h�P#w���k-�/���R6����DN�N�	[�>B�����EsP�T>�]�����]����<�-��N�n0&����2Y4&&A����~vE�:_�3�����2i��^�����\#Q�J�kY�?p��W�L�OZ����yW��s��6X��0�����[��9,%a����j)wI�%������_(�t���Q�����N~�|u��s�Y����<,�
���>�.������:���%��`�0���z]��LA�s�����0�Gn{�� �qo������u=}W���,2�
�EH����f��v��b�i�-�!%���������7Y��7�-�S�+�Z_����|�[�wZN ��oT&h{��r������4�-� ��V���;�Wi���@&l=q�������Md�E�QRf����\��rq�2�w��m+�r��5�x�c
��f~@����pO�f���@,��4����G1J�{y��[��
��=�b����w�i���E�@���Jp��^�%�W�9�n���P�R&��8"���b{4z�X���9�����w�k)���i�����j =#���	1h:`��\��;9[��xgg_����v=gQ���#��	��mS�� �Y(�h���]����n�7�f�!������y�,Q��9	�v�W�.������-����(�d��'�d�sJE�[_H�����(� �R�1l4�IqrC3V��P��V��J]��O2��������T�U�Zo)������2�W��:8�_*��H-�.a���<���G��H�C�VQ��+���E<`+�"�D���+
b���/8!K<��{^��T�:�C�QT��F���Z1�U������'w�'xz�����?�C��8%g=��d=.a;�w���tH���wX-�{:���uX��t��������o����
�_&#Z����u��������S�9`c����~�Q���b�����u�Z������l|�>w�c�BZ������������o�f��o�f��o�f��o�f����������7������o�����G�b������h)��!��B���.��:X�m�"W1����a��������Z�#��B�"�Qf�Q������-a��G������Q���k���5���r(V��*����1����7w�Es��	+�����l��CN��������z
V�_��f�^���jR�B�����4�>t�l�jm����"p3��"/����j]U��F'���������l��-���j�G|onA�����6$��^�������RH|z�n0������&SI���&3H���g��J�_E��$~�E�i�{�""W=fM��2m����I�����6�jZS_f�9���mm�h��$���"Im������T:�
��fm+
�nb��H��i�5MR������9�qQ
��7����]k_�_W����=?�7k�eG�5��Ap!�yp;���f8|;��Yd�!�Y,��I�����dn����d&���2�Z������A-���{�\���dPxf;\H�d���"��8��8�������o�2�������Z�f���"L�����c�0����@�N(l� �|x�>c�A�Z��@��T"uo����������M���8���"ZN^S,�����#N�q�|$��#�!�-�6���%���Yk95W9�+�(e����Rr�����s�Z^_����������V�tO���rt>���5E���\J)OP���.kW����0,��Rz�+�����/hW�������5�d����^��
����dLa4��S�;����+p3�Q�aC��;���T��]!
<�H#����B�Q�.�jJ��C�&�t��~D+�1�8t��?_\_������=�(s���/3+E�@$�UM~�#!6}��6uH�NW������t����)��p�J��O��Wv����sn4d��@/x�]�F#m�O��_��W��^l9z����{�J?y�O�����U�$��]��o�/���H����U!&2
VV��J7��ZY�)%��h���oJ��ZU�o���v�K�nIf���O���tn��T��G(�3�:q��.����z?�>�D�]����S�����rd�VP9"B�(���D��K�������D����,ho��kLlJ<7�~n�
���������3�����
+0H�P	�'����A����X`X���Q�:����]������)���K"m��Q+@C��}K�����S�v�A�y%Mx��I��=��5b|��Y�+��G�b��Z�a�)����%m��7�\J����c�f�l���!=C�p��y���3�#]lM�|s��y�	h�7�5Sozr���h��Y���l=&�ER��`��{�%�N�%��%�_���������}����/{����,m�����f�~X��������|��IU�f��/_�0A��<�Y`#�jNI18��
U���Y����M�a�V���/G�J�0%����`��w��s )�$�+sL��i���
"�:���i2�|w���Q3����=�o�
f��;�^�A�V��R�2�*����;�B���(�I���7<M��)K����!�i�������hS�"��������6�)J�V�C'��c��\Yp+�'#xG�������G�[�[gXzs�������m�����o#!,����x	�����V�]���ev�����8��C�x�U�<n��
]yM�~\,������)�����n�#�#��i��C�34�.OQf��L�����f}v�u�K��J�#H�����4�t����SJE���n���7��By��mRM������H���WI)!|eT;:��J���9l��x����,i�(39�}�Q��w��'����:O�)b���g�e����/����	-���J���r�<C�V���~$U���=m!��X5	���@��������<�����_�<
f[���xT/������^0>Zq�9�V�]����k�����o�&�=g�`�t~����u��Vk/�x�	����E'�=��Q#�s�>>
���9�B���e��D(�����8�2���,l�~������d�Y�R2���]��$QaMY�!�����<1�x���"��U0=��{N
��{��V�C|
���%��u_�- ��1�����Ri�4+�5�PJY��+D�P�Eg)��>t��4x�?�_�N9H�r"F���h�a�y��=n&y���\�q��xX��\'�'��s�y������"��QH�||�,�BW�B���P���n�mL4����J�V.I��� h�`b���ANL���:>a/9�Dha��E�.���4��_��` �9���G�:q"1x��.�6/y��������8����2�����P�iM�:������
�H1��/���5�I����xl��	������*}����rF(������W~��u���L�b7��C�>g�a�	��x�Q3�`��A~"19:�x]����O\6Y���W9si@4�O����%K���@B��:�D���m�ea��VKu���{V���q��4�V�J�2j�J��M�V�gs�x�$��`~����5���3��~�^��wA&�y���	"Q��s����~]�����Q�������M���``����eg�_��2���v.{i#����YsL��]P(��:��y�z�������/���8�B�+�N&E1�J��I*�Z��[��bF�����9�@��E��KD�i��,^Y8���S� ��&��W1SGP{Bm#�����$�
0�8��~~����o��N�Qx�$��w0~s�`�&G`�^���|$W��,z�]�Y��>��.:th)\����|���
�U��7�F�T��F3��m������3��Q�������%N�����`i%��������n�����4�������N���w��j�R��}��uz~u���UU���=��O���`|�����9~�?/������l�}�,pIm�E���p�������������q�,�/��$;axG3��$Y&�|P#��$�,���]9�v�Cp����_�o�w��{?P�E�x�1�_8�'W���`75S!�V�i-a;<w�����j�X]�Vl���A��������]l�CU�u�U�GO�R����.	"=_�"Q��j��\H�JA� �lg�f��"FT:y?��ih������N���Ue������xP*�V��H�����f�]���*r+�A�5L*� ���:v�?�Y�U����R)��=���`��������$n�+s�+�:��w��u����L��#K$x{�h�SV|�,DD��x����tz���/E��v�f0����v�$
T��ac��*+��Z�_�������v�d��oL��t"�/�x<��+�u���T��k
#189Simon Riggs
simon@2ndQuadrant.com
In reply to: Jeff Davis (#188)
Re: operator exclusion constraints

On Sun, 2009-11-08 at 13:41 -0800, Jeff Davis wrote:

On Sat, 2009-11-07 at 10:56 -0800, Jeff Davis wrote:

EXCLUDE probably flows most nicely with the optional USING clause or
without. My only complaint was that it's a transitive verb, so it seems
to impart more meaning than it actually can. I doubt anyone would
actually be more confused in practice, though. If a couple of people
agree, I'll change it to EXCLUDE.

It looks like EXCLUDE is the winner. Updated patch attached.

The feature is still called "operator exclusion constraints", and the
docs still make reference to that name, but the syntax specification has
been updated.

Don't think that name is very useful either... sounds like you want to
exclude operators, which is why I got lost in the first place. I'd call
them "generic exclusion constraints" or "user-defined exclusion
constraints". Sorry for this.

--
Simon Riggs www.2ndQuadrant.com

#190Jeff Davis
pgsql@j-davis.com
In reply to: Simon Riggs (#189)
Re: operator exclusion constraints

On Sun, 2009-11-08 at 22:03 +0000, Simon Riggs wrote:

Don't think that name is very useful either... sounds like you want to
exclude operators, which is why I got lost in the first place. I'd call
them "generic exclusion constraints" or "user-defined exclusion
constraints". Sorry for this.

Either of those names are fine with me, too. The current name is a
somewhat shortened version of the name "operator-based exclusion
constraints", so we can also just use that name. Or, just "exclusion
constraints".

I'll leave the current patch as-is for now, and wait for some reviewer
comments. This is purely a documentation issue, so there are bound to be
a few of these things that I can clarify at once.

Regards,
Jeff Davis

#191David E. Wheeler
david@kineticode.com
In reply to: Jeff Davis (#190)
Re: operator exclusion constraints

On Nov 8, 2009, at 7:43 PM, Jeff Davis wrote:

Either of those names are fine with me, too. The current name is a
somewhat shortened version of the name "operator-based exclusion
constraints", so we can also just use that name. Or, just "exclusion
constraints".

(exclusion constraints)++

David

#192Greg Stark
gsstark@mit.edu
In reply to: David E. Wheeler (#191)
Re: operator exclusion constraints

On Mon, Nov 9, 2009 at 5:12 PM, David E. Wheeler <david@kineticode.com> wrote:

On Nov 8, 2009, at 7:43 PM, Jeff Davis wrote:

Either of those names are fine with me, too. The current name is a
somewhat shortened version of the name "operator-based exclusion
constraints", so we can also just use that name. Or, just "exclusion
constraints".

(exclusion constraints)++

Out of curiosity, is this feature at all similar to SQL assertions?
What would we be missing to turn this into them?

--
greg

#193Alvaro Herrera
alvherre@commandprompt.com
In reply to: Greg Stark (#192)
Re: operator exclusion constraints

Greg Stark escribi�:

Out of curiosity, is this feature at all similar to SQL assertions?
What would we be missing to turn this into them?

I see no relationship to assertions. Those are not tied to any
particular table, and are defined with any random expression you care to
think of.

--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#194Alvaro Herrera
alvherre@commandprompt.com
In reply to: Tom Lane (#180)
Re: operator exclusion constraints

Tom Lane escribi�:

Andrew Dunstan <andrew@dunslane.net> writes:

This is a pretty good short explanation of how to deal with shift/reduce
problems in bison. With your permission I'm going to copy it to the Wiki

If you like, but I think the part about figuring out which production
is the problem seemed to be at least as important for Jeff ...

I agree that it would be worth an entry here
http://wiki.postgresql.org/wiki/Developer_FAQ

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

#195Jeff Davis
pgsql@j-davis.com
In reply to: Greg Stark (#192)
Re: operator exclusion constraints

On Mon, 2009-11-09 at 18:03 +0000, Greg Stark wrote:

Out of curiosity, is this feature at all similar to SQL assertions?
What would we be missing to turn this into them?

I addressed that here:

http://archives.postgresql.org/pgsql-hackers/2009-11/msg00049.php

The exclusion constraint mechanism can enforce a subset of the
constraints that ASSERT can express; although the same goes for all
other constraints, because ASSERT is very general.

The exclusion constraint mechanism requires finding the physical tuples
that cause a conflict, so that we know when to wait and on which
transaction to wait. Otherwise, we have to wait on all transactions;
i.e. serialize.

The problem with ASSERT is that it expresses a constraint based on a
query, which can return arbitrary logical records after an arbitrary
amount of manipulation. So there's no way to work backwards. If we try,
we'll end up either:
(a) supporting only a tiny subset, and throwing bizarre errors that
users don't understand when they try to work outside the template; or
(b) deciding to serialize when we can't do better, and again, users
will be confused about the performance and locking characteristics.

Regards,
Jeff Davis

#196Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#188)
Re: operator exclusion constraints

On Sun, Nov 8, 2009 at 4:41 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Sat, 2009-11-07 at 10:56 -0800, Jeff Davis wrote:

EXCLUDE probably flows most nicely with the optional USING clause or
without. My only complaint was that it's a transitive verb, so it seems
to impart more meaning than it actually can. I doubt anyone would
actually be more confused in practice, though. If a couple of people
agree, I'll change it to EXCLUDE.

It looks like EXCLUDE is the winner. Updated patch attached.

The feature is still called "operator exclusion constraints", and the
docs still make reference to that name, but the syntax specification has
been updated.

[ reviewing ]

First thoughts:

I think the create_table documentation gets into a little too much
gorey detail. I'm willing to take a pass at improving it, if you'd
like, but generally I think it should avoid discussion of
implementation details. For example, saying that it's not as fast as
a UNIQUE constraint is good; the fact that an extra index lookup is
involved is probably overkill. Incidentally, the wording in the
first paragraph fails to take into account the possibility that there
are multiple operators.

In index_create(), the elog() where relopxconstraints < 0 should just
complain about the value being negative, I think, rather than listing
the value. If you just say the value is -3, it doesn't give the user
a clue why that's bad.

There is a spurious diff hunk for reindex_relation().

In ATRewriteTable() you reindent a bunch of variable declarations;
pg_indent will undo this, so you should nix this part.

In ATAddOperatorExclusionConstraint(), the message "method %s does not
support gettuple" seems a bit user-unfriendly. Can we explain the
problem by referring to the functionality of getttuple(), rather than
the name of it?

I don't really like this message, for a number of reasons.

alter table foo add constraint bar exclude (a check with =, b check with =);
ERROR: operator exclusion constraint violation detected: "foo_a_exclusion"
DETAIL: Tuple "(1, 1, 2)" conflicts with existing tuple "(1, 1, 3)".

The corresponding error for a UNIQUE index is: could not create unique
index "bar", which I like better. Only the relevant columns from the
tuples are dumped, and the tuple is not surrounded by double quotes;
any reason not to parallel that here? Also, the message is all
lower-case. Similarly, for an insert/update situation, it seems that
a message like "key value violates exclusion constraint \"%s\"" would
be better than the existing message.

alter table X add constraint Y exclude (...) seems to ignore the value
of Y and create both the constraint and the index with a name of its
own choosing.

As a quick performance test, I inserted a million 3-integer tuples
into a 3-column table with a unique constraint or an operator
exclusion constraint over all three columns. The former took ~ 15 s,
the latter ~ 21.5 s. That seems acceptable.

I think preprocessOpExConstraints should be called
transformOpxConstraints - opx rather than opex because that's what you
most frequently use elsewhere in the patch, and transform rather than
preprocess for consistency with other, similar functions.

In ruleutils.c, the prototype for pg_get_opxdef_worker() has a small
whitespace inconsistency relative to the surrounding declarations.

I haven't really grokked the substantive things that this patch is
doing yet - these are just preliminary comments based on a quick
read-through. I'll write more after I have a chance to look at it in
more detail.

...Robert

#197David E. Wheeler
david@kineticode.com
In reply to: Robert Haas (#196)
Re: operator exclusion constraints

On Nov 13, 2009, at 8:39 PM, Robert Haas wrote:

alter table foo add constraint bar exclude (a check with =, b check with =);

I've been meaning to comment on this syntax one more time; apologies for the bike-shedding. But I'm wondering if the "CHECK" is strictly necessary there, since the WITH seems adequate, and there was some discussion before about the CHECK keyword possibly causing confusion with check constraints.

Best,

David

#198Tom Lane
tgl@sss.pgh.pa.us
In reply to: David E. Wheeler (#197)
Re: operator exclusion constraints

"David E. Wheeler" <david@kineticode.com> writes:

On Nov 13, 2009, at 8:39 PM, Robert Haas wrote:

alter table foo add constraint bar exclude (a check with =, b check with =);

I've been meaning to comment on this syntax one more time; apologies for the bike-shedding. But I'm wondering if the "CHECK" is strictly necessary there, since the WITH seems adequate, and there was some discussion before about the CHECK keyword possibly causing confusion with check constraints.

I had been manfully restraining myself from re-opening this discussion,
but yeah I was thinking the same thing. The original objection to using
just WITH was that it wasn't very clear what you were doing "with" the
operator; but that was back when we had a different initial keyword for
the construct. EXCLUDE ... WITH ... seems to match up pretty naturally.

regards, tom lane

#199David E. Wheeler
david@kineticode.com
In reply to: Tom Lane (#198)
Re: operator exclusion constraints

On Nov 14, 2009, at 8:55 AM, Tom Lane wrote:

I've been meaning to comment on this syntax one more time; apologies for the bike-shedding. But I'm wondering if the "CHECK" is strictly necessary there, since the WITH seems adequate, and there was some discussion before about the CHECK keyword possibly causing confusion with check constraints.

I had been manfully restraining myself from re-opening this discussion,
but yeah I was thinking the same thing. The original objection to using
just WITH was that it wasn't very clear what you were doing "with" the
operator; but that was back when we had a different initial keyword for
the construct. EXCLUDE ... WITH ... seems to match up pretty naturally.

You're more man than I, Tom, but yeah, with EXCLUDE, WITH works well on its own, methinks.

Best,

David

#200Robert Haas
robertmhaas@gmail.com
In reply to: David E. Wheeler (#199)
Re: operator exclusion constraints

On Sat, Nov 14, 2009 at 12:11 PM, David E. Wheeler <david@kineticode.com> wrote:

On Nov 14, 2009, at 8:55 AM, Tom Lane wrote:

I've been meaning to comment on this syntax one more time; apologies for the bike-shedding. But I'm wondering if the "CHECK" is strictly necessary there, since the WITH seems adequate, and there was some discussion before about the CHECK keyword possibly causing confusion with check constraints.

I had been manfully restraining myself from re-opening this discussion,
but yeah I was thinking the same thing.  The original objection to using
just WITH was that it wasn't very clear what you were doing "with" the
operator; but that was back when we had a different initial keyword for
the construct.  EXCLUDE ... WITH ... seems to match up pretty naturally.

You're more man than I, Tom, but yeah, with EXCLUDE, WITH works well on its own, methinks.

I haven't thought about this too deeply, but could we allow the "with
=" part to be optional? And would it be a good idea? Seems like you
would commonly have one or more keys that exclude on equality and then
the last one would use an overlap-type operator.

...Robert

#201Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#196)
Re: operator exclusion constraints

On Fri, 2009-11-13 at 23:39 -0500, Robert Haas wrote:

[ reviewing ]

Thank you for the comments so far.

In index_create(), the elog() where relopxconstraints < 0 should just
complain about the value being negative, I think, rather than listing
the value. If you just say the value is -3, it doesn't give the user
a clue why that's bad.

Hopefully the user never sees that message -- it's almost an Assert.
PostgreSQL uses elog(ERROR,...) in many places that should be
unreachable, but might happen due to bugs in distant places or
corruption. I'm not sure the exact convention there, but I figure that
some details are appropriate.

I tried to make all user-visible errors into ereport()s.

In ATAddOperatorExclusionConstraint(), the message "method %s does not
support gettuple" seems a bit user-unfriendly. Can we explain the
problem by referring to the functionality of getttuple(), rather than
the name of it?

How about "operator exclusion constraints don't support method X"? Then
perhaps have a detail-level message to explain that the access method
needs to support the gettuple interface.

Trying to describe what gettuple does doesn't help a humble user much.
All they care about is "can't use gin".

I don't really like this message, for a number of reasons.

alter table foo add constraint bar exclude (a check with =, b check with =);
ERROR: operator exclusion constraint violation detected: "foo_a_exclusion"
DETAIL: Tuple "(1, 1, 2)" conflicts with existing tuple "(1, 1, 3)".

The corresponding error for a UNIQUE index is: could not create unique
index "bar", which I like better. Only the relevant columns from the
tuples are dumped, and the tuple is not surrounded by double quotes;
any reason not to parallel that here?

By "relevant columns" I assume you mean the entire index tuple. That
means we need to have column names represented somewhere, because we
don't want the user to have to match up ordinal index columns.

Also, with exclusion constraints, both values are always relevant, not
just the one being inserted. What if the user just wants to adjust their
request slightly to avoid an overlap -- how do they know how far to go?
I know this could be accomplished with extra queries, as well, but that
doesn't always work for someone looking through the logs after the fact,
when the original values may be gone.

So, the kind of error message you're suggesting starts to get awkward:
(a: 1 = 1, b: 1 = 1)

or something? And then with more complex type output functions, and
expression indexes, it starts to look very complex.

What do you think is the cleanest approach?

Also, the message is all
lower-case.

I know the error conventions are documented somewhere, but I completely
forgot where. Can you please point me to the right place? I thought most
error messages were supposed to be lower case, and detail messages were
supposed to read like sentences.

Similarly, for an insert/update situation, it seems that
a message like "key value violates exclusion constraint \"%s\"" would
be better than the existing message.

I can certainly simplify it, but I was trying to match the usefulness of
UNIQUE constraint error messages.

As a quick performance test, I inserted a million 3-integer tuples
into a 3-column table with a unique constraint or an operator
exclusion constraint over all three columns. The former took ~ 15 s,
the latter ~ 21.5 s. That seems acceptable.

Great news. I had similar results, though they depend on the conflict
percentage as well (I assume you had zero conflicts).

Regards,
Jeff Davis

#202Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#200)
Re: operator exclusion constraints

Robert Haas <robertmhaas@gmail.com> writes:

I haven't thought about this too deeply, but could we allow the "with
=" part to be optional? And would it be a good idea?

I don't think so. We generally do not believe in defaulting operators
based on name. If there were a way to select the "standard" exclusion
operator based on opclass membership it might make sense, but almost by
definition this facility is going to be working with unusual opclasses
that might not even have an equality slot.

regards, tom lane

#203Brendan Jurd
direvus@gmail.com
In reply to: Jeff Davis (#201)
Re: operator exclusion constraints

2009/11/15 Jeff Davis <pgsql@j-davis.com>:

I know the error conventions are documented somewhere, but I completely
forgot where. Can you please point me to the right place? I thought most
error messages were supposed to be lower case, and detail messages were
supposed to read like sentences.

http://www.postgresql.org/docs/current/static/error-style-guide.html

And you are correct.

Cheers,
BJ

#204Greg Stark
gsstark@mit.edu
In reply to: Jeff Davis (#201)
Re: operator exclusion constraints

On Sat, Nov 14, 2009 at 6:00 PM, Jeff Davis <pgsql@j-davis.com> wrote:

Hopefully the user never sees that message -- it's almost an Assert.
PostgreSQL uses elog(ERROR,...) in many places that should be
unreachable, but might happen due to bugs in distant places or
corruption. I'm not sure the exact convention there, but I figure that
some details are appropriate.

Yeah, I think that's right. I think part of the rationale is that if
the admin mucks around with catalog tables or does some DDL with
inconsistent definitions (like an operator class that isn't internally
consistent for example) then we don't treat those errors as
user-visible errors that need to be translated but they shouldn't
cause a database crash either.

If it's possible for the case to arrive through users doing things
through entirely supported means then they might need to be real
ereports(). But I guess there are grey areas.

--
greg

#205Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#196)
2 attachment(s)
Re: operator exclusion constraints

New patches attached. You may find it easiest to follow the changes I'm
making through my git repo:

http://git.postgresql.org/gitweb?p=users/jdavis/postgres.git;a=shortlog;h=refs/heads/operator-exclusion-constraints

Note, the attached patches also changed CHECK WITH to just WITH, as
concluded in this discussion:

http://archives.postgresql.org/pgsql-hackers/2009-11/msg00785.php

On Fri, 2009-11-13 at 23:39 -0500, Robert Haas wrote:

I think the create_table documentation gets into a little too much
gorey detail. I'm willing to take a pass at improving it, if you'd
like, but generally I think it should avoid discussion of
implementation details. For example, saying that it's not as fast as
a UNIQUE constraint is good; the fact that an extra index lookup is
involved is probably overkill. Incidentally, the wording in the
first paragraph fails to take into account the possibility that there
are multiple operators.

Fixed. Of course, I welcome any further revisions you have.

There is a spurious diff hunk for reindex_relation().

Fixed.

In ATRewriteTable() you reindent a bunch of variable declarations;
pg_indent will undo this, so you should nix this part.

Fixed.

In ATAddOperatorExclusionConstraint(), the message "method %s does not
support gettuple" seems a bit user-unfriendly. Can we explain the
problem by referring to the functionality of getttuple(), rather than
the name of it?

Now it looks like:

ERROR: method "gin" does not support operator exclusion constraints
DETAIL: The index access method must support the gettuple() interface
to be used with an operator exclusion constraint.

Hopefully that is an improvement.

alter table X add constraint Y exclude (...) seems to ignore the value
of Y and create both the constraint and the index with a name of its
own choosing.

Bug, and fixed.

I think preprocessOpExConstraints should be called
transformOpxConstraints - opx rather than opex because that's what you
most frequently use elsewhere in the patch, and transform rather than
preprocess for consistency with other, similar functions.

Fixed.

In ruleutils.c, the prototype for pg_get_opxdef_worker() has a small
whitespace inconsistency relative to the surrounding declarations.

Fixed.

Thanks,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091114.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091114.context.patchDownload
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,63 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
  </synopsis>
  
   </refsynopsisdiv>
--- 51,69 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">index_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
+ 
+ <phrase>and <replaceable class="PARAMETER">index_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+ 
+ { column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+ 
  </synopsis>
  
   </refsynopsisdiv>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 553,619 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">index_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>EXCLUDE</> clause specifies an operator exclusion
+       constraint. An operator exclusion constraint is more general
+       than a <literal>UNIQUE</literal> constraint, and can use
+       arbitrary operators to detect conflicts. For instance, you can
+       specify the constraint that no two tuples in the table contain
+       overlapping circles (see <xref linkend="datatype-geometric">) by
+       using the <literal>&&</literal> operator.
+      </para>
+ 
+      <para>
+       The constraint specifies the conflict condition, so the operator
+       should return <literal>TRUE</literal> when applied to two
+       conflicting values. Also, the operator specified must be
+       commutative (that is, the commutator of the operator must be the
+       operator itself), must be a boolean operator, and must be
+       associated with an operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) using
+       <replaceable class="parameter">index_method</replaceable>. The
+       constraint is violated if, and only if, there exist two tuples
+       where all corresponding expressions between the tuples conflict
+       according
+       to <replaceable class="parameter">operator</replaceable>
+       (i.e. the operator returns <literal>TRUE</literal>).
+      </para>
+ 
+      <para>
+       Internally, operator exclusion constraints use an index to
+       perform a search looking for conflicting values, and handle
+       concurrent operations similar to a <literal>UNIQUE</literal>
+       constraint. If all of the operators are specified as the
+       equality operator (usually <literal>=</literal>), this
+       constraint behaves identically to a <literal>UNIQUE</literal>
+       constraint. However, it may exhibit slightly worse performance
+       than specifying <literal>UNIQUE</literal>. The advantage of
+       operator exclusion constraints is the ability to specify more
+       general constraints (like a non-overlapping constraint for
+       circles), and also the ability to use index methods other
+       than <literal>btree</literal>, such as <literal>GiST</literal>
+       (see <xref linkend="GiST">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">index_parameters</replaceable>
+       are the same as for a <literal>UNIQUE</literal>
+       constraint. The <replaceable class="parameter">predicate</replaceable>
+       allows you to specify the constraint on a subset of the table
+       (note the reqiuired parentheses around the predicate
+       expression), internally using a partial index
+       (see <xref linkend="SQL-CREATEINDEX">). The <replaceable class="parameter">index_element</replaceable>
+       is normally just a column name, but can also be an expression or
+       function call and the constraint will check the result (similar
+       to creating a unique index over an expression); and it can
+       contain indexing information such as the operator class.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1111,1116 **** CREATE TABLE cinemas (
--- 1178,1195 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUDE USING gist (c WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1109 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraint = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraint != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraint,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 803,808 **** index_create(Oid heapRelationId,
--- 811,874 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1082,1087 **** BuildIndexInfo(Relation index)
--- 1148,1157 ----
  	/* other info */
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 		ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ 	else
+ 		ii->ii_ExclusionConstraint = NULL;
  
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
***************
*** 1893,1898 **** IndexBuildHeapScan(Relation heapRelation,
--- 1963,1971 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't checked at index build time */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2267,2272 **** validate_index_heapscan(Relation heapRelation,
--- 2340,2351 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/*
+ 	 * Operator exclusion constraints aren't supported for concurrent
+ 	 * index builds.
+ 	 */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
  }
  
  
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *constrategiesArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ 		constrategiesArray = construct_array(strategyDatums,
+ 											 constraintNKeys,
+ 											 INT2OID,
+ 											 sizeof(int16), true, 's');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (constrategiesArray)
+ 		values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ 	else
+ 		nulls[Anum_pg_constraint_constrategies - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 548,555 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 564,583 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,252 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
--- 40,46 ----
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext = NULL;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL ||
! 		indexInfo->ii_ExclusionConstraint != NULL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 150,168 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraint == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_check_constraint(trigdata->tg_relation, indexRel,
! 							   slot, &(new_row->t_self), values, isnull,
! 							   indexInfo->ii_ExclusionConstraint,
! 							   indexInfo->ii_ExpressionsState,
! 							   econtext, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 423,428 **** DefineIndex(RangeVar *heapRelation,
--- 433,439 ----
  	indexInfo->ii_ReadyForInserts = !concurrent;
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
+ 	indexInfo->ii_ExclusionConstraint = exclusion_constraint;
  
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 446,469 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type = NULL;
+ 
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUDE";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 479,485 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 774,781 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 965,971 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUDE */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  	CommandId	mycid;
  	BulkInsertState bistate;
  	int			hi_options;
***************
*** 3103,3108 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3109,3117 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3111,3116 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3120,3153 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3155,3160 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3192,3199 ----
  		MemoryContext oldCxt;
  		List	   *dropped_attrs = NIL;
  		ListCell   *lc;
+ 		Datum	   *idxvals = NULL;
+ 		bool	   *idxnulls = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3173,3178 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3212,3226 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3198,3203 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3246,3252 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3268,3273 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3317,3323 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3276,3281 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3326,3351 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_check_constraint(oldrel, indexRelation, newslot,
+ 									   &tuple->t_self, idxvals, idxnulls,
+ 									   indexInfo->ii_ExclusionConstraint,
+ 									   indexInfo->ii_ExpressionsState,
+ 									   econtext, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				heap_insert(newrel, tuple, mycid, hi_options, bistate);
***************
*** 3290,3295 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3360,3378 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 4603,4608 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4686,4692 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4666,4671 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4750,4775 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5035,5040 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5139,5145 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5071,5076 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5176,5343 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	int16		*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support operator "
+ 							   "exclusion constraints",
+ 							   constraint->using_method),
+ 						errdetail("The index access method must support the "
+ 								  "gettuple() interface to be used with an "
+ 								  "operator exclusion constraint.")));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(int16) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = LookupOperName(NULL, opname, typoid, typoid, false, -1);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", quote_identifier(get_opname(opid))),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			elog(ERROR, "no strategy found for operator %d "
+ 				 "in operator family %d", opid, opfamily);
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							constraint->conname,	/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,68 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 ExprContext *econtext, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1010,1016 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1017,1023 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1075,1081 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1082,1088 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1083,1089 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1090,1118 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraint != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_check_constraint(heapRelation, indexRelation,
! 									   slot, tupleid, values, isnull,
! 									   indexInfo->ii_ExclusionConstraint,
! 									   indexInfo->ii_ExpressionsState,
! 									   econtext, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraint != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1217,1219 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1246,1496 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ 					   ItemPointer tupleid, Datum *values, bool *isnull,
+ 					   int16 *exclusion_constraint, List *index_exprs,
+ 					   ExprContext *econtext, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	Oid					*constr_procs;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	for (i = 0; i < index_natts; i++)
+ 		if (isnull[i])
+ 			return true;
+ 
+ 	/*
+ 	 * Find the function that tests for a conflict based on the
+ 	 * strategy number, operator family, and types.
+ 	 */
+ 	constr_procs = palloc(sizeof(Oid) * index_natts);
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		/*
+ 		 * Find the procedure implementing the strategy for the
+ 		 * index for two arguments both with the type of the
+ 		 * indexed attribute.
+ 		 */
+ 		Oid				oper;
+ 		Oid				opfamily = index->rd_opfamily[i];
+ 		Oid				typoid = index->rd_opcintype[i];
+ 		StrategyNumber	strategy = exclusion_constraint[i];
+ 
+ 		if (strategy == InvalidStrategy)
+ 			continue;
+ 
+ 		oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+ 
+ 		if(OidIsValid(oper))
+ 			constr_procs[i] = get_opcode(oper);
+ 		else
+ 			elog(ERROR, "cannot determine operator for type %d and "
+ 				 "strategy %d", typoid, strategy);
+ 	}
+ 
+ 	/*
+ 	 * Now search the tuples that are actually in the index for
+ 	 * any violations.
+ 	 */
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			continue;
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 
+ 	/*
+ 	 * We have to find all tuples, even those not visible yet.
+ 	 */
+ 	InitDirtySnapshot(DirtySnapshot);
+ 
+ retry:
+ 	found_self = false;
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		TransactionId xwait;
+ 
+ 		if(ItemPointerEquals(tupleid, &tup->t_self))
+ 		{
+ 			Assert(!found_self);
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, econtext, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		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
+ 		 * restart the scan.
+ 		 */
+ 		xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ 			DirtySnapshot.xmin : DirtySnapshot.xmax;
+ 
+ 		if (TransactionIdIsValid(xwait))
+ 		{
+ 			index_endscan(index_scan);
+ 			XactLockTableWait(xwait);
+ 			goto retry;
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", tuple_as_string(new_slot),
+ 						   tuple_as_string(existing_slot))));
+ 	}
+ 
+ 	Assert(conflict || found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	pfree(constr_procs);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 ExprContext *econtext, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ 	TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+ 	StringInfoData		buf;
+ 	int					i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < tupdesc->natts; i++)
+ 	{
+ 		char	*strval;
+ 		Datum	 value;
+ 		bool	 isnull;
+ 
+ 		value = slot_getattr(slot, i + 1, &isnull);
+ 
+ 		if (isnull)
+ 			strval = "null";
+ 		else
+ 		{
+ 			Oid		foutoid;
+ 			bool	typisvarlena;
+ 
+ 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ 							  &typisvarlena);
+ 			strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, strval);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	return buf.data;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2159,2164 **** _copyConstraint(Constraint *from)
--- 2159,2167 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2107,2112 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2107,2115 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2396,2401 **** _outConstraint(StringInfo str, Constraint *node)
--- 2396,2409 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 350,355 **** static TypeName *TableFuncTypeName(List *columns);
--- 350,356 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 430,435 **** static TypeName *TableFuncTypeName(List *columns);
--- 431,437 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 473,479 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 475,481 ----
  	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
! 	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
***************
*** 1589,1596 **** alter_table_cmds:
  		;
  
  alter_table_cmd:
! 			/* ALTER TABLE <name> ADD [COLUMN] <coldef> */
! 			ADD_P opt_column columnDef
  				{
  					AlterTableCmd *n = makeNode(AlterTableCmd);
  					n->subtype = AT_AddColumn;
--- 1591,1606 ----
  		;
  
  alter_table_cmd:
! 			/* 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;
***************
*** 2502,2507 **** ConstraintElem:
--- 2512,2532 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2542,2547 **** key_match:  MATCH FULL
--- 2567,2589 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $3);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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
***************
*** 10571,10576 **** unreserved_keyword:
--- 10613,10619 ----
  			| ENCRYPTED
  			| ENUM_P
  			| ESCAPE
+ 			| EXCLUDE
  			| EXCLUDING
  			| EXCLUSIVE
  			| EXECUTE
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 71,76 **** typedef struct
--- 71,77 ----
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
  	List	   *ixconstraints;	/* index-creating constraints */
+ 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void transformOpxConstraints(ParseState *pstate,
+ 									CreateStmtContext *cxt,
+ 									RangeVar *relation,
+ 									Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 175,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 182,188 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 734,739 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 782,793 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 872,877 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 926,941 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1842,1847 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1906,1912 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1889,1894 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1954,1962 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					transformOpxConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1947,1953 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2015,2024 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1962,1967 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2033,2045 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2254,2256 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2332,2385 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ transformOpxConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 148,153 **** static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
--- 148,155 ----
  					   int prettyFlags);
  static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  							int prettyFlags);
+ static char *pg_get_opxdef_worker(Oid indexrelid, uint16 *strategies,
+ 							int prettyFlags);
  static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
  				   int prettyFlags);
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
***************
*** 1193,1198 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1195,1231 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				bool	 isnull;
+ 				Oid		 indexOid = conForm->conindid;
+ 				Datum	 val;
+ 				Datum	*keys;
+ 				int		 nKeys;
+ 				int		 i;
+ 				uint16	*strategies;
+ 
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_constrategies,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null constrategies for constraint %u",
+ 						 constraintId);
+ 
+ 				deconstruct_array(DatumGetArrayTypeP(val),
+ 								  INT2OID, 2, true, 's',
+ 								  &keys, NULL, &nKeys);
+ 
+ 				strategies = palloc(nKeys * sizeof(uint16));
+ 				for(i = 0; i < nKeys; i++)
+ 					strategies[i] = DatumGetInt16(keys[i]);
+ 
+ 				appendStringInfo(&buf, pg_get_opxdef_worker(indexOid,
+ 															strategies,
+ 															prettyFlags));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1240,1245 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1273,1510 ----
  	}
  }
  
+ static char *
+ pg_get_opxdef_worker(Oid indexrelid, uint16_t *strategies, int prettyFlags)
+ {
+ 	HeapTuple	ht_idx;
+ 	HeapTuple	ht_idxrel;
+ 	HeapTuple	ht_am;
+ 	Form_pg_index idxrec;
+ 	Form_pg_class idxrelrec;
+ 	Form_pg_am	amrec;
+ 	List	   *indexprs;
+ 	ListCell   *indexpr_item;
+ 	List	   *context;
+ 	Oid			indrelid;
+ 	int			keyno;
+ 	Oid			keycoltype;
+ 	Datum		indclassDatum;
+ 	Datum		indoptionDatum;
+ 	bool		isnull;
+ 	oidvector  *indclass;
+ 	int2vector *indoption;
+ 	StringInfoData buf;
+ 	char	   *str;
+ 	char	   *sep;
+ 	Oid			tblspc;
+ 
+ 	/*
+ 	 * Fetch the pg_index tuple by the Oid of the index
+ 	 */
+ 	ht_idx = SearchSysCache(INDEXRELID,
+ 							ObjectIdGetDatum(indexrelid),
+ 							0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idx))
+ 		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ 	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+ 
+ 	indrelid = idxrec->indrelid;
+ 	Assert(indexrelid == idxrec->indexrelid);
+ 
+ 	/* Must get indclass and indoption the hard way */
+ 	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indclass, &isnull);
+ 	Assert(!isnull);
+ 	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+ 	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indoption, &isnull);
+ 	Assert(!isnull);
+ 	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ 
+ 	/*
+ 	 * Fetch the pg_class tuple of the index relation
+ 	 */
+ 	ht_idxrel = SearchSysCache(RELOID,
+ 							   ObjectIdGetDatum(indexrelid),
+ 							   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idxrel))
+ 		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+ 	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+ 
+ 	/*
+ 	 * Fetch the pg_am tuple of the index' access method
+ 	 */
+ 	ht_am = SearchSysCache(AMOID,
+ 						   ObjectIdGetDatum(idxrelrec->relam),
+ 						   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_am))
+ 		elog(ERROR, "cache lookup failed for access method %u",
+ 			 idxrelrec->relam);
+ 	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+ 
+ 	/*
+ 	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+ 	 * versions of the expressions and predicate, because we want to display
+ 	 * non-const-folded expressions.)
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+ 	{
+ 		Datum		exprsDatum;
+ 		bool		isnull;
+ 		char	   *exprsString;
+ 
+ 		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indexprs, &isnull);
+ 		Assert(!isnull);
+ 		exprsString = TextDatumGetCString(exprsDatum);
+ 		indexprs = (List *) stringToNode(exprsString);
+ 		pfree(exprsString);
+ 	}
+ 	else
+ 		indexprs = NIL;
+ 
+ 	indexpr_item = list_head(indexprs);
+ 
+ 	context = deparse_context_for(get_rel_name(indrelid), indrelid);
+ 
+ 	/*
+ 	 * Start the index definition.	Note that the index's name should never be
+ 	 * schema-qualified, but the indexed rel's name may be.
+ 	 */
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "EXCLUDE USING %s (",
+ 					 quote_identifier(NameStr(amrec->amname)));
+ 
+ 	/*
+ 	 * Report the indexed attributes
+ 	 */
+ 	sep = "";
+ 	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ 	{
+ 		AttrNumber	attnum = idxrec->indkey.values[keyno];
+ 		int16		opt = indoption->values[keyno];
+ 		Oid			opfamily = get_opclass_family(indclass->values[keyno]);
+ 		Oid			opid;
+ 		char	   *opName;
+ 
+ 		appendStringInfoString(&buf, sep);
+ 		sep = ", ";
+ 
+ 		if (attnum != 0)
+ 		{
+ 			/* Simple index column */
+ 			char	   *attname;
+ 
+ 			attname = get_relid_attribute_name(indrelid, attnum);
+ 			appendStringInfoString(&buf, quote_identifier(attname));
+ 			keycoltype = get_atttype(indrelid, attnum);
+ 		}
+ 		else
+ 		{
+ 			/* expressional index */
+ 			Node	   *indexkey;
+ 
+ 			if (indexpr_item == NULL)
+ 				elog(ERROR, "too few entries in indexprs list");
+ 			indexkey = (Node *) lfirst(indexpr_item);
+ 			indexpr_item = lnext(indexpr_item);
+ 			/* Deparse */
+ 			str = deparse_expression_pretty(indexkey, context, false, false,
+ 											prettyFlags, 0);
+ 
+ 			/* Need parens if it's not a bare function call */
+ 			if (indexkey && IsA(indexkey, FuncExpr) &&
+ 				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+ 				appendStringInfoString(&buf, str);
+ 			else
+ 				appendStringInfo(&buf, "(%s)", str);
+ 
+ 			keycoltype = exprType(indexkey);
+ 		}
+ 
+ 		/* Add the operator class name, if not default */
+ 		get_opclass_name(indclass->values[keyno], keycoltype, &buf);
+ 
+ 		/* Add options if relevant */
+ 		if (amrec->amcanorder)
+ 		{
+ 			/* if it supports sort ordering, report DESC and NULLS opts */
+ 			if (opt & INDOPTION_DESC)
+ 			{
+ 				appendStringInfo(&buf, " DESC");
+ 				/* NULLS FIRST is the default in this case */
+ 				if (!(opt & INDOPTION_NULLS_FIRST))
+ 					appendStringInfo(&buf, " NULLS LAST");
+ 			}
+ 			else
+ 			{
+ 				if (opt & INDOPTION_NULLS_FIRST)
+ 					appendStringInfo(&buf, " NULLS FIRST");
+ 			}
+ 		}
+ 
+ 		/* Add operator exclusion constraint */
+ 		appendStringInfo(&buf, " WITH ");
+ 
+ 		opid = get_opfamily_member(opfamily, keycoltype, keycoltype,
+ 								   strategies[keyno]);
+ 		opName = generate_operator_name(opid, keycoltype, keycoltype);
+ 
+ 		appendStringInfo(&buf, "%s", opName);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	/*
+ 	 * If it has options, append "WITH (options)"
+ 	 */
+ 	str = flatten_reloptions(indexrelid);
+ 	if (str)
+ 	{
+ 		appendStringInfo(&buf, " WITH (%s)", str);
+ 		pfree(str);
+ 	}
+ 
+ 	/*
+ 	 * If it's in a nondefault tablespace, say so, but only if requested
+ 	 */
+ 	tblspc = get_rel_tablespace(indexrelid);
+ 	if (OidIsValid(tblspc))
+ 		appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
+ 						 quote_identifier(get_tablespace_name(tblspc)));
+ 
+ 	/*
+ 	 * If it's a partial index, decompile and append the predicate
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+ 	{
+ 		Node	   *node;
+ 		Datum		predDatum;
+ 		bool		isnull;
+ 		char	   *predString;
+ 
+ 		/* Convert text string to node tree */
+ 		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indpred, &isnull);
+ 		Assert(!isnull);
+ 		predString = TextDatumGetCString(predDatum);
+ 		node = (Node *) stringToNode(predString);
+ 		pfree(predString);
+ 
+ 		/* Deparse */
+ 		str = deparse_expression_pretty(node, context, false, false,
+ 										prettyFlags, 0);
+ 		appendStringInfo(&buf, " WHERE (%s)", str);
+ 	}
+ 
+ 	/* Clean up */
+ 	ReleaseSysCache(ht_idx);
+ 	ReleaseSysCache(ht_idxrel);
+ 	ReleaseSysCache(ht_am);
+ 
+ 	return buf.data;
+ }
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3039,3119 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ 	Relation	conrel;
+ 	SysScanDesc conscan;
+ 	ScanKeyData skey[1];
+ 	HeapTuple	htup;
+ 	Datum		val;
+ 	bool		isnull;
+ 	bool		found = false;
+ 	int16	   *constraints = NULL;
+ 	Oid			relid = indexRelation->rd_index->indrelid;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_constrategies,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null constrategies for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != INT2OID)
+ 			elog(ERROR, "constrategies is not a 1-D smallint array");
+ 		constraints = palloc(sizeof(int16) * nelem);
+ 		memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	return constraints;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2855,2860 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 2855,2861 ----
  				 strcmp(te->desc, "CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "DEFAULT") == 0 ||
  				 strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ 				 strcmp(te->desc, "EXCLUSION CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "INDEX") == 0 ||
  				 strcmp(te->desc, "RULE") == 0 ||
  				 strcmp(te->desc, "TRIGGER") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 3680,3685 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3680,3686 ----
  				i_condeferred,
  				i_contableoid,
  				i_conoid,
+ 				i_condef,
  				i_tablespace,
  				i_options;
  	int			ntups;
***************
*** 3710,3716 **** getIndexes(TableInfo tblinfo[], int numTables)
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80200)
  		{
  			appendPQExpBuffer(query,
  							  "SELECT t.tableoid, t.oid, "
--- 3711,3745 ----
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			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, "
! 					 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
! 							  "c.oid AS conoid, "
! 							  "(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, "
***************
*** 3858,3863 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3887,3893 ----
  		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,3901 **** 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 we found a constraint matching the index, create an
--- 3925,3931 ----
  			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
  			contype = *(PQgetvalue(res, j, i_contype));
  
! 			if (contype == 'p' || contype == 'u' || contype == 'x')
  			{
  				/*
  				 * If we found a constraint matching the index, create an
***************
*** 3913,3919 **** getIndexes(TableInfo tblinfo[], int numTables)
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				constrinfo[j].condef = NULL;
  				constrinfo[j].confrelid = InvalidOid;
  				constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
  				constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
--- 3943,3952 ----
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				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';
***************
*** 10907,10912 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
--- 10940,10970 ----
  						 NULL, NULL);
  		}
  	}
+ 	else if (coninfo->contype == 'x')
+ 	{
+ 		appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(q, "    ADD CONSTRAINT %s %s;\n",
+ 						  fmtId(coninfo->dobj.name),
+ 						  coninfo->condef);
+ 
+ 		appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ 						  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 		appendPQExpBuffer(delq, "%s ",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ 						  fmtId(coninfo->dobj.name));
+ 
+ 		ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ 					 coninfo->dobj.name,
+ 					 tbinfo->dobj.namespace->dobj.name,
+ 					 NULL,
+ 					 tbinfo->rolname, false,
+ 					 "EXCLUSION CONSTRAINT", SECTION_POST_DATA,
+ 					 q->data, delq->data, NULL,
+ 					 coninfo->dobj.dependencies, coninfo->dobj.nDeps,
+ 					 NULL, NULL);
+ 	}
  	else
  	{
  		write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	int2		constrategies[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_constrategies	20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,338 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_check_constraint(Relation heap, Relation index,
+ 								   TupleTableSlot *new_slot,
+ 								   ItemPointer tupleid, Datum *values,
+ 								   bool *isnull, int16 *exclusion_constraint,
+ 								   List *index_exprs, ExprContext *econtext,
+ 								   bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	int16	   *ii_ExclusionConstraint;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1395,1400 **** typedef enum ConstrType			/* types of constraints */
--- 1395,1401 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1429,1439 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1430,1445 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 143,148 **** PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
--- 143,149 ----
  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)
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 43,49 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ 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>');
+ -- should fail
+ 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;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUDE USING gist
+     (c1 WITH &&, (c2::circle) WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Tuple "(<(20,20),10>, <(0,0), 5>)" conflicts with existing tuple "(<(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:  operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL:  Tuple "(<(0,0),5>, <(0,0), 5>)" conflicts with existing tuple "(<(0,0),5>, <(0,0), 5>)".
+ DROP TABLE circles;
operator-exclusion-constraints-20091114.patch.gzapplication/x-gzip; name=operator-exclusion-constraints-20091114.patch.gzDownload
#206Jeff Davis
pgsql@j-davis.com
In reply to: David E. Wheeler (#191)
Re: operator exclusion constraints

On Mon, 2009-11-09 at 09:12 -0800, David E. Wheeler wrote:

On Nov 8, 2009, at 7:43 PM, Jeff Davis wrote:

Either of those names are fine with me, too. The current name is a
somewhat shortened version of the name "operator-based exclusion
constraints", so we can also just use that name. Or, just "exclusion
constraints".

(exclusion constraints)++

Ok, I guess this is another issue that requires consensus.

Note: this is purely for documentation, release notes, and user-visible
error messages. This does not have any impact on the syntax, I think
we've got a strong consensus on that already and I would prefer not to
break that discussion open again.

1. Operator Exclusion Constraints (current)
2. Generic/Generalized/General Exclusion Constraints
3. Exclusion Constraints (has the potential to cause confusion with
constraint_exclusion)

Regards,
Jeff Davis

#207Jeff Davis
pgsql@j-davis.com
In reply to: David E. Wheeler (#199)
Re: operator exclusion constraints

On Sat, 2009-11-14 at 09:11 -0800, David E. Wheeler wrote:

On Nov 14, 2009, at 8:55 AM, Tom Lane wrote:

I had been manfully restraining myself from re-opening this discussion,
but yeah I was thinking the same thing. The original objection to using
just WITH was that it wasn't very clear what you were doing "with" the
operator; but that was back when we had a different initial keyword for
the construct. EXCLUDE ... WITH ... seems to match up pretty naturally.

You're more man than I, Tom, but yeah, with EXCLUDE, WITH works well on its own, methinks.

Changed in new patch here:

http://archives.postgresql.org/message-id/1258226849.708.97.camel@jdavis

Regards,
Jeff Davis

#208Robert Haas
robertmhaas@gmail.com
In reply to: Greg Stark (#204)
Re: operator exclusion constraints

On Sat, Nov 14, 2009 at 1:58 PM, Greg Stark <gsstark@mit.edu> wrote:

On Sat, Nov 14, 2009 at 6:00 PM, Jeff Davis <pgsql@j-davis.com> wrote:

Hopefully the user never sees that message -- it's almost an Assert.
PostgreSQL uses elog(ERROR,...) in many places that should be
unreachable, but might happen due to bugs in distant places or
corruption. I'm not sure the exact convention there, but I figure that
some details are appropriate.

Yeah, I think that's right. I think part of the rationale is that if
the admin mucks around with catalog tables or does some DDL with
inconsistent definitions (like an operator class that isn't internally
consistent for example) then we don't treat those errors as
user-visible errors that need to be translated but they shouldn't
cause a database crash either.

If it's possible for the case to arrive through users doing things
through entirely supported means then they might need to be real
ereports(). But I guess there are grey areas.

I guess my point wasn't that the message was likely to be exercised,
but rather that it isn't obvious that it's describing an error
condition at all. If you get this message:

relation "whatever" has relopxconstraints = -3

...you can't even tell that it's an error message unless it happens to
be preceded by "ERROR: ". If you get something like:

relation "whatever" has invalid relopxconstraints value (-3)

...it's much more clear that this is not just informational.

...Robert

#209Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#208)
Re: operator exclusion constraints

On Sat, 2009-11-14 at 14:36 -0500, Robert Haas wrote:

I guess my point wasn't that the message was likely to be exercised,
but rather that it isn't obvious that it's describing an error
condition at all. If you get this message:

relation "whatever" has relopxconstraints = -3

I pretty much copied similar code for relchecks, see
pg_constraint.c:570.

If you have a suggestion, I'll make the change. It doesn't sound too
urgent though, to me.

Regards,
Jeff Davis

#210Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#209)
Re: operator exclusion constraints

On Sat, Nov 14, 2009 at 2:39 PM, Jeff Davis <pgsql@j-davis.com> wrote:

If you have a suggestion, I'll make the change. It doesn't sound too
urgent though, to me.

Yeah, probably not.

...Robert

#211Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#205)
1 attachment(s)
Re: operator exclusion constraints

On Sat, Nov 14, 2009 at 2:27 PM, Jeff Davis <pgsql@j-davis.com> wrote:

New patches attached.

Forgive me if this is discussed before, but why does this store the
strategy numbers of the relevant operators instead of the operators
themselves? It seems like this could lead to surprising behavior if
the user modifies the definition of the operator class.

I'm wondering if we can't use the existing
BuildIndexValueDescription() rather than the new function
tuple_as_string(). I realize there are two tuples, but maybe it makes
sense to just call it twice?

I'm attaching a revised doc patch for your consideration.

...Robert

Attachments:

opx-doc.patchtext/x-diff; charset=US-ASCII; name=opx-doc.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 082dfe4..a73c015 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -51,10 +51,13 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
   PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
   CHECK ( <replaceable class="PARAMETER">expression</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> ] }
+    [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] |
+  EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ]
+    ( { <replaceable class="parameter">column</replaceable> | <replaceable class="parameter">column</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] WITH <replaceable class="parameter">operator</replaceable> [, ... ] )
+    <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</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><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> ]
@@ -547,6 +550,43 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    </varlistentry>
 
    <varlistentry>
+    <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ]
+      ( {<replaceable class="parameter">column</replaceable> | <replaceable class="parameter">column</replaceable> | (<replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] WITH <replaceable class="parameter">operator</replaceable> [, ... ] )
+      <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] }</literal></term>
+    <listitem>
+     <para>
+      The <literal>EXCLUDE</> clause specifies an operator exclusion
+      constraint.  An operator exclusion constraint guarantees that if any two
+      tuples are compared on the specified columns or expressions using the
+      specified operators, at least one such comparison will return false.
+      If all of the specified operators test for equality, it is equivalent
+      to a UNIQUE constraint, although an ordinary unique constraint will
+      normally be faster.  However, operator exclusion constraints can use
+      index methods other than btree, and can specify more general constraints.
+      For instance, you can specify the constraint that no two tuples in the
+      table contain overlapping circles
+      (see <xref linkend="datatype-geometric">) by using the
+      <literal>&&</literal> operator.
+     </para>
+
+     <para>
+      Operator exclusion constraints are implemented internally using
+      an index, so the specified operators must be associated with an
+      appropriate operator class for the given access method, and the
+      access method must support amgettuple (see <xref linkend="indexam">
+      for details).  The operators are also required to be their own
+      commutators (see <xref linkend="sql-createoperator">).
+     </para>
+
+     <para>
+      The <replaceable class="parameter">predicate</replaceable>
+      allows you to specify a constraint on a subset of the table,
+      internally using a partial index.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>DEFERRABLE</literal></term>
     <term><literal>NOT DEFERRABLE</literal></term>
     <listitem>
@@ -1111,6 +1151,18 @@ CREATE TABLE cinemas (
 </programlisting>
   </para>
 
+  <para>
+   Create table <structname>circles</> with an operator exclusion
+   constraint that prevents overlapping circles within it:
+
+<programlisting>
+CREATE TABLE circles (
+	c circle,
+	EXCLUDE USING gist (c WITH &&)
+);
+</programlisting>
+  </para>
+
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
#212Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#211)
Re: operator exclusion constraints

I'm in Tokyo right now, so please excuse my abbreviated reply.

On Tue, 2009-11-17 at 23:13 -0500, Robert Haas wrote:

Forgive me if this is discussed before, but why does this store the
strategy numbers of the relevant operators instead of the operators
themselves?

At constraint definition time, I need to make sure that the strategy
numbers can be identified anyway, so it wouldn't save any work in
ATAddOperatorExclusionConstraint. At the time it seemed slightly more
direct to use strategy numbers in index_check_constraint, but it's
probably about the same.

It seems like this could lead to surprising behavior if
the user modifies the definition of the operator class.

Right now, operator classes can't be modified in any meaningful way. Am
I missing something?

I'm wondering if we can't use the existing
BuildIndexValueDescription() rather than the new function
tuple_as_string(). I realize there are two tuples, but maybe it makes
sense to just call it twice?

Are you suggesting I change the error output, or reorganize the code to
try to reuse BuildIndexValueDescription, or both?

Regards,
Jeff Davis

#213Dimitri Fontaine
dfontaine@hi-media.com
In reply to: Robert Haas (#211)
Re: operator exclusion constraints

Robert Haas <robertmhaas@gmail.com> writes:

Forgive me if this is discussed before, but why does this store the
strategy numbers of the relevant operators instead of the operators
themselves? It seems like this could lead to surprising behavior if
the user modifies the definition of the operator class.

Wild guess:

http://www.postgresql.org/docs/8.4/static/xindex.html

34.14.2. Index Method Strategies

The operators associated with an operator class are identified by
"strategy numbers", which serve to identify the semantics of each
operator within the context of its operator class. For example,
B-trees impose a strict ordering on keys, lesser to greater, and so
operators like "less than" and "greater than or equal to" are
interesting with respect to a B-tree. Because PostgreSQL allows the
user to define operators, PostgreSQL cannot look at the name of an
operator (e.g., < or >=) and tell what kind of comparison it
is. Instead, the index method defines a set of "strategies", which can
be thought of as generalized operators. Each operator class specifies
which actual operator corresponds to each strategy for a particular
data type and interpretation of the index semantics.

Regards,
--
dim

#214Josh Berkus
josh@agliodbs.com
In reply to: Jeff Davis (#212)
Re: operator exclusion constraints

All,

FWIW, I'm doing a redesign of a client's production web application
right now. I was able, by combining OEC and the Period type from
pgfoundry, to make a set of constraints for declaratively asserting in a
sports database:

That the same player couldn't belong to two different teams at the same
time;
That the same player couldn't belong to the same team in two different
positions with overlapping time periods.

This worked as spec'd, and would be extremely useful for this real-world
app if it was ready to use in production now.

However, I do have an issue with the SQLSTATE returned from the OEC
violation. Currently it returns constraint violation, which, from the
perspective of an application developer, is not useful. OECs are, in
application terms, materially identical to UNIQUE constraints and serve
the same purpose. As such, I'd far rather see OECs return unique key
violation instead, as any existing application error-trapping code would
handle the violation more intelligently if it did.

--Josh Berkus

#215Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#212)
Re: operator exclusion constraints

On Wed, Nov 18, 2009 at 9:00 AM, Jeff Davis <pgsql@j-davis.com> wrote:

I'm in Tokyo right now, so please excuse my abbreviated reply.

On Tue, 2009-11-17 at 23:13 -0500, Robert Haas wrote:

Forgive me if this is discussed before, but why does this store the
strategy numbers of the relevant operators instead of the operators
themselves?

At constraint definition time, I need to make sure that the strategy
numbers can be identified anyway, so it wouldn't save any work in
ATAddOperatorExclusionConstraint. At the time it seemed slightly more
direct to use strategy numbers in index_check_constraint, but it's
probably about the same.

It sets off a red flag for me any time I see code that asks for A from
the user and then actually stores B under the hood, for fear that the
relationship that A and B might change. However...

It seems like this could lead to surprising behavior if
the user modifies the definition of the operator class.

Right now, operator classes can't be modified in any meaningful way. Am
I missing something?

...poking at it, I have to agree that at least as things stand right
now, I can't find a way to break it. Not sure if it's future-proof.

I'm wondering if we can't use the existing
BuildIndexValueDescription() rather than the new function
tuple_as_string().  I realize there are two tuples, but maybe it makes
sense to just call it twice?

Are you suggesting I change the error output, or reorganize the code to
try to reuse BuildIndexValueDescription, or both?

I was thinking maybe you call BuildIndexValueDescription twice and
make the error message say something like <output of first call>
conflicts with <output of second call>.

One other thing I noticed tonight while poking at this. If I install
contrib/citext, I can do this:

create table test (a citext, exclude using hash (a with =));

But if I install contrib/intarray, I can't do this:

create table test (a int4[], exclude using gist (a with =));
ERROR: operator does not exist: integer[] = integer[]

Not sure if I'm doing something wrong, or if this is a limitation of
the design, or if it's a bug, but it seems strange. I'm guessing it's
because intarray uses the anyarray operator rather than a dedicated
operator for int[], but it seems like that ought to work.

...Robert

#216Robert Haas
robertmhaas@gmail.com
In reply to: Josh Berkus (#214)
Re: operator exclusion constraints

On Wed, Nov 18, 2009 at 9:21 AM, Josh Berkus <josh@agliodbs.com> wrote:

All,

FWIW, I'm doing a redesign of a client's production web application
right now.  I was able, by combining OEC and the Period type from
pgfoundry, to make a set of constraints for declaratively asserting in a
sports database:

That the same player couldn't belong to two different teams at the same
time;
That the same player couldn't belong to the same team in two different
positions with overlapping time periods.

This worked as spec'd, and would be extremely useful for this real-world
app if it was ready to use in production now.

However, I do have an issue with the SQLSTATE returned from the OEC
violation.  Currently it returns constraint violation, which, from the
perspective of an application developer, is not useful.  OECs are, in
application terms, materially identical to UNIQUE constraints and serve
the same purpose.  As such, I'd far rather see OECs return unique key
violation instead, as any existing application error-trapping code would
handle the violation more intelligently if it did.

I guess I'm going to have to vote -1 on this proposal. I code see
inventing a pgsql-specific SQLSTATE value for exclusion constraints,
since they will be a pgsql-specific extension, but reusing the unique
key violation value seems misleading. I admit it may help in a
limited number of cases, but IMHO it's not worth the confusion.

That's just my $0.02, though.

...Robert

#217Josh Berkus
josh@agliodbs.com
In reply to: Robert Haas (#216)
Re: operator exclusion constraints

RObert,

I guess I'm going to have to vote -1 on this proposal. I code see
inventing a pgsql-specific SQLSTATE value for exclusion constraints,
since they will be a pgsql-specific extension, but reusing the unique
key violation value seems misleading. I admit it may help in a
limited number of cases, but IMHO it's not worth the confusion.

I'd rather have a new one than just using "contstraint violation" which
is terribly non-specific, and generally makes the application developer
think that a value is too large.

--Josh BErkus

#218David Fetter
david@fetter.org
In reply to: Josh Berkus (#217)
Re: operator exclusion constraints

On Fri, Nov 20, 2009 at 01:36:59PM +0900, Josh Berkus wrote:

RObert,

I guess I'm going to have to vote -1 on this proposal. I code see
inventing a pgsql-specific SQLSTATE value for exclusion constraints,
since they will be a pgsql-specific extension, but reusing the unique
key violation value seems misleading. I admit it may help in a
limited number of cases, but IMHO it's not worth the confusion.

I'd rather have a new one than just using "contstraint violation" which
is terribly non-specific, and generally makes the application developer
think that a value is too large.

What, if anything, does the standard have to say about violations of
ASSERTIONs? I know these aren't ASSERTIONs, but they much more
closely resemble them than they do UNIQUE constraints. For example,
if the operator is <> instead of =, a violation is actually the
opposite of a UNIQUE violation.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#219Peter Eisentraut
peter_e@gmx.net
In reply to: David Fetter (#218)
Re: operator exclusion constraints

On sön, 2009-11-22 at 16:03 -0800, David Fetter wrote:

What, if anything, does the standard have to say about violations of
ASSERTIONs? I know these aren't ASSERTIONs, but they much more
closely resemble them than they do UNIQUE constraints.

An assertion is by definition a constraint that is a schema component
independent of a table. Which an exclusion constraint may or may not
be, but it's an independent issue. (To clarify: It currently can't be,
because assertions are not implemented, but when they are, it could be.)
For the same reason, assertions don't have separate error codes, because
they are just constraints after all.

#220Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#215)
1 attachment(s)
Re: operator exclusion constraints

On Thu, 2009-11-19 at 22:55 -0500, Robert Haas wrote:

At constraint definition time, I need to make sure that the strategy
numbers can be identified anyway, so it wouldn't save any work in
ATAddOperatorExclusionConstraint. At the time it seemed slightly more
direct to use strategy numbers in index_check_constraint, but it's
probably about the same.

It sets off a red flag for me any time I see code that asks for A from
the user and then actually stores B under the hood, for fear that the
relationship that A and B might change. However...

Still not addressed because it touches a bit more code (including the
catalogs). I basically agree, however, so I'll take care of this soon.

I was thinking maybe you call BuildIndexValueDescription twice and
make the error message say something like <output of first call>
conflicts with <output of second call>.

Do you really think that's a better error message, or are you just
trying to re-use similar code?

Let's start from how the error message should read, and then see if we
can re-use some code to make it look that way. It's one of the most
visible aspects of the feature, and it needs to be reasonably concise
and understandable in the simple case, but contain all of the necessary
information.

I think it's better to avoid the "=" when describing the conflict. I
tend to read it as "equals" even though it's just punctuation in this
case, so it would be distracting. I could change it to a colon, I
suppose.

create table test (a int4[], exclude using gist (a with =));
ERROR: operator does not exist: integer[] = integer[]

Thanks, fixed. I am now using compatible_oper_opid(), which will find
any operators that don't require binary-incompatible coercion of the
operands.

Do you think there's any reason to support binary-incompatible coercion
of the operands? I can't think of a single use case, and if you really
need to, you can coerce the types explicitly in the expression.

Changes in attached patch:

* use compatible_oper_opid() to find operator from name
* Add new SQLSTATE 23P01 for the operator exclusion constraint error,
so that applications can more easily distinguish it from other
constraint violations.

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091124.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091124.context.patchDownload
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,63 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
  </synopsis>
  
   </refsynopsisdiv>
--- 51,69 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">index_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
+ 
+ <phrase>and <replaceable class="PARAMETER">index_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+ 
+ { column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+ 
  </synopsis>
  
   </refsynopsisdiv>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 553,619 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">index_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>EXCLUDE</> clause specifies an operator exclusion
+       constraint. An operator exclusion constraint is more general
+       than a <literal>UNIQUE</literal> constraint, and can use
+       arbitrary operators to detect conflicts. For instance, you can
+       specify the constraint that no two tuples in the table contain
+       overlapping circles (see <xref linkend="datatype-geometric">) by
+       using the <literal>&&</literal> operator.
+      </para>
+ 
+      <para>
+       The constraint specifies the conflict condition, so the operator
+       should return <literal>TRUE</literal> when applied to two
+       conflicting values. Also, the operator specified must be
+       commutative (that is, the commutator of the operator must be the
+       operator itself), must be a boolean operator, and must be
+       associated with an operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) using
+       <replaceable class="parameter">index_method</replaceable>. The
+       constraint is violated if, and only if, there exist two tuples
+       where all corresponding expressions between the tuples conflict
+       according
+       to <replaceable class="parameter">operator</replaceable>
+       (i.e. the operator returns <literal>TRUE</literal>).
+      </para>
+ 
+      <para>
+       Internally, operator exclusion constraints use an index to
+       perform a search looking for conflicting values, and handle
+       concurrent operations similar to a <literal>UNIQUE</literal>
+       constraint. If all of the operators are specified as the
+       equality operator (usually <literal>=</literal>), this
+       constraint behaves identically to a <literal>UNIQUE</literal>
+       constraint. However, it may exhibit slightly worse performance
+       than specifying <literal>UNIQUE</literal>. The advantage of
+       operator exclusion constraints is the ability to specify more
+       general constraints (like a non-overlapping constraint for
+       circles), and also the ability to use index methods other
+       than <literal>btree</literal>, such as <literal>GiST</literal>
+       (see <xref linkend="GiST">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">index_parameters</replaceable>
+       are the same as for a <literal>UNIQUE</literal>
+       constraint. The <replaceable class="parameter">predicate</replaceable>
+       allows you to specify the constraint on a subset of the table
+       (note the reqiuired parentheses around the predicate
+       expression), internally using a partial index
+       (see <xref linkend="SQL-CREATEINDEX">). The <replaceable class="parameter">index_element</replaceable>
+       is normally just a column name, but can also be an expression or
+       function call and the constraint will check the result (similar
+       to creating a unique index over an expression); and it can
+       contain indexing information such as the operator class.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1111,1116 **** CREATE TABLE cinemas (
--- 1178,1195 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUDE USING gist (c WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1109 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraint = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraint != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraint,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 804,809 **** index_create(Oid heapRelationId,
--- 812,875 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1083,1088 **** BuildIndexInfo(Relation index)
--- 1149,1158 ----
  	/* other info */
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 		ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ 	else
+ 		ii->ii_ExclusionConstraint = NULL;
  
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
***************
*** 1894,1899 **** IndexBuildHeapScan(Relation heapRelation,
--- 1964,1972 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't checked at index build time */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2268,2273 **** validate_index_heapscan(Relation heapRelation,
--- 2341,2352 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/*
+ 	 * Operator exclusion constraints aren't supported for concurrent
+ 	 * index builds.
+ 	 */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
  }
  
  
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *constrategiesArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ 		constrategiesArray = construct_array(strategyDatums,
+ 											 constraintNKeys,
+ 											 INT2OID,
+ 											 sizeof(int16), true, 's');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (constrategiesArray)
+ 		values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ 	else
+ 		nulls[Anum_pg_constraint_constrategies - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 548,555 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 564,583 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,252 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
--- 40,46 ----
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext = NULL;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL ||
! 		indexInfo->ii_ExclusionConstraint != NULL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 150,168 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraint == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_check_constraint(trigdata->tg_relation, indexRel,
! 							   slot, &(new_row->t_self), values, isnull,
! 							   indexInfo->ii_ExclusionConstraint,
! 							   indexInfo->ii_ExpressionsState,
! 							   econtext, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 423,428 **** DefineIndex(RangeVar *heapRelation,
--- 433,439 ----
  	indexInfo->ii_ReadyForInserts = !concurrent;
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
+ 	indexInfo->ii_ExclusionConstraint = exclusion_constraint;
  
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 446,469 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type = NULL;
+ 
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUDE";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 479,485 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 774,781 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 965,971 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUDE */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  	CommandId	mycid;
  	BulkInsertState bistate;
  	int			hi_options;
***************
*** 3103,3108 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3109,3117 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3111,3116 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3120,3153 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3155,3160 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3192,3199 ----
  		MemoryContext oldCxt;
  		List	   *dropped_attrs = NIL;
  		ListCell   *lc;
+ 		Datum	   *idxvals = NULL;
+ 		bool	   *idxnulls = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3173,3178 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3212,3226 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3198,3203 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3246,3252 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3268,3273 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3317,3323 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3276,3281 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3326,3351 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_check_constraint(oldrel, indexRelation, newslot,
+ 									   &tuple->t_self, idxvals, idxnulls,
+ 									   indexInfo->ii_ExclusionConstraint,
+ 									   indexInfo->ii_ExpressionsState,
+ 									   econtext, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				heap_insert(newrel, tuple, mycid, hi_options, bistate);
***************
*** 3290,3295 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3360,3378 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 4603,4608 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4686,4692 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4666,4671 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4750,4775 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5035,5040 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5139,5145 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5071,5076 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5176,5343 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	int16		*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support operator "
+ 							   "exclusion constraints",
+ 							   constraint->using_method),
+ 						errdetail("The index access method must support the "
+ 								  "gettuple() interface to be used with an "
+ 								  "operator exclusion constraint.")));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(int16) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = compatible_oper_opid(opname, typoid, typoid, false);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", quote_identifier(get_opname(opid))),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			elog(ERROR, "no strategy found for operator %d "
+ 				 "in operator family %d", opid, opfamily);
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							constraint->conname,	/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,68 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 ExprContext *econtext, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1011,1017 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1018,1024 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1076,1082 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1083,1089 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1084,1090 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1091,1119 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraint != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_check_constraint(heapRelation, indexRelation,
! 									   slot, tupleid, values, isnull,
! 									   indexInfo->ii_ExclusionConstraint,
! 									   indexInfo->ii_ExpressionsState,
! 									   econtext, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraint != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1218,1220 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1247,1497 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ 					   ItemPointer tupleid, Datum *values, bool *isnull,
+ 					   int16 *exclusion_constraint, List *index_exprs,
+ 					   ExprContext *econtext, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	Oid					*constr_procs;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	for (i = 0; i < index_natts; i++)
+ 		if (isnull[i])
+ 			return true;
+ 
+ 	/*
+ 	 * Find the function that tests for a conflict based on the
+ 	 * strategy number, operator family, and types.
+ 	 */
+ 	constr_procs = palloc(sizeof(Oid) * index_natts);
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		/*
+ 		 * Find the procedure implementing the strategy for the
+ 		 * index for two arguments both with the type of the
+ 		 * indexed attribute.
+ 		 */
+ 		Oid				oper;
+ 		Oid				opfamily = index->rd_opfamily[i];
+ 		Oid				typoid = index->rd_opcintype[i];
+ 		StrategyNumber	strategy = exclusion_constraint[i];
+ 
+ 		if (strategy == InvalidStrategy)
+ 			continue;
+ 
+ 		oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+ 
+ 		if(OidIsValid(oper))
+ 			constr_procs[i] = get_opcode(oper);
+ 		else
+ 			elog(ERROR, "cannot determine operator for type %d and "
+ 				 "strategy %d", typoid, strategy);
+ 	}
+ 
+ 	/*
+ 	 * Now search the tuples that are actually in the index for
+ 	 * any violations.
+ 	 */
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			continue;
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 
+ 	/*
+ 	 * We have to find all tuples, even those not visible yet.
+ 	 */
+ 	InitDirtySnapshot(DirtySnapshot);
+ 
+ retry:
+ 	found_self = false;
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		TransactionId xwait;
+ 
+ 		if(ItemPointerEquals(tupleid, &tup->t_self))
+ 		{
+ 			Assert(!found_self);
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, econtext, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		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
+ 		 * restart the scan.
+ 		 */
+ 		xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ 			DirtySnapshot.xmin : DirtySnapshot.xmax;
+ 
+ 		if (TransactionIdIsValid(xwait))
+ 		{
+ 			index_endscan(index_scan);
+ 			XactLockTableWait(xwait);
+ 			goto retry;
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_EXCLUSION_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", tuple_as_string(new_slot),
+ 						   tuple_as_string(existing_slot))));
+ 	}
+ 
+ 	Assert(conflict || found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	pfree(constr_procs);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 ExprContext *econtext, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ 	TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+ 	StringInfoData		buf;
+ 	int					i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < tupdesc->natts; i++)
+ 	{
+ 		char	*strval;
+ 		Datum	 value;
+ 		bool	 isnull;
+ 
+ 		value = slot_getattr(slot, i + 1, &isnull);
+ 
+ 		if (isnull)
+ 			strval = "null";
+ 		else
+ 		{
+ 			Oid		foutoid;
+ 			bool	typisvarlena;
+ 
+ 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ 							  &typisvarlena);
+ 			strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, strval);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	return buf.data;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2159,2164 **** _copyConstraint(Constraint *from)
--- 2159,2167 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2105,2110 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2105,2113 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2397,2402 **** _outConstraint(StringInfo str, Constraint *node)
--- 2397,2410 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 352,357 **** static TypeName *TableFuncTypeName(List *columns);
--- 352,358 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 432,437 **** static TypeName *TableFuncTypeName(List *columns);
--- 433,439 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 475,481 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 477,483 ----
  	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
! 	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,1598 **** alter_table_cmds:
  		;
  
  alter_table_cmd:
! 			/* ALTER TABLE <name> ADD [COLUMN] <coldef> */
! 			ADD_P opt_column columnDef
  				{
  					AlterTableCmd *n = makeNode(AlterTableCmd);
  					n->subtype = AT_AddColumn;
--- 1593,1608 ----
  		;
  
  alter_table_cmd:
! 			/* 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;
***************
*** 2504,2509 **** ConstraintElem:
--- 2514,2534 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2544,2549 **** key_match:  MATCH FULL
--- 2569,2591 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $3);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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,10624 **** unreserved_keyword:
--- 10661,10667 ----
  			| ENCRYPTED
  			| ENUM_P
  			| ESCAPE
+ 			| EXCLUDE
  			| EXCLUDING
  			| EXCLUSIVE
  			| EXECUTE
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 71,76 **** typedef struct
--- 71,77 ----
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
  	List	   *ixconstraints;	/* index-creating constraints */
+ 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void transformOpxConstraints(ParseState *pstate,
+ 									CreateStmtContext *cxt,
+ 									RangeVar *relation,
+ 									Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 175,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 182,188 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 734,739 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 782,793 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 872,877 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 926,941 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1842,1847 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1906,1912 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1889,1894 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1954,1962 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					transformOpxConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1947,1953 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2015,2024 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1962,1967 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2033,2045 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2254,2256 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2332,2385 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ transformOpxConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 148,153 **** static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
--- 148,155 ----
  					   int prettyFlags);
  static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  							int prettyFlags);
+ static char *pg_get_opxdef_worker(Oid indexrelid, uint16 *strategies,
+ 							int prettyFlags);
  static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
  				   int prettyFlags);
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
***************
*** 1244,1249 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1246,1282 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				bool	 isnull;
+ 				Oid		 indexOid = conForm->conindid;
+ 				Datum	 val;
+ 				Datum	*keys;
+ 				int		 nKeys;
+ 				int		 i;
+ 				uint16	*strategies;
+ 
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_constrategies,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null constrategies for constraint %u",
+ 						 constraintId);
+ 
+ 				deconstruct_array(DatumGetArrayTypeP(val),
+ 								  INT2OID, 2, true, 's',
+ 								  &keys, NULL, &nKeys);
+ 
+ 				strategies = palloc(nKeys * sizeof(uint16));
+ 				for(i = 0; i < nKeys; i++)
+ 					strategies[i] = DatumGetInt16(keys[i]);
+ 
+ 				appendStringInfo(&buf, pg_get_opxdef_worker(indexOid,
+ 															strategies,
+ 															prettyFlags));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1291,1296 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1324,1561 ----
  	}
  }
  
+ static char *
+ pg_get_opxdef_worker(Oid indexrelid, uint16_t *strategies, int prettyFlags)
+ {
+ 	HeapTuple	ht_idx;
+ 	HeapTuple	ht_idxrel;
+ 	HeapTuple	ht_am;
+ 	Form_pg_index idxrec;
+ 	Form_pg_class idxrelrec;
+ 	Form_pg_am	amrec;
+ 	List	   *indexprs;
+ 	ListCell   *indexpr_item;
+ 	List	   *context;
+ 	Oid			indrelid;
+ 	int			keyno;
+ 	Oid			keycoltype;
+ 	Datum		indclassDatum;
+ 	Datum		indoptionDatum;
+ 	bool		isnull;
+ 	oidvector  *indclass;
+ 	int2vector *indoption;
+ 	StringInfoData buf;
+ 	char	   *str;
+ 	char	   *sep;
+ 	Oid			tblspc;
+ 
+ 	/*
+ 	 * Fetch the pg_index tuple by the Oid of the index
+ 	 */
+ 	ht_idx = SearchSysCache(INDEXRELID,
+ 							ObjectIdGetDatum(indexrelid),
+ 							0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idx))
+ 		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ 	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+ 
+ 	indrelid = idxrec->indrelid;
+ 	Assert(indexrelid == idxrec->indexrelid);
+ 
+ 	/* Must get indclass and indoption the hard way */
+ 	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indclass, &isnull);
+ 	Assert(!isnull);
+ 	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+ 	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indoption, &isnull);
+ 	Assert(!isnull);
+ 	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ 
+ 	/*
+ 	 * Fetch the pg_class tuple of the index relation
+ 	 */
+ 	ht_idxrel = SearchSysCache(RELOID,
+ 							   ObjectIdGetDatum(indexrelid),
+ 							   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idxrel))
+ 		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+ 	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+ 
+ 	/*
+ 	 * Fetch the pg_am tuple of the index' access method
+ 	 */
+ 	ht_am = SearchSysCache(AMOID,
+ 						   ObjectIdGetDatum(idxrelrec->relam),
+ 						   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_am))
+ 		elog(ERROR, "cache lookup failed for access method %u",
+ 			 idxrelrec->relam);
+ 	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+ 
+ 	/*
+ 	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+ 	 * versions of the expressions and predicate, because we want to display
+ 	 * non-const-folded expressions.)
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+ 	{
+ 		Datum		exprsDatum;
+ 		bool		isnull;
+ 		char	   *exprsString;
+ 
+ 		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indexprs, &isnull);
+ 		Assert(!isnull);
+ 		exprsString = TextDatumGetCString(exprsDatum);
+ 		indexprs = (List *) stringToNode(exprsString);
+ 		pfree(exprsString);
+ 	}
+ 	else
+ 		indexprs = NIL;
+ 
+ 	indexpr_item = list_head(indexprs);
+ 
+ 	context = deparse_context_for(get_rel_name(indrelid), indrelid);
+ 
+ 	/*
+ 	 * Start the index definition.	Note that the index's name should never be
+ 	 * schema-qualified, but the indexed rel's name may be.
+ 	 */
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "EXCLUDE USING %s (",
+ 					 quote_identifier(NameStr(amrec->amname)));
+ 
+ 	/*
+ 	 * Report the indexed attributes
+ 	 */
+ 	sep = "";
+ 	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ 	{
+ 		AttrNumber	attnum = idxrec->indkey.values[keyno];
+ 		int16		opt = indoption->values[keyno];
+ 		Oid			opfamily = get_opclass_family(indclass->values[keyno]);
+ 		Oid			opid;
+ 		char	   *opName;
+ 
+ 		appendStringInfoString(&buf, sep);
+ 		sep = ", ";
+ 
+ 		if (attnum != 0)
+ 		{
+ 			/* Simple index column */
+ 			char	   *attname;
+ 
+ 			attname = get_relid_attribute_name(indrelid, attnum);
+ 			appendStringInfoString(&buf, quote_identifier(attname));
+ 			keycoltype = get_atttype(indrelid, attnum);
+ 		}
+ 		else
+ 		{
+ 			/* expressional index */
+ 			Node	   *indexkey;
+ 
+ 			if (indexpr_item == NULL)
+ 				elog(ERROR, "too few entries in indexprs list");
+ 			indexkey = (Node *) lfirst(indexpr_item);
+ 			indexpr_item = lnext(indexpr_item);
+ 			/* Deparse */
+ 			str = deparse_expression_pretty(indexkey, context, false, false,
+ 											prettyFlags, 0);
+ 
+ 			/* Need parens if it's not a bare function call */
+ 			if (indexkey && IsA(indexkey, FuncExpr) &&
+ 				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+ 				appendStringInfoString(&buf, str);
+ 			else
+ 				appendStringInfo(&buf, "(%s)", str);
+ 
+ 			keycoltype = exprType(indexkey);
+ 		}
+ 
+ 		/* Add the operator class name, if not default */
+ 		get_opclass_name(indclass->values[keyno], keycoltype, &buf);
+ 
+ 		/* Add options if relevant */
+ 		if (amrec->amcanorder)
+ 		{
+ 			/* if it supports sort ordering, report DESC and NULLS opts */
+ 			if (opt & INDOPTION_DESC)
+ 			{
+ 				appendStringInfo(&buf, " DESC");
+ 				/* NULLS FIRST is the default in this case */
+ 				if (!(opt & INDOPTION_NULLS_FIRST))
+ 					appendStringInfo(&buf, " NULLS LAST");
+ 			}
+ 			else
+ 			{
+ 				if (opt & INDOPTION_NULLS_FIRST)
+ 					appendStringInfo(&buf, " NULLS FIRST");
+ 			}
+ 		}
+ 
+ 		/* Add operator exclusion constraint */
+ 		appendStringInfo(&buf, " WITH ");
+ 
+ 		opid = get_opfamily_member(opfamily, keycoltype, keycoltype,
+ 								   strategies[keyno]);
+ 		opName = generate_operator_name(opid, keycoltype, keycoltype);
+ 
+ 		appendStringInfo(&buf, "%s", opName);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	/*
+ 	 * If it has options, append "WITH (options)"
+ 	 */
+ 	str = flatten_reloptions(indexrelid);
+ 	if (str)
+ 	{
+ 		appendStringInfo(&buf, " WITH (%s)", str);
+ 		pfree(str);
+ 	}
+ 
+ 	/*
+ 	 * If it's in a nondefault tablespace, say so, but only if requested
+ 	 */
+ 	tblspc = get_rel_tablespace(indexrelid);
+ 	if (OidIsValid(tblspc))
+ 		appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
+ 						 quote_identifier(get_tablespace_name(tblspc)));
+ 
+ 	/*
+ 	 * If it's a partial index, decompile and append the predicate
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+ 	{
+ 		Node	   *node;
+ 		Datum		predDatum;
+ 		bool		isnull;
+ 		char	   *predString;
+ 
+ 		/* Convert text string to node tree */
+ 		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indpred, &isnull);
+ 		Assert(!isnull);
+ 		predString = TextDatumGetCString(predDatum);
+ 		node = (Node *) stringToNode(predString);
+ 		pfree(predString);
+ 
+ 		/* Deparse */
+ 		str = deparse_expression_pretty(node, context, false, false,
+ 										prettyFlags, 0);
+ 		appendStringInfo(&buf, " WHERE (%s)", str);
+ 	}
+ 
+ 	/* Clean up */
+ 	ReleaseSysCache(ht_idx);
+ 	ReleaseSysCache(ht_idxrel);
+ 	ReleaseSysCache(ht_am);
+ 
+ 	return buf.data;
+ }
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3039,3119 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ 	Relation	conrel;
+ 	SysScanDesc conscan;
+ 	ScanKeyData skey[1];
+ 	HeapTuple	htup;
+ 	Datum		val;
+ 	bool		isnull;
+ 	bool		found = false;
+ 	int16	   *constraints = NULL;
+ 	Oid			relid = indexRelation->rd_index->indrelid;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_constrategies,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null constrategies for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != INT2OID)
+ 			elog(ERROR, "constrategies is not a 1-D smallint array");
+ 		constraints = palloc(sizeof(int16) * nelem);
+ 		memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	return constraints;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2855,2860 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 2855,2861 ----
  				 strcmp(te->desc, "CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "DEFAULT") == 0 ||
  				 strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ 				 strcmp(te->desc, "EXCLUSION CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "INDEX") == 0 ||
  				 strcmp(te->desc, "RULE") == 0 ||
  				 strcmp(te->desc, "TRIGGER") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 3680,3685 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3680,3686 ----
  				i_condeferred,
  				i_contableoid,
  				i_conoid,
+ 				i_condef,
  				i_tablespace,
  				i_options;
  	int			ntups;
***************
*** 3710,3716 **** getIndexes(TableInfo tblinfo[], int numTables)
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80200)
  		{
  			appendPQExpBuffer(query,
  							  "SELECT t.tableoid, t.oid, "
--- 3711,3745 ----
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			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, "
! 					 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
! 							  "c.oid AS conoid, "
! 							  "(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, "
***************
*** 3858,3863 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3887,3893 ----
  		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,3901 **** 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 we found a constraint matching the index, create an
--- 3925,3931 ----
  			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
  			contype = *(PQgetvalue(res, j, i_contype));
  
! 			if (contype == 'p' || contype == 'u' || contype == 'x')
  			{
  				/*
  				 * If we found a constraint matching the index, create an
***************
*** 3913,3919 **** getIndexes(TableInfo tblinfo[], int numTables)
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				constrinfo[j].condef = NULL;
  				constrinfo[j].confrelid = InvalidOid;
  				constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
  				constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
--- 3943,3952 ----
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				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';
***************
*** 10912,10917 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
--- 10945,10975 ----
  						 NULL, NULL);
  		}
  	}
+ 	else if (coninfo->contype == 'x')
+ 	{
+ 		appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(q, "    ADD CONSTRAINT %s %s;\n",
+ 						  fmtId(coninfo->dobj.name),
+ 						  coninfo->condef);
+ 
+ 		appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ 						  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 		appendPQExpBuffer(delq, "%s ",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ 						  fmtId(coninfo->dobj.name));
+ 
+ 		ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ 					 coninfo->dobj.name,
+ 					 tbinfo->dobj.namespace->dobj.name,
+ 					 NULL,
+ 					 tbinfo->rolname, false,
+ 					 "EXCLUSION CONSTRAINT", SECTION_POST_DATA,
+ 					 q->data, delq->data, NULL,
+ 					 coninfo->dobj.dependencies, coninfo->dobj.nDeps,
+ 					 NULL, NULL);
+ 	}
  	else
  	{
  		write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	int2		constrategies[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_constrategies	20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,338 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_check_constraint(Relation heap, Relation index,
+ 								   TupleTableSlot *new_slot,
+ 								   ItemPointer tupleid, Datum *values,
+ 								   bool *isnull, int16 *exclusion_constraint,
+ 								   List *index_exprs, ExprContext *econtext,
+ 								   bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	int16	   *ii_ExclusionConstraint;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1395,1400 **** typedef enum ConstrType			/* types of constraints */
--- 1395,1401 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1429,1439 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1430,1445 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 143,148 **** PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
--- 143,149 ----
  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)
*** a/src/include/utils/errcodes.h
--- b/src/include/utils/errcodes.h
***************
*** 167,172 ****
--- 167,173 ----
  #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')
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 43,49 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/pl/plpgsql/src/plerrcodes.h
--- b/src/pl/plpgsql/src/plerrcodes.h
***************
*** 304,309 ****
--- 304,313 ----
  },
  
  {
+ 	"exclusion_violation", ERRCODE_EXCLUSION_VIOLATION
+ },
+ 
+ {
  	"invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE
  },
  
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ 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>');
+ -- should fail
+ 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;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUDE USING gist
+     (c1 WITH &&, (c2::circle) WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Tuple "(<(20,20),10>, <(0,0), 5>)" conflicts with existing tuple "(<(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:  operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL:  Tuple "(<(0,0),5>, <(0,0), 5>)" conflicts with existing tuple "(<(0,0),5>, <(0,0), 5>)".
+ DROP TABLE circles;
#221Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#220)
Re: operator exclusion constraints

On Wed, Nov 25, 2009 at 3:23 AM, Jeff Davis <pgsql@j-davis.com> wrote:

I was thinking maybe you call BuildIndexValueDescription twice and
make the error message say something like <output of first call>
conflicts with <output of second call>.

Do you really think that's a better error message, or are you just
trying to re-use similar code?

Let's start from how the error message should read, and then see if we
can re-use some code to make it look that way. It's one of the most
visible aspects of the feature, and it needs to be reasonably concise
and understandable in the simple case, but contain all of the necessary
information.

I think it's better to avoid the "=" when describing the conflict. I
tend to read it as "equals" even though it's just punctuation in this
case, so it would be distracting. I could change it to a colon, I
suppose.

I disagree wholeheartedly. :-) My ideal error message is something like:

DETAIL: (a, b, c)=(1, 2, 3) conflicts with (a, b, c)=(4, 5, 6)

In particular, I think it's very important that we only emit the
columns which are part of the operator exclusion constraints, and NOT
all the columns of the tuple. The entire tuple could be very large -
one of the columns not involved in the constraint could be a 4K block
of text, for example, and spitting that out only obscures the real
source of the problem. You could argue that one of the columns
involved in the constraint could be a 4K block text, too, but in
practice I think the constraint columns are likely to be short nine
times out of ten. The equals sign is equating the column names to the
column values, not the values to each other, so I don't see that as
confusing.

create table test (a int4[], exclude using gist (a with =));
ERROR:  operator does not exist: integer[] = integer[]

Thanks, fixed. I am now using compatible_oper_opid(), which will find
any operators that don't require binary-incompatible coercion of the
operands.

Do you think there's any reason to support binary-incompatible coercion
of the operands? I can't think of a single use case, and if you really
need to, you can coerce the types explicitly in the expression.

My operator-class-fu is insufficient to render judgment on this point.
I think the thing to do is look at a bunch of non-built-in opclasses
and check for POLA violations.

...Robert

#222Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#221)
Re: operator exclusion constraints

On Wed, 2009-11-25 at 09:02 -0500, Robert Haas wrote:

I disagree wholeheartedly. :-) My ideal error message is something like:

DETAIL: (a, b, c)=(1, 2, 3) conflicts with (a, b, c)=(4, 5, 6)

In particular, I think it's very important that we only emit the
columns which are part of the operator exclusion constraints, and NOT
all the columns of the tuple. The entire tuple could be very large -
one of the columns not involved in the constraint could be a 4K block
of text, for example, and spitting that out only obscures the real
source of the problem. You could argue that one of the columns
involved in the constraint could be a 4K block text, too, but in
practice I think the constraint columns are likely to be short nine
times out of ten. The equals sign is equating the column names to the
column values, not the values to each other, so I don't see that as
confusing.

Ok, fair enough. But how do you feel about:
(a: 1, b: 2, c: 3)
as a tuple representation instead? I think it's closer to the way people
expect a tuple to be represented. I can change it in one place so that
the unique violations are reported that way, as well (as long as there
are no objections).

It still doesn't contain the operators, but they can look at the
constraint definition for that.

My operator-class-fu is insufficient to render judgment on this point.
I think the thing to do is look at a bunch of non-built-in opclasses
and check for POLA violations.

Ok, I'll consider this more.

Regards,
Jeff Davis

#223Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#222)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Wed, 2009-11-25 at 09:02 -0500, Robert Haas wrote:

I disagree wholeheartedly. :-) My ideal error message is something like:

DETAIL: (a, b, c)=(1, 2, 3) conflicts with (a, b, c)=(4, 5, 6)

Ok, fair enough. But how do you feel about:
(a: 1, b: 2, c: 3)
as a tuple representation instead?

This seems like change for the sake of change. We've been reporting
this type of error (in the context of foreign keys) using the first
syntax for a very long time. I don't feel a need to rearrange it.

regards, tom lane

#224Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#222)
Re: operator exclusion constraints

On Wed, 2009-11-25 at 15:59 -0800, Jeff Davis wrote:

My operator-class-fu is insufficient to render judgment on this point.
I think the thing to do is look at a bunch of non-built-in opclasses
and check for POLA violations.

Ok, I'll consider this more.

In cases where the operator class type is different from the search
operator's operands' types, one of the following is usually true:

* There is a binary cast from the opclass type (same as expression
type) to the search operator's operands' types, or it is otherwise
compatible (e.g. ANYARRAY).
* There is a candidate function that's a better match (e.g. there may
be an =(int8, int8) on int2_ops, but there's also =(int2, int2)).
* The left and right operand types are different, and therefore do not
work with operator exclusion constraints anyway (e.g. full text search
@@).

After installing all contrib modules, plus my period module, and
postgis, there appear to be no instances that violate these assumptions
(according to a join query and some manual testing). In theory there
could be, however.

It's kind of ugly to make it work, and a challenge to test it, so for
now I'll only accept operators returned by compatible_oper(). If you
disagree, I can make it work.

Regards,
Jeff Davis

#225Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#211)
1 attachment(s)
Re: operator exclusion constraints

On Tue, 2009-11-17 at 23:13 -0500, Robert Haas wrote:

Forgive me if this is discussed before, but why does this store the
strategy numbers of the relevant operators instead of the operators
themselves? It seems like this could lead to surprising behavior if
the user modifies the definition of the operator class.

Still open.

I'm wondering if we can't use the existing
BuildIndexValueDescription() rather than the new function
tuple_as_string(). I realize there are two tuples, but maybe it makes
sense to just call it twice?

Changed.

I'm attaching a revised doc patch for your consideration.

Thanks, I applied it. The only significant thing I changed was that I
did not inline the "index_elem" because it made it fairly hard to read.
Instead, I renamed it "exclude_elem" to make it a little more
meaningful, which I assume may have been your motivation for inlining
it.

Changes this patch:
* doc changes
* changed constraint violation message to be more like btree unique
violation
* improved error message when an operator is specified that doesn't
have a search strategy

Remaining issues:
* represent operator IDs in catalog, rather than strategy numbers
* if someone thinks it's an issue, support search strategies that
require binary-incompatible casts of the inputs

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091126.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091126.context.patchDownload
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,63 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
  </synopsis>
  
   </refsynopsisdiv>
--- 51,69 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   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> ) ] }
  [ 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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
+ 
+ <phrase>and <replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+ 
+ { column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+ 
  </synopsis>
  
   </refsynopsisdiv>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 553,598 ----
     </varlistentry>
  
     <varlistentry>
+     <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>EXCLUDE</> clause specifies an operator exclusion
+       constraint.  An operator exclusion constraint guarantees that if
+       any two tuples are compared on the specified columns or
+       expressions using the specified operators, at least one such
+       comparison will return <literal>FALSE</>.  If all of the
+       specified operators test for equality, it is equivalent to a
+       UNIQUE constraint, although an ordinary unique constraint will
+       normally be faster.  However, operator exclusion constraints can
+       use index methods other than btree (such as <literal>GiST</>
+       (see <xref linkend="GiST">), and can specify more general
+       constraints.  For instance, you can specify the constraint that
+       no two tuples in the table contain overlapping circles
+       (see <xref linkend="datatype-geometric">) by using the
+       <literal>&&</> operator.
+      </para>
+ 
+      <para>
+       Operator exclusion constraints are implemented internally using
+       an index, so the specified operators must be associated with an
+       appropriate operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) for access
+       method <replaceable>index_method</>, and the access method must
+       support <literal>amgettuple</> (see <xref linkend="indexam"> for details).
+       The operators are also required to be their own commutators
+       (see <xref linkend="sql-createoperator">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">predicate</> allows you to
+       specify a constraint on a subset of the table, internally using
+       a partial index. Note the require perentheses around the
+       predicate.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1111,1116 **** CREATE TABLE cinemas (
--- 1157,1174 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUDE USING gist (c WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/genam.c
--- b/src/backend/access/index/genam.c
***************
*** 144,155 **** 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			i;
--- 144,149 ----
***************
*** 170,176 **** BuildIndexValueDescription(Relation indexRelation,
  			Oid		foutoid;
  			bool	typisvarlena;
  
! 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
  							  &foutoid, &typisvarlena);
  			val = OidOutputFunctionCall(foutoid, values[i]);
  		}
--- 164,174 ----
  			Oid		foutoid;
  			bool	typisvarlena;
  
! 			/*
! 			 * Don't use the tupdesc for the type information, because
! 			 * that might be different for non-btree opclasses.
! 			 */
! 			getTypeOutputInfo(indexRelation->rd_opcintype[i],
  							  &foutoid, &typisvarlena);
  			val = OidOutputFunctionCall(foutoid, values[i]);
  		}
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1109 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraint = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraint != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraint,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 804,809 **** index_create(Oid heapRelationId,
--- 812,875 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1083,1088 **** BuildIndexInfo(Relation index)
--- 1149,1158 ----
  	/* other info */
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 		ii->ii_ExclusionConstraint = RelationGetOpExclusionConstraints(index);
+ 	else
+ 		ii->ii_ExclusionConstraint = NULL;
  
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
***************
*** 1894,1899 **** IndexBuildHeapScan(Relation heapRelation,
--- 1964,1972 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't checked at index build time */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2268,2273 **** validate_index_heapscan(Relation heapRelation,
--- 2341,2352 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/*
+ 	 * Operator exclusion constraints aren't supported for concurrent
+ 	 * index builds.
+ 	 */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
  }
  
  
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *constrategiesArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,149 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *strategyDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			strategyDatums[i] = Int16GetDatum(exclusion_constraint[i]);
+ 		constrategiesArray = construct_array(strategyDatums,
+ 											 constraintNKeys,
+ 											 INT2OID,
+ 											 sizeof(int16), true, 's');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 191,201 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (constrategiesArray)
+ 		values[Anum_pg_constraint_constrategies - 1] = PointerGetDatum(constrategiesArray);
+ 	else
+ 		nulls[Anum_pg_constraint_constrategies - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 408,418 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 548,555 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 564,583 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,252 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraint = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
--- 40,46 ----
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext = NULL;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL ||
! 		indexInfo->ii_ExclusionConstraint != NULL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 150,166 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraint == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_check_constraint(trigdata->tg_relation, indexRel,
! 							   slot, &(new_row->t_self), values, isnull,
! 							   indexInfo, estate, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 423,428 **** DefineIndex(RangeVar *heapRelation,
--- 433,439 ----
  	indexInfo->ii_ReadyForInserts = !concurrent;
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
+ 	indexInfo->ii_ExclusionConstraint = exclusion_constraint;
  
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 446,469 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type = NULL;
+ 
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUDE";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 479,485 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 774,781 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 965,971 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUDE */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  	CommandId	mycid;
  	BulkInsertState bistate;
  	int			hi_options;
***************
*** 3103,3108 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3109,3117 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3111,3116 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3120,3153 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3155,3160 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3192,3199 ----
  		MemoryContext oldCxt;
  		List	   *dropped_attrs = NIL;
  		ListCell   *lc;
+ 		Datum	   *idxvals = NULL;
+ 		bool	   *idxnulls = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3173,3178 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3212,3226 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3198,3203 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3246,3252 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3268,3273 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3317,3323 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3276,3281 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3326,3349 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_check_constraint(oldrel, indexRelation, newslot,
+ 									   &tuple->t_self, idxvals, idxnulls,
+ 									   indexInfo, estate, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				heap_insert(newrel, tuple, mycid, hi_options, bistate);
***************
*** 3290,3295 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3358,3376 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 4603,4608 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4684,4690 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4666,4671 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4748,4773 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5035,5040 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5137,5143 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5071,5076 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5174,5368 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	int16		*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support operator "
+ 							   "exclusion constraints",
+ 							   constraint->using_method),
+ 						errdetail("The index access method must support the "
+ 								  "gettuple() interface to be used with an "
+ 								  "operator exclusion constraint.")));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(int16) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = compatible_oper_opid(opname, typoid, typoid, false);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", format_operator(opid)),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		if (!op_in_opfamily(opid, opfamily))
+ 		{
+ 			char				*opclass_name;
+ 			HeapTuple			 tuple;
+ 			Form_pg_opclass		 opctup;
+ 
+ 			tuple = SearchSysCache(CLAOID,
+ 								   ObjectIdGetDatum(opclassid),
+ 								   0, 0, 0);
+ 			if (!HeapTupleIsValid(tuple))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 						 errmsg("operator class with OID %u does not exist",
+ 								opclassid)));
+ 
+ 			opctup = (Form_pg_opclass) GETSTRUCT(tuple);
+ 			opclass_name = pstrdup(opctup->opcname.data);
+ 			ReleaseSysCache(tuple);
+ 
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("no streategy found for operator %s in operator "
+ 							"class %s", format_operator(opid),
+ 							quote_identifier(opclass_name)),
+ 					 errdetail("The operator class must provide a strategy "
+ 							   "number for the given operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = get_op_opfamily_strategy(opid, opfamily);
+ 
+ 		Assert(exclusion_constraint[i] != InvalidStrategy);
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							constraint->conname,	/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,68 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 EState *estate, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
! static char * tuple_as_string(TupleTableSlot *slot);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1011,1017 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1018,1024 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1076,1082 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1083,1089 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1084,1090 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1091,1117 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraint != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_check_constraint(heapRelation, indexRelation,
! 									   slot, tupleid, values, isnull,
! 									   indexInfo, estate, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraint != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1218,1220 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1245,1514 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_check_constraint(Relation heap, Relation index, TupleTableSlot *new_slot,
+ 					   ItemPointer tupleid, Datum *values, bool *isnull,
+ 					   IndexInfo *indexInfo, EState *estate, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	Oid					*constr_procs;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 	int16		*exclusion_constraint = indexInfo->ii_ExclusionConstraint;
+ 	List		*index_exprs		  = indexInfo->ii_ExpressionsState;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	for (i = 0; i < index_natts; i++)
+ 		if (isnull[i])
+ 			return true;
+ 
+ 	/*
+ 	 * Find the function that tests for a conflict based on the
+ 	 * strategy number, operator family, and types.
+ 	 */
+ 	constr_procs = palloc(sizeof(Oid) * index_natts);
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		/*
+ 		 * Find the procedure implementing the strategy for the
+ 		 * index for two arguments both with the type of the
+ 		 * indexed attribute.
+ 		 */
+ 		Oid				oper;
+ 		Oid				opfamily = index->rd_opfamily[i];
+ 		Oid				typoid = index->rd_opcintype[i];
+ 		StrategyNumber	strategy = exclusion_constraint[i];
+ 
+ 		if (strategy == InvalidStrategy)
+ 			continue;
+ 
+ 		oper = get_opfamily_member(opfamily, typoid, typoid, strategy);
+ 
+ 		if(OidIsValid(oper))
+ 			constr_procs[i] = get_opcode(oper);
+ 		else
+ 			elog(ERROR, "cannot determine operator for type %d and "
+ 				 "strategy %d", typoid, strategy);
+ 	}
+ 
+ 	/*
+ 	 * Now search the tuples that are actually in the index for
+ 	 * any violations.
+ 	 */
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		if (exclusion_constraint[i] == InvalidStrategy)
+ 			continue;
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, exclusion_constraint[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 
+ 	/*
+ 	 * We have to find all tuples, even those not visible yet.
+ 	 */
+ 	InitDirtySnapshot(DirtySnapshot);
+ 
+ retry:
+ 	found_self = false;
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		TransactionId	 xwait;
+ 		char			*error_new		= NULL;
+ 		char			*error_existing = NULL;
+ 
+ 		if(ItemPointerEquals(tupleid, &tup->t_self))
+ 		{
+ 			Assert(!found_self);
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, estate, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		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
+ 		 * restart the scan.
+ 		 */
+ 		xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ 			DirtySnapshot.xmin : DirtySnapshot.xmax;
+ 
+ 		if (TransactionIdIsValid(xwait))
+ 		{
+ 			index_endscan(index_scan);
+ 			XactLockTableWait(xwait);
+ 			goto retry;
+ 		}
+ 
+ 		error_new = BuildIndexValueDescription(index, values, isnull);
+ 
+ 		{
+ 			Datum		*existing_values = palloc(sizeof(Datum) * index_natts);
+ 			bool		*existing_isnull = palloc(sizeof(bool) * index_natts);
+ 			ExprContext *econtext		 = GetPerTupleExprContext(estate);
+ 
+ 			/* Going to error soon, so it's OK to change the scan tuple. */
+ 			econtext->ecxt_scantuple = existing_slot;
+ 			FormIndexDatum(indexInfo, existing_slot, estate, existing_values,
+ 						   existing_isnull);
+ 
+ 			error_existing = BuildIndexValueDescription(index, existing_values,
+ 														existing_isnull);
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_EXCLUSION_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", error_new, error_existing)));
+ 	}
+ 
+ 	Assert(conflict || found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	pfree(constr_procs);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 EState *estate, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	ExprContext	*econtext	 = GetPerTupleExprContext(estate);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ static char *
+ tuple_as_string(TupleTableSlot *slot)
+ {
+ 	TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+ 	StringInfoData		buf;
+ 	int					i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < tupdesc->natts; i++)
+ 	{
+ 		char	*strval;
+ 		Datum	 value;
+ 		bool	 isnull;
+ 
+ 		value = slot_getattr(slot, i + 1, &isnull);
+ 
+ 		if (isnull)
+ 			strval = "null";
+ 		else
+ 		{
+ 			Oid		foutoid;
+ 			bool	typisvarlena;
+ 
+ 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &foutoid,
+ 							  &typisvarlena);
+ 			strval = DatumGetCString(OidOutputFunctionCall(foutoid, value));
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, strval);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	return buf.data;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2159,2164 **** _copyConstraint(Constraint *from)
--- 2159,2167 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2105,2110 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2105,2113 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2397,2402 **** _outConstraint(StringInfo str, Constraint *node)
--- 2397,2410 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 352,357 **** static TypeName *TableFuncTypeName(List *columns);
--- 352,358 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 432,437 **** static TypeName *TableFuncTypeName(List *columns);
--- 433,439 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 475,481 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 477,483 ----
  	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
! 	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,1598 **** alter_table_cmds:
  		;
  
  alter_table_cmd:
! 			/* ALTER TABLE <name> ADD [COLUMN] <coldef> */
! 			ADD_P opt_column columnDef
  				{
  					AlterTableCmd *n = makeNode(AlterTableCmd);
  					n->subtype = AT_AddColumn;
--- 1593,1608 ----
  		;
  
  alter_table_cmd:
! 			/* 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;
***************
*** 2504,2509 **** ConstraintElem:
--- 2514,2534 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2544,2549 **** key_match:  MATCH FULL
--- 2569,2591 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $3);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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,10624 **** unreserved_keyword:
--- 10661,10667 ----
  			| ENCRYPTED
  			| ENUM_P
  			| ESCAPE
+ 			| EXCLUDE
  			| EXCLUDING
  			| EXCLUSIVE
  			| EXECUTE
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 71,76 **** typedef struct
--- 71,77 ----
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
  	List	   *ixconstraints;	/* index-creating constraints */
+ 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void transformOpxConstraints(ParseState *pstate,
+ 									CreateStmtContext *cxt,
+ 									RangeVar *relation,
+ 									Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 175,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 182,188 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 734,739 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 782,793 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 872,877 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 926,941 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1842,1847 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1906,1912 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1889,1894 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1954,1962 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					transformOpxConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1947,1953 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2015,2024 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1962,1967 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2033,2045 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2254,2256 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2332,2385 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ transformOpxConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 148,153 **** static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
--- 148,155 ----
  					   int prettyFlags);
  static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  							int prettyFlags);
+ static char *pg_get_opxdef_worker(Oid indexrelid, uint16 *strategies,
+ 							int prettyFlags);
  static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
  				   int prettyFlags);
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
***************
*** 1244,1249 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1246,1282 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				bool	 isnull;
+ 				Oid		 indexOid = conForm->conindid;
+ 				Datum	 val;
+ 				Datum	*keys;
+ 				int		 nKeys;
+ 				int		 i;
+ 				uint16	*strategies;
+ 
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_constrategies,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null constrategies for constraint %u",
+ 						 constraintId);
+ 
+ 				deconstruct_array(DatumGetArrayTypeP(val),
+ 								  INT2OID, 2, true, 's',
+ 								  &keys, NULL, &nKeys);
+ 
+ 				strategies = palloc(nKeys * sizeof(uint16));
+ 				for(i = 0; i < nKeys; i++)
+ 					strategies[i] = DatumGetInt16(keys[i]);
+ 
+ 				appendStringInfo(&buf, pg_get_opxdef_worker(indexOid,
+ 															strategies,
+ 															prettyFlags));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1291,1296 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1324,1561 ----
  	}
  }
  
+ static char *
+ pg_get_opxdef_worker(Oid indexrelid, uint16_t *strategies, int prettyFlags)
+ {
+ 	HeapTuple	ht_idx;
+ 	HeapTuple	ht_idxrel;
+ 	HeapTuple	ht_am;
+ 	Form_pg_index idxrec;
+ 	Form_pg_class idxrelrec;
+ 	Form_pg_am	amrec;
+ 	List	   *indexprs;
+ 	ListCell   *indexpr_item;
+ 	List	   *context;
+ 	Oid			indrelid;
+ 	int			keyno;
+ 	Oid			keycoltype;
+ 	Datum		indclassDatum;
+ 	Datum		indoptionDatum;
+ 	bool		isnull;
+ 	oidvector  *indclass;
+ 	int2vector *indoption;
+ 	StringInfoData buf;
+ 	char	   *str;
+ 	char	   *sep;
+ 	Oid			tblspc;
+ 
+ 	/*
+ 	 * Fetch the pg_index tuple by the Oid of the index
+ 	 */
+ 	ht_idx = SearchSysCache(INDEXRELID,
+ 							ObjectIdGetDatum(indexrelid),
+ 							0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idx))
+ 		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ 	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+ 
+ 	indrelid = idxrec->indrelid;
+ 	Assert(indexrelid == idxrec->indexrelid);
+ 
+ 	/* Must get indclass and indoption the hard way */
+ 	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indclass, &isnull);
+ 	Assert(!isnull);
+ 	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+ 	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indoption, &isnull);
+ 	Assert(!isnull);
+ 	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ 
+ 	/*
+ 	 * Fetch the pg_class tuple of the index relation
+ 	 */
+ 	ht_idxrel = SearchSysCache(RELOID,
+ 							   ObjectIdGetDatum(indexrelid),
+ 							   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idxrel))
+ 		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+ 	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+ 
+ 	/*
+ 	 * Fetch the pg_am tuple of the index' access method
+ 	 */
+ 	ht_am = SearchSysCache(AMOID,
+ 						   ObjectIdGetDatum(idxrelrec->relam),
+ 						   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_am))
+ 		elog(ERROR, "cache lookup failed for access method %u",
+ 			 idxrelrec->relam);
+ 	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+ 
+ 	/*
+ 	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+ 	 * versions of the expressions and predicate, because we want to display
+ 	 * non-const-folded expressions.)
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+ 	{
+ 		Datum		exprsDatum;
+ 		bool		isnull;
+ 		char	   *exprsString;
+ 
+ 		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indexprs, &isnull);
+ 		Assert(!isnull);
+ 		exprsString = TextDatumGetCString(exprsDatum);
+ 		indexprs = (List *) stringToNode(exprsString);
+ 		pfree(exprsString);
+ 	}
+ 	else
+ 		indexprs = NIL;
+ 
+ 	indexpr_item = list_head(indexprs);
+ 
+ 	context = deparse_context_for(get_rel_name(indrelid), indrelid);
+ 
+ 	/*
+ 	 * Start the index definition.	Note that the index's name should never be
+ 	 * schema-qualified, but the indexed rel's name may be.
+ 	 */
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "EXCLUDE USING %s (",
+ 					 quote_identifier(NameStr(amrec->amname)));
+ 
+ 	/*
+ 	 * Report the indexed attributes
+ 	 */
+ 	sep = "";
+ 	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ 	{
+ 		AttrNumber	attnum = idxrec->indkey.values[keyno];
+ 		int16		opt = indoption->values[keyno];
+ 		Oid			opfamily = get_opclass_family(indclass->values[keyno]);
+ 		Oid			opid;
+ 		char	   *opName;
+ 
+ 		appendStringInfoString(&buf, sep);
+ 		sep = ", ";
+ 
+ 		if (attnum != 0)
+ 		{
+ 			/* Simple index column */
+ 			char	   *attname;
+ 
+ 			attname = get_relid_attribute_name(indrelid, attnum);
+ 			appendStringInfoString(&buf, quote_identifier(attname));
+ 			keycoltype = get_atttype(indrelid, attnum);
+ 		}
+ 		else
+ 		{
+ 			/* expressional index */
+ 			Node	   *indexkey;
+ 
+ 			if (indexpr_item == NULL)
+ 				elog(ERROR, "too few entries in indexprs list");
+ 			indexkey = (Node *) lfirst(indexpr_item);
+ 			indexpr_item = lnext(indexpr_item);
+ 			/* Deparse */
+ 			str = deparse_expression_pretty(indexkey, context, false, false,
+ 											prettyFlags, 0);
+ 
+ 			/* Need parens if it's not a bare function call */
+ 			if (indexkey && IsA(indexkey, FuncExpr) &&
+ 				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+ 				appendStringInfoString(&buf, str);
+ 			else
+ 				appendStringInfo(&buf, "(%s)", str);
+ 
+ 			keycoltype = exprType(indexkey);
+ 		}
+ 
+ 		/* Add the operator class name, if not default */
+ 		get_opclass_name(indclass->values[keyno], keycoltype, &buf);
+ 
+ 		/* Add options if relevant */
+ 		if (amrec->amcanorder)
+ 		{
+ 			/* if it supports sort ordering, report DESC and NULLS opts */
+ 			if (opt & INDOPTION_DESC)
+ 			{
+ 				appendStringInfo(&buf, " DESC");
+ 				/* NULLS FIRST is the default in this case */
+ 				if (!(opt & INDOPTION_NULLS_FIRST))
+ 					appendStringInfo(&buf, " NULLS LAST");
+ 			}
+ 			else
+ 			{
+ 				if (opt & INDOPTION_NULLS_FIRST)
+ 					appendStringInfo(&buf, " NULLS FIRST");
+ 			}
+ 		}
+ 
+ 		/* Add operator exclusion constraint */
+ 		appendStringInfo(&buf, " WITH ");
+ 
+ 		opid = get_opfamily_member(opfamily, keycoltype, keycoltype,
+ 								   strategies[keyno]);
+ 		opName = generate_operator_name(opid, keycoltype, keycoltype);
+ 
+ 		appendStringInfo(&buf, "%s", opName);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	/*
+ 	 * If it has options, append "WITH (options)"
+ 	 */
+ 	str = flatten_reloptions(indexrelid);
+ 	if (str)
+ 	{
+ 		appendStringInfo(&buf, " WITH (%s)", str);
+ 		pfree(str);
+ 	}
+ 
+ 	/*
+ 	 * If it's in a nondefault tablespace, say so, but only if requested
+ 	 */
+ 	tblspc = get_rel_tablespace(indexrelid);
+ 	if (OidIsValid(tblspc))
+ 		appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
+ 						 quote_identifier(get_tablespace_name(tblspc)));
+ 
+ 	/*
+ 	 * If it's a partial index, decompile and append the predicate
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+ 	{
+ 		Node	   *node;
+ 		Datum		predDatum;
+ 		bool		isnull;
+ 		char	   *predString;
+ 
+ 		/* Convert text string to node tree */
+ 		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indpred, &isnull);
+ 		Assert(!isnull);
+ 		predString = TextDatumGetCString(predDatum);
+ 		node = (Node *) stringToNode(predString);
+ 		pfree(predString);
+ 
+ 		/* Deparse */
+ 		str = deparse_expression_pretty(node, context, false, false,
+ 										prettyFlags, 0);
+ 		appendStringInfo(&buf, " WHERE (%s)", str);
+ 	}
+ 
+ 	/* Clean up */
+ 	ReleaseSysCache(ht_idx);
+ 	ReleaseSysCache(ht_idxrel);
+ 	ReleaseSysCache(ht_am);
+ 
+ 	return buf.data;
+ }
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,65 ****
--- 60,66 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3039,3119 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ int16 *
+ RelationGetOpExclusionConstraints(Relation indexRelation)
+ {
+ 	Relation	conrel;
+ 	SysScanDesc conscan;
+ 	ScanKeyData skey[1];
+ 	HeapTuple	htup;
+ 	Datum		val;
+ 	bool		isnull;
+ 	bool		found = false;
+ 	int16	   *constraints = NULL;
+ 	Oid			relid = indexRelation->rd_index->indrelid;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_constrategies,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null constrategies for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != INT2OID)
+ 			elog(ERROR, "constrategies is not a 1-D smallint array");
+ 		constraints = palloc(sizeof(int16) * nelem);
+ 		memcpy(constraints, ARR_DATA_PTR(arr), nelem * sizeof(int16));
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	return constraints;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2855,2860 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 2855,2861 ----
  				 strcmp(te->desc, "CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "DEFAULT") == 0 ||
  				 strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ 				 strcmp(te->desc, "EXCLUSION CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "INDEX") == 0 ||
  				 strcmp(te->desc, "RULE") == 0 ||
  				 strcmp(te->desc, "TRIGGER") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 3680,3685 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3680,3686 ----
  				i_condeferred,
  				i_contableoid,
  				i_conoid,
+ 				i_condef,
  				i_tablespace,
  				i_options;
  	int			ntups;
***************
*** 3710,3716 **** getIndexes(TableInfo tblinfo[], int numTables)
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80200)
  		{
  			appendPQExpBuffer(query,
  							  "SELECT t.tableoid, t.oid, "
--- 3711,3745 ----
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			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, "
! 					 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
! 							  "c.oid AS conoid, "
! 							  "(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, "
***************
*** 3858,3863 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3887,3893 ----
  		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,3901 **** 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 we found a constraint matching the index, create an
--- 3925,3931 ----
  			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
  			contype = *(PQgetvalue(res, j, i_contype));
  
! 			if (contype == 'p' || contype == 'u' || contype == 'x')
  			{
  				/*
  				 * If we found a constraint matching the index, create an
***************
*** 3913,3919 **** getIndexes(TableInfo tblinfo[], int numTables)
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				constrinfo[j].condef = NULL;
  				constrinfo[j].confrelid = InvalidOid;
  				constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
  				constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
--- 3943,3952 ----
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				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';
***************
*** 10912,10917 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
--- 10945,10975 ----
  						 NULL, NULL);
  		}
  	}
+ 	else if (coninfo->contype == 'x')
+ 	{
+ 		appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(q, "    ADD CONSTRAINT %s %s;\n",
+ 						  fmtId(coninfo->dobj.name),
+ 						  coninfo->condef);
+ 
+ 		appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ 						  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 		appendPQExpBuffer(delq, "%s ",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ 						  fmtId(coninfo->dobj.name));
+ 
+ 		ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ 					 coninfo->dobj.name,
+ 					 tbinfo->dobj.namespace->dobj.name,
+ 					 NULL,
+ 					 tbinfo->rolname, false,
+ 					 "EXCLUSION CONSTRAINT", SECTION_POST_DATA,
+ 					 q->data, delq->data, NULL,
+ 					 coninfo->dobj.dependencies, coninfo->dobj.nDeps,
+ 					 NULL, NULL);
+ 	}
  	else
  	{
  		write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	int2		constrategies[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_constrategies	20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const int16 *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			int16 *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,337 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_check_constraint(Relation heap, Relation index,
+ 								   TupleTableSlot *new_slot,
+ 								   ItemPointer tupleid, Datum *values,
+ 								   bool *isnull, IndexInfo *indexInfo,
+ 								   EState *estate, bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,64 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	int16	   *ii_ExclusionConstraint;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1395,1400 **** typedef enum ConstrType			/* types of constraints */
--- 1395,1401 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1429,1439 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1430,1445 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 143,148 **** PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
--- 143,149 ----
  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)
*** a/src/include/utils/errcodes.h
--- b/src/include/utils/errcodes.h
***************
*** 167,172 ****
--- 167,173 ----
  #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')
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 43,49 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern int16 *RelationGetOpExclusionConstraints(Relation indexRelation);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/pl/plpgsql/src/plerrcodes.h
--- b/src/pl/plpgsql/src/plerrcodes.h
***************
*** 304,309 ****
--- 304,313 ----
  },
  
  {
+ 	"exclusion_violation", ERRCODE_EXCLUSION_VIOLATION
+ },
+ 
+ {
  	"invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE
  },
  
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ 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>');
+ -- should fail
+ 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;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUDE USING gist
+     (c1 WITH &&, (c2::circle) WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Tuple "(c1, (c2::circle))=(<(20,20),10>, <(0,0),5>)" conflicts with existing tuple "(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:  operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL:  Tuple "(c1, (c2::circle))=(<(0,0),5>, <(0,0),5>)" conflicts with existing tuple "(c1, (c2::circle))=(<(0,0),5>, <(0,0),5>)".
+ DROP TABLE circles;
#226Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#225)
Re: operator exclusion constraints

On Thu, Nov 26, 2009 at 4:33 AM, Jeff Davis <pgsql@j-davis.com> wrote:

Thanks, I applied it. The only significant thing I changed was that I
did not inline the "index_elem" because it made it fairly hard to read.
Instead, I renamed it "exclude_elem" to make it a little more
meaningful, which I assume may have been your motivation for inlining
it.

No, it was really just that I didn't see any point in defining a token
that was only used in one place. It seems like it just represents a
jumble of tokens without any real connection between them, so I didn't
really see why we should break out that piece as opposed to anything
else.

Changes this patch:
 * doc changes
 * changed constraint violation message to be more like btree unique
  violation
 * improved error message when an operator is specified that doesn't
  have a search strategy

Remaining issues:
 * represent operator IDs in catalog, rather than strategy numbers
 * if someone thinks it's an issue, support search strategies that
  require binary-incompatible casts of the inputs

I'm (still) not an expert on this topic, but here's one more thought:
maybe we should try to set this up so that it works in any situation
in which an opclass itself would work. IOW, if you manage to define
an opclass, you should probably be able to define an
operator-exclusion constraint against it, rather than having some
opclasses work and others arbitrarily fail.

Just my $0.02,

...Robert

#227Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#226)
Re: operator exclusion constraints

On Thu, 2009-11-26 at 16:31 -0500, Robert Haas wrote:

On Thu, Nov 26, 2009 at 4:33 AM, Jeff Davis <pgsql@j-davis.com> wrote:

Thanks, I applied it. The only significant thing I changed was that I
did not inline the "index_elem" because it made it fairly hard to read.
Instead, I renamed it "exclude_elem" to make it a little more
meaningful, which I assume may have been your motivation for inlining
it.

No, it was really just that I didn't see any point in defining a token
that was only used in one place. It seems like it just represents a
jumble of tokens without any real connection between them, so I didn't
really see why we should break out that piece as opposed to anything
else.

"table_constraint" and "column_constraint" are only used one place. I
found it convenient to do so because, in the common case, exclude_elem
is just a column name. So the user clearly sees:

exclude_elem WITH operator

and the pairing isn't obscured by a bunch of optional stuff.

I'm (still) not an expert on this topic, but here's one more thought:
maybe we should try to set this up so that it works in any situation
in which an opclass itself would work. IOW, if you manage to define
an opclass, you should probably be able to define an
operator-exclusion constraint against it, rather than having some
opclasses work and others arbitrarily fail.

That's what I was aiming for, but it's theoretically possible for that
case to require casts. I will do a little more investigation and write a
test case. If it looks reasonable, I'll refactor a little and just do
it. It is a pretty obscure case (seeing as I have yet to observe it,
including all contrib modules plus postgis), but I might as well do it
right as long as it's reasonable to do. If it gets even messier to
implement, maybe I'll reconsider.

Regards,
Jeff Davis

#228Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#225)
1 attachment(s)
Re: operator exclusion constraints

On Thu, 2009-11-26 at 01:33 -0800, Jeff Davis wrote:

Remaining issues:
* represent operator IDs in catalog, rather than strategy numbers

Done, attached.

* if someone thinks it's an issue, support search strategies that
require binary-incompatible casts of the inputs

I've already solved the original problem involving ANYARRAY. If someone
comes up with a good use case, or provides me with a little more
guidance, I'll reconsider this problem again. Otherwise, I feel like I'm
solving a problem that doesn't exist (after all, none of the contrib
modules seem to have a problem with the current assumptions, nor does
postgis, nor does my PERIOD module). So, I'm considering this a
"non-issue" until further notice.

To summarize, the problem as I understand it is this:

You have two types, T1 and T2, and there's an implicit cast (with
function or with inout) from T1 to T2. And you have an opclass like:

CREATE OPERATOR CLASS t1_ops FOR TYPE t1
...
OPERATOR 17 %%(t2, t2)
...

And then you have an exclusion constraint like:
CREATE TABLE foo
(
a t1,
EXCLUDE (a t1_ops WITH %%)
);

What should the behavior be in the following two cases?
1. Only operator %%(t2, t2) exists.
2. Operator %%(t1, t1) and %%(t2, t2) exist.

If left unsolved, #1 results in an error because the operator requires
binary-incompatible coercion. #2 results in an error because %%(t1, t1)
is not in the opclass.

Arguably either one of them could succeed by finding %%(t2, t2) and
performing the appropriate conversion; but I think it's fair to just say
"the opclass is poorly defined".

Note that if the opclass is on type t2, you can simply cast "a" to t2
explicitly in the expression, like so:
EXCLUDE((a::t2) t2_ops WITH %%)

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091127.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091127.context.patchDownload
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,63 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
  </synopsis>
  
   </refsynopsisdiv>
--- 51,69 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   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> ) ] }
  [ 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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
+ 
+ <phrase>and <replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+ 
+ { column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+ 
  </synopsis>
  
   </refsynopsisdiv>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 553,598 ----
     </varlistentry>
  
     <varlistentry>
+     <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>EXCLUDE</> clause specifies an operator exclusion
+       constraint.  An operator exclusion constraint guarantees that if
+       any two tuples are compared on the specified columns or
+       expressions using the specified operators, at least one such
+       comparison will return <literal>FALSE</>.  If all of the
+       specified operators test for equality, it is equivalent to a
+       UNIQUE constraint, although an ordinary unique constraint will
+       normally be faster.  However, operator exclusion constraints can
+       use index methods other than btree (such as <literal>GiST</>
+       (see <xref linkend="GiST">), and can specify more general
+       constraints.  For instance, you can specify the constraint that
+       no two tuples in the table contain overlapping circles
+       (see <xref linkend="datatype-geometric">) by using the
+       <literal>&&</> operator.
+      </para>
+ 
+      <para>
+       Operator exclusion constraints are implemented internally using
+       an index, so the specified operators must be associated with an
+       appropriate operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) for access
+       method <replaceable>index_method</>, and the access method must
+       support <literal>amgettuple</> (see <xref linkend="indexam"> for details).
+       The operators are also required to be their own commutators
+       (see <xref linkend="sql-createoperator">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">predicate</> allows you to
+       specify a constraint on a subset of the table, internally using
+       a partial index. Note the require perentheses around the
+       predicate.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1111,1116 **** CREATE TABLE cinemas (
--- 1157,1174 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUDE USING gist (c WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/genam.c
--- b/src/backend/access/index/genam.c
***************
*** 144,155 **** 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			i;
--- 144,149 ----
***************
*** 170,176 **** BuildIndexValueDescription(Relation indexRelation,
  			Oid		foutoid;
  			bool	typisvarlena;
  
! 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
  							  &foutoid, &typisvarlena);
  			val = OidOutputFunctionCall(foutoid, values[i]);
  		}
--- 164,174 ----
  			Oid		foutoid;
  			bool	typisvarlena;
  
! 			/*
! 			 * Don't use the tupdesc for the type information, because
! 			 * that might be different for non-btree opclasses.
! 			 */
! 			getTypeOutputInfo(indexRelation->rd_opcintype[i],
  							  &foutoid, &typisvarlena);
  			val = OidOutputFunctionCall(foutoid, values[i]);
  		}
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1111 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraintOps	  = NULL;
+ 	newind->il_info->ii_ExclusionConstraintProcs  = NULL;
+ 	newind->il_info->ii_ExclusionConstraintStrats = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraintOps != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraintOps,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 804,809 **** index_create(Oid heapRelationId,
--- 812,875 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1084,1089 **** BuildIndexInfo(Relation index)
--- 1150,1169 ----
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
  
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 	{
+ 		RelationGetOpExclusionConstraints(index,
+ 										  &ii->ii_ExclusionConstraintOps,
+ 										  &ii->ii_ExclusionConstraintProcs,
+ 										  &ii->ii_ExclusionConstraintStrats);
+ 	}
+ 	else
+ 	{
+ 		ii->ii_ExclusionConstraintOps	 = NULL;
+ 		ii->ii_ExclusionConstraintProcs	 = NULL;
+ 		ii->ii_ExclusionConstraintStrats = NULL;
+ 	}
+ 
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
  	ii->ii_BrokenHotChain = false;
***************
*** 1894,1899 **** IndexBuildHeapScan(Relation heapRelation,
--- 1974,1984 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't checked at index build time */
+ 	indexInfo->ii_ExclusionConstraintOps	= NULL;
+ 	indexInfo->ii_ExclusionConstraintProcs	= NULL;
+ 	indexInfo->ii_ExclusionConstraintStrats = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2268,2273 **** validate_index_heapscan(Relation heapRelation,
--- 2353,2366 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/*
+ 	 * Operator exclusion constraints aren't supported for concurrent
+ 	 * index builds.
+ 	 */
+ 	indexInfo->ii_ExclusionConstraintOps	= NULL;
+ 	indexInfo->ii_ExclusionConstraintProcs	= NULL;
+ 	indexInfo->ii_ExclusionConstraintStrats = NULL;
  }
  
  
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const Oid *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *conoperatorsArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,148 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *opDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			opDatums[i] = ObjectIdGetDatum(exclusion_constraint[i]);
+ 		conoperatorsArray = construct_array(opDatums,
+ 											constraintNKeys,
+ 											OIDOID, sizeof(Oid), true, 'i');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 190,200 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (conoperatorsArray)
+ 		values[Anum_pg_constraint_conoperators - 1] = PointerGetDatum(conoperatorsArray);
+ 	else
+ 		nulls[Anum_pg_constraint_conoperators - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 407,417 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 547,554 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 563,582 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,254 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraintOps	= NULL;
+ 	indexInfo->ii_ExclusionConstraintProcs	= NULL;
+ 	indexInfo->ii_ExclusionConstraintStrats = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
--- 40,46 ----
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext = NULL;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL ||
! 		indexInfo->ii_ExclusionConstraintOps != NULL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 150,166 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraintOps == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_exclusion_constraint(trigdata->tg_relation, indexRel,
! 								   slot, &(new_row->t_self), values, isnull,
! 								   indexInfo, estate, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			Oid *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 423,428 **** DefineIndex(RangeVar *heapRelation,
--- 433,441 ----
  	indexInfo->ii_ReadyForInserts = !concurrent;
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
+ 	indexInfo->ii_ExclusionConstraintOps = exclusion_constraint;
+ 	indexInfo->ii_ExclusionConstraintProcs = NULL; /* not needed */
+ 	indexInfo->ii_ExclusionConstraintStrats = NULL; /* not needed */
  
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 448,471 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type = NULL;
+ 
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUDE";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 481,487 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 776,783 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 967,973 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUDE */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  	CommandId	mycid;
  	BulkInsertState bistate;
  	int			hi_options;
***************
*** 3103,3108 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3109,3117 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3111,3116 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3120,3153 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3155,3160 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3192,3199 ----
  		MemoryContext oldCxt;
  		List	   *dropped_attrs = NIL;
  		ListCell   *lc;
+ 		Datum	   *idxvals = NULL;
+ 		bool	   *idxnulls = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3173,3178 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3212,3226 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3198,3203 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3246,3252 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3268,3273 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3317,3323 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3276,3281 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3326,3349 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_exclusion_constraint(oldrel, indexRelation, newslot,
+ 										   &tuple->t_self, idxvals, idxnulls,
+ 										   indexInfo, estate, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				heap_insert(newrel, tuple, mycid, hi_options, bistate);
***************
*** 3290,3295 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3358,3376 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 4603,4608 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4684,4690 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4666,4671 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4748,4773 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5035,5040 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5137,5143 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5071,5076 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5174,5366 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	Oid			*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support operator "
+ 							   "exclusion constraints",
+ 							   constraint->using_method),
+ 						errdetail("The index access method must support the "
+ 								  "gettuple() interface to be used with an "
+ 								  "operator exclusion constraint.")));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(Oid) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = compatible_oper_opid(opname, typoid, typoid, false);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", format_operator(opid)),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		if (!op_in_opfamily(opid, opfamily))
+ 		{
+ 			char				*opclass_name;
+ 			HeapTuple			 tuple;
+ 			Form_pg_opclass		 opctup;
+ 
+ 			tuple = SearchSysCache(CLAOID,
+ 								   ObjectIdGetDatum(opclassid),
+ 								   0, 0, 0);
+ 			if (!HeapTupleIsValid(tuple))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 						 errmsg("operator class with OID %u does not exist",
+ 								opclassid)));
+ 
+ 			opctup = (Form_pg_opclass) GETSTRUCT(tuple);
+ 			opclass_name = pstrdup(opctup->opcname.data);
+ 			ReleaseSysCache(tuple);
+ 
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("no streategy found for operator %s in operator "
+ 							"class %s", format_operator(opid),
+ 							quote_identifier(opclass_name)),
+ 					 errdetail("The operator class must provide a strategy "
+ 							   "number for the given operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = opid;
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							constraint->conname,	/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,67 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 EState *estate, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1011,1017 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1017,1023 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1076,1082 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1082,1088 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1084,1090 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1090,1116 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraintOps != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_exclusion_constraint(heapRelation, indexRelation,
! 										   slot, tupleid, values, isnull,
! 										   indexInfo, estate, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraintOps != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1218,1220 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1244,1442 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_exclusion_constraint(Relation heap, Relation index,
+ 						   TupleTableSlot *new_slot, ItemPointer tupleid,
+ 						   Datum *values, bool *isnull, IndexInfo *indexInfo,
+ 						   EState *estate, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 	Oid			*constr_procs  = indexInfo->ii_ExclusionConstraintProcs;
+ 	uint16		*constr_strats = indexInfo->ii_ExclusionConstraintStrats;
+ 	List		*index_exprs   = indexInfo->ii_ExpressionsState;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	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);
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, constr_strats[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 
+ 	/*
+ 	 * May have to restart scan from this point if a potential
+ 	 * conflict is found.
+ 	 */
+ retry:
+ 	found_self = false;
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		TransactionId	 xwait;
+ 		char			*error_new		= NULL;
+ 		char			*error_existing = NULL;
+ 
+ 		if(ItemPointerEquals(tupleid, &tup->t_self))
+ 		{
+ 			Assert(!found_self);
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, estate, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		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
+ 		 * restart the scan.
+ 		 */
+ 		xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ 			DirtySnapshot.xmin : DirtySnapshot.xmax;
+ 
+ 		if (TransactionIdIsValid(xwait))
+ 		{
+ 			index_endscan(index_scan);
+ 			XactLockTableWait(xwait);
+ 			goto retry;
+ 		}
+ 
+ 		error_new = BuildIndexValueDescription(index, values, isnull);
+ 
+ 		{
+ 			Datum		*existing_values = palloc(sizeof(Datum) * index_natts);
+ 			bool		*existing_isnull = palloc(sizeof(bool) * index_natts);
+ 			ExprContext *econtext		 = GetPerTupleExprContext(estate);
+ 
+ 			/* Going to error soon, so it's OK to change the scan tuple. */
+ 			econtext->ecxt_scantuple = existing_slot;
+ 			FormIndexDatum(indexInfo, existing_slot, estate, existing_values,
+ 						   existing_isnull);
+ 
+ 			error_existing = BuildIndexValueDescription(index, existing_values,
+ 														existing_isnull);
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_EXCLUSION_VIOLATION),
+ 				 errmsg("operator exclusion constraint violation detected: "
+ 						"\"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", error_new, error_existing)));
+ 	}
+ 
+ 	Assert(conflict || found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 EState *estate, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	ExprContext	*econtext	 = GetPerTupleExprContext(estate);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2159,2164 **** _copyConstraint(Constraint *from)
--- 2159,2167 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2105,2110 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2105,2113 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2397,2402 **** _outConstraint(StringInfo str, Constraint *node)
--- 2397,2410 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 352,357 **** static TypeName *TableFuncTypeName(List *columns);
--- 352,358 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 432,437 **** static TypeName *TableFuncTypeName(List *columns);
--- 433,439 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 475,481 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 477,483 ----
  	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
! 	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,1598 **** alter_table_cmds:
  		;
  
  alter_table_cmd:
! 			/* ALTER TABLE <name> ADD [COLUMN] <coldef> */
! 			ADD_P opt_column columnDef
  				{
  					AlterTableCmd *n = makeNode(AlterTableCmd);
  					n->subtype = AT_AddColumn;
--- 1593,1608 ----
  		;
  
  alter_table_cmd:
! 			/* 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;
***************
*** 2504,2509 **** ConstraintElem:
--- 2514,2534 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2544,2549 **** key_match:  MATCH FULL
--- 2569,2591 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $3);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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,10624 **** unreserved_keyword:
--- 10661,10667 ----
  			| ENCRYPTED
  			| ENUM_P
  			| ESCAPE
+ 			| EXCLUDE
  			| EXCLUDING
  			| EXCLUSIVE
  			| EXECUTE
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 71,76 **** typedef struct
--- 71,77 ----
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
  	List	   *ixconstraints;	/* index-creating constraints */
+ 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void transformOpxConstraints(ParseState *pstate,
+ 									CreateStmtContext *cxt,
+ 									RangeVar *relation,
+ 									Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 175,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 182,188 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 734,739 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 782,793 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 872,877 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 926,941 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1842,1847 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1906,1912 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1889,1894 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1954,1962 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					transformOpxConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1947,1953 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2015,2024 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1962,1967 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2033,2045 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2254,2256 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2332,2385 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ transformOpxConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 148,153 **** static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
--- 148,155 ----
  					   int prettyFlags);
  static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  							int prettyFlags);
+ static char *pg_get_opxdef_worker(Oid indexrelid, Oid *operators,
+ 							int prettyFlags);
  static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
  				   int prettyFlags);
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
***************
*** 1244,1249 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1246,1282 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				bool	 isnull;
+ 				Oid		 indexOid = conForm->conindid;
+ 				Datum	 val;
+ 				Datum	*keys;
+ 				int		 nKeys;
+ 				int		 i;
+ 				Oid		*operators;
+ 
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_conoperators,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null conoperators for constraint %u",
+ 						 constraintId);
+ 
+ 				deconstruct_array(DatumGetArrayTypeP(val),
+ 								  OIDOID, sizeof(Oid), true, 'i',
+ 								  &keys, NULL, &nKeys);
+ 
+ 				operators = palloc(nKeys * sizeof(Oid));
+ 				for(i = 0; i < nKeys; i++)
+ 					operators[i] = DatumGetObjectId(keys[i]);
+ 
+ 				appendStringInfo(&buf, pg_get_opxdef_worker(indexOid,
+ 															operators,
+ 															prettyFlags));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1291,1296 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1324,1558 ----
  	}
  }
  
+ static char *
+ pg_get_opxdef_worker(Oid indexrelid, Oid *operators, int prettyFlags)
+ {
+ 	HeapTuple	ht_idx;
+ 	HeapTuple	ht_idxrel;
+ 	HeapTuple	ht_am;
+ 	Form_pg_index idxrec;
+ 	Form_pg_class idxrelrec;
+ 	Form_pg_am	amrec;
+ 	List	   *indexprs;
+ 	ListCell   *indexpr_item;
+ 	List	   *context;
+ 	Oid			indrelid;
+ 	int			keyno;
+ 	Oid			keycoltype;
+ 	Datum		indclassDatum;
+ 	Datum		indoptionDatum;
+ 	bool		isnull;
+ 	oidvector  *indclass;
+ 	int2vector *indoption;
+ 	StringInfoData buf;
+ 	char	   *str;
+ 	char	   *sep;
+ 	Oid			tblspc;
+ 
+ 	/*
+ 	 * Fetch the pg_index tuple by the Oid of the index
+ 	 */
+ 	ht_idx = SearchSysCache(INDEXRELID,
+ 							ObjectIdGetDatum(indexrelid),
+ 							0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idx))
+ 		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ 	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+ 
+ 	indrelid = idxrec->indrelid;
+ 	Assert(indexrelid == idxrec->indexrelid);
+ 
+ 	/* Must get indclass and indoption the hard way */
+ 	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indclass, &isnull);
+ 	Assert(!isnull);
+ 	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+ 	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indoption, &isnull);
+ 	Assert(!isnull);
+ 	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ 
+ 	/*
+ 	 * Fetch the pg_class tuple of the index relation
+ 	 */
+ 	ht_idxrel = SearchSysCache(RELOID,
+ 							   ObjectIdGetDatum(indexrelid),
+ 							   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idxrel))
+ 		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+ 	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+ 
+ 	/*
+ 	 * Fetch the pg_am tuple of the index' access method
+ 	 */
+ 	ht_am = SearchSysCache(AMOID,
+ 						   ObjectIdGetDatum(idxrelrec->relam),
+ 						   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_am))
+ 		elog(ERROR, "cache lookup failed for access method %u",
+ 			 idxrelrec->relam);
+ 	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+ 
+ 	/*
+ 	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+ 	 * versions of the expressions and predicate, because we want to display
+ 	 * non-const-folded expressions.)
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+ 	{
+ 		Datum		exprsDatum;
+ 		bool		isnull;
+ 		char	   *exprsString;
+ 
+ 		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indexprs, &isnull);
+ 		Assert(!isnull);
+ 		exprsString = TextDatumGetCString(exprsDatum);
+ 		indexprs = (List *) stringToNode(exprsString);
+ 		pfree(exprsString);
+ 	}
+ 	else
+ 		indexprs = NIL;
+ 
+ 	indexpr_item = list_head(indexprs);
+ 
+ 	context = deparse_context_for(get_rel_name(indrelid), indrelid);
+ 
+ 	/*
+ 	 * Start the index definition.	Note that the index's name should never be
+ 	 * schema-qualified, but the indexed rel's name may be.
+ 	 */
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "EXCLUDE USING %s (",
+ 					 quote_identifier(NameStr(amrec->amname)));
+ 
+ 	/*
+ 	 * Report the indexed attributes
+ 	 */
+ 	sep = "";
+ 	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ 	{
+ 		AttrNumber	attnum = idxrec->indkey.values[keyno];
+ 		int16		opt = indoption->values[keyno];
+ 		char	   *opName;
+ 
+ 		appendStringInfoString(&buf, sep);
+ 		sep = ", ";
+ 
+ 		if (attnum != 0)
+ 		{
+ 			/* Simple index column */
+ 			char	   *attname;
+ 
+ 			attname = get_relid_attribute_name(indrelid, attnum);
+ 			appendStringInfoString(&buf, quote_identifier(attname));
+ 			keycoltype = get_atttype(indrelid, attnum);
+ 		}
+ 		else
+ 		{
+ 			/* expressional index */
+ 			Node	   *indexkey;
+ 
+ 			if (indexpr_item == NULL)
+ 				elog(ERROR, "too few entries in indexprs list");
+ 			indexkey = (Node *) lfirst(indexpr_item);
+ 			indexpr_item = lnext(indexpr_item);
+ 			/* Deparse */
+ 			str = deparse_expression_pretty(indexkey, context, false, false,
+ 											prettyFlags, 0);
+ 
+ 			/* Need parens if it's not a bare function call */
+ 			if (indexkey && IsA(indexkey, FuncExpr) &&
+ 				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+ 				appendStringInfoString(&buf, str);
+ 			else
+ 				appendStringInfo(&buf, "(%s)", str);
+ 
+ 			keycoltype = exprType(indexkey);
+ 		}
+ 
+ 		/* Add the operator class name, if not default */
+ 		get_opclass_name(indclass->values[keyno], keycoltype, &buf);
+ 
+ 		/* Add options if relevant */
+ 		if (amrec->amcanorder)
+ 		{
+ 			/* if it supports sort ordering, report DESC and NULLS opts */
+ 			if (opt & INDOPTION_DESC)
+ 			{
+ 				appendStringInfo(&buf, " DESC");
+ 				/* NULLS FIRST is the default in this case */
+ 				if (!(opt & INDOPTION_NULLS_FIRST))
+ 					appendStringInfo(&buf, " NULLS LAST");
+ 			}
+ 			else
+ 			{
+ 				if (opt & INDOPTION_NULLS_FIRST)
+ 					appendStringInfo(&buf, " NULLS FIRST");
+ 			}
+ 		}
+ 
+ 		/* Add operator exclusion constraint */
+ 		appendStringInfo(&buf, " WITH ");
+ 
+ 		opName = generate_operator_name(operators[keyno], keycoltype,
+ 										keycoltype);
+ 
+ 		appendStringInfo(&buf, "%s", opName);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	/*
+ 	 * If it has options, append "WITH (options)"
+ 	 */
+ 	str = flatten_reloptions(indexrelid);
+ 	if (str)
+ 	{
+ 		appendStringInfo(&buf, " WITH (%s)", str);
+ 		pfree(str);
+ 	}
+ 
+ 	/*
+ 	 * If it's in a nondefault tablespace, say so, but only if requested
+ 	 */
+ 	tblspc = get_rel_tablespace(indexrelid);
+ 	if (OidIsValid(tblspc))
+ 		appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
+ 						 quote_identifier(get_tablespace_name(tblspc)));
+ 
+ 	/*
+ 	 * If it's a partial index, decompile and append the predicate
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+ 	{
+ 		Node	   *node;
+ 		Datum		predDatum;
+ 		bool		isnull;
+ 		char	   *predString;
+ 
+ 		/* Convert text string to node tree */
+ 		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indpred, &isnull);
+ 		Assert(!isnull);
+ 		predString = TextDatumGetCString(predDatum);
+ 		node = (Node *) stringToNode(predString);
+ 		pfree(predString);
+ 
+ 		/* Deparse */
+ 		str = deparse_expression_pretty(node, context, false, false,
+ 										prettyFlags, 0);
+ 		appendStringInfo(&buf, " WHERE (%s)", str);
+ 	}
+ 
+ 	/* Clean up */
+ 	ReleaseSysCache(ht_idx);
+ 	ReleaseSysCache(ht_idxrel);
+ 	ReleaseSysCache(ht_am);
+ 
+ 	return buf.data;
+ }
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,68 ****
--- 60,70 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3040,3147 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ void
+ RelationGetOpExclusionConstraints(Relation indexRelation, Oid **operators,
+ 								  Oid **procs, uint16 **strategies)
+ {
+ 	Relation	 conrel;
+ 	SysScanDesc	 conscan;
+ 	ScanKeyData	 skey[1];
+ 	HeapTuple	 htup;
+ 	Datum		 val;
+ 	bool		 isnull;
+ 	bool		 found = false;
+ 	Oid			 relid = indexRelation->rd_index->indrelid;
+ 	Oid			*ops;
+ 	Oid			*funcs;
+ 	uint16		*strats;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 		int					 i;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_conoperators,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null conoperators for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != OIDOID)
+ 			elog(ERROR, "conoperators is not a 1-D Oid array");
+ 
+ 		ops	   = palloc(sizeof(Oid) * nelem);
+ 		funcs  = palloc(sizeof(Oid) * nelem);
+ 		strats = palloc(sizeof(uint16) * nelem);
+ 
+ 		memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * nelem);
+ 
+ 		for (i = 0; i < nelem; i++)
+ 		{
+ 			funcs[i] = get_opcode(ops[i]);
+ 			if (!OidIsValid(funcs[i]))
+ 				elog(ERROR, "could not find function for operator: %d",
+ 					 ops[i]);
+ 
+ 			strats[i] = get_op_opfamily_strategy(
+ 				ops[i], indexRelation->rd_opfamily[i]);
+ 			if (strats[i] == InvalidStrategy)
+ 				elog(ERROR, "could not find strategy for operator %d in "
+ 					 "family %d", ops[i], indexRelation->rd_opfamily[i]);
+ 		}
+ 
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	*operators	= ops;
+ 	*procs		= funcs;
+ 	*strategies = strats;
+ 
+ 	return;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2855,2860 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 2855,2861 ----
  				 strcmp(te->desc, "CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "DEFAULT") == 0 ||
  				 strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ 				 strcmp(te->desc, "EXCLUSION CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "INDEX") == 0 ||
  				 strcmp(te->desc, "RULE") == 0 ||
  				 strcmp(te->desc, "TRIGGER") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 3680,3685 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3680,3686 ----
  				i_condeferred,
  				i_contableoid,
  				i_conoid,
+ 				i_condef,
  				i_tablespace,
  				i_options;
  	int			ntups;
***************
*** 3710,3716 **** getIndexes(TableInfo tblinfo[], int numTables)
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80200)
  		{
  			appendPQExpBuffer(query,
  							  "SELECT t.tableoid, t.oid, "
--- 3711,3745 ----
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			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, "
! 					 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
! 							  "c.oid AS conoid, "
! 							  "(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, "
***************
*** 3858,3863 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3887,3893 ----
  		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,3901 **** 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 we found a constraint matching the index, create an
--- 3925,3931 ----
  			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
  			contype = *(PQgetvalue(res, j, i_contype));
  
! 			if (contype == 'p' || contype == 'u' || contype == 'x')
  			{
  				/*
  				 * If we found a constraint matching the index, create an
***************
*** 3913,3919 **** getIndexes(TableInfo tblinfo[], int numTables)
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				constrinfo[j].condef = NULL;
  				constrinfo[j].confrelid = InvalidOid;
  				constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
  				constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
--- 3943,3952 ----
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				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';
***************
*** 10912,10917 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
--- 10945,10975 ----
  						 NULL, NULL);
  		}
  	}
+ 	else if (coninfo->contype == 'x')
+ 	{
+ 		appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(q, "    ADD CONSTRAINT %s %s;\n",
+ 						  fmtId(coninfo->dobj.name),
+ 						  coninfo->condef);
+ 
+ 		appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ 						  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 		appendPQExpBuffer(delq, "%s ",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ 						  fmtId(coninfo->dobj.name));
+ 
+ 		ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ 					 coninfo->dobj.name,
+ 					 tbinfo->dobj.namespace->dobj.name,
+ 					 NULL,
+ 					 tbinfo->rolname, false,
+ 					 "EXCLUSION CONSTRAINT", SECTION_POST_DATA,
+ 					 q->data, delq->data, NULL,
+ 					 coninfo->dobj.dependencies, coninfo->dobj.nDeps,
+ 					 NULL, NULL);
+ 	}
  	else
  	{
  		write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	Oid			conoperators[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_conoperators		20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const Oid *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			Oid *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,337 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_exclusion_constraint(Relation heap, Relation index,
+ 									   TupleTableSlot *new_slot,
+ 									   ItemPointer tupleid, Datum *values,
+ 									   bool *isnull, IndexInfo *indexInfo,
+ 									   EState *estate, bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,66 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	Oid		   *ii_ExclusionConstraintOps;
+ 	Oid		   *ii_ExclusionConstraintProcs;
+ 	uint16	   *ii_ExclusionConstraintStrats;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1395,1400 **** typedef enum ConstrType			/* types of constraints */
--- 1395,1401 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1429,1439 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1430,1445 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 143,148 **** PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
--- 143,149 ----
  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)
*** a/src/include/utils/errcodes.h
--- b/src/include/utils/errcodes.h
***************
*** 167,172 ****
--- 167,173 ----
  #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')
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 43,52 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern void RelationGetOpExclusionConstraints(Relation indexRelation,
+ 											  Oid **operators,
+ 											  Oid **procs,
+ 											  uint16 **strategies);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/pl/plpgsql/src/plerrcodes.h
--- b/src/pl/plpgsql/src/plerrcodes.h
***************
*** 304,309 ****
--- 304,313 ----
  },
  
  {
+ 	"exclusion_violation", ERRCODE_EXCLUSION_VIOLATION
+ },
+ 
+ {
  	"invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE
  },
  
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ 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>');
+ -- should fail
+ 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;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUDE USING gist
+     (c1 WITH &&, (c2::circle) WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  operator exclusion constraint violation detected: "circles_c1_exclusion"
+ DETAIL:  Tuple "(c1, (c2::circle))=(<(20,20),10>, <(0,0),5>)" conflicts with existing tuple "(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:  operator exclusion constraint violation detected: "circles_c1_exclusion1"
+ DETAIL:  Tuple "(c1, (c2::circle))=(<(0,0),5>, <(0,0),5>)" conflicts with existing tuple "(c1, (c2::circle))=(<(0,0),5>, <(0,0),5>)".
+ DROP TABLE circles;
#229Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#228)
Re: operator exclusion constraints

On Fri, Nov 27, 2009 at 10:18 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Thu, 2009-11-26 at 01:33 -0800, Jeff Davis wrote:

Remaining issues:
 * represent operator IDs in catalog, rather than strategy numbers

Done, attached.

 * if someone thinks it's an issue, support search strategies that
   require binary-incompatible casts of the inputs

I've already solved the original problem involving ANYARRAY. If someone
comes up with a good use case, or provides me with a little more
guidance, I'll reconsider this problem again. Otherwise, I feel like I'm
solving a problem that doesn't exist (after all, none of the contrib
modules seem to have a problem with the current assumptions, nor does
postgis, nor does my PERIOD module). So, I'm considering this a
"non-issue" until further notice.

To summarize, the problem as I understand it is this:

You have two types, T1 and T2, and there's an implicit cast (with
function or with inout) from T1 to T2. And you have an opclass like:

CREATE OPERATOR CLASS t1_ops FOR TYPE t1
...
 OPERATOR 17 %%(t2, t2)
...

And then you have an exclusion constraint like:
CREATE TABLE foo
(
 a t1,
 EXCLUDE (a t1_ops WITH %%)
);

What should the behavior be in the following two cases?
 1. Only operator %%(t2, t2) exists.
 2. Operator %%(t1, t1) and %%(t2, t2) exist.

If left unsolved, #1 results in an error because the operator requires
binary-incompatible coercion. #2 results in an error because %%(t1, t1)
is not in the opclass.

Arguably either one of them could succeed by finding %%(t2, t2) and
performing the appropriate conversion; but I think it's fair to just say
"the opclass is poorly defined".

Note that if the opclass is on type t2, you can simply cast "a" to t2
explicitly in the expression, like so:
 EXCLUDE((a::t2) t2_ops WITH %%)

For parity with unique constraints, I think that the message:

operator exclusion constraint violation detected: %s

should be changed to:

conflicting key value violates operator exclusion constraint "%s"

In ATAddOperatorExclusionConstraint, "streatagy" is misspelled.

Other than that, it looks good to me.

...Robert

#230Jeff Davis
pgsql@j-davis.com
In reply to: Robert Haas (#229)
1 attachment(s)
Re: operator exclusion constraints

On Tue, 2009-12-01 at 23:19 -0500, Robert Haas wrote:

For parity with unique constraints, I think that the message:

operator exclusion constraint violation detected: %s

should be changed to:

conflicting key value violates operator exclusion constraint "%s"

Done, and updated tests.

In ATAddOperatorExclusionConstraint, "streatagy" is misspelled.

Fixed.

Other than that, it looks good to me.

Great, thanks for the detailed review!

Regards,
Jeff Davis

Attachments:

operator-exclusion-constraints-20091201.context.patchtext/x-patch; charset=UTF-8; name=operator-exclusion-constraints-20091201.context.patchDownload
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 51,63 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
  </synopsis>
  
   </refsynopsisdiv>
--- 51,69 ----
    PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
    CHECK ( <replaceable class="PARAMETER">expression</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> ] |
!   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> ) ] }
  [ 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>
  
  [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
  [ USING INDEX TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
+ 
+ <phrase>and <replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+ 
+ { column | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+ 
  </synopsis>
  
   </refsynopsisdiv>
***************
*** 547,552 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
--- 553,598 ----
     </varlistentry>
  
     <varlistentry>
+     <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>EXCLUDE</> clause specifies an operator exclusion
+       constraint.  An operator exclusion constraint guarantees that if
+       any two tuples are compared on the specified columns or
+       expressions using the specified operators, at least one such
+       comparison will return <literal>FALSE</>.  If all of the
+       specified operators test for equality, it is equivalent to a
+       UNIQUE constraint, although an ordinary unique constraint will
+       normally be faster.  However, operator exclusion constraints can
+       use index methods other than btree (such as <literal>GiST</>
+       (see <xref linkend="GiST">), and can specify more general
+       constraints.  For instance, you can specify the constraint that
+       no two tuples in the table contain overlapping circles
+       (see <xref linkend="datatype-geometric">) by using the
+       <literal>&&</> operator.
+      </para>
+ 
+      <para>
+       Operator exclusion constraints are implemented internally using
+       an index, so the specified operators must be associated with an
+       appropriate operator class
+       (see <xref linkend="SQL-CREATEOPCLASS">) for access
+       method <replaceable>index_method</>, and the access method must
+       support <literal>amgettuple</> (see <xref linkend="indexam"> for details).
+       The operators are also required to be their own commutators
+       (see <xref linkend="sql-createoperator">).
+      </para>
+ 
+      <para>
+       The <replaceable class="parameter">predicate</> allows you to
+       specify a constraint on a subset of the table, internally using
+       a partial index. Note the require perentheses around the
+       predicate.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DEFERRABLE</literal></term>
      <term><literal>NOT DEFERRABLE</literal></term>
      <listitem>
***************
*** 1111,1116 **** CREATE TABLE cinemas (
--- 1157,1174 ----
  </programlisting>
    </para>
  
+   <para>
+    Create table <structname>circles</> with an operator exclusion
+    constraint that prevents overlapping circles within it:
+ 
+ <programlisting>
+ CREATE TABLE circles (
+ 	c circle,
+ 	EXCLUDE USING gist (c WITH &&)
+ );
+ </programlisting>
+   </para>
+ 
   </refsect1>
  
   <refsect1 id="SQL-CREATETABLE-compatibility">
*** a/src/backend/access/index/genam.c
--- b/src/backend/access/index/genam.c
***************
*** 144,155 **** 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			i;
--- 144,149 ----
***************
*** 170,176 **** BuildIndexValueDescription(Relation indexRelation,
  			Oid		foutoid;
  			bool	typisvarlena;
  
! 			getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
  							  &foutoid, &typisvarlena);
  			val = OidOutputFunctionCall(foutoid, values[i]);
  		}
--- 164,174 ----
  			Oid		foutoid;
  			bool	typisvarlena;
  
! 			/*
! 			 * Don't use the tupdesc for the type information, because
! 			 * that might be different for non-btree opclasses.
! 			 */
! 			getTypeOutputInfo(indexRelation->rd_opcintype[i],
  							  &foutoid, &typisvarlena);
  			val = OidOutputFunctionCall(foutoid, values[i]);
  		}
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 26,31 ****
--- 26,32 ----
   *		index_vacuum_cleanup	- post-deletion cleanup of an index
   *		index_getprocid - get a support procedure OID
   *		index_getprocinfo - get a support procedure's lookup info
+  *		index_check_constraint - check operator exclusion constraints
   *
   * NOTES
   *		This file contains the index_ routines which used
*** a/src/backend/bootstrap/bootparse.y
--- b/src/backend/bootstrap/bootparse.y
***************
*** 267,273 **** Boot_DeclareIndexStmt:
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 267,273 ----
  								$8,
  								NULL,
  								$10,
! 								NULL, NIL, NULL,
  								false, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
***************
*** 285,291 **** Boot_DeclareUniqueIndexStmt:
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
--- 285,291 ----
  								$9,
  								NULL,
  								$11,
! 								NULL, NIL, NULL,
  								true, false, false, false, false,
  								false, false, true, false, false);
  					do_end();
*** a/src/backend/bootstrap/bootstrap.c
--- b/src/backend/bootstrap/bootstrap.c
***************
*** 1101,1106 **** index_register(Oid heap,
--- 1101,1111 ----
  		copyObject(indexInfo->ii_Predicate);
  	newind->il_info->ii_PredicateState = NIL;
  
+ 	/* no operator exclusion constraints exist at bootstrap time */
+ 	newind->il_info->ii_ExclusionConstraintOps	  = NULL;
+ 	newind->il_info->ii_ExclusionConstraintProcs  = NULL;
+ 	newind->il_info->ii_ExclusionConstraintStrats = NULL;
+ 
  	newind->il_next = ILHead;
  	ILHead = newind;
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 676,681 **** InsertPgClassTuple(Relation pg_class_desc,
--- 676,682 ----
  	values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
  	values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
  	values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
+ 	values[Anum_pg_class_relopxconstraints - 1] = Int16GetDatum(rd_rel->relopxconstraints);
  	values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
  	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
***************
*** 1748,1753 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
--- 1749,1755 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 728,743 **** index_create(Oid heapRelationId,
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY or UNIQUE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions)
  				elog(ERROR, "constraints cannot have index expressions");
  
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
--- 728,750 ----
  				constraintType = CONSTRAINT_PRIMARY;
  			else if (indexInfo->ii_Unique)
  				constraintType = CONSTRAINT_UNIQUE;
+ 			else if (indexInfo->ii_ExclusionConstraintOps != NULL)
+ 				constraintType = CONSTRAINT_OPX;
  			else
  			{
! 				elog(ERROR, "constraint must be PRIMARY, UNIQUE or EXCLUDE");
  				constraintType = 0;		/* keep compiler quiet */
  			}
  
  			/* Shouldn't have any expressions */
! 			if (indexInfo->ii_Expressions &&
! 				constraintType != CONSTRAINT_OPX)
  				elog(ERROR, "constraints cannot have index expressions");
  
+ 			if (constraintType == CONSTRAINT_OPX && concurrent)
+ 				elog(ERROR, "concurrent index builds not supported for "
+ 					 "operator exclusion constraints");
+ 
  			conOid = CreateConstraintEntry(indexRelationName,
  										   namespaceId,
  										   constraintType,
***************
*** 757,762 **** index_create(Oid heapRelationId,
--- 764,770 ----
  										   ' ',
  										   ' ',
  										   ' ',
+ 										   indexInfo->ii_ExclusionConstraintOps,
  										   NULL,		/* no check constraint */
  										   NULL,
  										   NULL,
***************
*** 804,809 **** index_create(Oid heapRelationId,
--- 812,875 ----
  									 "Unique_ConstraintTrigger",
  									 false);
  			}
+ 
+ 			CommandCounterIncrement();
+ 
+ 			/* Increment pg_class.relopxconstraints for the heap and index. */
+ 			if (constraintType == CONSTRAINT_OPX)
+ 			{
+ 				Relation			pgrel;
+ 				Form_pg_class		heap_class;
+ 				HeapTuple			relTup;
+ 				HeapTuple			idxTup;
+ 
+ 				pgrel = heap_open(RelationRelationId, RowExclusiveLock);
+ 
+ 				/* Increment the count for the heap. */
+ 				relTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(heapRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(relTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 heapRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(relTup);
+ 
+ 				if (heap_class->relopxconstraints < 0)
+ 					elog(ERROR, "relation \"%s\" has relopxconstraints = %d",
+ 						 RelationGetRelationName(heapRelation),
+ 						 heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &relTup->t_self, relTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, relTup);
+ 
+ 				heap_freetuple(relTup);
+ 
+ 				/* Increment the count for the index. */
+ 				idxTup = SearchSysCacheCopy(RELOID,
+ 											ObjectIdGetDatum(indexRelationId),
+ 											0, 0, 0);
+ 				if (!HeapTupleIsValid(idxTup))
+ 					elog(ERROR, "cache lookup failed for relation %u",
+ 						 indexRelationId);
+ 				heap_class = (Form_pg_class) GETSTRUCT(idxTup);
+ 
+ 				if (heap_class->relopxconstraints != 0)
+ 					elog(ERROR, "index \"%s\" has relopxconstraints = %d",
+ 						 indexRelationName, heap_class->relopxconstraints);
+ 
+ 				heap_class->relopxconstraints++;
+ 
+ 				simple_heap_update(pgrel, &idxTup->t_self, idxTup);
+ 
+ 				CatalogUpdateIndexes(pgrel, idxTup);
+ 
+ 				heap_freetuple(idxTup);
+ 
+ 				heap_close(pgrel, RowExclusiveLock);
+ 			}
  		}
  		else
  		{
***************
*** 1084,1089 **** BuildIndexInfo(Relation index)
--- 1150,1169 ----
  	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = indexStruct->indisready;
  
+ 	if (index->rd_rel->relopxconstraints > 0)
+ 	{
+ 		RelationGetOpExclusionConstraints(index,
+ 										  &ii->ii_ExclusionConstraintOps,
+ 										  &ii->ii_ExclusionConstraintProcs,
+ 										  &ii->ii_ExclusionConstraintStrats);
+ 	}
+ 	else
+ 	{
+ 		ii->ii_ExclusionConstraintOps	 = NULL;
+ 		ii->ii_ExclusionConstraintProcs	 = NULL;
+ 		ii->ii_ExclusionConstraintStrats = NULL;
+ 	}
+ 
  	/* initialize index-build state to default */
  	ii->ii_Concurrent = false;
  	ii->ii_BrokenHotChain = false;
***************
*** 1894,1899 **** IndexBuildHeapScan(Relation heapRelation,
--- 1974,1984 ----
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
  
+ 	/* operator exclusion constraints aren't checked at index build time */
+ 	indexInfo->ii_ExclusionConstraintOps	= NULL;
+ 	indexInfo->ii_ExclusionConstraintProcs	= NULL;
+ 	indexInfo->ii_ExclusionConstraintStrats = NULL;
+ 
  	return reltuples;
  }
  
***************
*** 2268,2273 **** validate_index_heapscan(Relation heapRelation,
--- 2353,2366 ----
  	/* These may have been pointing to the now-gone estate */
  	indexInfo->ii_ExpressionsState = NIL;
  	indexInfo->ii_PredicateState = NIL;
+ 
+ 	/*
+ 	 * Operator exclusion constraints aren't supported for concurrent
+ 	 * index builds.
+ 	 */
+ 	indexInfo->ii_ExclusionConstraintOps	= NULL;
+ 	indexInfo->ii_ExclusionConstraintProcs	= NULL;
+ 	indexInfo->ii_ExclusionConstraintStrats = NULL;
  }
  
  
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 1779,1784 **** CREATE VIEW table_constraints AS
--- 1779,1785 ----
  
      WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
            AND c.conrelid = r.oid
+ 	  AND c.contype IN ('c','f','p','u')
            AND r.relkind = 'r'
            AND (NOT pg_is_other_temp_schema(nr.oid))
            AND (pg_has_role(r.relowner, 'USAGE')
*** a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c
***************
*** 59,64 **** CreateConstraintEntry(const char *constraintName,
--- 59,65 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const Oid *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
***************
*** 75,80 **** CreateConstraintEntry(const char *constraintName,
--- 76,82 ----
  	ArrayType  *conpfeqopArray;
  	ArrayType  *conppeqopArray;
  	ArrayType  *conffeqopArray;
+ 	ArrayType  *conoperatorsArray = NULL;
  	NameData	cname;
  	int			i;
  	ObjectAddress conobject;
***************
*** 130,135 **** CreateConstraintEntry(const char *constraintName,
--- 132,148 ----
  		conffeqopArray = NULL;
  	}
  
+ 	if (exclusion_constraint != NULL)
+ 	{
+ 		Datum *opDatums = palloc(sizeof(Datum) * constraintNKeys);
+ 
+ 		for (i = 0; i < constraintNKeys; i++)
+ 			opDatums[i] = ObjectIdGetDatum(exclusion_constraint[i]);
+ 		conoperatorsArray = construct_array(opDatums,
+ 											constraintNKeys,
+ 											OIDOID, sizeof(Oid), true, 'i');
+ 	}
+ 
  	/* initialize nulls and values */
  	for (i = 0; i < Natts_pg_constraint; i++)
  	{
***************
*** 177,182 **** CreateConstraintEntry(const char *constraintName,
--- 190,200 ----
  	else
  		nulls[Anum_pg_constraint_conffeqop - 1] = true;
  
+ 	if (conoperatorsArray)
+ 		values[Anum_pg_constraint_conoperators - 1] = PointerGetDatum(conoperatorsArray);
+ 	else
+ 		nulls[Anum_pg_constraint_conoperators - 1] = true;
+ 
  	/*
  	 * initialize the binary form of the check constraint.
  	 */
***************
*** 389,394 **** ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
--- 407,417 ----
  			found = true;
  			break;
  		}
+ 		else if (conCat == CONSTRAINT_OPX && con->conrelid == objId)
+ 		{
+ 			found = true;
+ 			break;
+ 		}
  	}
  
  	systable_endscan(conscan);
***************
*** 524,530 **** RemoveConstraintById(Oid conId)
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
--- 547,554 ----
  		 * being dropped.  This update will force backends to rebuild relcache
  		 * entries when we commit.
  		 */
! 		if (con->contype == CONSTRAINT_CHECK ||
! 			con->contype == CONSTRAINT_OPX)
  		{
  			Relation	pgrel;
  			HeapTuple	relTup;
***************
*** 539,548 **** RemoveConstraintById(Oid conId)
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (classForm->relchecks == 0)		/* should not happen */
! 				elog(ERROR, "relation \"%s\" has relchecks = 0",
! 					 RelationGetRelationName(rel));
! 			classForm->relchecks--;
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
--- 563,582 ----
  					 con->conrelid);
  			classForm = (Form_pg_class) GETSTRUCT(relTup);
  
! 			if (con->contype == CONSTRAINT_CHECK)
! 			{
! 				if (classForm->relchecks == 0)		/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relchecks = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relchecks--;
! 			}
! 			else
! 			{
! 				if (classForm->relopxconstraints == 0)	/* should not happen */
! 					elog(ERROR, "relation \"%s\" has relopxconstraints = 0",
! 						 RelationGetRelationName(rel));
! 				classForm->relopxconstraints--;
! 			}
  
  			simple_heap_update(pgrel, &relTup->t_self, relTup);
  
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 244,249 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
--- 244,254 ----
  	indexInfo->ii_Concurrent = false;
  	indexInfo->ii_BrokenHotChain = false;
  
+ 	/* toast tables don't have operator exclusion constraints */
+ 	indexInfo->ii_ExclusionConstraintOps	= NULL;
+ 	indexInfo->ii_ExclusionConstraintProcs	= NULL;
+ 	indexInfo->ii_ExclusionConstraintStrats = NULL;
+ 
  	classObjectId[0] = OID_BTREE_OPS_OID;
  	classObjectId[1] = INT4_BTREE_OPS_OID;
  
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 40,46 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
--- 40,46 ----
  	Relation	indexRel;
  	IndexInfo  *indexInfo;
  	EState	   *estate;
! 	ExprContext *econtext = NULL;
  	TupleTableSlot *slot;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
***************
*** 125,131 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
--- 125,132 ----
  	 * Typically the index won't have expressions, but if it does we need
  	 * an EState to evaluate them.
  	 */
! 	if (indexInfo->ii_Expressions != NIL ||
! 		indexInfo->ii_ExclusionConstraintOps != NULL)
  	{
  		estate = CreateExecutorState();
  		econtext = GetPerTupleExprContext(estate);
***************
*** 149,156 **** unique_key_recheck(PG_FUNCTION_ARGS)
  	 * Now do the uniqueness check. This is not a real insert; it is a
  	 * check that the index entry that has already been inserted is unique.
  	 */
! 	index_insert(indexRel, values, isnull, &(new_row->t_self),
! 				 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
--- 150,166 ----
  	 * 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.
  	 */
! 	if (indexInfo->ii_ExclusionConstraintOps == NULL)
! 	{
! 		index_insert(indexRel, values, isnull, &(new_row->t_self),
! 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
! 	}
! 	else
! 	{
! 		index_exclusion_constraint(trigdata->tg_relation, indexRel,
! 								   slot, &(new_row->t_self), values, isnull,
! 								   indexInfo, estate, false);
! 	}
  
  	/*
  	 * If that worked, then this index entry is unique, and we are done.
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 62,69 **** static void ComputeIndexAttrs(IndexInfo *indexInfo,
  				  char *accessMethodName, Oid accessMethodId,
  				  bool amcanorder,
  				  bool isconstraint);
- static Oid GetIndexOpClass(List *opclass, Oid attrType,
- 				char *accessMethodName, Oid accessMethodId);
  static bool relationHasPrimaryKey(Relation rel);
  
  
--- 62,67 ----
***************
*** 97,103 **** static bool relationHasPrimaryKey(Relation rel);
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! void
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
--- 95,101 ----
   * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
   * 'concurrent': avoid blocking writers to the table while building.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
***************
*** 106,111 **** DefineIndex(RangeVar *heapRelation,
--- 104,110 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			Oid *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 247,256 **** DefineIndex(RangeVar *heapRelation,
--- 246,266 ----
  	if (indexRelationName == NULL)
  	{
  		if (primary)
+ 		{
  			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
  												   NULL,
  												   "pkey",
  												   namespaceId);
+ 		}
+ 		else if (exclusion_constraint != NULL)
+ 		{
+ 			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
+ 
+ 			indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
+ 												   iparam->name,
+ 												   "exclusion",
+ 												   namespaceId);
+ 		}
  		else
  		{
  			IndexElem  *iparam = (IndexElem *) linitial(attributeList);
***************
*** 423,428 **** DefineIndex(RangeVar *heapRelation,
--- 433,441 ----
  	indexInfo->ii_ReadyForInserts = !concurrent;
  	indexInfo->ii_Concurrent = concurrent;
  	indexInfo->ii_BrokenHotChain = false;
+ 	indexInfo->ii_ExclusionConstraintOps = exclusion_constraint;
+ 	indexInfo->ii_ExclusionConstraintProcs = NULL; /* not needed */
+ 	indexInfo->ii_ExclusionConstraintStrats = NULL; /* not needed */
  
  	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  	coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
***************
*** 435,445 **** DefineIndex(RangeVar *heapRelation,
  	 * error checks)
  	 */
  	if (isconstraint && !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",
  				  indexRelationName, RelationGetRelationName(rel))));
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
--- 448,471 ----
  	 * error checks)
  	 */
  	if (isconstraint && !quiet)
+ 	{
+ 		char *constraint_type = NULL;
+ 
+ 		if (primary)
+ 			constraint_type = "PRIMARY KEY";
+ 		else if (unique)
+ 			constraint_type = "UNIQUE";
+ 		else if (exclusion_constraint != NULL)
+ 			constraint_type = "EXCLUDE";
+ 		else
+ 			elog(ERROR, "unknown constraint type");
+ 
  		ereport(NOTICE,
  		  (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
  				  is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
! 				  constraint_type,
  				  indexRelationName, RelationGetRelationName(rel))));
+ 	}
  
  	/* save lockrelid and locktag for below, then close rel */
  	heaprelid = rel->rd_lockInfo.lockRelId;
***************
*** 455,461 **** DefineIndex(RangeVar *heapRelation,
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return;					/* We're done, in the standard case */
  	}
  
  	/*
--- 481,487 ----
  						 isconstraint, deferrable, initdeferred,
  						 allowSystemTableMods, skip_build, concurrent);
  
! 		return indexRelationId;			/* We're done, in the standard case */
  	}
  
  	/*
***************
*** 750,755 **** DefineIndex(RangeVar *heapRelation,
--- 776,783 ----
  	 * Last thing to do is release the session-level lock on the parent table.
  	 */
  	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+ 	return indexRelationId;
  }
  
  
***************
*** 939,945 **** ComputeIndexAttrs(IndexInfo *indexInfo,
  /*
   * Resolve possibly-defaulted operator class specification
   */
! static Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
--- 967,973 ----
  /*
   * Resolve possibly-defaulted operator class specification
   */
! Oid
  GetIndexOpClass(List *opclass, Oid attrType,
  				char *accessMethodName, Oid accessMethodId)
  {
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 155,161 **** typedef struct NewConstraint
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
--- 155,162 ----
  	Oid			refrelid;		/* PK rel, if FOREIGN */
  	Oid			refindid;		/* OID of PK's index, if FOREIGN */
  	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
! 	Oid			conindid;		/* OID of constraint index, if EXCLUDE */
! 	Node	   *qual;			/* Check expr if CHECK else Constraint */
  	List	   *qualstate;		/* Execution state for CHECK */
  } NewConstraint;
  
***************
*** 305,310 **** static void ATAddCheckConstraint(List **wqueue,
--- 306,314 ----
  					 bool recurse, bool recursing);
  static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  						  Constraint *fkconstraint);
+ static void ATAddOperatorExclusionConstraint(AlteredTableInfo *tab,
+ 											 Relation rel,
+ 											 Constraint *constraint);
  static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 DropBehavior behavior,
  								 bool recurse, bool recursing,
***************
*** 3037,3042 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3041,3048 ----
  	int			i;
  	ListCell   *l;
  	EState	   *estate;
+ 	List	   *opxList  = NIL;
+ 	int			max_index_atts = 0;
  	CommandId	mycid;
  	BulkInsertState bistate;
  	int			hi_options;
***************
*** 3103,3108 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3109,3117 ----
  
  		switch (con->contype)
  		{
+ 			Relation	 indexRelation = NULL;
+ 			IndexInfo	*indexInfo	   = NULL;
+ 
  			case CONSTR_CHECK:
  				needscan = true;
  				con->qualstate = (List *)
***************
*** 3111,3116 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3120,3153 ----
  			case CONSTR_FOREIGN:
  				/* Nothing to do here */
  				break;
+ 			case CONSTR_OPERATOR_EXCLUSION:
+ 				needscan = true;
+ 
+ 				if (newrel != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 							 errmsg("cannot rewrite table while adding "
+ 									"operator exclusion constraint")));
+ 
+ 				indexRelation = index_open(con->conindid, AccessShareLock);
+ 
+ 				indexInfo	  = BuildIndexInfo(indexRelation);
+ 				indexInfo->ii_PredicateState = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, estate);
+ 
+ 				opxList		  = lappend(opxList,
+ 								   list_make2(indexRelation, indexInfo));
+ 
+ 				/*
+ 				 * Keep track of the greatest number of index
+ 				 * attributes for any operator exclusion constraint so
+ 				 * that we can preallocate the idxvals/idxnulls
+ 				 * arrays.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs > max_index_atts)
+ 					max_index_atts = indexInfo->ii_NumIndexAttrs;
+ 
+ 				break;
  			default:
  				elog(ERROR, "unrecognized constraint type: %d",
  					 (int) con->contype);
***************
*** 3155,3160 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3192,3199 ----
  		MemoryContext oldCxt;
  		List	   *dropped_attrs = NIL;
  		ListCell   *lc;
+ 		Datum	   *idxvals = NULL;
+ 		bool	   *idxnulls = NULL;
  
  		econtext = GetPerTupleExprContext(estate);
  
***************
*** 3173,3178 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3212,3226 ----
  		memset(values, 0, i * sizeof(Datum));
  		memset(isnull, true, i * sizeof(bool));
  
+ 		/* Preallocate idxvals/idxnulls arrays */
+ 		if (opxList != NIL)
+ 		{
+ 			idxvals	 = (Datum *) palloc(max_index_atts * sizeof(Datum));
+ 			idxnulls = (bool *) palloc(max_index_atts * sizeof(bool));
+ 			memset(idxvals, 0, max_index_atts * sizeof(Datum));
+ 			memset(idxnulls, true, max_index_atts * sizeof(bool));
+ 		}
+ 
  		/*
  		 * Any attributes that are dropped according to the new tuple
  		 * descriptor can be set to NULL. We precompute the list of dropped
***************
*** 3198,3203 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3246,3252 ----
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 
  			if (newrel)
  			{
  				Oid			tupOid = InvalidOid;
***************
*** 3268,3273 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3317,3323 ----
  											con->name)));
  						break;
  					case CONSTR_FOREIGN:
+ 					case CONSTR_OPERATOR_EXCLUSION:
  						/* Nothing to do here */
  						break;
  					default:
***************
*** 3276,3281 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3326,3349 ----
  				}
  			}
  
+ 			foreach (l, opxList)
+ 			{
+ 				List			*pair		   = (List *) lfirst(l);
+ 				Relation		 indexRelation = (Relation) linitial(pair);
+ 				IndexInfo		*indexInfo	   = (IndexInfo *) lsecond(pair);
+ 
+ 				FormIndexDatum(indexInfo, newslot, estate, idxvals, idxnulls);
+ 
+ 				/* ignore tuples that don't match the constraint predicate */
+ 				if (!ExecQual(indexInfo->ii_PredicateState, econtext, true))
+ 					continue;
+ 
+ 				/* check operator exclusion constraint */
+ 				index_exclusion_constraint(oldrel, indexRelation, newslot,
+ 										   &tuple->t_self, idxvals, idxnulls,
+ 										   indexInfo, estate, false);
+ 			}
+ 
  			/* Write the tuple out to the new relation */
  			if (newrel)
  				heap_insert(newrel, tuple, mycid, hi_options, bistate);
***************
*** 3290,3295 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
--- 3358,3376 ----
  
  		ExecDropSingleTupleTableSlot(oldslot);
  		ExecDropSingleTupleTableSlot(newslot);
+ 
+ 		if (idxvals != NULL)
+ 			pfree(idxvals);
+ 		if (idxnulls != NULL)
+ 			pfree(idxnulls);
+ 
+ 		foreach (l, opxList)
+ 		{
+ 			List			*pair		   = (List *) lfirst(l);
+ 			Relation		 indexRelation = (Relation) linitial(pair);
+ 
+ 			index_close(indexRelation, NoLock);
+ 		}
  	}
  
  	FreeExecutorState(estate);
***************
*** 4603,4608 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
--- 4684,4690 ----
  				stmt->indexParams,		/* parameters */
  				(Expr *) stmt->whereClause,
  				stmt->options,
+ 				NULL,
  				stmt->unique,
  				stmt->primary,
  				stmt->isconstraint,
***************
*** 4666,4671 **** ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4748,4773 ----
  			ATAddForeignKeyConstraint(tab, rel, newConstraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			/*
+ 			 * We don't recurse for operator exclusion constraints, either.
+ 			 */
+ 			if (newConstraint->conname)
+ 			{
+ 				if (ConstraintNameIsUsed(CONSTRAINT_OPX,
+ 										 RelationGetRelid(rel),
+ 										 RelationGetNamespace(rel),
+ 										 newConstraint->conname))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 							 errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ 									newConstraint->conname,
+ 									RelationGetRelationName(rel))));
+ 			}
+ 
+ 			ATAddOperatorExclusionConstraint(tab, rel, newConstraint);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized constraint type: %d",
  				 (int) newConstraint->contype);
***************
*** 5035,5040 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5137,5143 ----
  									  fkconstraint->fk_upd_action,
  									  fkconstraint->fk_del_action,
  									  fkconstraint->fk_matchtype,
+ 									  NULL,
  									  NULL,		/* no check constraint */
  									  NULL,
  									  NULL,
***************
*** 5071,5076 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
--- 5174,5366 ----
  	heap_close(pkrel, NoLock);
  }
  
+ static void
+ ATAddOperatorExclusionConstraint(AlteredTableInfo *tab, Relation rel,
+ 								 Constraint *constraint)
+ {
+ 	int			 natts;
+ 	ListCell	*lc;
+ 	HeapTuple	 tup;
+ 	Oid			 methodOid;
+ 	List		*indexElems	= NIL;
+ 	Oid			*exclusion_constraint;
+ 	Oid			 gettupleOid;
+ 	Oid			 index_oid = InvalidOid;
+ 	Form_pg_am	 am;
+ 	RangeVar	*rv;
+ 	int			 i;
+ 
+ 	/*
+ 	 * Find access method oid, and make sure it supports gettuple.
+ 	 */
+ 	tup = SearchSysCache(AMNAME,
+ 						 CStringGetDatum(constraint->using_method),
+ 						 0, 0, 0);
+ 	if (!HeapTupleIsValid(tup))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("access method \"%s\" does not exist",
+ 						constraint->using_method)));
+ 
+ 	methodOid = HeapTupleGetOid(tup);
+ 	am = (Form_pg_am) GETSTRUCT(tup);
+ 	gettupleOid = am->amgettuple;
+ 
+ 	ReleaseSysCache(tup);
+ 
+ 	if (!OidIsValid(gettupleOid))
+ 		ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						errmsg("method \"%s\" does not support operator "
+ 							   "exclusion constraints",
+ 							   constraint->using_method),
+ 						errdetail("The index access method must support the "
+ 								  "gettuple() interface to be used with an "
+ 								  "operator exclusion constraint.")));
+ 
+ 	natts = list_length(constraint->operator_exclusion);
+ 
+ 	exclusion_constraint = palloc(sizeof(Oid) * natts);
+ 
+ 	/*
+ 	 * Create the strategies array from the input (IndexElem, Operator)
+ 	 * pairs. Also, make an array of IndexElems to pass to DefineIndex().
+ 	 */
+ 	i = 0;
+ 	foreach (lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		List			*opname;
+ 		IndexElem		*elem;
+ 		Oid				 opfamily;
+ 		Oid				 opclassid;
+ 		Oid				 typoid;
+ 		Oid				 opid;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		elem = linitial(pair);
+ 		Assert(IsA(elem, IndexElem));
+ 		opname = lsecond(pair);
+ 		Assert(IsA(opname, List));
+ 
+ 		indexElems = lappend(indexElems, elem);
+ 
+ 		if (elem->name != NULL)
+ 		{
+ 			AttrNumber heapatt = get_attnum(RelationGetRelid(rel), elem->name);
+ 			if (heapatt < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 						 errmsg("column \"%s\" does not exist", elem->name),
+ 						 errhint("Cannot specify system column.")));
+ 
+ 			typoid = rel->rd_att->attrs[heapatt - 1]->atttypid;
+ 		}
+ 		else
+ 			typoid = exprType(elem->expr);
+ 
+ 		opid = compatible_oper_opid(opname, typoid, typoid, false);
+ 
+ 		opclassid = GetIndexOpClass(elem->opclass, typoid,
+ 									constraint->using_method, methodOid);
+ 
+ 		opfamily = get_opclass_family(opclassid);
+ 
+ 		/*
+ 		 * Only allow commutative operators to be used for operator
+ 		 * 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 for exclusion constraint must be "
+ 							"commutative", format_operator(opid)),
+ 					 errdetail("Set the operator's COMMUTATOR to be itself, "
+ 							   "or choose a different operator.")
+ 						));
+ 		}
+ 
+ 		if (!op_in_opfamily(opid, opfamily))
+ 		{
+ 			char				*opclass_name;
+ 			HeapTuple			 tuple;
+ 			Form_pg_opclass		 opctup;
+ 
+ 			tuple = SearchSysCache(CLAOID,
+ 								   ObjectIdGetDatum(opclassid),
+ 								   0, 0, 0);
+ 			if (!HeapTupleIsValid(tuple))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 						 errmsg("operator class with OID %u does not exist",
+ 								opclassid)));
+ 
+ 			opctup = (Form_pg_opclass) GETSTRUCT(tuple);
+ 			opclass_name = pstrdup(opctup->opcname.data);
+ 			ReleaseSysCache(tuple);
+ 
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("no strategy found for operator %s in operator "
+ 							"class %s", format_operator(opid),
+ 							quote_identifier(opclass_name)),
+ 					 errdetail("The operator class must provide a strategy "
+ 							   "number for the given operator.")
+ 						));
+ 		}
+ 
+ 		exclusion_constraint[i] = opid;
+ 
+ 		i++;
+ 	}
+ 
+ 	rv = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+ 					  pstrdup(RelationGetRelationName(rel)),
+ 					  -1);
+ 	/*
+ 	 * Build index to enforce the constraint.
+ 	 */
+ 	index_oid = DefineIndex(rv, /* relation range var */
+ 							constraint->conname,	/* index name */
+ 							InvalidOid,	/* predefined OID */
+ 							constraint->using_method,	/* am name */
+ 							constraint->indexspace, /* index tablespace */
+ 							indexElems,	/* parameters */
+ 							(Expr *) constraint->where_clause, /* where */
+ 							constraint->options, /* options */
+ 							exclusion_constraint, /* exclusion constraint */
+ 							false, /* unique */
+ 							false, /* primary */
+ 							true, /* is constraint? */
+ 							constraint->deferrable, /* deferrable */
+ 							constraint->initdeferred, /* init deferred */
+ 							true,	/* is_alter_table? */
+ 							true,	/* check rights? */
+ 							false,   /* skip build? */
+ 							false,   /* quiet? */
+ 							false);  /* concurrent? */
+ 
+ 	/*
+ 	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ 	 * (we can skip this during table creation).
+ 	 */
+ 	if (!constraint->skip_validation)
+ 	{
+ 		NewConstraint *newcon;
+ 
+ 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ 		newcon->name = constraint->conname;
+ 		newcon->contype = CONSTR_OPERATOR_EXCLUSION;
+ 		newcon->conindid = index_oid;
+ 		newcon->qual = (Node *) constraint;
+ 
+ 		tab->constraints = lappend(tab->constraints, newcon);
+ 	}
+ 
+ 
+ }
  
  /*
   * transformColumnNameList - transform list of column names
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2297,2302 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
--- 2297,2303 ----
  						  ' ',
  						  ' ',
  						  ' ',
+ 						  NULL,
  						  expr, /* Tree form check constraint */
  						  ccbin,	/* Binary form check constraint */
  						  ccsrc,	/* Source form check constraint */
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 44,53 ****
--- 44,57 ----
  
  #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/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/relcache.h"
  #include "utils/tqual.h"
***************
*** 55,61 ****
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! 
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
--- 59,67 ----
  
  static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
  static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
! static bool index_recheck_constraint(Relation index, TupleTableSlot *slot,
! 									 EState *estate, List *index_exprs,
! 									 Datum *new_values, Oid *constr_procs);
  
  /* ----------------------------------------------------------------
   *				 Executor state and memory management functions
***************
*** 1011,1017 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		isUnique;
  
  		if (indexRelation == NULL)
  			continue;
--- 1017,1023 ----
  		Relation	indexRelation = relationDescs[i];
  		IndexInfo  *indexInfo;
  		IndexUniqueCheck checkUnique;
! 		bool		satisfiesConstraint;
  
  		if (indexRelation == NULL)
  			continue;
***************
*** 1076,1082 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		isUnique =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
--- 1082,1088 ----
  		else
  			checkUnique = UNIQUE_CHECK_PARTIAL;
  
! 		satisfiesConstraint =
  			index_insert(indexRelation,	/* index relation */
  						 values,		/* array of index Datums */
  						 isnull,		/* null flags */
***************
*** 1084,1090 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
--- 1090,1116 ----
  						 heapRelation,	/* heap relation */
  						 checkUnique);	/* type of uniqueness check to do */
  
! 		/*
! 		 * Operator exclusion constraint check is simpler, because the
! 		 * check is separated from the index insert.
! 		 */
! 		if (indexInfo->ii_ExclusionConstraintOps != NULL)
! 		{
! 			bool errorOK = !indexRelation->rd_index->indimmediate;
! 
! 			/*
! 			 * An index for an operator exclusion constraint can't
! 			 * also be UNIQUE.
! 			 */
! 			satisfiesConstraint =
! 				index_exclusion_constraint(heapRelation, indexRelation,
! 										   slot, tupleid, values, isnull,
! 										   indexInfo, estate, errorOK);
! 		}
! 
! 		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
! 			 indexInfo->ii_ExclusionConstraintOps != NULL) &&
! 			!satisfiesConstraint)
  		{
  			/*
  			 * The tuple potentially violates the uniqueness constraint,
***************
*** 1218,1220 **** ShutdownExprContext(ExprContext *econtext, bool isCommit)
--- 1244,1442 ----
  
  	MemoryContextSwitchTo(oldcontext);
  }
+ 
+ bool
+ index_exclusion_constraint(Relation heap, Relation index,
+ 						   TupleTableSlot *new_slot, ItemPointer tupleid,
+ 						   Datum *values, bool *isnull, IndexInfo *indexInfo,
+ 						   EState *estate, bool errorOK)
+ {
+ 	IndexScanDesc		 index_scan;
+ 	HeapTuple			 tup;
+ 	ScanKeyData			*scankeys;
+ 	int2				 index_natts  = index->rd_index->indnatts;
+ 	SnapshotData		 DirtySnapshot;
+ 	int					 nkeys		  = 0;
+ 	int					 i;
+ 	bool				 found_self;
+ 	bool				 conflict	  = false;
+ 	TupleTableSlot		*existing_slot;
+ 
+ 	Oid			*constr_procs  = indexInfo->ii_ExclusionConstraintProcs;
+ 	uint16		*constr_strats = indexInfo->ii_ExclusionConstraintStrats;
+ 	List		*index_exprs   = indexInfo->ii_ExpressionsState;
+ 
+ 	/*
+ 	 * If any of the input values are NULL, the constraint check must
+ 	 * pass.
+ 	 */
+ 	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);
+ 
+ 	scankeys = palloc(index_natts * sizeof(ScanKeyData));
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	key_datum;
+ 
+ 		key_datum = values[i];
+ 
+ 		ScanKeyInit(&scankeys[nkeys], i + 1, constr_strats[i],
+ 					constr_procs[i], key_datum);
+ 		nkeys++;
+ 	}
+ 
+ 	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+ 
+ 	/*
+ 	 * May have to restart scan from this point if a potential
+ 	 * conflict is found.
+ 	 */
+ retry:
+ 	found_self = false;
+ 	index_scan = index_beginscan(heap, index, &DirtySnapshot, nkeys,
+ 								 scankeys);
+ 	while((tup = index_getnext(index_scan,
+ 							   ForwardScanDirection)) != NULL)
+ 	{
+ 		TransactionId	 xwait;
+ 		char			*error_new		= NULL;
+ 		char			*error_existing = NULL;
+ 
+ 		if(ItemPointerEquals(tupleid, &tup->t_self))
+ 		{
+ 			Assert(!found_self);
+ 			found_self = true;
+ 			continue;
+ 		}
+ 
+ 		ExecStoreTuple(tup,	existing_slot, index_scan->xs_cbuf, false);
+ 
+ 		if (index_scan->xs_recheck)
+ 		{
+ 			bool				 matches;
+ 
+ 			matches = index_recheck_constraint(
+ 				index, existing_slot, estate, index_exprs, values,
+ 				constr_procs);
+ 
+ 			if (!matches)
+ 				continue; /* tuple doesn't actually match, so no conflict */
+ 		}
+ 
+ 		/*
+ 		 * At this point we have either a conflict or a potential
+ 		 * conflict.
+ 		 */
+ 
+ 		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
+ 		 * restart the scan.
+ 		 */
+ 		xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
+ 			DirtySnapshot.xmin : DirtySnapshot.xmax;
+ 
+ 		if (TransactionIdIsValid(xwait))
+ 		{
+ 			index_endscan(index_scan);
+ 			XactLockTableWait(xwait);
+ 			goto retry;
+ 		}
+ 
+ 		error_new = BuildIndexValueDescription(index, values, isnull);
+ 
+ 		{
+ 			Datum		*existing_values = palloc(sizeof(Datum) * index_natts);
+ 			bool		*existing_isnull = palloc(sizeof(bool) * index_natts);
+ 			ExprContext *econtext		 = GetPerTupleExprContext(estate);
+ 
+ 			/* Going to error soon, so it's OK to change the scan tuple. */
+ 			econtext->ecxt_scantuple = existing_slot;
+ 			FormIndexDatum(indexInfo, existing_slot, estate, existing_values,
+ 						   existing_isnull);
+ 
+ 			error_existing = BuildIndexValueDescription(index, existing_values,
+ 														existing_isnull);
+ 		}
+ 
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_EXCLUSION_VIOLATION),
+ 				 errmsg("conflicting key value violates operator exclusion "
+ 						"constraint \"%s\"", RelationGetRelationName(index)),
+ 				 errdetail("Tuple \"%s\" conflicts with existing tuple "
+ 						   "\"%s\".", error_new, error_existing)));
+ 	}
+ 
+ 	Assert(conflict || found_self);
+ 
+ 	ExecDropSingleTupleTableSlot(existing_slot);
+ 
+ 	index_endscan(index_scan);
+ 
+ 	pfree(scankeys);
+ 
+ 	return !conflict;
+ }
+ 
+ static bool
+ index_recheck_constraint(Relation index, TupleTableSlot *slot,
+ 						 EState *estate, List *index_exprs,
+ 						 Datum *new_values, Oid *constr_procs)
+ {
+ 	int			 index_natts = index->rd_index->indnatts;
+ 	int2		*index_keys	 = index->rd_index->indkey.values;
+ 	ListCell	*lc			 = list_head(index_exprs);
+ 	ExprContext	*econtext	 = GetPerTupleExprContext(estate);
+ 	int			 i;
+ 
+ 	for (i = 0; i < index_natts; i++)
+ 	{
+ 		Datum	old_value;
+ 		bool	isnull;
+ 
+ 		if (index_keys[i] == 0)
+ 		{
+ 			ExprState	*exprstate;
+ 
+ 			Assert(lc != NULL);
+ 			exprstate = (ExprState *) lfirst(lc);
+ 
+ 			old_value = ExecEvalExpr(exprstate, econtext, &isnull, NULL);
+ 			lc = lnext(lc);
+ 		}
+ 		else
+ 		{
+ 			old_value = slot_getattr(slot, index_keys[i], &isnull);
+ 
+ 			/*
+ 			 * Any null should cause the constraint to pass, so this
+ 			 * recheck should immediately return false. Note: This
+ 			 * isn't consistent in the case where the index search
+ 			 * does match NULLs.
+ 			 */
+ 			if (isnull)
+ 				return false;
+ 		}
+ 
+ 		if (!DatumGetBool(OidFunctionCall2(constr_procs[i], old_value,
+ 										   new_values[i])))
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2159,2164 **** _copyConstraint(Constraint *from)
--- 2159,2167 ----
  	COPY_NODE_FIELD(keys);
  	COPY_NODE_FIELD(options);
  	COPY_STRING_FIELD(indexspace);
+ 	COPY_STRING_FIELD(using_method);
+ 	COPY_NODE_FIELD(operator_exclusion);
+ 	COPY_NODE_FIELD(where_clause);
  	COPY_NODE_FIELD(pktable);
  	COPY_NODE_FIELD(fk_attrs);
  	COPY_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2105,2110 **** _equalConstraint(Constraint *a, Constraint *b)
--- 2105,2113 ----
  	COMPARE_NODE_FIELD(keys);
  	COMPARE_NODE_FIELD(options);
  	COMPARE_STRING_FIELD(indexspace);
+ 	COMPARE_STRING_FIELD(using_method);
+ 	COMPARE_NODE_FIELD(operator_exclusion);
+ 	COMPARE_NODE_FIELD(where_clause);
  	COMPARE_NODE_FIELD(pktable);
  	COMPARE_NODE_FIELD(fk_attrs);
  	COMPARE_NODE_FIELD(pk_attrs);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2398,2403 **** _outConstraint(StringInfo str, Constraint *node)
--- 2398,2411 ----
  			WRITE_BOOL_FIELD(skip_validation);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			appendStringInfo(str, "OPERATOR_EXCLUSION");
+ 			WRITE_STRING_FIELD(indexspace);
+ 			WRITE_STRING_FIELD(using_method);
+ 			WRITE_NODE_FIELD(operator_exclusion);
+ 			WRITE_NODE_FIELD(where_clause);
+ 			break;
+ 
  		case CONSTR_ATTR_DEFERRABLE:
  			appendStringInfo(str, "ATTR_DEFERRABLE");
  			break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 352,357 **** static TypeName *TableFuncTypeName(List *columns);
--- 352,358 ----
  %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
+ 				exclusion_where_clause
  %type <list>	func_arg_list
  %type <node>	func_arg_expr
  %type <list>	row type_list array_expr_list
***************
*** 432,437 **** static TypeName *TableFuncTypeName(List *columns);
--- 433,439 ----
  %type <str>		opt_existing_window_name
  %type <ival>	opt_frame_clause frame_extent frame_bound
  
+ %type <list>	ExclusionConstraintList ExclusionConstraintElem
  
  /*
   * Non-keyword token types.  These are hard-wired into the "flex" lexer.
***************
*** 475,481 **** 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
  
  	FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
  	FREEZE FROM FULL FUNCTION FUNCTIONS
--- 477,483 ----
  	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
! 	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,1598 **** alter_table_cmds:
  		;
  
  alter_table_cmd:
! 			/* ALTER TABLE <name> ADD [COLUMN] <coldef> */
! 			ADD_P opt_column columnDef
  				{
  					AlterTableCmd *n = makeNode(AlterTableCmd);
  					n->subtype = AT_AddColumn;
--- 1593,1608 ----
  		;
  
  alter_table_cmd:
! 			/* 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;
***************
*** 2504,2509 **** ConstraintElem:
--- 2514,2534 ----
  					n->initdeferred		= ($11 & 2) != 0;
  					$$ = (Node *)n;
  				}
+ 			| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
+ 				opt_definition OptConsTableSpace exclusion_where_clause
+ 				ConstraintAttributeSpec
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype			  = CONSTR_OPERATOR_EXCLUSION;
+ 					n->using_method		  = $2;
+ 					n->operator_exclusion = $4;
+ 					n->options			  = $6;
+ 					n->indexspace		  = $7;
+ 					n->where_clause		  = $8;
+ 					n->deferrable		  = ($9 & 1) != 0;
+ 					n->initdeferred		  = ($9 & 2) != 0;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  opt_column_list:
***************
*** 2544,2549 **** key_match:  MATCH FULL
--- 2569,2591 ----
  			}
  		;
  
+ ExclusionConstraintList:
+ 			ExclusionConstraintElem					{ $$ = list_make1($1); }
+ 			| ExclusionConstraintList ',' ExclusionConstraintElem
+ 				{ $$ = lappend($1, $3); }
+ 		;
+ 
+ ExclusionConstraintElem: index_elem WITH any_operator
+ 			{
+ 				$$ = list_make2($1, $3);
+ 			}
+ 		;
+ 
+ exclusion_where_clause:
+ 			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,10624 **** unreserved_keyword:
--- 10661,10667 ----
  			| ENCRYPTED
  			| ENUM_P
  			| ESCAPE
+ 			| EXCLUDE
  			| EXCLUDING
  			| EXCLUSIVE
  			| EXECUTE
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 71,76 **** typedef struct
--- 71,77 ----
  	List	   *ckconstraints;	/* CHECK constraints */
  	List	   *fkconstraints;	/* FOREIGN KEY constraints */
  	List	   *ixconstraints;	/* index-creating constraints */
+ 	List	   *opxconstraints;	/* operator exclusion constraints */
  	List	   *inh_indexes;	/* cloned indexes from INCLUDING INDEXES */
  	List	   *blist;			/* "before list" of things to do before
  								 * creating the table */
***************
*** 117,122 **** static void transformFKConstraints(ParseState *pstate,
--- 118,127 ----
  static void transformConstraintAttrs(ParseState *pstate, List *constraintList);
  static void transformColumnType(ParseState *pstate, ColumnDef *column);
  static void setSchemaName(char *context_schema, char **stmt_schema_name);
+ static void transformOpxConstraints(ParseState *pstate,
+ 									CreateStmtContext *cxt,
+ 									RangeVar *relation,
+ 									Constraint *constraint);
  
  
  /*
***************
*** 141,146 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 146,153 ----
  	List	   *result;
  	List	   *save_alist;
  	ListCell   *elements;
+ 	ListCell   *lc;
+ 	List	   *opxlist = NIL;
  
  	/*
  	 * We must not scribble on the passed-in CreateStmt, so copy it.  (This is
***************
*** 175,180 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 182,188 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 233,238 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 241,281 ----
  	transformFKConstraints(pstate, &cxt, true, false);
  
  	/*
+ 	 * Transform operator exclusion constraints into an
+ 	 * AlterTableStmt.
+ 	 */
+ 	if (cxt.opxconstraints != NIL)
+ 	{
+ 		AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
+ 
+ 		alterstmt->relation = cxt.relation;
+ 		alterstmt->cmds = NIL;
+ 		alterstmt->relkind = OBJECT_TABLE;
+ 
+ 		foreach (lc, cxt.opxconstraints)
+ 		{
+ 			Constraint		*constraint = (Constraint *) lfirst(lc);
+ 			AlterTableCmd	*altercmd	= makeNode(AlterTableCmd);
+ 
+ 			Assert(IsA(constraint, Constraint));
+ 			Assert(constraint->contype == CONSTR_OPERATOR_EXCLUSION);
+ 
+ 			/*
+ 			 * Don't need to validate against existing rows during
+ 			 * creation.
+ 			 */
+ 			constraint->skip_validation = true;
+ 
+ 			altercmd->subtype = AT_AddConstraint;
+ 			altercmd->name = NULL;
+ 			altercmd->def = (Node *) constraint;
+ 			alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+ 		}
+ 
+ 		opxlist = list_make1(alterstmt);
+ 	}
+ 
+ 	/*
  	 * Output results.
  	 */
  	stmt->tableElts = cxt.columns;
***************
*** 241,246 **** transformCreateStmt(CreateStmt *stmt, const char *queryString)
--- 284,290 ----
  	result = lappend(cxt.blist, stmt);
  	result = list_concat(result, cxt.alist);
  	result = list_concat(result, save_alist);
+ 	result = list_concat(result, opxlist);
  
  	return result;
  }
***************
*** 514,519 **** transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
--- 558,567 ----
  			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
  			break;
  
+ 		case CONSTR_OPERATOR_EXCLUSION:
+ 			cxt->opxconstraints = lappend(cxt->opxconstraints, constraint);
+ 			break;
+ 
  		case CONSTR_NULL:
  		case CONSTR_NOTNULL:
  		case CONSTR_DEFAULT:
***************
*** 734,739 **** transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
--- 782,793 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			if (index_stmt == NULL)
+ 			{
+ 				index_close(parent_index, AccessShareLock);
+ 				continue;
+ 			}
+ 
  			/* Copy comment on index */
  			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
  			{
***************
*** 872,877 **** generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
--- 926,941 ----
  		elog(ERROR, "cache lookup failed for relation %u", source_relid);
  	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
  
+ 	/*
+ 	 * Skip indexes for operator exclusion constraints, those should
+ 	 * not be copied when INCLUDING INDEXES is specified.
+ 	 */
+ 	if (idxrelrec->relopxconstraints != 0)
+ 	{
+ 		ReleaseSysCache(ht_idxrel);
+ 		return NULL;
+ 	}
+ 
  	/* Fetch pg_index tuple for source index from relcache entry */
  	ht_idx = source_idx->rd_indextuple;
  	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
***************
*** 1842,1847 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1906,1912 ----
  	cxt.ckconstraints = NIL;
  	cxt.fkconstraints = NIL;
  	cxt.ixconstraints = NIL;
+ 	cxt.opxconstraints = NIL;
  	cxt.inh_indexes = NIL;
  	cxt.blist = NIL;
  	cxt.alist = NIL;
***************
*** 1889,1894 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 1954,1962 ----
  				 */
  				if (IsA(cmd->def, Constraint))
  				{
+ 					transformOpxConstraints(pstate, &cxt, stmt->relation,
+ 											  (Constraint *) cmd->def);
+ 
  					transformTableConstraint(pstate, &cxt,
  											 (Constraint *) cmd->def);
  					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
***************
*** 1947,1953 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
  	}
  	cxt.alist = NIL;
  
! 	/* Append any CHECK or FK constraints to the commands list */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
--- 2015,2024 ----
  	}
  	cxt.alist = NIL;
  
! 	/*
! 	 * Append any CHECK, FK or operator exclusion constraints to the
! 	 * commands list
! 	 */
  	foreach(l, cxt.ckconstraints)
  	{
  		newcmd = makeNode(AlterTableCmd);
***************
*** 1962,1967 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2033,2045 ----
  		newcmd->def = (Node *) lfirst(l);
  		newcmds = lappend(newcmds, newcmd);
  	}
+ 	foreach(l, cxt.opxconstraints)
+ 	{
+ 		newcmd = makeNode(AlterTableCmd);
+ 		newcmd->subtype = AT_AddConstraint;
+ 		newcmd->def = (Node *) lfirst(l);
+ 		newcmds = lappend(newcmds, newcmd);
+ 	}
  
  	/* Close rel but keep lock */
  	relation_close(rel, NoLock);
***************
*** 2254,2256 **** setSchemaName(char *context_schema, char **stmt_schema_name)
--- 2332,2385 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ static void
+ transformOpxConstraints(ParseState *pstate, CreateStmtContext *cxt,
+ 						RangeVar *relation, Constraint *constraint)
+ {
+ 	ListCell			*lc;
+ 	RangeTblEntry		*rte;
+ 
+ 	/*
+ 	 * Put the parent table into the rtable so that the expressions can refer
+ 	 * to its fields without qualification.
+ 	 */
+ 	rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	/* preprocess index expressions */
+ 	foreach(lc, constraint->operator_exclusion)
+ 	{
+ 		List			*pair = lfirst(lc);
+ 		IndexElem		*ielem;
+ 
+ 		Assert(list_length(pair) == 2);
+ 
+ 		ielem = linitial(pair);
+ 		Assert(IsA(ielem, IndexElem));
+ 
+ 		if (ielem->expr)
+ 		{
+ 			ielem->expr = transformExpr(pstate, ielem->expr);
+ 
+ 			/*
+ 			 * We check only that the result type is legitimate; this
+ 			 * is for consistency with what transformWhereClause()
+ 			 * checks for the predicate.  DefineIndex() will make more
+ 			 * checks.
+ 			 */
+ 			if (expression_returns_set(ielem->expr))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("index expression cannot return a set")
+ 							));
+ 		}
+ 	}
+ 
+ 	/* preprocess index predicate */
+ 	if (constraint->where_clause)
+ 		constraint->where_clause = transformWhereClause(
+ 			pstate, constraint->where_clause, "WHERE");
+ }
+ 
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 797,802 **** ProcessUtility(Node *parsetree,
--- 797,803 ----
  							stmt->indexParams,	/* parameters */
  							(Expr *) stmt->whereClause,
  							stmt->options,
+ 							NULL,
  							stmt->unique,
  							stmt->primary,
  							stmt->isconstraint,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 148,153 **** static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
--- 148,155 ----
  					   int prettyFlags);
  static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  							int prettyFlags);
+ static char *pg_get_opxdef_worker(Oid indexrelid, Oid *operators,
+ 							int prettyFlags);
  static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
  				   int prettyFlags);
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
***************
*** 1244,1249 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
--- 1246,1282 ----
  
  				break;
  			}
+ 		case CONSTRAINT_OPX:
+ 			{
+ 				bool	 isnull;
+ 				Oid		 indexOid = conForm->conindid;
+ 				Datum	 val;
+ 				Datum	*keys;
+ 				int		 nKeys;
+ 				int		 i;
+ 				Oid		*operators;
+ 
+ 				val = SysCacheGetAttr(CONSTROID, tup,
+ 									  Anum_pg_constraint_conoperators,
+ 									  &isnull);
+ 				if (isnull)
+ 					elog(ERROR, "null conoperators for constraint %u",
+ 						 constraintId);
+ 
+ 				deconstruct_array(DatumGetArrayTypeP(val),
+ 								  OIDOID, sizeof(Oid), true, 'i',
+ 								  &keys, NULL, &nKeys);
+ 
+ 				operators = palloc(nKeys * sizeof(Oid));
+ 				for(i = 0; i < nKeys; i++)
+ 					operators[i] = DatumGetObjectId(keys[i]);
+ 
+ 				appendStringInfo(&buf, pg_get_opxdef_worker(indexOid,
+ 															operators,
+ 															prettyFlags));
+ 
+ 				break;
+ 			}
  		default:
  			elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
  			break;
***************
*** 1291,1296 **** decompile_column_index_array(Datum column_index_array, Oid relId,
--- 1324,1558 ----
  	}
  }
  
+ static char *
+ pg_get_opxdef_worker(Oid indexrelid, Oid *operators, int prettyFlags)
+ {
+ 	HeapTuple	ht_idx;
+ 	HeapTuple	ht_idxrel;
+ 	HeapTuple	ht_am;
+ 	Form_pg_index idxrec;
+ 	Form_pg_class idxrelrec;
+ 	Form_pg_am	amrec;
+ 	List	   *indexprs;
+ 	ListCell   *indexpr_item;
+ 	List	   *context;
+ 	Oid			indrelid;
+ 	int			keyno;
+ 	Oid			keycoltype;
+ 	Datum		indclassDatum;
+ 	Datum		indoptionDatum;
+ 	bool		isnull;
+ 	oidvector  *indclass;
+ 	int2vector *indoption;
+ 	StringInfoData buf;
+ 	char	   *str;
+ 	char	   *sep;
+ 	Oid			tblspc;
+ 
+ 	/*
+ 	 * Fetch the pg_index tuple by the Oid of the index
+ 	 */
+ 	ht_idx = SearchSysCache(INDEXRELID,
+ 							ObjectIdGetDatum(indexrelid),
+ 							0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idx))
+ 		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+ 	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+ 
+ 	indrelid = idxrec->indrelid;
+ 	Assert(indexrelid == idxrec->indexrelid);
+ 
+ 	/* Must get indclass and indoption the hard way */
+ 	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indclass, &isnull);
+ 	Assert(!isnull);
+ 	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+ 	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indoption, &isnull);
+ 	Assert(!isnull);
+ 	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ 
+ 	/*
+ 	 * Fetch the pg_class tuple of the index relation
+ 	 */
+ 	ht_idxrel = SearchSysCache(RELOID,
+ 							   ObjectIdGetDatum(indexrelid),
+ 							   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_idxrel))
+ 		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+ 	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+ 
+ 	/*
+ 	 * Fetch the pg_am tuple of the index' access method
+ 	 */
+ 	ht_am = SearchSysCache(AMOID,
+ 						   ObjectIdGetDatum(idxrelrec->relam),
+ 						   0, 0, 0);
+ 	if (!HeapTupleIsValid(ht_am))
+ 		elog(ERROR, "cache lookup failed for access method %u",
+ 			 idxrelrec->relam);
+ 	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+ 
+ 	/*
+ 	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+ 	 * versions of the expressions and predicate, because we want to display
+ 	 * non-const-folded expressions.)
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+ 	{
+ 		Datum		exprsDatum;
+ 		bool		isnull;
+ 		char	   *exprsString;
+ 
+ 		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									 Anum_pg_index_indexprs, &isnull);
+ 		Assert(!isnull);
+ 		exprsString = TextDatumGetCString(exprsDatum);
+ 		indexprs = (List *) stringToNode(exprsString);
+ 		pfree(exprsString);
+ 	}
+ 	else
+ 		indexprs = NIL;
+ 
+ 	indexpr_item = list_head(indexprs);
+ 
+ 	context = deparse_context_for(get_rel_name(indrelid), indrelid);
+ 
+ 	/*
+ 	 * Start the index definition.	Note that the index's name should never be
+ 	 * schema-qualified, but the indexed rel's name may be.
+ 	 */
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "EXCLUDE USING %s (",
+ 					 quote_identifier(NameStr(amrec->amname)));
+ 
+ 	/*
+ 	 * Report the indexed attributes
+ 	 */
+ 	sep = "";
+ 	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+ 	{
+ 		AttrNumber	attnum = idxrec->indkey.values[keyno];
+ 		int16		opt = indoption->values[keyno];
+ 		char	   *opName;
+ 
+ 		appendStringInfoString(&buf, sep);
+ 		sep = ", ";
+ 
+ 		if (attnum != 0)
+ 		{
+ 			/* Simple index column */
+ 			char	   *attname;
+ 
+ 			attname = get_relid_attribute_name(indrelid, attnum);
+ 			appendStringInfoString(&buf, quote_identifier(attname));
+ 			keycoltype = get_atttype(indrelid, attnum);
+ 		}
+ 		else
+ 		{
+ 			/* expressional index */
+ 			Node	   *indexkey;
+ 
+ 			if (indexpr_item == NULL)
+ 				elog(ERROR, "too few entries in indexprs list");
+ 			indexkey = (Node *) lfirst(indexpr_item);
+ 			indexpr_item = lnext(indexpr_item);
+ 			/* Deparse */
+ 			str = deparse_expression_pretty(indexkey, context, false, false,
+ 											prettyFlags, 0);
+ 
+ 			/* Need parens if it's not a bare function call */
+ 			if (indexkey && IsA(indexkey, FuncExpr) &&
+ 				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+ 				appendStringInfoString(&buf, str);
+ 			else
+ 				appendStringInfo(&buf, "(%s)", str);
+ 
+ 			keycoltype = exprType(indexkey);
+ 		}
+ 
+ 		/* Add the operator class name, if not default */
+ 		get_opclass_name(indclass->values[keyno], keycoltype, &buf);
+ 
+ 		/* Add options if relevant */
+ 		if (amrec->amcanorder)
+ 		{
+ 			/* if it supports sort ordering, report DESC and NULLS opts */
+ 			if (opt & INDOPTION_DESC)
+ 			{
+ 				appendStringInfo(&buf, " DESC");
+ 				/* NULLS FIRST is the default in this case */
+ 				if (!(opt & INDOPTION_NULLS_FIRST))
+ 					appendStringInfo(&buf, " NULLS LAST");
+ 			}
+ 			else
+ 			{
+ 				if (opt & INDOPTION_NULLS_FIRST)
+ 					appendStringInfo(&buf, " NULLS FIRST");
+ 			}
+ 		}
+ 
+ 		/* Add operator exclusion constraint */
+ 		appendStringInfo(&buf, " WITH ");
+ 
+ 		opName = generate_operator_name(operators[keyno], keycoltype,
+ 										keycoltype);
+ 
+ 		appendStringInfo(&buf, "%s", opName);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	/*
+ 	 * If it has options, append "WITH (options)"
+ 	 */
+ 	str = flatten_reloptions(indexrelid);
+ 	if (str)
+ 	{
+ 		appendStringInfo(&buf, " WITH (%s)", str);
+ 		pfree(str);
+ 	}
+ 
+ 	/*
+ 	 * If it's in a nondefault tablespace, say so, but only if requested
+ 	 */
+ 	tblspc = get_rel_tablespace(indexrelid);
+ 	if (OidIsValid(tblspc))
+ 		appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
+ 						 quote_identifier(get_tablespace_name(tblspc)));
+ 
+ 	/*
+ 	 * If it's a partial index, decompile and append the predicate
+ 	 */
+ 	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+ 	{
+ 		Node	   *node;
+ 		Datum		predDatum;
+ 		bool		isnull;
+ 		char	   *predString;
+ 
+ 		/* Convert text string to node tree */
+ 		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ 									Anum_pg_index_indpred, &isnull);
+ 		Assert(!isnull);
+ 		predString = TextDatumGetCString(predDatum);
+ 		node = (Node *) stringToNode(predString);
+ 		pfree(predString);
+ 
+ 		/* Deparse */
+ 		str = deparse_expression_pretty(node, context, false, false,
+ 										prettyFlags, 0);
+ 		appendStringInfo(&buf, " WHERE (%s)", str);
+ 	}
+ 
+ 	/* Clean up */
+ 	ReleaseSysCache(ht_idx);
+ 	ReleaseSysCache(ht_idxrel);
+ 	ReleaseSysCache(ht_am);
+ 
+ 	return buf.data;
+ }
  
  /* ----------
   * get_expr			- Decompile an expression tree
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 60,68 ****
--- 60,70 ----
  #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"
***************
*** 3038,3043 **** CheckConstraintFetch(Relation relation)
--- 3040,3147 ----
  }
  
  /*
+  * Load any operator exclusion constraints for the relation.
+  */
+ void
+ RelationGetOpExclusionConstraints(Relation indexRelation, Oid **operators,
+ 								  Oid **procs, uint16 **strategies)
+ {
+ 	Relation	 conrel;
+ 	SysScanDesc	 conscan;
+ 	ScanKeyData	 skey[1];
+ 	HeapTuple	 htup;
+ 	Datum		 val;
+ 	bool		 isnull;
+ 	bool		 found	= false;
+ 	Oid			 relid	= indexRelation->rd_index->indrelid;
+ 	Oid			*ops	= NULL;
+ 	Oid			*funcs	= NULL;
+ 	uint16		*strats = NULL;
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	conrel = heap_open(ConstraintRelationId, AccessShareLock);
+ 	conscan = systable_beginscan(conrel, ConstraintRelidIndexId, true,
+ 								 SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ 	{
+ 		Form_pg_constraint	 conform = (Form_pg_constraint) GETSTRUCT(htup);
+ 		ArrayType			*arr;
+ 		int					 nelem;
+ 		int					 i;
+ 
+ 		/* We want check constraints only */
+ 		if (conform->contype != CONSTRAINT_OPX)
+ 			continue;
+ 
+ 		if (conform->conindid != indexRelation->rd_id)
+ 			continue;
+ 
+ 		if (found)
+ 			elog(ERROR, "unexpected operator exclusion constraint record "
+ 				 "found for rel %s", RelationGetRelationName(indexRelation));
+ 
+ 		val = fastgetattr(htup,
+ 						  Anum_pg_constraint_conoperators,
+ 						  conrel->rd_att, &isnull);
+ 		if (isnull)
+ 			elog(ERROR, "null conoperators for rel %s",
+ 				 RelationGetRelationName(indexRelation));
+ 
+ 		arr = DatumGetArrayTypeP(val);	/* ensure not toasted */
+ 		nelem = ARR_DIMS(arr)[0];
+ 		if (ARR_NDIM(arr) != 1 ||
+ 			nelem != indexRelation->rd_rel->relnatts ||
+ 			nelem > INDEX_MAX_KEYS ||
+ 			ARR_HASNULL(arr) ||
+ 			ARR_ELEMTYPE(arr) != OIDOID)
+ 			elog(ERROR, "conoperators is not a 1-D Oid array");
+ 
+ 		ops	   = palloc(sizeof(Oid) * nelem);
+ 		funcs  = palloc(sizeof(Oid) * nelem);
+ 		strats = palloc(sizeof(uint16) * nelem);
+ 
+ 		memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * nelem);
+ 
+ 		for (i = 0; i < nelem; i++)
+ 		{
+ 			funcs[i] = get_opcode(ops[i]);
+ 			if (!OidIsValid(funcs[i]))
+ 				elog(ERROR, "could not find function for operator: %d",
+ 					 ops[i]);
+ 
+ 			strats[i] = get_op_opfamily_strategy(
+ 				ops[i], indexRelation->rd_opfamily[i]);
+ 			if (strats[i] == InvalidStrategy)
+ 				elog(ERROR, "could not find strategy for operator %d in "
+ 					 "family %d", ops[i], indexRelation->rd_opfamily[i]);
+ 		}
+ 
+ 		if ((Pointer) arr != DatumGetPointer(val))
+ 			pfree(arr);				/* free de-toasted copy, if any */
+ 
+ 		found = true;
+ 	}
+ 
+ 	systable_endscan(conscan);
+ 	heap_close(conrel, AccessShareLock);
+ 
+ 	if (!found)
+ 		elog(ERROR, "constraint record missing for rel %s",
+ 			 RelationGetRelationName(indexRelation));
+ 
+ 	*operators	= ops;
+ 	*procs		= funcs;
+ 	*strategies = strats;
+ 
+ 	return;
+ }
+ 
+ /*
   * RelationGetIndexList -- get a list of OIDs of indexes on this relation
   *
   * The index list is created only if someone requests it.  We scan pg_index
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2855,2860 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 2855,2861 ----
  				 strcmp(te->desc, "CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "DEFAULT") == 0 ||
  				 strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ 				 strcmp(te->desc, "EXCLUSION CONSTRAINT") == 0 ||
  				 strcmp(te->desc, "INDEX") == 0 ||
  				 strcmp(te->desc, "RULE") == 0 ||
  				 strcmp(te->desc, "TRIGGER") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 3680,3685 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3680,3686 ----
  				i_condeferred,
  				i_contableoid,
  				i_conoid,
+ 				i_condef,
  				i_tablespace,
  				i_options;
  	int			ntups;
***************
*** 3710,3716 **** getIndexes(TableInfo tblinfo[], int numTables)
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80200)
  		{
  			appendPQExpBuffer(query,
  							  "SELECT t.tableoid, t.oid, "
--- 3711,3745 ----
  		 * assume an index won't have more than one internal dependency.
  		 */
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			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, "
! 					 "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
! 							  "c.oid AS conoid, "
! 							  "(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, "
***************
*** 3858,3863 **** getIndexes(TableInfo tblinfo[], int numTables)
--- 3887,3893 ----
  		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,3901 **** 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 we found a constraint matching the index, create an
--- 3925,3931 ----
  			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
  			contype = *(PQgetvalue(res, j, i_contype));
  
! 			if (contype == 'p' || contype == 'u' || contype == 'x')
  			{
  				/*
  				 * If we found a constraint matching the index, create an
***************
*** 3913,3919 **** getIndexes(TableInfo tblinfo[], int numTables)
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				constrinfo[j].condef = NULL;
  				constrinfo[j].confrelid = InvalidOid;
  				constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
  				constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
--- 3943,3952 ----
  				constrinfo[j].contable = tbinfo;
  				constrinfo[j].condomain = NULL;
  				constrinfo[j].contype = contype;
! 				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';
***************
*** 10912,10917 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
--- 10945,10975 ----
  						 NULL, NULL);
  		}
  	}
+ 	else if (coninfo->contype == 'x')
+ 	{
+ 		appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(q, "    ADD CONSTRAINT %s %s;\n",
+ 						  fmtId(coninfo->dobj.name),
+ 						  coninfo->condef);
+ 
+ 		appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ 						  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 		appendPQExpBuffer(delq, "%s ",
+ 						  fmtId(tbinfo->dobj.name));
+ 		appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ 						  fmtId(coninfo->dobj.name));
+ 
+ 		ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ 					 coninfo->dobj.name,
+ 					 tbinfo->dobj.namespace->dobj.name,
+ 					 NULL,
+ 					 tbinfo->rolname, false,
+ 					 "EXCLUSION CONSTRAINT", SECTION_POST_DATA,
+ 					 q->data, delq->data, NULL,
+ 					 coninfo->dobj.dependencies, coninfo->dobj.nDeps,
+ 					 NULL, NULL);
+ 	}
  	else
  	{
  		write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 1100,1105 **** describeOneTableDetails(const char *schemaname,
--- 1100,1106 ----
  	struct
  	{
  		int16		checks;
+ 		int16		opxconstraints;
  		char		relkind;
  		bool		hasindex;
  		bool		hasrules;
***************
*** 1121,1127 **** describeOneTableDetails(const char *schemaname,
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80400)
  	{
  		printfPQExpBuffer(&buf,
  			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
--- 1122,1143 ----
  	initPQExpBuffer(&tmpbuf);
  
  	/* Get general table info */
! 	if (pset.sversion >= 80500)
! 	{
! 		printfPQExpBuffer(&buf,
! 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
! 						  "c.relhastriggers, c.relhasoids, "
! 						  "%s, c.reltablespace, c.relopxconstraints \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, "
***************
*** 1189,1194 **** describeOneTableDetails(const char *schemaname,
--- 1205,1212 ----
  		strdup(PQgetvalue(res, 0, 6)) : 0;
  	tableinfo.tablespace = (pset.sversion >= 80000) ?
  		atooid(PQgetvalue(res, 0, 7)) : 0;
+ 	tableinfo.opxconstraints = pset.sversion >= 80500 ?
+ 		atoi(PQgetvalue(res, 0, 8)) : 0;
  	PQclear(res);
  	res = NULL;
  
***************
*** 1642,1647 **** describeOneTableDetails(const char *schemaname,
--- 1660,1698 ----
  			PQclear(result);
  		}
  
+ 		/* print operator exclusion constraints */
+ 		if (tableinfo.opxconstraints)
+ 		{
+ 			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,
+ 									_("Operator 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)
  		{
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 424,437 **** DATA(insert ( 1249 tableoid			26 0 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 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 } }
  
  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_));
--- 424,438 ----
  { 1259, {"relkind"},	   18, -1, 0,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relnatts"},	   21, -1, 0,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
  { 1259, {"relchecks"},	   21, -1, 0,	2, 17, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relopxconstraints"},	   21, -1, 0,	2, 18, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhasoids"},    16, -1, 0,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
! { 1259, {"relhaspkey"},    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_));
***************
*** 450,463 **** DATA(insert ( 1259 relistemp		16 -1 0 1  14 0 -1 -1 t p c t f f t 0 _null_));
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 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_));
--- 451,465 ----
  DATA(insert ( 1259 relkind			18 -1 0 1  15 0 -1 -1 t p c t f f t 0 _null_));
  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 relopxconstraints		21 -1 0 2  18 0 -1 -1 t p s t f f t 0 _null_));
! DATA(insert ( 1259 relhasoids		16 -1 0 1  19 0 -1 -1 t p c t f f t 0 _null_));
! DATA(insert ( 1259 relhaspkey		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_));
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 54,59 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83)
--- 54,60 ----
  	 * contain entries with negative attnums for system attributes.
  	 */
  	int2		relchecks;		/* # of CHECK constraints for class */
+ 	int2		relopxconstraints;	/* # of opx constraints for class */
  	bool		relhasoids;		/* T if we generate OIDs for rows of rel */
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
***************
*** 87,93 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					25
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 88,94 ----
   * ----------------
   */
  
! #define Natts_pg_class					26
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 105,118 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #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
  
  /* ----------------
   *		initial contents of pg_class
--- 106,120 ----
  #define Anum_pg_class_relkind			15
  #define Anum_pg_class_relnatts			16
  #define Anum_pg_class_relchecks			17
! #define Anum_pg_class_relopxconstraints	18
! #define Anum_pg_class_relhasoids		19
! #define Anum_pg_class_relhaspkey		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,136 **** 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_ ));
  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_ ));
  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_ ));
  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_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
--- 126,138 ----
   */
  
  /* 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 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  #define		  RELKIND_INDEX			  'i'		/* secondary index */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 120,125 **** CATALOG(pg_constraint,2606)
--- 120,133 ----
  	Oid			conffeqop[1];
  
  	/*
+ 	 * If constraint is an operator exclusion constraint, these are
+ 	 * the strategy numbers used for constraint. The size of the array
+ 	 * is equal to the number of attributes in the index referenced by
+ 	 * conindid.
+ 	 */
+ 	Oid			conoperators[1];
+ 
+ 	/*
  	 * If a check constraint, nodeToString representation of expression
  	 */
  	text		conbin;
***************
*** 141,147 **** typedef FormData_pg_constraint *Form_pg_constraint;
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					21
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
--- 149,155 ----
   *		compiler constants for pg_constraint
   * ----------------
   */
! #define Natts_pg_constraint					22
  #define Anum_pg_constraint_conname			1
  #define Anum_pg_constraint_connamespace		2
  #define Anum_pg_constraint_contype			3
***************
*** 161,168 **** 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
  
  
  /* Valid values for contype */
--- 169,177 ----
  #define Anum_pg_constraint_conpfeqop		17
  #define Anum_pg_constraint_conppeqop		18
  #define Anum_pg_constraint_conffeqop		19
! #define Anum_pg_constraint_conoperators		20
! #define Anum_pg_constraint_conbin			21
! #define Anum_pg_constraint_consrc			22
  
  
  /* Valid values for contype */
***************
*** 170,175 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 179,185 ----
  #define CONSTRAINT_FOREIGN			'f'
  #define CONSTRAINT_PRIMARY			'p'
  #define CONSTRAINT_UNIQUE			'u'
+ #define CONSTRAINT_OPX				'x'
  
  /*
   * Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
***************
*** 209,214 **** extern Oid CreateConstraintEntry(const char *constraintName,
--- 219,225 ----
  					  char foreignUpdateType,
  					  char foreignDeleteType,
  					  char foreignMatchType,
+ 					  const Oid *exclusion_constraint,
  					  Node *conExpr,
  					  const char *conBin,
  					  const char *conSrc,
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
  			char *indexRelationName,
  			Oid indexRelationId,
  			char *accessMethodName,
***************
*** 26,31 **** extern void DefineIndex(RangeVar *heapRelation,
--- 26,32 ----
  			List *attributeList,
  			Expr *predicate,
  			List *options,
+ 			Oid *exclusion_constraint,
  			bool unique,
  			bool primary,
  			bool isconstraint,
***************
*** 45,50 **** extern char *makeObjectName(const char *name1, const char *name2,
--- 46,53 ----
  extern char *ChooseRelationName(const char *name1, const char *name2,
  				   const char *label, Oid namespaceid);
  extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+ extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ 						   char *accessMethodName, Oid accessMethodId);
  
  /* commands/functioncmds.c */
  extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 328,332 **** extern void RegisterExprContextCallback(ExprContext *econtext,
--- 328,337 ----
  extern void UnregisterExprContextCallback(ExprContext *econtext,
  							  ExprContextCallbackFunction function,
  							  Datum arg);
+ extern bool index_exclusion_constraint(Relation heap, Relation index,
+ 									   TupleTableSlot *new_slot,
+ 									   ItemPointer tupleid, Datum *values,
+ 									   bool *isnull, IndexInfo *indexInfo,
+ 									   EState *estate, bool errorOK);
  
  #endif   /* EXECUTOR_H  */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 58,63 **** typedef struct IndexInfo
--- 58,66 ----
  	List	   *ii_ExpressionsState;	/* list of ExprState */
  	List	   *ii_Predicate;	/* list of Expr */
  	List	   *ii_PredicateState;		/* list of ExprState */
+ 	Oid		   *ii_ExclusionConstraintOps;
+ 	Oid		   *ii_ExclusionConstraintProcs;
+ 	uint16	   *ii_ExclusionConstraintStrats;
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1395,1400 **** typedef enum ConstrType			/* types of constraints */
--- 1395,1401 ----
  	CONSTR_CHECK,
  	CONSTR_PRIMARY,
  	CONSTR_UNIQUE,
+ 	CONSTR_OPERATOR_EXCLUSION,
  	CONSTR_FOREIGN,
  	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
  	CONSTR_ATTR_NOT_DEFERRABLE,
***************
*** 1429,1439 **** 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): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
--- 1430,1445 ----
  	Node	   *raw_expr;		/* expr, as untransformed parse tree */
  	char	   *cooked_expr;	/* expr, as nodeToString representation */
  
! 	/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
  	List	   *keys;			/* String nodes naming referenced column(s) */
  	List	   *options;		/* options from WITH clause */
  	char	   *indexspace;		/* index tablespace; NULL for default */
  
+ 	/* Fields used for index constraints: */
+ 	List	   *operator_exclusion;	/* list of (colname, operator) pairs */
+ 	char	   *using_method;		/* access method for this constraint */
+ 	Node	   *where_clause;		/* predicate for exclusion constraint */
+ 
  	/* Fields used for FOREIGN KEY constraints: */
  	RangeVar   *pktable;		/* Primary key table */
  	List	   *fk_attrs;		/* Attributes of foreign key */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 143,148 **** PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
--- 143,149 ----
  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)
*** a/src/include/utils/errcodes.h
--- b/src/include/utils/errcodes.h
***************
*** 167,172 ****
--- 167,173 ----
  #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')
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 43,48 **** extern Oid	RelationGetOidIndex(Relation relation);
--- 43,52 ----
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
  extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
+ extern void RelationGetOpExclusionConstraints(Relation indexRelation,
+ 											  Oid **operators,
+ 											  Oid **procs,
+ 											  uint16 **strategies);
  
  extern void RelationSetIndexList(Relation relation,
  					 List *indexIds, Oid oidIndex);
*** a/src/pl/plpgsql/src/plerrcodes.h
--- b/src/pl/plpgsql/src/plerrcodes.h
***************
*** 304,309 ****
--- 304,313 ----
  },
  
  {
+ 	"exclusion_violation", ERRCODE_EXCLUSION_VIOLATION
+ },
+ 
+ {
  	"invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE
  },
  
*** a/src/test/regress/input/constraints.source
--- b/src/test/regress/input/constraints.source
***************
*** 366,368 **** COMMIT;
--- 366,397 ----
  SELECT * FROM unique_tbl;
  
  DROP TABLE unique_tbl;
+ 
+ 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>');
+ -- should fail
+ 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;
+ 
+ 
*** a/src/test/regress/output/constraints.source
--- b/src/test/regress/output/constraints.source
***************
*** 512,514 **** SELECT * FROM unique_tbl;
--- 512,542 ----
  (5 rows)
  
  DROP TABLE unique_tbl;
+ CREATE TABLE circles (
+   c1 CIRCLE,
+   c2 TEXT,
+   EXCLUDE USING gist
+     (c1 WITH &&, (c2::circle) WITH ~=)
+     WHERE (circle_center(c1) <> '(0,0)')
+ );
+ NOTICE:  ALTER TABLE / ADD 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>');
+ -- should fail
+ INSERT INTO circles VALUES('<(20,20), 10>', '<(0,0), 5>');
+ ERROR:  conflicting key value violates operator exclusion constraint "circles_c1_exclusion"
+ DETAIL:  Tuple "(c1, (c2::circle))=(<(20,20),10>, <(0,0),5>)" conflicts with existing tuple "(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:  conflicting key value violates operator exclusion constraint "circles_c1_exclusion1"
+ DETAIL:  Tuple "(c1, (c2::circle))=(<(0,0),5>, <(0,0),5>)" conflicts with existing tuple "(c1, (c2::circle))=(<(0,0),5>, <(0,0),5>)".
+ DROP TABLE circles;
#231Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#230)
Re: operator exclusion constraints

On Wed, Dec 2, 2009 at 12:18 AM, Jeff Davis <pgsql@j-davis.com> wrote:

On Tue, 2009-12-01 at 23:19 -0500, Robert Haas wrote:

For parity with unique constraints, I think that the message:

operator exclusion constraint violation detected: %s

should be changed to:

conflicting key value violates operator exclusion constraint "%s"

Done, and updated tests.

In ATAddOperatorExclusionConstraint, "streatagy" is misspelled.

Fixed.

Other than that, it looks good to me.

Great, thanks for the detailed review!

Marked as Ready for Committer.

...Robert

#232Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#230)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

On Tue, 2009-12-01 at 23:19 -0500, Robert Haas wrote:

For parity with unique constraints, I think that the message:

operator exclusion constraint violation detected: %s

should be changed to:

conflicting key value violates operator exclusion constraint "%s"

Done, and updated tests.

I'm starting to go through this patch now. I thought the consensus
was to refer to them as just "exclusion constraints"? I'm not seeing
that the word "operator" really adds anything.

regards, tom lane

#233Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#232)
Re: operator exclusion constraints

On Thu, 2009-12-03 at 19:00 -0500, Tom Lane wrote:

I'm starting to go through this patch now. I thought the consensus
was to refer to them as just "exclusion constraints"? I'm not seeing
that the word "operator" really adds anything.

I assume you're referring to the name used in documentation and error
messages. I didn't see a clear consensus, but the relevant thread is
here:

http://archives.postgresql.org/message-id/1258227283.708.108.camel@jdavis

"Exclusion Constraints" is fine with me, as are the other options listed
in that email.

Regards,
Jeff Davis

#234Robert Haas
robertmhaas@gmail.com
In reply to: Jeff Davis (#233)
Re: operator exclusion constraints

On Thu, Dec 3, 2009 at 7:42 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Thu, 2009-12-03 at 19:00 -0500, Tom Lane wrote:

I'm starting to go through this patch now.  I thought the consensus
was to refer to them as just "exclusion constraints"?  I'm not seeing
that the word "operator" really adds anything.

I assume you're referring to the name used in documentation and error
messages. I didn't see a clear consensus, but the relevant thread is
here:

http://archives.postgresql.org/message-id/1258227283.708.108.camel@jdavis

"Exclusion Constraints" is fine with me, as are the other options listed
in that email.

Yeah, I don't remember any such consensus either, but it's not a dumb
name. I have been idly wondering throughout this process whether we
should try to pick a name that conveys the fact that these constraints
are inextricably tied to the opclass/index machinery - but I'm not
sure it's possible to really give that flavor in a short phrase, or
that it's actually important to do so. IOW... "whatever". :-)

...Robert

#235David E. Wheeler
david@kineticode.com
In reply to: Robert Haas (#234)
Re: operator exclusion constraints

On Dec 3, 2009, at 6:26 PM, Robert Haas wrote:

Yeah, I don't remember any such consensus either, but it's not a dumb
name. I have been idly wondering throughout this process whether we
should try to pick a name that conveys the fact that these constraints
are inextricably tied to the opclass/index machinery - but I'm not
sure it's possible to really give that flavor in a short phrase, or
that it's actually important to do so. IOW... "whatever". :-)

"Whatever constraints"? "Operator Whatevers"? "WhatEVER"s? I like it.

David

#236Noname
tomas@tuxteam.de
In reply to: David E. Wheeler (#235)
Re: operator exclusion constraints

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Thu, Dec 03, 2009 at 08:38:06PM -0800, David E. Wheeler wrote:

[...]

"Whatever constraints"? "Operator Whatevers"? "WhatEVER"s? I like it.

drigting serioulsy off-topic: there's precedent for that in the most
venerable piece of free software; TeX has a "whatsit node" (basically an
extension mechanism).

Regards
- -- tomás
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)

iD8DBQFLGKM7Bcgs9XrR2kYRAuHFAJ0ZZYzlXHJEgwEbsraAlKVI58yLAgCfU4Cz
n+0vobY0HxROigSHUGog7QI=
=MWH+
-----END PGP SIGNATURE-----

#237Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#234)
Re: operator exclusion constraints

Robert Haas <robertmhaas@gmail.com> writes:

On Thu, Dec 3, 2009 at 7:42 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Thu, 2009-12-03 at 19:00 -0500, Tom Lane wrote:

I'm starting to go through this patch now. �I thought the consensus
was to refer to them as just "exclusion constraints"? �I'm not seeing
that the word "operator" really adds anything.

I assume you're referring to the name used in documentation and error
messages. I didn't see a clear consensus, but the relevant thread is
here:

http://archives.postgresql.org/message-id/1258227283.708.108.camel@jdavis

"Exclusion Constraints" is fine with me, as are the other options listed
in that email.

Yeah, I don't remember any such consensus either, but it's not a dumb
name. I have been idly wondering throughout this process whether we
should try to pick a name that conveys the fact that these constraints
are inextricably tied to the opclass/index machinery - but I'm not
sure it's possible to really give that flavor in a short phrase, or
that it's actually important to do so. IOW... "whatever". :-)

Well, unique constraints are tied to the opclass/index machinery too.

Unless there's loud squawks I'm going to exercise committer's
prerogative and make all the docs and messages just say "exclusion
constraint".

regards, tom lane

#238Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#237)
Re: operator exclusion constraints

On Fri, 2009-12-04 at 11:35 -0500, Tom Lane wrote:

Unless there's loud squawks I'm going to exercise committer's
prerogative and make all the docs and messages just say "exclusion
constraint".

Sounds fine to me.

Regards,
Jeff Davis

#239Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#237)
Re: operator exclusion constraints

On Dec 4, 2009, at 11:35 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Thu, Dec 3, 2009 at 7:42 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Thu, 2009-12-03 at 19:00 -0500, Tom Lane wrote:

I'm starting to go through this patch now. I thought the consensus
was to refer to them as just "exclusion constraints"? I'm not
seeing
that the word "operator" really adds anything.

I assume you're referring to the name used in documentation and
error
messages. I didn't see a clear consensus, but the relevant thread is
here:

http://archives.postgresql.org/message-id/1258227283.708.108.camel@jdavis

"Exclusion Constraints" is fine with me, as are the other options
listed
in that email.

Yeah, I don't remember any such consensus either, but it's not a dumb
name. I have been idly wondering throughout this process whether we
should try to pick a name that conveys the fact that these
constraints
are inextricably tied to the opclass/index machinery - but I'm not
sure it's possible to really give that flavor in a short phrase, or
that it's actually important to do so. IOW... "whatever". :-)

Well, unique constraints are tied to the opclass/index machinery too.

Unless there's loud squawks I'm going to exercise committer's
prerogative and make all the docs and messages just say "exclusion
constraint".

Go for it. Membership has its privileges. :-)

...Robert

#240David Fetter
david@fetter.org
In reply to: Tom Lane (#237)
Re: operator exclusion constraints

On Fri, Dec 04, 2009 at 11:35:52AM -0500, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Thu, Dec 3, 2009 at 7:42 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Thu, 2009-12-03 at 19:00 -0500, Tom Lane wrote:

I'm starting to go through this patch now. �I thought the
consensus was to refer to them as just "exclusion constraints"?
�I'm not seeing that the word "operator" really adds anything.

I assume you're referring to the name used in documentation and
error messages. I didn't see a clear consensus, but the relevant
thread is here:

http://archives.postgresql.org/message-id/1258227283.708.108.camel@jdavis

"Exclusion Constraints" is fine with me, as are the other options
listed in that email.

Yeah, I don't remember any such consensus either, but it's not a
dumb name. I have been idly wondering throughout this process
whether we should try to pick a name that conveys the fact that
these constraints are inextricably tied to the opclass/index
machinery - but I'm not sure it's possible to really give that
flavor in a short phrase, or that it's actually important to do
so. IOW... "whatever". :-)

Well, unique constraints are tied to the opclass/index machinery
too.

Unless there's loud squawks I'm going to exercise committer's
prerogative and make all the docs and messages just say "exclusion
constraint".

We have "constraint exclusion" already, which could make this
confusing. How about either the original "operator exclusion
constraint" or the slightly easier "whatever constraint" names?

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#241Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#230)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

[ exclusion constraints patch ]

Still working on this patch. I ran into something I didn't like at all:

+                 if (newrel != NULL)
+                     ereport(ERROR,
+                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                              errmsg("cannot rewrite table while adding "
+                                     "operator exclusion constraint")));

This would be bad enough if the restriction were what the message
alleges, ie, you can't write an ALTER TABLE that both rewrites the heap
and adds an exclusion constraint. However, actually the error also
occurs if you issue a rewriting ALTER TABLE against a table that already
has an exclusion constraint. That raises it from annoyance to
showstopper IMO.

There is not a whole lot that can be done to fix this given the design
that exclusion checking is supposed to be done in ATRewriteTable ---
when that runs, we of course have not built the new index yet, so
there's no way to use it to check the constraint.

I think the right way to go at it is to drop that part of the patch
and instead have index_build execute a separate pass to check the
constraint after it's built an exclusion-constraint index. In the
typical case where you're just doing ALTER TABLE ADD CONSTRAINT EXCLUDE,
this ends up being the same amount of work since there's no need to
run an ATRewriteTable scan at all. It would be extra work if someone
tried to add multiple exclusion constraints in one ALTER; but I have a
hard time getting excited about that, especially in view of the fact
that the cost of the index searches is going to swamp the heapscan
anyway.

This approach would also mean that the constraint is re-verified
if someone does a REINDEX. I think this is appropriate behavior
since reindexing a regular unique index also causes re-verification.
However I can imagine someone objecting on grounds of cost ---
it would probably about double the cost of reindexing compared
to assuming the constraint is still good. We could probably teach
index_build to skip the check pass during REINDEX, but I don't
really want to.

Comments?

regards, tom lane

#242Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#241)
Re: operator exclusion constraints

On Sun, 2009-12-06 at 10:46 -0500, Tom Lane wrote:

This would be bad enough if the restriction were what the message
alleges, ie, you can't write an ALTER TABLE that both rewrites the heap
and adds an exclusion constraint. However, actually the error also
occurs if you issue a rewriting ALTER TABLE against a table that already
has an exclusion constraint. That raises it from annoyance to
showstopper IMO.

The following works as expected for me:

create table foo(i int, j int, k int);

-- the following causes the error in question:
alter table foo add exclude(i with =),
alter column k type text;

alter table foo add exclude(i with =); -- works
alter table foo alter column k type text; -- works

So the workaround is simply to break the ALTER into two statements.

(Aside: the error message should probably have a DETAIL component
telling the user to break up the ALTER commands into separate actions.)

Aha -- I think I see the problem you're having: if you try to rewrite
one of the columns contained in the exclusion constraint, you get that
error:

create table bar_exclude(i int, exclude (i with =));

-- raises same error, which is confusing
alter table bar_exclude alter column i type text;

Compared with UNIQUE:
create table bar_unique(i int unique);
alter table bar_unique alter column i type text; -- works

However, I think we _want_ exclusion constraints to fail in that case.
Unique constraints can succeed because both types support UNIQUE, and
the semantics are the same. But in the case of exclusion constraints,
it's quite likely that the semantics will be different or the named
operator won't exist at all. I think it's more fair to compare with a
unique index on an expression:

create table bar_unique2(i int);
create unique index bar_unique2_idx on bar_unique2 ((i + 1));

alter table bar_unique2 alter column i type text;
ERROR: operator does not exist: text + integer

You could make the argument that we should do the same thing: try to
re-apply the expression on top of the new column. The only situation
where I can imagine that would be useful is if you are using exclusion
constraints in place of UNIQUE (even then, it's different, because it
uses operator names, not BTEqualStrategyNumber for the default btree
opclass).

If the user alters the type of a column that's part of an exclusion
constraint, the semantics are complex enough that I think we should
inform them that they need to drop the constraint, change the column,
and re-add it. So, my personal opinion is that we need to change the
error message (and probably have two distinct error messages for the two
cases) rather than changing the algorithm.

Comments?

Regards,
Jeff Davis

#243Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#242)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

Aha -- I think I see the problem you're having: if you try to rewrite
one of the columns contained in the exclusion constraint, you get that
error:

It fails for me regardless of which column is actually modified.
It could be this is a consequence of other changes I've been making,
but given the way ALTER TABLE works it's hard to see why the specific
column being modified would matter. Anything that forces a table
rewrite would lead to running through this code path.

I don't really agree with your argument that it's okay to reject the
case of altering the underlying column type, anyhow. There's enough
commonality of operator names that it's sensible to try to preserve
the constraint across a change. Consider replacing a circle with a
polygon, say. A no-overlap restriction is still sensible.

regards, tom lane

#244Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#243)
Re: operator exclusion constraints

On Sun, 2009-12-06 at 14:06 -0500, Tom Lane wrote:

It fails for me regardless of which column is actually modified.
It could be this is a consequence of other changes I've been making,
but given the way ALTER TABLE works it's hard to see why the specific
column being modified would matter. Anything that forces a table
rewrite would lead to running through this code path.

In ATAddOperatorExclusionConstraint():

tab->constraints = lappend(tab->constraints, newcon);

if that isn't done, it doesn't go into the code path with that error
message at all.

I don't really agree with your argument that it's okay to reject the
case of altering the underlying column type, anyhow. There's enough
commonality of operator names that it's sensible to try to preserve
the constraint across a change. Consider replacing a circle with a
polygon, say. A no-overlap restriction is still sensible.

Let's say someone is changing from box to a postgis geometry
representing a box. For boxes, "=" actually means "equal area" (aside:
this is insane); but for polygons, "=" mean "equal". Changing in the
other direction is bad, as well. I know operators mostly follow
convention most of the time, but I'm concerned with the subtle (and
surprising) differences.

But if someone is changing a column type, which causes a table rewrite,
hopefully they've planned it. I'll look into doing it as you suggest.

Regards,
Jeff Davis

#245Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#244)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

... I'll look into doing it as you suggest.

I'm already working with a pretty-heavily-editorialized version.
Don't worry about it.

regards, tom lane

#246Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#230)
Re: operator exclusion constraints

Jeff Davis <pgsql@j-davis.com> writes:

[ exclusion constraint patch ]

Applied after quite a lot of editorialization. For future reference,
here is a summary of what I did:

* Reworded and renamed stuff to try to be consistent about calling these
things "exclusion constraints". The original code and docs bore quite
a few traces of the various other terminologies, which is not too
surprising given the history but would have been confusing to future
readers of the code.

* Moved the verification of new exclusion constraints into index_build
processing as per discussion.

* Unified the EXCLUDE parsing path with the existing unique/pkey path
by the expedient of adding an excludeOpNames list to IndexStmt. This
got rid of quite a lot of duplicated code, and fixed a number of bizarre
corner cases like the bogus wording of the index creation NOTICE messages.

* Cleaned up some things that didn't really meet project practices.
To mention a couple: one aspect of the "try to make the patch look
like it had always been there" rule is to insert new stuff in unsurprising
places. Adding code at the end of a list or file very often doesn't meet
this test. I tried to put the EXCLUDE constraint stuff beside
UNIQUE/PRIMARY KEY handling where possible. Another pet peeve that was
triggered a lot in this patch is that you seemed to be intent on fitting
ereport() calls into 80 columns no matter what, and would break the error
message texts in random places to make it fit. There's a good practical
reason not to do that: it makes it hard to grep the source code for an
error message. You can break at phrase boundaries if you must, but
in general I prefer to just let the text go past the right margin.

There are a couple of loose ends yet:

* I made CREATE...LIKE processing handle exclusion constraints the same
as unique/pkey constraints, ie, they're processed by INCLUDING INDEXES.
There was some discussion of rearranging things to make these be processed
by INCLUDING CONSTRAINTS instead, but no consensus that I recall. In
any case, failing to copy them at all is clearly no good.

* I'm not too satisfied with the behavior of psql's \d:

regression=# create table foo (f1 int primary key using index tablespace ts1,
regression(# f2 int, EXCLUDE USING btree (f2 WITH =) using index tablespace ts1,
regression(# f3 int, EXCLUDE USING btree (f3 WITH =) DEFERRABLE INITIALLY DEFERRED);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"
NOTICE: CREATE TABLE / EXCLUDE will create implicit index "foo_f2_exclusion" for table "foo"
NOTICE: CREATE TABLE / EXCLUDE will create implicit index "foo_f3_exclusion" for table "foo"
CREATE TABLE
regression=# \d foo
Table "public.foo"
Column | Type | Modifiers
--------+---------+-----------
f1 | integer | not null
f2 | integer |
f3 | integer |
Indexes:
"foo_pkey" PRIMARY KEY, btree (f1), tablespace "ts1"
"foo_f2_exclusion" btree (f2), tablespace "ts1"
"foo_f3_exclusion" btree (f3) DEFERRABLE INITIALLY DEFERRED
Exclusion constraints:
"foo_f2_exclusion" EXCLUDE USING btree (f2 WITH =)
"foo_f3_exclusion" EXCLUDE USING btree (f3 WITH =) DEFERRABLE INITIALLY DEFERRED

regression=#

This might have been defensible back when the idea was to keep constraints
decoupled from indexes, but now it just looks bizarre. We should either
get rid of the "Exclusion constraints:" display and attach the info to
the index entries, or hide indexes that are attached to exclusion
constraints. I lean to the former on the grounds of the precedent for
unique/pkey indexes --- which is not totally arbitrary, since an index
is usable as a query index regardless of its function as a constraint.
It's probably a debatable point though.

regards, tom lane

#247Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#246)
Re: operator exclusion constraints

On Mon, 2009-12-07 at 00:26 -0500, Tom Lane wrote:

Jeff Davis <pgsql@j-davis.com> writes:

[ exclusion constraint patch ]

Applied after quite a lot of editorialization. For future reference,
here is a summary of what I did:

Thank you for the suggestions, and the other constructive criticism
during development.

Regards,
Jeff Davis

#248Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#246)
Re: operator exclusion constraints

Awhile back I wrote:

* I'm not too satisfied with the behavior of psql's \d:

regression=# create table foo (f1 int primary key using index tablespace ts1,
regression(# f2 int, EXCLUDE USING btree (f2 WITH =) using index tablespace ts1,
regression(# f3 int, EXCLUDE USING btree (f3 WITH =) DEFERRABLE INITIALLY DEFERRED);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"
NOTICE: CREATE TABLE / EXCLUDE will create implicit index "foo_f2_exclusion" for table "foo"
NOTICE: CREATE TABLE / EXCLUDE will create implicit index "foo_f3_exclusion" for table "foo"
CREATE TABLE
regression=# \d foo
Table "public.foo"
Column | Type | Modifiers
--------+---------+-----------
f1 | integer | not null
f2 | integer |
f3 | integer |
Indexes:
"foo_pkey" PRIMARY KEY, btree (f1), tablespace "ts1"
"foo_f2_exclusion" btree (f2), tablespace "ts1"
"foo_f3_exclusion" btree (f3) DEFERRABLE INITIALLY DEFERRED
Exclusion constraints:
"foo_f2_exclusion" EXCLUDE USING btree (f2 WITH =)
"foo_f3_exclusion" EXCLUDE USING btree (f3 WITH =) DEFERRABLE INITIALLY DEFERRED

regression=#

This might have been defensible back when the idea was to keep constraints
decoupled from indexes, but now it just looks bizarre. We should either
get rid of the "Exclusion constraints:" display and attach the info to
the index entries, or hide indexes that are attached to exclusion
constraints. I lean to the former on the grounds of the precedent for
unique/pkey indexes --- which is not totally arbitrary, since an index
is usable as a query index regardless of its function as a constraint.
It's probably a debatable point though.

Attached is a patch against HEAD that folds exclusion constraints into
\d's regular indexes list. With this, the above example produces

Table "public.foo"
Column | Type | Modifiers
--------+---------+-----------
f1 | integer | not null
f2 | integer |
f3 | integer |
Indexes:
"foo_pkey" PRIMARY KEY, btree (f1), tablespace "ts1"
"foo_f2_exclusion" EXCLUDE USING btree (f2 WITH =), tablespace "ts1"
"foo_f3_exclusion" EXCLUDE USING btree (f3 WITH =) DEFERRABLE INITIALLY DEFERRED

Any objections?

regards, tom lane

#249Greg Stark
gsstark@mit.edu
In reply to: Tom Lane (#248)
Re: operator exclusion constraints

On Thu, Mar 11, 2010 at 5:29 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Indexes:
    "foo_pkey" PRIMARY KEY, btree (f1), tablespace "ts1"
    "foo_f2_exclusion" btree (f2), tablespace "ts1"
    "foo_f3_exclusion" btree (f3) DEFERRABLE INITIALLY DEFERRED
Exclusion constraints:
    "foo_f2_exclusion" EXCLUDE USING btree (f2 WITH =)
    "foo_f3_exclusion" EXCLUDE USING btree (f3 WITH =) DEFERRABLE INITIALLY DEFERRED

This might have been defensible back when the idea was to keep constraints
decoupled from indexes, but now it just looks bizarre.

The only really bizarre part is the "DEFERRABLE INITIALLY DEFERRED" on
the index.

 We should either
get rid of the "Exclusion constraints:" display and attach the info to
the index entries, or hide indexes that are attached to exclusion
constraints.  I lean to the former on the grounds of the precedent for
unique/pkey indexes --- which is not totally arbitrary, since an index
is usable as a query index regardless of its function as a constraint.
It's probably a debatable point though.

There is a third option -- print PRIMARY keys twice, once as a btree
index and again as a constraint where it says somehting like "USING
index foo_pkey"
I think in the long term that would be best -- especially if we
combine it with a patch to be able to create a new primary key
constraint using an existing index. That's something people have been
asking for anyways and I think it's a somewhat important property that
these lines can be copy pasted and run nearly as-is to recreate the
objects.

I definitely agree that your other proposed way to go is worse. I
think people need a list of indexes in one place.

So given the current syntax for creating these I think your proposed
change is the least worst alternative.

--
greg

#250Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#249)
Re: operator exclusion constraints

Greg Stark <gsstark@mit.edu> writes:

There is a third option -- print PRIMARY keys twice, once as a btree
index and again as a constraint where it says somehting like "USING
index foo_pkey"

No, that's exactly what I hate about the current behavior for exclusion
constraints, and I'd like it even less for more-common options like
primary or unique constraints. \d is too d*mn verbose already; there is
no percentage in making it even longer by printing redundant entries for
many indexes.

One thing I did think about was converting PK/UNIQUE indexes to be
printed by pg_get_constraintdef() too, rather than assembling an ad-hoc
output for them as we do now. This would make the code a bit simpler
but would involve some small changes in the output --- in particular,
you wouldn't see any indication that they were btrees, since there's
no place for that in standard constraint syntax. On balance it didn't
seem like an improvement, although it would partially respond to your
desire to have the output be cut-and-pasteable.

regards, tom lane

#251Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#248)
Re: operator exclusion constraints

On Thu, 2010-03-11 at 00:29 -0500, Tom Lane wrote:

Patch changes:

Indexes:
"foo_pkey" PRIMARY KEY, btree (f1), tablespace "ts1"
"foo_f2_exclusion" btree (f2), tablespace "ts1"
"foo_f3_exclusion" btree (f3) DEFERRABLE INITIALLY DEFERRED
Exclusion constraints:
"foo_f2_exclusion" EXCLUDE USING btree (f2 WITH =)
"foo_f3_exclusion" EXCLUDE USING btree (f3 WITH =) DEFERRABLE INITIALLY DEFERRED

To:

Indexes:
"foo_pkey" PRIMARY KEY, btree (f1), tablespace "ts1"
"foo_f2_exclusion" EXCLUDE USING btree (f2 WITH =), tablespace "ts1"
"foo_f3_exclusion" EXCLUDE USING btree (f3 WITH =) DEFERRABLE INITIALLY DEFERRED

Any objections?

Looks good to me.

Regards,
Jeff Davis