From 89660718827121c302ca35543babcafc7c92daf4 Mon Sep 17 00:00:00 2001 From: Andrey Borodin 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)