/*--------------------------------------------------------------------------
 *
 * regress_injection.c
 *		Functions supporting test-specific subject matter.
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *		src/test/modules/injection_points/regress_injection.c
 *
 * -------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/table.h"
#include "fmgr.h"
#include "miscadmin.h"
#include "storage/procarray.h"
#include "utils/xid8.h"

/*
 * removable_cutoff - for syscache-update-pruned.spec
 *
 * Wrapper around GetOldestNonRemovableTransactionId().  In general, this can
 * move backward.  runningcheck=false isolation tests can reasonably prevent
 * that.  For the causes of backward movement, see
 * postgr.es/m/CAEze2Wj%2BV0kTx86xB_YbyaqTr5hnE_igdWAwuhSyjXBYscf5-Q%40mail.gmail.com
 * and the header comment for ComputeXidHorizons().  One can assume this
 * doesn't move backward if one arranges for concurrent activity not to reach
 * AbortTransaction() and not to allocate an XID while connected to another
 * database.  Non-runningcheck tests can control most concurrent activity,
 * except autovacuum and the isolationtester control connection.  Neither
 * allocates XIDs, and AbortTransaction() in those would justify test failure.
 */
PG_FUNCTION_INFO_V1(removable_cutoff);
Datum
removable_cutoff(PG_FUNCTION_ARGS)
{
	Relation	rel = NULL;
	TransactionId xid;
	FullTransactionId next_fxid_before,
				next_fxid;

	/* could take other relkinds callee takes, but we've not yet needed it */
	if (!PG_ARGISNULL(0))
		rel = table_open(PG_GETARG_OID(0), AccessShareLock);

	/*
	 * No lock or snapshot necessarily prevents oldestXid from advancing past
	 * "xid" while this function runs.  That concerns us only in that we must
	 * not ascribe "xid" to the wrong epoch.  (That may never arise in
	 * isolation testing, but let's set a good example.)  As a crude solution,
	 * retry until nextXid doesn't change.
	 */
	next_fxid = ReadNextFullTransactionId();
	do
	{
		CHECK_FOR_INTERRUPTS();
		next_fxid_before = next_fxid;
		xid = GetOldestNonRemovableTransactionId(rel);
		next_fxid = ReadNextFullTransactionId();
	} while (!FullTransactionIdEquals(next_fxid, next_fxid_before));

	if (rel)
		table_close(rel, AccessShareLock);

	PG_RETURN_FULLTRANSACTIONID(FullTransactionIdFromAllowableAt(next_fxid,
																 xid));
}
