v2025-07-11-0003-GIN-in-pg_amcheck.patch
application/octet-stream
Filename: v2025-07-11-0003-GIN-in-pg_amcheck.patch
Type: application/octet-stream
Part: 0
Message:
Re: amcheck: support for GiST
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 v2025-0003
Subject: GIN in pg_amcheck
| File | + | − |
|---|---|---|
| src/bin/pg_amcheck/pg_amcheck.c | 51 | 12 |
| src/bin/pg_amcheck/t/003_check.pl | 39 | 17 |
From 89660718827121c302ca35543babcafc7c92daf4 Mon Sep 17 00:00:00 2001
From: Andrey Borodin <amborodin@acm.org>
Date: Wed, 2 Jul 2025 21:21:26 +0300
Subject: [PATCH v2025-07-11 3/3] GIN in pg_amcheck
---
src/bin/pg_amcheck/pg_amcheck.c | 63 +++++++++++++++++++++++++------
src/bin/pg_amcheck/t/003_check.pl | 56 ++++++++++++++++++---------
2 files changed, 90 insertions(+), 29 deletions(-)
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 2575178cd1a..272d0fde708 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -151,6 +151,7 @@ typedef struct DatabaseInfo
char *amcheck_schema; /* escaped, quoted literal */
bool is_checkunique;
bool gist_supported;
+ bool gin_supported;
} DatabaseInfo;
typedef struct RelationInfo
@@ -181,6 +182,8 @@ static void prepare_btree_command(PQExpBuffer sql, RelationInfo *rel,
PGconn *conn);
static void prepare_gist_command(PQExpBuffer sql, RelationInfo *rel,
PGconn *conn);
+static void prepare_gin_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);
@@ -291,6 +294,7 @@ main(int argc, char *argv[])
int encoding = pg_get_encoding_from_locale(NULL, false);
ConnParams cparams;
bool gist_warn_printed = false;
+ bool gin_warn_printed = false;
pg_logging_init(argv[0]);
progname = get_progname(argv[0]);
@@ -634,6 +638,9 @@ main(int argc, char *argv[])
/* GiST indexes are supported in 1.6+ */
dat->gist_supported = ((vmaj == 1 && vmin >= 6) || vmaj > 1);
+ /* GIN indexes are supported in 1.5+ */
+ dat->gin_supported = ((vmaj == 1 && vmin >= 5) || vmaj > 1);
+
PQclear(result);
compile_relation_list_one_db(conn, &relations, dat, &pagestotal);
@@ -805,6 +812,17 @@ main(int argc, char *argv[])
gist_warn_printed = true;
}
}
+ else if (rel->amoid == GIN_AM_OID)
+ {
+ if (rel->datinfo->gin_supported)
+ prepare_gin_command(&sql, rel, free_slot->connection);
+ else
+ {
+ if (!gin_warn_printed)
+ pg_log_warning("GIN verification is not supported by installed amcheck version");
+ gin_warn_printed = true;
+ }
+ }
else
/* should not happen at this stage */
pg_log_info("Verification of index type %u not supported",
@@ -956,6 +974,27 @@ prepare_gist_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
rel->reloid);
}
+/*
+ * prepare_gin_command
+ * Similar to btree equivalent prepares command to check GIN index.
+ */
+static void
+prepare_gin_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
+{
+ resetPQExpBuffer(sql);
+
+ appendPQExpBuffer(sql,
+ "SELECT %s.gin_index_check("
+ "index := c.oid)"
+ "\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,
+ rel->reloid);
+}
+
/*
* run_command
*
@@ -1968,27 +2007,27 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
appendPQExpBuffer(&sql,
"\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"
+ "(c.relam = %u OR 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, GIST_AM_OID);
+ HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID, GIN_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 c.relam = %u) OR NOT ip.index_only)",
- HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID);
+ "\nAND ((c.relam = %u OR c.relam = %u OR c.relam = %u) OR NOT ip.index_only)",
+ HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID, GIN_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 c.relam = %u) OR NOT ep.index_only OR ep.rel_regex IS NULL)",
- HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID);
+ "\nAND ((c.relam = %u OR 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, GIN_AM_OID);
/*
* Exclude temporary tables and indexes, which must necessarily belong to
@@ -2027,7 +2066,7 @@ 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, %u)"
+ " AND c.relam IN (%u, %u, %u, %u)"
"AND c.relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_SEQUENCE) ", "
@@ -2039,10 +2078,10 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
CppAsString2(RELKIND_SEQUENCE) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")) OR "
- "((c.relam = %u OR c.relam = %u) AND c.relkind = "
+ "((c.relam = %u OR c.relam = %u OR c.relam = %u) AND c.relkind = "
CppAsString2(RELKIND_INDEX) "))",
- HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID,
- HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID);
+ HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID, GIN_AM_OID,
+ HEAP_TABLE_AM_OID, BTREE_AM_OID, GIST_AM_OID, GIN_AM_OID);
appendPQExpBufferStr(&sql,
"\nORDER BY c.oid)");
@@ -2100,9 +2139,9 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
appendPQExpBufferStr(&sql,
"\nWHERE true");
appendPQExpBuffer(&sql,
- " AND (c.relam = %u or c.relam = %u) "
+ " AND (c.relam = %u or c.relam = %u or c.relam = %u) "
"AND c.relkind = " CppAsString2(RELKIND_INDEX),
- BTREE_AM_OID, GIST_AM_OID);
+ BTREE_AM_OID, GIST_AM_OID, GIN_AM_OID);
if (opts.no_toast_expansion)
appendPQExpBuffer(&sql,
" AND c.relnamespace != %u",
diff --git a/src/bin/pg_amcheck/t/003_check.pl b/src/bin/pg_amcheck/t/003_check.pl
index 2d6efbf8b05..f22146d1466 100644
--- a/src/bin/pg_amcheck/t/003_check.pl
+++ b/src/bin/pg_amcheck/t/003_check.pl
@@ -1,4 +1,3 @@
-
# Copyright (c) 2021-2025, PostgreSQL Global Development Group
use strict;
@@ -185,7 +184,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 s6))
+ for my $schema (qw(s1 s2 s3 s4 s5 s6 s7))
{
$node->safe_psql(
$dbname, qq(
@@ -295,20 +294,22 @@ plan_to_remove_toast_file('db1', 's4.t2');
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
+# Corrupt GIN index in schema "s6"
+plan_to_remove_relation_file('db1', 's6.t1_gin');
+plan_to_corrupt_first_page('db1', 's6.t2_gin');
+
+# Corrupt all other object types in schema "s7". 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', '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_remove_relation_file('db1', 's7.seq1');
+plan_to_remove_relation_file('db1', 's7.t1_hash');
+plan_to_remove_relation_file('db1', 's7.t1_brin');
+plan_to_remove_relation_file('db1', 's7.t1_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');
+plan_to_corrupt_first_page('db1', 's7.seq2');
+plan_to_corrupt_first_page('db1', 's7.t2_hash');
+plan_to_corrupt_first_page('db1', 's7.t2_brin');
+plan_to_corrupt_first_page('db1', 's7.t2_spgist');
# Database 'db2' corruptions
@@ -475,10 +476,22 @@ $node->command_checks_all(
[$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' ],
+# In schema db1.s6 we should see GIN corruption messages on stdout, and
+# nothing on stderr.
+#
+$node->command_checks_all(
+ [ @cmd, '-s', 's6', 'db1' ],
+ 2,
+ [
+ $missing_file_re,
+ ],
+ [$no_output_re],
+ 'pg_amcheck schema s6 reports GIN index errors');
+
+# Check that no corruption is reported in schema db1.s7
+$node->command_checks_all([ @cmd, '-s', 's7', 'db1' ],
0, [$no_output_re], [$no_output_re],
- 'pg_amcheck over schema s6 reports no corruption');
+ 'pg_amcheck over schema s7 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.
@@ -663,5 +676,14 @@ $node->command_checks_all(
[
qr/pg_amcheck: warning: GiST verification is not supported by installed amcheck version/
],
- 'pg_amcheck smoke test --checkunique');
+ 'pg_amcheck smoke test GiST version warning');
+
+$node->command_checks_all(
+ [ @cmd, '-s', 's6', 'db1' ],
+ 0,
+ [$no_output_re],
+ [
+ qr/pg_amcheck: warning: GIN verification is not supported by installed amcheck version/
+ ],
+ 'pg_amcheck smoke test GIN version warning');
done_testing();
--
2.39.5 (Apple Git-154)