summaryrefslogtreecommitdiffstats
path: root/src/bin/pg_amcheck/pg_amcheck.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/pg_amcheck/pg_amcheck.c')
-rw-r--r--src/bin/pg_amcheck/pg_amcheck.c2216
1 files changed, 2216 insertions, 0 deletions
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
new file mode 100644
index 0000000..2c86dda
--- /dev/null
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -0,0 +1,2216 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_amcheck.c
+ * Detects corruption within database relations.
+ *
+ * Copyright (c) 2017-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/bin/pg_amcheck/pg_amcheck.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include <time.h>
+
+#include "catalog/pg_am_d.h"
+#include "catalog/pg_namespace_d.h"
+#include "common/logging.h"
+#include "common/username.h"
+#include "fe_utils/cancel.h"
+#include "fe_utils/option_utils.h"
+#include "fe_utils/parallel_slot.h"
+#include "fe_utils/query_utils.h"
+#include "fe_utils/simple_list.h"
+#include "fe_utils/string_utils.h"
+#include "getopt_long.h" /* pgrminclude ignore */
+#include "pgtime.h"
+#include "storage/block.h"
+
+typedef struct PatternInfo
+{
+ const char *pattern; /* Unaltered pattern from the command line */
+ char *db_regex; /* Database regexp parsed from pattern, or
+ * NULL */
+ char *nsp_regex; /* Schema regexp parsed from pattern, or NULL */
+ char *rel_regex; /* Relation regexp parsed from pattern, or
+ * 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 matched; /* true if the pattern matched in any database */
+} PatternInfo;
+
+typedef struct PatternInfoArray
+{
+ PatternInfo *data;
+ size_t len;
+} PatternInfoArray;
+
+/* pg_amcheck command line options controlled by user flags */
+typedef struct AmcheckOptions
+{
+ bool dbpattern;
+ bool alldb;
+ bool echo;
+ bool verbose;
+ bool strict_names;
+ bool show_progress;
+ int jobs;
+
+ /*
+ * Whether to install missing extensions, and optionally the name of the
+ * schema in which to install the extension's objects.
+ */
+ bool install_missing;
+ char *install_schema;
+
+ /* Objects to check or not to check, as lists of PatternInfo structs. */
+ PatternInfoArray include;
+ PatternInfoArray exclude;
+
+ /*
+ * 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
+ * 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.
+ */
+ bool excludetbl;
+ bool excludeidx;
+ bool excludensp;
+
+ /*
+ * If any inclusion pattern exists, then we should only be checking
+ * matching relations rather than all relations, so this is true iff
+ * include is empty.
+ */
+ bool allrel;
+
+ /* heap table checking options */
+ bool no_toast_expansion;
+ bool reconcile_toast;
+ bool on_error_stop;
+ int64 startblock;
+ int64 endblock;
+ const char *skip;
+
+ /* btree index checking options */
+ bool parent_check;
+ bool rootdescend;
+ bool heapallindexed;
+
+ /* heap and btree hybrid option */
+ bool no_btree_expansion;
+} AmcheckOptions;
+
+static AmcheckOptions opts = {
+ .dbpattern = false,
+ .alldb = false,
+ .echo = false,
+ .verbose = false,
+ .strict_names = true,
+ .show_progress = false,
+ .jobs = 1,
+ .install_missing = false,
+ .install_schema = "pg_catalog",
+ .include = {NULL, 0},
+ .exclude = {NULL, 0},
+ .excludetbl = false,
+ .excludeidx = false,
+ .excludensp = false,
+ .allrel = true,
+ .no_toast_expansion = false,
+ .reconcile_toast = true,
+ .on_error_stop = false,
+ .startblock = -1,
+ .endblock = -1,
+ .skip = "none",
+ .parent_check = false,
+ .rootdescend = false,
+ .heapallindexed = false,
+ .no_btree_expansion = false
+};
+
+static const char *progname = NULL;
+
+/* Whether all relations have so far passed their corruption checks */
+static bool all_checks_pass = true;
+
+/* Time last progress report was displayed */
+static pg_time_t last_progress_report = 0;
+static bool progress_since_last_stderr = false;
+
+typedef struct DatabaseInfo
+{
+ char *datname;
+ char *amcheck_schema; /* escaped, quoted literal */
+} DatabaseInfo;
+
+typedef struct RelationInfo
+{
+ const DatabaseInfo *datinfo; /* shared by other relinfos */
+ Oid reloid;
+ bool is_heap; /* true if heap, false if btree */
+ char *nspname;
+ char *relname;
+ int relpages;
+ int blocks_to_check;
+ char *sql; /* set during query run, pg_free'd after */
+} RelationInfo;
+
+/*
+ * Query for determining if contrib's amcheck is installed. If so, selects the
+ * namespace name where amcheck's functions can be found.
+ */
+static const char *amcheck_sql =
+"SELECT n.nspname, x.extversion FROM pg_catalog.pg_extension x"
+"\nJOIN pg_catalog.pg_namespace n ON x.extnamespace = n.oid"
+"\nWHERE x.extname = 'amcheck'";
+
+static void prepare_heap_command(PQExpBuffer sql, RelationInfo *rel,
+ PGconn *conn);
+static void prepare_btree_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 void help(const char *progname);
+static void progress_report(uint64 relations_total, uint64 relations_checked,
+ uint64 relpages_total, uint64 relpages_checked,
+ const char *datname, bool force, bool finished);
+
+static void append_database_pattern(PatternInfoArray *pia, const char *pattern,
+ int encoding);
+static void append_schema_pattern(PatternInfoArray *pia, const char *pattern,
+ int encoding);
+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,
+ int encoding);
+static void compile_database_list(PGconn *conn, SimplePtrList *databases,
+ const char *initial_dbname);
+static void compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
+ const DatabaseInfo *datinfo,
+ uint64 *pagecount);
+
+#define log_no_match(...) do { \
+ if (opts.strict_names) \
+ pg_log_generic(PG_LOG_ERROR, __VA_ARGS__); \
+ else \
+ pg_log_generic(PG_LOG_WARNING, __VA_ARGS__); \
+ } while(0)
+
+#define FREE_AND_SET_NULL(x) do { \
+ pg_free(x); \
+ (x) = NULL; \
+ } while (0)
+
+int
+main(int argc, char *argv[])
+{
+ PGconn *conn = NULL;
+ SimplePtrListCell *cell;
+ SimplePtrList databases = {NULL, NULL};
+ SimplePtrList relations = {NULL, NULL};
+ bool failed = false;
+ const char *latest_datname;
+ int parallel_workers;
+ ParallelSlotArray *sa;
+ PQExpBufferData sql;
+ uint64 reltotal = 0;
+ uint64 pageschecked = 0;
+ uint64 pagestotal = 0;
+ uint64 relprogress = 0;
+ int pattern_id;
+
+ static struct option long_options[] = {
+ /* Connection options */
+ {"host", required_argument, NULL, 'h'},
+ {"port", required_argument, NULL, 'p'},
+ {"username", required_argument, NULL, 'U'},
+ {"no-password", no_argument, NULL, 'w'},
+ {"password", no_argument, NULL, 'W'},
+ {"maintenance-db", required_argument, NULL, 1},
+
+ /* check options */
+ {"all", no_argument, NULL, 'a'},
+ {"database", required_argument, NULL, 'd'},
+ {"exclude-database", required_argument, NULL, 'D'},
+ {"echo", no_argument, NULL, 'e'},
+ {"index", required_argument, NULL, 'i'},
+ {"exclude-index", required_argument, NULL, 'I'},
+ {"jobs", required_argument, NULL, 'j'},
+ {"progress", no_argument, NULL, 'P'},
+ {"relation", required_argument, NULL, 'r'},
+ {"exclude-relation", required_argument, NULL, 'R'},
+ {"schema", required_argument, NULL, 's'},
+ {"exclude-schema", required_argument, NULL, 'S'},
+ {"table", required_argument, NULL, 't'},
+ {"exclude-table", required_argument, NULL, 'T'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"no-dependent-indexes", no_argument, NULL, 2},
+ {"no-dependent-toast", no_argument, NULL, 3},
+ {"exclude-toast-pointers", no_argument, NULL, 4},
+ {"on-error-stop", no_argument, NULL, 5},
+ {"skip", required_argument, NULL, 6},
+ {"startblock", required_argument, NULL, 7},
+ {"endblock", required_argument, NULL, 8},
+ {"rootdescend", no_argument, NULL, 9},
+ {"no-strict-names", no_argument, NULL, 10},
+ {"heapallindexed", no_argument, NULL, 11},
+ {"parent-check", no_argument, NULL, 12},
+ {"install-missing", optional_argument, NULL, 13},
+
+ {NULL, 0, NULL, 0}
+ };
+
+ int optindex;
+ int c;
+
+ const char *db = NULL;
+ const char *maintenance_db = NULL;
+
+ const char *host = NULL;
+ const char *port = NULL;
+ const char *username = NULL;
+ enum trivalue prompt_password = TRI_DEFAULT;
+ int encoding = pg_get_encoding_from_locale(NULL, false);
+ ConnParams cparams;
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_amcheck"));
+
+ handle_help_version_opts(argc, argv, progname, help);
+
+ /* process command-line options */
+ while ((c = getopt_long(argc, argv, "ad:D:eh:Hi:I:j:p:Pr:R:s:S:t:T:U:wWv",
+ long_options, &optindex)) != -1)
+ {
+ char *endptr;
+ unsigned long optval;
+
+ switch (c)
+ {
+ case 'a':
+ opts.alldb = true;
+ break;
+ case 'd':
+ opts.dbpattern = true;
+ append_database_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'D':
+ opts.dbpattern = true;
+ append_database_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 'e':
+ opts.echo = true;
+ break;
+ case 'h':
+ host = pg_strdup(optarg);
+ break;
+ case 'i':
+ opts.allrel = false;
+ append_btree_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'I':
+ opts.excludeidx = true;
+ append_btree_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 'j':
+ opts.jobs = atoi(optarg);
+ if (opts.jobs < 1)
+ {
+ pg_log_error("number of parallel jobs must be at least 1");
+ exit(1);
+ }
+ break;
+ case 'p':
+ port = pg_strdup(optarg);
+ break;
+ case 'P':
+ opts.show_progress = true;
+ break;
+ case 'r':
+ opts.allrel = false;
+ append_relation_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'R':
+ opts.excludeidx = true;
+ opts.excludetbl = true;
+ append_relation_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 's':
+ opts.allrel = false;
+ append_schema_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'S':
+ opts.excludensp = true;
+ append_schema_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 't':
+ opts.allrel = false;
+ append_heap_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'T':
+ opts.excludetbl = true;
+ append_heap_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 'U':
+ username = pg_strdup(optarg);
+ break;
+ case 'w':
+ prompt_password = TRI_NO;
+ break;
+ case 'W':
+ prompt_password = TRI_YES;
+ break;
+ case 'v':
+ opts.verbose = true;
+ pg_logging_increase_verbosity();
+ break;
+ case 1:
+ maintenance_db = pg_strdup(optarg);
+ break;
+ case 2:
+ opts.no_btree_expansion = true;
+ break;
+ case 3:
+ opts.no_toast_expansion = true;
+ break;
+ case 4:
+ opts.reconcile_toast = false;
+ break;
+ case 5:
+ opts.on_error_stop = true;
+ break;
+ case 6:
+ if (pg_strcasecmp(optarg, "all-visible") == 0)
+ opts.skip = "all-visible";
+ else if (pg_strcasecmp(optarg, "all-frozen") == 0)
+ opts.skip = "all-frozen";
+ else if (pg_strcasecmp(optarg, "none") == 0)
+ opts.skip = "none";
+ else
+ {
+ pg_log_error("invalid argument for option %s", "--skip");
+ exit(1);
+ }
+ break;
+ case 7:
+ errno = 0;
+ optval = strtoul(optarg, &endptr, 10);
+ if (endptr == optarg || *endptr != '\0' || errno != 0)
+ {
+ pg_log_error("invalid start block");
+ exit(1);
+ }
+ if (optval > MaxBlockNumber)
+ {
+ pg_log_error("start block out of bounds");
+ exit(1);
+ }
+ opts.startblock = optval;
+ break;
+ case 8:
+ errno = 0;
+ optval = strtoul(optarg, &endptr, 10);
+ if (endptr == optarg || *endptr != '\0' || errno != 0)
+ {
+ pg_log_error("invalid end block");
+ exit(1);
+ }
+ if (optval > MaxBlockNumber)
+ {
+ pg_log_error("end block out of bounds");
+ exit(1);
+ }
+ opts.endblock = optval;
+ break;
+ case 9:
+ opts.rootdescend = true;
+ opts.parent_check = true;
+ break;
+ case 10:
+ opts.strict_names = false;
+ break;
+ case 11:
+ opts.heapallindexed = true;
+ break;
+ case 12:
+ opts.parent_check = true;
+ break;
+ case 13:
+ opts.install_missing = true;
+ if (optarg)
+ opts.install_schema = pg_strdup(optarg);
+ break;
+ default:
+ fprintf(stderr,
+ _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+ }
+
+ if (opts.endblock >= 0 && opts.endblock < opts.startblock)
+ {
+ pg_log_error("end block precedes start block");
+ exit(1);
+ }
+
+ /*
+ * A single non-option arguments specifies a database name or connection
+ * string.
+ */
+ if (optind < argc)
+ {
+ db = argv[optind];
+ optind++;
+ }
+
+ if (optind < argc)
+ {
+ pg_log_error("too many command-line arguments (first is \"%s\")",
+ argv[optind]);
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+ exit(1);
+ }
+
+ /* fill cparams except for dbname, which is set below */
+ cparams.pghost = host;
+ cparams.pgport = port;
+ cparams.pguser = username;
+ cparams.prompt_password = prompt_password;
+ cparams.dbname = NULL;
+ cparams.override_dbname = NULL;
+
+ setup_cancel_handler(NULL);
+
+ /* choose the database for our initial connection */
+ if (opts.alldb)
+ {
+ if (db != NULL)
+ {
+ pg_log_error("cannot specify a database name with --all");
+ exit(1);
+ }
+ cparams.dbname = maintenance_db;
+ }
+ else if (db != NULL)
+ {
+ if (opts.dbpattern)
+ {
+ pg_log_error("cannot specify both a database name and database patterns");
+ exit(1);
+ }
+ cparams.dbname = db;
+ }
+
+ if (opts.alldb || opts.dbpattern)
+ {
+ conn = connectMaintenanceDatabase(&cparams, progname, opts.echo);
+ compile_database_list(conn, &databases, NULL);
+ }
+ else
+ {
+ if (cparams.dbname == NULL)
+ {
+ if (getenv("PGDATABASE"))
+ cparams.dbname = getenv("PGDATABASE");
+ else if (getenv("PGUSER"))
+ cparams.dbname = getenv("PGUSER");
+ else
+ cparams.dbname = get_user_name_or_exit(progname);
+ }
+ conn = connectDatabase(&cparams, progname, opts.echo, false, true);
+ compile_database_list(conn, &databases, PQdb(conn));
+ }
+
+ if (databases.head == NULL)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ pg_log_error("no databases to check");
+ exit(0);
+ }
+
+ /*
+ * Compile a list of all relations spanning all databases to be checked.
+ */
+ for (cell = databases.head; cell; cell = cell->next)
+ {
+ PGresult *result;
+ int ntups;
+ const char *amcheck_schema = NULL;
+ DatabaseInfo *dat = (DatabaseInfo *) cell->ptr;
+
+ cparams.override_dbname = dat->datname;
+ if (conn == NULL || strcmp(PQdb(conn), dat->datname) != 0)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ conn = connectDatabase(&cparams, progname, opts.echo, false, true);
+ }
+
+ /*
+ * Optionally install amcheck if not already installed in this
+ * database.
+ */
+ if (opts.install_missing)
+ {
+ char *schema;
+ char *install_sql;
+
+ /*
+ * Must re-escape the schema name for each database, as the
+ * escaping rules may change.
+ */
+ schema = PQescapeIdentifier(conn, opts.install_schema,
+ strlen(opts.install_schema));
+ install_sql = psprintf("CREATE EXTENSION IF NOT EXISTS amcheck WITH SCHEMA %s",
+ schema);
+
+ executeCommand(conn, install_sql, opts.echo);
+ pfree(install_sql);
+ pfree(schema);
+ }
+
+ /*
+ * Verify that amcheck is installed for this next database. User
+ * error could result in a database not having amcheck that should
+ * have it, but we also could be iterating over multiple databases
+ * where not all of them have amcheck installed (for example,
+ * 'template1').
+ */
+ result = executeQuery(conn, amcheck_sql, opts.echo);
+ if (PQresultStatus(result) != PGRES_TUPLES_OK)
+ {
+ /* Querying the catalog failed. */
+ pg_log_error("database \"%s\": %s",
+ PQdb(conn), PQerrorMessage(conn));
+ pg_log_info("query was: %s", amcheck_sql);
+ PQclear(result);
+ disconnectDatabase(conn);
+ exit(1);
+ }
+ ntups = PQntuples(result);
+ if (ntups == 0)
+ {
+ /* Querying the catalog succeeded, but amcheck is missing. */
+ pg_log_warning("skipping database \"%s\": amcheck is not installed",
+ PQdb(conn));
+ disconnectDatabase(conn);
+ conn = NULL;
+ continue;
+ }
+ amcheck_schema = PQgetvalue(result, 0, 0);
+ if (opts.verbose)
+ pg_log_info("in database \"%s\": using amcheck version \"%s\" in schema \"%s\"",
+ PQdb(conn), PQgetvalue(result, 0, 1), amcheck_schema);
+ dat->amcheck_schema = PQescapeIdentifier(conn, amcheck_schema,
+ strlen(amcheck_schema));
+ PQclear(result);
+
+ compile_relation_list_one_db(conn, &relations, dat, &pagestotal);
+ }
+
+ /*
+ * Check that all inclusion patterns matched at least one schema or
+ * relation that we can check.
+ */
+ for (pattern_id = 0; pattern_id < opts.include.len; pattern_id++)
+ {
+ PatternInfo *pat = &opts.include.data[pattern_id];
+
+ if (!pat->matched && (pat->nsp_regex != NULL || pat->rel_regex != NULL))
+ {
+ failed = opts.strict_names;
+
+ 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\"",
+ pat->pattern);
+ else if (pat->rel_regex == NULL)
+ log_no_match("no relations to check in schemas matching \"%s\"",
+ pat->pattern);
+ else
+ log_no_match("no relations to check matching \"%s\"",
+ pat->pattern);
+ }
+ }
+
+ if (failed)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ exit(1);
+ }
+
+ /*
+ * Set parallel_workers to the lesser of opts.jobs and the number of
+ * relations.
+ */
+ parallel_workers = 0;
+ for (cell = relations.head; cell; cell = cell->next)
+ {
+ reltotal++;
+ if (parallel_workers < opts.jobs)
+ parallel_workers++;
+ }
+
+ if (reltotal == 0)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ pg_log_error("no relations to check");
+ exit(1);
+ }
+ progress_report(reltotal, relprogress, pagestotal, pageschecked,
+ NULL, true, false);
+
+ /*
+ * Main event loop.
+ *
+ * We use server-side parallelism to check up to parallel_workers
+ * relations in parallel. The list of relations was computed in database
+ * order, which minimizes the number of connects and disconnects as we
+ * process the list.
+ */
+ latest_datname = NULL;
+ sa = ParallelSlotsSetup(parallel_workers, &cparams, progname, opts.echo,
+ NULL);
+ if (conn != NULL)
+ {
+ ParallelSlotsAdoptConn(sa, conn);
+ conn = NULL;
+ }
+
+ initPQExpBuffer(&sql);
+ for (relprogress = 0, cell = relations.head; cell; cell = cell->next)
+ {
+ ParallelSlot *free_slot;
+ RelationInfo *rel;
+
+ rel = (RelationInfo *) cell->ptr;
+
+ if (CancelRequested)
+ {
+ failed = true;
+ break;
+ }
+
+ /*
+ * The list of relations is in database sorted order. If this next
+ * relation is in a different database than the last one seen, we are
+ * about to start checking this database. Note that other slots may
+ * still be working on relations from prior databases.
+ */
+ latest_datname = rel->datinfo->datname;
+
+ progress_report(reltotal, relprogress, pagestotal, pageschecked,
+ latest_datname, false, false);
+
+ relprogress++;
+ pageschecked += rel->blocks_to_check;
+
+ /*
+ * Get a parallel slot for the next amcheck command, blocking if
+ * necessary until one is available, or until a previously issued slot
+ * command fails, indicating that we should abort checking the
+ * remaining objects.
+ */
+ free_slot = ParallelSlotsGetIdle(sa, rel->datinfo->datname);
+ if (!free_slot)
+ {
+ /*
+ * Something failed. We don't need to know what it was, because
+ * the handler should already have emitted the necessary error
+ * messages.
+ */
+ failed = true;
+ break;
+ }
+
+ if (opts.verbose)
+ PQsetErrorVerbosity(free_slot->connection, PQERRORS_VERBOSE);
+
+ /*
+ * Execute the appropriate amcheck command for this relation using our
+ * slot's database connection. We do not wait for the command to
+ * complete, nor do we perform any error checking, as that is done by
+ * the parallel slots and our handler callback functions.
+ */
+ if (rel->is_heap)
+ {
+ if (opts.verbose)
+ {
+ if (opts.show_progress && progress_since_last_stderr)
+ fprintf(stderr, "\n");
+ pg_log_info("checking heap table \"%s.%s.%s\"",
+ rel->datinfo->datname, rel->nspname, rel->relname);
+ progress_since_last_stderr = false;
+ }
+ prepare_heap_command(&sql, rel, free_slot->connection);
+ rel->sql = pstrdup(sql.data); /* pg_free'd after command */
+ ParallelSlotSetHandler(free_slot, verify_heap_slot_handler, rel);
+ run_command(free_slot, rel->sql);
+ }
+ else
+ {
+ if (opts.verbose)
+ {
+ if (opts.show_progress && progress_since_last_stderr)
+ fprintf(stderr, "\n");
+
+ pg_log_info("checking btree index \"%s.%s.%s\"",
+ rel->datinfo->datname, rel->nspname, rel->relname);
+ progress_since_last_stderr = false;
+ }
+ prepare_btree_command(&sql, rel, free_slot->connection);
+ rel->sql = pstrdup(sql.data); /* pg_free'd after command */
+ ParallelSlotSetHandler(free_slot, verify_btree_slot_handler, rel);
+ run_command(free_slot, rel->sql);
+ }
+ }
+ termPQExpBuffer(&sql);
+
+ if (!failed)
+ {
+
+ /*
+ * Wait for all slots to complete, or for one to indicate that an
+ * error occurred. Like above, we rely on the handler emitting the
+ * necessary error messages.
+ */
+ if (sa && !ParallelSlotsWaitCompletion(sa))
+ failed = true;
+
+ progress_report(reltotal, relprogress, pagestotal, pageschecked, NULL, true, true);
+ }
+
+ if (sa)
+ {
+ ParallelSlotsTerminate(sa);
+ FREE_AND_SET_NULL(sa);
+ }
+
+ if (failed)
+ exit(1);
+
+ if (!all_checks_pass)
+ exit(2);
+}
+
+/*
+ * prepare_heap_command
+ *
+ * Creates a SQL command for running amcheck checking on the given heap
+ * relation. The command is phrased as a SQL query, with column order and
+ * names matching the expectations of verify_heap_slot_handler, which will
+ * receive and handle each row returned from the verify_heapam() function.
+ *
+ * The constructed SQL command will silently skip temporary tables, as checking
+ * them would needlessly draw errors from the underlying amcheck function.
+ *
+ * sql: buffer into which the heap table checking command will be written
+ * rel: relation information for the heap table to be checked
+ * conn: the connection to be used, for string escaping purposes
+ */
+static void
+prepare_heap_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
+{
+ resetPQExpBuffer(sql);
+ appendPQExpBuffer(sql,
+ "SELECT v.blkno, v.offnum, v.attnum, v.msg "
+ "FROM pg_catalog.pg_class c, %s.verify_heapam("
+ "\nrelation := c.oid, on_error_stop := %s, check_toast := %s, skip := '%s'",
+ rel->datinfo->amcheck_schema,
+ opts.on_error_stop ? "true" : "false",
+ opts.reconcile_toast ? "true" : "false",
+ opts.skip);
+
+ if (opts.startblock >= 0)
+ appendPQExpBuffer(sql, ", startblock := " INT64_FORMAT, opts.startblock);
+ if (opts.endblock >= 0)
+ appendPQExpBuffer(sql, ", endblock := " INT64_FORMAT, opts.endblock);
+
+ appendPQExpBuffer(sql,
+ "\n) v WHERE c.oid = %u "
+ "AND c.relpersistence != 't'",
+ rel->reloid);
+}
+
+/*
+ * prepare_btree_command
+ *
+ * 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.
+ *
+ * The constructed SQL command will silently skip temporary indexes, and
+ * indexes being reindexed concurrently, as checking them would needlessly draw
+ * errors from the underlying amcheck functions.
+ *
+ * sql: buffer into which the heap table checking command will be written
+ * rel: relation information for the index to be checked
+ * conn: the connection to be used, for string escaping purposes
+ */
+static void
+prepare_btree_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
+{
+ resetPQExpBuffer(sql);
+
+ if (opts.parent_check)
+ appendPQExpBuffer(sql,
+ "SELECT %s.bt_index_parent_check("
+ "index := c.oid, heapallindexed := %s, rootdescend := %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"),
+ (opts.rootdescend ? "true" : "false"),
+ rel->reloid);
+ else
+ appendPQExpBuffer(sql,
+ "SELECT %s.bt_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
+ *
+ * Sends a command to the server without waiting for the command to complete.
+ * Logs an error if the command cannot be sent, but otherwise any errors are
+ * expected to be handled by a ParallelSlotHandler.
+ *
+ * If reconnecting to the database is necessary, the cparams argument may be
+ * modified.
+ *
+ * slot: slot with connection to the server we should use for the command
+ * sql: query to send
+ */
+static void
+run_command(ParallelSlot *slot, const char *sql)
+{
+ if (opts.echo)
+ printf("%s\n", sql);
+
+ if (PQsendQuery(slot->connection, sql) == 0)
+ {
+ pg_log_error("error sending command to database \"%s\": %s",
+ PQdb(slot->connection),
+ PQerrorMessage(slot->connection));
+ pg_log_error("command was: %s", sql);
+ exit(1);
+ }
+}
+
+/*
+ * should_processing_continue
+ *
+ * Checks a query result returned from a query (presumably issued on a slot's
+ * connection) to determine if parallel slots should continue issuing further
+ * commands.
+ *
+ * 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
+ * functions always use errors to communicate corruption messages. We can't
+ * just abort processing because we got a mere ERROR.
+ *
+ * res: result from an executed sql query
+ */
+static bool
+should_processing_continue(PGresult *res)
+{
+ const char *severity;
+
+ switch (PQresultStatus(res))
+ {
+ /* These are expected and ok */
+ case PGRES_COMMAND_OK:
+ case PGRES_TUPLES_OK:
+ case PGRES_NONFATAL_ERROR:
+ break;
+
+ /* This is expected but requires closer scrutiny */
+ case PGRES_FATAL_ERROR:
+ severity = PQresultErrorField(res, PG_DIAG_SEVERITY_NONLOCALIZED);
+ if (severity == NULL)
+ return false; /* libpq failure, probably lost connection */
+ if (strcmp(severity, "FATAL") == 0)
+ return false;
+ if (strcmp(severity, "PANIC") == 0)
+ return false;
+ break;
+
+ /* These are unexpected */
+ case PGRES_BAD_RESPONSE:
+ case PGRES_EMPTY_QUERY:
+ case PGRES_COPY_OUT:
+ case PGRES_COPY_IN:
+ case PGRES_COPY_BOTH:
+ case PGRES_SINGLE_TUPLE:
+ case PGRES_PIPELINE_SYNC:
+ case PGRES_PIPELINE_ABORTED:
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Returns a copy of the argument string with all lines indented four spaces.
+ *
+ * The caller should pg_free the result when finished with it.
+ */
+static char *
+indent_lines(const char *str)
+{
+ PQExpBufferData buf;
+ const char *c;
+ char *result;
+
+ initPQExpBuffer(&buf);
+ appendPQExpBufferStr(&buf, " ");
+ for (c = str; *c; c++)
+ {
+ appendPQExpBufferChar(&buf, *c);
+ if (c[0] == '\n' && c[1] != '\0')
+ appendPQExpBufferStr(&buf, " ");
+ }
+ result = pstrdup(buf.data);
+ termPQExpBuffer(&buf);
+
+ return result;
+}
+
+/*
+ * verify_heap_slot_handler
+ *
+ * ParallelSlotHandler that receives results from a heap table checking command
+ * created by prepare_heap_command and outputs the results for the user.
+ *
+ * res: result from an executed sql query
+ * conn: connection on which the sql query was executed
+ * context: the sql query being handled, as a cstring
+ */
+static bool
+verify_heap_slot_handler(PGresult *res, PGconn *conn, void *context)
+{
+ RelationInfo *rel = (RelationInfo *) context;
+
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ int i;
+ int ntups = PQntuples(res);
+
+ if (ntups > 0)
+ all_checks_pass = false;
+
+ for (i = 0; i < ntups; i++)
+ {
+ const char *msg;
+
+ /* The message string should never be null, but check */
+ if (PQgetisnull(res, i, 3))
+ msg = "NO MESSAGE";
+ else
+ msg = PQgetvalue(res, i, 3);
+
+ if (!PQgetisnull(res, i, 2))
+ printf(_("heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname,
+ PQgetvalue(res, i, 0), /* blkno */
+ PQgetvalue(res, i, 1), /* offnum */
+ PQgetvalue(res, i, 2)); /* attnum */
+
+ else if (!PQgetisnull(res, i, 1))
+ printf(_("heap table \"%s.%s.%s\", block %s, offset %s:\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname,
+ PQgetvalue(res, i, 0), /* blkno */
+ PQgetvalue(res, i, 1)); /* offnum */
+
+ else if (!PQgetisnull(res, i, 0))
+ printf(_("heap table \"%s.%s.%s\", block %s:\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname,
+ PQgetvalue(res, i, 0)); /* blkno */
+
+ else
+ printf(_("heap table \"%s.%s.%s\":\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname);
+
+ printf(" %s\n", msg);
+ }
+ }
+ else if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ char *msg = indent_lines(PQerrorMessage(conn));
+
+ all_checks_pass = false;
+ printf(_("heap table \"%s.%s.%s\":\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname);
+ printf("%s", msg);
+ if (opts.verbose)
+ printf(_("query was: %s\n"), rel->sql);
+ FREE_AND_SET_NULL(msg);
+ }
+
+ FREE_AND_SET_NULL(rel->sql);
+ FREE_AND_SET_NULL(rel->nspname);
+ FREE_AND_SET_NULL(rel->relname);
+
+ return should_processing_continue(res);
+}
+
+/*
+ * verify_btree_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
+ * are an error code, the useful information about the corruption is expected
+ * in the connection's error message.
+ *
+ * res: result from an executed sql query
+ * conn: connection on which the sql query was executed
+ * context: unused
+ */
+static bool
+verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context)
+{
+ RelationInfo *rel = (RelationInfo *) context;
+
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ int ntups = PQntuples(res);
+
+ if (ntups > 1)
+ {
+ /*
+ * We expect the btree 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
+ * indicates corruption, but because it suggests a mismatch between
+ * amcheck and pg_amcheck versions.
+ *
+ * In conjunction with --progress, anything written to stderr at
+ * this time would present strangely to the user without an extra
+ * newline, so we print one. If we were multithreaded, we'd have
+ * to avoid splitting this across multiple calls, but we're in an
+ * event loop, so it doesn't matter.
+ */
+ 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",
+ rel->datinfo->datname, rel->nspname, rel->relname, ntups);
+ if (opts.verbose)
+ pg_log_info("query was: %s", rel->sql);
+ pg_log_warning("Are %s's and amcheck's versions compatible?",
+ progname);
+ progress_since_last_stderr = false;
+ }
+ }
+ else
+ {
+ char *msg = indent_lines(PQerrorMessage(conn));
+
+ all_checks_pass = false;
+ printf(_("btree index \"%s.%s.%s\":\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname);
+ printf("%s", msg);
+ if (opts.verbose)
+ printf(_("query was: %s\n"), rel->sql);
+ FREE_AND_SET_NULL(msg);
+ }
+
+ FREE_AND_SET_NULL(rel->sql);
+ FREE_AND_SET_NULL(rel->nspname);
+ FREE_AND_SET_NULL(rel->relname);
+
+ return should_processing_continue(res);
+}
+
+/*
+ * help
+ *
+ * Prints help page for the program
+ *
+ * progname: the name of the executed program, such as "pg_amcheck"
+ */
+static void
+help(const char *progname)
+{
+ printf(_("%s checks objects in a PostgreSQL database for corruption.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
+ printf(_("\nTarget options:\n"));
+ printf(_(" -a, --all check all databases\n"));
+ printf(_(" -d, --database=PATTERN check matching database(s)\n"));
+ printf(_(" -D, --exclude-database=PATTERN do NOT check matching database(s)\n"));
+ printf(_(" -i, --index=PATTERN check matching index(es)\n"));
+ printf(_(" -I, --exclude-index=PATTERN do NOT check matching index(es)\n"));
+ printf(_(" -r, --relation=PATTERN check matching relation(s)\n"));
+ printf(_(" -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"));
+ printf(_(" -s, --schema=PATTERN check matching schema(s)\n"));
+ printf(_(" -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"));
+ printf(_(" -t, --table=PATTERN check matching table(s)\n"));
+ printf(_(" -T, --exclude-table=PATTERN do NOT check matching table(s)\n"));
+ printf(_(" --no-dependent-indexes do NOT expand list of relations to include indexes\n"));
+ printf(_(" --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"));
+ printf(_(" --no-strict-names do NOT require patterns to match objects\n"));
+ printf(_("\nTable checking options:\n"));
+ printf(_(" --exclude-toast-pointers do NOT follow relation TOAST pointers\n"));
+ printf(_(" --on-error-stop stop checking at end of first corrupt page\n"));
+ printf(_(" --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"));
+ printf(_(" --startblock=BLOCK begin checking table(s) at the given block number\n"));
+ printf(_(" --endblock=BLOCK check table(s) only up to the given block number\n"));
+ printf(_("\nB-tree index checking options:\n"));
+ 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(_("\nConnection options:\n"));
+ printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
+ printf(_(" -p, --port=PORT database server port\n"));
+ printf(_(" -U, --username=USERNAME user name to connect as\n"));
+ printf(_(" -w, --no-password never prompt for password\n"));
+ printf(_(" -W, --password force password prompt\n"));
+ printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
+ printf(_("\nOther options:\n"));
+ printf(_(" -e, --echo show the commands being sent to the server\n"));
+ printf(_(" -j, --jobs=NUM use this many concurrent connections to the server\n"));
+ printf(_(" -P, --progress show progress information\n"));
+ printf(_(" -v, --verbose write a lot of output\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_(" --install-missing install missing extensions\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+
+ printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
+
+/*
+ * Print a progress report based on the global variables.
+ *
+ * Progress report is written at maximum once per second, unless the force
+ * parameter is set to true.
+ *
+ * If finished is set to true, this is the last progress report. The cursor
+ * is moved to the next line.
+ */
+static void
+progress_report(uint64 relations_total, uint64 relations_checked,
+ uint64 relpages_total, uint64 relpages_checked,
+ const char *datname, bool force, bool finished)
+{
+ int percent_rel = 0;
+ int percent_pages = 0;
+ char checked_rel[32];
+ char total_rel[32];
+ char checked_pages[32];
+ char total_pages[32];
+ pg_time_t now;
+
+ if (!opts.show_progress)
+ return;
+
+ now = time(NULL);
+ if (now == last_progress_report && !force && !finished)
+ return; /* Max once per second */
+
+ last_progress_report = now;
+ if (relations_total)
+ percent_rel = (int) (relations_checked * 100 / relations_total);
+ if (relpages_total)
+ percent_pages = (int) (relpages_checked * 100 / relpages_total);
+
+ /*
+ * Separate step to keep platform-dependent format code out of fprintf
+ * calls. We only test for INT64_FORMAT availability in snprintf, not
+ * fprintf.
+ */
+ snprintf(checked_rel, sizeof(checked_rel), INT64_FORMAT, relations_checked);
+ snprintf(total_rel, sizeof(total_rel), INT64_FORMAT, relations_total);
+ snprintf(checked_pages, sizeof(checked_pages), INT64_FORMAT, relpages_checked);
+ snprintf(total_pages, sizeof(total_pages), INT64_FORMAT, relpages_total);
+
+#define VERBOSE_DATNAME_LENGTH 35
+ if (opts.verbose)
+ {
+ if (!datname)
+
+ /*
+ * No datname given, so clear the status line (used for first and
+ * last call)
+ */
+ fprintf(stderr,
+ _("%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"),
+ (int) strlen(total_rel),
+ checked_rel, total_rel, percent_rel,
+ (int) strlen(total_pages),
+ checked_pages, total_pages, percent_pages,
+ VERBOSE_DATNAME_LENGTH + 2, "");
+ else
+ {
+ bool truncate = (strlen(datname) > VERBOSE_DATNAME_LENGTH);
+
+ fprintf(stderr,
+ _("%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"),
+ (int) strlen(total_rel),
+ checked_rel, total_rel, percent_rel,
+ (int) strlen(total_pages),
+ checked_pages, total_pages, percent_pages,
+ /* Prefix with "..." if we do leading truncation */
+ truncate ? "..." : "",
+ truncate ? VERBOSE_DATNAME_LENGTH - 3 : VERBOSE_DATNAME_LENGTH,
+ truncate ? VERBOSE_DATNAME_LENGTH - 3 : VERBOSE_DATNAME_LENGTH,
+ /* Truncate datname at beginning if it's too long */
+ truncate ? datname + strlen(datname) - VERBOSE_DATNAME_LENGTH + 3 : datname);
+ }
+ }
+ else
+ fprintf(stderr,
+ _("%*s/%s relations (%d%%), %*s/%s pages (%d%%)"),
+ (int) strlen(total_rel),
+ checked_rel, total_rel, percent_rel,
+ (int) strlen(total_pages),
+ checked_pages, total_pages, percent_pages);
+
+ /*
+ * Stay on the same line if reporting to a terminal and we're not done
+ * yet.
+ */
+ if (!finished && isatty(fileno(stderr)))
+ {
+ fputc('\r', stderr);
+ progress_since_last_stderr = true;
+ }
+ else
+ fputc('\n', stderr);
+}
+
+/*
+ * Extend the pattern info array to hold one additional initialized pattern
+ * info entry.
+ *
+ * Returns a pointer to the new entry.
+ */
+static PatternInfo *
+extend_pattern_info_array(PatternInfoArray *pia)
+{
+ PatternInfo *result;
+
+ pia->len++;
+ pia->data = (PatternInfo *) pg_realloc(pia->data, pia->len * sizeof(PatternInfo));
+ result = &pia->data[pia->len - 1];
+ memset(result, 0, sizeof(*result));
+
+ return result;
+}
+
+/*
+ * append_database_pattern
+ *
+ * Adds the given pattern interpreted as a database name pattern.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the database name pattern
+ * encoding: client encoding for parsing the pattern
+ */
+static void
+append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+{
+ PQExpBufferData buf;
+ int dotcnt;
+ PatternInfo *info = extend_pattern_info_array(pia);
+
+ initPQExpBuffer(&buf);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
+ info->pattern = pattern;
+ info->db_regex = pstrdup(buf.data);
+
+ termPQExpBuffer(&buf);
+}
+
+/*
+ * append_schema_pattern
+ *
+ * Adds the given pattern interpreted as a schema name pattern.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the schema name pattern
+ * encoding: client encoding for parsing the pattern
+ */
+static void
+append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+{
+ PQExpBufferData dbbuf;
+ PQExpBufferData nspbuf;
+ int dotcnt;
+ PatternInfo *info = extend_pattern_info_array(pia);
+
+ initPQExpBuffer(&dbbuf);
+ initPQExpBuffer(&nspbuf);
+
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
+ info->pattern = pattern;
+ if (dbbuf.data[0])
+ {
+ opts.dbpattern = true;
+ info->db_regex = pstrdup(dbbuf.data);
+ }
+ if (nspbuf.data[0])
+ info->nsp_regex = pstrdup(nspbuf.data);
+
+ termPQExpBuffer(&dbbuf);
+ termPQExpBuffer(&nspbuf);
+}
+
+/*
+ * append_relation_pattern_helper
+ *
+ * Adds to a list the given pattern interpreted as a relation pattern.
+ *
+ * pia: the pattern info array to be appended
+ * 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
+ */
+static void
+append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
+ int encoding, bool heap_only, bool btree_only)
+{
+ PQExpBufferData dbbuf;
+ PQExpBufferData nspbuf;
+ PQExpBufferData relbuf;
+ int dotcnt;
+ PatternInfo *info = extend_pattern_info_array(pia);
+
+ initPQExpBuffer(&dbbuf);
+ initPQExpBuffer(&nspbuf);
+ initPQExpBuffer(&relbuf);
+
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
+ info->pattern = pattern;
+ if (dbbuf.data[0])
+ {
+ opts.dbpattern = true;
+ info->db_regex = pstrdup(dbbuf.data);
+ }
+ if (nspbuf.data[0])
+ info->nsp_regex = pstrdup(nspbuf.data);
+ if (relbuf.data[0])
+ info->rel_regex = pstrdup(relbuf.data);
+
+ termPQExpBuffer(&dbbuf);
+ termPQExpBuffer(&nspbuf);
+ termPQExpBuffer(&relbuf);
+
+ info->heap_only = heap_only;
+ info->btree_only = btree_only;
+}
+
+/*
+ * append_relation_pattern
+ *
+ * Adds the given pattern interpreted as a relation pattern, to be matched
+ * against both heap tables and btree indexes.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the relation name pattern
+ * encoding: client encoding for parsing the pattern
+ */
+static void
+append_relation_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+{
+ append_relation_pattern_helper(pia, pattern, encoding, false, false);
+}
+
+/*
+ * append_heap_pattern
+ *
+ * Adds the given pattern interpreted as a relation pattern, to be matched only
+ * against heap tables.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the relation name pattern
+ * encoding: client encoding for parsing the pattern
+ */
+static void
+append_heap_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+{
+ append_relation_pattern_helper(pia, pattern, encoding, true, false);
+}
+
+/*
+ * append_btree_pattern
+ *
+ * Adds the given pattern interpreted as a relation pattern, to be matched only
+ * against btree 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_relation_pattern_helper(pia, pattern, encoding, false, true);
+}
+
+/*
+ * append_db_pattern_cte
+ *
+ * Appends to the buffer the body of a Common Table Expression (CTE) containing
+ * the database portions filtered from the list of patterns expressed as two
+ * columns:
+ *
+ * pattern_id: the index of this pattern in pia->data[]
+ * rgx: the database regular expression parsed from the pattern
+ *
+ * Patterns without a database portion are skipped. Patterns with more than
+ * just a database portion are optionally skipped, depending on argument
+ * 'inclusive'.
+ *
+ * buf: the buffer to be appended
+ * pia: the array of patterns to be inserted into the CTE
+ * conn: the database connection
+ * inclusive: whether to include patterns with schema and/or relation parts
+ *
+ * Returns whether any database patterns were appended.
+ */
+static bool
+append_db_pattern_cte(PQExpBuffer buf, const PatternInfoArray *pia,
+ PGconn *conn, bool inclusive)
+{
+ int pattern_id;
+ const char *comma;
+ bool have_values;
+
+ comma = "";
+ have_values = false;
+ for (pattern_id = 0; pattern_id < pia->len; pattern_id++)
+ {
+ PatternInfo *info = &pia->data[pattern_id];
+
+ if (info->db_regex != NULL &&
+ (inclusive || (info->nsp_regex == NULL && info->rel_regex == NULL)))
+ {
+ if (!have_values)
+ appendPQExpBufferStr(buf, "\nVALUES");
+ have_values = true;
+ appendPQExpBuffer(buf, "%s\n(%d, ", comma, pattern_id);
+ appendStringLiteralConn(buf, info->db_regex, conn);
+ appendPQExpBufferStr(buf, ")");
+ comma = ",";
+ }
+ }
+
+ if (!have_values)
+ appendPQExpBufferStr(buf, "\nSELECT NULL, NULL, NULL WHERE false");
+
+ return have_values;
+}
+
+/*
+ * compile_database_list
+ *
+ * If any database patterns exist, or if --all was given, compiles a distinct
+ * list of databases to check using a SQL query based on the patterns plus the
+ * literal initial database name, if given. If no database patterns exist and
+ * --all was not given, the query is not necessary, and only the initial
+ * database name (if any) is added to the list.
+ *
+ * conn: connection to the initial database
+ * databases: the list onto which databases should be appended
+ * initial_dbname: an optional extra database name to include in the list
+ */
+static void
+compile_database_list(PGconn *conn, SimplePtrList *databases,
+ const char *initial_dbname)
+{
+ PGresult *res;
+ PQExpBufferData sql;
+ int ntups;
+ int i;
+ bool fatal;
+
+ if (initial_dbname)
+ {
+ DatabaseInfo *dat = (DatabaseInfo *) pg_malloc0(sizeof(DatabaseInfo));
+
+ /* This database is included. Add to list */
+ if (opts.verbose)
+ pg_log_info("including database \"%s\"", initial_dbname);
+
+ dat->datname = pstrdup(initial_dbname);
+ simple_ptr_list_append(databases, dat);
+ }
+
+ initPQExpBuffer(&sql);
+
+ /* Append the include patterns CTE. */
+ appendPQExpBufferStr(&sql, "WITH include_raw (pattern_id, rgx) AS (");
+ if (!append_db_pattern_cte(&sql, &opts.include, conn, true) &&
+ !opts.alldb)
+ {
+ /*
+ * None of the inclusion patterns (if any) contain database portions,
+ * so there is no need to query the database to resolve database
+ * patterns.
+ *
+ * Since we're also not operating under --all, we don't need to query
+ * the exhaustive list of connectable databases, either.
+ */
+ termPQExpBuffer(&sql);
+ return;
+ }
+
+ /* Append the exclude patterns CTE. */
+ appendPQExpBufferStr(&sql, "),\nexclude_raw (pattern_id, rgx) AS (");
+ append_db_pattern_cte(&sql, &opts.exclude, conn, false);
+ appendPQExpBufferStr(&sql, "),");
+
+ /*
+ * Append the database CTE, which includes whether each database is
+ * connectable and also joins against exclude_raw to determine whether
+ * each database is excluded.
+ */
+ appendPQExpBufferStr(&sql,
+ "\ndatabase (datname) AS ("
+ "\nSELECT d.datname "
+ "FROM pg_catalog.pg_database d "
+ "LEFT OUTER JOIN exclude_raw e "
+ "ON d.datname ~ e.rgx "
+ "\nWHERE d.datallowconn "
+ "AND e.pattern_id IS NULL"
+ "),"
+
+ /*
+ * Append the include_pat CTE, which joins the include_raw CTE against the
+ * databases CTE to determine if all the inclusion patterns had matches,
+ * and whether each matched pattern had the misfortune of only matching
+ * excluded or unconnectable databases.
+ */
+ "\ninclude_pat (pattern_id, checkable) AS ("
+ "\nSELECT i.pattern_id, "
+ "COUNT(*) FILTER ("
+ "WHERE d IS NOT NULL"
+ ") AS checkable"
+ "\nFROM include_raw i "
+ "LEFT OUTER JOIN database d "
+ "ON d.datname ~ i.rgx"
+ "\nGROUP BY i.pattern_id"
+ "),"
+
+ /*
+ * Append the filtered_databases CTE, which selects from the database CTE
+ * optionally joined against the include_raw CTE to only select databases
+ * that match an inclusion pattern. This appears to duplicate what the
+ * include_pat CTE already did above, but here we want only databases, and
+ * there we wanted patterns.
+ */
+ "\nfiltered_databases (datname) AS ("
+ "\nSELECT DISTINCT d.datname "
+ "FROM database d");
+ if (!opts.alldb)
+ appendPQExpBufferStr(&sql,
+ " INNER JOIN include_raw i "
+ "ON d.datname ~ i.rgx");
+ appendPQExpBufferStr(&sql,
+ ")"
+
+ /*
+ * Select the checkable databases and the unmatched inclusion patterns.
+ */
+ "\nSELECT pattern_id, datname FROM ("
+ "\nSELECT pattern_id, NULL::TEXT AS datname "
+ "FROM include_pat "
+ "WHERE checkable = 0 "
+ "UNION ALL"
+ "\nSELECT NULL, datname "
+ "FROM filtered_databases"
+ ") AS combined_records"
+ "\nORDER BY pattern_id NULLS LAST, datname");
+
+ res = executeQuery(conn, sql.data, opts.echo);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ pg_log_error("query failed: %s", PQerrorMessage(conn));
+ pg_log_info("query was: %s", sql.data);
+ disconnectDatabase(conn);
+ exit(1);
+ }
+ termPQExpBuffer(&sql);
+
+ ntups = PQntuples(res);
+ for (fatal = false, i = 0; i < ntups; i++)
+ {
+ int pattern_id = -1;
+ const char *datname = NULL;
+
+ if (!PQgetisnull(res, i, 0))
+ pattern_id = atoi(PQgetvalue(res, i, 0));
+ if (!PQgetisnull(res, i, 1))
+ datname = PQgetvalue(res, i, 1);
+
+ if (pattern_id >= 0)
+ {
+ /*
+ * Current record pertains to an inclusion pattern that matched no
+ * checkable databases.
+ */
+ fatal = opts.strict_names;
+ if (pattern_id >= opts.include.len)
+ {
+ pg_log_error("internal error: received unexpected database pattern_id %d",
+ pattern_id);
+ exit(1);
+ }
+ log_no_match("no connectable databases to check matching \"%s\"",
+ opts.include.data[pattern_id].pattern);
+ }
+ else
+ {
+ DatabaseInfo *dat;
+
+ /* Current record pertains to a database */
+ Assert(datname != NULL);
+
+ /* Avoid entering a duplicate entry matching the initial_dbname */
+ if (initial_dbname != NULL && strcmp(initial_dbname, datname) == 0)
+ continue;
+
+ /* This database is included. Add to list */
+ if (opts.verbose)
+ pg_log_info("including database \"%s\"", datname);
+
+ dat = (DatabaseInfo *) pg_malloc0(sizeof(DatabaseInfo));
+ dat->datname = pstrdup(datname);
+ simple_ptr_list_append(databases, dat);
+ }
+ }
+ PQclear(res);
+
+ if (fatal)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ exit(1);
+ }
+}
+
+/*
+ * append_rel_pattern_raw_cte
+ *
+ * Appends to the buffer the body of a Common Table Expression (CTE) containing
+ * the given patterns as six columns:
+ *
+ * pattern_id: the index of this pattern in pia->data[]
+ * db_regex: the database regexp parsed from the pattern, or NULL if the
+ * pattern had no database part
+ * nsp_regex: the namespace regexp parsed from the pattern, or NULL if the
+ * pattern had no namespace part
+ * 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)
+ *
+ * buf: the buffer to be appended
+ * patterns: the array of patterns to be inserted into the CTE
+ * conn: the database connection
+ */
+static void
+append_rel_pattern_raw_cte(PQExpBuffer buf, const PatternInfoArray *pia,
+ PGconn *conn)
+{
+ int pattern_id;
+ const char *comma;
+ bool have_values;
+
+ comma = "";
+ have_values = false;
+ for (pattern_id = 0; pattern_id < pia->len; pattern_id++)
+ {
+ PatternInfo *info = &pia->data[pattern_id];
+
+ if (!have_values)
+ appendPQExpBufferStr(buf, "\nVALUES");
+ have_values = true;
+ appendPQExpBuffer(buf, "%s\n(%d::INTEGER, ", comma, pattern_id);
+ if (info->db_regex == NULL)
+ appendPQExpBufferStr(buf, "NULL");
+ else
+ appendStringLiteralConn(buf, info->db_regex, conn);
+ appendPQExpBufferStr(buf, "::TEXT, ");
+ if (info->nsp_regex == NULL)
+ appendPQExpBufferStr(buf, "NULL");
+ else
+ appendStringLiteralConn(buf, info->nsp_regex, conn);
+ appendPQExpBufferStr(buf, "::TEXT, ");
+ if (info->rel_regex == NULL)
+ appendPQExpBufferStr(buf, "NULL");
+ else
+ appendStringLiteralConn(buf, info->rel_regex, conn);
+ if (info->heap_only)
+ appendPQExpBufferStr(buf, "::TEXT, true::BOOLEAN");
+ else
+ appendPQExpBufferStr(buf, "::TEXT, false::BOOLEAN");
+ if (info->btree_only)
+ appendPQExpBufferStr(buf, ", true::BOOLEAN");
+ else
+ appendPQExpBufferStr(buf, ", false::BOOLEAN");
+ appendPQExpBufferStr(buf, ")");
+ comma = ",";
+ }
+
+ if (!have_values)
+ appendPQExpBufferStr(buf,
+ "\nSELECT NULL::INTEGER, NULL::TEXT, NULL::TEXT, "
+ "NULL::TEXT, NULL::BOOLEAN, NULL::BOOLEAN "
+ "WHERE false");
+}
+
+/*
+ * append_rel_pattern_filtered_cte
+ *
+ * Appends to the buffer a Common Table Expression (CTE) which selects
+ * all patterns from the named raw CTE, filtered by database. All patterns
+ * which have no database portion or whose database portion matches our
+ * connection's database name are selected, with other patterns excluded.
+ *
+ * The basic idea here is that if we're connected to database "foo" and we have
+ * patterns "foo.bar.baz", "alpha.beta" and "one.two.three", we only want to
+ * use the first two while processing relations in this database, as the third
+ * one is not relevant.
+ *
+ * buf: the buffer to be appended
+ * raw: the name of the CTE to select from
+ * filtered: the name of the CTE to create
+ * conn: the database connection
+ */
+static void
+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 "
+ "FROM %s r"
+ "\nWHERE (r.db_regex IS NULL "
+ "OR ",
+ filtered, raw);
+ appendStringLiteralConn(buf, PQdb(conn), conn);
+ appendPQExpBufferStr(buf, " ~ r.db_regex)");
+ appendPQExpBufferStr(buf,
+ " AND (r.nsp_regex IS NOT NULL"
+ " OR r.rel_regex IS NOT NULL)"
+ "),");
+}
+
+/*
+ * compile_relation_list_one_db
+ *
+ * Compiles a list of relations to check within the currently connected
+ * database based on the user supplied options, sorted by descending size,
+ * and appends them to the given list of relations.
+ *
+ * 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
+ * database details per relation, the relation structs use references to the
+ * same database object, provided by the caller.
+ *
+ * conn: connection to this next database, which should be the same as in 'dat'
+ * relations: list onto which the relations information should be appended
+ * dat: the database info struct for use by each relation
+ * pagecount: gets incremented by the number of blocks to check in all
+ * relations added
+ */
+static void
+compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
+ const DatabaseInfo *dat,
+ uint64 *pagecount)
+{
+ PGresult *res;
+ PQExpBufferData sql;
+ int ntups;
+ int i;
+
+ initPQExpBuffer(&sql);
+ appendPQExpBufferStr(&sql, "WITH");
+
+ /* Append CTEs for the relation inclusion patterns, if any */
+ if (!opts.allrel)
+ {
+ appendPQExpBufferStr(&sql,
+ " include_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, btree_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);
+ }
+
+ /* Append CTEs for the relation exclusion patterns, if any */
+ if (opts.excludetbl || opts.excludeidx || opts.excludensp)
+ {
+ appendPQExpBufferStr(&sql,
+ " exclude_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, btree_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);
+ }
+
+ /* Append the relation CTE. */
+ appendPQExpBufferStr(&sql,
+ " relation (pattern_id, oid, nspname, relname, reltoastrelid, relpages, is_heap, is_btree) 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"
+ "\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);
+ 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);
+ 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);
+
+ /*
+ * Exclude temporary tables and indexes, which must necessarily belong to
+ * other sessions. (We don't create any ourselves.) We must ultimately
+ * exclude indexes marked invalid or not ready, but we delay that decision
+ * until firing off the amcheck command, as the state of an index may
+ * change by then.
+ */
+ appendPQExpBufferStr(&sql, "\nWHERE c.relpersistence != 't'");
+ if (opts.excludetbl || opts.excludeidx || opts.excludensp)
+ appendPQExpBufferStr(&sql, "\nAND ep.pattern_id IS NULL");
+
+ /*
+ * We need to be careful not to break the --no-dependent-toast and
+ * --no-dependent-indexes options. By default, the btree indexes, toast
+ * tables, and toast table btree indexes associated with primary heap
+ * tables are included, using their own CTEs below. We implement the
+ * --exclude-* options by not creating those CTEs, but that's no use if
+ * we've already selected the toast and indexes here. On the other hand,
+ * we want inclusion patterns that match indexes or toast tables to be
+ * honored. So, if inclusion patterns were given, we want to select all
+ * tables, toast tables, or indexes that match the patterns. But if no
+ * inclusion patterns were given, and we're simply matching all relations,
+ * then we only want to match the primary tables here.
+ */
+ if (opts.allrel)
+ appendPQExpBuffer(&sql,
+ " AND c.relam = %u "
+ "AND c.relkind IN ('r', 'm', 't') "
+ "AND c.relnamespace != %u",
+ HEAP_TABLE_AM_OID, PG_TOAST_NAMESPACE);
+ else
+ appendPQExpBuffer(&sql,
+ " AND c.relam IN (%u, %u)"
+ "AND c.relkind IN ('r', 'm', 't', 'i') "
+ "AND ((c.relam = %u AND c.relkind IN ('r', 'm', 't')) OR "
+ "(c.relam = %u AND c.relkind = 'i'))",
+ HEAP_TABLE_AM_OID, BTREE_AM_OID,
+ HEAP_TABLE_AM_OID, BTREE_AM_OID);
+
+ appendPQExpBufferStr(&sql,
+ "\nORDER BY c.oid)");
+
+ if (!opts.no_toast_expansion)
+ {
+ /*
+ * Include a CTE for toast tables associated with primary heap tables
+ * selected above, filtering by exclusion patterns (if any) that match
+ * toast table names.
+ */
+ appendPQExpBufferStr(&sql,
+ ", toast (oid, nspname, relname, relpages) AS ("
+ "\nSELECT t.oid, 'pg_toast', t.relname, t.relpages"
+ "\nFROM pg_catalog.pg_class t "
+ "INNER JOIN relation r "
+ "ON r.reltoastrelid = t.oid");
+ if (opts.excludetbl || opts.excludensp)
+ appendPQExpBufferStr(&sql,
+ "\nLEFT OUTER JOIN exclude_pat ep"
+ "\nON ('pg_toast' ~ ep.nsp_regex OR ep.nsp_regex IS NULL)"
+ "\nAND (t.relname ~ ep.rel_regex OR ep.rel_regex IS NULL)"
+ "\nAND ep.heap_only"
+ "\nWHERE ep.pattern_id IS NULL"
+ "\nAND t.relpersistence != 't'");
+ appendPQExpBufferStr(&sql,
+ "\n)");
+ }
+ if (!opts.no_btree_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.
+ */
+ appendPQExpBuffer(&sql,
+ ", index (oid, nspname, relname, relpages) AS ("
+ "\nSELECT c.oid, 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 "
+ "ON i.indexrelid = c.oid "
+ "AND c.relpersistence != 't'");
+ if (opts.excludeidx || opts.excludensp)
+ appendPQExpBufferStr(&sql,
+ "\nINNER JOIN pg_catalog.pg_namespace n "
+ "ON c.relnamespace = n.oid"
+ "\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"
+ "\nWHERE ep.pattern_id IS NULL");
+ else
+ appendPQExpBufferStr(&sql,
+ "\nWHERE true");
+ appendPQExpBuffer(&sql,
+ " AND c.relam = %u "
+ "AND c.relkind = 'i'",
+ BTREE_AM_OID);
+ if (opts.no_toast_expansion)
+ appendPQExpBuffer(&sql,
+ " AND c.relnamespace != %u",
+ PG_TOAST_NAMESPACE);
+ appendPQExpBufferStr(&sql, "\n)");
+ }
+
+ if (!opts.no_toast_expansion && !opts.no_btree_expansion)
+ {
+ /*
+ * Include a CTE for btree indexes associated with toast tables of
+ * primary heap tables selected above, filtering by exclusion patterns
+ * (if any) that match the toast index names.
+ */
+ appendPQExpBuffer(&sql,
+ ", toast_index (oid, nspname, relname, relpages) AS ("
+ "\nSELECT c.oid, 'pg_toast', c.relname, c.relpages "
+ "FROM toast t "
+ "INNER JOIN pg_catalog.pg_index i "
+ "ON t.oid = i.indrelid"
+ "\nINNER JOIN pg_catalog.pg_class c "
+ "ON i.indexrelid = c.oid "
+ "AND c.relpersistence != 't'");
+ if (opts.excludeidx)
+ appendPQExpBufferStr(&sql,
+ "\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 "
+ "WHERE ep.pattern_id IS NULL");
+ else
+ appendPQExpBufferStr(&sql,
+ "\nWHERE true");
+ appendPQExpBuffer(&sql,
+ " AND c.relam = %u"
+ " AND c.relkind = 'i')",
+ BTREE_AM_OID);
+ }
+
+ /*
+ * Roll-up distinct rows from CTEs.
+ *
+ * Relations that match more than one pattern may occur more than once in
+ * the list, and indexes and toast for primary relations may also have
+ * matched in their own right, so we rely on UNION to deduplicate the
+ * list.
+ */
+ appendPQExpBuffer(&sql,
+ "\nSELECT pattern_id, is_heap, is_btree, oid, nspname, relname, relpages "
+ "FROM (");
+ appendPQExpBufferStr(&sql,
+ /* Inclusion patterns that failed to match */
+ "\nSELECT pattern_id, is_heap, is_btree, "
+ "NULL::OID AS oid, "
+ "NULL::TEXT AS nspname, "
+ "NULL::TEXT AS relname, "
+ "NULL::INTEGER AS relpages"
+ "\nFROM relation "
+ "WHERE pattern_id IS NOT NULL "
+ "UNION"
+ /* Primary relations */
+ "\nSELECT NULL::INTEGER AS pattern_id, "
+ "is_heap, is_btree, oid, nspname, relname, relpages "
+ "FROM relation");
+ if (!opts.no_toast_expansion)
+ appendPQExpBufferStr(&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 "
+ "FROM toast");
+ if (!opts.no_btree_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 "
+ "FROM index");
+ if (!opts.no_toast_expansion && !opts.no_btree_expansion)
+ appendPQExpBufferStr(&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");
+ appendPQExpBufferStr(&sql,
+ "\n) AS combined_records "
+ "ORDER BY relpages DESC NULLS FIRST, oid");
+
+ res = executeQuery(conn, sql.data, opts.echo);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ pg_log_error("query failed: %s", PQerrorMessage(conn));
+ pg_log_info("query was: %s", sql.data);
+ disconnectDatabase(conn);
+ exit(1);
+ }
+ termPQExpBuffer(&sql);
+
+ ntups = PQntuples(res);
+ for (i = 0; i < ntups; i++)
+ {
+ int pattern_id = -1;
+ bool is_heap = false;
+ bool is_btree PG_USED_FOR_ASSERTS_ONLY = false;
+ Oid oid = InvalidOid;
+ const char *nspname = NULL;
+ const char *relname = NULL;
+ int relpages = 0;
+
+ if (!PQgetisnull(res, i, 0))
+ pattern_id = atoi(PQgetvalue(res, i, 0));
+ 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');
+ if (!PQgetisnull(res, i, 3))
+ oid = atooid(PQgetvalue(res, i, 3));
+ if (!PQgetisnull(res, i, 4))
+ nspname = PQgetvalue(res, i, 4);
+ if (!PQgetisnull(res, i, 5))
+ relname = PQgetvalue(res, i, 5);
+ if (!PQgetisnull(res, i, 6))
+ relpages = atoi(PQgetvalue(res, i, 6));
+
+ if (pattern_id >= 0)
+ {
+ /*
+ * Current record pertains to an inclusion pattern. Record that
+ * it matched.
+ */
+
+ if (pattern_id >= opts.include.len)
+ {
+ pg_log_error("internal error: received unexpected relation pattern_id %d",
+ pattern_id);
+ exit(1);
+ }
+
+ opts.include.data[pattern_id].matched = true;
+ }
+ else
+ {
+ /* Current record pertains to a relation */
+
+ RelationInfo *rel = (RelationInfo *) pg_malloc0(sizeof(RelationInfo));
+
+ Assert(OidIsValid(oid));
+ Assert((is_heap && !is_btree) || (is_btree && !is_heap));
+
+ rel->datinfo = dat;
+ rel->reloid = oid;
+ rel->is_heap = is_heap;
+ rel->nspname = pstrdup(nspname);
+ rel->relname = pstrdup(relname);
+ rel->relpages = relpages;
+ rel->blocks_to_check = relpages;
+ if (is_heap && (opts.startblock >= 0 || opts.endblock >= 0))
+ {
+ /*
+ * We apply --startblock and --endblock to heap tables, but
+ * not btree 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)
+ rel->blocks_to_check = opts.endblock + 1;
+ if (opts.startblock >= 0)
+ {
+ if (rel->blocks_to_check > opts.startblock)
+ rel->blocks_to_check -= opts.startblock;
+ else
+ rel->blocks_to_check = 0;
+ }
+ }
+ *pagecount += rel->blocks_to_check;
+
+ simple_ptr_list_append(relations, rel);
+ }
+ }
+ PQclear(res);
+}