mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Fix data loss at inplace update after heap_update().
As previously-added tests demonstrated, heap_inplace_update() could instead update an unrelated tuple of the same catalog. It could lose the update. Losing relhasindex=t was a source of index corruption. Inplace-updating commands like VACUUM will now wait for heap_update() commands like GRANT TABLE and GRANT DATABASE. That isn't ideal, but a long-running GRANT already hurts VACUUM progress more just by keeping an XID running. The VACUUM will behave like a DELETE or UPDATE waiting for the uncommitted change. For implementation details, start at the systable_inplace_update_begin() header comment and README.tuplock. Back-patch to v12 (all supported versions). In back branches, retain a deprecated heap_inplace_update(), for extensions. Reported by Smolkin Grigory. Reviewed by Nitin Motiani, (in earlier versions) Heikki Linnakangas, and (in earlier versions) Alexander Lakhin. Discussion: https://postgr.es/m/CAMp+ueZQz3yDk7qg42hk6-9gxniYbp-=bG2mgqecErqR5gGGOA@mail.gmail.com
This commit is contained in:
		@@ -153,3 +153,14 @@ The following infomask bits are applicable:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
 | 
					We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
 | 
				
			||||||
is set.
 | 
					is set.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Reading inplace-updated columns
 | 
				
			||||||
 | 
					-------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Inplace updates create an exception to the rule that tuple data won't change
 | 
				
			||||||
 | 
					under a reader holding a pin.  A reader of a heap_fetch() result tuple may
 | 
				
			||||||
 | 
					witness a torn read.  Current inplace-updated fields are aligned and are no
 | 
				
			||||||
 | 
					wider than four bytes, and current readers don't need consistency across
 | 
				
			||||||
 | 
					fields.  Hence, they get by with just fetching each field once.  XXX such a
 | 
				
			||||||
 | 
					caller may also read a value that has not reached WAL; see
 | 
				
			||||||
 | 
					systable_inplace_update_finish().
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5880,23 +5880,245 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * heap_inplace_update - update a tuple "in place" (ie, overwrite it)
 | 
					 * heap_inplace_lock - protect inplace update from concurrent heap_update()
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Overwriting violates both MVCC and transactional safety, so the uses
 | 
					 * Evaluate whether the tuple's state is compatible with a no-key update.
 | 
				
			||||||
 * of this function in Postgres are extremely limited.  Nonetheless we
 | 
					 * Current transaction rowmarks are fine, as is KEY SHARE from any
 | 
				
			||||||
 * find some places to use it.
 | 
					 * transaction.  If compatible, return true with the buffer exclusive-locked,
 | 
				
			||||||
 | 
					 * and the caller must release that by calling
 | 
				
			||||||
 | 
					 * heap_inplace_update_and_unlock(), calling heap_inplace_unlock(), or raising
 | 
				
			||||||
 | 
					 * an error.  Otherwise, return false after blocking transactions, if any,
 | 
				
			||||||
 | 
					 * have ended.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * The tuple cannot change size, and therefore it's reasonable to assume
 | 
					 * Since this is intended for system catalogs and SERIALIZABLE doesn't cover
 | 
				
			||||||
 * that its null bitmap (if any) doesn't change either.  So we just
 | 
					 * DDL, this doesn't guarantee any particular predicate locking.
 | 
				
			||||||
 * overwrite the data portion of the tuple without touching the null
 | 
					 | 
				
			||||||
 * bitmap or any of the header fields.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * tuple is an in-memory tuple structure containing the data to be written
 | 
					 * One could modify this to return true for tuples with delete in progress,
 | 
				
			||||||
 * over the target tuple.  Also, tuple->t_self identifies the target tuple.
 | 
					 * All inplace updaters take a lock that conflicts with DROP.  If explicit
 | 
				
			||||||
 | 
					 * "DELETE FROM pg_class" is in progress, we'll wait for it like we would an
 | 
				
			||||||
 | 
					 * update.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Note that the tuple updated here had better not come directly from the
 | 
					 * Readers of inplace-updated fields expect changes to those fields are
 | 
				
			||||||
 * syscache if the relation has a toast relation as this tuple could
 | 
					 * durable.  For example, vac_truncate_clog() reads datfrozenxid from
 | 
				
			||||||
 * include toast values that have been expanded, causing a failure here.
 | 
					 * pg_database tuples via catalog snapshots.  A future snapshot must not
 | 
				
			||||||
 | 
					 * return a lower datfrozenxid for the same database OID (lower in the
 | 
				
			||||||
 | 
					 * FullTransactionIdPrecedes() sense).  We achieve that since no update of a
 | 
				
			||||||
 | 
					 * tuple can start while we hold a lock on its buffer.  In cases like
 | 
				
			||||||
 | 
					 * BEGIN;GRANT;CREATE INDEX;COMMIT we're inplace-updating a tuple visible only
 | 
				
			||||||
 | 
					 * to this transaction.  ROLLBACK then is one case where it's okay to lose
 | 
				
			||||||
 | 
					 * inplace updates.  (Restoring relhasindex=false on ROLLBACK is fine, since
 | 
				
			||||||
 | 
					 * any concurrent CREATE INDEX would have blocked, then inplace-updated the
 | 
				
			||||||
 | 
					 * committed tuple.)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * In principle, we could avoid waiting by overwriting every tuple in the
 | 
				
			||||||
 | 
					 * updated tuple chain.  Reader expectations permit updating a tuple only if
 | 
				
			||||||
 | 
					 * it's aborted, is the tail of the chain, or we already updated the tuple
 | 
				
			||||||
 | 
					 * referenced in its t_ctid.  Hence, we would need to overwrite the tuples in
 | 
				
			||||||
 | 
					 * order from tail to head.  That would imply either (a) mutating all tuples
 | 
				
			||||||
 | 
					 * in one critical section or (b) accepting a chance of partial completion.
 | 
				
			||||||
 | 
					 * Partial completion of a relfrozenxid update would have the weird
 | 
				
			||||||
 | 
					 * consequence that the table's next VACUUM could see the table's relfrozenxid
 | 
				
			||||||
 | 
					 * move forward between vacuum_get_cutoffs() and finishing.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					bool
 | 
				
			||||||
 | 
					heap_inplace_lock(Relation relation,
 | 
				
			||||||
 | 
									  HeapTuple oldtup_ptr, Buffer buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						HeapTupleData oldtup = *oldtup_ptr; /* minimize diff vs. heap_update() */
 | 
				
			||||||
 | 
						TM_Result	result;
 | 
				
			||||||
 | 
						bool		ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Assert(BufferIsValid(buffer));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*----------
 | 
				
			||||||
 | 
						 * Interpret HeapTupleSatisfiesUpdate() like heap_update() does, except:
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * - wait unconditionally
 | 
				
			||||||
 | 
						 * - no tuple locks
 | 
				
			||||||
 | 
						 * - don't recheck header after wait: simpler to defer to next iteration
 | 
				
			||||||
 | 
						 * - don't try to continue even if the updater aborts: likewise
 | 
				
			||||||
 | 
						 * - no crosscheck
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						result = HeapTupleSatisfiesUpdate(&oldtup, GetCurrentCommandId(false),
 | 
				
			||||||
 | 
														  buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (result == TM_Invisible)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							/* no known way this can happen */
 | 
				
			||||||
 | 
							ereport(ERROR,
 | 
				
			||||||
 | 
									(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 | 
				
			||||||
 | 
									 errmsg_internal("attempted to overwrite invisible tuple")));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else if (result == TM_SelfModified)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * CREATE INDEX might reach this if an expression is silly enough to
 | 
				
			||||||
 | 
							 * call e.g. SELECT ... FROM pg_class FOR SHARE.  C code of other SQL
 | 
				
			||||||
 | 
							 * statements might get here after a heap_update() of the same row, in
 | 
				
			||||||
 | 
							 * the absence of an intervening CommandCounterIncrement().
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							ereport(ERROR,
 | 
				
			||||||
 | 
									(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 | 
				
			||||||
 | 
									 errmsg("tuple to be updated was already modified by an operation triggered by the current command")));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else if (result == TM_BeingModified)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							TransactionId xwait;
 | 
				
			||||||
 | 
							uint16		infomask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							xwait = HeapTupleHeaderGetRawXmax(oldtup.t_data);
 | 
				
			||||||
 | 
							infomask = oldtup.t_data->t_infomask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (infomask & HEAP_XMAX_IS_MULTI)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								LockTupleMode lockmode = LockTupleNoKeyExclusive;
 | 
				
			||||||
 | 
								MultiXactStatus mxact_status = MultiXactStatusNoKeyUpdate;
 | 
				
			||||||
 | 
								int			remain;
 | 
				
			||||||
 | 
								bool		current_is_member;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
 | 
				
			||||||
 | 
															lockmode, ¤t_is_member))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 | 
				
			||||||
 | 
									ret = false;
 | 
				
			||||||
 | 
									MultiXactIdWait((MultiXactId) xwait, mxact_status, infomask,
 | 
				
			||||||
 | 
													relation, &oldtup.t_self, XLTW_Update,
 | 
				
			||||||
 | 
													&remain);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									ret = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else if (TransactionIdIsCurrentTransactionId(xwait))
 | 
				
			||||||
 | 
								ret = true;
 | 
				
			||||||
 | 
							else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
 | 
				
			||||||
 | 
								ret = true;
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 | 
				
			||||||
 | 
								ret = false;
 | 
				
			||||||
 | 
								XactLockTableWait(xwait, relation, &oldtup.t_self,
 | 
				
			||||||
 | 
												  XLTW_Update);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							ret = (result == TM_Ok);
 | 
				
			||||||
 | 
							if (!ret)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * GetCatalogSnapshot() relies on invalidation messages to know when to
 | 
				
			||||||
 | 
						 * take a new snapshot.  COMMIT of xwait is responsible for sending the
 | 
				
			||||||
 | 
						 * invalidation.  We're not acquiring heavyweight locks sufficient to
 | 
				
			||||||
 | 
						 * block if not yet sent, so we must take a new snapshot to ensure a later
 | 
				
			||||||
 | 
						 * attempt has a fair chance.  While we don't need this if xwait aborted,
 | 
				
			||||||
 | 
						 * don't bother optimizing that.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (!ret)
 | 
				
			||||||
 | 
							InvalidateCatalogSnapshot();
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * heap_inplace_update_and_unlock - core of systable_inplace_update_finish
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The tuple cannot change size, and therefore its header fields and null
 | 
				
			||||||
 | 
					 * bitmap (if any) don't change either.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					heap_inplace_update_and_unlock(Relation relation,
 | 
				
			||||||
 | 
												   HeapTuple oldtup, HeapTuple tuple,
 | 
				
			||||||
 | 
												   Buffer buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						HeapTupleHeader htup = oldtup->t_data;
 | 
				
			||||||
 | 
						uint32		oldlen;
 | 
				
			||||||
 | 
						uint32		newlen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Assert(ItemPointerEquals(&oldtup->t_self, &tuple->t_self));
 | 
				
			||||||
 | 
						oldlen = oldtup->t_len - htup->t_hoff;
 | 
				
			||||||
 | 
						newlen = tuple->t_len - tuple->t_data->t_hoff;
 | 
				
			||||||
 | 
						if (oldlen != newlen || htup->t_hoff != tuple->t_data->t_hoff)
 | 
				
			||||||
 | 
							elog(ERROR, "wrong tuple length");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* NO EREPORT(ERROR) from here till changes are logged */
 | 
				
			||||||
 | 
						START_CRIT_SECTION();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						memcpy((char *) htup + htup->t_hoff,
 | 
				
			||||||
 | 
							   (char *) tuple->t_data + tuple->t_data->t_hoff,
 | 
				
			||||||
 | 
							   newlen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*----------
 | 
				
			||||||
 | 
						 * XXX A crash here can allow datfrozenxid() to get ahead of relfrozenxid:
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * ["D" is a VACUUM (ONLY_DATABASE_STATS)]
 | 
				
			||||||
 | 
						 * ["R" is a VACUUM tbl]
 | 
				
			||||||
 | 
						 * D: vac_update_datfrozenid() -> systable_beginscan(pg_class)
 | 
				
			||||||
 | 
						 * D: systable_getnext() returns pg_class tuple of tbl
 | 
				
			||||||
 | 
						 * R: memcpy() into pg_class tuple of tbl
 | 
				
			||||||
 | 
						 * D: raise pg_database.datfrozenxid, XLogInsert(), finish
 | 
				
			||||||
 | 
						 * [crash]
 | 
				
			||||||
 | 
						 * [recovery restores datfrozenxid w/o relfrozenxid]
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						MarkBufferDirty(buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* XLOG stuff */
 | 
				
			||||||
 | 
						if (RelationNeedsWAL(relation))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							xl_heap_inplace xlrec;
 | 
				
			||||||
 | 
							XLogRecPtr	recptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							XLogBeginInsert();
 | 
				
			||||||
 | 
							XLogRegisterData((char *) &xlrec, SizeOfHeapInplace);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
 | 
				
			||||||
 | 
							XLogRegisterBufData(0, (char *) htup + htup->t_hoff, newlen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/* inplace updates aren't decoded atm, don't log the origin */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_INPLACE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PageSetLSN(BufferGetPage(buffer), recptr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						END_CRIT_SECTION();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						heap_inplace_unlock(relation, oldtup, buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Send out shared cache inval if necessary.  Note that because we only
 | 
				
			||||||
 | 
						 * pass the new version of the tuple, this mustn't be used for any
 | 
				
			||||||
 | 
						 * operations that could change catcache lookup keys.  But we aren't
 | 
				
			||||||
 | 
						 * bothering with index updates either, so that's true a fortiori.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * XXX ROLLBACK discards the invalidation.  See test inplace-inval.spec.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (!IsBootstrapProcessingMode())
 | 
				
			||||||
 | 
							CacheInvalidateHeapTuple(relation, tuple, NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * heap_inplace_unlock - reverse of heap_inplace_lock
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					heap_inplace_unlock(Relation relation,
 | 
				
			||||||
 | 
										HeapTuple oldtup, Buffer buffer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * heap_inplace_update - deprecated
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This exists only to keep modules working in back branches.  Affected
 | 
				
			||||||
 | 
					 * modules should migrate to systable_inplace_update_begin().
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
heap_inplace_update(Relation relation, HeapTuple tuple)
 | 
					heap_inplace_update(Relation relation, HeapTuple tuple)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -749,3 +749,139 @@ systable_endscan_ordered(SysScanDesc sysscan)
 | 
				
			|||||||
		UnregisterSnapshot(sysscan->snapshot);
 | 
							UnregisterSnapshot(sysscan->snapshot);
 | 
				
			||||||
	pfree(sysscan);
 | 
						pfree(sysscan);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * systable_inplace_update_begin --- update a row "in place" (overwrite it)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Overwriting violates both MVCC and transactional safety, so the uses of
 | 
				
			||||||
 | 
					 * this function in Postgres are extremely limited.  Nonetheless we find some
 | 
				
			||||||
 | 
					 * places to use it.  Standard flow:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ... [any slow preparation not requiring oldtup] ...
 | 
				
			||||||
 | 
					 * systable_inplace_update_begin([...], &tup, &inplace_state);
 | 
				
			||||||
 | 
					 * if (!HeapTupleIsValid(tup))
 | 
				
			||||||
 | 
					 *	elog(ERROR, [...]);
 | 
				
			||||||
 | 
					 * ... [buffer is exclusive-locked; mutate "tup"] ...
 | 
				
			||||||
 | 
					 * if (dirty)
 | 
				
			||||||
 | 
					 *	systable_inplace_update_finish(inplace_state, tup);
 | 
				
			||||||
 | 
					 * else
 | 
				
			||||||
 | 
					 *	systable_inplace_update_cancel(inplace_state);
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The first several params duplicate the systable_beginscan() param list.
 | 
				
			||||||
 | 
					 * "oldtupcopy" is an output parameter, assigned NULL if the key ceases to
 | 
				
			||||||
 | 
					 * find a live tuple.  (In PROC_IN_VACUUM, that is a low-probability transient
 | 
				
			||||||
 | 
					 * condition.)  If "oldtupcopy" gets non-NULL, you must pass output parameter
 | 
				
			||||||
 | 
					 * "state" to systable_inplace_update_finish() or
 | 
				
			||||||
 | 
					 * systable_inplace_update_cancel().
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					systable_inplace_update_begin(Relation relation,
 | 
				
			||||||
 | 
												  Oid indexId,
 | 
				
			||||||
 | 
												  bool indexOK,
 | 
				
			||||||
 | 
												  Snapshot snapshot,
 | 
				
			||||||
 | 
												  int nkeys, const ScanKeyData *key,
 | 
				
			||||||
 | 
												  HeapTuple *oldtupcopy,
 | 
				
			||||||
 | 
												  void **state)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						ScanKey		mutable_key = palloc(sizeof(ScanKeyData) * nkeys);
 | 
				
			||||||
 | 
						int			retries = 0;
 | 
				
			||||||
 | 
						SysScanDesc scan;
 | 
				
			||||||
 | 
						HeapTuple	oldtup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * For now, we don't allow parallel updates.  Unlike a regular update,
 | 
				
			||||||
 | 
						 * this should never create a combo CID, so it might be possible to relax
 | 
				
			||||||
 | 
						 * this restriction, but not without more thought and testing.  It's not
 | 
				
			||||||
 | 
						 * clear that it would be useful, anyway.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (IsInParallelMode())
 | 
				
			||||||
 | 
							ereport(ERROR,
 | 
				
			||||||
 | 
									(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
 | 
				
			||||||
 | 
									 errmsg("cannot update tuples during a parallel operation")));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Accept a snapshot argument, for symmetry, but this function advances
 | 
				
			||||||
 | 
						 * its snapshot as needed to reach the tail of the updated tuple chain.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						Assert(snapshot == NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Assert(IsInplaceUpdateRelation(relation) || !IsSystemRelation(relation));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Loop for an exclusive-locked buffer of a non-updated tuple. */
 | 
				
			||||||
 | 
						for (;;)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							TupleTableSlot *slot;
 | 
				
			||||||
 | 
							BufferHeapTupleTableSlot *bslot;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK_FOR_INTERRUPTS();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * Processes issuing heap_update (e.g. GRANT) at maximum speed could
 | 
				
			||||||
 | 
							 * drive us to this error.  A hostile table owner has stronger ways to
 | 
				
			||||||
 | 
							 * damage their own table, so that's minor.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							if (retries++ > 10000)
 | 
				
			||||||
 | 
								elog(ERROR, "giving up after too many tries to overwrite row");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							memcpy(mutable_key, key, sizeof(ScanKeyData) * nkeys);
 | 
				
			||||||
 | 
							scan = systable_beginscan(relation, indexId, indexOK, snapshot,
 | 
				
			||||||
 | 
													  nkeys, mutable_key);
 | 
				
			||||||
 | 
							oldtup = systable_getnext(scan);
 | 
				
			||||||
 | 
							if (!HeapTupleIsValid(oldtup))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								systable_endscan(scan);
 | 
				
			||||||
 | 
								*oldtupcopy = NULL;
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							slot = scan->slot;
 | 
				
			||||||
 | 
							Assert(TTS_IS_BUFFERTUPLE(slot));
 | 
				
			||||||
 | 
							bslot = (BufferHeapTupleTableSlot *) slot;
 | 
				
			||||||
 | 
							if (heap_inplace_lock(scan->heap_rel,
 | 
				
			||||||
 | 
												  bslot->base.tuple, bslot->buffer))
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							systable_endscan(scan);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						*oldtupcopy = heap_copytuple(oldtup);
 | 
				
			||||||
 | 
						*state = scan;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * systable_inplace_update_finish --- second phase of inplace update
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The tuple cannot change size, and therefore its header fields and null
 | 
				
			||||||
 | 
					 * bitmap (if any) don't change either.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					systable_inplace_update_finish(void *state, HeapTuple tuple)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						SysScanDesc scan = (SysScanDesc) state;
 | 
				
			||||||
 | 
						Relation	relation = scan->heap_rel;
 | 
				
			||||||
 | 
						TupleTableSlot *slot = scan->slot;
 | 
				
			||||||
 | 
						BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
 | 
				
			||||||
 | 
						HeapTuple	oldtup = bslot->base.tuple;
 | 
				
			||||||
 | 
						Buffer		buffer = bslot->buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						heap_inplace_update_and_unlock(relation, oldtup, tuple, buffer);
 | 
				
			||||||
 | 
						systable_endscan(scan);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * systable_inplace_update_cancel --- abandon inplace update
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This is an alternative to making a no-op update.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					systable_inplace_update_cancel(void *state)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						SysScanDesc scan = (SysScanDesc) state;
 | 
				
			||||||
 | 
						Relation	relation = scan->heap_rel;
 | 
				
			||||||
 | 
						TupleTableSlot *slot = scan->slot;
 | 
				
			||||||
 | 
						BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
 | 
				
			||||||
 | 
						HeapTuple	oldtup = bslot->base.tuple;
 | 
				
			||||||
 | 
						Buffer		buffer = bslot->buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						heap_inplace_unlock(relation, oldtup, buffer);
 | 
				
			||||||
 | 
						systable_endscan(scan);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2814,7 +2814,9 @@ index_update_stats(Relation rel,
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	Oid			relid = RelationGetRelid(rel);
 | 
						Oid			relid = RelationGetRelid(rel);
 | 
				
			||||||
	Relation	pg_class;
 | 
						Relation	pg_class;
 | 
				
			||||||
 | 
						ScanKeyData key[1];
 | 
				
			||||||
	HeapTuple	tuple;
 | 
						HeapTuple	tuple;
 | 
				
			||||||
 | 
						void	   *state;
 | 
				
			||||||
	Form_pg_class rd_rel;
 | 
						Form_pg_class rd_rel;
 | 
				
			||||||
	bool		dirty;
 | 
						bool		dirty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2848,33 +2850,12 @@ index_update_stats(Relation rel,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	pg_class = table_open(RelationRelationId, RowExclusiveLock);
 | 
						pg_class = table_open(RelationRelationId, RowExclusiveLock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
					 | 
				
			||||||
	 * Make a copy of the tuple to update.  Normally we use the syscache, but
 | 
					 | 
				
			||||||
	 * we can't rely on that during bootstrap or while reindexing pg_class
 | 
					 | 
				
			||||||
	 * itself.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	if (IsBootstrapProcessingMode() ||
 | 
					 | 
				
			||||||
		ReindexIsProcessingHeap(RelationRelationId))
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		/* don't assume syscache will work */
 | 
					 | 
				
			||||||
		TableScanDesc pg_class_scan;
 | 
					 | 
				
			||||||
		ScanKeyData key[1];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ScanKeyInit(&key[0],
 | 
						ScanKeyInit(&key[0],
 | 
				
			||||||
				Anum_pg_class_oid,
 | 
									Anum_pg_class_oid,
 | 
				
			||||||
				BTEqualStrategyNumber, F_OIDEQ,
 | 
									BTEqualStrategyNumber, F_OIDEQ,
 | 
				
			||||||
				ObjectIdGetDatum(relid));
 | 
									ObjectIdGetDatum(relid));
 | 
				
			||||||
 | 
						systable_inplace_update_begin(pg_class, ClassOidIndexId, true, NULL,
 | 
				
			||||||
		pg_class_scan = table_beginscan_catalog(pg_class, 1, key);
 | 
													  1, key, &tuple, &state);
 | 
				
			||||||
		tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
 | 
					 | 
				
			||||||
		tuple = heap_copytuple(tuple);
 | 
					 | 
				
			||||||
		table_endscan(pg_class_scan);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	else
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		/* normal case, use syscache */
 | 
					 | 
				
			||||||
		tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!HeapTupleIsValid(tuple))
 | 
						if (!HeapTupleIsValid(tuple))
 | 
				
			||||||
		elog(ERROR, "could not find tuple for relation %u", relid);
 | 
							elog(ERROR, "could not find tuple for relation %u", relid);
 | 
				
			||||||
@@ -2933,11 +2914,12 @@ index_update_stats(Relation rel,
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	if (dirty)
 | 
						if (dirty)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		heap_inplace_update(pg_class, tuple);
 | 
							systable_inplace_update_finish(state, tuple);
 | 
				
			||||||
		/* the above sends a cache inval message */
 | 
							/* the above sends a cache inval message */
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							systable_inplace_update_cancel(state);
 | 
				
			||||||
		/* no need to change tuple, but force relcache inval anyway */
 | 
							/* no need to change tuple, but force relcache inval anyway */
 | 
				
			||||||
		CacheInvalidateRelcacheByTuple(tuple);
 | 
							CacheInvalidateRelcacheByTuple(tuple);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
#include "postgres.h"
 | 
					#include "postgres.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "access/genam.h"
 | 
				
			||||||
#include "access/heapam.h"
 | 
					#include "access/heapam.h"
 | 
				
			||||||
#include "access/toast_compression.h"
 | 
					#include "access/toast_compression.h"
 | 
				
			||||||
#include "access/xact.h"
 | 
					#include "access/xact.h"
 | 
				
			||||||
@@ -32,6 +33,7 @@
 | 
				
			|||||||
#include "nodes/makefuncs.h"
 | 
					#include "nodes/makefuncs.h"
 | 
				
			||||||
#include "storage/lock.h"
 | 
					#include "storage/lock.h"
 | 
				
			||||||
#include "utils/builtins.h"
 | 
					#include "utils/builtins.h"
 | 
				
			||||||
 | 
					#include "utils/fmgroids.h"
 | 
				
			||||||
#include "utils/rel.h"
 | 
					#include "utils/rel.h"
 | 
				
			||||||
#include "utils/syscache.h"
 | 
					#include "utils/syscache.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -337,21 +339,36 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	class_rel = table_open(RelationRelationId, RowExclusiveLock);
 | 
						class_rel = table_open(RelationRelationId, RowExclusiveLock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!IsBootstrapProcessingMode())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							/* normal case, use a transactional update */
 | 
				
			||||||
		reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
 | 
							reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
 | 
				
			||||||
		if (!HeapTupleIsValid(reltup))
 | 
							if (!HeapTupleIsValid(reltup))
 | 
				
			||||||
			elog(ERROR, "cache lookup failed for relation %u", relOid);
 | 
								elog(ERROR, "cache lookup failed for relation %u", relOid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
 | 
							((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!IsBootstrapProcessingMode())
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		/* normal case, use a transactional update */
 | 
					 | 
				
			||||||
		CatalogTupleUpdate(class_rel, &reltup->t_self, reltup);
 | 
							CatalogTupleUpdate(class_rel, &reltup->t_self, reltup);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		/* While bootstrapping, we cannot UPDATE, so overwrite in-place */
 | 
							/* While bootstrapping, we cannot UPDATE, so overwrite in-place */
 | 
				
			||||||
		heap_inplace_update(class_rel, reltup);
 | 
					
 | 
				
			||||||
 | 
							ScanKeyData key[1];
 | 
				
			||||||
 | 
							void	   *state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ScanKeyInit(&key[0],
 | 
				
			||||||
 | 
										Anum_pg_class_oid,
 | 
				
			||||||
 | 
										BTEqualStrategyNumber, F_OIDEQ,
 | 
				
			||||||
 | 
										ObjectIdGetDatum(relOid));
 | 
				
			||||||
 | 
							systable_inplace_update_begin(class_rel, ClassOidIndexId, true,
 | 
				
			||||||
 | 
														  NULL, 1, key, &reltup, &state);
 | 
				
			||||||
 | 
							if (!HeapTupleIsValid(reltup))
 | 
				
			||||||
 | 
								elog(ERROR, "cache lookup failed for relation %u", relOid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							systable_inplace_update_finish(state, reltup);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	heap_freetuple(reltup);
 | 
						heap_freetuple(reltup);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1587,7 +1587,7 @@ dropdb(const char *dbname, bool missing_ok, bool force)
 | 
				
			|||||||
	Relation	pgdbrel;
 | 
						Relation	pgdbrel;
 | 
				
			||||||
	HeapTuple	tup;
 | 
						HeapTuple	tup;
 | 
				
			||||||
	ScanKeyData scankey;
 | 
						ScanKeyData scankey;
 | 
				
			||||||
	SysScanDesc scan;
 | 
						void	   *inplace_state;
 | 
				
			||||||
	Form_pg_database datform;
 | 
						Form_pg_database datform;
 | 
				
			||||||
	int			notherbackends;
 | 
						int			notherbackends;
 | 
				
			||||||
	int			npreparedxacts;
 | 
						int			npreparedxacts;
 | 
				
			||||||
@@ -1725,24 +1725,6 @@ dropdb(const char *dbname, bool missing_ok, bool force)
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	pgstat_drop_database(db_id);
 | 
						pgstat_drop_database(db_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
					 | 
				
			||||||
	 * Get the pg_database tuple to scribble on.  Note that this does not
 | 
					 | 
				
			||||||
	 * directly rely on the syscache to avoid issues with flattened toast
 | 
					 | 
				
			||||||
	 * values for the in-place update.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	ScanKeyInit(&scankey,
 | 
					 | 
				
			||||||
				Anum_pg_database_datname,
 | 
					 | 
				
			||||||
				BTEqualStrategyNumber, F_NAMEEQ,
 | 
					 | 
				
			||||||
				CStringGetDatum(dbname));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	scan = systable_beginscan(pgdbrel, DatabaseNameIndexId, true,
 | 
					 | 
				
			||||||
							  NULL, 1, &scankey);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tup = systable_getnext(scan);
 | 
					 | 
				
			||||||
	if (!HeapTupleIsValid(tup))
 | 
					 | 
				
			||||||
		elog(ERROR, "cache lookup failed for database %u", db_id);
 | 
					 | 
				
			||||||
	datform = (Form_pg_database) GETSTRUCT(tup);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
	 * Except for the deletion of the catalog row, subsequent actions are not
 | 
						 * Except for the deletion of the catalog row, subsequent actions are not
 | 
				
			||||||
	 * transactional (consider DropDatabaseBuffers() discarding modified
 | 
						 * transactional (consider DropDatabaseBuffers() discarding modified
 | 
				
			||||||
@@ -1754,8 +1736,17 @@ dropdb(const char *dbname, bool missing_ok, bool force)
 | 
				
			|||||||
	 * modification is durable before performing irreversible filesystem
 | 
						 * modification is durable before performing irreversible filesystem
 | 
				
			||||||
	 * operations.
 | 
						 * operations.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
 | 
						ScanKeyInit(&scankey,
 | 
				
			||||||
 | 
									Anum_pg_database_datname,
 | 
				
			||||||
 | 
									BTEqualStrategyNumber, F_NAMEEQ,
 | 
				
			||||||
 | 
									CStringGetDatum(dbname));
 | 
				
			||||||
 | 
						systable_inplace_update_begin(pgdbrel, DatabaseNameIndexId, true,
 | 
				
			||||||
 | 
													  NULL, 1, &scankey, &tup, &inplace_state);
 | 
				
			||||||
 | 
						if (!HeapTupleIsValid(tup))
 | 
				
			||||||
 | 
							elog(ERROR, "cache lookup failed for database %u", db_id);
 | 
				
			||||||
 | 
						datform = (Form_pg_database) GETSTRUCT(tup);
 | 
				
			||||||
	datform->datconnlimit = DATCONNLIMIT_INVALID_DB;
 | 
						datform->datconnlimit = DATCONNLIMIT_INVALID_DB;
 | 
				
			||||||
	heap_inplace_update(pgdbrel, tup);
 | 
						systable_inplace_update_finish(inplace_state, tup);
 | 
				
			||||||
	XLogFlush(XactLastRecEnd);
 | 
						XLogFlush(XactLastRecEnd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
@@ -1763,8 +1754,7 @@ dropdb(const char *dbname, bool missing_ok, bool force)
 | 
				
			|||||||
	 * the row will be gone, but if we fail, dropdb() can be invoked again.
 | 
						 * the row will be gone, but if we fail, dropdb() can be invoked again.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	CatalogTupleDelete(pgdbrel, &tup->t_self);
 | 
						CatalogTupleDelete(pgdbrel, &tup->t_self);
 | 
				
			||||||
 | 
						heap_freetuple(tup);
 | 
				
			||||||
	systable_endscan(scan);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
	 * Drop db-specific replication slots.
 | 
						 * Drop db-specific replication slots.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1427,7 +1427,9 @@ vac_update_relstats(Relation relation,
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	Oid			relid = RelationGetRelid(relation);
 | 
						Oid			relid = RelationGetRelid(relation);
 | 
				
			||||||
	Relation	rd;
 | 
						Relation	rd;
 | 
				
			||||||
 | 
						ScanKeyData key[1];
 | 
				
			||||||
	HeapTuple	ctup;
 | 
						HeapTuple	ctup;
 | 
				
			||||||
 | 
						void	   *inplace_state;
 | 
				
			||||||
	Form_pg_class pgcform;
 | 
						Form_pg_class pgcform;
 | 
				
			||||||
	bool		dirty,
 | 
						bool		dirty,
 | 
				
			||||||
				futurexid,
 | 
									futurexid,
 | 
				
			||||||
@@ -1438,7 +1440,12 @@ vac_update_relstats(Relation relation,
 | 
				
			|||||||
	rd = table_open(RelationRelationId, RowExclusiveLock);
 | 
						rd = table_open(RelationRelationId, RowExclusiveLock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Fetch a copy of the tuple to scribble on */
 | 
						/* Fetch a copy of the tuple to scribble on */
 | 
				
			||||||
	ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
 | 
						ScanKeyInit(&key[0],
 | 
				
			||||||
 | 
									Anum_pg_class_oid,
 | 
				
			||||||
 | 
									BTEqualStrategyNumber, F_OIDEQ,
 | 
				
			||||||
 | 
									ObjectIdGetDatum(relid));
 | 
				
			||||||
 | 
						systable_inplace_update_begin(rd, ClassOidIndexId, true,
 | 
				
			||||||
 | 
													  NULL, 1, key, &ctup, &inplace_state);
 | 
				
			||||||
	if (!HeapTupleIsValid(ctup))
 | 
						if (!HeapTupleIsValid(ctup))
 | 
				
			||||||
		elog(ERROR, "pg_class entry for relid %u vanished during vacuuming",
 | 
							elog(ERROR, "pg_class entry for relid %u vanished during vacuuming",
 | 
				
			||||||
			 relid);
 | 
								 relid);
 | 
				
			||||||
@@ -1546,7 +1553,9 @@ vac_update_relstats(Relation relation,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/* If anything changed, write out the tuple. */
 | 
						/* If anything changed, write out the tuple. */
 | 
				
			||||||
	if (dirty)
 | 
						if (dirty)
 | 
				
			||||||
		heap_inplace_update(rd, ctup);
 | 
							systable_inplace_update_finish(inplace_state, ctup);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							systable_inplace_update_cancel(inplace_state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	table_close(rd, RowExclusiveLock);
 | 
						table_close(rd, RowExclusiveLock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1598,6 +1607,7 @@ vac_update_datfrozenxid(void)
 | 
				
			|||||||
	bool		bogus = false;
 | 
						bool		bogus = false;
 | 
				
			||||||
	bool		dirty = false;
 | 
						bool		dirty = false;
 | 
				
			||||||
	ScanKeyData key[1];
 | 
						ScanKeyData key[1];
 | 
				
			||||||
 | 
						void	   *inplace_state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
	 * Restrict this task to one backend per database.  This avoids race
 | 
						 * Restrict this task to one backend per database.  This avoids race
 | 
				
			||||||
@@ -1721,20 +1731,18 @@ vac_update_datfrozenxid(void)
 | 
				
			|||||||
	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 | 
						relation = table_open(DatabaseRelationId, RowExclusiveLock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
	 * Get the pg_database tuple to scribble on.  Note that this does not
 | 
						 * Fetch a copy of the tuple to scribble on.  We could check the syscache
 | 
				
			||||||
	 * directly rely on the syscache to avoid issues with flattened toast
 | 
						 * tuple first.  If that concluded !dirty, we'd avoid waiting on
 | 
				
			||||||
	 * values for the in-place update.
 | 
						 * concurrent heap_update() and would avoid exclusive-locking the buffer.
 | 
				
			||||||
 | 
						 * For now, don't optimize that.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	ScanKeyInit(&key[0],
 | 
						ScanKeyInit(&key[0],
 | 
				
			||||||
				Anum_pg_database_oid,
 | 
									Anum_pg_database_oid,
 | 
				
			||||||
				BTEqualStrategyNumber, F_OIDEQ,
 | 
									BTEqualStrategyNumber, F_OIDEQ,
 | 
				
			||||||
				ObjectIdGetDatum(MyDatabaseId));
 | 
									ObjectIdGetDatum(MyDatabaseId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	scan = systable_beginscan(relation, DatabaseOidIndexId, true,
 | 
						systable_inplace_update_begin(relation, DatabaseOidIndexId, true,
 | 
				
			||||||
							  NULL, 1, key);
 | 
													  NULL, 1, key, &tuple, &inplace_state);
 | 
				
			||||||
	tuple = systable_getnext(scan);
 | 
					 | 
				
			||||||
	tuple = heap_copytuple(tuple);
 | 
					 | 
				
			||||||
	systable_endscan(scan);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!HeapTupleIsValid(tuple))
 | 
						if (!HeapTupleIsValid(tuple))
 | 
				
			||||||
		elog(ERROR, "could not find tuple for database %u", MyDatabaseId);
 | 
							elog(ERROR, "could not find tuple for database %u", MyDatabaseId);
 | 
				
			||||||
@@ -1768,7 +1776,9 @@ vac_update_datfrozenxid(void)
 | 
				
			|||||||
		newMinMulti = dbform->datminmxid;
 | 
							newMinMulti = dbform->datminmxid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (dirty)
 | 
						if (dirty)
 | 
				
			||||||
		heap_inplace_update(relation, tuple);
 | 
							systable_inplace_update_finish(inplace_state, tuple);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							systable_inplace_update_cancel(inplace_state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	heap_freetuple(tuple);
 | 
						heap_freetuple(tuple);
 | 
				
			||||||
	table_close(relation, RowExclusiveLock);
 | 
						table_close(relation, RowExclusiveLock);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -230,5 +230,14 @@ extern SysScanDesc systable_beginscan_ordered(Relation heapRelation,
 | 
				
			|||||||
extern HeapTuple systable_getnext_ordered(SysScanDesc sysscan,
 | 
					extern HeapTuple systable_getnext_ordered(SysScanDesc sysscan,
 | 
				
			||||||
										  ScanDirection direction);
 | 
															  ScanDirection direction);
 | 
				
			||||||
extern void systable_endscan_ordered(SysScanDesc sysscan);
 | 
					extern void systable_endscan_ordered(SysScanDesc sysscan);
 | 
				
			||||||
 | 
					extern void systable_inplace_update_begin(Relation relation,
 | 
				
			||||||
 | 
															  Oid indexId,
 | 
				
			||||||
 | 
															  bool indexOK,
 | 
				
			||||||
 | 
															  Snapshot snapshot,
 | 
				
			||||||
 | 
															  int nkeys, const ScanKeyData *key,
 | 
				
			||||||
 | 
															  HeapTuple *oldtupcopy,
 | 
				
			||||||
 | 
															  void **state);
 | 
				
			||||||
 | 
					extern void systable_inplace_update_finish(void *state, HeapTuple tuple);
 | 
				
			||||||
 | 
					extern void systable_inplace_update_cancel(void *state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif							/* GENAM_H */
 | 
					#endif							/* GENAM_H */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -256,6 +256,13 @@ extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 | 
				
			|||||||
								 bool follow_updates,
 | 
													 bool follow_updates,
 | 
				
			||||||
								 Buffer *buffer, struct TM_FailureData *tmfd);
 | 
													 Buffer *buffer, struct TM_FailureData *tmfd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern bool heap_inplace_lock(Relation relation,
 | 
				
			||||||
 | 
												  HeapTuple oldtup_ptr, Buffer buffer);
 | 
				
			||||||
 | 
					extern void heap_inplace_update_and_unlock(Relation relation,
 | 
				
			||||||
 | 
															   HeapTuple oldtup, HeapTuple tuple,
 | 
				
			||||||
 | 
															   Buffer buffer);
 | 
				
			||||||
 | 
					extern void heap_inplace_unlock(Relation relation,
 | 
				
			||||||
 | 
													HeapTuple oldtup, Buffer buffer);
 | 
				
			||||||
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
 | 
					extern void heap_inplace_update(Relation relation, HeapTuple tuple);
 | 
				
			||||||
extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
 | 
					extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
 | 
				
			||||||
									  const struct VacuumCutoffs *cutoffs,
 | 
														  const struct VacuumCutoffs *cutoffs,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,12 +9,13 @@ step b1: BEGIN;
 | 
				
			|||||||
step grant1: 
 | 
					step grant1: 
 | 
				
			||||||
	GRANT TEMP ON DATABASE isolation_regression TO regress_temp_grantee;
 | 
						GRANT TEMP ON DATABASE isolation_regression TO regress_temp_grantee;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
step vac2: VACUUM (FREEZE);
 | 
					step vac2: VACUUM (FREEZE); <waiting ...>
 | 
				
			||||||
step snap3: 
 | 
					step snap3: 
 | 
				
			||||||
	INSERT INTO frozen_witness
 | 
						INSERT INTO frozen_witness
 | 
				
			||||||
	SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog;
 | 
						SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
step c1: COMMIT;
 | 
					step c1: COMMIT;
 | 
				
			||||||
 | 
					step vac2: <... completed>
 | 
				
			||||||
step cmp3: 
 | 
					step cmp3: 
 | 
				
			||||||
	SELECT 'datfrozenxid retreated'
 | 
						SELECT 'datfrozenxid retreated'
 | 
				
			||||||
	FROM pg_database
 | 
						FROM pg_database
 | 
				
			||||||
@@ -22,7 +23,6 @@ step cmp3:
 | 
				
			|||||||
		AND age(datfrozenxid) > (SELECT min(age(x)) FROM frozen_witness);
 | 
							AND age(datfrozenxid) > (SELECT min(age(x)) FROM frozen_witness);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
?column?
 | 
					?column?
 | 
				
			||||||
----------------------
 | 
					--------
 | 
				
			||||||
datfrozenxid retreated
 | 
					(0 rows)
 | 
				
			||||||
(1 row)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,15 +14,16 @@ relhasindex
 | 
				
			|||||||
f          
 | 
					f          
 | 
				
			||||||
(1 row)
 | 
					(1 row)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
 | 
					step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); <waiting ...>
 | 
				
			||||||
step c1: COMMIT;
 | 
					step c1: COMMIT;
 | 
				
			||||||
 | 
					step addk2: <... completed>
 | 
				
			||||||
step read2: 
 | 
					step read2: 
 | 
				
			||||||
	SELECT relhasindex FROM pg_class
 | 
						SELECT relhasindex FROM pg_class
 | 
				
			||||||
	WHERE oid = 'intra_grant_inplace'::regclass;
 | 
						WHERE oid = 'intra_grant_inplace'::regclass;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
relhasindex
 | 
					relhasindex
 | 
				
			||||||
-----------
 | 
					-----------
 | 
				
			||||||
f          
 | 
					t          
 | 
				
			||||||
(1 row)
 | 
					(1 row)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,8 +59,9 @@ relhasindex
 | 
				
			|||||||
f          
 | 
					f          
 | 
				
			||||||
(1 row)
 | 
					(1 row)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
 | 
					step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); <waiting ...>
 | 
				
			||||||
step r3: ROLLBACK;
 | 
					step r3: ROLLBACK;
 | 
				
			||||||
 | 
					step addk2: <... completed>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
starting permutation: b2 sfnku2 addk2 c2
 | 
					starting permutation: b2 sfnku2 addk2 c2
 | 
				
			||||||
step b2: BEGIN;
 | 
					step b2: BEGIN;
 | 
				
			||||||
@@ -98,7 +100,7 @@ f
 | 
				
			|||||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
 | 
					step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
 | 
				
			||||||
step c2: COMMIT;
 | 
					step c2: COMMIT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
starting permutation: b3 sfu3 b1 grant1 read2 addk2 r3 c1 read2
 | 
					starting permutation: b3 sfu3 b1 grant1 read2 as3 addk2 r3 c1 read2
 | 
				
			||||||
step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
 | 
					step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
 | 
				
			||||||
step sfu3: 
 | 
					step sfu3: 
 | 
				
			||||||
	SELECT relhasindex FROM pg_class
 | 
						SELECT relhasindex FROM pg_class
 | 
				
			||||||
@@ -122,17 +124,19 @@ relhasindex
 | 
				
			|||||||
f          
 | 
					f          
 | 
				
			||||||
(1 row)
 | 
					(1 row)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
 | 
					step as3: LOCK TABLE intra_grant_inplace IN ACCESS SHARE MODE;
 | 
				
			||||||
 | 
					step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); <waiting ...>
 | 
				
			||||||
step r3: ROLLBACK;
 | 
					step r3: ROLLBACK;
 | 
				
			||||||
step grant1: <... completed>
 | 
					step grant1: <... completed>
 | 
				
			||||||
step c1: COMMIT;
 | 
					step c1: COMMIT;
 | 
				
			||||||
 | 
					step addk2: <... completed>
 | 
				
			||||||
step read2: 
 | 
					step read2: 
 | 
				
			||||||
	SELECT relhasindex FROM pg_class
 | 
						SELECT relhasindex FROM pg_class
 | 
				
			||||||
	WHERE oid = 'intra_grant_inplace'::regclass;
 | 
						WHERE oid = 'intra_grant_inplace'::regclass;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
relhasindex
 | 
					relhasindex
 | 
				
			||||||
-----------
 | 
					-----------
 | 
				
			||||||
f          
 | 
					t          
 | 
				
			||||||
(1 row)
 | 
					(1 row)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,5 +42,4 @@ step cmp3	{
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# XXX extant bug
 | 
					 | 
				
			||||||
permutation snap3 b1 grant1 vac2(c1) snap3 c1 cmp3
 | 
					permutation snap3 b1 grant1 vac2(c1) snap3 c1 cmp3
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,6 +48,7 @@ step sfu3	{
 | 
				
			|||||||
	SELECT relhasindex FROM pg_class
 | 
						SELECT relhasindex FROM pg_class
 | 
				
			||||||
	WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
 | 
						WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					step as3	{ LOCK TABLE intra_grant_inplace IN ACCESS SHARE MODE; }
 | 
				
			||||||
step r3	{ ROLLBACK; }
 | 
					step r3	{ ROLLBACK; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Additional heap_update()
 | 
					# Additional heap_update()
 | 
				
			||||||
@@ -73,7 +74,7 @@ step keyshr5	{
 | 
				
			|||||||
teardown	{ ROLLBACK; }
 | 
					teardown	{ ROLLBACK; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# XXX extant bugs: permutation comments refer to planned post-bugfix behavior
 | 
					# XXX extant bugs: permutation comments refer to planned future LockTuple()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
permutation
 | 
					permutation
 | 
				
			||||||
	b1
 | 
						b1
 | 
				
			||||||
@@ -117,6 +118,7 @@ permutation
 | 
				
			|||||||
	b1
 | 
						b1
 | 
				
			||||||
	grant1(r3)	# acquire LockTuple(), await sfu3 xmax
 | 
						grant1(r3)	# acquire LockTuple(), await sfu3 xmax
 | 
				
			||||||
	read2
 | 
						read2
 | 
				
			||||||
 | 
						as3			# XXX temporary until patch adds locking to addk2
 | 
				
			||||||
	addk2(c1)	# block in LockTuple() behind grant1
 | 
						addk2(c1)	# block in LockTuple() behind grant1
 | 
				
			||||||
	r3			# unblock grant1; addk2 now awaits grant1 xmax
 | 
						r3			# unblock grant1; addk2 now awaits grant1 xmax
 | 
				
			||||||
	c1
 | 
						c1
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user