*** 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 * ---------- */ 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 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 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 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 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 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 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 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 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 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 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 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,