fklocks.patch

application/octet-stream

Filename: fklocks.patch
Type: application/octet-stream
Part: 0
Message: FOR KEY LOCK foreign keys

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
File+
src/backend/access/heap/heapam.c 152 0
src/backend/catalog/index.c 1 0
src/backend/executor/execMain.c 2 0
src/backend/executor/nodeLockRows.c 8 0
src/backend/nodes/copyfuncs.c 2 0
src/backend/nodes/equalfuncs.c 2 0
src/backend/nodes/outfuncs.c 2 0
src/backend/nodes/readfuncs.c 1 0
src/backend/optimizer/plan/initsplan.c 3 0
src/backend/optimizer/plan/planner.c 15 0
src/backend/parser/analyze.c 12 0
src/backend/parser/gram.y 10 0
src/backend/rewrite/rewriteHandler.c 13 0
src/backend/tcop/utility.c 15 0
src/backend/utils/adt/ri_triggers.c 23 0
src/backend/utils/adt/ruleutils.c 16 0
src/backend/utils/cache/relcache.c 17 0
src/include/access/heapam.h 1 0
src/include/access/htup.h 7 0
src/include/nodes/execnodes.h 1 0
src/include/nodes/parsenodes.h 17 0
src/include/nodes/plannodes.h 5 0
src/include/parser/analyze.h 1 0
src/include/utils/relcache.h 1 0
src/include/utils/rel.h 1 0
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
***************
*** 2377,2382 **** heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
--- 2377,2383 ----
  	HTSU_Result result;
  	TransactionId xid = GetCurrentTransactionId();
  	Bitmapset  *hot_attrs;
+ 	Bitmapset  *keylck_attrs;
  	ItemId		lp;
  	HeapTupleData oldtup;
  	HeapTuple	heaptup;
***************
*** 2390,2395 **** heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
--- 2391,2397 ----
  	bool		have_tuple_lock = false;
  	bool		iscombo;
  	bool		use_hot_update = false;
+ 	bool		keylocked_update = false;
  	bool		all_visible_cleared = false;
  	bool		all_visible_cleared_new = false;
  
***************
*** 2407,2413 **** heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
  	 * Note that we get a copy here, so we need not worry about relcache flush
  	 * happening midway through.
  	 */
! 	hot_attrs = RelationGetIndexAttrBitmap(relation);
  
  	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(otid));
  	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
--- 2409,2416 ----
  	 * Note that we get a copy here, so we need not worry about relcache flush
  	 * happening midway through.
  	 */
! 	hot_attrs = RelationGetIndexAttrBitmap(relation, false);
! 	keylck_attrs = RelationGetIndexAttrBitmap(relation, true);
  
  	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(otid));
  	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
***************
*** 2444,2527 **** l2:
  		xwait = HeapTupleHeaderGetXmax(oldtup.t_data);
  		infomask = oldtup.t_data->t_infomask;
  
- 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
- 
  		/*
! 		 * Acquire tuple lock to establish our priority for the tuple (see
! 		 * heap_lock_tuple).  LockTuple will release us when we are
! 		 * next-in-line for the tuple.
! 		 *
! 		 * If we are forced to "start over" below, we keep the tuple lock;
! 		 * this arranges that we stay at the head of the line while rechecking
! 		 * tuple state.
  		 */
! 		if (!have_tuple_lock)
  		{
! 			LockTuple(relation, &(oldtup.t_self), ExclusiveLock);
! 			have_tuple_lock = true;
  		}
  
! 		/*
! 		 * Sleep until concurrent transaction ends.  Note that we don't care
! 		 * if the locker has an exclusive or shared lock, because we need
! 		 * exclusive.
! 		 */
! 
! 		if (infomask & HEAP_XMAX_IS_MULTI)
  		{
! 			/* wait for multixact */
! 			MultiXactIdWait((MultiXactId) xwait);
! 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
  
  			/*
! 			 * If xwait had just locked the tuple then some other xact could
! 			 * update this tuple before we get to this point.  Check for xmax
! 			 * change, and start over if so.
  			 */
! 			if (!(oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
! 				!TransactionIdEquals(HeapTupleHeaderGetXmax(oldtup.t_data),
! 									 xwait))
! 				goto l2;
  
  			/*
! 			 * You might think the multixact is necessarily done here, but not
! 			 * so: it could have surviving members, namely our own xact or
! 			 * other subxacts of this backend.	It is legal for us to update
! 			 * the tuple in either case, however (the latter case is
! 			 * essentially a situation of upgrading our former shared lock to
! 			 * exclusive).	We don't bother changing the on-disk hint bits
! 			 * since we are about to overwrite the xmax altogether.
  			 */
! 		}
! 		else
! 		{
! 			/* wait for regular transaction to end */
! 			XactLockTableWait(xwait);
! 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
  
  			/*
! 			 * xwait is done, but if xwait had just locked the tuple then some
! 			 * other xact could update this tuple before we get to this point.
! 			 * Check for xmax change, and start over if so.
  			 */
! 			if ((oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
! 				!TransactionIdEquals(HeapTupleHeaderGetXmax(oldtup.t_data),
! 									 xwait))
! 				goto l2;
! 
! 			/* Otherwise check if it committed or aborted */
! 			UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
  		}
- 
- 		/*
- 		 * We may overwrite if previous xmax aborted, or if it committed but
- 		 * only locked the tuple without updating it.
- 		 */
- 		if (oldtup.t_data->t_infomask & (HEAP_XMAX_INVALID |
- 										 HEAP_IS_LOCKED))
- 			result = HeapTupleMayBeUpdated;
- 		else
- 			result = HeapTupleUpdated;
  	}
  
  	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
--- 2447,2547 ----
  		xwait = HeapTupleHeaderGetXmax(oldtup.t_data);
  		infomask = oldtup.t_data->t_infomask;
  
  		/*
! 		 * if it's only key-locked and we're not updating an indexed column,
! 		 * we can act though MayBeUpdated was returned, but the resulting tuple
! 		 * needs a bunch of fields copied from the original.
  		 */
! 		if ((infomask & HEAP_XMAX_KEY_LOCK) &&
! 			!(infomask & HEAP_XMAX_SHARED_LOCK) &&
! 			HeapSatisfiesHOTUpdate(relation, keylck_attrs,
! 								   &oldtup, newtup))
  		{
! 			result = HeapTupleMayBeUpdated;
! 			keylocked_update = true;
  		}
  
! 		if (!keylocked_update)
  		{
! 			LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
  
  			/*
! 			 * Acquire tuple lock to establish our priority for the tuple (see
! 			 * heap_lock_tuple).  LockTuple will release us when we are
! 			 * next-in-line for the tuple.
! 			 *
! 			 * If we are forced to "start over" below, we keep the tuple lock;
! 			 * this arranges that we stay at the head of the line while rechecking
! 			 * tuple state.
  			 */
! 			if (!have_tuple_lock)
! 			{
! 				LockTuple(relation, &(oldtup.t_self), ExclusiveLock);
! 				have_tuple_lock = true;
! 			}
  
  			/*
! 			 * Sleep until concurrent transaction ends.  Note that we don't care
! 			 * if the locker has an exclusive or shared lock, because we need
! 			 * exclusive.
  			 */
! 
! 			if (infomask & HEAP_XMAX_IS_MULTI)
! 			{
! 				/* wait for multixact */
! 				MultiXactIdWait((MultiXactId) xwait);
! 				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
! 
! 				/*
! 				 * If xwait had just locked the tuple then some other xact could
! 				 * update this tuple before we get to this point.  Check for xmax
! 				 * change, and start over if so.
! 				 */
! 				if (!(oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
! 					!TransactionIdEquals(HeapTupleHeaderGetXmax(oldtup.t_data),
! 										 xwait))
! 					goto l2;
! 
! 				/*
! 				 * You might think the multixact is necessarily done here, but not
! 				 * so: it could have surviving members, namely our own xact or
! 				 * other subxacts of this backend.	It is legal for us to update
! 				 * the tuple in either case, however (the latter case is
! 				 * essentially a situation of upgrading our former shared lock to
! 				 * exclusive).	We don't bother changing the on-disk hint bits
! 				 * since we are about to overwrite the xmax altogether.
! 				 */
! 			}
! 			else
! 			{
! 				/* wait for regular transaction to end */
! 				XactLockTableWait(xwait);
! 				LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
! 
! 				/*
! 				 * xwait is done, but if xwait had just locked the tuple then some
! 				 * other xact could update this tuple before we get to this point.
! 				 * Check for xmax change, and start over if so.
! 				 */
! 				if ((oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
! 					!TransactionIdEquals(HeapTupleHeaderGetXmax(oldtup.t_data),
! 										 xwait))
! 					goto l2;
! 
! 				/* Otherwise check if it committed or aborted */
! 				UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
! 			}
  
  			/*
! 			 * We may overwrite if previous xmax aborted, or if it committed but
! 			 * only locked the tuple without updating it.
  			 */
! 			if (oldtup.t_data->t_infomask & (HEAP_XMAX_INVALID |
! 											 HEAP_IS_LOCKED))
! 				result = HeapTupleMayBeUpdated;
! 			else
! 				result = HeapTupleUpdated;
  		}
  	}
  
  	if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated)
***************
*** 2563,2575 **** l2:
  
  	newtup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
  	newtup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
! 	newtup->t_data->t_infomask |= (HEAP_XMAX_INVALID | HEAP_UPDATED);
  	HeapTupleHeaderSetXmin(newtup->t_data, xid);
- 	HeapTupleHeaderSetCmin(newtup->t_data, cid);
- 	HeapTupleHeaderSetXmax(newtup->t_data, 0);	/* for cleanliness */
  	newtup->t_tableOid = RelationGetRelid(relation);
  
  	/*
  	 * Replace cid with a combo cid if necessary.  Note that we already put
  	 * the plain cid into the new tuple.
  	 */
--- 2583,2625 ----
  
  	newtup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
  	newtup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
! 	newtup->t_data->t_infomask |= HEAP_UPDATED;
  	HeapTupleHeaderSetXmin(newtup->t_data, xid);
  	newtup->t_tableOid = RelationGetRelid(relation);
  
  	/*
+ 	 * If this update is touching a tuple that was key-locked, we need to
+ 	 * carry forward some bits from the old tuple into the new copy.
+ 	 */
+ 	if (keylocked_update)
+ 	{
+ 		HeapTupleHeaderSetXmax(newtup->t_data,
+ 							   HeapTupleHeaderGetXmax(oldtup.t_data));
+ 		newtup->t_data->t_infomask |= (oldtup.t_data->t_infomask & 
+ 									   (HEAP_XMAX_IS_MULTI |
+ 										HEAP_XMAX_KEY_LOCK));
+ 		/*
+ 		 * we also need to copy the combo CID stuff, but only if the original
+ 		 * tuple was created by us; otherwise the combocid module complains
+ 		 * (Alternatively we could use HeapTupleHeaderGetRawCommandId)
+ 		 */
+ 		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(oldtup.t_data)))
+ 		{
+ 			newtup->t_data->t_infomask |= (oldtup.t_data->t_infomask & 
+ 										   HEAP_COMBOCID);
+ 			HeapTupleHeaderSetCmin(newtup->t_data,
+ 								   HeapTupleHeaderGetCmin(oldtup.t_data));
+ 		}
+ 
+ 	}
+ 	else
+ 	{
+ 		newtup->t_data->t_infomask |= HEAP_XMAX_INVALID;
+ 		HeapTupleHeaderSetXmax(newtup->t_data, 0);	/* for cleanliness */
+ 		HeapTupleHeaderSetCmin(newtup->t_data, cid);
+ 	}
+ 
+ 	/*
  	 * Replace cid with a combo cid if necessary.  Note that we already put
  	 * the plain cid into the new tuple.
  	 */
***************
*** 3080,3086 **** heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer,
  	LOCKMODE	tuple_lock_type;
  	bool		have_tuple_lock = false;
  
! 	tuple_lock_type = (mode == LockTupleShared) ? ShareLock : ExclusiveLock;
  
  	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
  	LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
--- 3130,3149 ----
  	LOCKMODE	tuple_lock_type;
  	bool		have_tuple_lock = false;
  
! 	/* in FOR KEY LOCK mode, we use a share lock temporarily */
! 	switch (mode)
! 	{
! 		case LockTupleShared:
! 		case LockTupleKeylock:
! 			tuple_lock_type = ShareLock;
! 			break;
! 		case LockTupleExclusive:
! 			tuple_lock_type = ExclusiveLock;
! 			break;
! 		default:
! 			elog(ERROR, "invalid tuple lock mode");
! 			tuple_lock_type = 0;	/* keep compiler quiet */
! 	}
  
  	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
  	LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
***************
*** 3113,3130 **** l3:
  		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
  
  		/*
! 		 * If we wish to acquire share lock, and the tuple is already
! 		 * share-locked by a multixact that includes any subtransaction of the
  		 * current top transaction, then we effectively hold the desired lock
  		 * already.  We *must* succeed without trying to take the tuple lock,
  		 * else we will deadlock against anyone waiting to acquire exclusive
  		 * lock.  We don't need to make any state changes in this case.
  		 */
! 		if (mode == LockTupleShared &&
  			(infomask & HEAP_XMAX_IS_MULTI) &&
  			MultiXactIdIsCurrent((MultiXactId) xwait))
  		{
! 			Assert(infomask & HEAP_XMAX_SHARED_LOCK);
  			/* Probably can't hold tuple lock here, but may as well check */
  			if (have_tuple_lock)
  				UnlockTuple(relation, tid, tuple_lock_type);
--- 3176,3193 ----
  		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
  
  		/*
! 		 * If we wish to acquire a key or share lock, and the tuple is already
! 		 * share- or key-locked by a multixact that includes any subtransaction of the
  		 * current top transaction, then we effectively hold the desired lock
  		 * already.  We *must* succeed without trying to take the tuple lock,
  		 * else we will deadlock against anyone waiting to acquire exclusive
  		 * lock.  We don't need to make any state changes in this case.
  		 */
! 		if ((mode == LockTupleShared || mode == LockTupleKeylock) &&
  			(infomask & HEAP_XMAX_IS_MULTI) &&
  			MultiXactIdIsCurrent((MultiXactId) xwait))
  		{
! 			Assert(infomask & HEAP_IS_SHARE_LOCKED);
  			/* Probably can't hold tuple lock here, but may as well check */
  			if (have_tuple_lock)
  				UnlockTuple(relation, tid, tuple_lock_type);
***************
*** 3155,3164 **** l3:
  			have_tuple_lock = true;
  		}
  
! 		if (mode == LockTupleShared && (infomask & HEAP_XMAX_SHARED_LOCK))
  		{
  			/*
! 			 * Acquiring sharelock when there's at least one sharelocker
  			 * already.  We need not wait for him/them to complete.
  			 */
  			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
--- 3218,3228 ----
  			have_tuple_lock = true;
  		}
  
! 		if ((mode == LockTupleShared || mode == LockTupleKeylock) &&
! 			(infomask & HEAP_IS_SHARE_LOCKED))
  		{
  			/*
! 			 * Acquiring sharelock or keylock when there's at least one such locker
  			 * already.  We need not wait for him/them to complete.
  			 */
  			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
***************
*** 3167,3173 **** l3:
  			 * Make sure it's still a shared lock, else start over.  (It's OK
  			 * if the ownership of the shared lock has changed, though.)
  			 */
! 			if (!(tuple->t_data->t_infomask & HEAP_XMAX_SHARED_LOCK))
  				goto l3;
  		}
  		else if (infomask & HEAP_XMAX_IS_MULTI)
--- 3231,3237 ----
  			 * Make sure it's still a shared lock, else start over.  (It's OK
  			 * if the ownership of the shared lock has changed, though.)
  			 */
! 			if (!(tuple->t_data->t_infomask & HEAP_IS_SHARE_LOCKED))
  				goto l3;
  		}
  		else if (infomask & HEAP_XMAX_IS_MULTI)
***************
*** 3277,3284 **** l3:
  	if (!(old_infomask & (HEAP_XMAX_INVALID |
  						  HEAP_XMAX_COMMITTED |
  						  HEAP_XMAX_IS_MULTI)) &&
! 		(mode == LockTupleShared ?
  		 (old_infomask & HEAP_IS_LOCKED) :
  		 (old_infomask & HEAP_XMAX_EXCL_LOCK)) &&
  		TransactionIdIsCurrentTransactionId(xmax))
  	{
--- 3341,3350 ----
  	if (!(old_infomask & (HEAP_XMAX_INVALID |
  						  HEAP_XMAX_COMMITTED |
  						  HEAP_XMAX_IS_MULTI)) &&
! 		(mode == LockTupleKeylock ?
  		 (old_infomask & HEAP_IS_LOCKED) :
+ 		 mode == LockTupleShared ?
+ 		 (old_infomask & (HEAP_XMAX_SHARED_LOCK | HEAP_XMAX_EXCL_LOCK)) :
  		 (old_infomask & HEAP_XMAX_EXCL_LOCK)) &&
  		TransactionIdIsCurrentTransactionId(xmax))
  	{
***************
*** 3302,3311 **** l3:
  									HEAP_IS_LOCKED |
  									HEAP_MOVED);
  
! 	if (mode == LockTupleShared)
  	{
  		/*
! 		 * If this is the first acquisition of a shared lock in the current
  		 * transaction, set my per-backend OldestMemberMXactId setting. We can
  		 * be certain that the transaction will never become a member of any
  		 * older MultiXactIds than that.  (We have to do this even if we end
--- 3368,3377 ----
  									HEAP_IS_LOCKED |
  									HEAP_MOVED);
  
! 	if (mode == LockTupleShared || mode == LockTupleKeylock)
  	{
  		/*
! 		 * If this is the first acquisition of a keylock or shared lock in the current
  		 * transaction, set my per-backend OldestMemberMXactId setting. We can
  		 * be certain that the transaction will never become a member of any
  		 * older MultiXactIds than that.  (We have to do this even if we end
***************
*** 3314,3320 **** l3:
  		 */
  		MultiXactIdSetOldestMember();
  
! 		new_infomask |= HEAP_XMAX_SHARED_LOCK;
  
  		/*
  		 * Check to see if we need a MultiXactId because there are multiple
--- 3380,3387 ----
  		 */
  		MultiXactIdSetOldestMember();
  
! 		new_infomask |= mode == LockTupleShared ? HEAP_XMAX_SHARED_LOCK :
! 			HEAP_XMAX_KEY_LOCK;
  
  		/*
  		 * Check to see if we need a MultiXactId because there are multiple
***************
*** 3414,3420 **** l3:
  		xlrec.target.tid = tuple->t_self;
  		xlrec.locking_xid = xid;
  		xlrec.xid_is_mxact = ((new_infomask & HEAP_XMAX_IS_MULTI) != 0);
! 		xlrec.shared_lock = (mode == LockTupleShared);
  		rdata[0].data = (char *) &xlrec;
  		rdata[0].len = SizeOfHeapLock;
  		rdata[0].buffer = InvalidBuffer;
--- 3481,3487 ----
  		xlrec.target.tid = tuple->t_self;
  		xlrec.locking_xid = xid;
  		xlrec.xid_is_mxact = ((new_infomask & HEAP_XMAX_IS_MULTI) != 0);
! 		xlrec.lock_strength = mode == LockTupleShared ? 's' : mode == LockTupleKeylock ? 'k' : 'x';
  		rdata[0].data = (char *) &xlrec;
  		rdata[0].len = SizeOfHeapLock;
  		rdata[0].buffer = InvalidBuffer;
***************
*** 4733,4740 **** heap_xlog_lock(XLogRecPtr lsn, XLogRecord *record)
  						  HEAP_MOVED);
  	if (xlrec->xid_is_mxact)
  		htup->t_infomask |= HEAP_XMAX_IS_MULTI;
! 	if (xlrec->shared_lock)
  		htup->t_infomask |= HEAP_XMAX_SHARED_LOCK;
  	else
  		htup->t_infomask |= HEAP_XMAX_EXCL_LOCK;
  	HeapTupleHeaderClearHotUpdated(htup);
--- 4800,4809 ----
  						  HEAP_MOVED);
  	if (xlrec->xid_is_mxact)
  		htup->t_infomask |= HEAP_XMAX_IS_MULTI;
! 	if (xlrec->lock_strength == 's')
  		htup->t_infomask |= HEAP_XMAX_SHARED_LOCK;
+ 	else if (xlrec->lock_strength == 'k')
+ 		htup->t_infomask |= HEAP_XMAX_KEY_LOCK;
  	else
  		htup->t_infomask |= HEAP_XMAX_EXCL_LOCK;
  	HeapTupleHeaderClearHotUpdated(htup);
***************
*** 4937,4944 **** heap_desc(StringInfo buf, uint8 xl_info, char *rec)
  	{
  		xl_heap_lock *xlrec = (xl_heap_lock *) rec;
  
! 		if (xlrec->shared_lock)
  			appendStringInfo(buf, "shared_lock: ");
  		else
  			appendStringInfo(buf, "exclusive_lock: ");
  		if (xlrec->xid_is_mxact)
--- 5006,5015 ----
  	{
  		xl_heap_lock *xlrec = (xl_heap_lock *) rec;
  
! 		if (xlrec->lock_strength == 's')
  			appendStringInfo(buf, "shared_lock: ");
+ 		else if (xlrec->lock_strength == 'k')
+ 			appendStringInfo(buf, "key_lock: ");
  		else
  			appendStringInfo(buf, "exclusive_lock: ");
  		if (xlrec->xid_is_mxact)
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 2597,2603 **** reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt)
  
  	/* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */
  	if (is_pg_class)
! 		(void) RelationGetIndexAttrBitmap(rel);
  
  	PG_TRY();
  	{
--- 2597,2603 ----
  
  	/* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */
  	if (is_pg_class)
! 		(void) RelationGetIndexAttrBitmap(rel, false);
  
  	PG_TRY();
  	{
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 701,707 **** InitPlan(QueryDesc *queryDesc, int eflags)
  	}
  
  	/*
! 	 * Similarly, we have to lock relations selected FOR UPDATE/FOR SHARE
  	 * before we initialize the plan tree, else we'd be risking lock upgrades.
  	 * While we are at it, build the ExecRowMark list.
  	 */
--- 701,707 ----
  	}
  
  	/*
! 	 * Similarly, we have to lock relations selected FOR UPDATE/FOR SHARE/KEY LOCK
  	 * before we initialize the plan tree, else we'd be risking lock upgrades.
  	 * While we are at it, build the ExecRowMark list.
  	 */
***************
*** 721,726 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 721,727 ----
  		{
  			case ROW_MARK_EXCLUSIVE:
  			case ROW_MARK_SHARE:
+ 			case ROW_MARK_KEYLOCK:
  				relid = getrelid(rc->rti, rangeTable);
  				relation = heap_open(relid, RowShareLock);
  				break;
*** a/src/backend/executor/nodeLockRows.c
--- b/src/backend/executor/nodeLockRows.c
***************
*** 112,119 **** lnext:
  		/* okay, try to lock the tuple */
  		if (erm->markType == ROW_MARK_EXCLUSIVE)
  			lockmode = LockTupleExclusive;
! 		else
  			lockmode = LockTupleShared;
  
  		test = heap_lock_tuple(erm->relation, &tuple, &buffer,
  							   &update_ctid, &update_xmax,
--- 112,126 ----
  		/* okay, try to lock the tuple */
  		if (erm->markType == ROW_MARK_EXCLUSIVE)
  			lockmode = LockTupleExclusive;
! 		else if (erm->markType == ROW_MARK_SHARE)
  			lockmode = LockTupleShared;
+ 		else if (erm->markType == ROW_MARK_KEYLOCK)
+ 			lockmode = LockTupleKeylock;
+ 		else
+ 		{
+ 			elog(ERROR, "unsupported rowmark type");
+ 			lockmode = LockTupleExclusive;	/* keep compiler quiet */
+ 		}
  
  		test = heap_lock_tuple(erm->relation, &tuple, &buffer,
  							   &update_ctid, &update_xmax,
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1927,1933 **** _copyRowMarkClause(RowMarkClause *from)
  	RowMarkClause *newnode = makeNode(RowMarkClause);
  
  	COPY_SCALAR_FIELD(rti);
! 	COPY_SCALAR_FIELD(forUpdate);
  	COPY_SCALAR_FIELD(noWait);
  	COPY_SCALAR_FIELD(pushedDown);
  
--- 1927,1933 ----
  	RowMarkClause *newnode = makeNode(RowMarkClause);
  
  	COPY_SCALAR_FIELD(rti);
! 	COPY_SCALAR_FIELD(strength);
  	COPY_SCALAR_FIELD(noWait);
  	COPY_SCALAR_FIELD(pushedDown);
  
***************
*** 2266,2272 **** _copyLockingClause(LockingClause *from)
  	LockingClause *newnode = makeNode(LockingClause);
  
  	COPY_NODE_FIELD(lockedRels);
! 	COPY_SCALAR_FIELD(forUpdate);
  	COPY_SCALAR_FIELD(noWait);
  
  	return newnode;
--- 2266,2272 ----
  	LockingClause *newnode = makeNode(LockingClause);
  
  	COPY_NODE_FIELD(lockedRels);
! 	COPY_SCALAR_FIELD(strength);
  	COPY_SCALAR_FIELD(noWait);
  
  	return newnode;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2210,2216 **** static bool
  _equalLockingClause(LockingClause *a, LockingClause *b)
  {
  	COMPARE_NODE_FIELD(lockedRels);
! 	COMPARE_SCALAR_FIELD(forUpdate);
  	COMPARE_SCALAR_FIELD(noWait);
  
  	return true;
--- 2210,2216 ----
  _equalLockingClause(LockingClause *a, LockingClause *b)
  {
  	COMPARE_NODE_FIELD(lockedRels);
! 	COMPARE_SCALAR_FIELD(strength);
  	COMPARE_SCALAR_FIELD(noWait);
  
  	return true;
***************
*** 2277,2283 **** static bool
  _equalRowMarkClause(RowMarkClause *a, RowMarkClause *b)
  {
  	COMPARE_SCALAR_FIELD(rti);
! 	COMPARE_SCALAR_FIELD(forUpdate);
  	COMPARE_SCALAR_FIELD(noWait);
  	COMPARE_SCALAR_FIELD(pushedDown);
  
--- 2277,2283 ----
  _equalRowMarkClause(RowMarkClause *a, RowMarkClause *b)
  {
  	COMPARE_SCALAR_FIELD(rti);
! 	COMPARE_SCALAR_FIELD(strength);
  	COMPARE_SCALAR_FIELD(noWait);
  	COMPARE_SCALAR_FIELD(pushedDown);
  
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 1970,1976 **** _outLockingClause(StringInfo str, LockingClause *node)
  	WRITE_NODE_TYPE("LOCKINGCLAUSE");
  
  	WRITE_NODE_FIELD(lockedRels);
! 	WRITE_BOOL_FIELD(forUpdate);
  	WRITE_BOOL_FIELD(noWait);
  }
  
--- 1970,1976 ----
  	WRITE_NODE_TYPE("LOCKINGCLAUSE");
  
  	WRITE_NODE_FIELD(lockedRels);
! 	WRITE_ENUM_FIELD(strength, LockClauseStrength);
  	WRITE_BOOL_FIELD(noWait);
  }
  
***************
*** 2132,2138 **** _outRowMarkClause(StringInfo str, RowMarkClause *node)
  	WRITE_NODE_TYPE("ROWMARKCLAUSE");
  
  	WRITE_UINT_FIELD(rti);
! 	WRITE_BOOL_FIELD(forUpdate);
  	WRITE_BOOL_FIELD(noWait);
  	WRITE_BOOL_FIELD(pushedDown);
  }
--- 2132,2138 ----
  	WRITE_NODE_TYPE("ROWMARKCLAUSE");
  
  	WRITE_UINT_FIELD(rti);
! 	WRITE_BOOL_FIELD(strength);
  	WRITE_BOOL_FIELD(noWait);
  	WRITE_BOOL_FIELD(pushedDown);
  }
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 299,305 **** _readRowMarkClause(void)
  	READ_LOCALS(RowMarkClause);
  
  	READ_UINT_FIELD(rti);
! 	READ_BOOL_FIELD(forUpdate);
  	READ_BOOL_FIELD(noWait);
  	READ_BOOL_FIELD(pushedDown);
  
--- 299,305 ----
  	READ_LOCALS(RowMarkClause);
  
  	READ_UINT_FIELD(rti);
! 	READ_BOOL_FIELD(strength);
  	READ_BOOL_FIELD(noWait);
  	READ_BOOL_FIELD(pushedDown);
  
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 561,571 **** make_outerjoininfo(PlannerInfo *root,
  	Assert(jointype != JOIN_RIGHT);
  
  	/*
! 	 * Presently the executor cannot support FOR UPDATE/SHARE marking of rels
  	 * appearing on the nullable side of an outer join. (It's somewhat unclear
  	 * what that would mean, anyway: what should we mark when a result row is
  	 * generated from no element of the nullable relation?)  So, complain if
! 	 * any nullable rel is FOR UPDATE/SHARE.
  	 *
  	 * You might be wondering why this test isn't made far upstream in the
  	 * parser.	It's because the parser hasn't got enough info --- consider
--- 561,571 ----
  	Assert(jointype != JOIN_RIGHT);
  
  	/*
! 	 * Presently the executor cannot support FOR UPDATE/SHARE/KEY LOCK marking of rels
  	 * appearing on the nullable side of an outer join. (It's somewhat unclear
  	 * what that would mean, anyway: what should we mark when a result row is
  	 * generated from no element of the nullable relation?)  So, complain if
! 	 * any nullable rel is FOR UPDATE/SHARE/KEY LOCK.
  	 *
  	 * You might be wondering why this test isn't made far upstream in the
  	 * parser.	It's because the parser hasn't got enough info --- consider
***************
*** 583,589 **** make_outerjoininfo(PlannerInfo *root,
  			(jointype == JOIN_FULL && bms_is_member(rc->rti, left_rels)))
  			ereport(ERROR,
  					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("SELECT FOR UPDATE/SHARE cannot be applied to the nullable side of an outer join")));
  	}
  
  	sjinfo->syn_lefthand = left_rels;
--- 583,589 ----
  			(jointype == JOIN_FULL && bms_is_member(rc->rti, left_rels)))
  			ereport(ERROR,
  					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("SELECT FOR UPDATE/SHARE/KEY LOCK cannot be applied to the nullable side of an outer join")));
  	}
  
  	sjinfo->syn_lefthand = left_rels;
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 1829,1835 **** preprocess_rowmarks(PlannerInfo *root)
  	if (parse->rowMarks)
  	{
  		/*
! 		 * We've got trouble if FOR UPDATE/SHARE appears inside grouping,
  		 * since grouping renders a reference to individual tuple CTIDs
  		 * invalid.  This is also checked at parse time, but that's
  		 * insufficient because of rule substitution, query pullup, etc.
--- 1829,1835 ----
  	if (parse->rowMarks)
  	{
  		/*
! 		 * We've got trouble if FOR UPDATE/SHARE/KEY LOCK appears inside grouping,
  		 * since grouping renders a reference to individual tuple CTIDs
  		 * invalid.  This is also checked at parse time, but that's
  		 * insufficient because of rule substitution, query pullup, etc.
***************
*** 1839,1845 **** preprocess_rowmarks(PlannerInfo *root)
  	else
  	{
  		/*
! 		 * We only need rowmarks for UPDATE, DELETE, or FOR UPDATE/SHARE.
  		 */
  		if (parse->commandType != CMD_UPDATE &&
  			parse->commandType != CMD_DELETE)
--- 1839,1845 ----
  	else
  	{
  		/*
! 		 * We only need rowmarks for UPDATE, DELETE, or FOR UPDATE/SHARE/KEY LOCK.
  		 */
  		if (parse->commandType != CMD_UPDATE &&
  			parse->commandType != CMD_DELETE)
***************
*** 1849,1855 **** preprocess_rowmarks(PlannerInfo *root)
  	/*
  	 * We need to have rowmarks for all base relations except the target. We
  	 * make a bitmapset of all base rels and then remove the items we don't
! 	 * need or have FOR UPDATE/SHARE marks for.
  	 */
  	rels = get_base_rel_indexes((Node *) parse->jointree);
  	if (parse->resultRelation)
--- 1849,1855 ----
  	/*
  	 * We need to have rowmarks for all base relations except the target. We
  	 * make a bitmapset of all base rels and then remove the items we don't
! 	 * need or have FOR UPDATE/SHARE/KEY LOCK marks for.
  	 */
  	rels = get_base_rel_indexes((Node *) parse->jointree);
  	if (parse->resultRelation)
***************
*** 1885,1894 **** preprocess_rowmarks(PlannerInfo *root)
  
  		newrc = makeNode(PlanRowMark);
  		newrc->rti = newrc->prti = rc->rti;
! 		if (rc->forUpdate)
! 			newrc->markType = ROW_MARK_EXCLUSIVE;
! 		else
! 			newrc->markType = ROW_MARK_SHARE;
  		newrc->noWait = rc->noWait;
  		newrc->isParent = false;
  
--- 1885,1902 ----
  
  		newrc = makeNode(PlanRowMark);
  		newrc->rti = newrc->prti = rc->rti;
! 		switch (rc->strength)
! 		{
! 			case LCS_FORUPDATE:
! 				newrc->markType = ROW_MARK_EXCLUSIVE;
! 				break;
! 			case LCS_FORSHARE:
! 				newrc->markType = ROW_MARK_SHARE;
! 				break;
! 			case LCS_FORKEYLOCK:
! 				newrc->markType = ROW_MARK_KEYLOCK;
! 				break;
! 		}
  		newrc->noWait = rc->noWait;
  		newrc->isParent = false;
  
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 2148,2154 **** transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
  	/* make a clause we can pass down to subqueries to select all rels */
  	allrels = makeNode(LockingClause);
  	allrels->lockedRels = NIL;	/* indicates all rels */
! 	allrels->forUpdate = lc->forUpdate;
  	allrels->noWait = lc->noWait;
  
  	if (lockedRels == NIL)
--- 2148,2154 ----
  	/* make a clause we can pass down to subqueries to select all rels */
  	allrels = makeNode(LockingClause);
  	allrels->lockedRels = NIL;	/* indicates all rels */
! 	allrels->strength = lc->strength;
  	allrels->noWait = lc->noWait;
  
  	if (lockedRels == NIL)
***************
*** 2164,2175 **** transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
  			{
  				case RTE_RELATION:
  					applyLockingClause(qry, i,
! 									   lc->forUpdate, lc->noWait, pushedDown);
  					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
  					break;
  				case RTE_SUBQUERY:
  					applyLockingClause(qry, i,
! 									   lc->forUpdate, lc->noWait, pushedDown);
  
  					/*
  					 * FOR UPDATE/SHARE of subquery is propagated to all of
--- 2164,2175 ----
  			{
  				case RTE_RELATION:
  					applyLockingClause(qry, i,
! 									   lc->strength, lc->noWait, pushedDown);
  					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
  					break;
  				case RTE_SUBQUERY:
  					applyLockingClause(qry, i,
! 									   lc->strength, lc->noWait, pushedDown);
  
  					/*
  					 * FOR UPDATE/SHARE of subquery is propagated to all of
***************
*** 2213,2225 **** transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
  					{
  						case RTE_RELATION:
  							applyLockingClause(qry, i,
! 											   lc->forUpdate, lc->noWait,
  											   pushedDown);
  							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
  							break;
  						case RTE_SUBQUERY:
  							applyLockingClause(qry, i,
! 											   lc->forUpdate, lc->noWait,
  											   pushedDown);
  							/* see comment above */
  							transformLockingClause(pstate, rte->subquery,
--- 2213,2225 ----
  					{
  						case RTE_RELATION:
  							applyLockingClause(qry, i,
! 											   lc->strength, lc->noWait,
  											   pushedDown);
  							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
  							break;
  						case RTE_SUBQUERY:
  							applyLockingClause(qry, i,
! 											   lc->strength, lc->noWait,
  											   pushedDown);
  							/* see comment above */
  							transformLockingClause(pstate, rte->subquery,
***************
*** 2278,2284 **** transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
   */
  void
  applyLockingClause(Query *qry, Index rtindex,
! 				   bool forUpdate, bool noWait, bool pushedDown)
  {
  	RowMarkClause *rc;
  
--- 2278,2284 ----
   */
  void
  applyLockingClause(Query *qry, Index rtindex,
! 				   LockClauseStrength strength, bool noWait, bool pushedDown)
  {
  	RowMarkClause *rc;
  
***************
*** 2290,2299 **** applyLockingClause(Query *qry, Index rtindex,
  	if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
  	{
  		/*
! 		 * If the same RTE is specified both FOR UPDATE and FOR SHARE, treat
! 		 * it as FOR UPDATE.  (Reasonable, since you can't take both a shared
! 		 * and exclusive lock at the same time; it'll end up being exclusive
! 		 * anyway.)
  		 *
  		 * We also consider that NOWAIT wins if it's specified both ways. This
  		 * is a bit more debatable but raising an error doesn't seem helpful.
--- 2290,2299 ----
  	if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
  	{
  		/*
! 		 * If the same RTE is specified for more than one locking strength,
! 		 * treat is as the strongest.  (Reasonable, since you can't take both a
! 		 * shared and exclusive lock at the same time; it'll end up being
! 		 * exclusive anyway.)
  		 *
  		 * We also consider that NOWAIT wins if it's specified both ways. This
  		 * is a bit more debatable but raising an error doesn't seem helpful.
***************
*** 2302,2308 **** applyLockingClause(Query *qry, Index rtindex,
  		 *
  		 * And of course pushedDown becomes false if any clause is explicit.
  		 */
! 		rc->forUpdate |= forUpdate;
  		rc->noWait |= noWait;
  		rc->pushedDown &= pushedDown;
  		return;
--- 2302,2308 ----
  		 *
  		 * And of course pushedDown becomes false if any clause is explicit.
  		 */
! 		rc->strength = Max(rc->strength, strength);
  		rc->noWait |= noWait;
  		rc->pushedDown &= pushedDown;
  		return;
***************
*** 2311,2317 **** applyLockingClause(Query *qry, Index rtindex,
  	/* Make a new RowMarkClause */
  	rc = makeNode(RowMarkClause);
  	rc->rti = rtindex;
! 	rc->forUpdate = forUpdate;
  	rc->noWait = noWait;
  	rc->pushedDown = pushedDown;
  	qry->rowMarks = lappend(qry->rowMarks, rc);
--- 2311,2317 ----
  	/* Make a new RowMarkClause */
  	rc = makeNode(RowMarkClause);
  	rc->rti = rtindex;
! 	rc->strength = strength;
  	rc->noWait = noWait;
  	rc->pushedDown = pushedDown;
  	qry->rowMarks = lappend(qry->rowMarks, rc);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 8240,8246 **** for_locking_item:
  				{
  					LockingClause *n = makeNode(LockingClause);
  					n->lockedRels = $3;
! 					n->forUpdate = TRUE;
  					n->noWait = $4;
  					$$ = (Node *) n;
  				}
--- 8240,8246 ----
  				{
  					LockingClause *n = makeNode(LockingClause);
  					n->lockedRels = $3;
! 					n->strength = LCS_FORUPDATE;
  					n->noWait = $4;
  					$$ = (Node *) n;
  				}
***************
*** 8248,8257 **** for_locking_item:
  				{
  					LockingClause *n = makeNode(LockingClause);
  					n->lockedRels = $3;
! 					n->forUpdate = FALSE;
  					n->noWait = $4;
  					$$ = (Node *) n;
  				}
  		;
  
  locked_rels_list:
--- 8248,8265 ----
  				{
  					LockingClause *n = makeNode(LockingClause);
  					n->lockedRels = $3;
! 					n->strength = LCS_FORSHARE;
  					n->noWait = $4;
  					$$ = (Node *) n;
  				}
+ 			| FOR KEY LOCK_P locked_rels_list opt_nowait
+ 				{
+ 					LockingClause *n = makeNode(LockingClause);
+ 					n->lockedRels = $4;
+ 					n->strength = LCS_FORKEYLOCK;
+ 					n->noWait = $5;
+ 					$$ = (Node *) n;
+ 				}
  		;
  
  locked_rels_list:
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 55,61 **** static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation,
  static void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
  								Relation target_relation);
  static void markQueryForLocking(Query *qry, Node *jtnode,
! 					bool forUpdate, bool noWait, bool pushedDown);
  static List *matchLocks(CmdType event, RuleLock *rulelocks,
  		   int varno, Query *parsetree);
  static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
--- 55,61 ----
  static void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
  								Relation target_relation);
  static void markQueryForLocking(Query *qry, Node *jtnode,
! 					LockClauseStrength strength, bool noWait, bool pushedDown);
  static List *matchLocks(CmdType event, RuleLock *rulelocks,
  		   int varno, Query *parsetree);
  static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
***************
*** 1352,1359 **** ApplyRetrieveRule(Query *parsetree,
  	rte->modifiedCols = NULL;
  
  	/*
! 	 * If FOR UPDATE/SHARE of view, mark all the contained tables as implicit
! 	 * FOR UPDATE/SHARE, the same as the parser would have done if the view's
  	 * subquery had been written out explicitly.
  	 *
  	 * Note: we don't consider forUpdatePushedDown here; such marks will be
--- 1352,1359 ----
  	rte->modifiedCols = NULL;
  
  	/*
! 	 * If FOR UPDATE/SHARE/KEY LOCK of view, mark all the contained tables as implicit
! 	 * FOR UPDATE/SHARE/KEY LOCK, the same as the parser would have done if the view's
  	 * subquery had been written out explicitly.
  	 *
  	 * Note: we don't consider forUpdatePushedDown here; such marks will be
***************
*** 1361,1373 **** ApplyRetrieveRule(Query *parsetree,
  	 */
  	if (rc != NULL)
  		markQueryForLocking(rule_action, (Node *) rule_action->jointree,
! 							rc->forUpdate, rc->noWait, true);
  
  	return parsetree;
  }
  
  /*
!  * Recursively mark all relations used by a view as FOR UPDATE/SHARE.
   *
   * This may generate an invalid query, eg if some sub-query uses an
   * aggregate.  We leave it to the planner to detect that.
--- 1361,1373 ----
  	 */
  	if (rc != NULL)
  		markQueryForLocking(rule_action, (Node *) rule_action->jointree,
! 							rc->strength, rc->noWait, true);
  
  	return parsetree;
  }
  
  /*
!  * Recursively mark all relations used by a view as FOR UPDATE/SHARE/KEY LOCK.
   *
   * This may generate an invalid query, eg if some sub-query uses an
   * aggregate.  We leave it to the planner to detect that.
***************
*** 1379,1385 **** ApplyRetrieveRule(Query *parsetree,
   */
  static void
  markQueryForLocking(Query *qry, Node *jtnode,
! 					bool forUpdate, bool noWait, bool pushedDown)
  {
  	if (jtnode == NULL)
  		return;
--- 1379,1385 ----
   */
  static void
  markQueryForLocking(Query *qry, Node *jtnode,
! 					LockClauseStrength strength, bool noWait, bool pushedDown)
  {
  	if (jtnode == NULL)
  		return;
***************
*** 1390,1404 **** markQueryForLocking(Query *qry, Node *jtnode,
  
  		if (rte->rtekind == RTE_RELATION)
  		{
! 			applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
  			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
  		}
  		else if (rte->rtekind == RTE_SUBQUERY)
  		{
! 			applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
! 			/* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
  			markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
! 								forUpdate, noWait, true);
  		}
  		/* other RTE types are unaffected by FOR UPDATE */
  	}
--- 1390,1404 ----
  
  		if (rte->rtekind == RTE_RELATION)
  		{
! 			applyLockingClause(qry, rti, strength, noWait, pushedDown);
  			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
  		}
  		else if (rte->rtekind == RTE_SUBQUERY)
  		{
! 			applyLockingClause(qry, rti, strength, noWait, pushedDown);
! 			/* FOR UPDATE/SHARE/KEY LOCK of subquery is propagated to subquery's rels */
  			markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
! 								strength, noWait, true);
  		}
  		/* other RTE types are unaffected by FOR UPDATE */
  	}
***************
*** 1408,1421 **** markQueryForLocking(Query *qry, Node *jtnode,
  		ListCell   *l;
  
  		foreach(l, f->fromlist)
! 			markQueryForLocking(qry, lfirst(l), forUpdate, noWait, pushedDown);
  	}
  	else if (IsA(jtnode, JoinExpr))
  	{
  		JoinExpr   *j = (JoinExpr *) jtnode;
  
! 		markQueryForLocking(qry, j->larg, forUpdate, noWait, pushedDown);
! 		markQueryForLocking(qry, j->rarg, forUpdate, noWait, pushedDown);
  	}
  	else
  		elog(ERROR, "unrecognized node type: %d",
--- 1408,1421 ----
  		ListCell   *l;
  
  		foreach(l, f->fromlist)
! 			markQueryForLocking(qry, lfirst(l), strength, noWait, pushedDown);
  	}
  	else if (IsA(jtnode, JoinExpr))
  	{
  		JoinExpr   *j = (JoinExpr *) jtnode;
  
! 		markQueryForLocking(qry, j->larg, strength, noWait, pushedDown);
! 		markQueryForLocking(qry, j->rarg, strength, noWait, pushedDown);
  	}
  	else
  		elog(ERROR, "unrecognized node type: %d",
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 2271,2280 **** CreateCommandTag(Node *parsetree)
  						else if (stmt->rowMarks != NIL)
  						{
  							/* not 100% but probably close enough */
! 							if (((RowMarkClause *) linitial(stmt->rowMarks))->forUpdate)
! 								tag = "SELECT FOR UPDATE";
! 							else
! 								tag = "SELECT FOR SHARE";
  						}
  						else
  							tag = "SELECT";
--- 2271,2291 ----
  						else if (stmt->rowMarks != NIL)
  						{
  							/* not 100% but probably close enough */
! 							switch (((RowMarkClause *) linitial(stmt->rowMarks))->strength)
! 							{
! 								case LCS_FORUPDATE:
! 									tag = "SELECT FOR UPDATE";
! 									break;
! 								case LCS_FORSHARE:
! 									tag = "SELECT FOR SHARE";
! 									break;
! 								case LCS_FORKEYLOCK:
! 									tag = "SELECT FOR KEY LOCK";
! 									break;
! 								default:
! 									tag =  "???";
! 									break;
! 							}
  						}
  						else
  							tag = "SELECT";
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 308,314 **** RI_FKey_check(PG_FUNCTION_ARGS)
  	 * Get the relation descriptors of the FK and PK tables.
  	 *
  	 * pk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR SHARE will get on it.
  	 */
  	fk_rel = trigdata->tg_relation;
  	pk_rel = heap_open(riinfo.pk_relid, RowShareLock);
--- 308,314 ----
  	 * Get the relation descriptors of the FK and PK tables.
  	 *
  	 * pk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR KEY LOCK will get on it.
  	 */
  	fk_rel = trigdata->tg_relation;
  	pk_rel = heap_open(riinfo.pk_relid, RowShareLock);
***************
*** 338,349 **** RI_FKey_check(PG_FUNCTION_ARGS)
  
  			/* ---------
  			 * The query string built is
! 			 *	SELECT 1 FROM ONLY <pktable>
  			 * ----------
  			 */
  			quoteRelationName(pkrelname, pk_rel);
  			snprintf(querystr, sizeof(querystr),
! 					 "SELECT 1 FROM ONLY %s x FOR SHARE OF x",
  					 pkrelname);
  
  			/* Prepare and save the plan */
--- 338,349 ----
  
  			/* ---------
  			 * The query string built is
! 			 *	SELECT 1 FROM ONLY <pktable> x FOR KEY LOCK OF x
  			 * ----------
  			 */
  			quoteRelationName(pkrelname, pk_rel);
  			snprintf(querystr, sizeof(querystr),
! 					 "SELECT 1 FROM ONLY %s x FOR KEY LOCK OF x",
  					 pkrelname);
  
  			/* Prepare and save the plan */
***************
*** 463,469 **** RI_FKey_check(PG_FUNCTION_ARGS)
  
  		/* ----------
  		 * The query string built is
! 		 *	SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
  		 * The type id's for the $ parameters are those of the
  		 * corresponding FK attributes.
  		 * ----------
--- 463,470 ----
  
  		/* ----------
  		 * The query string built is
! 		 *	SELECT 1 FROM ONLY <pktable> x WHERE pkatt1 = $1 [AND ...]
! 		 *	       FOR KEY LOCK OF x
  		 * The type id's for the $ parameters are those of the
  		 * corresponding FK attributes.
  		 * ----------
***************
*** 487,493 **** RI_FKey_check(PG_FUNCTION_ARGS)
  			querysep = "AND";
  			queryoids[i] = fk_type;
  		}
! 		appendStringInfo(&querybuf, " FOR SHARE OF x");
  
  		/* Prepare and save the plan */
  		qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
--- 488,494 ----
  			querysep = "AND";
  			queryoids[i] = fk_type;
  		}
! 		appendStringInfo(&querybuf, " FOR KEY LOCK OF x");
  
  		/* Prepare and save the plan */
  		qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
***************
*** 625,631 **** ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
  
  		/* ----------
  		 * The query string built is
! 		 *	SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
  		 * The type id's for the $ parameters are those of the
  		 * PK attributes themselves.
  		 * ----------
--- 626,633 ----
  
  		/* ----------
  		 * The query string built is
! 		 *	SELECT 1 FROM ONLY <pktable> x WHERE pkatt1 = $1 [AND ...]
! 		 *	       FOR KEY LOCK OF x
  		 * The type id's for the $ parameters are those of the
  		 * PK attributes themselves.
  		 * ----------
***************
*** 648,654 **** ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
  			querysep = "AND";
  			queryoids[i] = pk_type;
  		}
! 		appendStringInfo(&querybuf, " FOR SHARE OF x");
  
  		/* Prepare and save the plan */
  		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
--- 650,656 ----
  			querysep = "AND";
  			queryoids[i] = pk_type;
  		}
! 		appendStringInfo(&querybuf, " FOR KEY LOCK OF x");
  
  		/* Prepare and save the plan */
  		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
***************
*** 712,718 **** RI_FKey_noaction_del(PG_FUNCTION_ARGS)
  	 * Get the relation descriptors of the FK and PK tables and the old tuple.
  	 *
  	 * fk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR SHARE will get on it.
  	 */
  	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
  	pk_rel = trigdata->tg_relation;
--- 714,720 ----
  	 * Get the relation descriptors of the FK and PK tables and the old tuple.
  	 *
  	 * fk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR KEY LOCK will get on it.
  	 */
  	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
  	pk_rel = trigdata->tg_relation;
***************
*** 780,786 **** RI_FKey_noaction_del(PG_FUNCTION_ARGS)
  
  				/* ----------
  				 * The query string built is
! 				 *	SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
  				 * The type id's for the $ parameters are those of the
  				 * corresponding PK attributes.
  				 * ----------
--- 782,789 ----
  
  				/* ----------
  				 * The query string built is
! 				 *	SELECT 1 FROM ONLY <fktable> x WHERE $1 = fkatt1 [AND ...]
! 				 *	       FOR KEY LOCK OF x
  				 * The type id's for the $ parameters are those of the
  				 * corresponding PK attributes.
  				 * ----------
***************
*** 805,811 **** RI_FKey_noaction_del(PG_FUNCTION_ARGS)
  					querysep = "AND";
  					queryoids[i] = pk_type;
  				}
! 				appendStringInfo(&querybuf, " FOR SHARE OF x");
  
  				/* Prepare and save the plan */
  				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
--- 808,814 ----
  					querysep = "AND";
  					queryoids[i] = pk_type;
  				}
! 				appendStringInfo(&querybuf, " FOR KEY LOCK OF x");
  
  				/* Prepare and save the plan */
  				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
***************
*** 890,896 **** RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
  	 * old tuple.
  	 *
  	 * fk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR SHARE will get on it.
  	 */
  	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
  	pk_rel = trigdata->tg_relation;
--- 893,899 ----
  	 * old tuple.
  	 *
  	 * fk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR KEY LOCK will get on it.
  	 */
  	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
  	pk_rel = trigdata->tg_relation;
***************
*** 993,999 **** RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
  					querysep = "AND";
  					queryoids[i] = pk_type;
  				}
! 				appendStringInfo(&querybuf, " FOR SHARE OF x");
  
  				/* Prepare and save the plan */
  				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
--- 996,1002 ----
  					querysep = "AND";
  					queryoids[i] = pk_type;
  				}
! 				appendStringInfo(&querybuf, " FOR KEY LOCK OF x");
  
  				/* Prepare and save the plan */
  				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
***************
*** 1431,1437 **** RI_FKey_restrict_del(PG_FUNCTION_ARGS)
  	 * Get the relation descriptors of the FK and PK tables and the old tuple.
  	 *
  	 * fk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR SHARE will get on it.
  	 */
  	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
  	pk_rel = trigdata->tg_relation;
--- 1434,1440 ----
  	 * Get the relation descriptors of the FK and PK tables and the old tuple.
  	 *
  	 * fk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR KEY LOCK will get on it.
  	 */
  	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
  	pk_rel = trigdata->tg_relation;
***************
*** 1489,1495 **** RI_FKey_restrict_del(PG_FUNCTION_ARGS)
  
  				/* ----------
  				 * The query string built is
! 				 *	SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
  				 * The type id's for the $ parameters are those of the
  				 * corresponding PK attributes.
  				 * ----------
--- 1492,1499 ----
  
  				/* ----------
  				 * The query string built is
! 				 *	SELECT 1 FROM ONLY <fktable> x WHERE $1 = fkatt1 [AND ...]
! 				 *	       FOR KEY LOCK OF x
  				 * The type id's for the $ parameters are those of the
  				 * corresponding PK attributes.
  				 * ----------
***************
*** 1514,1520 **** RI_FKey_restrict_del(PG_FUNCTION_ARGS)
  					querysep = "AND";
  					queryoids[i] = pk_type;
  				}
! 				appendStringInfo(&querybuf, " FOR SHARE OF x");
  
  				/* Prepare and save the plan */
  				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
--- 1518,1524 ----
  					querysep = "AND";
  					queryoids[i] = pk_type;
  				}
! 				appendStringInfo(&querybuf, " FOR KEY LOCK OF x");
  
  				/* Prepare and save the plan */
  				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
***************
*** 1604,1610 **** RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
  	 * old tuple.
  	 *
  	 * fk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR SHARE will get on it.
  	 */
  	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
  	pk_rel = trigdata->tg_relation;
--- 1608,1614 ----
  	 * old tuple.
  	 *
  	 * fk_rel is opened in RowShareLock mode since that's what our eventual
! 	 * SELECT FOR KEY LOCK will get on it.
  	 */
  	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
  	pk_rel = trigdata->tg_relation;
***************
*** 1672,1678 **** RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
  
  				/* ----------
  				 * The query string built is
! 				 *	SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
  				 * The type id's for the $ parameters are those of the
  				 * corresponding PK attributes.
  				 * ----------
--- 1676,1683 ----
  
  				/* ----------
  				 * The query string built is
! 				 *	SELECT 1 FROM ONLY <fktable> x WHERE $1 = fkatt1 [AND ...]
! 				 *	       FOR KEY LOCK OF x
  				 * The type id's for the $ parameters are those of the
  				 * corresponding PK attributes.
  				 * ----------
***************
*** 1697,1703 **** RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
  					querysep = "AND";
  					queryoids[i] = pk_type;
  				}
! 				appendStringInfo(&querybuf, " FOR SHARE OF x");
  
  				/* Prepare and save the plan */
  				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
--- 1702,1708 ----
  					querysep = "AND";
  					queryoids[i] = pk_type;
  				}
! 				appendStringInfo(&querybuf, " FOR KEY LOCK OF x");
  
  				/* Prepare and save the plan */
  				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 2821,2832 **** get_select_query_def(Query *query, deparse_context *context,
  			if (rc->pushedDown)
  				continue;
  
! 			if (rc->forUpdate)
! 				appendContextKeyword(context, " FOR UPDATE",
! 									 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
! 			else
! 				appendContextKeyword(context, " FOR SHARE",
! 									 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
  			appendStringInfo(buf, " OF %s",
  							 quote_identifier(rte->eref->aliasname));
  			if (rc->noWait)
--- 2821,2842 ----
  			if (rc->pushedDown)
  				continue;
  
! 			switch (rc->strength)
! 			{
! 				case LCS_FORKEYLOCK:
! 					appendContextKeyword(context, " FOR KEY LOCK",
! 										 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
! 					break;
! 				case LCS_FORSHARE:
! 					appendContextKeyword(context, " FOR SHARE",
! 										 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
! 					break;
! 				case LCS_FORUPDATE:
! 					appendContextKeyword(context, " FOR UPDATE",
! 										 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
! 					break;
! 			}
! 
  			appendStringInfo(buf, " OF %s",
  							 quote_identifier(rte->eref->aliasname));
  			if (rc->noWait)
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 3590,3595 **** RelationGetIndexPredicate(Relation relation)
--- 3590,3597 ----
   * simple index keys, but attributes used in expressions and partial-index
   * predicates.)
   *
+  * If "unique" is true, only attributes of unique indexes are considered.
+  *
   * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
   * we can include system attributes (e.g., OID) in the bitmap representation.
   *
***************
*** 3597,3612 **** RelationGetIndexPredicate(Relation relation)
   * be bms_free'd when not needed anymore.
   */
  Bitmapset *
! RelationGetIndexAttrBitmap(Relation relation)
  {
  	Bitmapset  *indexattrs;
  	List	   *indexoidlist;
  	ListCell   *l;
  	MemoryContext oldcxt;
  
  	/* Quick exit if we already computed the result. */
  	if (relation->rd_indexattr != NULL)
! 		return bms_copy(relation->rd_indexattr);
  
  	/* Fast path if definitely no indexes */
  	if (!RelationGetForm(relation)->relhasindex)
--- 3599,3615 ----
   * be bms_free'd when not needed anymore.
   */
  Bitmapset *
! RelationGetIndexAttrBitmap(Relation relation, bool unique)
  {
  	Bitmapset  *indexattrs;
+ 	Bitmapset  *uindexattrs;
  	List	   *indexoidlist;
  	ListCell   *l;
  	MemoryContext oldcxt;
  
  	/* Quick exit if we already computed the result. */
  	if (relation->rd_indexattr != NULL)
! 		return bms_copy(unique ? relation->rd_uindexattr : relation->rd_indexattr);
  
  	/* Fast path if definitely no indexes */
  	if (!RelationGetForm(relation)->relhasindex)
***************
*** 3625,3630 **** RelationGetIndexAttrBitmap(Relation relation)
--- 3628,3634 ----
  	 * For each index, add referenced attributes to indexattrs.
  	 */
  	indexattrs = NULL;
+ 	uindexattrs = NULL;
  	foreach(l, indexoidlist)
  	{
  		Oid			indexOid = lfirst_oid(l);
***************
*** 3643,3657 **** RelationGetIndexAttrBitmap(Relation relation)
--- 3647,3670 ----
  			int			attrnum = indexInfo->ii_KeyAttrNumbers[i];
  
  			if (attrnum != 0)
+ 			{
  				indexattrs = bms_add_member(indexattrs,
  							   attrnum - FirstLowInvalidHeapAttributeNumber);
+ 				if (indexInfo->ii_Unique)
+ 					uindexattrs = bms_add_member(uindexattrs,
+ 											   	 attrnum - FirstLowInvalidHeapAttributeNumber);
+ 			}
  		}
  
  		/* Collect all attributes used in expressions, too */
  		pull_varattnos((Node *) indexInfo->ii_Expressions, &indexattrs);
+ 		if (indexInfo->ii_Unique)
+ 			pull_varattnos((Node *) indexInfo->ii_Expressions, &uindexattrs);
  
  		/* Collect all attributes in the index predicate, too */
  		pull_varattnos((Node *) indexInfo->ii_Predicate, &indexattrs);
+ 		if (indexInfo->ii_Unique)
+ 			pull_varattnos((Node *) indexInfo->ii_Predicate, &uindexattrs);
  
  		index_close(indexDesc, AccessShareLock);
  	}
***************
*** 3661,3670 **** RelationGetIndexAttrBitmap(Relation relation)
  	/* Now save a copy of the bitmap in the relcache entry. */
  	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
  	relation->rd_indexattr = bms_copy(indexattrs);
  	MemoryContextSwitchTo(oldcxt);
  
  	/* We return our original working copy for caller to play with */
! 	return indexattrs;
  }
  
  /*
--- 3674,3684 ----
  	/* Now save a copy of the bitmap in the relcache entry. */
  	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
  	relation->rd_indexattr = bms_copy(indexattrs);
+ 	relation->rd_uindexattr = bms_copy(uindexattrs);
  	MemoryContextSwitchTo(oldcxt);
  
  	/* We return our original working copy for caller to play with */
! 	return unique ? uindexattrs : indexattrs;
  }
  
  /*
*** a/src/include/access/heapam.h
--- b/src/include/access/heapam.h
***************
*** 33,38 **** typedef struct BulkInsertStateData *BulkInsertState;
--- 33,39 ----
  
  typedef enum
  {
+ 	LockTupleKeylock,
  	LockTupleShared,
  	LockTupleExclusive
  } LockTupleMode;
*** a/src/include/access/htup.h
--- b/src/include/access/htup.h
***************
*** 163,174 **** typedef HeapTupleHeaderData *HeapTupleHeader;
  #define HEAP_HASVARWIDTH		0x0002	/* has variable-width attribute(s) */
  #define HEAP_HASEXTERNAL		0x0004	/* has external stored attribute(s) */
  #define HEAP_HASOID				0x0008	/* has an object-id field */
! /* bit 0x0010 is available */
  #define HEAP_COMBOCID			0x0020	/* t_cid is a combo cid */
  #define HEAP_XMAX_EXCL_LOCK		0x0040	/* xmax is exclusive locker */
  #define HEAP_XMAX_SHARED_LOCK	0x0080	/* xmax is shared locker */
  /* if either LOCK bit is set, xmax hasn't deleted the tuple, only locked it */
! #define HEAP_IS_LOCKED	(HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_SHARED_LOCK)
  #define HEAP_XMIN_COMMITTED		0x0100	/* t_xmin committed */
  #define HEAP_XMIN_INVALID		0x0200	/* t_xmin invalid/aborted */
  #define HEAP_XMAX_COMMITTED		0x0400	/* t_xmax committed */
--- 163,177 ----
  #define HEAP_HASVARWIDTH		0x0002	/* has variable-width attribute(s) */
  #define HEAP_HASEXTERNAL		0x0004	/* has external stored attribute(s) */
  #define HEAP_HASOID				0x0008	/* has an object-id field */
! #define HEAP_XMAX_KEY_LOCK		0x0010	/* xmax is a "key" locker */
  #define HEAP_COMBOCID			0x0020	/* t_cid is a combo cid */
  #define HEAP_XMAX_EXCL_LOCK		0x0040	/* xmax is exclusive locker */
  #define HEAP_XMAX_SHARED_LOCK	0x0080	/* xmax is shared locker */
+ /* if either SHARE or KEY lock bit is set, this is a "shared" lock */
+ #define HEAP_IS_SHARE_LOCKED (HEAP_XMAX_SHARED_LOCK | HEAP_XMAX_KEY_LOCK)
  /* if either LOCK bit is set, xmax hasn't deleted the tuple, only locked it */
! #define HEAP_IS_LOCKED	(HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_SHARED_LOCK | \
! 						 HEAP_XMAX_KEY_LOCK)
  #define HEAP_XMIN_COMMITTED		0x0100	/* t_xmin committed */
  #define HEAP_XMIN_INVALID		0x0200	/* t_xmin invalid/aborted */
  #define HEAP_XMAX_COMMITTED		0x0400	/* t_xmax committed */
***************
*** 725,734 **** typedef struct xl_heap_lock
  	xl_heaptid	target;			/* locked tuple id */
  	TransactionId locking_xid;	/* might be a MultiXactId not xid */
  	bool		xid_is_mxact;	/* is it? */
! 	bool		shared_lock;	/* shared or exclusive row lock? */
  } xl_heap_lock;
  
! #define SizeOfHeapLock	(offsetof(xl_heap_lock, shared_lock) + sizeof(bool))
  
  /* This is what we need to know about in-place update */
  typedef struct xl_heap_inplace
--- 728,737 ----
  	xl_heaptid	target;			/* locked tuple id */
  	TransactionId locking_xid;	/* might be a MultiXactId not xid */
  	bool		xid_is_mxact;	/* is it? */
! 	char		lock_strength;	/* keylock, shared, exclusive lock? */
  } xl_heap_lock;
  
! #define SizeOfHeapLock	(offsetof(xl_heap_lock, lock_strength) + sizeof(char))
  
  /* This is what we need to know about in-place update */
  typedef struct xl_heap_inplace
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 404,410 **** typedef struct EState
   * ExecRowMark -
   *	   runtime representation of FOR UPDATE/SHARE clauses
   *
!  * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we should have an
   * ExecRowMark for each non-target relation in the query (except inheritance
   * parent RTEs, which can be ignored at runtime).  See PlanRowMark for details
   * about most of the fields.  In addition to fields directly derived from
--- 404,410 ----
   * ExecRowMark -
   *	   runtime representation of FOR UPDATE/SHARE clauses
   *
!  * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE/KEY LOCK, we should have an
   * ExecRowMark for each non-target relation in the query (except inheritance
   * parent RTEs, which can be ignored at runtime).  See PlanRowMark for details
   * about most of the fields.  In addition to fields directly derived from
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 550,567 **** typedef struct DefElem
  } DefElem;
  
  /*
!  * LockingClause - raw representation of FOR UPDATE/SHARE options
   *
   * Note: lockedRels == NIL means "all relations in query".	Otherwise it
   * is a list of RangeVar nodes.  (We use RangeVar mainly because it carries
   * a location field --- currently, parse analysis insists on unqualified
   * names in LockingClause.)
   */
  typedef struct LockingClause
  {
  	NodeTag		type;
  	List	   *lockedRels;		/* FOR UPDATE or FOR SHARE relations */
! 	bool		forUpdate;		/* true = FOR UPDATE, false = FOR SHARE */
  	bool		noWait;			/* NOWAIT option */
  } LockingClause;
  
--- 550,575 ----
  } DefElem;
  
  /*
!  * LockingClause - raw representation of FOR UPDATE/SHARE/KEY LOCK options
   *
   * Note: lockedRels == NIL means "all relations in query".	Otherwise it
   * is a list of RangeVar nodes.  (We use RangeVar mainly because it carries
   * a location field --- currently, parse analysis insists on unqualified
   * names in LockingClause.)
   */
+ typedef enum LockClauseStrength
+ {
+ 	/* order is important -- see applyLockingClause */
+ 	LCS_FORKEYLOCK,
+ 	LCS_FORSHARE,
+ 	LCS_FORUPDATE
+ } LockClauseStrength;
+ 
  typedef struct LockingClause
  {
  	NodeTag		type;
  	List	   *lockedRels;		/* FOR UPDATE or FOR SHARE relations */
! 	LockClauseStrength strength;
  	bool		noWait;			/* NOWAIT option */
  } LockingClause;
  
***************
*** 832,849 **** typedef struct WindowClause
   *	   parser output representation of FOR UPDATE/SHARE clauses
   *
   * Query.rowMarks contains a separate RowMarkClause node for each relation
!  * identified as a FOR UPDATE/SHARE target.  If FOR UPDATE/SHARE is applied
!  * to a subquery, we generate RowMarkClauses for all normal and subquery rels
!  * in the subquery, but they are marked pushedDown = true to distinguish them
!  * from clauses that were explicitly written at this query level.  Also,
!  * Query.hasForUpdate tells whether there were explicit FOR UPDATE/SHARE
!  * clauses in the current query level.
   */
  typedef struct RowMarkClause
  {
  	NodeTag		type;
  	Index		rti;			/* range table index of target relation */
! 	bool		forUpdate;		/* true = FOR UPDATE, false = FOR SHARE */
  	bool		noWait;			/* NOWAIT option */
  	bool		pushedDown;		/* pushed down from higher query level? */
  } RowMarkClause;
--- 840,857 ----
   *	   parser output representation of FOR UPDATE/SHARE clauses
   *
   * Query.rowMarks contains a separate RowMarkClause node for each relation
!  * identified as a FOR UPDATE/SHARE/KEY LOCK target.  If one of these clauses
!  * is applied to a subquery, we generate RowMarkClauses for all normal and
!  * subquery rels in the subquery, but they are marked pushedDown = true to
!  * distinguish them from clauses that were explicitly written at this query
!  * level.  Also, Query.hasForUpdate tells whether there were explicit FOR
!  * UPDATE/SHARE clauses in the current query level.
   */
  typedef struct RowMarkClause
  {
  	NodeTag		type;
  	Index		rti;			/* range table index of target relation */
! 	LockClauseStrength strength;
  	bool		noWait;			/* NOWAIT option */
  	bool		pushedDown;		/* pushed down from higher query level? */
  } RowMarkClause;
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 702,708 **** typedef struct Limit
   * RowMarkType -
   *	  enums for types of row-marking operations
   *
!  * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we have to uniquely
   * identify all the source rows, not only those from the target relations, so
   * that we can perform EvalPlanQual rechecking at need.  For plain tables we
   * can just fetch the TID, the same as for a target relation.  Otherwise (for
--- 702,708 ----
   * RowMarkType -
   *	  enums for types of row-marking operations
   *
!  * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE/KEY LOCK, we have to uniquely
   * identify all the source rows, not only those from the target relations, so
   * that we can perform EvalPlanQual rechecking at need.  For plain tables we
   * can just fetch the TID, the same as for a target relation.  Otherwise (for
***************
*** 714,732 **** typedef enum RowMarkType
  {
  	ROW_MARK_EXCLUSIVE,			/* obtain exclusive tuple lock */
  	ROW_MARK_SHARE,				/* obtain shared tuple lock */
  	ROW_MARK_REFERENCE,			/* just fetch the TID */
  	ROW_MARK_COPY				/* physically copy the row value */
  } RowMarkType;
  
! #define RowMarkRequiresRowShareLock(marktype)  ((marktype) <= ROW_MARK_SHARE)
  
  /*
   * PlanRowMark -
   *	   plan-time representation of FOR UPDATE/SHARE clauses
   *
!  * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we create a separate
   * PlanRowMark node for each non-target relation in the query.	Relations that
!  * are not specified as FOR UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
   * real tables) or ROW_MARK_COPY (if not).
   *
   * Initially all PlanRowMarks have rti == prti and isParent == false.
--- 714,733 ----
  {
  	ROW_MARK_EXCLUSIVE,			/* obtain exclusive tuple lock */
  	ROW_MARK_SHARE,				/* obtain shared tuple lock */
+ 	ROW_MARK_KEYLOCK,			/* obtain keylock tuple lock */
  	ROW_MARK_REFERENCE,			/* just fetch the TID */
  	ROW_MARK_COPY				/* physically copy the row value */
  } RowMarkType;
  
! #define RowMarkRequiresRowShareLock(marktype)  ((marktype) <= ROW_MARK_KEYLOCK)
  
  /*
   * PlanRowMark -
   *	   plan-time representation of FOR UPDATE/SHARE clauses
   *
!  * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE/KEY LOCK, we create a separate
   * PlanRowMark node for each non-target relation in the query.	Relations that
!  * are not specified as FOR UPDATE/SHARE/KEY LOCK are marked ROW_MARK_REFERENCE (if
   * real tables) or ROW_MARK_COPY (if not).
   *
   * Initially all PlanRowMarks have rti == prti and isParent == false.
*** a/src/include/parser/analyze.h
--- b/src/include/parser/analyze.h
***************
*** 31,36 **** extern bool analyze_requires_snapshot(Node *parseTree);
  
  extern void CheckSelectLocking(Query *qry);
  extern void applyLockingClause(Query *qry, Index rtindex,
! 				   bool forUpdate, bool noWait, bool pushedDown);
  
  #endif   /* ANALYZE_H */
--- 31,36 ----
  
  extern void CheckSelectLocking(Query *qry);
  extern void applyLockingClause(Query *qry, Index rtindex,
! 				   LockClauseStrength strength, bool noWait, bool pushedDown);
  
  #endif   /* ANALYZE_H */
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 156,161 **** typedef struct RelationData
--- 156,162 ----
  	Oid			rd_id;			/* relation's object id */
  	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
  	Bitmapset  *rd_indexattr;	/* identifies columns used in indexes */
+ 	Bitmapset  *rd_uindexattr;	/* identifies columns used in unique indexes */
  	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
  	LockInfoData rd_lockInfo;	/* lock mgr's info for locking relation */
  	RuleLock   *rd_rules;		/* rewrite rules */
*** a/src/include/utils/relcache.h
--- b/src/include/utils/relcache.h
***************
*** 42,48 **** extern List *RelationGetIndexList(Relation relation);
  extern Oid	RelationGetOidIndex(Relation relation);
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
! extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
  extern void RelationGetExclusionInfo(Relation indexRelation,
  						 Oid **operators,
  						 Oid **procs,
--- 42,48 ----
  extern Oid	RelationGetOidIndex(Relation relation);
  extern List *RelationGetIndexExpressions(Relation relation);
  extern List *RelationGetIndexPredicate(Relation relation);
! extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation, bool unique);
  extern void RelationGetExclusionInfo(Relation indexRelation,
  						 Oid **operators,
  						 Oid **procs,