fklocks-20110211.patch
text/plain
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
***************
*** 2417,2422 **** heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
--- 2417,2423 ----
HTSU_Result result;
TransactionId xid = GetCurrentTransactionId();
Bitmapset *hot_attrs;
+ Bitmapset *keylck_attrs;
ItemId lp;
HeapTupleData oldtup;
HeapTuple heaptup;
***************
*** 2430,2435 **** heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
--- 2431,2437 ----
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;
***************
*** 2447,2453 **** 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);
--- 2449,2456 ----
* 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);
***************
*** 2484,2567 **** 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)
--- 2487,2587 ----
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)
***************
*** 2609,2621 **** 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.
*/
--- 2629,2671 ----
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.
*/
***************
*** 3142,3148 **** 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);
--- 3192,3211 ----
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);
***************
*** 3175,3192 **** 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);
--- 3238,3255 ----
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);
***************
*** 3217,3226 **** 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);
--- 3280,3290 ----
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);
***************
*** 3229,3235 **** 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)
--- 3293,3299 ----
* 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)
***************
*** 3339,3346 **** 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))
{
--- 3403,3412 ----
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))
{
***************
*** 3364,3373 **** 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
--- 3430,3439 ----
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
***************
*** 3376,3382 **** l3:
*/
MultiXactIdSetOldestMember();
! new_infomask |= HEAP_XMAX_SHARED_LOCK;
/*
* Check to see if we need a MultiXactId because there are multiple
--- 3442,3449 ----
*/
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
***************
*** 3476,3482 **** 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;
--- 3543,3549 ----
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;
***************
*** 4795,4802 **** 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);
--- 4862,4871 ----
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);
***************
*** 4999,5006 **** 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)
--- 5068,5077 ----
{
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
***************
*** 2863,2869 **** reindex_relation(Oid relid, bool toast_too, int flags)
/* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */
if (is_pg_class)
! (void) RelationGetIndexAttrBitmap(rel);
PG_TRY();
{
--- 2863,2869 ----
/* 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
***************
*** 1953,1959 **** _copyRowMarkClause(RowMarkClause *from)
RowMarkClause *newnode = makeNode(RowMarkClause);
COPY_SCALAR_FIELD(rti);
! COPY_SCALAR_FIELD(forUpdate);
COPY_SCALAR_FIELD(noWait);
COPY_SCALAR_FIELD(pushedDown);
--- 1953,1959 ----
RowMarkClause *newnode = makeNode(RowMarkClause);
COPY_SCALAR_FIELD(rti);
! COPY_SCALAR_FIELD(strength);
COPY_SCALAR_FIELD(noWait);
COPY_SCALAR_FIELD(pushedDown);
***************
*** 2310,2316 **** _copyLockingClause(LockingClause *from)
LockingClause *newnode = makeNode(LockingClause);
COPY_NODE_FIELD(lockedRels);
! COPY_SCALAR_FIELD(forUpdate);
COPY_SCALAR_FIELD(noWait);
return newnode;
--- 2310,2316 ----
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
***************
*** 2266,2272 **** static bool
_equalLockingClause(LockingClause *a, LockingClause *b)
{
COMPARE_NODE_FIELD(lockedRels);
! COMPARE_SCALAR_FIELD(forUpdate);
COMPARE_SCALAR_FIELD(noWait);
return true;
--- 2266,2272 ----
_equalLockingClause(LockingClause *a, LockingClause *b)
{
COMPARE_NODE_FIELD(lockedRels);
! COMPARE_SCALAR_FIELD(strength);
COMPARE_SCALAR_FIELD(noWait);
return true;
***************
*** 2335,2341 **** static bool
_equalRowMarkClause(RowMarkClause *a, RowMarkClause *b)
{
COMPARE_SCALAR_FIELD(rti);
! COMPARE_SCALAR_FIELD(forUpdate);
COMPARE_SCALAR_FIELD(noWait);
COMPARE_SCALAR_FIELD(pushedDown);
--- 2335,2341 ----
_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
***************
*** 2005,2011 **** _outLockingClause(StringInfo str, LockingClause *node)
WRITE_NODE_TYPE("LOCKINGCLAUSE");
WRITE_NODE_FIELD(lockedRels);
! WRITE_BOOL_FIELD(forUpdate);
WRITE_BOOL_FIELD(noWait);
}
--- 2005,2011 ----
WRITE_NODE_TYPE("LOCKINGCLAUSE");
WRITE_NODE_FIELD(lockedRels);
! WRITE_ENUM_FIELD(strength, LockClauseStrength);
WRITE_BOOL_FIELD(noWait);
}
***************
*** 2181,2187 **** _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);
}
--- 2181,2187 ----
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
***************
*** 1830,1836 **** 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.
--- 1830,1836 ----
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.
***************
*** 1840,1846 **** 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)
--- 1840,1846 ----
else
{
/*
! * We only need rowmarks for UPDATE, DELETE, or FOR UPDATE/SHARE/KEY LOCK.
*/
if (parse->commandType != CMD_UPDATE &&
parse->commandType != CMD_DELETE)
***************
*** 1850,1856 **** 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)
--- 1850,1856 ----
/*
* 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)
***************
*** 1887,1896 **** preprocess_rowmarks(PlannerInfo *root)
newrc = makeNode(PlanRowMark);
newrc->rti = newrc->prti = rc->rti;
newrc->rowmarkId = ++(root->glob->lastRowMarkId);
! if (rc->forUpdate)
! newrc->markType = ROW_MARK_EXCLUSIVE;
! else
! newrc->markType = ROW_MARK_SHARE;
newrc->noWait = rc->noWait;
newrc->isParent = false;
--- 1887,1904 ----
newrc = makeNode(PlanRowMark);
newrc->rti = newrc->prti = rc->rti;
newrc->rowmarkId = ++(root->glob->lastRowMarkId);
! 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
***************
*** 2161,2167 **** 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)
--- 2161,2167 ----
/* 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)
***************
*** 2177,2188 **** 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
--- 2177,2188 ----
{
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
***************
*** 2226,2238 **** 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,
--- 2226,2238 ----
{
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,
***************
*** 2291,2297 **** transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
*/
void
applyLockingClause(Query *qry, Index rtindex,
! bool forUpdate, bool noWait, bool pushedDown)
{
RowMarkClause *rc;
--- 2291,2297 ----
*/
void
applyLockingClause(Query *qry, Index rtindex,
! LockClauseStrength strength, bool noWait, bool pushedDown)
{
RowMarkClause *rc;
***************
*** 2303,2312 **** 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.
--- 2303,2312 ----
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.
***************
*** 2315,2321 **** 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;
--- 2315,2321 ----
*
* And of course pushedDown becomes false if any clause is explicit.
*/
! rc->strength = Max(rc->strength, strength);
rc->noWait |= noWait;
rc->pushedDown &= pushedDown;
return;
***************
*** 2324,2330 **** 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);
--- 2324,2330 ----
/* 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
***************
*** 8542,8548 **** for_locking_item:
{
LockingClause *n = makeNode(LockingClause);
n->lockedRels = $3;
! n->forUpdate = TRUE;
n->noWait = $4;
$$ = (Node *) n;
}
--- 8542,8548 ----
{
LockingClause *n = makeNode(LockingClause);
n->lockedRels = $3;
! n->strength = LCS_FORUPDATE;
n->noWait = $4;
$$ = (Node *) n;
}
***************
*** 8550,8559 **** for_locking_item:
{
LockingClause *n = makeNode(LockingClause);
n->lockedRels = $3;
! n->forUpdate = FALSE;
n->noWait = $4;
$$ = (Node *) n;
}
;
locked_rels_list:
--- 8550,8567 ----
{
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,
***************
*** 1354,1361 **** 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
--- 1354,1361 ----
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
***************
*** 1363,1375 **** 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.
--- 1363,1375 ----
*/
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.
***************
*** 1381,1387 **** ApplyRetrieveRule(Query *parsetree,
*/
static void
markQueryForLocking(Query *qry, Node *jtnode,
! bool forUpdate, bool noWait, bool pushedDown)
{
if (jtnode == NULL)
return;
--- 1381,1387 ----
*/
static void
markQueryForLocking(Query *qry, Node *jtnode,
! LockClauseStrength strength, bool noWait, bool pushedDown)
{
if (jtnode == NULL)
return;
***************
*** 1392,1406 **** 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 */
}
--- 1392,1406 ----
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 */
}
***************
*** 1410,1423 **** 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",
--- 1410,1423 ----
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
***************
*** 2205,2214 **** 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";
--- 2205,2225 ----
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
***************
*** 2837,2848 **** 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)
--- 2837,2858 ----
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
***************
*** 3608,3613 **** RelationGetIndexPredicate(Relation relation)
--- 3608,3615 ----
* 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.
*
***************
*** 3615,3630 **** 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)
--- 3617,3633 ----
* 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)
***************
*** 3643,3648 **** RelationGetIndexAttrBitmap(Relation relation)
--- 3646,3652 ----
* For each index, add referenced attributes to indexattrs.
*/
indexattrs = NULL;
+ uindexattrs = NULL;
foreach(l, indexoidlist)
{
Oid indexOid = lfirst_oid(l);
***************
*** 3661,3675 **** RelationGetIndexAttrBitmap(Relation relation)
--- 3665,3688 ----
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);
}
***************
*** 3679,3688 **** 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;
}
/*
--- 3692,3702 ----
/* 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
***************
*** 554,571 **** 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;
--- 554,579 ----
} 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;
***************
*** 839,856 **** 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;
--- 847,864 ----
* 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
***************
*** 706,712 **** 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
--- 706,712 ----
* 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
***************
*** 718,736 **** 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.
--- 718,737 ----
{
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,