From a1204bfad60d3b692b200ef5824b9a7600af075b Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 21 Jul 2020 08:00:04 -0700
Subject: [PATCH v12 1/3] Adding function verify_btreeam and bumping version

For most errors found while verifying a btree index, the new
function verify_btreeam returns one row per error containing the
block number where the error was discovered and an error message
describing the problem.  The pre-existing behavior for functions
bt_index_parent_check and bt_index_check is unchanged.
---
 contrib/amcheck/Makefile                 |   2 +-
 contrib/amcheck/amcheck.control          |   2 +-
 contrib/amcheck/expected/check_btree.out |  35 +
 contrib/amcheck/sql/check_btree.sql      |  13 +
 contrib/amcheck/verify_nbtree.c          | 834 +++++++++++++----------
 src/tools/pgindent/typedefs.list         |   1 +
 6 files changed, 516 insertions(+), 371 deletions(-)

diff --git a/contrib/amcheck/Makefile b/contrib/amcheck/Makefile
index a2b1b1036b..b288c28fa0 100644
--- a/contrib/amcheck/Makefile
+++ b/contrib/amcheck/Makefile
@@ -6,7 +6,7 @@ OBJS = \
 	verify_nbtree.o
 
 EXTENSION = amcheck
-DATA = amcheck--1.1--1.2.sql amcheck--1.0--1.1.sql amcheck--1.0.sql
+DATA = amcheck--1.2--1.3.sql amcheck--1.1--1.2.sql amcheck--1.0--1.1.sql amcheck--1.0.sql
 PGFILEDESC = "amcheck - function for verifying relation integrity"
 
 REGRESS = check check_btree
diff --git a/contrib/amcheck/amcheck.control b/contrib/amcheck/amcheck.control
index c6e310046d..ab50931f75 100644
--- a/contrib/amcheck/amcheck.control
+++ b/contrib/amcheck/amcheck.control
@@ -1,5 +1,5 @@
 # amcheck extension
 comment = 'functions for verifying relation integrity'
-default_version = '1.2'
+default_version = '1.3'
 module_pathname = '$libdir/amcheck'
 relocatable = true
diff --git a/contrib/amcheck/expected/check_btree.out b/contrib/amcheck/expected/check_btree.out
index f82f48d23b..7297abb577 100644
--- a/contrib/amcheck/expected/check_btree.out
+++ b/contrib/amcheck/expected/check_btree.out
@@ -21,6 +21,8 @@ SELECT bt_index_check('bttest_a_idx'::regclass);
 ERROR:  permission denied for function bt_index_check
 SELECT bt_index_parent_check('bttest_a_idx'::regclass);
 ERROR:  permission denied for function bt_index_parent_check
+SELECT * FROM verify_btreeam('bttest_a_idx'::regclass);
+ERROR:  permission denied for function verify_btreeam
 RESET ROLE;
 -- we, intentionally, don't check relation permissions - it's useful
 -- to run this cluster-wide with a restricted account, and as tested
@@ -29,6 +31,7 @@ GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO regress_bttest_role;
 GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass) TO regress_bttest_role;
 GRANT EXECUTE ON FUNCTION bt_index_check(regclass, boolean) TO regress_bttest_role;
 GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass, boolean) TO regress_bttest_role;
+GRANT EXECUTE ON FUNCTION verify_btreeam(regclass, boolean) TO regress_bttest_role;
 SET ROLE regress_bttest_role;
 SELECT bt_index_check('bttest_a_idx');
  bt_index_check 
@@ -42,17 +45,23 @@ SELECT bt_index_parent_check('bttest_a_idx');
  
 (1 row)
 
+SELECT * FROM verify_btreeam('bttest_a_idx');
+ERROR:  permission denied for function verify_btreeam
 RESET ROLE;
 -- verify plain tables are rejected (error)
 SELECT bt_index_check('bttest_a');
 ERROR:  "bttest_a" is not an index
 SELECT bt_index_parent_check('bttest_a');
 ERROR:  "bttest_a" is not an index
+SELECT * FROM verify_btreeam('bttest_a');
+ERROR:  "bttest_a" is not an index
 -- verify non-existing indexes are rejected (error)
 SELECT bt_index_check(17);
 ERROR:  could not open relation with OID 17
 SELECT bt_index_parent_check(17);
 ERROR:  could not open relation with OID 17
+SELECT * FROM verify_btreeam(17);
+ERROR:  could not open relation with OID 17
 -- verify wrong index types are rejected (error)
 BEGIN;
 CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id);
@@ -60,6 +69,12 @@ SELECT bt_index_parent_check('bttest_a_brin_idx');
 ERROR:  only B-Tree indexes are supported as targets for verification
 DETAIL:  Relation "bttest_a_brin_idx" is not a B-Tree index.
 ROLLBACK;
+BEGIN;
+CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id);
+SELECT * FROM verify_btreeam('bttest_a_brin_idx');
+ERROR:  only B-Tree indexes are supported as targets for verification
+DETAIL:  Relation "bttest_a_brin_idx" is not a B-Tree index.
+ROLLBACK;
 -- normal check outside of xact
 SELECT bt_index_check('bttest_a_idx');
  bt_index_check 
@@ -67,6 +82,11 @@ SELECT bt_index_check('bttest_a_idx');
  
 (1 row)
 
+SELECT * FROM verify_btreeam('bttest_a_idx');
+ blkno | msg 
+-------+-----
+(0 rows)
+
 -- more expansive tests
 SELECT bt_index_check('bttest_a_idx', true);
  bt_index_check 
@@ -93,6 +113,11 @@ SELECT bt_index_parent_check('bttest_b_idx');
  
 (1 row)
 
+SELECT * FROM verify_btreeam('bttest_a_idx');
+ blkno | msg 
+-------+-----
+(0 rows)
+
 -- make sure we don't have any leftover locks
 SELECT * FROM pg_locks
 WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx']::regclass[])
@@ -118,6 +143,11 @@ SELECT bt_index_check('bttest_multi_idx');
  
 (1 row)
 
+SELECT * FROM verify_btreeam('bttest_multi_idx');
+ blkno | msg 
+-------+-----
+(0 rows)
+
 -- more expansive tests for index with included columns
 SELECT bt_index_parent_check('bttest_multi_idx', true, true);
  bt_index_parent_check 
@@ -134,6 +164,11 @@ SELECT bt_index_parent_check('bttest_multi_idx', true, true);
  
 (1 row)
 
+SELECT * FROM verify_btreeam('bttest_multi_idx');
+ blkno | msg 
+-------+-----
+(0 rows)
+
 --
 -- Test for multilevel page deletion/downlink present checks, and rootdescend
 -- checks
diff --git a/contrib/amcheck/sql/check_btree.sql b/contrib/amcheck/sql/check_btree.sql
index a1fef644cb..816ca9d033 100644
--- a/contrib/amcheck/sql/check_btree.sql
+++ b/contrib/amcheck/sql/check_btree.sql
@@ -24,6 +24,7 @@ CREATE ROLE regress_bttest_role;
 SET ROLE regress_bttest_role;
 SELECT bt_index_check('bttest_a_idx'::regclass);
 SELECT bt_index_parent_check('bttest_a_idx'::regclass);
+SELECT * FROM verify_btreeam('bttest_a_idx'::regclass);
 RESET ROLE;
 
 -- we, intentionally, don't check relation permissions - it's useful
@@ -33,27 +34,36 @@ GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO regress_bttest_role;
 GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass) TO regress_bttest_role;
 GRANT EXECUTE ON FUNCTION bt_index_check(regclass, boolean) TO regress_bttest_role;
 GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass, boolean) TO regress_bttest_role;
+GRANT EXECUTE ON FUNCTION verify_btreeam(regclass, boolean) TO regress_bttest_role;
 SET ROLE regress_bttest_role;
 SELECT bt_index_check('bttest_a_idx');
 SELECT bt_index_parent_check('bttest_a_idx');
+SELECT * FROM verify_btreeam('bttest_a_idx');
 RESET ROLE;
 
 -- verify plain tables are rejected (error)
 SELECT bt_index_check('bttest_a');
 SELECT bt_index_parent_check('bttest_a');
+SELECT * FROM verify_btreeam('bttest_a');
 
 -- verify non-existing indexes are rejected (error)
 SELECT bt_index_check(17);
 SELECT bt_index_parent_check(17);
+SELECT * FROM verify_btreeam(17);
 
 -- verify wrong index types are rejected (error)
 BEGIN;
 CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id);
 SELECT bt_index_parent_check('bttest_a_brin_idx');
 ROLLBACK;
+BEGIN;
+CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id);
+SELECT * FROM verify_btreeam('bttest_a_brin_idx');
+ROLLBACK;
 
 -- normal check outside of xact
 SELECT bt_index_check('bttest_a_idx');
+SELECT * FROM verify_btreeam('bttest_a_idx');
 -- more expansive tests
 SELECT bt_index_check('bttest_a_idx', true);
 SELECT bt_index_parent_check('bttest_b_idx', true);
@@ -61,6 +71,7 @@ SELECT bt_index_parent_check('bttest_b_idx', true);
 BEGIN;
 SELECT bt_index_check('bttest_a_idx');
 SELECT bt_index_parent_check('bttest_b_idx');
+SELECT * FROM verify_btreeam('bttest_a_idx');
 -- make sure we don't have any leftover locks
 SELECT * FROM pg_locks
 WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx']::regclass[])
@@ -74,6 +85,7 @@ SELECT bt_index_check('bttest_a_idx', true);
 
 -- normal check outside of xact for index with included columns
 SELECT bt_index_check('bttest_multi_idx');
+SELECT * FROM verify_btreeam('bttest_multi_idx');
 -- more expansive tests for index with included columns
 SELECT bt_index_parent_check('bttest_multi_idx', true, true);
 
@@ -81,6 +93,7 @@ SELECT bt_index_parent_check('bttest_multi_idx', true, true);
 TRUNCATE bttest_multi;
 INSERT INTO bttest_multi SELECT i, i%2  FROM generate_series(1, 100000) as i;
 SELECT bt_index_parent_check('bttest_multi_idx', true, true);
+SELECT * FROM verify_btreeam('bttest_multi_idx');
 
 --
 -- Test for multilevel page deletion/downlink present checks, and rootdescend
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index e4d501a85d..ea70fc41a9 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -32,16 +32,22 @@
 #include "catalog/index.h"
 #include "catalog/pg_am.h"
 #include "commands/tablecmds.h"
+#include "funcapi.h"
 #include "lib/bloomfilter.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
+#include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
-
+#include "amcheck.h"
 
 PG_MODULE_MAGIC;
 
+PG_FUNCTION_INFO_V1(bt_index_check);
+PG_FUNCTION_INFO_V1(bt_index_parent_check);
+PG_FUNCTION_INFO_V1(verify_btreeam);
+
 /*
  * A B-Tree cannot possibly have this many levels, since there must be one
  * block per level, which is bound by the range of BlockNumber:
@@ -50,6 +56,20 @@ PG_MODULE_MAGIC;
 #define BTreeTupleGetNKeyAtts(itup, rel)   \
 	Min(IndexRelationGetNumberOfKeyAttributes(rel), BTreeTupleGetNAtts(itup, rel))
 
+/*
+ * Context for use within verify_btreeam()
+ */
+typedef struct BtreeCheckContext
+{
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	bool		is_corrupt;
+	bool		on_error_stop;
+}			BtreeCheckContext;
+
+#define CONTINUE_CHECKING(ctx) \
+	(ctx == NULL || !((ctx)->is_corrupt && (ctx)->on_error_stop))
+
 /*
  * State associated with verifying a B-Tree index
  *
@@ -116,6 +136,9 @@ typedef struct BtreeCheckState
 	bloom_filter *filter;
 	/* Debug counter */
 	int64		heaptuplespresent;
+
+	/* Error reporting context */
+	BtreeCheckContext *ctx;
 } BtreeCheckState;
 
 /*
@@ -133,28 +156,28 @@ typedef struct BtreeLevel
 	bool		istruerootlevel;
 } BtreeLevel;
 
-PG_FUNCTION_INFO_V1(bt_index_check);
-PG_FUNCTION_INFO_V1(bt_index_parent_check);
-
 static void bt_index_check_internal(Oid indrelid, bool parentcheck,
-									bool heapallindexed, bool rootdescend);
+									bool heapallindexed, bool rootdescend,
+									BtreeCheckContext * ctx);
 static inline void btree_index_checkable(Relation rel);
 static inline bool btree_index_mainfork_expected(Relation rel);
 static void bt_check_every_level(Relation rel, Relation heaprel,
 								 bool heapkeyspace, bool readonly, bool heapallindexed,
-								 bool rootdescend);
+								 bool rootdescend, BtreeCheckContext * ctx);
 static BtreeLevel bt_check_level_from_leftmost(BtreeCheckState *state,
-											   BtreeLevel level);
-static void bt_target_page_check(BtreeCheckState *state);
-static BTScanInsert bt_right_page_check_scankey(BtreeCheckState *state);
+											   BtreeLevel level, BtreeCheckContext * ctx);
+static void bt_target_page_check(BtreeCheckState *state, BtreeCheckContext * ctx);
+static BTScanInsert bt_right_page_check_scankey(BtreeCheckState *state, BtreeCheckContext * ctx);
 static void bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
-						   OffsetNumber downlinkoffnum);
+						   OffsetNumber downlinkoffnum, BtreeCheckContext * ctx);
 static void bt_child_highkey_check(BtreeCheckState *state,
 								   OffsetNumber target_downlinkoffnum,
 								   Page loaded_child,
-								   uint32 target_level);
+								   uint32 target_level,
+								   BtreeCheckContext * ctx);
 static void bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
-									  BlockNumber targetblock, Page target);
+									  BlockNumber targetblock, Page target,
+									  BtreeCheckContext * ctx);
 static void bt_tuple_present_callback(Relation index, ItemPointer tid,
 									  Datum *values, bool *isnull,
 									  bool tupleIsAlive, void *checkstate);
@@ -176,7 +199,7 @@ static inline bool invariant_l_nontarget_offset(BtreeCheckState *state,
 												BlockNumber nontargetblock,
 												Page nontarget,
 												OffsetNumber upperbound);
-static Page palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum);
+static Page palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum, BtreeCheckContext * ctx);
 static inline BTScanInsert bt_mkscankey_pivotsearch(Relation rel,
 													IndexTuple itup);
 static ItemId PageGetItemIdCareful(BtreeCheckState *state, BlockNumber block,
@@ -185,6 +208,26 @@ static inline ItemPointer BTreeTupleGetHeapTIDCareful(BtreeCheckState *state,
 													  IndexTuple itup, bool nonpivot);
 static inline ItemPointer BTreeTupleGetPointsToTID(IndexTuple itup);
 
+static TupleDesc verify_btreeam_tupdesc(void);
+static void confess(BtreeCheckContext * ctx, BlockNumber blkno, char *msg);
+
+/*
+ * Macro for either calling ereport(...) or confess(...) depending on whether
+ * a context for returning the error message exists.  Prior to version 1.3,
+ * all functions reported any detected corruption via ereport, but starting in
+ * 1.3, the new function verify_btreeam reports detected corruption back to
+ * the caller as a set of rows, and pre-existing functions continue to report
+ * corruption via ereport.  This macro allows the shared implementation to
+ * to do the right thing depending on context.
+ */
+#define econfess(ctx, blkno, code, ...) \
+	do { \
+		if (ctx) \
+			confess(ctx, blkno, psprintf(__VA_ARGS__)); \
+		else \
+			ereport(ERROR, (errcode(code), errmsg(__VA_ARGS__))); \
+	} while(0)
+
 /*
  * bt_index_check(index regclass, heapallindexed boolean)
  *
@@ -203,7 +246,7 @@ bt_index_check(PG_FUNCTION_ARGS)
 	if (PG_NARGS() == 2)
 		heapallindexed = PG_GETARG_BOOL(1);
 
-	bt_index_check_internal(indrelid, false, heapallindexed, false);
+	bt_index_check_internal(indrelid, false, heapallindexed, false, NULL);
 
 	PG_RETURN_VOID();
 }
@@ -229,17 +272,64 @@ bt_index_parent_check(PG_FUNCTION_ARGS)
 	if (PG_NARGS() == 3)
 		rootdescend = PG_GETARG_BOOL(2);
 
-	bt_index_check_internal(indrelid, true, heapallindexed, rootdescend);
+	bt_index_check_internal(indrelid, true, heapallindexed, rootdescend, NULL);
 
 	PG_RETURN_VOID();
 }
 
+Datum
+verify_btreeam(PG_FUNCTION_ARGS)
+{
+#define BTREECHECK_RELATION_COLS 2
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext oldcontext;
+	BtreeCheckContext ctx;
+	bool		randomAccess;
+	Oid			indrelid;
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* check supplied arguments */
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing required parameter for 'rel'")));
+	indrelid = PG_GETARG_OID(0);
+
+	memset(&ctx, 0, sizeof(BtreeCheckContext));
+
+	ctx.on_error_stop = PG_ARGISNULL(1) ? false : PG_GETARG_BOOL(1);
+
+	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
+	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+	ctx.tupdesc = verify_btreeam_tupdesc();
+	ctx.tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = ctx.tupstore;
+	rsinfo->setDesc = ctx.tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	bt_index_check_internal(indrelid, true, true, true, &ctx);
+
+	PG_RETURN_NULL();
+}
+
 /*
  * Helper for bt_index_[parent_]check, coordinating the bulk of the work.
  */
 static void
 bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed,
-						bool rootdescend)
+						bool rootdescend, BtreeCheckContext * ctx)
 {
 	Oid			heapid;
 	Relation	indrel;
@@ -300,15 +390,16 @@ bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed,
 
 		RelationOpenSmgr(indrel);
 		if (!smgrexists(indrel->rd_smgr, MAIN_FORKNUM))
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("index \"%s\" lacks a main relation fork",
-							RelationGetRelationName(indrel))));
+			econfess(ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "index \"%s\" lacks a main relation fork",
+					 RelationGetRelationName(indrel));
 
 		/* Check index, possibly against table it is an index on */
-		_bt_metaversion(indrel, &heapkeyspace, &allequalimage);
-		bt_check_every_level(indrel, heaprel, heapkeyspace, parentcheck,
-							 heapallindexed, rootdescend);
+		if (CONTINUE_CHECKING(ctx))
+			_bt_metaversion(indrel, &heapkeyspace, &allequalimage);
+		if (CONTINUE_CHECKING(ctx))
+			bt_check_every_level(indrel, heaprel, heapkeyspace, parentcheck,
+								 heapallindexed, rootdescend, ctx);
 	}
 
 	/*
@@ -402,7 +493,8 @@ btree_index_mainfork_expected(Relation rel)
  */
 static void
 bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
-					 bool readonly, bool heapallindexed, bool rootdescend)
+					 bool readonly, bool heapallindexed, bool rootdescend,
+					 BtreeCheckContext * ctx)
 {
 	BtreeCheckState *state;
 	Page		metapage;
@@ -434,6 +526,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 	state->readonly = readonly;
 	state->heapallindexed = heapallindexed;
 	state->rootdescend = rootdescend;
+	state->ctx = ctx;
 
 	if (state->heapallindexed)
 	{
@@ -506,7 +599,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 	state->checkstrategy = GetAccessStrategy(BAS_BULKREAD);
 
 	/* Get true root block from meta-page */
-	metapage = palloc_btree_page(state, BTREE_METAPAGE);
+	metapage = palloc_btree_page(state, BTREE_METAPAGE, ctx);
 	metad = BTPageGetMeta(metapage);
 
 	/*
@@ -535,19 +628,18 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 	current.level = metad->btm_level;
 	current.leftmost = metad->btm_root;
 	current.istruerootlevel = true;
-	while (current.leftmost != P_NONE)
+	while (CONTINUE_CHECKING(state->ctx) && current.leftmost != P_NONE)
 	{
 		/*
 		 * Verify this level, and get left most page for next level down, if
 		 * not at leaf level
 		 */
-		current = bt_check_level_from_leftmost(state, current);
+		current = bt_check_level_from_leftmost(state, current, ctx);
 
 		if (current.leftmost == InvalidBlockNumber)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("index \"%s\" has no valid pages on level below %u or first level",
-							RelationGetRelationName(rel), previouslevel)));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "index \"%s\" has no valid pages on level below %u or first level",
+					 RelationGetRelationName(rel), previouslevel);
 
 		previouslevel = current.level;
 	}
@@ -555,7 +647,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 	/*
 	 * * Check whether heap contains unindexed/malformed tuples *
 	 */
-	if (state->heapallindexed)
+	if (CONTINUE_CHECKING(state->ctx) && state->heapallindexed)
 	{
 		IndexInfo  *indexinfo = BuildIndexInfo(state->rel);
 		TableScanDesc scan;
@@ -639,7 +731,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
  * each call to bt_target_page_check().
  */
 static BtreeLevel
-bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
+bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level, BtreeCheckContext * ctx)
 {
 	/* State to establish early, concerning entire level */
 	BTPageOpaque opaque;
@@ -672,7 +764,7 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
 
 		/* Initialize state for this iteration */
 		state->targetblock = current;
-		state->target = palloc_btree_page(state, state->targetblock);
+		state->target = palloc_btree_page(state, state->targetblock, ctx);
 		state->targetlsn = PageGetLSN(state->target);
 
 		opaque = (BTPageOpaque) PageGetSpecialPointer(state->target);
@@ -691,18 +783,16 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
 			 * checked.
 			 */
 			if (state->readonly && P_ISDELETED(opaque))
-				ereport(ERROR,
-						(errcode(ERRCODE_INDEX_CORRUPTED),
-						 errmsg("downlink or sibling link points to deleted block in index \"%s\"",
-								RelationGetRelationName(state->rel)),
-						 errdetail_internal("Block=%u left block=%u left link from block=%u.",
-											current, leftcurrent, opaque->btpo_prev)));
+				econfess(state->ctx, current, ERRCODE_INDEX_CORRUPTED,
+						 "downlink or sibling link points to deleted block in index \"%s\" "
+						 "(Block=%u left block=%u left link from block=%u)",
+						 RelationGetRelationName(state->rel),
+						 current, leftcurrent, opaque->btpo_prev);
 
 			if (P_RIGHTMOST(opaque))
-				ereport(ERROR,
-						(errcode(ERRCODE_INDEX_CORRUPTED),
-						 errmsg("block %u fell off the end of index \"%s\"",
-								current, RelationGetRelationName(state->rel))));
+				econfess(state->ctx, current, ERRCODE_INDEX_CORRUPTED,
+						 "block %u fell off the end of index \"%s\"",
+						 current, RelationGetRelationName(state->rel));
 			else
 				ereport(DEBUG1,
 						(errcode(ERRCODE_NO_DATA),
@@ -722,16 +812,14 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
 			if (state->readonly)
 			{
 				if (!P_LEFTMOST(opaque))
-					ereport(ERROR,
-							(errcode(ERRCODE_INDEX_CORRUPTED),
-							 errmsg("block %u is not leftmost in index \"%s\"",
-									current, RelationGetRelationName(state->rel))));
+					econfess(state->ctx, current, ERRCODE_INDEX_CORRUPTED,
+							 "block %u is not leftmost in index \"%s\"",
+							 current, RelationGetRelationName(state->rel));
 
 				if (level.istruerootlevel && !P_ISROOT(opaque))
-					ereport(ERROR,
-							(errcode(ERRCODE_INDEX_CORRUPTED),
-							 errmsg("block %u is not true root in index \"%s\"",
-									current, RelationGetRelationName(state->rel))));
+					econfess(state->ctx, current, ERRCODE_INDEX_CORRUPTED,
+							 "block %u is not true root in index \"%s\"",
+							 current, RelationGetRelationName(state->rel));
 			}
 
 			/*
@@ -780,33 +868,30 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
 		 * so sibling pointers should always be in mutual agreement
 		 */
 		if (state->readonly && opaque->btpo_prev != leftcurrent)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("left link/right link pair in index \"%s\" not in agreement",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Block=%u left block=%u left link from block=%u.",
-										current, leftcurrent, opaque->btpo_prev)));
+			econfess(state->ctx, current, ERRCODE_INDEX_CORRUPTED,
+					 "left link/right link pair in index \"%s\" not in agreement "
+					 "(Block=%u left block=%u left link from block=%u)",
+					 RelationGetRelationName(state->rel),
+					 current, leftcurrent, opaque->btpo_prev);
 
 		/* Check level, which must be valid for non-ignorable page */
 		if (level.level != opaque->btpo.level)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("leftmost down link for level points to block in index \"%s\" whose level is not one level down",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Block pointed to=%u expected level=%u level in pointed to block=%u.",
-										current, level.level, opaque->btpo.level)));
+			econfess(state->ctx, current, ERRCODE_INDEX_CORRUPTED,
+					 "leftmost down link for level points to block in index \"%s\" whose level is not one level down "
+					 "(Block pointed to=%u expected level=%u level in pointed to block=%u)",
+					 RelationGetRelationName(state->rel),
+					 current, level.level, opaque->btpo.level);
 
 		/* Verify invariants for page */
-		bt_target_page_check(state);
+		bt_target_page_check(state, ctx);
 
 nextpage:
 
 		/* Try to detect circular links */
 		if (current == leftcurrent || current == opaque->btpo_prev)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("circular link chain found in block %u of index \"%s\"",
-							current, RelationGetRelationName(state->rel))));
+			econfess(state->ctx, current, ERRCODE_INDEX_CORRUPTED,
+					 "circular link chain found in block %u of index \"%s\"",
+					 current, RelationGetRelationName(state->rel));
 
 		leftcurrent = current;
 		current = opaque->btpo_next;
@@ -850,7 +935,7 @@ nextpage:
 		/* Free page and associated memory for this iteration */
 		MemoryContextReset(state->targetcontext);
 	}
-	while (current != P_NONE);
+	while (CONTINUE_CHECKING(state->ctx) && current != P_NONE);
 
 	if (state->lowkey)
 	{
@@ -902,7 +987,7 @@ nextpage:
  * resetting state->targetcontext.
  */
 static void
-bt_target_page_check(BtreeCheckState *state)
+bt_target_page_check(BtreeCheckState *state, BtreeCheckContext * ctx)
 {
 	OffsetNumber offset;
 	OffsetNumber max;
@@ -930,16 +1015,15 @@ bt_target_page_check(BtreeCheckState *state)
 							 P_HIKEY))
 		{
 			itup = (IndexTuple) PageGetItem(state->target, itemid);
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("wrong number of high key index tuple attributes in index \"%s\"",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Index block=%u natts=%u block type=%s page lsn=%X/%X.",
-										state->targetblock,
-										BTreeTupleGetNAtts(itup, state->rel),
-										P_ISLEAF(topaque) ? "heap" : "index",
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+			econfess(state->ctx, state->targetblock, ERRCODE_INDEX_CORRUPTED,
+					 "wrong number of high key index tuple attributes in index \"%s\" "
+					 "(Index block=%u natts=%u block type=%s page lsn=%X/%X)",
+					 RelationGetRelationName(state->rel),
+					 state->targetblock,
+					 BTreeTupleGetNAtts(itup, state->rel),
+					 P_ISLEAF(topaque) ? "heap" : "index",
+					 (uint32) (state->targetlsn >> 32),
+					 (uint32) state->targetlsn);
 		}
 	}
 
@@ -949,7 +1033,7 @@ bt_target_page_check(BtreeCheckState *state)
 	 * real item (if any).
 	 */
 	for (offset = P_FIRSTDATAKEY(topaque);
-		 offset <= max;
+		 offset <= max && CONTINUE_CHECKING(state->ctx);
 		 offset = OffsetNumberNext(offset))
 	{
 		ItemId		itemid;
@@ -973,16 +1057,15 @@ bt_target_page_check(BtreeCheckState *state)
 		 * frequently, and is surprisingly tolerant of corrupt lp_len fields.
 		 */
 		if (tupsize != ItemIdGetLength(itemid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("index tuple size does not equal lp_len in index \"%s\"",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Index tid=(%u,%u) tuple size=%zu lp_len=%u page lsn=%X/%X.",
-										state->targetblock, offset,
-										tupsize, ItemIdGetLength(itemid),
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn),
-					 errhint("This could be a torn page problem.")));
+			econfess(state->ctx, state->targetblock, ERRCODE_INDEX_CORRUPTED,
+					 "index tuple size does not equal lp_len in index \"%s\" "
+					 "(Index tid=(%u,%u) tuple size=%zu lp_len=%u page lsn=%X/%X) "
+					 "(This could be a torn page problem)",
+					 RelationGetRelationName(state->rel),
+					 state->targetblock, offset,
+					 tupsize, ItemIdGetLength(itemid),
+					 (uint32) (state->targetlsn >> 32),
+					 (uint32) state->targetlsn);
 
 		/* Check the number of index tuple attributes */
 		if (!_bt_check_natts(state->rel, state->heapkeyspace, state->target,
@@ -998,17 +1081,16 @@ bt_target_page_check(BtreeCheckState *state)
 							ItemPointerGetBlockNumberNoCheck(tid),
 							ItemPointerGetOffsetNumberNoCheck(tid));
 
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("wrong number of index tuple attributes in index \"%s\"",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Index tid=%s natts=%u points to %s tid=%s page lsn=%X/%X.",
-										itid,
-										BTreeTupleGetNAtts(itup, state->rel),
-										P_ISLEAF(topaque) ? "heap" : "index",
-										htid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "wrong number of index tuple attributes in index \"%s\" "
+					 "(Index tid=%s natts=%u points to %s tid=%s page lsn=%X/%X)",
+					 RelationGetRelationName(state->rel),
+					 itid,
+					 BTreeTupleGetNAtts(itup, state->rel),
+					 P_ISLEAF(topaque) ? "heap" : "index",
+					 htid,
+					 (uint32) (state->targetlsn >> 32),
+					 (uint32) state->targetlsn);
 		}
 
 		/*
@@ -1027,7 +1109,8 @@ bt_target_page_check(BtreeCheckState *state)
 				bt_child_highkey_check(state,
 									   offset,
 									   NULL,
-									   topaque->btpo.level);
+									   topaque->btpo.level,
+									   ctx);
 			}
 			continue;
 		}
@@ -1049,14 +1132,13 @@ bt_target_page_check(BtreeCheckState *state)
 			htid = psprintf("(%u,%u)", ItemPointerGetBlockNumber(tid),
 							ItemPointerGetOffsetNumber(tid));
 
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("could not find tuple using search from root page in index \"%s\"",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Index tid=%s points to heap tid=%s page lsn=%X/%X.",
-										itid, htid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "could not find tuple using search from root page in index \"%s\" "
+					 "(Index tid=%s points to heap tid=%s page lsn=%X/%X)",
+					 RelationGetRelationName(state->rel),
+					 itid, htid,
+					 (uint32) (state->targetlsn >> 32),
+					 (uint32) state->targetlsn);
 		}
 
 		/*
@@ -1079,14 +1161,13 @@ bt_target_page_check(BtreeCheckState *state)
 				{
 					char	   *itid = psprintf("(%u,%u)", state->targetblock, offset);
 
-					ereport(ERROR,
-							(errcode(ERRCODE_INDEX_CORRUPTED),
-							 errmsg_internal("posting list contains misplaced TID in index \"%s\"",
-											 RelationGetRelationName(state->rel)),
-							 errdetail_internal("Index tid=%s posting list offset=%d page lsn=%X/%X.",
-												itid, i,
-												(uint32) (state->targetlsn >> 32),
-												(uint32) state->targetlsn)));
+					econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+							 "posting list contains misplaced TID in index \"%s\" "
+							 "(Index tid=%s posting list offset=%d page lsn=%X/%X)",
+							 RelationGetRelationName(state->rel),
+							 itid, i,
+							 (uint32) (state->targetlsn >> 32),
+							 (uint32) state->targetlsn);
 				}
 
 				ItemPointerCopy(current, &last);
@@ -1134,16 +1215,15 @@ bt_target_page_check(BtreeCheckState *state)
 							ItemPointerGetBlockNumberNoCheck(tid),
 							ItemPointerGetOffsetNumberNoCheck(tid));
 
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("index row size %zu exceeds maximum for index \"%s\"",
-							tupsize, RelationGetRelationName(state->rel)),
-					 errdetail_internal("Index tid=%s points to %s tid=%s page lsn=%X/%X.",
-										itid,
-										P_ISLEAF(topaque) ? "heap" : "index",
-										htid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "index row size %zu exceeds maximum for index \"%s\" "
+					 "(Index tid=%s points to %s tid=%s page lsn=%X/%X)",
+					 tupsize, RelationGetRelationName(state->rel),
+					 itid,
+					 P_ISLEAF(topaque) ? "heap" : "index",
+					 htid,
+					 (uint32) (state->targetlsn >> 32),
+					 (uint32) state->targetlsn);
 		}
 
 		/* Fingerprint leaf page tuples (those that point to the heap) */
@@ -1242,16 +1322,15 @@ bt_target_page_check(BtreeCheckState *state)
 							ItemPointerGetBlockNumberNoCheck(tid),
 							ItemPointerGetOffsetNumberNoCheck(tid));
 
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("high key invariant violated for index \"%s\"",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Index tid=%s points to %s tid=%s page lsn=%X/%X.",
-										itid,
-										P_ISLEAF(topaque) ? "heap" : "index",
-										htid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "high key invariant violated for index \"%s\" "
+					 "(Index tid=%s points to %s tid=%s page lsn=%X/%X)",
+					 RelationGetRelationName(state->rel),
+					 itid,
+					 P_ISLEAF(topaque) ? "heap" : "index",
+					 htid,
+					 (uint32) (state->targetlsn >> 32),
+					 (uint32) state->targetlsn);
 		}
 		/* Reset, in case scantid was set to (itup) posting tuple's max TID */
 		skey->scantid = scantid;
@@ -1289,21 +1368,20 @@ bt_target_page_check(BtreeCheckState *state)
 							 ItemPointerGetBlockNumberNoCheck(tid),
 							 ItemPointerGetOffsetNumberNoCheck(tid));
 
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("item order invariant violated for index \"%s\"",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Lower index tid=%s (points to %s tid=%s) "
-										"higher index tid=%s (points to %s tid=%s) "
-										"page lsn=%X/%X.",
-										itid,
-										P_ISLEAF(topaque) ? "heap" : "index",
-										htid,
-										nitid,
-										P_ISLEAF(topaque) ? "heap" : "index",
-										nhtid,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "item order invariant violated for index \"%s\" "
+					 "(Lower index tid=%s (points to %s tid=%s) "
+					 "higher index tid=%s (points to %s tid=%s) "
+					 "page lsn=%X/%X)",
+					 RelationGetRelationName(state->rel),
+					 itid,
+					 P_ISLEAF(topaque) ? "heap" : "index",
+					 htid,
+					 nitid,
+					 P_ISLEAF(topaque) ? "heap" : "index",
+					 nhtid,
+					 (uint32) (state->targetlsn >> 32),
+					 (uint32) state->targetlsn);
 		}
 
 		/*
@@ -1328,7 +1406,7 @@ bt_target_page_check(BtreeCheckState *state)
 			BTScanInsert rightkey;
 
 			/* Get item in next/right page */
-			rightkey = bt_right_page_check_scankey(state);
+			rightkey = bt_right_page_check_scankey(state, ctx);
 
 			if (rightkey &&
 				!invariant_g_offset(state, rightkey, max))
@@ -1343,7 +1421,7 @@ bt_target_page_check(BtreeCheckState *state)
 				if (!state->readonly)
 				{
 					/* Get fresh copy of target page */
-					state->target = palloc_btree_page(state, state->targetblock);
+					state->target = palloc_btree_page(state, state->targetblock, ctx);
 					/* Note that we deliberately do not update target LSN */
 					topaque = (BTPageOpaque) PageGetSpecialPointer(state->target);
 
@@ -1354,14 +1432,13 @@ bt_target_page_check(BtreeCheckState *state)
 						return;
 				}
 
-				ereport(ERROR,
-						(errcode(ERRCODE_INDEX_CORRUPTED),
-						 errmsg("cross page item order invariant violated for index \"%s\"",
-								RelationGetRelationName(state->rel)),
-						 errdetail_internal("Last item on page tid=(%u,%u) page lsn=%X/%X.",
-											state->targetblock, offset,
-											(uint32) (state->targetlsn >> 32),
-											(uint32) state->targetlsn)));
+				econfess(state->ctx, state->targetblock, ERRCODE_INDEX_CORRUPTED,
+						 "cross page item order invariant violated for index \"%s\" "
+						 "(Last item on page tid=(%u,%u) page lsn=%X/%X)",
+						 RelationGetRelationName(state->rel),
+						 state->targetblock, offset,
+						 (uint32) (state->targetlsn >> 32),
+						 (uint32) state->targetlsn);
 			}
 		}
 
@@ -1374,7 +1451,7 @@ bt_target_page_check(BtreeCheckState *state)
 		 * because it has no useful value to compare).
 		 */
 		if (!P_ISLEAF(topaque) && state->readonly)
-			bt_child_check(state, skey, offset);
+			bt_child_check(state, skey, offset, ctx);
 	}
 
 	/*
@@ -1386,10 +1463,11 @@ bt_target_page_check(BtreeCheckState *state)
 	 * right of the child page pointer to by our rightmost downlink.  And they
 	 * might have missing downlinks.  This final call checks for them.
 	 */
-	if (!P_ISLEAF(topaque) && P_RIGHTMOST(topaque) && state->readonly)
+	if (CONTINUE_CHECKING(state->ctx) &&
+		!P_ISLEAF(topaque) && P_RIGHTMOST(topaque) && state->readonly)
 	{
 		bt_child_highkey_check(state, InvalidOffsetNumber,
-							   NULL, topaque->btpo.level);
+							   NULL, topaque->btpo.level, ctx);
 	}
 }
 
@@ -1410,7 +1488,7 @@ bt_target_page_check(BtreeCheckState *state)
  * been concurrently deleted.
  */
 static BTScanInsert
-bt_right_page_check_scankey(BtreeCheckState *state)
+bt_right_page_check_scankey(BtreeCheckState *state, BtreeCheckContext * ctx)
 {
 	BTPageOpaque opaque;
 	ItemId		rightitem;
@@ -1455,7 +1533,7 @@ bt_right_page_check_scankey(BtreeCheckState *state)
 	{
 		CHECK_FOR_INTERRUPTS();
 
-		rightpage = palloc_btree_page(state, targetnext);
+		rightpage = palloc_btree_page(state, targetnext, ctx);
 		opaque = (BTPageOpaque) PageGetSpecialPointer(rightpage);
 
 		if (!P_IGNORE(opaque) || P_RIGHTMOST(opaque))
@@ -1666,7 +1744,8 @@ static void
 bt_child_highkey_check(BtreeCheckState *state,
 					   OffsetNumber target_downlinkoffnum,
 					   Page loaded_child,
-					   uint32 target_level)
+					   uint32 target_level,
+					   BtreeCheckContext * ctx)
 {
 	BlockNumber blkno = state->prevrightlink;
 	Page		page;
@@ -1708,7 +1787,7 @@ bt_child_highkey_check(BtreeCheckState *state,
 	}
 
 	/* Move to the right on the child level */
-	while (true)
+	while (CONTINUE_CHECKING(state->ctx))
 	{
 		/*
 		 * Did we traverse the whole tree level and this is check for pages to
@@ -1723,51 +1802,47 @@ bt_child_highkey_check(BtreeCheckState *state,
 
 		/* Did we traverse the whole tree level and don't find next downlink? */
 		if (blkno == P_NONE)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("can't traverse from downlink %u to downlink %u of index \"%s\"",
-							state->prevrightlink, downlink,
-							RelationGetRelationName(state->rel))));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "can't traverse from downlink %u to downlink %u of index \"%s\"",
+					 state->prevrightlink, downlink,
+					 RelationGetRelationName(state->rel));
 
 		/* Load page contents */
 		if (blkno == downlink && loaded_child)
 			page = loaded_child;
 		else
-			page = palloc_btree_page(state, blkno);
+			page = palloc_btree_page(state, blkno, ctx);
 
 		opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 
 		/* The first page we visit at the level should be leftmost */
 		if (first && !BlockNumberIsValid(state->prevrightlink) && !P_LEFTMOST(opaque))
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("the first child of leftmost target page is not leftmost of its level in index \"%s\"",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.",
-										state->targetblock, blkno,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+			econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+					 "the first child of leftmost target page is not leftmost of its level in index \"%s\" "
+					 "(Target block=%u child block=%u target page lsn=%X/%X)",
+					 RelationGetRelationName(state->rel),
+					 state->targetblock, blkno,
+					 (uint32) (state->targetlsn >> 32),
+					 (uint32) state->targetlsn);
 
 		/* Check level for non-ignorable page */
 		if (!P_IGNORE(opaque) && opaque->btpo.level != target_level - 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("block found while following rightlinks from child of index \"%s\" has invalid level",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Block pointed to=%u expected level=%u level in pointed to block=%u.",
-										blkno, target_level - 1, opaque->btpo.level)));
+			econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+					 "block found while following rightlinks from child of index \"%s\" has invalid level "
+					 "(Block pointed to=%u expected level=%u level in pointed to block=%u)",
+					 RelationGetRelationName(state->rel),
+					 blkno, target_level - 1, opaque->btpo.level);
 
 		/* Try to detect circular links */
 		if ((!first && blkno == state->prevrightlink) || blkno == opaque->btpo_prev)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("circular link chain found in block %u of index \"%s\"",
-							blkno, RelationGetRelationName(state->rel))));
+			econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+					 "circular link chain found in block %u of index \"%s\"",
+					 blkno, RelationGetRelationName(state->rel));
 
 		if (blkno != downlink && !P_IGNORE(opaque))
 		{
 			/* blkno probably has missing parent downlink */
-			bt_downlink_missing_check(state, rightsplit, blkno, page);
+			bt_downlink_missing_check(state, rightsplit, blkno, page, ctx);
 		}
 
 		rightsplit = P_INCOMPLETE_SPLIT(opaque);
@@ -1825,14 +1900,13 @@ bt_child_highkey_check(BtreeCheckState *state,
 				if (pivotkey_offset > PageGetMaxOffsetNumber(state->target))
 				{
 					if (P_RIGHTMOST(topaque))
-						ereport(ERROR,
-								(errcode(ERRCODE_INDEX_CORRUPTED),
-								 errmsg("child high key is greater than rightmost pivot key on target level in index \"%s\"",
-										RelationGetRelationName(state->rel)),
-								 errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.",
-													state->targetblock, blkno,
-													(uint32) (state->targetlsn >> 32),
-													(uint32) state->targetlsn)));
+						econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+								 "child high key is greater than rightmost pivot key on target level in index \"%s\" "
+								 "(Target block=%u child block=%u target page lsn=%X/%X)",
+								 RelationGetRelationName(state->rel),
+								 state->targetblock, blkno,
+								 (uint32) (state->targetlsn >> 32),
+								 (uint32) state->targetlsn);
 					pivotkey_offset = P_HIKEY;
 				}
 				itemid = PageGetItemIdCareful(state, state->targetblock,
@@ -1856,27 +1930,25 @@ bt_child_highkey_check(BtreeCheckState *state,
 				 * page.
 				 */
 				if (!state->lowkey)
-					ereport(ERROR,
-							(errcode(ERRCODE_INDEX_CORRUPTED),
-							 errmsg("can't find left sibling high key in index \"%s\"",
-									RelationGetRelationName(state->rel)),
-							 errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.",
-												state->targetblock, blkno,
-												(uint32) (state->targetlsn >> 32),
-												(uint32) state->targetlsn)));
+					econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+							 "can't find left sibling high key in index \"%s\" "
+							 "(Target block=%u child block=%u target page lsn=%X/%X)",
+							 RelationGetRelationName(state->rel),
+							 state->targetblock, blkno,
+							 (uint32) (state->targetlsn >> 32),
+							 (uint32) state->targetlsn);
 				itup = state->lowkey;
 			}
 
 			if (!bt_pivot_tuple_identical(highkey, itup))
 			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INDEX_CORRUPTED),
-						 errmsg("mismatch between parent key and child high key in index \"%s\"",
-								RelationGetRelationName(state->rel)),
-						 errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.",
-											state->targetblock, blkno,
-											(uint32) (state->targetlsn >> 32),
-											(uint32) state->targetlsn)));
+				econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+						 "mismatch between parent key and child high key in index \"%s\" "
+						 "(Target block=%u child block=%u target page lsn=%X/%X)",
+						 RelationGetRelationName(state->rel),
+						 state->targetblock, blkno,
+						 (uint32) (state->targetlsn >> 32),
+						 (uint32) state->targetlsn);
 			}
 		}
 
@@ -1913,7 +1985,7 @@ bt_child_highkey_check(BtreeCheckState *state,
  */
 static void
 bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
-			   OffsetNumber downlinkoffnum)
+			   OffsetNumber downlinkoffnum, BtreeCheckContext * ctx)
 {
 	ItemId		itemid;
 	IndexTuple	itup;
@@ -1978,7 +2050,7 @@ bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
 	 * the operator class obeys the transitive law.
 	 */
 	topaque = (BTPageOpaque) PageGetSpecialPointer(state->target);
-	child = palloc_btree_page(state, childblock);
+	child = palloc_btree_page(state, childblock, ctx);
 	copaque = (BTPageOpaque) PageGetSpecialPointer(child);
 	maxoffset = PageGetMaxOffsetNumber(child);
 
@@ -1987,7 +2059,7 @@ bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
 	 * check for downlink connectivity.
 	 */
 	bt_child_highkey_check(state, downlinkoffnum,
-						   child, topaque->btpo.level);
+						   child, topaque->btpo.level, ctx);
 
 	/*
 	 * Since there cannot be a concurrent VACUUM operation in readonly mode,
@@ -2014,17 +2086,16 @@ bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
 	 * to test.
 	 */
 	if (P_ISDELETED(copaque))
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("downlink to deleted page found in index \"%s\"",
-						RelationGetRelationName(state->rel)),
-				 errdetail_internal("Parent block=%u child block=%u parent page lsn=%X/%X.",
-									state->targetblock, childblock,
-									(uint32) (state->targetlsn >> 32),
-									(uint32) state->targetlsn)));
+		econfess(state->ctx, state->targetblock, ERRCODE_INDEX_CORRUPTED,
+				 "downlink to deleted page found in index \"%s\" "
+				 "(Parent block=%u child block=%u parent page lsn=%X/%X)",
+				 RelationGetRelationName(state->rel),
+				 state->targetblock, childblock,
+				 (uint32) (state->targetlsn >> 32),
+				 (uint32) state->targetlsn);
 
 	for (offset = P_FIRSTDATAKEY(copaque);
-		 offset <= maxoffset;
+		 offset <= maxoffset && CONTINUE_CHECKING(state->ctx);
 		 offset = OffsetNumberNext(offset))
 	{
 		/*
@@ -2056,14 +2127,13 @@ bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
 
 		if (!invariant_l_nontarget_offset(state, targetkey, childblock, child,
 										  offset))
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("down-link lower bound invariant violated for index \"%s\"",
-							RelationGetRelationName(state->rel)),
-					 errdetail_internal("Parent block=%u child index tid=(%u,%u) parent page lsn=%X/%X.",
-										state->targetblock, childblock, offset,
-										(uint32) (state->targetlsn >> 32),
-										(uint32) state->targetlsn)));
+			econfess(state->ctx, state->targetblock, ERRCODE_INDEX_CORRUPTED,
+					 "down-link lower bound invariant violated for index \"%s\" "
+					 "(Parent block=%u child index tid=(%u,%u) parent page lsn=%X/%X)",
+					 RelationGetRelationName(state->rel),
+					 state->targetblock, childblock, offset,
+					 (uint32) (state->targetlsn >> 32),
+					 (uint32) state->targetlsn);
 	}
 
 	pfree(child);
@@ -2084,7 +2154,7 @@ bt_child_check(BtreeCheckState *state, BTScanInsert targetkey,
  */
 static void
 bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
-						  BlockNumber blkno, Page page)
+						  BlockNumber blkno, Page page, BtreeCheckContext * ctx)
 {
 	BTPageOpaque opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	ItemId		itemid;
@@ -2150,14 +2220,13 @@ bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
 	 * inconsistencies anywhere else.
 	 */
 	if (P_ISLEAF(opaque))
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("leaf index block lacks downlink in index \"%s\"",
-						RelationGetRelationName(state->rel)),
-				 errdetail_internal("Block=%u page lsn=%X/%X.",
-									blkno,
-									(uint32) (pagelsn >> 32),
-									(uint32) pagelsn)));
+		econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+				 "leaf index block lacks downlink in index \"%s\" "
+				 "(Block=%u page lsn=%X/%X)",
+				 RelationGetRelationName(state->rel),
+				 blkno,
+				 (uint32) (pagelsn >> 32),
+				 (uint32) pagelsn);
 
 	/* Descend from the given page, which is an internal page */
 	elog(DEBUG1, "checking for interrupted multi-level deletion due to missing downlink in index \"%s\"",
@@ -2167,11 +2236,11 @@ bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
 	itemid = PageGetItemIdCareful(state, blkno, page, P_FIRSTDATAKEY(opaque));
 	itup = (IndexTuple) PageGetItem(page, itemid);
 	childblk = BTreeTupleGetDownLink(itup);
-	for (;;)
+	while (CONTINUE_CHECKING(state->ctx))
 	{
 		CHECK_FOR_INTERRUPTS();
 
-		child = palloc_btree_page(state, childblk);
+		child = palloc_btree_page(state, childblk, ctx);
 		copaque = (BTPageOpaque) PageGetSpecialPointer(child);
 
 		if (P_ISLEAF(copaque))
@@ -2179,13 +2248,12 @@ bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
 
 		/* Do an extra sanity check in passing on internal pages */
 		if (copaque->btpo.level != level - 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg_internal("downlink points to block in index \"%s\" whose level is not one level down",
-									 RelationGetRelationName(state->rel)),
-					 errdetail_internal("Top parent/under check block=%u block pointed to=%u expected level=%u level in pointed to block=%u.",
-										blkno, childblk,
-										level - 1, copaque->btpo.level)));
+			econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+					 "downlink points to block in index \"%s\" whose level is not one level down "
+					 "(Top parent/under check block=%u block pointed to=%u expected level=%u level in pointed to block=%u)",
+					 RelationGetRelationName(state->rel),
+					 blkno, childblk,
+					 level - 1, copaque->btpo.level);
 
 		level = copaque->btpo.level;
 		itemid = PageGetItemIdCareful(state, childblk, child,
@@ -2217,14 +2285,13 @@ bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
 	 * parent/ancestor page) lacked a downlink is incidental.
 	 */
 	if (P_ISDELETED(copaque))
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg_internal("downlink to deleted leaf page found in index \"%s\"",
-								 RelationGetRelationName(state->rel)),
-				 errdetail_internal("Top parent/target block=%u leaf block=%u top parent/under check lsn=%X/%X.",
-									blkno, childblk,
-									(uint32) (pagelsn >> 32),
-									(uint32) pagelsn)));
+		econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+				 "downlink to deleted leaf page found in index \"%s\" "
+				 "(Top parent/target block=%u leaf block=%u top parent/under check lsn=%X/%X)",
+				 RelationGetRelationName(state->rel),
+				 blkno, childblk,
+				 (uint32) (pagelsn >> 32),
+				 (uint32) pagelsn);
 
 	/*
 	 * Iff leaf page is half-dead, its high key top parent link should point
@@ -2244,14 +2311,13 @@ bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit,
 			return;
 	}
 
-	ereport(ERROR,
-			(errcode(ERRCODE_INDEX_CORRUPTED),
-			 errmsg("internal index block lacks downlink in index \"%s\"",
-					RelationGetRelationName(state->rel)),
-			 errdetail_internal("Block=%u level=%u page lsn=%X/%X.",
-								blkno, opaque->btpo.level,
-								(uint32) (pagelsn >> 32),
-								(uint32) pagelsn)));
+	econfess(state->ctx, blkno, ERRCODE_INDEX_CORRUPTED,
+			 "internal index block lacks downlink in index \"%s\" "
+			 "(Block=%u level=%u page lsn=%X/%X)",
+			 RelationGetRelationName(state->rel),
+			 blkno, opaque->btpo.level,
+			 (uint32) (pagelsn >> 32),
+			 (uint32) pagelsn);
 }
 
 /*
@@ -2327,16 +2393,12 @@ bt_tuple_present_callback(Relation index, ItemPointer tid, Datum *values,
 	/* Probe Bloom filter -- tuple should be present */
 	if (bloom_lacks_element(state->filter, (unsigned char *) norm,
 							IndexTupleSize(norm)))
-		ereport(ERROR,
-				(errcode(ERRCODE_DATA_CORRUPTED),
-				 errmsg("heap tuple (%u,%u) from table \"%s\" lacks matching index tuple within index \"%s\"",
-						ItemPointerGetBlockNumber(&(itup->t_tid)),
-						ItemPointerGetOffsetNumber(&(itup->t_tid)),
-						RelationGetRelationName(state->heaprel),
-						RelationGetRelationName(state->rel)),
-				 !state->readonly
-				 ? errhint("Retrying verification using the function bt_index_parent_check() might provide a more specific error.")
-				 : 0));
+		econfess(state->ctx, ItemPointerGetBlockNumber(&(itup->t_tid)), ERRCODE_DATA_CORRUPTED,
+				 "heap tuple (%u,%u) from table \"%s\" lacks matching index tuple within index \"%s\"",
+				 ItemPointerGetBlockNumber(&(itup->t_tid)),
+				 ItemPointerGetOffsetNumber(&(itup->t_tid)),
+				 RelationGetRelationName(state->heaprel),
+				 RelationGetRelationName(state->rel));
 
 	state->heaptuplespresent++;
 	pfree(itup);
@@ -2395,7 +2457,7 @@ bt_normalize_tuple(BtreeCheckState *state, IndexTuple itup)
 	if (!IndexTupleHasVarwidths(itup))
 		return itup;
 
-	for (i = 0; i < tupleDescriptor->natts; i++)
+	for (i = 0; CONTINUE_CHECKING(state->ctx) && i < tupleDescriptor->natts; i++)
 	{
 		Form_pg_attribute att;
 
@@ -2415,12 +2477,11 @@ bt_normalize_tuple(BtreeCheckState *state, IndexTuple itup)
 		 * should never be encountered here
 		 */
 		if (VARATT_IS_EXTERNAL(DatumGetPointer(normalized[i])))
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("external varlena datum in tuple that references heap row (%u,%u) in index \"%s\"",
-							ItemPointerGetBlockNumber(&(itup->t_tid)),
-							ItemPointerGetOffsetNumber(&(itup->t_tid)),
-							RelationGetRelationName(state->rel))));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "external varlena datum in tuple that references heap row (%u,%u) in index \"%s\"",
+					 ItemPointerGetBlockNumber(&(itup->t_tid)),
+					 ItemPointerGetOffsetNumber(&(itup->t_tid)),
+					 RelationGetRelationName(state->rel));
 		else if (VARATT_IS_COMPRESSED(DatumGetPointer(normalized[i])))
 		{
 			formnewtup = true;
@@ -2780,7 +2841,7 @@ invariant_l_nontarget_offset(BtreeCheckState *state, BTScanInsert key,
  * misbehaves.
  */
 static Page
-palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum)
+palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum, BtreeCheckContext * ctx)
 {
 	Buffer		buffer;
 	Page		page;
@@ -2810,10 +2871,9 @@ palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum)
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 
 	if (P_ISMETA(opaque) && blocknum != BTREE_METAPAGE)
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("invalid meta page found at block %u in index \"%s\"",
-						blocknum, RelationGetRelationName(state->rel))));
+		econfess(state->ctx, blocknum, ERRCODE_INDEX_CORRUPTED,
+				 "invalid meta page found at block %u in index \"%s\"",
+				 blocknum, RelationGetRelationName(state->rel));
 
 	/* Check page from block that ought to be meta page */
 	if (blocknum == BTREE_METAPAGE)
@@ -2822,20 +2882,18 @@ palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum)
 
 		if (!P_ISMETA(opaque) ||
 			metad->btm_magic != BTREE_MAGIC)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("index \"%s\" meta page is corrupt",
-							RelationGetRelationName(state->rel))));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "index \"%s\" meta page is corrupt",
+					 RelationGetRelationName(state->rel));
 
 		if (metad->btm_version < BTREE_MIN_VERSION ||
 			metad->btm_version > BTREE_VERSION)
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("version mismatch in index \"%s\": file version %d, "
-							"current version %d, minimum supported version %d",
-							RelationGetRelationName(state->rel),
-							metad->btm_version, BTREE_VERSION,
-							BTREE_MIN_VERSION)));
+			econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+					 "version mismatch in index \"%s\": file version %d, "
+					 "current version %d, minimum supported version %d",
+					 RelationGetRelationName(state->rel),
+					 metad->btm_version, BTREE_VERSION,
+					 BTREE_MIN_VERSION);
 
 		/* Finished with metapage checks */
 		return page;
@@ -2846,17 +2904,15 @@ palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum)
 	 * page level
 	 */
 	if (P_ISLEAF(opaque) && !P_ISDELETED(opaque) && opaque->btpo.level != 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("invalid leaf page level %u for block %u in index \"%s\"",
-						opaque->btpo.level, blocknum, RelationGetRelationName(state->rel))));
+		econfess(state->ctx, InvalidBlockNumber, ERRCODE_INDEX_CORRUPTED,
+				 "invalid leaf page level %u for block %u in index \"%s\"",
+				 opaque->btpo.level, blocknum, RelationGetRelationName(state->rel));
 
 	if (!P_ISLEAF(opaque) && !P_ISDELETED(opaque) &&
 		opaque->btpo.level == 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("invalid internal page level 0 for block %u in index \"%s\"",
-						blocknum, RelationGetRelationName(state->rel))));
+		econfess(state->ctx, blocknum, ERRCODE_INDEX_CORRUPTED,
+				 "invalid internal page level 0 for block %u in index \"%s\"",
+				 blocknum, RelationGetRelationName(state->rel));
 
 	/*
 	 * Sanity checks for number of items on page.
@@ -2882,23 +2938,20 @@ palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum)
 	 */
 	maxoffset = PageGetMaxOffsetNumber(page);
 	if (maxoffset > MaxIndexTuplesPerPage)
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("Number of items on block %u of index \"%s\" exceeds MaxIndexTuplesPerPage (%u)",
-						blocknum, RelationGetRelationName(state->rel),
-						MaxIndexTuplesPerPage)));
+		econfess(ctx, blocknum, ERRCODE_INDEX_CORRUPTED,
+				 "Number of items on block %u of index \"%s\" exceeds MaxIndexTuplesPerPage (%u)",
+				 blocknum, RelationGetRelationName(state->rel),
+				 MaxIndexTuplesPerPage);
 
 	if (!P_ISLEAF(opaque) && !P_ISDELETED(opaque) && maxoffset < P_FIRSTDATAKEY(opaque))
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("internal block %u in index \"%s\" lacks high key and/or at least one downlink",
-						blocknum, RelationGetRelationName(state->rel))));
+		econfess(ctx, blocknum, ERRCODE_INDEX_CORRUPTED,
+				 "internal block %u in index \"%s\" lacks high key and/or at least one downlink",
+				 blocknum, RelationGetRelationName(state->rel));
 
 	if (P_ISLEAF(opaque) && !P_ISDELETED(opaque) && !P_RIGHTMOST(opaque) && maxoffset < P_HIKEY)
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("non-rightmost leaf block %u in index \"%s\" lacks high key item",
-						blocknum, RelationGetRelationName(state->rel))));
+		econfess(ctx, blocknum, ERRCODE_INDEX_CORRUPTED,
+				 "non-rightmost leaf block %u in index \"%s\" lacks high key item",
+				 blocknum, RelationGetRelationName(state->rel));
 
 	/*
 	 * In general, internal pages are never marked half-dead, except on
@@ -2910,17 +2963,15 @@ palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum)
 	 * Internal pages should never have garbage items, either.
 	 */
 	if (!P_ISLEAF(opaque) && P_ISHALFDEAD(opaque))
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("internal page block %u in index \"%s\" is half-dead",
-						blocknum, RelationGetRelationName(state->rel)),
-				 errhint("This can be caused by an interrupted VACUUM in version 9.3 or older, before upgrade. Please REINDEX it.")));
+		econfess(state->ctx, blocknum, ERRCODE_INDEX_CORRUPTED,
+				 "internal page block %u in index \"%s\" is half-dead "
+				 "(This can be caused by an interrupted VACUUM in version 9.3 or older, before upgrade. Please REINDEX it)",
+				 blocknum, RelationGetRelationName(state->rel));
 
 	if (!P_ISLEAF(opaque) && P_HAS_GARBAGE(opaque))
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("internal page block %u in index \"%s\" has garbage items",
-						blocknum, RelationGetRelationName(state->rel))));
+		econfess(state->ctx, blocknum, ERRCODE_INDEX_CORRUPTED,
+				 "internal page block %u in index \"%s\" has garbage items",
+				 blocknum, RelationGetRelationName(state->rel));
 
 	return page;
 }
@@ -2971,14 +3022,13 @@ PageGetItemIdCareful(BtreeCheckState *state, BlockNumber block, Page page,
 
 	if (ItemIdGetOffset(itemid) + ItemIdGetLength(itemid) >
 		BLCKSZ - sizeof(BTPageOpaqueData))
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("line pointer points past end of tuple space in index \"%s\"",
-						RelationGetRelationName(state->rel)),
-				 errdetail_internal("Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u.",
-									block, offset, ItemIdGetOffset(itemid),
-									ItemIdGetLength(itemid),
-									ItemIdGetFlags(itemid))));
+		econfess(state->ctx, block, ERRCODE_INDEX_CORRUPTED,
+				 "line pointer points past end of tuple space in index \"%s\" "
+				 "(Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u)",
+				 RelationGetRelationName(state->rel),
+				 block, offset, ItemIdGetOffset(itemid),
+				 ItemIdGetLength(itemid),
+				 ItemIdGetFlags(itemid));
 
 	/*
 	 * Verify that line pointer isn't LP_REDIRECT or LP_UNUSED, since nbtree
@@ -2987,14 +3037,13 @@ PageGetItemIdCareful(BtreeCheckState *state, BlockNumber block, Page page,
 	 */
 	if (ItemIdIsRedirected(itemid) || !ItemIdIsUsed(itemid) ||
 		ItemIdGetLength(itemid) == 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("invalid line pointer storage in index \"%s\"",
-						RelationGetRelationName(state->rel)),
-				 errdetail_internal("Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u.",
-									block, offset, ItemIdGetOffset(itemid),
-									ItemIdGetLength(itemid),
-									ItemIdGetFlags(itemid))));
+		econfess(state->ctx, block, ERRCODE_INDEX_CORRUPTED,
+				 "invalid line pointer storage in index \"%s\" "
+				 "(Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u)",
+				 RelationGetRelationName(state->rel),
+				 block, offset, ItemIdGetOffset(itemid),
+				 ItemIdGetLength(itemid),
+				 ItemIdGetFlags(itemid));
 
 	return itemid;
 }
@@ -3016,26 +3065,23 @@ BTreeTupleGetHeapTIDCareful(BtreeCheckState *state, IndexTuple itup,
 	 */
 	Assert(state->heapkeyspace);
 	if (BTreeTupleIsPivot(itup) && nonpivot)
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg_internal("block %u or its right sibling block or child block in index \"%s\" has unexpected pivot tuple",
-								 state->targetblock,
-								 RelationGetRelationName(state->rel))));
+		econfess(state->ctx, state->targetblock, ERRCODE_INDEX_CORRUPTED,
+				 "block %u or its right sibling block or child block in index \"%s\" has unexpected pivot tuple",
+				 state->targetblock,
+				 RelationGetRelationName(state->rel));
 
 	if (!BTreeTupleIsPivot(itup) && !nonpivot)
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg_internal("block %u or its right sibling block or child block in index \"%s\" has unexpected non-pivot tuple",
-								 state->targetblock,
-								 RelationGetRelationName(state->rel))));
+		econfess(state->ctx, state->targetblock, ERRCODE_INDEX_CORRUPTED,
+				 "block %u or its right sibling block or child block in index \"%s\" has unexpected non-pivot tuple",
+				 state->targetblock,
+				 RelationGetRelationName(state->rel));
 
 	htid = BTreeTupleGetHeapTID(itup);
 	if (!ItemPointerIsValid(htid) && nonpivot)
-		ereport(ERROR,
-				(errcode(ERRCODE_INDEX_CORRUPTED),
-				 errmsg("block %u or its right sibling block or child block in index \"%s\" contains non-pivot tuple that lacks a heap TID",
-						state->targetblock,
-						RelationGetRelationName(state->rel))));
+		econfess(state->ctx, state->targetblock, ERRCODE_INDEX_CORRUPTED,
+				 "block %u or its right sibling block or child block in index \"%s\" contains non-pivot tuple that lacks a heap TID",
+				 state->targetblock,
+				 RelationGetRelationName(state->rel));
 
 	return htid;
 }
@@ -3066,3 +3112,53 @@ BTreeTupleGetPointsToTID(IndexTuple itup)
 	/* Pivot tuple returns TID with downlink block (heapkeyspace variant) */
 	return &itup->t_tid;
 }
+
+/*
+ * Helper function to construct the TupleDesc needed by verify_heapam.
+ */
+static TupleDesc
+verify_btreeam_tupdesc(void)
+{
+	TupleDesc	tupdesc;
+	AttrNumber	a = 0;
+
+	tupdesc = CreateTemplateTupleDesc(BTREECHECK_RELATION_COLS);
+	TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, ++a, "msg", TEXTOID, -1, 0);
+	Assert(a == BTREECHECK_RELATION_COLS);
+
+	return BlessTupleDesc(tupdesc);
+}
+
+/*
+ * confess
+ *
+ *   Return a message about index corruption
+ *
+ *   The msg argument is pfree'd by this function.
+ */
+static void
+confess(BtreeCheckContext * ctx, BlockNumber blkno, char *msg)
+{
+	Datum		values[BTREECHECK_RELATION_COLS];
+	bool		nulls[BTREECHECK_RELATION_COLS];
+	HeapTuple	tuple;
+
+	MemSet(values, 0, sizeof(values));
+	MemSet(nulls, 0, sizeof(nulls));
+	values[0] = Int64GetDatum(blkno);
+	nulls[0] = (blkno == InvalidBlockNumber);
+	values[1] = CStringGetTextDatum(msg);
+
+	/*
+	 * In principle, there is nothing to prevent a scan over a large, highly
+	 * corrupted table from using workmem worth of memory building up the
+	 * tuplestore.  That is OK, but leaves no room for leaking all the msg
+	 * arguments that are allocated during the scan.
+	 */
+	pfree(msg);
+
+	tuple = heap_form_tuple(ctx->tupdesc, values, nulls);
+	tuplestore_puttuple(ctx->tupstore, tuple);
+	ctx->is_corrupt = true;
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7eaaad1e14..467120f1d0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -275,6 +275,7 @@ BrinSpecialSpace
 BrinStatsData
 BrinTuple
 BrinValues
+BtreeCheckContext
 BtreeCheckState
 BtreeLevel
 Bucket
-- 
2.21.1 (Apple Git-122.3)

