v25-0004-Add-GiST-support-to-pg_amcheck.patch

application/octet-stream

Filename: v25-0004-Add-GiST-support-to-pg_amcheck.patch
Type: application/octet-stream
Part: 1
Message: Re: Amcheck verification of GiST and GIN

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v25-0004
Subject: Add GiST support to pg_amcheck
File+
src/bin/pg_amcheck/pg_amcheck.c 122 83
src/bin/pg_amcheck/t/002_nonesuch.pl 4 4
src/bin/pg_amcheck/t/003_check.pl 31 17
From 1323d72f29adbc78a5ea3ff66b956b6c7272cbd5 Mon Sep 17 00:00:00 2001
From: "Andrey M. Borodin" <x4mmm@flight.local>
Date: Sun, 5 Feb 2023 15:52:14 -0800
Subject: [PATCH v25 4/4] Add GiST support to pg_amcheck

---
 src/bin/pg_amcheck/pg_amcheck.c      | 205 ++++++++++++++++-----------
 src/bin/pg_amcheck/t/002_nonesuch.pl |   8 +-
 src/bin/pg_amcheck/t/003_check.pl    |  48 ++++---
 3 files changed, 157 insertions(+), 104 deletions(-)

diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 68f8180c19..337399539d 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -39,8 +39,7 @@ typedef struct PatternInfo
 								 * NULL */
 	bool		heap_only;		/* true if rel_regex should only match heap
 								 * tables */
-	bool		btree_only;		/* true if rel_regex should only match btree
-								 * indexes */
+	bool		index_only;		/* true if rel_regex should only match indexes */
 	bool		matched;		/* true if the pattern matched in any database */
 } PatternInfo;
 
@@ -74,7 +73,7 @@ typedef struct AmcheckOptions
 
 	/*
 	 * As an optimization, if any pattern in the exclude list applies to heap
-	 * tables, or similarly if any such pattern applies to btree indexes, or
+	 * tables, or similarly if any such pattern applies to indexes, or
 	 * to schemas, then these will be true, otherwise false.  These should
 	 * always agree with what you'd conclude by grep'ing through the exclude
 	 * list.
@@ -98,13 +97,13 @@ typedef struct AmcheckOptions
 	int64		endblock;
 	const char *skip;
 
-	/* btree index checking options */
+	/* index checking options */
 	bool		parent_check;
 	bool		rootdescend;
 	bool		heapallindexed;
 
-	/* heap and btree hybrid option */
-	bool		no_btree_expansion;
+	/* heap and indexes hybrid option */
+	bool		no_index_expansion;
 } AmcheckOptions;
 
 static AmcheckOptions opts = {
@@ -132,7 +131,7 @@ static AmcheckOptions opts = {
 	.parent_check = false,
 	.rootdescend = false,
 	.heapallindexed = false,
-	.no_btree_expansion = false
+	.no_index_expansion = false
 };
 
 static const char *progname = NULL;
@@ -154,7 +153,8 @@ typedef struct RelationInfo
 {
 	const DatabaseInfo *datinfo;	/* shared by other relinfos */
 	Oid			reloid;
-	bool		is_heap;		/* true if heap, false if btree */
+	Oid			amoid;
+	bool		is_heap;		/* true if heap, false if index */
 	char	   *nspname;
 	char	   *relname;
 	int			relpages;
@@ -175,10 +175,12 @@ static void prepare_heap_command(PQExpBuffer sql, RelationInfo *rel,
 								 PGconn *conn);
 static void prepare_btree_command(PQExpBuffer sql, RelationInfo *rel,
 								  PGconn *conn);
+static void prepare_gist_command(PQExpBuffer sql, RelationInfo *rel,
+								  PGconn *conn);
 static void run_command(ParallelSlot *slot, const char *sql);
 static bool verify_heap_slot_handler(PGresult *res, PGconn *conn,
 									 void *context);
-static bool verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context);
+static bool verify_index_slot_handler(PGresult *res, PGconn *conn, void *context);
 static void help(const char *progname);
 static void progress_report(uint64 relations_total, uint64 relations_checked,
 							uint64 relpages_total, uint64 relpages_checked,
@@ -192,7 +194,7 @@ static void append_relation_pattern(PatternInfoArray *pia, const char *pattern,
 									int encoding);
 static void append_heap_pattern(PatternInfoArray *pia, const char *pattern,
 								int encoding);
-static void append_btree_pattern(PatternInfoArray *pia, const char *pattern,
+static void append_index_pattern(PatternInfoArray *pia, const char *pattern,
 								 int encoding);
 static void compile_database_list(PGconn *conn, SimplePtrList *databases,
 								  const char *initial_dbname);
@@ -318,11 +320,11 @@ main(int argc, char *argv[])
 				break;
 			case 'i':
 				opts.allrel = false;
-				append_btree_pattern(&opts.include, optarg, encoding);
+				append_index_pattern(&opts.include, optarg, encoding);
 				break;
 			case 'I':
 				opts.excludeidx = true;
-				append_btree_pattern(&opts.exclude, optarg, encoding);
+				append_index_pattern(&opts.exclude, optarg, encoding);
 				break;
 			case 'j':
 				if (!option_parse_int(optarg, "-j/--jobs", 1, INT_MAX,
@@ -377,7 +379,7 @@ main(int argc, char *argv[])
 				maintenance_db = pg_strdup(optarg);
 				break;
 			case 2:
-				opts.no_btree_expansion = true;
+				opts.no_index_expansion = true;
 				break;
 			case 3:
 				opts.no_toast_expansion = true;
@@ -609,8 +611,8 @@ main(int argc, char *argv[])
 			if (pat->heap_only)
 				log_no_match("no heap tables to check matching \"%s\"",
 							 pat->pattern);
-			else if (pat->btree_only)
-				log_no_match("no btree indexes to check matching \"%s\"",
+			else if (pat->index_only)
+				log_no_match("no indexes to check matching \"%s\"",
 							 pat->pattern);
 			else if (pat->rel_regex == NULL)
 				log_no_match("no relations to check in schemas matching \"%s\"",
@@ -743,13 +745,20 @@ main(int argc, char *argv[])
 				if (opts.show_progress && progress_since_last_stderr)
 					fprintf(stderr, "\n");
 
-				pg_log_info("checking btree index \"%s.%s.%s\"",
+				pg_log_info("checking index \"%s.%s.%s\"",
 							rel->datinfo->datname, rel->nspname, rel->relname);
 				progress_since_last_stderr = false;
 			}
-			prepare_btree_command(&sql, rel, free_slot->connection);
+			if (rel->amoid == BTREE_AM_OID)
+				prepare_btree_command(&sql, rel, free_slot->connection);
+			else if (rel->amoid == GIST_AM_OID)
+				prepare_gist_command(&sql, rel, free_slot->connection);
+			else
+				/* should not happen at this stage */
+				pg_log_info("Verification of index type %u not supported",
+							rel->amoid);
 			rel->sql = pstrdup(sql.data);	/* pg_free'd after command */
-			ParallelSlotSetHandler(free_slot, verify_btree_slot_handler, rel);
+			ParallelSlotSetHandler(free_slot, verify_index_slot_handler, rel);
 			run_command(free_slot, rel->sql);
 		}
 	}
@@ -827,7 +836,7 @@ prepare_heap_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
  * Creates a SQL command for running amcheck checking on the given btree index
  * relation.  The command does not select any columns, as btree checking
  * functions do not return any, but rather return corruption information by
- * raising errors, which verify_btree_slot_handler expects.
+ * raising errors, which verify_index_slot_handler expects.
  *
  * The constructed SQL command will silently skip temporary indexes, and
  * indexes being reindexed concurrently, as checking them would needlessly draw
@@ -869,6 +878,28 @@ prepare_btree_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
 						  rel->reloid);
 }
 
+/*
+ * prepare_gist_command
+ * Similar to btree equivalent prepares command to check GiST index.
+ */
+static void
+prepare_gist_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
+{
+	resetPQExpBuffer(sql);
+
+	appendPQExpBuffer(sql,
+						"SELECT %s.gist_index_check("
+						"index := c.oid, heapallindexed := %s)"
+						"\nFROM pg_catalog.pg_class c, pg_catalog.pg_index i "
+						"WHERE c.oid = %u "
+						"AND c.oid = i.indexrelid "
+						"AND c.relpersistence != 't' "
+						"AND i.indisready AND i.indisvalid AND i.indislive",
+						rel->datinfo->amcheck_schema,
+						(opts.heapallindexed ? "true" : "false"),
+						rel->reloid);
+}
+
 /*
  * run_command
  *
@@ -908,7 +939,7 @@ run_command(ParallelSlot *slot, const char *sql)
  * Note: Heap relation corruption is reported by verify_heapam() via the result
  * set, rather than an ERROR, but running verify_heapam() on a corrupted heap
  * table may still result in an error being returned from the server due to
- * missing relation files, bad checksums, etc.  The btree corruption checking
+ * missing relation files, bad checksums, etc.  The corruption checking
  * functions always use errors to communicate corruption messages.  We can't
  * just abort processing because we got a mere ERROR.
  *
@@ -1057,11 +1088,11 @@ verify_heap_slot_handler(PGresult *res, PGconn *conn, void *context)
 }
 
 /*
- * verify_btree_slot_handler
+ * verify_index_slot_handler
  *
- * ParallelSlotHandler that receives results from a btree checking command
- * created by prepare_btree_command and outputs them for the user.  The results
- * from the btree checking command is assumed to be empty, but when the results
+ * ParallelSlotHandler that receives results from a checking command created by
+ * prepare_[btree,gist]_command and outputs them for the user.  The results
+ * from the checking command is assumed to be empty, but when the results
  * are an error code, the useful information about the corruption is expected
  * in the connection's error message.
  *
@@ -1070,7 +1101,7 @@ verify_heap_slot_handler(PGresult *res, PGconn *conn, void *context)
  * context: unused
  */
 static bool
-verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context)
+verify_index_slot_handler(PGresult *res, PGconn *conn, void *context)
 {
 	RelationInfo *rel = (RelationInfo *) context;
 
@@ -1081,7 +1112,7 @@ verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context)
 		if (ntups > 1)
 		{
 			/*
-			 * We expect the btree checking functions to return one void row
+			 * We expect the checking functions to return one void row
 			 * each, or zero rows if the check was skipped due to the object
 			 * being in the wrong state to be checked, so we should output
 			 * some sort of warning if we get anything more, not because it
@@ -1096,7 +1127,7 @@ verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context)
 			 */
 			if (opts.show_progress && progress_since_last_stderr)
 				fprintf(stderr, "\n");
-			pg_log_warning("btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d",
+			pg_log_warning("index \"%s.%s.%s\": checking function returned unexpected number of rows: %d",
 						   rel->datinfo->datname, rel->nspname, rel->relname, ntups);
 			if (opts.verbose)
 				pg_log_warning_detail("Query was: %s", rel->sql);
@@ -1110,7 +1141,7 @@ verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context)
 		char	   *msg = indent_lines(PQerrorMessage(conn));
 
 		all_checks_pass = false;
-		printf(_("btree index \"%s.%s.%s\":\n"),
+		printf(_("index \"%s.%s.%s\":\n"),
 			   rel->datinfo->datname, rel->nspname, rel->relname);
 		printf("%s", msg);
 		if (opts.verbose)
@@ -1163,6 +1194,8 @@ help(const char *progname)
 	printf(_("      --heapallindexed            check that all heap tuples are found within indexes\n"));
 	printf(_("      --parent-check              check index parent/child relationships\n"));
 	printf(_("      --rootdescend               search from root page to refind tuples\n"));
+	printf(_("\nGiST index checking options:\n"));
+	printf(_("      --heapallindexed            check that all heap tuples are found within indexes\n"));
 	printf(_("\nConnection options:\n"));
 	printf(_("  -h, --host=HOSTNAME             database server host or socket directory\n"));
 	printf(_("  -p, --port=PORT                 database server port\n"));
@@ -1376,11 +1409,11 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
  * pattern: the relation name pattern
  * encoding: client encoding for parsing the pattern
  * heap_only: whether the pattern should only be matched against heap tables
- * btree_only: whether the pattern should only be matched against btree indexes
+ * index_only: whether the pattern should only be matched against indexes
  */
 static void
 append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
-							   int encoding, bool heap_only, bool btree_only)
+							   int encoding, bool heap_only, bool index_only)
 {
 	PQExpBufferData dbbuf;
 	PQExpBufferData nspbuf;
@@ -1415,14 +1448,14 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
 	termPQExpBuffer(&relbuf);
 
 	info->heap_only = heap_only;
-	info->btree_only = btree_only;
+	info->index_only = index_only;
 }
 
 /*
  * append_relation_pattern
  *
  * Adds the given pattern interpreted as a relation pattern, to be matched
- * against both heap tables and btree indexes.
+ * against both heap tables and indexes.
  *
  * pia: the pattern info array to be appended
  * pattern: the relation name pattern
@@ -1451,17 +1484,17 @@ append_heap_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
 }
 
 /*
- * append_btree_pattern
+ * append_index_pattern
  *
  * Adds the given pattern interpreted as a relation pattern, to be matched only
- * against btree indexes.
+ * against indexes.
  *
  * pia: the pattern info array to be appended
  * pattern: the relation name pattern
  * encoding: client encoding for parsing the pattern
  */
 static void
-append_btree_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+append_index_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
 {
 	append_relation_pattern_helper(pia, pattern, encoding, false, true);
 }
@@ -1719,7 +1752,7 @@ compile_database_list(PGconn *conn, SimplePtrList *databases,
  *     rel_regex: the relname regexp parsed from the pattern, or NULL if the
  *                pattern had no relname part
  *     heap_only: true if the pattern applies only to heap tables (not indexes)
- *     btree_only: true if the pattern applies only to btree indexes (not tables)
+ *     index_only: true if the pattern applies only to indexes (not tables)
  *
  * buf: the buffer to be appended
  * patterns: the array of patterns to be inserted into the CTE
@@ -1761,7 +1794,7 @@ append_rel_pattern_raw_cte(PQExpBuffer buf, const PatternInfoArray *pia,
 			appendPQExpBufferStr(buf, "::TEXT, true::BOOLEAN");
 		else
 			appendPQExpBufferStr(buf, "::TEXT, false::BOOLEAN");
-		if (info->btree_only)
+		if (info->index_only)
 			appendPQExpBufferStr(buf, ", true::BOOLEAN");
 		else
 			appendPQExpBufferStr(buf, ", false::BOOLEAN");
@@ -1799,8 +1832,8 @@ append_rel_pattern_filtered_cte(PQExpBuffer buf, const char *raw,
 								const char *filtered, PGconn *conn)
 {
 	appendPQExpBuffer(buf,
-					  "\n%s (pattern_id, nsp_regex, rel_regex, heap_only, btree_only) AS ("
-					  "\nSELECT pattern_id, nsp_regex, rel_regex, heap_only, btree_only "
+					  "\n%s (pattern_id, nsp_regex, rel_regex, heap_only, index_only) AS ("
+					  "\nSELECT pattern_id, nsp_regex, rel_regex, heap_only, index_only "
 					  "FROM %s r"
 					  "\nWHERE (r.db_regex IS NULL "
 					  "OR ",
@@ -1823,7 +1856,7 @@ append_rel_pattern_filtered_cte(PQExpBuffer buf, const char *raw,
  * The cells of the constructed list contain all information about the relation
  * necessary to connect to the database and check the object, including which
  * database to connect to, where contrib/amcheck is installed, and the Oid and
- * type of object (heap table vs. btree index).  Rather than duplicating the
+ * type of object (heap table vs. index).  Rather than duplicating the
  * database details per relation, the relation structs use references to the
  * same database object, provided by the caller.
  *
@@ -1850,7 +1883,7 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 	if (!opts.allrel)
 	{
 		appendPQExpBufferStr(&sql,
-							 " include_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, btree_only) AS (");
+							 " include_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, index_only) AS (");
 		append_rel_pattern_raw_cte(&sql, &opts.include, conn);
 		appendPQExpBufferStr(&sql, "\n),");
 		append_rel_pattern_filtered_cte(&sql, "include_raw", "include_pat", conn);
@@ -1860,7 +1893,7 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 	if (opts.excludetbl || opts.excludeidx || opts.excludensp)
 	{
 		appendPQExpBufferStr(&sql,
-							 " exclude_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, btree_only) AS (");
+							 " exclude_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, index_only) AS (");
 		append_rel_pattern_raw_cte(&sql, &opts.exclude, conn);
 		appendPQExpBufferStr(&sql, "\n),");
 		append_rel_pattern_filtered_cte(&sql, "exclude_raw", "exclude_pat", conn);
@@ -1868,36 +1901,36 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 
 	/* Append the relation CTE. */
 	appendPQExpBufferStr(&sql,
-						 " relation (pattern_id, oid, nspname, relname, reltoastrelid, relpages, is_heap, is_btree) AS ("
+						 " relation (pattern_id, oid, amoid, nspname, relname, reltoastrelid, relpages, is_heap, is_index) AS ("
 						 "\nSELECT DISTINCT ON (c.oid");
 	if (!opts.allrel)
 		appendPQExpBufferStr(&sql, ", ip.pattern_id) ip.pattern_id,");
 	else
 		appendPQExpBufferStr(&sql, ") NULL::INTEGER AS pattern_id,");
 	appendPQExpBuffer(&sql,
-					  "\nc.oid, n.nspname, c.relname, c.reltoastrelid, c.relpages, "
-					  "c.relam = %u AS is_heap, "
-					  "c.relam = %u AS is_btree"
+					  "\nc.oid, c.relam as amoid, n.nspname, c.relname, "
+					  "c.reltoastrelid, c.relpages, c.relam = %u AS is_heap, "
+					  "(c.relam = %u OR c.relam = %u) AS is_index"
 					  "\nFROM pg_catalog.pg_class c "
 					  "INNER JOIN pg_catalog.pg_namespace n "
 					  "ON c.relnamespace = n.oid",
-					  HEAP_TABLE_AM_OID, BTREE_AM_OID);
+					  HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID);
 	if (!opts.allrel)
 		appendPQExpBuffer(&sql,
 						  "\nINNER JOIN include_pat ip"
 						  "\nON (n.nspname ~ ip.nsp_regex OR ip.nsp_regex IS NULL)"
 						  "\nAND (c.relname ~ ip.rel_regex OR ip.rel_regex IS NULL)"
 						  "\nAND (c.relam = %u OR NOT ip.heap_only)"
-						  "\nAND (c.relam = %u OR NOT ip.btree_only)",
-						  HEAP_TABLE_AM_OID, BTREE_AM_OID);
+						  "\nAND ((c.relam = %u OR c.relam = %u) OR NOT ip.index_only)",
+						  HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID);
 	if (opts.excludetbl || opts.excludeidx || opts.excludensp)
 		appendPQExpBuffer(&sql,
 						  "\nLEFT OUTER JOIN exclude_pat ep"
 						  "\nON (n.nspname ~ ep.nsp_regex OR ep.nsp_regex IS NULL)"
 						  "\nAND (c.relname ~ ep.rel_regex OR ep.rel_regex IS NULL)"
 						  "\nAND (c.relam = %u OR NOT ep.heap_only OR ep.rel_regex IS NULL)"
-						  "\nAND (c.relam = %u OR NOT ep.btree_only OR ep.rel_regex IS NULL)",
-						  HEAP_TABLE_AM_OID, BTREE_AM_OID);
+						  "\nAND ((c.relam = %u OR c.relam = %u) OR NOT ep.index_only OR ep.rel_regex IS NULL)",
+						  HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID);
 
 	/*
 	 * Exclude temporary tables and indexes, which must necessarily belong to
@@ -1931,12 +1964,12 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 						  HEAP_TABLE_AM_OID, PG_TOAST_NAMESPACE);
 	else
 		appendPQExpBuffer(&sql,
-						  " AND c.relam IN (%u, %u)"
+						  " AND c.relam IN (%u, %u, %u)"
 						  "AND c.relkind IN ('r', 'S', 'm', 't', 'i') "
 						  "AND ((c.relam = %u AND c.relkind IN ('r', 'S', 'm', 't')) OR "
-						  "(c.relam = %u AND c.relkind = 'i'))",
-						  HEAP_TABLE_AM_OID, BTREE_AM_OID,
-						  HEAP_TABLE_AM_OID, BTREE_AM_OID);
+						  "((c.relam = %u OR c.relam = %u) AND c.relkind = 'i'))",
+						  HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID,
+						  HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID);
 
 	appendPQExpBufferStr(&sql,
 						 "\nORDER BY c.oid)");
@@ -1965,17 +1998,18 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 		appendPQExpBufferStr(&sql,
 							 "\n)");
 	}
-	if (!opts.no_btree_expansion)
+	if (!opts.no_index_expansion)
 	{
 		/*
 		 * Include a CTE for btree indexes associated with primary heap tables
 		 * selected above, filtering by exclusion patterns (if any) that match
-		 * btree index names.
+		 * btree index names. Currently, only btree indexes can be PK, but this
+		 * might chance in future.
 		 */
 		appendPQExpBufferStr(&sql,
-							 ", index (oid, nspname, relname, relpages) AS ("
-							 "\nSELECT c.oid, r.nspname, c.relname, c.relpages "
-							 "FROM relation r"
+							 ", index (oid, amoid, nspname, relname, relpages) AS ("
+							 "\nSELECT c.oid, c.relam as amoid, r.nspname, "
+							 "c.relname, c.relpages FROM relation r"
 							 "\nINNER JOIN pg_catalog.pg_index i "
 							 "ON r.oid = i.indrelid "
 							 "INNER JOIN pg_catalog.pg_class c "
@@ -1988,7 +2022,7 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 								 "\nLEFT OUTER JOIN exclude_pat ep "
 								 "ON (n.nspname ~ ep.nsp_regex OR ep.nsp_regex IS NULL) "
 								 "AND (c.relname ~ ep.rel_regex OR ep.rel_regex IS NULL) "
-								 "AND ep.btree_only"
+								 "AND ep.index_only"
 								 "\nWHERE ep.pattern_id IS NULL");
 		else
 			appendPQExpBufferStr(&sql,
@@ -1996,7 +2030,7 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 		appendPQExpBuffer(&sql,
 						  " AND c.relam = %u "
 						  "AND c.relkind = 'i'",
-						  BTREE_AM_OID);
+						  BTREE_AM_OID); /* Do not expect other AMs here */
 		if (opts.no_toast_expansion)
 			appendPQExpBuffer(&sql,
 							  " AND c.relnamespace != %u",
@@ -2004,7 +2038,7 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 		appendPQExpBufferStr(&sql, "\n)");
 	}
 
-	if (!opts.no_toast_expansion && !opts.no_btree_expansion)
+	if (!opts.no_toast_expansion && !opts.no_index_expansion)
 	{
 		/*
 		 * Include a CTE for btree indexes associated with toast tables of
@@ -2025,13 +2059,13 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 								 "\nLEFT OUTER JOIN exclude_pat ep "
 								 "ON ('pg_toast' ~ ep.nsp_regex OR ep.nsp_regex IS NULL) "
 								 "AND (c.relname ~ ep.rel_regex OR ep.rel_regex IS NULL) "
-								 "AND ep.btree_only "
+								 "AND ep.index_only "
 								 "WHERE ep.pattern_id IS NULL");
 		else
 			appendPQExpBufferStr(&sql,
 								 "\nWHERE true");
 		appendPQExpBuffer(&sql,
-						  " AND c.relam = %u"
+						  " AND c.relam = %u "
 						  " AND c.relkind = 'i')",
 						  BTREE_AM_OID);
 	}
@@ -2045,12 +2079,13 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 	 * list.
 	 */
 	appendPQExpBufferStr(&sql,
-						 "\nSELECT pattern_id, is_heap, is_btree, oid, nspname, relname, relpages "
+						 "\nSELECT pattern_id, is_heap, is_index, oid, amoid, nspname, relname, relpages "
 						 "FROM (");
 	appendPQExpBufferStr(&sql,
 	/* Inclusion patterns that failed to match */
-						 "\nSELECT pattern_id, is_heap, is_btree, "
+						 "\nSELECT pattern_id, is_heap, is_index, "
 						 "NULL::OID AS oid, "
+						 "NULL::OID AS amoid, "
 						 "NULL::TEXT AS nspname, "
 						 "NULL::TEXT AS relname, "
 						 "NULL::INTEGER AS relpages"
@@ -2059,29 +2094,29 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 						 "UNION"
 	/* Primary relations */
 						 "\nSELECT NULL::INTEGER AS pattern_id, "
-						 "is_heap, is_btree, oid, nspname, relname, relpages "
+						 "is_heap, is_index, oid, amoid, nspname, relname, relpages "
 						 "FROM relation");
 	if (!opts.no_toast_expansion)
-		appendPQExpBufferStr(&sql,
+		appendPQExpBuffer(&sql,
 							 " UNION"
 		/* Toast tables for primary relations */
 							 "\nSELECT NULL::INTEGER AS pattern_id, TRUE AS is_heap, "
-							 "FALSE AS is_btree, oid, nspname, relname, relpages "
+							 "FALSE AS is_index, oid, 0 as amoid, nspname, relname, relpages "
 							 "FROM toast");
-	if (!opts.no_btree_expansion)
+	if (!opts.no_index_expansion)
 		appendPQExpBufferStr(&sql,
 							 " UNION"
 		/* Indexes for primary relations */
 							 "\nSELECT NULL::INTEGER AS pattern_id, FALSE AS is_heap, "
-							 "TRUE AS is_btree, oid, nspname, relname, relpages "
+							 "TRUE AS is_index, oid, amoid, nspname, relname, relpages "
 							 "FROM index");
-	if (!opts.no_toast_expansion && !opts.no_btree_expansion)
-		appendPQExpBufferStr(&sql,
+	if (!opts.no_toast_expansion && !opts.no_index_expansion)
+		appendPQExpBuffer(&sql,
 							 " UNION"
 		/* Indexes for toast relations */
 							 "\nSELECT NULL::INTEGER AS pattern_id, FALSE AS is_heap, "
-							 "TRUE AS is_btree, oid, nspname, relname, relpages "
-							 "FROM toast_index");
+							 "TRUE AS is_index, oid, %u as amoid, nspname, relname, relpages "
+							 "FROM toast_index", BTREE_AM_OID);
 	appendPQExpBufferStr(&sql,
 						 "\n) AS combined_records "
 						 "ORDER BY relpages DESC NULLS FIRST, oid");
@@ -2101,8 +2136,9 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 	{
 		int			pattern_id = -1;
 		bool		is_heap = false;
-		bool		is_btree PG_USED_FOR_ASSERTS_ONLY = false;
+		bool		is_index PG_USED_FOR_ASSERTS_ONLY = false;
 		Oid			oid = InvalidOid;
+		Oid			amoid = InvalidOid;
 		const char *nspname = NULL;
 		const char *relname = NULL;
 		int			relpages = 0;
@@ -2112,15 +2148,17 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 		if (!PQgetisnull(res, i, 1))
 			is_heap = (PQgetvalue(res, i, 1)[0] == 't');
 		if (!PQgetisnull(res, i, 2))
-			is_btree = (PQgetvalue(res, i, 2)[0] == 't');
+			is_index = (PQgetvalue(res, i, 2)[0] == 't');
 		if (!PQgetisnull(res, i, 3))
 			oid = atooid(PQgetvalue(res, i, 3));
 		if (!PQgetisnull(res, i, 4))
-			nspname = PQgetvalue(res, i, 4);
+			amoid = atooid(PQgetvalue(res, i, 4));
 		if (!PQgetisnull(res, i, 5))
-			relname = PQgetvalue(res, i, 5);
+			nspname = PQgetvalue(res, i, 5);
 		if (!PQgetisnull(res, i, 6))
-			relpages = atoi(PQgetvalue(res, i, 6));
+			relname = PQgetvalue(res, i, 6);
+		if (!PQgetisnull(res, i, 7))
+			relpages = atoi(PQgetvalue(res, i, 7));
 
 		if (pattern_id >= 0)
 		{
@@ -2142,10 +2180,11 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 			RelationInfo *rel = (RelationInfo *) pg_malloc0(sizeof(RelationInfo));
 
 			Assert(OidIsValid(oid));
-			Assert((is_heap && !is_btree) || (is_btree && !is_heap));
+			Assert((is_heap && !is_index) || (is_index && !is_heap));
 
 			rel->datinfo = dat;
 			rel->reloid = oid;
+			rel->amoid = amoid;
 			rel->is_heap = is_heap;
 			rel->nspname = pstrdup(nspname);
 			rel->relname = pstrdup(relname);
@@ -2155,7 +2194,7 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 			{
 				/*
 				 * We apply --startblock and --endblock to heap tables, but
-				 * not btree indexes, and for progress purposes we need to
+				 * not supported indexes, and for progress purposes we need to
 				 * track how many blocks we expect to check.
 				 */
 				if (opts.endblock >= 0 && rel->blocks_to_check > opts.endblock)
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 58be2c694d..5e8a63a844 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -272,8 +272,8 @@ $node->command_checks_all(
 	[
 		qr/pg_amcheck: warning: no heap tables to check matching "no_such_table"/,
 		qr/pg_amcheck: warning: no heap tables to check matching "no\*such\*table"/,
-		qr/pg_amcheck: warning: no btree indexes to check matching "no_such_index"/,
-		qr/pg_amcheck: warning: no btree indexes to check matching "no\*such\*index"/,
+		qr/pg_amcheck: warning: no indexes to check matching "no_such_index"/,
+		qr/pg_amcheck: warning: no indexes to check matching "no\*such\*index"/,
 		qr/pg_amcheck: warning: no relations to check matching "no_such_relation"/,
 		qr/pg_amcheck: warning: no relations to check matching "no\*such\*relation"/,
 		qr/pg_amcheck: warning: no heap tables to check matching "no\*such\*table"/,
@@ -319,8 +319,8 @@ $node->command_checks_all(
 		qr/pg_amcheck: warning: no heap tables to check matching "template1\.public\.foo"/,
 		qr/pg_amcheck: warning: no heap tables to check matching "another_db\.public\.foo"/,
 		qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database\.public\.foo"/,
-		qr/pg_amcheck: warning: no btree indexes to check matching "template1\.public\.foo_idx"/,
-		qr/pg_amcheck: warning: no btree indexes to check matching "another_db\.public\.foo_idx"/,
+		qr/pg_amcheck: warning: no indexes to check matching "template1\.public\.foo_idx"/,
+		qr/pg_amcheck: warning: no indexes to check matching "another_db\.public\.foo_idx"/,
 		qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database\.public\.foo_idx"/,
 		qr/pg_amcheck: error: no relations to check/,
 	],
diff --git a/src/bin/pg_amcheck/t/003_check.pl b/src/bin/pg_amcheck/t/003_check.pl
index 359abe25a1..8b6326dd3d 100644
--- a/src/bin/pg_amcheck/t/003_check.pl
+++ b/src/bin/pg_amcheck/t/003_check.pl
@@ -185,7 +185,7 @@ for my $dbname (qw(db1 db2 db3))
 	# schemas.  The schemas are all identical to start, but
 	# we will corrupt them differently later.
 	#
-	for my $schema (qw(s1 s2 s3 s4 s5))
+	for my $schema (qw(s1 s2 s3 s4 s5 s6))
 	{
 		$node->safe_psql(
 			$dbname, qq(
@@ -288,22 +288,24 @@ plan_to_corrupt_first_page('db1', 's3.t2_btree');
 # Corrupt toast table, partitions, and materialized views in schema "s4"
 plan_to_remove_toast_file('db1', 's4.t2');
 
-# Corrupt all other object types in schema "s5".  We don't have amcheck support
+# Corrupt GiST index in schema "s5"
+plan_to_remove_relation_file('db1', 's5.t1_gist');
+plan_to_corrupt_first_page('db1', 's5.t2_gist');
+
+# Corrupt all other object types in schema "s6".  We don't have amcheck support
 # for these types, but we check that their corruption does not trigger any
 # errors in pg_amcheck
-plan_to_remove_relation_file('db1', 's5.seq1');
-plan_to_remove_relation_file('db1', 's5.t1_hash');
-plan_to_remove_relation_file('db1', 's5.t1_gist');
-plan_to_remove_relation_file('db1', 's5.t1_gin');
-plan_to_remove_relation_file('db1', 's5.t1_brin');
-plan_to_remove_relation_file('db1', 's5.t1_spgist');
+plan_to_remove_relation_file('db1', 's6.seq1');
+plan_to_remove_relation_file('db1', 's6.t1_hash');
+plan_to_remove_relation_file('db1', 's6.t1_gin');
+plan_to_remove_relation_file('db1', 's6.t1_brin');
+plan_to_remove_relation_file('db1', 's6.t1_spgist');
 
-plan_to_corrupt_first_page('db1', 's5.seq2');
-plan_to_corrupt_first_page('db1', 's5.t2_hash');
-plan_to_corrupt_first_page('db1', 's5.t2_gist');
-plan_to_corrupt_first_page('db1', 's5.t2_gin');
-plan_to_corrupt_first_page('db1', 's5.t2_brin');
-plan_to_corrupt_first_page('db1', 's5.t2_spgist');
+plan_to_corrupt_first_page('db1', 's6.seq2');
+plan_to_corrupt_first_page('db1', 's6.t2_hash');
+plan_to_corrupt_first_page('db1', 's6.t2_gin');
+plan_to_corrupt_first_page('db1', 's6.t2_brin');
+plan_to_corrupt_first_page('db1', 's6.t2_spgist');
 
 
 # Database 'db2' corruptions
@@ -434,10 +436,22 @@ $node->command_checks_all(
 	[$no_output_re],
 	'pg_amcheck in schema s4 excluding toast reports no corruption');
 
-# Check that no corruption is reported in schema db1.s5
-$node->command_checks_all([ @cmd, '-s', 's5', 'db1' ],
+# In schema db1.s5 we should see GiST corruption messages on stdout, and
+# nothing on stderr.
+#
+$node->command_checks_all(
+	[ @cmd, '-s', 's5', 'db1' ],
+	2,
+	[
+		$missing_file_re, $line_pointer_corruption_re,
+	],
+	[$no_output_re],
+	'pg_amcheck schema s5 reports GiST index errors');
+
+# Check that no corruption is reported in schema db1.s6
+$node->command_checks_all([ @cmd, '-s', 's6', 'db1' ],
 	0, [$no_output_re], [$no_output_re],
-	'pg_amcheck over schema s5 reports no corruption');
+	'pg_amcheck over schema s6 reports no corruption');
 
 # In schema db1.s1, only indexes are corrupt.  Verify that when we exclude
 # the indexes, no corruption is reported about the schema.
-- 
2.32.0 (Apple Git-132)