2021-03-12 19:00:01 +01:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* pg_amcheck.c
|
|
|
|
* Detects corruption within database relations.
|
|
|
|
*
|
2024-01-04 02:49:05 +01:00
|
|
|
* Copyright (c) 2017-2024, PostgreSQL Global Development Group
|
2021-03-12 19:00:01 +01:00
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/bin/pg_amcheck/pg_amcheck.c
|
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres_fe.h"
|
|
|
|
|
2021-07-24 11:35:03 +02:00
|
|
|
#include <limits.h>
|
2021-03-12 19:00:01 +01:00
|
|
|
#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;
|
|
|
|
|
2021-04-24 16:13:07 +02:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
2021-03-12 19:00:01 +01:00
|
|
|
/* 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;
|
2023-10-27 23:21:23 +02:00
|
|
|
bool checkunique;
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
/* 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,
|
2021-04-24 16:13:07 +02:00
|
|
|
.install_missing = false,
|
|
|
|
.install_schema = "pg_catalog",
|
2021-03-12 19:00:01 +01:00
|
|
|
.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,
|
2023-10-27 23:21:23 +02:00
|
|
|
.checkunique = false,
|
2021-03-12 19:00:01 +01:00
|
|
|
.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 */
|
2023-10-27 23:21:23 +02:00
|
|
|
bool is_checkunique;
|
2021-03-12 19:00:01 +01:00
|
|
|
} 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.
|
|
|
|
*/
|
2023-09-26 12:28:57 +02:00
|
|
|
static const char *const amcheck_sql =
|
2021-03-12 19:00:01 +01:00
|
|
|
"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,
|
2022-09-20 22:09:30 +02:00
|
|
|
const DatabaseInfo *dat,
|
2021-03-12 19:00:01 +01:00
|
|
|
uint64 *pagecount);
|
|
|
|
|
|
|
|
#define log_no_match(...) do { \
|
|
|
|
if (opts.strict_names) \
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_log_error(__VA_ARGS__); \
|
2021-03-12 19:00:01 +01:00
|
|
|
else \
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_log_warning(__VA_ARGS__); \
|
2021-03-12 19:00:01 +01:00
|
|
|
} 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},
|
2021-04-24 16:13:07 +02:00
|
|
|
{"install-missing", optional_argument, NULL, 13},
|
2023-10-27 23:21:23 +02:00
|
|
|
{"checkunique", no_argument, NULL, 14},
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
{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 */
|
2022-12-12 14:33:41 +01:00
|
|
|
while ((c = getopt_long(argc, argv, "ad:D:eh:Hi:I:j:p:Pr:R:s:S:t:T:U:vwW",
|
2021-03-12 19:00:01 +01:00
|
|
|
long_options, &optindex)) != -1)
|
|
|
|
{
|
|
|
|
char *endptr;
|
2021-08-20 07:48:22 +02:00
|
|
|
unsigned long optval;
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
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':
|
2021-07-24 11:35:03 +02:00
|
|
|
if (!option_parse_int(optarg, "-j/--jobs", 1, INT_MAX,
|
|
|
|
&opts.jobs))
|
2021-03-12 19:00:01 +01:00
|
|
|
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;
|
2022-12-12 14:33:41 +01:00
|
|
|
case 'v':
|
|
|
|
opts.verbose = true;
|
|
|
|
pg_logging_increase_verbosity();
|
|
|
|
break;
|
2021-03-12 19:00:01 +01:00
|
|
|
case 'w':
|
|
|
|
prompt_password = TRI_NO;
|
|
|
|
break;
|
|
|
|
case 'W':
|
|
|
|
prompt_password = TRI_YES;
|
|
|
|
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)
|
2021-08-18 11:23:43 +02:00
|
|
|
opts.skip = "all-visible";
|
2021-03-12 19:00:01 +01:00
|
|
|
else if (pg_strcasecmp(optarg, "all-frozen") == 0)
|
2021-08-18 11:23:43 +02:00
|
|
|
opts.skip = "all-frozen";
|
|
|
|
else if (pg_strcasecmp(optarg, "none") == 0)
|
|
|
|
opts.skip = "none";
|
2021-03-12 19:00:01 +01:00
|
|
|
else
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("invalid argument for option %s", "--skip");
|
2021-03-12 19:00:01 +01:00
|
|
|
break;
|
|
|
|
case 7:
|
2021-08-20 07:48:22 +02:00
|
|
|
errno = 0;
|
|
|
|
optval = strtoul(optarg, &endptr, 10);
|
|
|
|
if (endptr == optarg || *endptr != '\0' || errno != 0)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("invalid start block");
|
2021-08-20 07:48:22 +02:00
|
|
|
if (optval > MaxBlockNumber)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("start block out of bounds");
|
2021-08-20 07:48:22 +02:00
|
|
|
opts.startblock = optval;
|
2021-03-12 19:00:01 +01:00
|
|
|
break;
|
|
|
|
case 8:
|
2021-08-20 07:48:22 +02:00
|
|
|
errno = 0;
|
|
|
|
optval = strtoul(optarg, &endptr, 10);
|
|
|
|
if (endptr == optarg || *endptr != '\0' || errno != 0)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("invalid end block");
|
2021-08-20 07:48:22 +02:00
|
|
|
if (optval > MaxBlockNumber)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("end block out of bounds");
|
2021-08-20 07:48:22 +02:00
|
|
|
opts.endblock = optval;
|
2021-03-12 19:00:01 +01:00
|
|
|
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;
|
2021-04-24 16:13:07 +02:00
|
|
|
case 13:
|
|
|
|
opts.install_missing = true;
|
|
|
|
if (optarg)
|
|
|
|
opts.install_schema = pg_strdup(optarg);
|
|
|
|
break;
|
2023-10-27 23:21:23 +02:00
|
|
|
case 14:
|
|
|
|
opts.checkunique = true;
|
|
|
|
break;
|
2021-03-12 19:00:01 +01:00
|
|
|
default:
|
2022-04-08 20:55:14 +02:00
|
|
|
/* getopt_long already emitted a complaint */
|
|
|
|
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
|
2021-03-12 19:00:01 +01:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts.endblock >= 0 && opts.endblock < opts.startblock)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("end block precedes start block");
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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]);
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
|
2021-03-12 19:00:01 +01:00
|
|
|
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)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("cannot specify a database name with --all");
|
2021-03-12 19:00:01 +01:00
|
|
|
cparams.dbname = maintenance_db;
|
|
|
|
}
|
|
|
|
else if (db != NULL)
|
|
|
|
{
|
|
|
|
if (opts.dbpattern)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("cannot specify both a database name and database patterns");
|
2021-03-12 19:00:01 +01:00
|
|
|
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);
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_log_warning("no databases to check");
|
2021-03-12 19:00:01 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-04-24 16:13:07 +02:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
|
2021-03-12 19:00:01 +01:00
|
|
|
/*
|
|
|
|
* 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));
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_log_error_detail("Query was: %s", amcheck_sql);
|
2021-03-12 19:00:01 +01:00
|
|
|
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));
|
2023-10-27 23:21:23 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check the version of amcheck extension. Skip requested unique
|
2023-10-30 19:52:35 +01:00
|
|
|
* constraint check with warning if it is not yet supported by
|
|
|
|
* amcheck.
|
2023-10-27 23:21:23 +02:00
|
|
|
*/
|
|
|
|
if (opts.checkunique == true)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Now amcheck has only major and minor versions in the string but
|
|
|
|
* we also support revision just in case. Now it is expected to be
|
|
|
|
* zero.
|
|
|
|
*/
|
|
|
|
int vmaj = 0,
|
|
|
|
vmin = 0,
|
|
|
|
vrev = 0;
|
|
|
|
const char *amcheck_version = PQgetvalue(result, 0, 1);
|
|
|
|
|
|
|
|
sscanf(amcheck_version, "%d.%d.%d", &vmaj, &vmin, &vrev);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* checkunique option is supported in amcheck since version 1.4
|
|
|
|
*/
|
|
|
|
if ((vmaj == 1 && vmin < 4) || vmaj == 0)
|
|
|
|
{
|
|
|
|
pg_log_warning("--checkunique option is not supported by amcheck "
|
|
|
|
"version \"%s\"", amcheck_version);
|
|
|
|
dat->is_checkunique = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
dat->is_checkunique = true;
|
|
|
|
}
|
|
|
|
|
2021-03-12 19:00:01 +01:00
|
|
|
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;
|
|
|
|
|
2021-08-20 12:44:54 +02:00
|
|
|
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);
|
2021-03-12 19:00:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("no relations to check");
|
2021-03-12 19:00:01 +01:00
|
|
|
}
|
|
|
|
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");
|
2021-08-07 20:34:49 +02:00
|
|
|
pg_log_info("checking heap table \"%s.%s.%s\"",
|
2021-03-12 19:00:01 +01:00
|
|
|
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");
|
|
|
|
|
2021-08-07 20:34:49 +02:00
|
|
|
pg_log_info("checking btree index \"%s.%s.%s\"",
|
2021-03-12 19:00:01 +01:00
|
|
|
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.
|
|
|
|
*
|
2021-10-13 23:08:12 +02:00
|
|
|
* The constructed SQL command will silently skip temporary tables, as checking
|
|
|
|
* them would needlessly draw errors from the underlying amcheck function.
|
|
|
|
*
|
2021-03-12 19:00:01 +01:00
|
|
|
* 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,
|
2021-10-13 23:08:12 +02:00
|
|
|
"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'",
|
2021-03-12 19:00:01 +01:00
|
|
|
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);
|
|
|
|
|
2021-10-13 23:08:12 +02:00
|
|
|
appendPQExpBuffer(sql,
|
|
|
|
"\n) v WHERE c.oid = %u "
|
|
|
|
"AND c.relpersistence != 't'",
|
|
|
|
rel->reloid);
|
2021-03-12 19:00:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*
|
2021-10-13 23:08:12 +02:00
|
|
|
* 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.
|
|
|
|
*
|
2021-03-12 19:00:01 +01:00
|
|
|
* 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,
|
2021-10-13 23:08:12 +02:00
|
|
|
"SELECT %s.bt_index_parent_check("
|
2023-10-27 23:21:23 +02:00
|
|
|
"index := c.oid, heapallindexed := %s, rootdescend := %s "
|
|
|
|
"%s)"
|
2021-10-13 23:08:12 +02:00
|
|
|
"\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",
|
2021-03-12 19:00:01 +01:00
|
|
|
rel->datinfo->amcheck_schema,
|
|
|
|
(opts.heapallindexed ? "true" : "false"),
|
2021-10-13 23:08:12 +02:00
|
|
|
(opts.rootdescend ? "true" : "false"),
|
2023-10-27 23:21:23 +02:00
|
|
|
(rel->datinfo->is_checkunique ? ", checkunique := true" : ""),
|
2021-10-13 23:08:12 +02:00
|
|
|
rel->reloid);
|
2021-03-12 19:00:01 +01:00
|
|
|
else
|
|
|
|
appendPQExpBuffer(sql,
|
2021-10-13 23:08:12 +02:00
|
|
|
"SELECT %s.bt_index_check("
|
2023-10-27 23:21:23 +02:00
|
|
|
"index := c.oid, heapallindexed := %s "
|
|
|
|
"%s)"
|
2021-10-13 23:08:12 +02:00
|
|
|
"\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",
|
2021-03-12 19:00:01 +01:00
|
|
|
rel->datinfo->amcheck_schema,
|
2021-10-13 23:08:12 +02:00
|
|
|
(opts.heapallindexed ? "true" : "false"),
|
2023-10-27 23:21:23 +02:00
|
|
|
(rel->datinfo->is_checkunique ? ", checkunique := true" : ""),
|
2021-10-13 23:08:12 +02:00
|
|
|
rel->reloid);
|
2021-03-12 19:00:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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));
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_log_error_detail("Command was: %s", sql);
|
2021-03-12 19:00:01 +01:00
|
|
|
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);
|
2022-06-06 17:26:57 +02:00
|
|
|
if (severity == NULL)
|
|
|
|
return false; /* libpq failure, probably lost connection */
|
2021-03-12 19:00:01 +01:00
|
|
|
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:
|
2021-03-15 22:13:42 +01:00
|
|
|
case PGRES_PIPELINE_SYNC:
|
|
|
|
case PGRES_PIPELINE_ABORTED:
|
Support retrieval of results in chunks with libpq.
This patch generalizes libpq's existing single-row mode to allow
individual partial-result PGresults to contain up to N rows, rather
than always one row. This reduces malloc overhead compared to plain
single-row mode, and it is very useful for psql's FETCH_COUNT feature,
since otherwise we'd have to add code (and cycles) to either merge
single-row PGresults into a bigger one or teach psql's
results-printing logic to accept arrays of PGresults.
To avoid API breakage, PQsetSingleRowMode() remains the same, and we
add a new function PQsetChunkedRowsMode() to invoke the more general
case. Also, PGresults obtained the old way continue to carry the
PGRES_SINGLE_TUPLE status code, while if PQsetChunkedRowsMode() is
used then their status code is PGRES_TUPLES_CHUNK. The underlying
logic is the same either way, though.
Daniel Vérité, reviewed by Laurenz Albe and myself (and whacked
around a bit by me, so any remaining bugs are my fault)
Discussion: https://postgr.es/m/CAKZiRmxsVTkO928CM+-ADvsMyePmU3L9DQCa9NwqjvLPcEe5QA@mail.gmail.com
2024-04-07 02:41:32 +02:00
|
|
|
case PGRES_TUPLES_CHUNK:
|
2021-03-12 19:00:01 +01:00
|
|
|
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))
|
2021-08-13 17:15:03 +02:00
|
|
|
printf(_("heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"),
|
2021-03-12 19:00:01 +01:00
|
|
|
rel->datinfo->datname, rel->nspname, rel->relname,
|
|
|
|
PQgetvalue(res, i, 0), /* blkno */
|
|
|
|
PQgetvalue(res, i, 1), /* offnum */
|
2021-08-13 17:15:03 +02:00
|
|
|
PQgetvalue(res, i, 2)); /* attnum */
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
else if (!PQgetisnull(res, i, 1))
|
2021-08-13 17:15:03 +02:00
|
|
|
printf(_("heap table \"%s.%s.%s\", block %s, offset %s:\n"),
|
2021-03-12 19:00:01 +01:00
|
|
|
rel->datinfo->datname, rel->nspname, rel->relname,
|
|
|
|
PQgetvalue(res, i, 0), /* blkno */
|
2021-08-13 17:15:03 +02:00
|
|
|
PQgetvalue(res, i, 1)); /* offnum */
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
else if (!PQgetisnull(res, i, 0))
|
2021-08-13 17:15:03 +02:00
|
|
|
printf(_("heap table \"%s.%s.%s\", block %s:\n"),
|
2021-03-12 19:00:01 +01:00
|
|
|
rel->datinfo->datname, rel->nspname, rel->relname,
|
2021-08-13 17:15:03 +02:00
|
|
|
PQgetvalue(res, i, 0)); /* blkno */
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
else
|
2021-08-13 17:15:03 +02:00
|
|
|
printf(_("heap table \"%s.%s.%s\":\n"),
|
|
|
|
rel->datinfo->datname, rel->nspname, rel->relname);
|
|
|
|
|
|
|
|
printf(" %s\n", msg);
|
2021-03-12 19:00:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
|
|
{
|
|
|
|
char *msg = indent_lines(PQerrorMessage(conn));
|
|
|
|
|
|
|
|
all_checks_pass = false;
|
2021-08-13 17:15:03 +02:00
|
|
|
printf(_("heap table \"%s.%s.%s\":\n"),
|
|
|
|
rel->datinfo->datname, rel->nspname, rel->relname);
|
|
|
|
printf("%s", msg);
|
2021-03-12 19:00:01 +01:00
|
|
|
if (opts.verbose)
|
2021-08-07 13:36:59 +02:00
|
|
|
printf(_("query was: %s\n"), rel->sql);
|
2021-03-12 19:00:01 +01:00
|
|
|
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)
|
|
|
|
{
|
2021-10-13 23:08:12 +02:00
|
|
|
int ntups = PQntuples(res);
|
2021-03-12 19:00:01 +01:00
|
|
|
|
2021-10-13 23:08:12 +02:00
|
|
|
if (ntups > 1)
|
2021-03-12 19:00:01 +01:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We expect the btree checking functions to return one void row
|
2021-10-13 23:08:12 +02:00
|
|
|
* 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.
|
2021-03-12 19:00:01 +01:00
|
|
|
*
|
|
|
|
* 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");
|
2021-08-07 20:34:49 +02:00
|
|
|
pg_log_warning("btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d",
|
2021-03-12 19:00:01 +01:00
|
|
|
rel->datinfo->datname, rel->nspname, rel->relname, ntups);
|
|
|
|
if (opts.verbose)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_log_warning_detail("Query was: %s", rel->sql);
|
|
|
|
pg_log_warning_hint("Are %s's and amcheck's versions compatible?",
|
|
|
|
progname);
|
2021-03-12 19:00:01 +01:00
|
|
|
progress_since_last_stderr = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char *msg = indent_lines(PQerrorMessage(conn));
|
|
|
|
|
|
|
|
all_checks_pass = false;
|
2021-08-13 17:15:03 +02:00
|
|
|
printf(_("btree index \"%s.%s.%s\":\n"),
|
|
|
|
rel->datinfo->datname, rel->nspname, rel->relname);
|
|
|
|
printf("%s", msg);
|
2021-03-12 19:00:01 +01:00
|
|
|
if (opts.verbose)
|
2021-08-07 13:36:59 +02:00
|
|
|
printf(_("query was: %s\n"), rel->sql);
|
2021-03-12 19:00:01 +01:00
|
|
|
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)
|
|
|
|
{
|
2021-05-13 08:09:53 +02:00
|
|
|
printf(_("%s checks objects in a PostgreSQL database for corruption.\n\n"), progname);
|
2021-04-12 19:04:33 +02:00
|
|
|
printf(_("Usage:\n"));
|
|
|
|
printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
|
2021-05-13 08:09:53 +02:00
|
|
|
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"));
|
2021-08-07 20:34:49 +02:00
|
|
|
printf(_(" --heapallindexed check that all heap tuples are found within indexes\n"));
|
2021-05-13 08:09:53 +02:00
|
|
|
printf(_(" --parent-check check index parent/child relationships\n"));
|
|
|
|
printf(_(" --rootdescend search from root page to refind tuples\n"));
|
2023-10-27 23:21:23 +02:00
|
|
|
printf(_(" --checkunique check unique constraint if index is unique\n"));
|
2021-04-12 19:04:33 +02:00
|
|
|
printf(_("\nConnection options:\n"));
|
2021-05-13 08:09:53 +02:00
|
|
|
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"));
|
2021-06-03 06:55:04 +02:00
|
|
|
printf(_(" -P, --progress show progress information\n"));
|
2021-05-13 08:09:53 +02:00
|
|
|
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"));
|
2021-06-03 06:55:04 +02:00
|
|
|
printf(_(" -?, --help show this help, then exit\n"));
|
2021-04-12 19:04:33 +02:00
|
|
|
|
|
|
|
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
|
|
|
|
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
|
2021-03-12 19:00:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
|
2021-09-15 09:19:01 +02:00
|
|
|
snprintf(checked_rel, sizeof(checked_rel), UINT64_FORMAT, relations_checked);
|
|
|
|
snprintf(total_rel, sizeof(total_rel), UINT64_FORMAT, relations_total);
|
|
|
|
snprintf(checked_pages, sizeof(checked_pages), UINT64_FORMAT, relpages_checked);
|
|
|
|
snprintf(total_pages, sizeof(total_pages), UINT64_FORMAT, relpages_total);
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
#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,
|
2021-08-07 20:34:49 +02:00
|
|
|
_("%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"),
|
2021-03-12 19:00:01 +01:00
|
|
|
(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,
|
2021-08-07 20:34:49 +02:00
|
|
|
_("%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"),
|
2021-03-12 19:00:01 +01:00
|
|
|
(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,
|
2021-08-07 20:34:49 +02:00
|
|
|
_("%*s/%s relations (%d%%), %*s/%s pages (%d%%)"),
|
2021-03-12 19:00:01 +01:00
|
|
|
(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;
|
Allow db.schema.table patterns, but complain about random garbage.
psql, pg_dump, and pg_amcheck share code to process object name
patterns like 'foo*.bar*' to match all tables with names starting in
'bar' that are in schemas starting with 'foo'. Before v14, any number
of extra name parts were silently ignored, so a command line '\d
foo.bar.baz.bletch.quux' was interpreted as '\d bletch.quux'. In v14,
as a result of commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868, we
instead treated this as a request for table quux in a schema named
'foo.bar.baz.bletch'. That caused problems for people like Justin
Pryzby who were accustomed to copying strings of the form
db.schema.table from messages generated by PostgreSQL itself and using
them as arguments to \d.
Accordingly, revise things so that if an object name pattern contains
more parts than we're expecting, we throw an error, unless there's
exactly one extra part and it matches the current database name.
That way, thisdb.myschema.mytable is accepted as meaning just
myschema.mytable, but otherdb.myschema.mytable is an error, and so
is some.random.garbage.myschema.mytable.
Mark Dilger, per report from Justin Pryzby and discussion among
various people.
Discussion: https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
2022-04-20 17:02:35 +02:00
|
|
|
int dotcnt;
|
2021-03-12 19:00:01 +01:00
|
|
|
PatternInfo *info = extend_pattern_info_array(pia);
|
|
|
|
|
|
|
|
initPQExpBuffer(&buf);
|
Allow db.schema.table patterns, but complain about random garbage.
psql, pg_dump, and pg_amcheck share code to process object name
patterns like 'foo*.bar*' to match all tables with names starting in
'bar' that are in schemas starting with 'foo'. Before v14, any number
of extra name parts were silently ignored, so a command line '\d
foo.bar.baz.bletch.quux' was interpreted as '\d bletch.quux'. In v14,
as a result of commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868, we
instead treated this as a request for table quux in a schema named
'foo.bar.baz.bletch'. That caused problems for people like Justin
Pryzby who were accustomed to copying strings of the form
db.schema.table from messages generated by PostgreSQL itself and using
them as arguments to \d.
Accordingly, revise things so that if an object name pattern contains
more parts than we're expecting, we throw an error, unless there's
exactly one extra part and it matches the current database name.
That way, thisdb.myschema.mytable is accepted as meaning just
myschema.mytable, but otherdb.myschema.mytable is an error, and so
is some.random.garbage.myschema.mytable.
Mark Dilger, per report from Justin Pryzby and discussion among
various people.
Discussion: https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
2022-04-20 17:02:35 +02:00
|
|
|
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);
|
|
|
|
}
|
2021-03-12 19:00:01 +01:00
|
|
|
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;
|
Allow db.schema.table patterns, but complain about random garbage.
psql, pg_dump, and pg_amcheck share code to process object name
patterns like 'foo*.bar*' to match all tables with names starting in
'bar' that are in schemas starting with 'foo'. Before v14, any number
of extra name parts were silently ignored, so a command line '\d
foo.bar.baz.bletch.quux' was interpreted as '\d bletch.quux'. In v14,
as a result of commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868, we
instead treated this as a request for table quux in a schema named
'foo.bar.baz.bletch'. That caused problems for people like Justin
Pryzby who were accustomed to copying strings of the form
db.schema.table from messages generated by PostgreSQL itself and using
them as arguments to \d.
Accordingly, revise things so that if an object name pattern contains
more parts than we're expecting, we throw an error, unless there's
exactly one extra part and it matches the current database name.
That way, thisdb.myschema.mytable is accepted as meaning just
myschema.mytable, but otherdb.myschema.mytable is an error, and so
is some.random.garbage.myschema.mytable.
Mark Dilger, per report from Justin Pryzby and discussion among
various people.
Discussion: https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
2022-04-20 17:02:35 +02:00
|
|
|
int dotcnt;
|
2021-03-12 19:00:01 +01:00
|
|
|
PatternInfo *info = extend_pattern_info_array(pia);
|
|
|
|
|
|
|
|
initPQExpBuffer(&dbbuf);
|
|
|
|
initPQExpBuffer(&nspbuf);
|
|
|
|
|
Allow db.schema.table patterns, but complain about random garbage.
psql, pg_dump, and pg_amcheck share code to process object name
patterns like 'foo*.bar*' to match all tables with names starting in
'bar' that are in schemas starting with 'foo'. Before v14, any number
of extra name parts were silently ignored, so a command line '\d
foo.bar.baz.bletch.quux' was interpreted as '\d bletch.quux'. In v14,
as a result of commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868, we
instead treated this as a request for table quux in a schema named
'foo.bar.baz.bletch'. That caused problems for people like Justin
Pryzby who were accustomed to copying strings of the form
db.schema.table from messages generated by PostgreSQL itself and using
them as arguments to \d.
Accordingly, revise things so that if an object name pattern contains
more parts than we're expecting, we throw an error, unless there's
exactly one extra part and it matches the current database name.
That way, thisdb.myschema.mytable is accepted as meaning just
myschema.mytable, but otherdb.myschema.mytable is an error, and so
is some.random.garbage.myschema.mytable.
Mark Dilger, per report from Justin Pryzby and discussion among
various people.
Discussion: https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
2022-04-20 17:02:35 +02:00
|
|
|
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);
|
|
|
|
}
|
2021-03-12 19:00:01 +01:00
|
|
|
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;
|
Allow db.schema.table patterns, but complain about random garbage.
psql, pg_dump, and pg_amcheck share code to process object name
patterns like 'foo*.bar*' to match all tables with names starting in
'bar' that are in schemas starting with 'foo'. Before v14, any number
of extra name parts were silently ignored, so a command line '\d
foo.bar.baz.bletch.quux' was interpreted as '\d bletch.quux'. In v14,
as a result of commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868, we
instead treated this as a request for table quux in a schema named
'foo.bar.baz.bletch'. That caused problems for people like Justin
Pryzby who were accustomed to copying strings of the form
db.schema.table from messages generated by PostgreSQL itself and using
them as arguments to \d.
Accordingly, revise things so that if an object name pattern contains
more parts than we're expecting, we throw an error, unless there's
exactly one extra part and it matches the current database name.
That way, thisdb.myschema.mytable is accepted as meaning just
myschema.mytable, but otherdb.myschema.mytable is an error, and so
is some.random.garbage.myschema.mytable.
Mark Dilger, per report from Justin Pryzby and discussion among
various people.
Discussion: https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
2022-04-20 17:02:35 +02:00
|
|
|
int dotcnt;
|
2021-03-12 19:00:01 +01:00
|
|
|
PatternInfo *info = extend_pattern_info_array(pia);
|
|
|
|
|
|
|
|
initPQExpBuffer(&dbbuf);
|
|
|
|
initPQExpBuffer(&nspbuf);
|
|
|
|
initPQExpBuffer(&relbuf);
|
|
|
|
|
Allow db.schema.table patterns, but complain about random garbage.
psql, pg_dump, and pg_amcheck share code to process object name
patterns like 'foo*.bar*' to match all tables with names starting in
'bar' that are in schemas starting with 'foo'. Before v14, any number
of extra name parts were silently ignored, so a command line '\d
foo.bar.baz.bletch.quux' was interpreted as '\d bletch.quux'. In v14,
as a result of commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868, we
instead treated this as a request for table quux in a schema named
'foo.bar.baz.bletch'. That caused problems for people like Justin
Pryzby who were accustomed to copying strings of the form
db.schema.table from messages generated by PostgreSQL itself and using
them as arguments to \d.
Accordingly, revise things so that if an object name pattern contains
more parts than we're expecting, we throw an error, unless there's
exactly one extra part and it matches the current database name.
That way, thisdb.myschema.mytable is accepted as meaning just
myschema.mytable, but otherdb.myschema.mytable is an error, and so
is some.random.garbage.myschema.mytable.
Mark Dilger, per report from Justin Pryzby and discussion among
various people.
Discussion: https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
2022-04-20 17:02:35 +02:00
|
|
|
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);
|
|
|
|
}
|
2021-03-12 19:00:01 +01:00
|
|
|
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);
|
2022-09-06 03:19:44 +02:00
|
|
|
appendPQExpBufferChar(buf, ')');
|
2021-03-12 19:00:01 +01:00
|
|
|
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)
|
2021-05-13 08:09:53 +02:00
|
|
|
pg_log_info("including database \"%s\"", initial_dbname);
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
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 "
|
Handle DROP DATABASE getting interrupted
Until now, when DROP DATABASE got interrupted in the wrong moment, the removal
of the pg_database row would also roll back, even though some irreversible
steps have already been taken. E.g. DropDatabaseBuffers() might have thrown
out dirty buffers, or files could have been unlinked. But we continued to
allow connections to such a corrupted database.
To fix this, mark databases invalid with an in-place update, just before
starting to perform irreversible steps. As we can't add a new column in the
back branches, we use pg_database.datconnlimit = -2 for this purpose.
An invalid database cannot be connected to anymore, but can still be
dropped.
Unfortunately we can't easily add output to psql's \l to indicate that some
database is invalid, it doesn't fit in any of the existing columns.
Add tests verifying that a interrupted DROP DATABASE is handled correctly in
the backend and in various tools.
Reported-by: Evgeny Morozov <postgresql3@realityexists.net>
Author: Andres Freund <andres@anarazel.de>
Reviewed-by: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Thomas Munro <thomas.munro@gmail.com>
Discussion: https://postgr.es/m/20230509004637.cgvmfwrbht7xm7p6@awork3.anarazel.de
Discussion: https://postgr.es/m/20230314174521.74jl6ffqsee5mtug@awork3.anarazel.de
Backpatch: 11-, bug present in all supported versions
2023-07-13 22:03:28 +02:00
|
|
|
"\nWHERE d.datallowconn AND datconnlimit != -2 "
|
2021-03-12 19:00:01 +01:00
|
|
|
"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));
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_log_error_detail("Query was: %s", sql.data);
|
2021-03-12 19:00:01 +01:00
|
|
|
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)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("internal error: received unexpected database pattern_id %d",
|
|
|
|
pattern_id);
|
2021-03-12 19:00:01 +01:00
|
|
|
log_no_match("no connectable databases to check matching \"%s\"",
|
|
|
|
opts.include.data[pattern_id].pattern);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-03-12 20:35:10 +01:00
|
|
|
DatabaseInfo *dat;
|
|
|
|
|
2021-03-12 19:00:01 +01:00
|
|
|
/* 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)
|
2021-05-13 08:09:53 +02:00
|
|
|
pg_log_info("including database \"%s\"", datname);
|
2021-03-12 19:00:01 +01:00
|
|
|
|
2021-03-12 20:35:10 +01:00
|
|
|
dat = (DatabaseInfo *) pg_malloc0(sizeof(DatabaseInfo));
|
2021-03-12 19:00:01 +01:00
|
|
|
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");
|
2022-09-06 03:19:44 +02:00
|
|
|
appendPQExpBufferChar(buf, ')');
|
2021-03-12 19:00:01 +01:00
|
|
|
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);
|
|
|
|
|
2021-10-13 23:08:12 +02:00
|
|
|
/*
|
|
|
|
* 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'");
|
2021-03-12 19:00:01 +01:00
|
|
|
if (opts.excludetbl || opts.excludeidx || opts.excludensp)
|
2021-10-13 23:08:12 +02:00
|
|
|
appendPQExpBufferStr(&sql, "\nAND ep.pattern_id IS NULL");
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 "
|
2021-09-28 15:26:25 +02:00
|
|
|
"AND c.relkind IN ('r', 'S', 'm', 't') "
|
2021-03-12 19:00:01 +01:00
|
|
|
"AND c.relnamespace != %u",
|
|
|
|
HEAP_TABLE_AM_OID, PG_TOAST_NAMESPACE);
|
|
|
|
else
|
|
|
|
appendPQExpBuffer(&sql,
|
|
|
|
" AND c.relam IN (%u, %u)"
|
2021-09-28 15:26:25 +02:00
|
|
|
"AND c.relkind IN ('r', 'S', 'm', 't', 'i') "
|
|
|
|
"AND ((c.relam = %u AND c.relkind IN ('r', 'S', 'm', 't')) OR "
|
2021-03-12 19:00:01 +01:00
|
|
|
"(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"
|
2021-10-13 23:08:12 +02:00
|
|
|
"\nWHERE ep.pattern_id IS NULL"
|
|
|
|
"\nAND t.relpersistence != 't'");
|
2021-03-12 19:00:01 +01:00
|
|
|
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.
|
|
|
|
*/
|
2022-09-06 03:19:44 +02:00
|
|
|
appendPQExpBufferStr(&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'");
|
2021-03-12 19:00:01 +01:00
|
|
|
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.
|
|
|
|
*/
|
2022-09-06 03:19:44 +02:00
|
|
|
appendPQExpBufferStr(&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'");
|
2021-03-12 19:00:01 +01:00
|
|
|
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.
|
|
|
|
*/
|
2022-09-06 03:19:44 +02:00
|
|
|
appendPQExpBufferStr(&sql,
|
|
|
|
"\nSELECT pattern_id, is_heap, is_btree, oid, nspname, relname, relpages "
|
|
|
|
"FROM (");
|
2021-03-12 19:00:01 +01:00
|
|
|
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));
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_log_error_detail("Query was: %s", sql.data);
|
2021-03-12 19:00:01 +01:00
|
|
|
disconnectDatabase(conn);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
termPQExpBuffer(&sql);
|
|
|
|
|
|
|
|
ntups = PQntuples(res);
|
|
|
|
for (i = 0; i < ntups; i++)
|
|
|
|
{
|
|
|
|
int pattern_id = -1;
|
|
|
|
bool is_heap = false;
|
2021-03-12 21:04:10 +01:00
|
|
|
bool is_btree PG_USED_FOR_ASSERTS_ONLY = false;
|
2021-03-12 19:00:01 +01:00
|
|
|
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)
|
2022-04-08 20:55:14 +02:00
|
|
|
pg_fatal("internal error: received unexpected relation pattern_id %d",
|
|
|
|
pattern_id);
|
2021-03-12 19:00:01 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|