*** a/contrib/pg_stat_statements/pg_stat_statements.c
--- b/contrib/pg_stat_statements/pg_stat_statements.c
***************
*** 1418,1423 **** JumbleQuery(pgssJumbleState *jstate, Query *query)
--- 1418,1424 ----
  	JumbleRangeTable(jstate, query->rtable);
  	JumbleExpr(jstate, (Node *) query->jointree);
  	JumbleExpr(jstate, (Node *) query->targetList);
+ 	APP_JUMB(query->specClause);
  	JumbleExpr(jstate, (Node *) query->returningList);
  	JumbleExpr(jstate, (Node *) query->groupClause);
  	JumbleExpr(jstate, query->havingQual);
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
***************
*** 2541,2551 **** compute_infobits(uint16 infomask, uint16 infomask2)
   * (the last only for HeapTupleSelfUpdated, since we
   * cannot obtain cmax from a combocid generated by another transaction).
   * See comments for struct HeapUpdateFailureData for additional info.
   */
  HTSU_Result
  heap_delete(Relation relation, ItemPointer tid,
  			CommandId cid, Snapshot crosscheck, bool wait,
! 			HeapUpdateFailureData *hufd)
  {
  	HTSU_Result result;
  	TransactionId xid = GetCurrentTransactionId();
--- 2541,2555 ----
   * (the last only for HeapTupleSelfUpdated, since we
   * cannot obtain cmax from a combocid generated by another transaction).
   * See comments for struct HeapUpdateFailureData for additional info.
+  *
+  * If 'kill' is true, we're killing a tuple we just inserted in the same
+  * command. Instead of the normal visibility checks, we check that the tuple
+  * was inserted by the current transaction and given command id.
   */
  HTSU_Result
  heap_delete(Relation relation, ItemPointer tid,
  			CommandId cid, Snapshot crosscheck, bool wait,
! 			HeapUpdateFailureData *hufd, bool kill)
  {
  	HTSU_Result result;
  	TransactionId xid = GetCurrentTransactionId();
***************
*** 2601,2607 **** heap_delete(Relation relation, ItemPointer tid,
  	tp.t_self = *tid;
  
  l1:
! 	result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
  
  	if (result == HeapTupleInvisible)
  	{
--- 2605,2620 ----
  	tp.t_self = *tid;
  
  l1:
! 	if (!kill)
! 		result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
! 	else
! 	{
! 		if (tp.t_data->t_choice.t_heap.t_xmin != xid ||
! 			tp.t_data->t_choice.t_heap.t_field3.t_cid != cid)
! 			elog(ERROR, "attempted to kill a tuple inserted by another transaction or command");
! 		result = HeapTupleMayBeUpdated;
! 	}
! 
  
  	if (result == HeapTupleInvisible)
  	{
***************
*** 2870,2876 **** simple_heap_delete(Relation relation, ItemPointer tid)
  	result = heap_delete(relation, tid,
  						 GetCurrentCommandId(true), InvalidSnapshot,
  						 true /* wait for commit */ ,
! 						 &hufd);
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
--- 2883,2889 ----
  	result = heap_delete(relation, tid,
  						 GetCurrentCommandId(true), InvalidSnapshot,
  						 true /* wait for commit */ ,
! 						 &hufd, false);
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
***************
*** 3950,3957 **** l3:
  
  	if (result == HeapTupleInvisible)
  	{
! 		UnlockReleaseBuffer(*buffer);
! 		elog(ERROR, "attempted to lock invisible tuple");
  	}
  	else if (result == HeapTupleBeingUpdated)
  	{
--- 3963,3975 ----
  
  	if (result == HeapTupleInvisible)
  	{
! 		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
! 		/*
! 		 * this is expected if we're locking a tuple in ON DUPLICATE KEY LOCK
! 		 * FOR UPDATE mode, and the inserting transaction killed the tuple in
! 		 * the same* transaction.
! 		 */
! 		return HeapTupleInvisible;
  	}
  	else if (result == HeapTupleBeingUpdated)
  	{
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 1644,1651 **** BuildIndexInfo(Relation index)
  		ii->ii_ExclusionStrats = NULL;
  	}
  
  	/* other info */
- 	ii->ii_Unique = indexStruct->indisunique;
  	ii->ii_ReadyForInserts = IndexIsReady(indexStruct);
  
  	/* initialize index-build state to default */
--- 1644,1692 ----
  		ii->ii_ExclusionStrats = NULL;
  	}
  
+ 	/*
+ 	 * fetch info for checking unique constraints. (this is currently only
+ 	 * used by ExecCheckIndexConstraints(), for INSERT ... ON DUPLICATE KEY.
+ 	 * In regular insertions, the index AM handles the unique check itself.
+ 	 * Might make sense to do this lazily, only when needed)
+ 	 */
+ 	if (indexStruct->indisunique)
+ 	{
+ 		int			ncols = index->rd_rel->relnatts;
+ 
+ 		if (index->rd_rel->relam != BTREE_AM_OID)
+ 			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+ 
+ 		ii->ii_UniqueOps = (Oid *) palloc(sizeof(Oid) * ncols);
+ 		ii->ii_UniqueProcs = (Oid *) palloc(sizeof(Oid) * ncols);
+ 		ii->ii_UniqueStrats = (uint16 *) palloc(sizeof(uint16) * ncols);
+ 
+ 		/*
+ 		 * We have to look up the operator's strategy number.  This
+ 		 * provides a cross-check that the operator does match the index.
+ 		 */
+ 		/* We need the func OIDs and strategy numbers too */
+ 		for (i = 0; i < ncols; i++)
+ 		{
+ 			ii->ii_UniqueStrats[i] = BTEqualStrategyNumber;
+ 			ii->ii_UniqueOps[i] =
+ 				get_opfamily_member(index->rd_opfamily[i],
+ 									index->rd_opcintype[i],
+ 									index->rd_opcintype[i],
+ 									ii->ii_UniqueStrats[i]);
+ 			ii->ii_UniqueProcs[i] = get_opcode(ii->ii_UniqueOps[i]);
+ 		}
+ 		ii->ii_Unique = true;
+ 	}
+ 	else
+ 	{
+ 		ii->ii_UniqueOps = NULL;
+ 		ii->ii_UniqueProcs = NULL;
+ 		ii->ii_UniqueStrats = NULL;
+ 		ii->ii_Unique = false;
+ 	}
+ 
  	/* other info */
  	ii->ii_ReadyForInserts = IndexIsReady(indexStruct);
  
  	/* initialize index-build state to default */
***************
*** 2566,2575 **** IndexCheckExclusion(Relation heapRelation,
  		/*
  		 * Check that this tuple has no conflicts.
  		 */
! 		check_exclusion_constraint(heapRelation,
  								   indexRelation, indexInfo,
  								   &(heapTuple->t_self), values, isnull,
! 								   estate, true, false);
  	}
  
  	heap_endscan(scan);
--- 2607,2616 ----
  		/*
  		 * Check that this tuple has no conflicts.
  		 */
! 		check_exclusion_or_unique_constraint(heapRelation,
  								   indexRelation, indexInfo,
  								   &(heapTuple->t_self), values, isnull,
! 								   estate, true, false, true, NULL);
  	}
  
  	heap_endscan(scan);
*** a/src/backend/commands/constraint.c
--- b/src/backend/commands/constraint.c
***************
*** 170,178 **** unique_key_recheck(PG_FUNCTION_ARGS)
  		 * For exclusion constraints we just do the normal check, but now it's
  		 * okay to throw error.
  		 */
! 		check_exclusion_constraint(trigdata->tg_relation, indexRel, indexInfo,
  								   &(new_row->t_self), values, isnull,
! 								   estate, false, false);
  	}
  
  	/*
--- 170,178 ----
  		 * For exclusion constraints we just do the normal check, but now it's
  		 * okay to throw error.
  		 */
! 		check_exclusion_or_unique_constraint(trigdata->tg_relation, indexRel, indexInfo,
  								   &(new_row->t_self), values, isnull,
! 								   estate, false, false, true, NULL);
  	}
  
  	/*
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2284,2290 **** CopyFrom(CopyState cstate)
  
  				if (resultRelInfo->ri_NumIndices > 0)
  					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
! 														   estate);
  
  				/* AFTER ROW INSERT Triggers */
  				ExecARInsertTriggers(estate, resultRelInfo, tuple,
--- 2284,2290 ----
  
  				if (resultRelInfo->ri_NumIndices > 0)
  					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
! 														   estate, false);
  
  				/* AFTER ROW INSERT Triggers */
  				ExecARInsertTriggers(estate, resultRelInfo, tuple,
***************
*** 2391,2397 **** CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
  			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
  			recheckIndexes =
  				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
! 									  estate);
  			ExecARInsertTriggers(estate, resultRelInfo,
  								 bufferedTuples[i],
  								 recheckIndexes);
--- 2391,2397 ----
  			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
  			recheckIndexes =
  				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
! 									  estate, false);
  			ExecARInsertTriggers(estate, resultRelInfo,
  								 bufferedTuples[i],
  								 recheckIndexes);
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 990,996 **** ExecCloseIndices(ResultRelInfo *resultRelInfo)
   *
   *		This returns a list of index OIDs for any unique or exclusion
   *		constraints that are deferred and that had
!  *		potential (unconfirmed) conflicts.
   *
   *		CAUTION: this must not be called for a HOT update.
   *		We can't defend against that here for lack of info.
--- 990,997 ----
   *
   *		This returns a list of index OIDs for any unique or exclusion
   *		constraints that are deferred and that had
!  *		potential (unconfirmed) conflicts. (if noErrorOnDuplicate == true,
!  *		the same is done for non-deferred constraints)
   *
   *		CAUTION: this must not be called for a HOT update.
   *		We can't defend against that here for lack of info.
***************
*** 1000,1006 **** ExecCloseIndices(ResultRelInfo *resultRelInfo)
  List *
  ExecInsertIndexTuples(TupleTableSlot *slot,
  					  ItemPointer tupleid,
! 					  EState *estate)
  {
  	List	   *result = NIL;
  	ResultRelInfo *resultRelInfo;
--- 1001,1008 ----
  List *
  ExecInsertIndexTuples(TupleTableSlot *slot,
  					  ItemPointer tupleid,
! 					  EState *estate,
! 					  bool noErrorOnDuplicate)
  {
  	List	   *result = NIL;
  	ResultRelInfo *resultRelInfo;
***************
*** 1092,1100 **** ExecInsertIndexTuples(TupleTableSlot *slot,
--- 1094,1107 ----
  		 * For a deferrable unique index, we tell the index AM to just detect
  		 * possible non-uniqueness, and we add the index OID to the result
  		 * list if further checking is needed.
+ 		 *
+ 		 * For a IGNORE/REJECT DUPLICATES insertion, just detect possible
+ 		 * non-uniqueness, and tell the caller if it failed.
  		 */
  		if (!indexRelation->rd_index->indisunique)
  			checkUnique = UNIQUE_CHECK_NO;
+ 		else if (noErrorOnDuplicate)
+ 			checkUnique = UNIQUE_CHECK_PARTIAL;
  		else if (indexRelation->rd_index->indimmediate)
  			checkUnique = UNIQUE_CHECK_YES;
  		else
***************
*** 1121,1133 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  		 */
  		if (indexInfo->ii_ExclusionOps != NULL)
  		{
! 			bool		errorOK = !indexRelation->rd_index->indimmediate;
  
  			satisfiesConstraint =
! 				check_exclusion_constraint(heapRelation,
  										   indexRelation, indexInfo,
  										   tupleid, values, isnull,
! 										   estate, false, errorOK);
  		}
  
  		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
--- 1128,1142 ----
  		 */
  		if (indexInfo->ii_ExclusionOps != NULL)
  		{
! 			bool		errorOK = (!indexRelation->rd_index->indimmediate &&
! 								   !noErrorOnDuplicate);
  
  			satisfiesConstraint =
! 				check_exclusion_or_unique_constraint(heapRelation,
  										   indexRelation, indexInfo,
  										   tupleid, values, isnull,
! 										   estate, false, errorOK, false,
! 										   NULL);
  		}
  
  		if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
***************
*** 1146,1163 **** ExecInsertIndexTuples(TupleTableSlot *slot,
  	return result;
  }
  
  /*
!  * Check for violation of an exclusion constraint
   *
   * heap: the table containing the new tuple
   * index: the index supporting the exclusion constraint
   * indexInfo: info about the index, including the exclusion properties
!  * tupleid: heap TID of the new tuple we have just inserted
   * values, isnull: the *index* column values computed for the new tuple
   * estate: an EState we can do evaluation in
   * newIndex: if true, we are trying to build a new index (this affects
   *		only the wording of error messages)
   * errorOK: if true, don't throw error for violation
   *
   * Returns true if OK, false if actual or potential violation
   *
--- 1155,1294 ----
  	return result;
  }
  
+ /* ----------------------------------------------------------------
+  *		ExecCheckIndexConstraints
+  *
+  *		This routine checks if a tuple violates any unique or
+  *		exclusion constraints. If no conflict, returns true.
+  *		Otherwise returns false, and the TID of the conflicting
+  *		tuple is returned in *conflictTid
+  *
+  *
+  *		Note that this doesn't lock the values in any way, so it's
+  *		possible that a conflicting tuple is inserted immediately
+  *		after this returns, and a later insert with the same values
+  *		still conflicts. But this can be used for a pre-check before
+  *		insertion.
+  * ----------------------------------------------------------------
+  */
+ bool
+ ExecCheckIndexConstraints(TupleTableSlot *slot,
+ 						  EState *estate, ItemPointer conflictTid)
+ {
+ 	ResultRelInfo *resultRelInfo;
+ 	int			i;
+ 	int			numIndices;
+ 	RelationPtr relationDescs;
+ 	Relation	heapRelation;
+ 	IndexInfo **indexInfoArray;
+ 	ExprContext *econtext;
+ 	Datum		values[INDEX_MAX_KEYS];
+ 	bool		isnull[INDEX_MAX_KEYS];
+ 	ItemPointerData invalidItemPtr;
+ 
+ 	ItemPointerSetInvalid(&invalidItemPtr);
+ 
+ 
+ 	/*
+ 	 * Get information from the result relation info structure.
+ 	 */
+ 	resultRelInfo = estate->es_result_relation_info;
+ 	numIndices = resultRelInfo->ri_NumIndices;
+ 	relationDescs = resultRelInfo->ri_IndexRelationDescs;
+ 	indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
+ 	heapRelation = resultRelInfo->ri_RelationDesc;
+ 
+ 	/*
+ 	 * We will use the EState's per-tuple context for evaluating predicates
+ 	 * and index expressions (creating it if it's not already there).
+ 	 */
+ 	econtext = GetPerTupleExprContext(estate);
+ 
+ 	/* Arrange for econtext's scan tuple to be the tuple under test */
+ 	econtext->ecxt_scantuple = slot;
+ 
+ 	/*
+ 	 * for each index, form and insert the index tuple
+ 	 */
+ 	for (i = 0; i < numIndices; i++)
+ 	{
+ 		Relation	indexRelation = relationDescs[i];
+ 		IndexInfo  *indexInfo;
+ 		bool		satisfiesConstraint;
+ 
+ 		if (indexRelation == NULL)
+ 			continue;
+ 
+ 		indexInfo = indexInfoArray[i];
+ 
+ 		if (!indexInfo->ii_Unique && !indexInfo->ii_ExclusionOps)
+ 			continue;
+ 
+ 		/* If the index is marked as read-only, ignore it */
+ 		if (!indexInfo->ii_ReadyForInserts)
+ 			continue;
+ 
+ 		/* Check for partial index */
+ 		if (indexInfo->ii_Predicate != NIL)
+ 		{
+ 			List	   *predicate;
+ 
+ 			/*
+ 			 * If predicate state not set up yet, create it (in the estate's
+ 			 * per-query context)
+ 			 */
+ 			predicate = indexInfo->ii_PredicateState;
+ 			if (predicate == NIL)
+ 			{
+ 				predicate = (List *)
+ 					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
+ 									estate);
+ 				indexInfo->ii_PredicateState = predicate;
+ 			}
+ 
+ 			/* Skip this index-update if the predicate isn't satisfied */
+ 			if (!ExecQual(predicate, econtext, false))
+ 				continue;
+ 		}
+ 
+ 		/*
+ 		 * FormIndexDatum fills in its values and isnull parameters with the
+ 		 * appropriate values for the column(s) of the index.
+ 		 */
+ 		FormIndexDatum(indexInfo,
+ 					   slot,
+ 					   estate,
+ 					   values,
+ 					   isnull);
+ 
+ 		satisfiesConstraint =
+ 			check_exclusion_or_unique_constraint(heapRelation,
+ 												 indexRelation, indexInfo,
+ 												 &invalidItemPtr, values, isnull,
+ 												 estate, false, true, true,
+ 												 conflictTid);
+ 		if (!satisfiesConstraint)
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
  /*
!  * Check for violation of an exclusion or unique constraint
   *
   * heap: the table containing the new tuple
   * index: the index supporting the exclusion constraint
   * indexInfo: info about the index, including the exclusion properties
!  * tupleid: heap TID of the new tuple we have just inserted (invalid if we
!  *		haven't inserted a new tuple yet)
   * values, isnull: the *index* column values computed for the new tuple
   * estate: an EState we can do evaluation in
   * newIndex: if true, we are trying to build a new index (this affects
   *		only the wording of error messages)
   * errorOK: if true, don't throw error for violation
+  * wait: if true, wait for conflicting transaction to finish, even if !errorOK
+  * conflictTid: if not-NULL, the TID of conflicting tuple is returned here.
   *
   * Returns true if OK, false if actual or potential violation
   *
***************
*** 1169,1182 **** ExecInsertIndexTuples(TupleTableSlot *slot,
   *
   * When errorOK is false, we'll throw error on violation, so a false result
   * is impossible.
   */
  bool
! check_exclusion_constraint(Relation heap, Relation index, IndexInfo *indexInfo,
  						   ItemPointer tupleid, Datum *values, bool *isnull,
! 						   EState *estate, bool newIndex, bool errorOK)
  {
! 	Oid		   *constr_procs = indexInfo->ii_ExclusionProcs;
! 	uint16	   *constr_strats = indexInfo->ii_ExclusionStrats;
  	Oid		   *index_collations = index->rd_indcollation;
  	int			index_natts = index->rd_index->indnatts;
  	IndexScanDesc index_scan;
--- 1300,1319 ----
   *
   * When errorOK is false, we'll throw error on violation, so a false result
   * is impossible.
+  *
+  * Note: The indexam is normally responsible for checking unique constraints,
+  * so this normally only needs to be used for exclusion constraints. But this
+  * is done when doing a "pre-check" for conflicts with "INSERT ... ON DUPLICATE
+  * KEY", before inserting the actual tuple.
   */
  bool
! check_exclusion_or_unique_constraint(Relation heap, Relation index, IndexInfo *indexInfo,
  						   ItemPointer tupleid, Datum *values, bool *isnull,
! 						   EState *estate, bool newIndex,
! 						   bool errorOK, bool wait, ItemPointer conflictTid)
  {
! 	Oid		   *constr_procs;
! 	uint16	   *constr_strats;
  	Oid		   *index_collations = index->rd_indcollation;
  	int			index_natts = index->rd_index->indnatts;
  	IndexScanDesc index_scan;
***************
*** 1190,1195 **** check_exclusion_constraint(Relation heap, Relation index, IndexInfo *indexInfo,
--- 1327,1343 ----
  	TupleTableSlot *existing_slot;
  	TupleTableSlot *save_scantuple;
  
+ 	if (indexInfo->ii_ExclusionOps)
+ 	{
+ 		constr_procs = indexInfo->ii_ExclusionProcs;
+ 		constr_strats = indexInfo->ii_ExclusionStrats;
+ 	}
+ 	else
+ 	{
+ 		constr_procs = indexInfo->ii_UniqueProcs;
+ 		constr_strats = indexInfo->ii_UniqueStrats;
+ 	}
+ 
  	/*
  	 * If any of the input values are NULL, the constraint check is assumed to
  	 * pass (i.e., we assume the operators are strict).
***************
*** 1253,1259 **** retry:
  		/*
  		 * Ignore the entry for the tuple we're trying to check.
  		 */
! 		if (ItemPointerEquals(tupleid, &tup->t_self))
  		{
  			if (found_self)		/* should not happen */
  				elog(ERROR, "found self tuple multiple times in index \"%s\"",
--- 1401,1408 ----
  		/*
  		 * Ignore the entry for the tuple we're trying to check.
  		 */
! 		if (ItemPointerIsValid(tupleid) &&
! 			ItemPointerEquals(tupleid, &tup->t_self))
  		{
  			if (found_self)		/* should not happen */
  				elog(ERROR, "found self tuple multiple times in index \"%s\"",
***************
*** 1287,1295 **** retry:
  		 * we're not supposed to raise error, just return the fact of the
  		 * potential conflict without waiting to see if it's real.
  		 */
! 		if (errorOK)
  		{
  			conflict = true;
  			break;
  		}
  
--- 1436,1446 ----
  		 * we're not supposed to raise error, just return the fact of the
  		 * potential conflict without waiting to see if it's real.
  		 */
! 		if (errorOK && !wait)
  		{
  			conflict = true;
+ 			if (conflictTid)
+ 				*conflictTid = tup->t_self;
  			break;
  		}
  
***************
*** 1314,1319 **** retry:
--- 1465,1478 ----
  		/*
  		 * We have a definite conflict.  Report it.
  		 */
+ 		if (errorOK)
+ 		{
+ 			conflict = true;
+ 			if (conflictTid)
+ 				*conflictTid = tup->t_self;
+ 			break;
+ 		}
+ 
  		error_new = BuildIndexValueDescription(index, values, isnull);
  		error_existing = BuildIndexValueDescription(index, existing_values,
  													existing_isnull);
***************
*** 1345,1350 **** retry:
--- 1504,1512 ----
  	 * However, it is possible to define exclusion constraints for which that
  	 * wouldn't be true --- for instance, if the operator is <>. So we no
  	 * longer complain if found_self is still false.
+ 	 *
+ 	 * It would also not be true in the pre-check mode, when we haven't
+ 	 * inserted a tuple yet.
  	 */
  
  	econtext->ecxt_scantuple = save_scantuple;
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 39,44 ****
--- 39,45 ----
  
  #include "access/htup_details.h"
  #include "access/xact.h"
+ #include "catalog/catalog.h"
  #include "commands/trigger.h"
  #include "executor/executor.h"
  #include "executor/nodeModifyTable.h"
***************
*** 152,157 **** ExecProcessReturning(ProjectionInfo *projectReturning,
--- 153,264 ----
  }
  
  /* ----------------------------------------------------------------
+  * ExecLockHeapTupleForUpdateSpec:  Try to lock tuple for update as part of
+  * speculative insertion.
+  *
+  * Returns value indicating if we're done with heap tuple locking, or if
+  * another attempt at value locking is required.
+  * ----------------------------------------------------------------
+  */
+ static bool
+ ExecLockHeapTupleForUpdateSpec(EState *estate,
+ 				   ResultRelInfo *relinfo,
+ 				   ItemPointer tid)
+ {
+ 	Relation	relation = relinfo->ri_RelationDesc;
+ 	HeapTupleData tuple;
+ 	Buffer		buffer;
+ 
+ 	HTSU_Result test;
+ 	HeapUpdateFailureData hufd;
+ 
+ 	Assert(ItemPointerIsValid(tid));
+ 
+ 	/*
+ 	 * Lock tuple for update.
+ 	 *
+ 	 * Wait for other transaction to complete.
+ 	 */
+ 	tuple.t_self = *tid;
+ 	test = heap_lock_tuple(relation, &tuple,
+ 						   estate->es_output_cid,
+ 						   LockTupleExclusive, false,
+ 						   true, &buffer, &hufd);
+ 	ReleaseBuffer(buffer);
+ 
+ 	switch (test)
+ 	{
+ 		case HeapTupleInvisible:
+ 			/*
+ 			 * This can happen if the inserting transaction aborted. Try again.
+ 			 */
+ 			return false;
+ 
+ 		case HeapTupleSelfUpdated:
+ 			/*
+ 			 * The target tuple was already updated or deleted by the current
+ 			 * command, or by a later command in the current transaction.  We
+ 			 * conclude that we're done in the former case, and throw an error
+ 			 * in the latter case, for the same reasons enumerated in
+ 			 * ExecUpdate and ExecDelete.
+ 			 */
+ 			if (hufd.cmax != estate->es_output_cid)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
+ 						 errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
+ 						 errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
+ 
+ 			/*
+ 			 * The fact that this command has already updated or deleted the
+ 			 * tuple is grounds for concluding that we're done.  Appropriate
+ 			 * locks will already be held.  It isn't the responsibility of the
+ 			 * speculative insertion LOCK FOR UPDATE infrastructure to ensure
+ 			 * an atomic INSERT-or-UPDATE in the event of a tuple being updated
+ 			 * or deleted by the same xact in the interim.
+ 			 */
+ 			return true;
+ 		case HeapTupleMayBeUpdated:
+ 			/*
+ 			 * Success -- we're done, as tuple is locked and known to be
+ 			 * visible to our snapshot under conventional MVCC rules if the
+ 			 * current isolation level mandates that (in READ COMMITTED mode, a
+ 			 * special exception to the conventional rules applies).
+ 			 */
+ 			return true;
+ 		case HeapTupleUpdated:
+ 			if (IsolationUsesXactSnapshot())
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ 						 errmsg("could not serialize access due to concurrent update")));
+ 			/*
+ 			 * Tell caller to try again from the very start.  We don't use the
+ 			 * usual EvalPlanQual looping pattern here, fundamentally because
+ 			 * we don't have a useful qual to verify the next tuple with.
+ 			 *
+ 			 * We might devise a means of verifying, by way of binary equality
+ 			 * in a similar manner to HOT codepaths, if any unique indexed
+ 			 * columns changed, but this would only serve to ameliorate the
+ 			 * fundamental problem.  It might well not be good enough, because
+ 			 * those columns could change too.  It's not clear that doing any
+ 			 * better here would be worth it.
+ 			 *
+ 			 * At this point, all bets are off -- it might actually turn out to
+ 			 * be okay to proceed with insertion instead of locking now (the
+ 			 * tuple we attempted to lock could have been deleted, for
+ 			 * example).  On the other hand, it might not be okay, but for an
+ 			 * entirely different reason, with an entirely separate TID to
+ 			 * blame and lock.  This TID may not even be part of the same
+ 			 * update chain.
+ 			 */
+ 			return false;
+ 		default:
+ 			elog(ERROR, "unrecognized heap_lock_tuple status: %u", test);
+ 	}
+ 
+ 	return false;
+ }
+ 
+ /* ----------------------------------------------------------------
   *		ExecInsert
   *
   *		For INSERT, we have to insert the tuple into the target relation
***************
*** 164,176 **** static TupleTableSlot *
  ExecInsert(TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate,
! 		   bool canSetTag)
  {
  	HeapTuple	tuple;
  	ResultRelInfo *resultRelInfo;
  	Relation	resultRelationDesc;
  	Oid			newId;
  	List	   *recheckIndexes = NIL;
  
  	/*
  	 * get the heap tuple out of the tuple table slot, making sure we have a
--- 271,287 ----
  ExecInsert(TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EState *estate,
! 		   bool canSetTag,
! 		   SpecType spec)
  {
  	HeapTuple	tuple;
  	ResultRelInfo *resultRelInfo;
  	Relation	resultRelationDesc;
  	Oid			newId;
  	List	   *recheckIndexes = NIL;
+ 	ProjectionInfo *returning;
+ 	bool		rejects = (spec == SPEC_IGNORE_REJECTS ||
+ 						   spec == SPEC_UPDATE_REJECTS);
  
  	/*
  	 * get the heap tuple out of the tuple table slot, making sure we have a
***************
*** 183,188 **** ExecInsert(TupleTableSlot *slot,
--- 294,326 ----
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ 	returning = resultRelInfo->ri_projectReturning;
+ 
+ 	/*
+ 	 * If speculative insertion is requested, take necessary precautions.
+ 	 *
+ 	 * Value locks are typically actually implemented by AMs as shared locks on
+ 	 * buffers.  This could be quite hazardous, because in the worst case those
+ 	 * locks could be on catalog indexes, with the system then liable to
+ 	 * deadlock due to innocent catalog access when inserting a heap tuple.
+ 	 * However, we take a precaution against that here.
+ 	 *
+ 	 * Rather than forever committing to carefully managing these hazards
+ 	 * during the extended (but still short) window after locking in which heap
+ 	 * tuple insertion will potentially later take place (a window that ends,
+ 	 * in the "insertion proceeds" case, when locks are released by the second
+ 	 * phase of speculative insertion having completed for unique indexes), it
+ 	 * is expedient to simply forbid speculative insertion into catalogs
+ 	 * altogether.  There is no consequence to allowing speculative insertion
+ 	 * into TOAST tables, which we also forbid, but that doesn't seem terribly
+ 	 * useful.
+ 	 */
+ 	if (spec != SPEC_NONE &&
+ 		IsSystemRelation(resultRelationDesc))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("speculative insertion into catalogs and TOAST tables not supported"),
+ 				 errtable(resultRelationDesc)));
  
  	/*
  	 * If the result relation has OIDs, force the tuple's OID to zero so that
***************
*** 246,251 **** ExecInsert(TupleTableSlot *slot,
--- 384,392 ----
  	}
  	else
  	{
+ 		bool			conflicted;
+ 		ItemPointerData	conflictTid;
+ 
  		/*
  		 * Constraints might reference the tableoid column, so initialize
  		 * t_tableOid before evaluating them.
***************
*** 258,278 **** ExecInsert(TupleTableSlot *slot,
  		if (resultRelationDesc->rd_att->constr)
  			ExecConstraints(resultRelInfo, slot, estate);
  
  		/*
! 		 * insert the tuple
  		 *
! 		 * Note: heap_insert returns the tid (location) of the new tuple in
! 		 * the t_self field.
  		 */
! 		newId = heap_insert(resultRelationDesc, tuple,
! 							estate->es_output_cid, 0, NULL);
  
! 		/*
! 		 * insert index entries for tuple
! 		 */
! 		if (resultRelInfo->ri_NumIndices > 0)
! 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
! 												   estate);
  	}
  
  	if (canSetTag)
--- 399,498 ----
  		if (resultRelationDesc->rd_att->constr)
  			ExecConstraints(resultRelInfo, slot, estate);
  
+ 	retry:
+ 		conflicted = false;
+ 		ItemPointerSetInvalid(&conflictTid);
+ 
  		/*
! 		 * If we are expecting duplicates, do a non-conclusive first check.
! 		 * We might still fail later, after inserting the heap tuple, if a
! 		 * conflicting row was inserted concurrently. We'll handle that by
! 		 * deleting the already-inserted tuple and retrying, but that's fairly
! 		 * expensive, so we try to avoid it.
  		 *
! 		 * XXX: If we know or assume that there are few duplicates, it would
! 		 * be better to skip this, and just optimistically proceed with the
! 		 * insertion below. You would then leave behind some garbage when a
! 		 * conflict happens, but if it's rare, it doesn't matter much. Some
! 		 * kind of heuristic might be in order here, like stop doing these
! 		 * pre-checks if the last 100 insertions have not been duplicates.
  		 */
! 		if (spec != SPEC_NONE && resultRelInfo->ri_NumIndices > 0)
! 		{
! 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid))
! 				conflicted = true;
! 		}
  
! 		if (!conflicted)
! 		{
! 			/*
! 			 * insert the tuple
! 			 *
! 			 * Note: heap_insert returns the tid (location) of the new tuple in
! 			 * the t_self field.
! 			 */
! 			newId = heap_insert(resultRelationDesc, tuple,
! 								estate->es_output_cid, 0, NULL);
! 
! 			/*
! 			 * Insert index entries for tuple.
! 			 *
! 			 * Locks will be acquired if needed, or the locks acquired by
! 			 * ExecLockIndexTuples() may be used instead.
! 			 */
! 			if (resultRelInfo->ri_NumIndices > 0)
! 				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
! 													   estate,
! 													   spec != SPEC_NONE);
! 
! 			if (spec != SPEC_NONE && recheckIndexes)
! 			{
! 				HeapUpdateFailureData hufd;
! 				heap_delete(resultRelationDesc,  &(tuple->t_self),
! 							estate->es_output_cid, NULL, false, &hufd, true);
! 				conflicted = true;
! 			}
! 		}
! 
! 		if (conflicted)
! 		{
! 			if (spec == SPEC_UPDATE || spec == SPEC_UPDATE_REJECTS)
! 			{
! 				/*
! 				 * Try to lock row for update.
! 				 *
! 				 * XXX: We don't have the TID of the conflicting tuple if
! 				 * the index insertion failed and we had to kill the already
! 				 * inserted tuple. We'd need to modify the index AM to pass
! 				 * through the TID back here. So for now, we just retry, and
! 				 * hopefully the new pre-check will fail on the same tuple
! 				 * (or it's finished by now), and we'll get its TID that way
! 				 */
! 				if (!ItemPointerIsValid(&conflictTid))
! 				{
! 					elog(DEBUG1, "insertion conflicted after pre-check");
! 					goto retry;
! 				}
! 
! 				if (!ExecLockHeapTupleForUpdateSpec(estate,
! 													resultRelInfo,
! 													&conflictTid))
! 				{
! 					/*
! 					 * Couldn't lock row - restart from just before value
! 					 * locking.  It's subtly wrong to assume anything about
! 					 * the row version that is under consideration for
! 					 * locking if another transaction locked it first.
! 					 */
! 					goto retry;
! 				}
! 			}
! 
! 			if (rejects)
! 				return ExecProcessReturning(returning, slot, planSlot);
! 			else
! 				return NULL;
! 		}
  	}
  
  	if (canSetTag)
***************
*** 291,300 **** ExecInsert(TupleTableSlot *slot,
  	if (resultRelInfo->ri_WithCheckOptions != NIL)
  		ExecWithCheckOptions(resultRelInfo, slot, estate);
  
! 	/* Process RETURNING if present */
! 	if (resultRelInfo->ri_projectReturning)
! 		return ExecProcessReturning(resultRelInfo->ri_projectReturning,
! 									slot, planSlot);
  
  	return NULL;
  }
--- 511,522 ----
  	if (resultRelInfo->ri_WithCheckOptions != NIL)
  		ExecWithCheckOptions(resultRelInfo, slot, estate);
  
! 	/*
! 	 * Process RETURNING if present and not only returning speculative
! 	 * insertion rejects
! 	 */
! 	if (returning && !rejects)
! 		return ExecProcessReturning(returning, slot, planSlot);
  
  	return NULL;
  }
***************
*** 403,409 **** ldelete:;
  							 estate->es_output_cid,
  							 estate->es_crosscheck_snapshot,
  							 true /* wait for commit */ ,
! 							 &hufd);
  		switch (result)
  		{
  			case HeapTupleSelfUpdated:
--- 625,632 ----
  							 estate->es_output_cid,
  							 estate->es_crosscheck_snapshot,
  							 true /* wait for commit */ ,
! 							 &hufd,
! 							 false);
  		switch (result)
  		{
  			case HeapTupleSelfUpdated:
***************
*** 781,787 **** lreplace:;
  		 */
  		if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
  			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
! 												   estate);
  	}
  
  	if (canSetTag)
--- 1004,1010 ----
  		 */
  		if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
  			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
! 												   estate, false);
  	}
  
  	if (canSetTag)
***************
*** 1011,1017 **** ExecModifyTable(ModifyTableState *node)
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
  				break;
  			case CMD_UPDATE:
  				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
--- 1234,1241 ----
  		switch (operation)
  		{
  			case CMD_INSERT:
! 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag,
! 								  node->spec);
  				break;
  			case CMD_UPDATE:
  				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
***************
*** 1086,1091 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 1310,1316 ----
  	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
  	mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
  	mtstate->mt_nplans = nplans;
+ 	mtstate->spec = node->spec;
  
  	/* set up epqstate with dummy subplan data for the moment */
  	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
***************
*** 1296,1301 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 1521,1527 ----
  				break;
  			case CMD_UPDATE:
  			case CMD_DELETE:
+ 				Assert(node->spec == SPEC_NONE);
  				junk_filter_needed = true;
  				break;
  			default:
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 182,187 **** _copyModifyTable(const ModifyTable *from)
--- 182,188 ----
  	COPY_NODE_FIELD(returningLists);
  	COPY_NODE_FIELD(fdwPrivLists);
  	COPY_NODE_FIELD(rowMarks);
+ 	COPY_SCALAR_FIELD(spec);
  	COPY_SCALAR_FIELD(epqParam);
  
  	return newnode;
***************
*** 2085,2090 **** _copyWithClause(const WithClause *from)
--- 2086,2103 ----
  	return newnode;
  }
  
+ static ReturningClause *
+ _copyReturningClause(const ReturningClause *from)
+ {
+ 	ReturningClause *newnode = makeNode(ReturningClause);
+ 
+ 	COPY_NODE_FIELD(returningList);
+ 	COPY_SCALAR_FIELD(rejects);
+ 	COPY_LOCATION_FIELD(location);
+ 
+ 	return newnode;
+ }
+ 
  static CommonTableExpr *
  _copyCommonTableExpr(const CommonTableExpr *from)
  {
***************
*** 2475,2480 **** _copyQuery(const Query *from)
--- 2488,2494 ----
  	COPY_NODE_FIELD(jointree);
  	COPY_NODE_FIELD(targetList);
  	COPY_NODE_FIELD(withCheckOptions);
+ 	COPY_SCALAR_FIELD(specClause);
  	COPY_NODE_FIELD(returningList);
  	COPY_NODE_FIELD(groupClause);
  	COPY_NODE_FIELD(havingQual);
***************
*** 2498,2504 **** _copyInsertStmt(const InsertStmt *from)
  	COPY_NODE_FIELD(relation);
  	COPY_NODE_FIELD(cols);
  	COPY_NODE_FIELD(selectStmt);
! 	COPY_NODE_FIELD(returningList);
  	COPY_NODE_FIELD(withClause);
  
  	return newnode;
--- 2512,2519 ----
  	COPY_NODE_FIELD(relation);
  	COPY_NODE_FIELD(cols);
  	COPY_NODE_FIELD(selectStmt);
! 	COPY_SCALAR_FIELD(specClause);
! 	COPY_NODE_FIELD(rlist);
  	COPY_NODE_FIELD(withClause);
  
  	return newnode;
***************
*** 2512,2518 **** _copyDeleteStmt(const DeleteStmt *from)
  	COPY_NODE_FIELD(relation);
  	COPY_NODE_FIELD(usingClause);
  	COPY_NODE_FIELD(whereClause);
! 	COPY_NODE_FIELD(returningList);
  	COPY_NODE_FIELD(withClause);
  
  	return newnode;
--- 2527,2533 ----
  	COPY_NODE_FIELD(relation);
  	COPY_NODE_FIELD(usingClause);
  	COPY_NODE_FIELD(whereClause);
! 	COPY_NODE_FIELD(rlist);
  	COPY_NODE_FIELD(withClause);
  
  	return newnode;
***************
*** 2527,2533 **** _copyUpdateStmt(const UpdateStmt *from)
  	COPY_NODE_FIELD(targetList);
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(fromClause);
! 	COPY_NODE_FIELD(returningList);
  	COPY_NODE_FIELD(withClause);
  
  	return newnode;
--- 2542,2548 ----
  	COPY_NODE_FIELD(targetList);
  	COPY_NODE_FIELD(whereClause);
  	COPY_NODE_FIELD(fromClause);
! 	COPY_NODE_FIELD(rlist);
  	COPY_NODE_FIELD(withClause);
  
  	return newnode;
***************
*** 4579,4584 **** copyObject(const void *from)
--- 4594,4602 ----
  		case T_WithClause:
  			retval = _copyWithClause(from);
  			break;
+ 		case T_ReturningClause:
+ 			retval = _copyReturningClause(from);
+ 			break;
  		case T_CommonTableExpr:
  			retval = _copyCommonTableExpr(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 859,864 **** _equalQuery(const Query *a, const Query *b)
--- 859,865 ----
  	COMPARE_NODE_FIELD(jointree);
  	COMPARE_NODE_FIELD(targetList);
  	COMPARE_NODE_FIELD(withCheckOptions);
+ 	COMPARE_SCALAR_FIELD(specClause);
  	COMPARE_NODE_FIELD(returningList);
  	COMPARE_NODE_FIELD(groupClause);
  	COMPARE_NODE_FIELD(havingQual);
***************
*** 880,886 **** _equalInsertStmt(const InsertStmt *a, const InsertStmt *b)
  	COMPARE_NODE_FIELD(relation);
  	COMPARE_NODE_FIELD(cols);
  	COMPARE_NODE_FIELD(selectStmt);
! 	COMPARE_NODE_FIELD(returningList);
  	COMPARE_NODE_FIELD(withClause);
  
  	return true;
--- 881,888 ----
  	COMPARE_NODE_FIELD(relation);
  	COMPARE_NODE_FIELD(cols);
  	COMPARE_NODE_FIELD(selectStmt);
! 	COMPARE_SCALAR_FIELD(specClause);
! 	COMPARE_NODE_FIELD(rlist);
  	COMPARE_NODE_FIELD(withClause);
  
  	return true;
***************
*** 892,898 **** _equalDeleteStmt(const DeleteStmt *a, const DeleteStmt *b)
  	COMPARE_NODE_FIELD(relation);
  	COMPARE_NODE_FIELD(usingClause);
  	COMPARE_NODE_FIELD(whereClause);
! 	COMPARE_NODE_FIELD(returningList);
  	COMPARE_NODE_FIELD(withClause);
  
  	return true;
--- 894,900 ----
  	COMPARE_NODE_FIELD(relation);
  	COMPARE_NODE_FIELD(usingClause);
  	COMPARE_NODE_FIELD(whereClause);
! 	COMPARE_NODE_FIELD(rlist);
  	COMPARE_NODE_FIELD(withClause);
  
  	return true;
***************
*** 905,911 **** _equalUpdateStmt(const UpdateStmt *a, const UpdateStmt *b)
  	COMPARE_NODE_FIELD(targetList);
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(fromClause);
! 	COMPARE_NODE_FIELD(returningList);
  	COMPARE_NODE_FIELD(withClause);
  
  	return true;
--- 907,913 ----
  	COMPARE_NODE_FIELD(targetList);
  	COMPARE_NODE_FIELD(whereClause);
  	COMPARE_NODE_FIELD(fromClause);
! 	COMPARE_NODE_FIELD(rlist);
  	COMPARE_NODE_FIELD(withClause);
  
  	return true;
***************
*** 2344,2349 **** _equalWithClause(const WithClause *a, const WithClause *b)
--- 2346,2361 ----
  }
  
  static bool
+ _equalReturningClause(const ReturningClause *a, const ReturningClause *b)
+ {
+ 	COMPARE_NODE_FIELD(returningList);
+ 	COMPARE_SCALAR_FIELD(rejects);
+ 	COMPARE_LOCATION_FIELD(location);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
  {
  	COMPARE_STRING_FIELD(ctename);
***************
*** 3049,3054 **** equal(const void *a, const void *b)
--- 3061,3069 ----
  		case T_WithClause:
  			retval = _equalWithClause(a, b);
  			break;
+ 		case T_ReturningClause:
+ 			retval = _equalReturningClause(a, b);
+ 			break;
  		case T_CommonTableExpr:
  			retval = _equalCommonTableExpr(a, b);
  			break;
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 1460,1465 **** exprLocation(const Node *expr)
--- 1460,1468 ----
  		case T_WithClause:
  			loc = ((const WithClause *) expr)->location;
  			break;
+ 		case T_ReturningClause:
+ 			loc = ((const ReturningClause *) expr)->location;
+ 			break;
  		case T_CommonTableExpr:
  			loc = ((const CommonTableExpr *) expr)->location;
  			break;
***************
*** 2946,2952 **** raw_expression_tree_walker(Node *node,
  					return true;
  				if (walker(stmt->selectStmt, context))
  					return true;
! 				if (walker(stmt->returningList, context))
  					return true;
  				if (walker(stmt->withClause, context))
  					return true;
--- 2949,2955 ----
  					return true;
  				if (walker(stmt->selectStmt, context))
  					return true;
! 				if (walker(stmt->rlist, context))
  					return true;
  				if (walker(stmt->withClause, context))
  					return true;
***************
*** 2962,2968 **** raw_expression_tree_walker(Node *node,
  					return true;
  				if (walker(stmt->whereClause, context))
  					return true;
! 				if (walker(stmt->returningList, context))
  					return true;
  				if (walker(stmt->withClause, context))
  					return true;
--- 2965,2971 ----
  					return true;
  				if (walker(stmt->whereClause, context))
  					return true;
! 				if (walker(stmt->rlist, context))
  					return true;
  				if (walker(stmt->withClause, context))
  					return true;
***************
*** 2980,2986 **** raw_expression_tree_walker(Node *node,
  					return true;
  				if (walker(stmt->fromClause, context))
  					return true;
! 				if (walker(stmt->returningList, context))
  					return true;
  				if (walker(stmt->withClause, context))
  					return true;
--- 2983,2989 ----
  					return true;
  				if (walker(stmt->fromClause, context))
  					return true;
! 				if (walker(stmt->rlist, context))
  					return true;
  				if (walker(stmt->withClause, context))
  					return true;
***************
*** 3175,3180 **** raw_expression_tree_walker(Node *node,
--- 3178,3185 ----
  			break;
  		case T_WithClause:
  			return walker(((WithClause *) node)->ctes, context);
+ 		case T_ReturningClause:
+ 			return walker(((ReturningClause *) node)->returningList, context);
  		case T_CommonTableExpr:
  			return walker(((CommonTableExpr *) node)->ctequery, context);
  		default:
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 336,341 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 336,342 ----
  	WRITE_NODE_FIELD(returningLists);
  	WRITE_NODE_FIELD(fdwPrivLists);
  	WRITE_NODE_FIELD(rowMarks);
+ 	WRITE_ENUM_FIELD(spec, SpecType);
  	WRITE_INT_FIELD(epqParam);
  }
  
***************
*** 2250,2255 **** _outQuery(StringInfo str, const Query *node)
--- 2251,2257 ----
  	WRITE_NODE_FIELD(jointree);
  	WRITE_NODE_FIELD(targetList);
  	WRITE_NODE_FIELD(withCheckOptions);
+ 	WRITE_ENUM_FIELD(specClause, SpecType);
  	WRITE_NODE_FIELD(returningList);
  	WRITE_NODE_FIELD(groupClause);
  	WRITE_NODE_FIELD(havingQual);
***************
*** 2323,2328 **** _outWithClause(StringInfo str, const WithClause *node)
--- 2325,2340 ----
  }
  
  static void
+ _outReturningClause(StringInfo str, const ReturningClause *node)
+ {
+ 	WRITE_NODE_TYPE("RETURNINGCLAUSE");
+ 
+ 	WRITE_NODE_FIELD(returningList);
+ 	WRITE_BOOL_FIELD(rejects);
+ 	WRITE_LOCATION_FIELD(location);
+ }
+ 
+ static void
  _outCommonTableExpr(StringInfo str, const CommonTableExpr *node)
  {
  	WRITE_NODE_TYPE("COMMONTABLEEXPR");
***************
*** 3156,3161 **** _outNode(StringInfo str, const void *obj)
--- 3168,3176 ----
  			case T_WithClause:
  				_outWithClause(str, obj);
  				break;
+ 			case T_ReturningClause:
+ 				_outReturningClause(str, obj);
+ 				break;
  			case T_CommonTableExpr:
  				_outCommonTableExpr(str, obj);
  				break;
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 211,216 **** _readQuery(void)
--- 211,217 ----
  	READ_NODE_FIELD(jointree);
  	READ_NODE_FIELD(targetList);
  	READ_NODE_FIELD(withCheckOptions);
+ 	READ_ENUM_FIELD(specClause, SpecType);
  	READ_NODE_FIELD(returningList);
  	READ_NODE_FIELD(groupClause);
  	READ_NODE_FIELD(havingQual);
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 4722,4728 **** make_modifytable(PlannerInfo *root,
  				 CmdType operation, bool canSetTag,
  				 List *resultRelations, List *subplans,
  				 List *withCheckOptionLists, List *returningLists,
! 				 List *rowMarks, int epqParam)
  {
  	ModifyTable *node = makeNode(ModifyTable);
  	Plan	   *plan = &node->plan;
--- 4722,4728 ----
  				 CmdType operation, bool canSetTag,
  				 List *resultRelations, List *subplans,
  				 List *withCheckOptionLists, List *returningLists,
! 				 List *rowMarks, SpecType spec, int epqParam)
  {
  	ModifyTable *node = makeNode(ModifyTable);
  	Plan	   *plan = &node->plan;
***************
*** 4774,4779 **** make_modifytable(PlannerInfo *root,
--- 4774,4780 ----
  	node->withCheckOptionLists = withCheckOptionLists;
  	node->returningLists = returningLists;
  	node->rowMarks = rowMarks;
+ 	node->spec = spec;
  	node->epqParam = epqParam;
  
  	/*
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 609,614 **** subquery_planner(PlannerGlobal *glob, Query *parse,
--- 609,615 ----
  											 withCheckOptionLists,
  											 returningLists,
  											 rowMarks,
+ 											 parse->specClause,
  											 SS_assign_special_param(root));
  		}
  	}
***************
*** 1008,1013 **** inheritance_planner(PlannerInfo *root)
--- 1009,1015 ----
  									 withCheckOptionLists,
  									 returningLists,
  									 rowMarks,
+ 									 parse->specClause,
  									 SS_assign_special_param(root));
  }
  
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 61,67 **** static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
  static void determineRecursiveColTypes(ParseState *pstate,
  						   Node *larg, List *nrtargetlist);
  static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
! static List *transformReturningList(ParseState *pstate, List *returningList);
  static Query *transformDeclareCursorStmt(ParseState *pstate,
  						   DeclareCursorStmt *stmt);
  static Query *transformExplainStmt(ParseState *pstate,
--- 61,68 ----
  static void determineRecursiveColTypes(ParseState *pstate,
  						   Node *larg, List *nrtargetlist);
  static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
! static List *transformReturningClause(ParseState *pstate, ReturningClause *returningList,
! 						   bool *rejects);
  static Query *transformDeclareCursorStmt(ParseState *pstate,
  						   DeclareCursorStmt *stmt);
  static Query *transformExplainStmt(ParseState *pstate,
***************
*** 343,348 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
--- 344,350 ----
  {
  	Query	   *qry = makeNode(Query);
  	Node	   *qual;
+ 	bool		rejects;
  
  	qry->commandType = CMD_DELETE;
  
***************
*** 373,384 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
  	qual = transformWhereClause(pstate, stmt->whereClause,
  								EXPR_KIND_WHERE, "WHERE");
  
! 	qry->returningList = transformReturningList(pstate, stmt->returningList);
  
  	/* done building the range table and jointree */
  	qry->rtable = pstate->p_rtable;
  	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
  
  	qry->hasSubLinks = pstate->p_hasSubLinks;
  	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
  	qry->hasAggs = pstate->p_hasAggs;
--- 375,394 ----
  	qual = transformWhereClause(pstate, stmt->whereClause,
  								EXPR_KIND_WHERE, "WHERE");
  
! 	qry->returningList = transformReturningClause(pstate, stmt->rlist, &rejects);
! 
! 	if (rejects)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_SYNTAX_ERROR),
! 				 errmsg("RETURNING clause does not accept REJECTS for DELETE statements"),
! 				 parser_errposition(pstate,
! 									exprLocation((Node *) stmt->rlist))));
  
  	/* done building the range table and jointree */
  	qry->rtable = pstate->p_rtable;
  	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
  
+ 	qry->specClause = SPEC_NONE;
  	qry->hasSubLinks = pstate->p_hasSubLinks;
  	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
  	qry->hasAggs = pstate->p_hasAggs;
***************
*** 399,404 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 409,415 ----
  {
  	Query	   *qry = makeNode(Query);
  	SelectStmt *selectStmt = (SelectStmt *) stmt->selectStmt;
+ 	SpecType	spec = stmt->specClause;
  	List	   *exprList = NIL;
  	bool		isGeneralSelect;
  	List	   *sub_rtable;
***************
*** 410,415 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 421,427 ----
  	ListCell   *icols;
  	ListCell   *attnos;
  	ListCell   *lc;
+ 	bool		rejects = false;
  
  	/* There can't be any outer WITH to worry about */
  	Assert(pstate->p_ctenamespace == NIL);
***************
*** 737,755 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
  	 * RETURNING will work.  Also, remove any namespace entries added in a
  	 * sub-SELECT or VALUES list.
  	 */
! 	if (stmt->returningList)
  	{
  		pstate->p_namespace = NIL;
  		addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
  					  false, true, true);
! 		qry->returningList = transformReturningList(pstate,
! 													stmt->returningList);
  	}
  
  	/* done building the range table and jointree */
  	qry->rtable = pstate->p_rtable;
  	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
  
  	qry->hasSubLinks = pstate->p_hasSubLinks;
  
  	assign_query_collations(pstate, qry);
--- 749,782 ----
  	 * RETURNING will work.  Also, remove any namespace entries added in a
  	 * sub-SELECT or VALUES list.
  	 */
! 	if (stmt->rlist)
  	{
  		pstate->p_namespace = NIL;
  		addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
  					  false, true, true);
! 		qry->returningList = transformReturningClause(pstate,
! 													stmt->rlist, &rejects);
  	}
  
  	/* done building the range table and jointree */
  	qry->rtable = pstate->p_rtable;
  	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
  
+ 	/* Normalize speculative insertion specification */
+ 	if (rejects)
+ 	{
+ 		if (spec == SPEC_IGNORE)
+ 			spec = SPEC_IGNORE_REJECTS;
+ 		else if (spec == SPEC_UPDATE)
+ 			spec = SPEC_UPDATE_REJECTS;
+ 		else
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_SYNTAX_ERROR),
+ 					 errmsg("RETURNING clause with REJECTS can only appear when ON DUPLICATE KEY was also specified"),
+ 					 parser_errposition(pstate,
+ 										exprLocation((Node *) stmt->rlist))));
+ 	}
+ 	qry->specClause = spec;
  	qry->hasSubLinks = pstate->p_hasSubLinks;
  
  	assign_query_collations(pstate, qry);
***************
*** 997,1002 **** transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
--- 1024,1030 ----
  
  	qry->rtable = pstate->p_rtable;
  	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+ 	qry->specClause = SPEC_NONE;
  
  	qry->hasSubLinks = pstate->p_hasSubLinks;
  	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
***************
*** 1893,1898 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
--- 1921,1927 ----
  	Node	   *qual;
  	ListCell   *origTargetList;
  	ListCell   *tl;
+ 	bool		rejects;
  
  	qry->commandType = CMD_UPDATE;
  	pstate->p_is_update = true;
***************
*** 1922,1931 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
  	qual = transformWhereClause(pstate, stmt->whereClause,
  								EXPR_KIND_WHERE, "WHERE");
  
! 	qry->returningList = transformReturningList(pstate, stmt->returningList);
  
  	qry->rtable = pstate->p_rtable;
  	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
  
  	qry->hasSubLinks = pstate->p_hasSubLinks;
  
--- 1951,1969 ----
  	qual = transformWhereClause(pstate, stmt->whereClause,
  								EXPR_KIND_WHERE, "WHERE");
  
! 	qry->returningList = transformReturningClause(pstate, stmt->rlist,
! 												&rejects);
! 
! 	if (rejects)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_SYNTAX_ERROR),
! 				 errmsg("RETURNING clause does not accept REJECTS for UPDATE statements"),
! 				 parser_errposition(pstate,
! 									exprLocation((Node *) stmt->rlist))));
  
  	qry->rtable = pstate->p_rtable;
  	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+ 	qry->specClause = SPEC_NONE;
  
  	qry->hasSubLinks = pstate->p_hasSubLinks;
  
***************
*** 1995,2011 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
  }
  
  /*
!  * transformReturningList -
   *	handle a RETURNING clause in INSERT/UPDATE/DELETE
   */
  static List *
! transformReturningList(ParseState *pstate, List *returningList)
  {
! 	List	   *rlist;
  	int			save_next_resno;
  
! 	if (returningList == NIL)
! 		return NIL;				/* nothing to do */
  
  	/*
  	 * We need to assign resnos starting at one in the RETURNING list. Save
--- 2033,2055 ----
  }
  
  /*
!  * transformReturningClause -
   *	handle a RETURNING clause in INSERT/UPDATE/DELETE
   */
  static List *
! transformReturningClause(ParseState *pstate, ReturningClause *clause,
! 					   bool *rejects)
  {
! 	List	   *tlist, *rlist;
  	int			save_next_resno;
  
! 	if (clause == NULL)
! 	{
! 		*rejects = false;
! 		return NIL;
! 	}
! 
! 	rlist = clause->returningList;
  
  	/*
  	 * We need to assign resnos starting at one in the RETURNING list. Save
***************
*** 2016,2030 **** transformReturningList(ParseState *pstate, List *returningList)
  	pstate->p_next_resno = 1;
  
  	/* transform RETURNING identically to a SELECT targetlist */
! 	rlist = transformTargetList(pstate, returningList, EXPR_KIND_RETURNING);
  
  	/* mark column origins */
! 	markTargetListOrigins(pstate, rlist);
  
  	/* restore state */
  	pstate->p_next_resno = save_next_resno;
  
! 	return rlist;
  }
  
  
--- 2060,2098 ----
  	pstate->p_next_resno = 1;
  
  	/* transform RETURNING identically to a SELECT targetlist */
! 	tlist = transformTargetList(pstate, rlist, EXPR_KIND_RETURNING);
! 
! 	/* Cannot accept system column Vars when returning rejects */
! 	if (clause->rejects)
! 	{
! 		ListCell *l;
! 
! 		foreach(l, tlist)
! 		{
! 			TargetEntry *tle = (TargetEntry *) lfirst(l);
! 			Var *var = (Var *) tle->expr;
! 
! 			if (var->varattno <= 0)
! 			{
! 				ereport(ERROR,
! 						(errcode(ERRCODE_UNDEFINED_COLUMN),
! 						 errmsg("RETURNING clause cannot return system columns when REJECTS is specified"),
! 						 parser_errposition(pstate,
! 											exprLocation((Node *) var))));
! 			}
! 		}
! 	}
! 
! 	/* pass on if we return rejects */
! 	*rejects = clause->rejects;
  
  	/* mark column origins */
! 	markTargetListOrigins(pstate, tlist);
  
  	/* restore state */
  	pstate->p_next_resno = save_next_resno;
  
! 	return tlist;
  }
  
  
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 204,209 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 204,210 ----
  	RangeVar			*range;
  	IntoClause			*into;
  	WithClause			*with;
+ 	ReturningClause		*returnc;
  	A_Indices			*aind;
  	ResTarget			*target;
  	struct PrivTarget	*privtarget;
***************
*** 342,351 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  				opclass_purpose opt_opfamily transaction_mode_list_or_empty
  				OptTableFuncElementList TableFuncElementList opt_type_modifiers
  				prep_type_clause
! 				execute_param_clause using_clause returning_clause
! 				opt_enum_val_list enum_val_list table_func_column_list
! 				create_generic_options alter_generic_options
! 				relation_expr_list dostmt_opt_list
  
  %type <list>	opt_fdw_options fdw_options
  %type <defelt>	fdw_option
--- 343,351 ----
  				opclass_purpose opt_opfamily transaction_mode_list_or_empty
  				OptTableFuncElementList TableFuncElementList opt_type_modifiers
  				prep_type_clause
! 				execute_param_clause using_clause opt_enum_val_list
! 				enum_val_list table_func_column_list create_generic_options
! 				alter_generic_options relation_expr_list dostmt_opt_list
  
  %type <list>	opt_fdw_options fdw_options
  %type <defelt>	fdw_option
***************
*** 396,401 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 396,402 ----
  %type <defelt>	SeqOptElem
  
  %type <istmt>	insert_rest
+ %type <ival>	opt_on_duplicate_key
  
  %type <vsetstmt> set_rest set_rest_more SetResetClause FunctionSetResetClause
  
***************
*** 489,494 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 490,497 ----
  %type <node>	func_expr func_expr_windowless
  %type <node>	common_table_expr
  %type <with>	with_clause opt_with_clause
+ %type <boolean> opt_rejects
+ %type <returnc> returning_clause
  %type <list>	cte_list
  
  %type <list>	window_clause window_definition_list opt_partition_clause
***************
*** 538,543 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 541,547 ----
  	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
  	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
  	DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ 	DUPLICATE
  
  	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
  	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
***************
*** 550,556 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  
  	HANDLER HAVING HEADER_P HOLD HOUR_P
  
! 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P
  	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
  	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
  	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
--- 554,560 ----
  
  	HANDLER HAVING HEADER_P HOLD HOUR_P
  
! 	IDENTITY_P IF_P IGNORE ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P
  	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
  	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
  	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
***************
*** 579,585 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  	QUOTE
  
  	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
! 	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
  	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
  	ROW ROWS RULE
  
--- 583,589 ----
  	QUOTE
  
  	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
! 	REJECTS RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
  	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
  	ROW ROWS RULE
  
***************
*** 8870,8880 **** DeallocateStmt: DEALLOCATE name
   *****************************************************************************/
  
  InsertStmt:
! 			opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
  				{
  					$5->relation = $4;
! 					$5->returningList = $6;
  					$5->withClause = $1;
  					$$ = (Node *) $5;
  				}
  		;
--- 8874,8886 ----
   *****************************************************************************/
  
  InsertStmt:
! 			opt_with_clause INSERT INTO qualified_name insert_rest
! 			opt_on_duplicate_key returning_clause
  				{
  					$5->relation = $4;
! 					$5->rlist = $7;
  					$5->withClause = $1;
+ 					$5->specClause = $6;
  					$$ = (Node *) $5;
  				}
  		;
***************
*** 8918,8926 **** insert_column_item:
  				}
  		;
  
  returning_clause:
! 			RETURNING target_list		{ $$ = $2; }
! 			| /* EMPTY */				{ $$ = NIL; }
  		;
  
  
--- 8924,8968 ----
  				}
  		;
  
+ opt_on_duplicate_key:
+ 			ON DUPLICATE KEY LOCK_P FOR UPDATE
+ 				{
+ 					$$ = SPEC_UPDATE;
+ 				}
+ 			|
+ 			ON DUPLICATE KEY IGNORE
+ 				{
+ 					$$ = SPEC_IGNORE;
+ 				}
+ 			| /*EMPTY*/
+ 				{
+ 					$$ = SPEC_NONE;
+ 				}
+ 		;
+ 
+ opt_rejects:
+ 			REJECTS
+ 				{
+ 					$$ = TRUE;
+ 				}
+ 			| /*EMPTY*/
+ 				{
+ 					$$ = FALSE;
+ 				}
+ 		;
+ 
  returning_clause:
! 			RETURNING opt_rejects target_list
! 				{
! 					$$ = makeNode(ReturningClause);
! 					$$->returningList = $3;
! 					$$->rejects = $2;
! 					$$->location = @1;
! 				}
! 			| /* EMPTY */
! 				{
! 					$$ = NULL;
! 				}
  		;
  
  
***************
*** 8938,8944 **** DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
  					n->relation = $4;
  					n->usingClause = $5;
  					n->whereClause = $6;
! 					n->returningList = $7;
  					n->withClause = $1;
  					$$ = (Node *)n;
  				}
--- 8980,8986 ----
  					n->relation = $4;
  					n->usingClause = $5;
  					n->whereClause = $6;
! 					n->rlist = $7;
  					n->withClause = $1;
  					$$ = (Node *)n;
  				}
***************
*** 9005,9011 **** UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
  					n->targetList = $5;
  					n->fromClause = $6;
  					n->whereClause = $7;
! 					n->returningList = $8;
  					n->withClause = $1;
  					$$ = (Node *)n;
  				}
--- 9047,9053 ----
  					n->targetList = $5;
  					n->fromClause = $6;
  					n->whereClause = $7;
! 					n->rlist = $8;
  					n->withClause = $1;
  					$$ = (Node *)n;
  				}
***************
*** 12589,12594 **** unreserved_keyword:
--- 12631,12637 ----
  			| DOMAIN_P
  			| DOUBLE_P
  			| DROP
+ 			| DUPLICATE
  			| EACH
  			| ENABLE_P
  			| ENCODING
***************
*** 12619,12624 **** unreserved_keyword:
--- 12662,12668 ----
  			| HOUR_P
  			| IDENTITY_P
  			| IF_P
+ 			| IGNORE
  			| IMMEDIATE
  			| IMMUTABLE
  			| IMPLICIT_P
***************
*** 12944,12949 **** reserved_keyword:
--- 12988,12994 ----
  			| PLACING
  			| PRIMARY
  			| REFERENCES
+ 			| REJECTS
  			| RETURNING
  			| SELECT
  			| SESSION_USER
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 4047,4053 **** RelationGetExclusionInfo(Relation indexRelation,
  	MemoryContextSwitchTo(oldcxt);
  }
  
- 
  /*
   * Routines to support ereport() reports of relation-related errors
   *
--- 4047,4052 ----
*** a/src/backend/utils/time/tqual.c
--- b/src/backend/utils/time/tqual.c
***************
*** 837,842 **** HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
--- 837,843 ----
   *	Here, we consider the effects of:
   *		all transactions committed as of the time of the given snapshot
   *		previous commands of this transaction
+  *		all rows only locked (not updated) by this transaction, committed by another
   *
   *	Does _not_ include:
   *		transactions shown as in-progress by the snapshot
***************
*** 959,965 **** HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
--- 960,985 ----
  	 * when...
  	 */
  	if (XidInMVCCSnapshot(HeapTupleHeaderGetXmin(tuple), snapshot))
+ 	{
+ 		/*
+ 		 * Not visible to snapshot under conventional MVCC rules, but may still
+ 		 * be exclusive locked by our xact and not updated, which will satisfy
+ 		 * MVCC under a special rule.  Importantly, this special rule will not
+ 		 * be invoked if the row is updated, so only one version can be visible
+ 		 * at once.
+ 		 *
+ 		 * Currently this is useful to exactly one case -- INSERT...ON
+ 		 * DUPLICATE KEY LOCK FOR UPDATE, where it's possible and sometimes
+ 		 * desirable to lock a row that would not otherwise be visible to the
+ 		 * given MVCC snapshot.  The locked row should on that basis alone
+ 		 * become visible, for the benefit of READ COMMITTED mode.
+ 		 */
+ 		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) &&
+ 			TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+ 			return true;
+ 
  		return false;			/* treat as still in progress */
+ 	}
  
  	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
  		return true;
*** a/src/include/access/heapam.h
--- b/src/include/access/heapam.h
***************
*** 138,144 **** extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
  				  CommandId cid, int options, BulkInsertState bistate);
  extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
  			CommandId cid, Snapshot crosscheck, bool wait,
! 			HeapUpdateFailureData *hufd);
  extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
  			HeapTuple newtup,
  			CommandId cid, Snapshot crosscheck, bool wait,
--- 138,144 ----
  				  CommandId cid, int options, BulkInsertState bistate);
  extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
  			CommandId cid, Snapshot crosscheck, bool wait,
! 			HeapUpdateFailureData *hufd, bool kill);
  extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
  			HeapTuple newtup,
  			CommandId cid, Snapshot crosscheck, bool wait,
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 348,361 **** extern void ExecCloseScanRelation(Relation scanrel);
  
  extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
  extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
  extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
! 					  EState *estate);
! extern bool check_exclusion_constraint(Relation heap, Relation index,
  						   IndexInfo *indexInfo,
  						   ItemPointer tupleid,
  						   Datum *values, bool *isnull,
  						   EState *estate,
! 						   bool newIndex, bool errorOK);
  
  extern void RegisterExprContextCallback(ExprContext *econtext,
  							ExprContextCallbackFunction function,
--- 348,366 ----
  
  extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
  extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
+ extern List *ExecLockIndexValues(TupleTableSlot *slot, EState *estate,
+ 						   SpecType specReason);
  extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
! 					  EState *estate, bool noErrorOnDuplicate);
! extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
! 						  ItemPointer conflictTid);
! extern bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
  						   IndexInfo *indexInfo,
  						   ItemPointer tupleid,
  						   Datum *values, bool *isnull,
  						   EState *estate,
! 						   bool newIndex, bool errorOK, bool wait,
! 						   ItemPointer conflictTid);
  
  extern void RegisterExprContextCallback(ExprContext *econtext,
  							ExprContextCallbackFunction function,
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 41,46 ****
--- 41,49 ----
   *		ExclusionOps		Per-column exclusion operators, or NULL if none
   *		ExclusionProcs		Underlying function OIDs for ExclusionOps
   *		ExclusionStrats		Opclass strategy numbers for ExclusionOps
+  *		UniqueOps			Theses are like Exclusion*, but for unique indexes
+  *		UniqueProcs
+  *		UniqueStrats
   *		Unique				is it a unique index?
   *		ReadyForInserts		is it valid for inserts?
   *		Concurrent			are we doing a concurrent index build?
***************
*** 62,67 **** typedef struct IndexInfo
--- 65,73 ----
  	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
  	Oid		   *ii_ExclusionProcs;		/* array with one entry per column */
  	uint16	   *ii_ExclusionStrats;		/* array with one entry per column */
+ 	Oid		   *ii_UniqueOps;	/* array with one entry per column */
+ 	Oid		   *ii_UniqueProcs;		/* array with one entry per column */
+ 	uint16	   *ii_UniqueStrats;		/* array with one entry per column */
  	bool		ii_Unique;
  	bool		ii_ReadyForInserts;
  	bool		ii_Concurrent;
***************
*** 1085,1090 **** typedef struct ModifyTableState
--- 1091,1097 ----
  	int			mt_whichplan;	/* which one is being executed (0..n-1) */
  	ResultRelInfo *resultRelInfo;		/* per-subplan target relations */
  	List	  **mt_arowmarks;	/* per-subplan ExecAuxRowMark lists */
+ 	SpecType	spec;			/* reason for speculative insertion */
  	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
  	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
  } ModifyTableState;
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 403,408 **** typedef enum NodeTag
--- 403,409 ----
  	T_RowMarkClause,
  	T_XmlSerialize,
  	T_WithClause,
+ 	T_ReturningClause,
  	T_CommonTableExpr,
  
  	/*
***************
*** 546,551 **** typedef enum CmdType
--- 547,565 ----
  								 * with qual */
  } CmdType;
  
+ /* SpecType -
+  *	  "Speculative insertion" clause
+  *
+  * This also appears across various subsystems
+  */
+ typedef enum
+ {
+ 	SPEC_NONE,				/* No reason to insert speculatively */
+ 	SPEC_IGNORE,			/* "ON DUPLICATE KEY IGNORE" */
+ 	SPEC_IGNORE_REJECTS,	/* same as SPEC_IGNORE, plus RETURNING rejected */
+ 	SPEC_UPDATE,			/* "ON DUPLICATE KEY LOCK FOR UPDATE" */
+ 	SPEC_UPDATE_REJECTS		/* same as SPEC_UPDATE, plus RETURNING rejected */
+ } SpecType;
  
  /*
   * JoinType -
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 130,135 **** typedef struct Query
--- 130,137 ----
  
  	List	   *withCheckOptions; /* a list of WithCheckOption's */
  
+ 	SpecType	specClause;		/* speculative insertion clause */
+ 
  	List	   *returningList;	/* return-values list (of TargetEntry) */
  
  	List	   *groupClause;	/* a list of SortGroupClause's */
***************
*** 978,983 **** typedef struct WithClause
--- 980,1000 ----
  } WithClause;
  
  /*
+  * ReturningClause -
+  * 		representation of returninglist for parsing
+  *
+  * Note: ReturningClause does not propogate into the Query representation;
+  * returningList does, while rejects influences speculative insertion.
+  */
+ typedef struct ReturningClause
+ {
+ 	NodeTag		type;
+ 	List	   *returningList;	/* List proper */
+ 	bool		rejects;		/* A list of rejects? */
+ 	int			location;		/* token location, or -1 if unknown */
+ } ReturningClause;
+ 
+ /*
   * CommonTableExpr -
   *	   representation of WITH list element
   *
***************
*** 1027,1033 **** typedef struct InsertStmt
  	RangeVar   *relation;		/* relation to insert into */
  	List	   *cols;			/* optional: names of the target columns */
  	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
! 	List	   *returningList;	/* list of expressions to return */
  	WithClause *withClause;		/* WITH clause */
  } InsertStmt;
  
--- 1044,1051 ----
  	RangeVar   *relation;		/* relation to insert into */
  	List	   *cols;			/* optional: names of the target columns */
  	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
! 	SpecType	specClause;		/* ON DUPLICATE KEY specification */
! 	ReturningClause *rlist;		/* expressions to return */
  	WithClause *withClause;		/* WITH clause */
  } InsertStmt;
  
***************
*** 1041,1047 **** typedef struct DeleteStmt
  	RangeVar   *relation;		/* relation to delete from */
  	List	   *usingClause;	/* optional using clause for more tables */
  	Node	   *whereClause;	/* qualifications */
! 	List	   *returningList;	/* list of expressions to return */
  	WithClause *withClause;		/* WITH clause */
  } DeleteStmt;
  
--- 1059,1065 ----
  	RangeVar   *relation;		/* relation to delete from */
  	List	   *usingClause;	/* optional using clause for more tables */
  	Node	   *whereClause;	/* qualifications */
! 	ReturningClause *rlist;		/* expressions to return */
  	WithClause *withClause;		/* WITH clause */
  } DeleteStmt;
  
***************
*** 1056,1062 **** typedef struct UpdateStmt
  	List	   *targetList;		/* the target list (of ResTarget) */
  	Node	   *whereClause;	/* qualifications */
  	List	   *fromClause;		/* optional from clause for more tables */
! 	List	   *returningList;	/* list of expressions to return */
  	WithClause *withClause;		/* WITH clause */
  } UpdateStmt;
  
--- 1074,1080 ----
  	List	   *targetList;		/* the target list (of ResTarget) */
  	Node	   *whereClause;	/* qualifications */
  	List	   *fromClause;		/* optional from clause for more tables */
! 	ReturningClause *rlist;		/* expressions to return */
  	WithClause *withClause;		/* WITH clause */
  } UpdateStmt;
  
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 176,181 **** typedef struct ModifyTable
--- 176,182 ----
  	List	   *returningLists; /* per-target-table RETURNING tlists */
  	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
  	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
+ 	SpecType	spec;			/* speculative insertion specification */
  	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
  } ModifyTable;
  
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
***************
*** 84,90 **** extern ModifyTable *make_modifytable(PlannerInfo *root,
  				 CmdType operation, bool canSetTag,
  				 List *resultRelations, List *subplans,
  				 List *withCheckOptionLists, List *returningLists,
! 				 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
  
  /*
--- 84,90 ----
  				 CmdType operation, bool canSetTag,
  				 List *resultRelations, List *subplans,
  				 List *withCheckOptionLists, List *returningLists,
! 				 List *rowMarks, SpecType spec, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
  
  /*
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 133,138 **** PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD)
--- 133,139 ----
  PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD)
+ PG_KEYWORD("duplicate", DUPLICATE, UNRESERVED_KEYWORD)
  PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD)
  PG_KEYWORD("else", ELSE, RESERVED_KEYWORD)
  PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD)
***************
*** 180,185 **** PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD)
--- 181,187 ----
  PG_KEYWORD("hour", HOUR_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD)
+ PG_KEYWORD("ignore", IGNORE, UNRESERVED_KEYWORD)
  PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD)
  PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD)
  PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD)
***************
*** 307,312 **** PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD)
--- 309,315 ----
  PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD)
  PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD)
  PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD)
+ PG_KEYWORD("rejects", REJECTS, RESERVED_KEYWORD)
  PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD)
  PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD)
*** a/src/test/isolation/isolation_schedule
--- b/src/test/isolation/isolation_schedule
***************
*** 22,24 **** test: aborted-keyrevoke
--- 22,26 ----
  test: multixact-no-deadlock
  test: drop-index-concurrently-1
  test: timeouts
+ test: insert-duplicate-key-ignore
+ test: insert-duplicate-key-lock-for-update
*** /dev/null
--- b/src/test/isolation/specs/insert-duplicate-key-ignore.spec
***************
*** 0 ****
--- 1,42 ----
+ # INSERT...ON DUPLICATE KEY IGNORE test
+ #
+ # This test tries to expose problems with the interaction between concurrent
+ # sessions during INSERT...ON DUPLICATE KEY IGNORE.
+ #
+ # The convention here is that session 1 always ends up inserting, and session 2
+ # always ends up ignoring.
+ 
+ setup
+ {
+   CREATE TABLE ints (key int primary key, val text);
+ }
+ 
+ teardown
+ {
+   DROP TABLE ints;
+ }
+ 
+ session "s1"
+ setup
+ {
+   BEGIN ISOLATION LEVEL READ COMMITTED;
+ }
+ step "ignore1" { INSERT INTO ints(key, val) VALUES(1, 'ignore1') ON DUPLICATE KEY IGNORE; }
+ step "select1" { SELECT * FROM ints; }
+ step "c1" { COMMIT; }
+ step "a1" { ABORT; }
+ 
+ session "s2"
+ setup
+ {
+   BEGIN ISOLATION LEVEL READ COMMITTED;
+ }
+ step "ignore2" { INSERT INTO ints(key, val) VALUES(1, 'ignore2') ON DUPLICATE KEY IGNORE; }
+ step "select2" { SELECT * FROM ints; }
+ step "c2" { COMMIT; }
+ step "a2" { ABORT; }
+ 
+ # Regular case where one session block-waits on another to determine if it
+ # should proceed with an insert or ignore.
+ permutation "ignore1" "ignore2" "c1" "select2" "c2"
+ permutation "ignore1" "ignore2" "a1" "select2" "c2"
*** /dev/null
--- b/src/test/isolation/specs/insert-duplicate-key-lock-for-update.spec
***************
*** 0 ****
--- 1,39 ----
+ # INSERT...ON DUPLICATE KEY LOCK FOR UPDATE test
+ #
+ # This test tries to expose problems with the interaction between concurrent
+ # sessions during INSERT...ON DUPLICATE LOCK FOR UPDATE.
+ 
+ setup
+ {
+   CREATE TABLE ints (key int primary key, val text);
+ }
+ 
+ teardown
+ {
+   DROP TABLE ints;
+ }
+ 
+ session "s1"
+ setup
+ {
+   BEGIN ISOLATION LEVEL READ COMMITTED;
+ }
+ step "lock1" { INSERT INTO ints(key, val) VALUES(1, 'lock1') ON DUPLICATE KEY LOCK FOR UPDATE; }
+ step "select1" { SELECT * FROM ints; }
+ step "c1" { COMMIT; }
+ step "a1" { ABORT; }
+ 
+ session "s2"
+ setup
+ {
+   BEGIN ISOLATION LEVEL READ COMMITTED;
+ }
+ step "lock2" { INSERT INTO ints(key, val) VALUES(1, 'lock2') ON DUPLICATE KEY LOCK FOR UPDATE; }
+ step "select2" { SELECT * FROM ints; }
+ step "c2" { COMMIT; }
+ step "a2" { ABORT; }
+ 
+ # Regular case where one session block-waits on another to determine if it
+ # should proceed with an insert or lock.
+ permutation "lock1" "lock2" "c1" "select2" "c2"
+ permutation "lock1" "lock2" "a1" "select2" "c2"
