Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_amcheck.c
4 : : * Detects corruption within database relations.
5 : : *
6 : : * Copyright (c) 2017-2026, PostgreSQL Global Development Group
7 : : *
8 : : * IDENTIFICATION
9 : : * src/bin/pg_amcheck/pg_amcheck.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : : #include "postgres_fe.h"
14 : :
15 : : #include <limits.h>
16 : : #include <time.h>
17 : :
18 : : #include "catalog/pg_am_d.h"
19 : : #include "catalog/pg_class_d.h"
20 : : #include "catalog/pg_namespace_d.h"
21 : : #include "common/logging.h"
22 : : #include "common/username.h"
23 : : #include "fe_utils/cancel.h"
24 : : #include "fe_utils/option_utils.h"
25 : : #include "fe_utils/parallel_slot.h"
26 : : #include "fe_utils/query_utils.h"
27 : : #include "fe_utils/simple_list.h"
28 : : #include "fe_utils/string_utils.h"
29 : : #include "getopt_long.h"
30 : : #include "pgtime.h"
31 : : #include "storage/block.h"
32 : :
33 : : typedef struct PatternInfo
34 : : {
35 : : const char *pattern; /* Unaltered pattern from the command line */
36 : : char *db_regex; /* Database regexp parsed from pattern, or
37 : : * NULL */
38 : : char *nsp_regex; /* Schema regexp parsed from pattern, or NULL */
39 : : char *rel_regex; /* Relation regexp parsed from pattern, or
40 : : * NULL */
41 : : bool heap_only; /* true if rel_regex should only match heap
42 : : * tables */
43 : : bool btree_only; /* true if rel_regex should only match btree
44 : : * indexes */
45 : : bool matched; /* true if the pattern matched in any database */
46 : : } PatternInfo;
47 : :
48 : : typedef struct PatternInfoArray
49 : : {
50 : : PatternInfo *data;
51 : : size_t len;
52 : : } PatternInfoArray;
53 : :
54 : : /* pg_amcheck command line options controlled by user flags */
55 : : typedef struct AmcheckOptions
56 : : {
57 : : bool dbpattern;
58 : : bool alldb;
59 : : bool echo;
60 : : bool verbose;
61 : : bool strict_names;
62 : : bool show_progress;
63 : : int jobs;
64 : :
65 : : /*
66 : : * Whether to install missing extensions, and optionally the name of the
67 : : * schema in which to install the extension's objects.
68 : : */
69 : : bool install_missing;
70 : : char *install_schema;
71 : :
72 : : /* Objects to check or not to check, as lists of PatternInfo structs. */
73 : : PatternInfoArray include;
74 : : PatternInfoArray exclude;
75 : :
76 : : /*
77 : : * As an optimization, if any pattern in the exclude list applies to heap
78 : : * tables, or similarly if any such pattern applies to btree indexes, or
79 : : * to schemas, then these will be true, otherwise false. These should
80 : : * always agree with what you'd conclude by grep'ing through the exclude
81 : : * list.
82 : : */
83 : : bool excludetbl;
84 : : bool excludeidx;
85 : : bool excludensp;
86 : :
87 : : /*
88 : : * If any inclusion pattern exists, then we should only be checking
89 : : * matching relations rather than all relations, so this is true iff
90 : : * include is empty.
91 : : */
92 : : bool allrel;
93 : :
94 : : /* heap table checking options */
95 : : bool no_toast_expansion;
96 : : bool reconcile_toast;
97 : : bool on_error_stop;
98 : : int64 startblock;
99 : : int64 endblock;
100 : : const char *skip;
101 : :
102 : : /* btree index checking options */
103 : : bool parent_check;
104 : : bool rootdescend;
105 : : bool heapallindexed;
106 : : bool checkunique;
107 : :
108 : : /* heap and btree hybrid option */
109 : : bool no_btree_expansion;
110 : : } AmcheckOptions;
111 : :
112 : : static AmcheckOptions opts = {
113 : : .dbpattern = false,
114 : : .alldb = false,
115 : : .echo = false,
116 : : .verbose = false,
117 : : .strict_names = true,
118 : : .show_progress = false,
119 : : .jobs = 1,
120 : : .install_missing = false,
121 : : .install_schema = "pg_catalog",
122 : : .include = {NULL, 0},
123 : : .exclude = {NULL, 0},
124 : : .excludetbl = false,
125 : : .excludeidx = false,
126 : : .excludensp = false,
127 : : .allrel = true,
128 : : .no_toast_expansion = false,
129 : : .reconcile_toast = true,
130 : : .on_error_stop = false,
131 : : .startblock = -1,
132 : : .endblock = -1,
133 : : .skip = "none",
134 : : .parent_check = false,
135 : : .rootdescend = false,
136 : : .heapallindexed = false,
137 : : .checkunique = false,
138 : : .no_btree_expansion = false
139 : : };
140 : :
141 : : static const char *progname = NULL;
142 : :
143 : : /* Whether all relations have so far passed their corruption checks */
144 : : static bool all_checks_pass = true;
145 : :
146 : : /* Time last progress report was displayed */
147 : : static pg_time_t last_progress_report = 0;
148 : : static bool progress_since_last_stderr = false;
149 : :
150 : : typedef struct DatabaseInfo
151 : : {
152 : : char *datname;
153 : : char *amcheck_schema; /* escaped, quoted literal */
154 : : bool is_checkunique;
155 : : } DatabaseInfo;
156 : :
157 : : typedef struct RelationInfo
158 : : {
159 : : const DatabaseInfo *datinfo; /* shared by other relinfos */
160 : : Oid reloid;
161 : : bool is_heap; /* true if heap, false if btree */
162 : : char *nspname;
163 : : char *relname;
164 : : int relpages;
165 : : int blocks_to_check;
166 : : char *sql; /* set during query run, pg_free'd after */
167 : : } RelationInfo;
168 : :
169 : : /*
170 : : * Query for determining if contrib's amcheck is installed. If so, selects the
171 : : * namespace name where amcheck's functions can be found.
172 : : */
173 : : static const char *const amcheck_sql =
174 : : "SELECT n.nspname, x.extversion FROM pg_catalog.pg_extension x"
175 : : "\nJOIN pg_catalog.pg_namespace n ON x.extnamespace = n.oid"
176 : : "\nWHERE x.extname = 'amcheck'";
177 : :
178 : : static void prepare_heap_command(PQExpBuffer sql, RelationInfo *rel,
179 : : PGconn *conn);
180 : : static void prepare_btree_command(PQExpBuffer sql, RelationInfo *rel,
181 : : PGconn *conn);
182 : : static void run_command(ParallelSlot *slot, const char *sql);
183 : : static bool verify_heap_slot_handler(PGresult *res, PGconn *conn,
184 : : void *context);
185 : : static bool verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context);
186 : : static void help(const char *progname);
187 : : static void progress_report(uint64 relations_total, uint64 relations_checked,
188 : : uint64 relpages_total, uint64 relpages_checked,
189 : : const char *datname, bool force, bool finished);
190 : :
191 : : static void append_database_pattern(PatternInfoArray *pia, const char *pattern,
192 : : int encoding);
193 : : static void append_schema_pattern(PatternInfoArray *pia, const char *pattern,
194 : : int encoding);
195 : : static void append_relation_pattern(PatternInfoArray *pia, const char *pattern,
196 : : int encoding);
197 : : static void append_heap_pattern(PatternInfoArray *pia, const char *pattern,
198 : : int encoding);
199 : : static void append_btree_pattern(PatternInfoArray *pia, const char *pattern,
200 : : int encoding);
201 : : static void compile_database_list(PGconn *conn, SimplePtrList *databases,
202 : : const char *initial_dbname);
203 : : static void compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
204 : : const DatabaseInfo *dat,
205 : : uint64 *pagecount);
206 : :
207 : : #define log_no_match(...) do { \
208 : : if (opts.strict_names) \
209 : : pg_log_error(__VA_ARGS__); \
210 : : else \
211 : : pg_log_warning(__VA_ARGS__); \
212 : : } while(0)
213 : :
214 : : #define FREE_AND_SET_NULL(x) do { \
215 : : pg_free(x); \
216 : : (x) = NULL; \
217 : : } while (0)
218 : :
219 : : int
220 : 61 : main(int argc, char *argv[])
221 : : {
222 : 61 : PGconn *conn = NULL;
223 : : SimplePtrListCell *cell;
224 : 61 : SimplePtrList databases = {NULL, NULL};
225 : 61 : SimplePtrList relations = {NULL, NULL};
226 : 61 : bool failed = false;
227 : : const char *latest_datname;
228 : : int parallel_workers;
229 : : ParallelSlotArray *sa;
230 : : PQExpBufferData sql;
231 : 61 : uint64 reltotal = 0;
232 : 61 : uint64 pageschecked = 0;
233 : 61 : uint64 pagestotal = 0;
234 : 61 : uint64 relprogress = 0;
235 : :
236 : : static struct option long_options[] = {
237 : : /* Connection options */
238 : : {"host", required_argument, NULL, 'h'},
239 : : {"port", required_argument, NULL, 'p'},
240 : : {"username", required_argument, NULL, 'U'},
241 : : {"no-password", no_argument, NULL, 'w'},
242 : : {"password", no_argument, NULL, 'W'},
243 : : {"maintenance-db", required_argument, NULL, 1},
244 : :
245 : : /* check options */
246 : : {"all", no_argument, NULL, 'a'},
247 : : {"database", required_argument, NULL, 'd'},
248 : : {"exclude-database", required_argument, NULL, 'D'},
249 : : {"echo", no_argument, NULL, 'e'},
250 : : {"index", required_argument, NULL, 'i'},
251 : : {"exclude-index", required_argument, NULL, 'I'},
252 : : {"jobs", required_argument, NULL, 'j'},
253 : : {"progress", no_argument, NULL, 'P'},
254 : : {"relation", required_argument, NULL, 'r'},
255 : : {"exclude-relation", required_argument, NULL, 'R'},
256 : : {"schema", required_argument, NULL, 's'},
257 : : {"exclude-schema", required_argument, NULL, 'S'},
258 : : {"table", required_argument, NULL, 't'},
259 : : {"exclude-table", required_argument, NULL, 'T'},
260 : : {"verbose", no_argument, NULL, 'v'},
261 : : {"no-dependent-indexes", no_argument, NULL, 2},
262 : : {"no-dependent-toast", no_argument, NULL, 3},
263 : : {"exclude-toast-pointers", no_argument, NULL, 4},
264 : : {"on-error-stop", no_argument, NULL, 5},
265 : : {"skip", required_argument, NULL, 6},
266 : : {"startblock", required_argument, NULL, 7},
267 : : {"endblock", required_argument, NULL, 8},
268 : : {"rootdescend", no_argument, NULL, 9},
269 : : {"no-strict-names", no_argument, NULL, 10},
270 : : {"heapallindexed", no_argument, NULL, 11},
271 : : {"parent-check", no_argument, NULL, 12},
272 : : {"install-missing", optional_argument, NULL, 13},
273 : : {"checkunique", no_argument, NULL, 14},
274 : :
275 : : {NULL, 0, NULL, 0}
276 : : };
277 : :
278 : : int optindex;
279 : : int c;
280 : :
281 : 61 : const char *db = NULL;
282 : 61 : const char *maintenance_db = NULL;
283 : :
284 : 61 : const char *host = NULL;
285 : 61 : const char *port = NULL;
286 : 61 : const char *username = NULL;
287 : 61 : enum trivalue prompt_password = TRI_DEFAULT;
288 : 61 : int encoding = pg_get_encoding_from_locale(NULL, false);
289 : : ConnParams cparams;
290 : :
291 : 61 : pg_logging_init(argv[0]);
292 : 61 : progname = get_progname(argv[0]);
293 : 61 : set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_amcheck"));
294 : :
295 : 61 : handle_help_version_opts(argc, argv, progname, help);
296 : :
297 : : /* process command-line options */
298 : 219 : while ((c = getopt_long(argc, argv, "ad:D:eh:Hi:I:j:p:Pr:R:s:S:t:T:U:vwW",
299 [ + + ]: 219 : long_options, &optindex)) != -1)
300 : : {
301 : : char *endptr;
302 : : unsigned long optval;
303 : :
304 [ + + + - : 172 : switch (c)
- + + - +
- + - + +
+ + + - -
- - + + +
- - + + +
+ + + - +
+ ]
305 : : {
306 : 4 : case 'a':
307 : 4 : opts.alldb = true;
308 : 4 : break;
309 : 33 : case 'd':
310 : 33 : opts.dbpattern = true;
311 : 33 : append_database_pattern(&opts.include, optarg, encoding);
312 : 31 : break;
313 : 1 : case 'D':
314 : 1 : opts.dbpattern = true;
315 : 1 : append_database_pattern(&opts.exclude, optarg, encoding);
316 : 0 : break;
317 : 0 : case 'e':
318 : 0 : opts.echo = true;
319 : 0 : break;
320 : 0 : case 'h':
321 : 0 : host = pg_strdup(optarg);
322 : 0 : break;
323 : 11 : case 'i':
324 : 11 : opts.allrel = false;
325 : 11 : append_btree_pattern(&opts.include, optarg, encoding);
326 : 11 : break;
327 : 2 : case 'I':
328 : 2 : opts.excludeidx = true;
329 : 2 : append_btree_pattern(&opts.exclude, optarg, encoding);
330 : 2 : break;
331 : 0 : case 'j':
332 [ # # ]: 0 : if (!option_parse_int(optarg, "-j/--jobs", 1, INT_MAX,
333 : : &opts.jobs))
334 : 0 : exit(1);
335 : 0 : break;
336 : 31 : case 'p':
337 : 31 : port = pg_strdup(optarg);
338 : 31 : break;
339 : 0 : case 'P':
340 : 0 : opts.show_progress = true;
341 : 0 : break;
342 : 7 : case 'r':
343 : 7 : opts.allrel = false;
344 : 7 : append_relation_pattern(&opts.include, optarg, encoding);
345 : 7 : break;
346 : 0 : case 'R':
347 : 0 : opts.excludeidx = true;
348 : 0 : opts.excludetbl = true;
349 : 0 : append_relation_pattern(&opts.exclude, optarg, encoding);
350 : 0 : break;
351 : 21 : case 's':
352 : 21 : opts.allrel = false;
353 : 21 : append_schema_pattern(&opts.include, optarg, encoding);
354 : 19 : break;
355 : 8 : case 'S':
356 : 8 : opts.excludensp = true;
357 : 8 : append_schema_pattern(&opts.exclude, optarg, encoding);
358 : 7 : break;
359 : 16 : case 't':
360 : 16 : opts.allrel = false;
361 : 16 : append_heap_pattern(&opts.include, optarg, encoding);
362 : 14 : break;
363 : 3 : case 'T':
364 : 3 : opts.excludetbl = true;
365 : 3 : append_heap_pattern(&opts.exclude, optarg, encoding);
366 : 2 : break;
367 : 1 : case 'U':
368 : 1 : username = pg_strdup(optarg);
369 : 1 : break;
370 : 0 : case 'v':
371 : 0 : opts.verbose = true;
372 : 0 : pg_logging_increase_verbosity();
373 : 0 : break;
374 : 0 : case 'w':
375 : 0 : prompt_password = TRI_NO;
376 : 0 : break;
377 : 0 : case 'W':
378 : 0 : prompt_password = TRI_YES;
379 : 0 : break;
380 : 0 : case 1:
381 : 0 : maintenance_db = pg_strdup(optarg);
382 : 0 : break;
383 : 4 : case 2:
384 : 4 : opts.no_btree_expansion = true;
385 : 4 : break;
386 : 1 : case 3:
387 : 1 : opts.no_toast_expansion = true;
388 : 1 : break;
389 : 1 : case 4:
390 : 1 : opts.reconcile_toast = false;
391 : 1 : break;
392 : 0 : case 5:
393 : 0 : opts.on_error_stop = true;
394 : 0 : break;
395 : 0 : case 6:
396 [ # # ]: 0 : if (pg_strcasecmp(optarg, "all-visible") == 0)
397 : 0 : opts.skip = "all-visible";
398 [ # # ]: 0 : else if (pg_strcasecmp(optarg, "all-frozen") == 0)
399 : 0 : opts.skip = "all-frozen";
400 [ # # ]: 0 : else if (pg_strcasecmp(optarg, "none") == 0)
401 : 0 : opts.skip = "none";
402 : : else
403 : 0 : pg_fatal("invalid argument for option %s", "--skip");
404 : 0 : break;
405 : 2 : case 7:
406 : 2 : errno = 0;
407 : 2 : optval = strtoul(optarg, &endptr, 10);
408 [ + + + - : 2 : if (endptr == optarg || *endptr != '\0' || errno != 0)
- + ]
409 : 1 : pg_fatal("invalid start block");
410 [ - + ]: 1 : if (optval > MaxBlockNumber)
411 : 0 : pg_fatal("start block out of bounds");
412 : 1 : opts.startblock = optval;
413 : 1 : break;
414 : 2 : case 8:
415 : 2 : errno = 0;
416 : 2 : optval = strtoul(optarg, &endptr, 10);
417 [ + - + + : 2 : if (endptr == optarg || *endptr != '\0' || errno != 0)
- + ]
418 : 1 : pg_fatal("invalid end block");
419 [ - + ]: 1 : if (optval > MaxBlockNumber)
420 : 0 : pg_fatal("end block out of bounds");
421 : 1 : opts.endblock = optval;
422 : 1 : break;
423 : 2 : case 9:
424 : 2 : opts.rootdescend = true;
425 : 2 : opts.parent_check = true;
426 : 2 : break;
427 : 11 : case 10:
428 : 11 : opts.strict_names = false;
429 : 11 : break;
430 : 2 : case 11:
431 : 2 : opts.heapallindexed = true;
432 : 2 : break;
433 : 2 : case 12:
434 : 2 : opts.parent_check = true;
435 : 2 : break;
436 : 0 : case 13:
437 : 0 : opts.install_missing = true;
438 [ # # ]: 0 : if (optarg)
439 : 0 : opts.install_schema = pg_strdup(optarg);
440 : 0 : break;
441 : 6 : case 14:
442 : 6 : opts.checkunique = true;
443 : 6 : break;
444 : 1 : default:
445 : : /* getopt_long already emitted a complaint */
446 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
447 : 1 : exit(1);
448 : : }
449 : : }
450 : :
451 [ + + + - ]: 47 : if (opts.endblock >= 0 && opts.endblock < opts.startblock)
452 : 1 : pg_fatal("end block precedes start block");
453 : :
454 : : /*
455 : : * A single non-option arguments specifies a database name or connection
456 : : * string.
457 : : */
458 [ + + ]: 46 : if (optind < argc)
459 : : {
460 : 25 : db = argv[optind];
461 : 25 : optind++;
462 : : }
463 : :
464 [ - + ]: 46 : if (optind < argc)
465 : : {
466 : 0 : pg_log_error("too many command-line arguments (first is \"%s\")",
467 : : argv[optind]);
468 : 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
469 : 0 : exit(1);
470 : : }
471 : :
472 : : /* fill cparams except for dbname, which is set below */
473 : 46 : cparams.pghost = host;
474 : 46 : cparams.pgport = port;
475 : 46 : cparams.pguser = username;
476 : 46 : cparams.prompt_password = prompt_password;
477 : 46 : cparams.dbname = NULL;
478 : 46 : cparams.override_dbname = NULL;
479 : :
480 : 46 : setup_cancel_handler(NULL);
481 : :
482 : : /* choose the database for our initial connection */
483 [ + + ]: 46 : if (opts.alldb)
484 : : {
485 [ - + ]: 4 : if (db != NULL)
486 : 0 : pg_fatal("cannot specify a database name with --all");
487 : 4 : cparams.dbname = maintenance_db;
488 : : }
489 [ + + ]: 42 : else if (db != NULL)
490 : : {
491 [ - + ]: 25 : if (opts.dbpattern)
492 : 0 : pg_fatal("cannot specify both a database name and database patterns");
493 : 25 : cparams.dbname = db;
494 : : }
495 : :
496 [ + + + + ]: 46 : if (opts.alldb || opts.dbpattern)
497 : : {
498 : 21 : conn = connectMaintenanceDatabase(&cparams, progname, opts.echo);
499 : 21 : compile_database_list(conn, &databases, NULL);
500 : : }
501 : : else
502 : : {
503 [ - + ]: 25 : if (cparams.dbname == NULL)
504 : : {
505 [ # # ]: 0 : if (getenv("PGDATABASE"))
506 : 0 : cparams.dbname = getenv("PGDATABASE");
507 [ # # ]: 0 : else if (getenv("PGUSER"))
508 : 0 : cparams.dbname = getenv("PGUSER");
509 : : else
510 : 0 : cparams.dbname = get_user_name_or_exit(progname);
511 : : }
512 : 25 : conn = connectDatabase(&cparams, progname, opts.echo, false, true);
513 : 23 : compile_database_list(conn, &databases, PQdb(conn));
514 : : }
515 : :
516 [ - + ]: 37 : if (databases.head == NULL)
517 : : {
518 [ # # ]: 0 : if (conn != NULL)
519 : 0 : disconnectDatabase(conn);
520 : 0 : pg_log_warning("no databases to check");
521 : 0 : exit(0);
522 : : }
523 : :
524 : : /*
525 : : * Compile a list of all relations spanning all databases to be checked.
526 : : */
527 [ + + ]: 94 : for (cell = databases.head; cell; cell = cell->next)
528 : : {
529 : : PGresult *result;
530 : : int ntups;
531 : 57 : const char *amcheck_schema = NULL;
532 : 57 : DatabaseInfo *dat = (DatabaseInfo *) cell->ptr;
533 : :
534 : 57 : cparams.override_dbname = dat->datname;
535 [ + + + + ]: 57 : if (conn == NULL || strcmp(PQdb(conn), dat->datname) != 0)
536 : : {
537 [ + + ]: 29 : if (conn != NULL)
538 : 25 : disconnectDatabase(conn);
539 : 29 : conn = connectDatabase(&cparams, progname, opts.echo, false, true);
540 : : }
541 : :
542 : : /*
543 : : * Optionally install amcheck if not already installed in this
544 : : * database.
545 : : */
546 [ - + ]: 57 : if (opts.install_missing)
547 : : {
548 : : char *schema;
549 : : char *install_sql;
550 : :
551 : : /*
552 : : * Must re-escape the schema name for each database, as the
553 : : * escaping rules may change.
554 : : */
555 : 0 : schema = PQescapeIdentifier(conn, opts.install_schema,
556 : 0 : strlen(opts.install_schema));
557 : 0 : install_sql = psprintf("CREATE EXTENSION IF NOT EXISTS amcheck WITH SCHEMA %s",
558 : : schema);
559 : :
560 : 0 : executeCommand(conn, install_sql, opts.echo);
561 : 0 : pfree(install_sql);
562 : 0 : PQfreemem(schema);
563 : : }
564 : :
565 : : /*
566 : : * Verify that amcheck is installed for this next database. User
567 : : * error could result in a database not having amcheck that should
568 : : * have it, but we also could be iterating over multiple databases
569 : : * where not all of them have amcheck installed (for example,
570 : : * 'template1').
571 : : */
572 : 57 : result = executeQuery(conn, amcheck_sql, opts.echo);
573 [ - + ]: 57 : if (PQresultStatus(result) != PGRES_TUPLES_OK)
574 : : {
575 : : /* Querying the catalog failed. */
576 : 0 : pg_log_error("database \"%s\": %s",
577 : : PQdb(conn), PQerrorMessage(conn));
578 : 0 : pg_log_error_detail("Query was: %s", amcheck_sql);
579 : 0 : PQclear(result);
580 : 0 : disconnectDatabase(conn);
581 : 0 : exit(1);
582 : : }
583 : 57 : ntups = PQntuples(result);
584 [ + + ]: 57 : if (ntups == 0)
585 : : {
586 : : /* Querying the catalog succeeded, but amcheck is missing. */
587 : 11 : pg_log_warning("skipping database \"%s\": amcheck is not installed",
588 : : PQdb(conn));
589 : 11 : PQclear(result);
590 : 11 : disconnectDatabase(conn);
591 : 11 : conn = NULL;
592 : 11 : continue;
593 : : }
594 : 46 : amcheck_schema = PQgetvalue(result, 0, 0);
595 [ - + ]: 46 : if (opts.verbose)
596 : 0 : pg_log_info("in database \"%s\": using amcheck version \"%s\" in schema \"%s\"",
597 : : PQdb(conn), PQgetvalue(result, 0, 1), amcheck_schema);
598 : 46 : dat->amcheck_schema = PQescapeIdentifier(conn, amcheck_schema,
599 : : strlen(amcheck_schema));
600 : :
601 : : /*
602 : : * Check the version of amcheck extension. Skip requested unique
603 : : * constraint check with warning if it is not yet supported by
604 : : * amcheck.
605 : : */
606 [ + + ]: 46 : if (opts.checkunique == true)
607 : : {
608 : : /*
609 : : * Now amcheck has only major and minor versions in the string but
610 : : * we also support revision just in case. Now it is expected to be
611 : : * zero.
612 : : */
613 : 8 : int vmaj = 0,
614 : 8 : vmin = 0,
615 : 8 : vrev = 0;
616 : 8 : const char *amcheck_version = PQgetvalue(result, 0, 1);
617 : :
618 : 8 : sscanf(amcheck_version, "%d.%d.%d", &vmaj, &vmin, &vrev);
619 : :
620 : : /*
621 : : * checkunique option is supported in amcheck since version 1.4
622 : : */
623 [ + - + + : 8 : if ((vmaj == 1 && vmin < 4) || vmaj == 0)
- + ]
624 : : {
625 : 1 : pg_log_warning("option %s is not supported by amcheck version %s",
626 : : "--checkunique", amcheck_version);
627 : 1 : dat->is_checkunique = false;
628 : : }
629 : : else
630 : 7 : dat->is_checkunique = true;
631 : : }
632 : :
633 : 46 : PQclear(result);
634 : :
635 : 46 : compile_relation_list_one_db(conn, &relations, dat, &pagestotal);
636 : : }
637 : :
638 : : /*
639 : : * Check that all inclusion patterns matched at least one schema or
640 : : * relation that we can check.
641 : : */
642 [ + + ]: 103 : for (size_t pattern_id = 0; pattern_id < opts.include.len; pattern_id++)
643 : : {
644 : 66 : PatternInfo *pat = &opts.include.data[pattern_id];
645 : :
646 [ + + + + : 66 : if (!pat->matched && (pat->nsp_regex != NULL || pat->rel_regex != NULL))
+ + ]
647 : : {
648 : 23 : failed = opts.strict_names;
649 : :
650 [ + + ]: 23 : if (pat->heap_only)
651 [ + + ]: 7 : log_no_match("no heap tables to check matching \"%s\"",
652 : : pat->pattern);
653 [ + + ]: 16 : else if (pat->btree_only)
654 [ - + ]: 5 : log_no_match("no btree indexes to check matching \"%s\"",
655 : : pat->pattern);
656 [ + + ]: 11 : else if (pat->rel_regex == NULL)
657 [ - + ]: 4 : log_no_match("no relations to check in schemas matching \"%s\"",
658 : : pat->pattern);
659 : : else
660 [ - + ]: 7 : log_no_match("no relations to check matching \"%s\"",
661 : : pat->pattern);
662 : : }
663 : : }
664 : :
665 [ + + ]: 37 : if (failed)
666 : : {
667 [ + - ]: 1 : if (conn != NULL)
668 : 1 : disconnectDatabase(conn);
669 : 1 : exit(1);
670 : : }
671 : :
672 : : /*
673 : : * Set parallel_workers to the lesser of opts.jobs and the number of
674 : : * relations.
675 : : */
676 : 36 : parallel_workers = 0;
677 [ + + ]: 8238 : for (cell = relations.head; cell; cell = cell->next)
678 : : {
679 : 8202 : reltotal++;
680 [ + + ]: 8202 : if (parallel_workers < opts.jobs)
681 : 32 : parallel_workers++;
682 : : }
683 : :
684 [ + + ]: 36 : if (reltotal == 0)
685 : : {
686 [ - + ]: 4 : if (conn != NULL)
687 : 0 : disconnectDatabase(conn);
688 : 4 : pg_fatal("no relations to check");
689 : : }
690 : 32 : progress_report(reltotal, relprogress, pagestotal, pageschecked,
691 : : NULL, true, false);
692 : :
693 : : /*
694 : : * Main event loop.
695 : : *
696 : : * We use server-side parallelism to check up to parallel_workers
697 : : * relations in parallel. The list of relations was computed in database
698 : : * order, which minimizes the number of connects and disconnects as we
699 : : * process the list.
700 : : */
701 : 32 : latest_datname = NULL;
702 : 32 : sa = ParallelSlotsSetup(parallel_workers, &cparams, progname, opts.echo,
703 : : NULL);
704 [ + + ]: 32 : if (conn != NULL)
705 : : {
706 : 29 : ParallelSlotsAdoptConn(sa, conn);
707 : 29 : conn = NULL;
708 : : }
709 : :
710 : 32 : initPQExpBuffer(&sql);
711 [ + + ]: 8234 : for (relprogress = 0, cell = relations.head; cell; cell = cell->next)
712 : : {
713 : : ParallelSlot *free_slot;
714 : : RelationInfo *rel;
715 : :
716 : 8202 : rel = (RelationInfo *) cell->ptr;
717 : :
718 [ - + ]: 8202 : if (CancelRequested)
719 : : {
720 : 0 : failed = true;
721 : 0 : break;
722 : : }
723 : :
724 : : /*
725 : : * The list of relations is in database sorted order. If this next
726 : : * relation is in a different database than the last one seen, we are
727 : : * about to start checking this database. Note that other slots may
728 : : * still be working on relations from prior databases.
729 : : */
730 : 8202 : latest_datname = rel->datinfo->datname;
731 : :
732 : 8202 : progress_report(reltotal, relprogress, pagestotal, pageschecked,
733 : : latest_datname, false, false);
734 : :
735 : 8202 : relprogress++;
736 : 8202 : pageschecked += rel->blocks_to_check;
737 : :
738 : : /*
739 : : * Get a parallel slot for the next amcheck command, blocking if
740 : : * necessary until one is available, or until a previously issued slot
741 : : * command fails, indicating that we should abort checking the
742 : : * remaining objects.
743 : : */
744 : 8202 : free_slot = ParallelSlotsGetIdle(sa, rel->datinfo->datname);
745 [ - + ]: 8202 : if (!free_slot)
746 : : {
747 : : /*
748 : : * Something failed. We don't need to know what it was, because
749 : : * the handler should already have emitted the necessary error
750 : : * messages.
751 : : */
752 : 0 : failed = true;
753 : 0 : break;
754 : : }
755 : :
756 [ - + ]: 8202 : if (opts.verbose)
757 : 0 : PQsetErrorVerbosity(free_slot->connection, PQERRORS_VERBOSE);
758 : :
759 : : /*
760 : : * Execute the appropriate amcheck command for this relation using our
761 : : * slot's database connection. We do not wait for the command to
762 : : * complete, nor do we perform any error checking, as that is done by
763 : : * the parallel slots and our handler callback functions.
764 : : */
765 [ + + ]: 8202 : if (rel->is_heap)
766 : : {
767 [ - + ]: 3597 : if (opts.verbose)
768 : : {
769 [ # # # # ]: 0 : if (opts.show_progress && progress_since_last_stderr)
770 : 0 : fprintf(stderr, "\n");
771 : 0 : pg_log_info("checking heap table \"%s.%s.%s\"",
772 : : rel->datinfo->datname, rel->nspname, rel->relname);
773 : 0 : progress_since_last_stderr = false;
774 : : }
775 : 3597 : prepare_heap_command(&sql, rel, free_slot->connection);
776 : 3597 : rel->sql = pstrdup(sql.data); /* pg_free'd after command */
777 : 3597 : ParallelSlotSetHandler(free_slot, verify_heap_slot_handler, rel);
778 : 3597 : run_command(free_slot, rel->sql);
779 : : }
780 : : else
781 : : {
782 [ - + ]: 4605 : if (opts.verbose)
783 : : {
784 [ # # # # ]: 0 : if (opts.show_progress && progress_since_last_stderr)
785 : 0 : fprintf(stderr, "\n");
786 : :
787 : 0 : pg_log_info("checking btree index \"%s.%s.%s\"",
788 : : rel->datinfo->datname, rel->nspname, rel->relname);
789 : 0 : progress_since_last_stderr = false;
790 : : }
791 : 4605 : prepare_btree_command(&sql, rel, free_slot->connection);
792 : 4605 : rel->sql = pstrdup(sql.data); /* pg_free'd after command */
793 : 4605 : ParallelSlotSetHandler(free_slot, verify_btree_slot_handler, rel);
794 : 4605 : run_command(free_slot, rel->sql);
795 : : }
796 : : }
797 : 32 : termPQExpBuffer(&sql);
798 : :
799 [ + - ]: 32 : if (!failed)
800 : : {
801 : :
802 : : /*
803 : : * Wait for all slots to complete, or for one to indicate that an
804 : : * error occurred. Like above, we rely on the handler emitting the
805 : : * necessary error messages.
806 : : */
807 [ + - - + ]: 32 : if (sa && !ParallelSlotsWaitCompletion(sa))
808 : 0 : failed = true;
809 : :
810 : 32 : progress_report(reltotal, relprogress, pagestotal, pageschecked, NULL, true, true);
811 : : }
812 : :
813 [ + - ]: 32 : if (sa)
814 : : {
815 : 32 : ParallelSlotsTerminate(sa);
816 : 32 : FREE_AND_SET_NULL(sa);
817 : : }
818 : :
819 [ - + ]: 32 : if (failed)
820 : 0 : exit(1);
821 : :
822 [ + + ]: 32 : if (!all_checks_pass)
823 : 14 : exit(2);
824 : : }
825 : :
826 : : /*
827 : : * prepare_heap_command
828 : : *
829 : : * Creates a SQL command for running amcheck checking on the given heap
830 : : * relation. The command is phrased as a SQL query, with column order and
831 : : * names matching the expectations of verify_heap_slot_handler, which will
832 : : * receive and handle each row returned from the verify_heapam() function.
833 : : *
834 : : * The constructed SQL command will silently skip temporary tables, as checking
835 : : * them would needlessly draw errors from the underlying amcheck function.
836 : : *
837 : : * sql: buffer into which the heap table checking command will be written
838 : : * rel: relation information for the heap table to be checked
839 : : * conn: the connection to be used, for string escaping purposes
840 : : */
841 : : static void
842 : 3597 : prepare_heap_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
843 : : {
844 : 3597 : resetPQExpBuffer(sql);
845 : 7194 : appendPQExpBuffer(sql,
846 : : "SELECT v.blkno, v.offnum, v.attnum, v.msg "
847 : : "FROM pg_catalog.pg_class c, %s.verify_heapam("
848 : : "\nrelation := c.oid, on_error_stop := %s, check_toast := %s, skip := '%s'",
849 : 3597 : rel->datinfo->amcheck_schema,
850 [ - + ]: 3597 : opts.on_error_stop ? "true" : "false",
851 [ + + ]: 3597 : opts.reconcile_toast ? "true" : "false",
852 : : opts.skip);
853 : :
854 [ - + ]: 3597 : if (opts.startblock >= 0)
855 : 0 : appendPQExpBuffer(sql, ", startblock := " INT64_FORMAT, opts.startblock);
856 [ - + ]: 3597 : if (opts.endblock >= 0)
857 : 0 : appendPQExpBuffer(sql, ", endblock := " INT64_FORMAT, opts.endblock);
858 : :
859 : 3597 : appendPQExpBuffer(sql,
860 : : "\n) v WHERE c.oid = %u "
861 : : "AND c.relpersistence != " CppAsString2(RELPERSISTENCE_TEMP),
862 : : rel->reloid);
863 : 3597 : }
864 : :
865 : : /*
866 : : * prepare_btree_command
867 : : *
868 : : * Creates a SQL command for running amcheck checking on the given btree index
869 : : * relation. The command does not select any columns, as btree checking
870 : : * functions do not return any, but rather return corruption information by
871 : : * raising errors, which verify_btree_slot_handler expects.
872 : : *
873 : : * The constructed SQL command will silently skip temporary indexes, and
874 : : * indexes being reindexed concurrently, as checking them would needlessly draw
875 : : * errors from the underlying amcheck functions.
876 : : *
877 : : * sql: buffer into which the heap table checking command will be written
878 : : * rel: relation information for the index to be checked
879 : : * conn: the connection to be used, for string escaping purposes
880 : : */
881 : : static void
882 : 4605 : prepare_btree_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
883 : : {
884 : 4605 : resetPQExpBuffer(sql);
885 : :
886 [ + + ]: 4605 : if (opts.parent_check)
887 : 144 : appendPQExpBuffer(sql,
888 : : "SELECT %s.bt_index_parent_check("
889 : : "index := c.oid, heapallindexed := %s, rootdescend := %s "
890 : : "%s)"
891 : : "\nFROM pg_catalog.pg_class c, pg_catalog.pg_index i "
892 : : "WHERE c.oid = %u "
893 : : "AND c.oid = i.indexrelid "
894 : : "AND c.relpersistence != " CppAsString2(RELPERSISTENCE_TEMP) " "
895 : : "AND i.indisready AND i.indisvalid AND i.indislive",
896 : 48 : rel->datinfo->amcheck_schema,
897 [ + + ]: 48 : (opts.heapallindexed ? "true" : "false"),
898 [ + + ]: 48 : (opts.rootdescend ? "true" : "false"),
899 [ + + ]: 48 : (rel->datinfo->is_checkunique ? ", checkunique := true" : ""),
900 : : rel->reloid);
901 : : else
902 : 9114 : appendPQExpBuffer(sql,
903 : : "SELECT %s.bt_index_check("
904 : : "index := c.oid, heapallindexed := %s "
905 : : "%s)"
906 : : "\nFROM pg_catalog.pg_class c, pg_catalog.pg_index i "
907 : : "WHERE c.oid = %u "
908 : : "AND c.oid = i.indexrelid "
909 : : "AND c.relpersistence != " CppAsString2(RELPERSISTENCE_TEMP) " "
910 : : "AND i.indisready AND i.indisvalid AND i.indislive",
911 : 4557 : rel->datinfo->amcheck_schema,
912 [ - + ]: 4557 : (opts.heapallindexed ? "true" : "false"),
913 [ + + ]: 4557 : (rel->datinfo->is_checkunique ? ", checkunique := true" : ""),
914 : : rel->reloid);
915 : 4605 : }
916 : :
917 : : /*
918 : : * run_command
919 : : *
920 : : * Sends a command to the server without waiting for the command to complete.
921 : : * Logs an error if the command cannot be sent, but otherwise any errors are
922 : : * expected to be handled by a ParallelSlotHandler.
923 : : *
924 : : * If reconnecting to the database is necessary, the cparams argument may be
925 : : * modified.
926 : : *
927 : : * slot: slot with connection to the server we should use for the command
928 : : * sql: query to send
929 : : */
930 : : static void
931 : 8202 : run_command(ParallelSlot *slot, const char *sql)
932 : : {
933 [ - + ]: 8202 : if (opts.echo)
934 : 0 : printf("%s\n", sql);
935 : :
936 [ - + ]: 8202 : if (PQsendQuery(slot->connection, sql) == 0)
937 : : {
938 : 0 : pg_log_error("error sending command to database \"%s\": %s",
939 : : PQdb(slot->connection),
940 : : PQerrorMessage(slot->connection));
941 : 0 : pg_log_error_detail("Command was: %s", sql);
942 : 0 : exit(1);
943 : : }
944 : 8202 : }
945 : :
946 : : /*
947 : : * should_processing_continue
948 : : *
949 : : * Checks a query result returned from a query (presumably issued on a slot's
950 : : * connection) to determine if parallel slots should continue issuing further
951 : : * commands.
952 : : *
953 : : * Note: Heap relation corruption is reported by verify_heapam() via the result
954 : : * set, rather than an ERROR, but running verify_heapam() on a corrupted heap
955 : : * table may still result in an error being returned from the server due to
956 : : * missing relation files, bad checksums, etc. The btree corruption checking
957 : : * functions always use errors to communicate corruption messages. We can't
958 : : * just abort processing because we got a mere ERROR.
959 : : *
960 : : * res: result from an executed sql query
961 : : */
962 : : static bool
963 : 8202 : should_processing_continue(PGresult *res)
964 : : {
965 : : const char *severity;
966 : :
967 [ + + - - ]: 8202 : switch (PQresultStatus(res))
968 : : {
969 : : /* These are expected and ok */
970 : 8148 : case PGRES_COMMAND_OK:
971 : : case PGRES_TUPLES_OK:
972 : : case PGRES_NONFATAL_ERROR:
973 : 8148 : break;
974 : :
975 : : /* This is expected but requires closer scrutiny */
976 : 54 : case PGRES_FATAL_ERROR:
977 : 54 : severity = PQresultErrorField(res, PG_DIAG_SEVERITY_NONLOCALIZED);
978 [ - + ]: 54 : if (severity == NULL)
979 : 0 : return false; /* libpq failure, probably lost connection */
980 [ - + ]: 54 : if (strcmp(severity, "FATAL") == 0)
981 : 0 : return false;
982 [ - + ]: 54 : if (strcmp(severity, "PANIC") == 0)
983 : 0 : return false;
984 : 54 : break;
985 : :
986 : : /* These are unexpected */
987 : 0 : case PGRES_BAD_RESPONSE:
988 : : case PGRES_EMPTY_QUERY:
989 : : case PGRES_COPY_OUT:
990 : : case PGRES_COPY_IN:
991 : : case PGRES_COPY_BOTH:
992 : : case PGRES_SINGLE_TUPLE:
993 : : case PGRES_PIPELINE_SYNC:
994 : : case PGRES_PIPELINE_ABORTED:
995 : : case PGRES_TUPLES_CHUNK:
996 : 0 : return false;
997 : : }
998 : 8202 : return true;
999 : : }
1000 : :
1001 : : /*
1002 : : * Returns a copy of the argument string with all lines indented four spaces.
1003 : : *
1004 : : * The caller should pg_free the result when finished with it.
1005 : : */
1006 : : static char *
1007 : 54 : indent_lines(const char *str)
1008 : : {
1009 : : PQExpBufferData buf;
1010 : : const char *c;
1011 : : char *result;
1012 : :
1013 : 54 : initPQExpBuffer(&buf);
1014 : 54 : appendPQExpBufferStr(&buf, " ");
1015 [ + + ]: 4300 : for (c = str; *c; c++)
1016 : : {
1017 : 4246 : appendPQExpBufferChar(&buf, *c);
1018 [ + + + + ]: 4246 : if (c[0] == '\n' && c[1] != '\0')
1019 : 2 : appendPQExpBufferStr(&buf, " ");
1020 : : }
1021 : 54 : result = pstrdup(buf.data);
1022 : 54 : termPQExpBuffer(&buf);
1023 : :
1024 : 54 : return result;
1025 : : }
1026 : :
1027 : : /*
1028 : : * verify_heap_slot_handler
1029 : : *
1030 : : * ParallelSlotHandler that receives results from a heap table checking command
1031 : : * created by prepare_heap_command and outputs the results for the user.
1032 : : *
1033 : : * res: result from an executed sql query
1034 : : * conn: connection on which the sql query was executed
1035 : : * context: the sql query being handled, as a cstring
1036 : : */
1037 : : static bool
1038 : 3597 : verify_heap_slot_handler(PGresult *res, PGconn *conn, void *context)
1039 : : {
1040 : 3597 : RelationInfo *rel = (RelationInfo *) context;
1041 : :
1042 [ + + ]: 3597 : if (PQresultStatus(res) == PGRES_TUPLES_OK)
1043 : : {
1044 : : int i;
1045 : 3577 : int ntups = PQntuples(res);
1046 : :
1047 [ + + ]: 3577 : if (ntups > 0)
1048 : 9 : all_checks_pass = false;
1049 : :
1050 [ + + ]: 3627 : for (i = 0; i < ntups; i++)
1051 : : {
1052 : : const char *msg;
1053 : :
1054 : : /* The message string should never be null, but check */
1055 [ - + ]: 50 : if (PQgetisnull(res, i, 3))
1056 : 0 : msg = "NO MESSAGE";
1057 : : else
1058 : 50 : msg = PQgetvalue(res, i, 3);
1059 : :
1060 [ + + ]: 50 : if (!PQgetisnull(res, i, 2))
1061 : 2 : printf(_("heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"),
1062 : : rel->datinfo->datname, rel->nspname, rel->relname,
1063 : : PQgetvalue(res, i, 0), /* blkno */
1064 : : PQgetvalue(res, i, 1), /* offnum */
1065 : : PQgetvalue(res, i, 2)); /* attnum */
1066 : :
1067 [ + - ]: 48 : else if (!PQgetisnull(res, i, 1))
1068 : 48 : printf(_("heap table \"%s.%s.%s\", block %s, offset %s:\n"),
1069 : : rel->datinfo->datname, rel->nspname, rel->relname,
1070 : : PQgetvalue(res, i, 0), /* blkno */
1071 : : PQgetvalue(res, i, 1)); /* offnum */
1072 : :
1073 [ # # ]: 0 : else if (!PQgetisnull(res, i, 0))
1074 : 0 : printf(_("heap table \"%s.%s.%s\", block %s:\n"),
1075 : : rel->datinfo->datname, rel->nspname, rel->relname,
1076 : : PQgetvalue(res, i, 0)); /* blkno */
1077 : :
1078 : : else
1079 : 0 : printf(_("heap table \"%s.%s.%s\":\n"),
1080 : : rel->datinfo->datname, rel->nspname, rel->relname);
1081 : :
1082 : 50 : printf(" %s\n", msg);
1083 : : }
1084 : : }
1085 [ + - ]: 20 : else if (PQresultStatus(res) != PGRES_TUPLES_OK)
1086 : : {
1087 : 20 : char *msg = indent_lines(PQerrorMessage(conn));
1088 : :
1089 : 20 : all_checks_pass = false;
1090 : 20 : printf(_("heap table \"%s.%s.%s\":\n"),
1091 : : rel->datinfo->datname, rel->nspname, rel->relname);
1092 : 20 : printf("%s", msg);
1093 [ - + ]: 20 : if (opts.verbose)
1094 : 0 : printf(_("query was: %s\n"), rel->sql);
1095 : 20 : FREE_AND_SET_NULL(msg);
1096 : : }
1097 : :
1098 : 3597 : FREE_AND_SET_NULL(rel->sql);
1099 : 3597 : FREE_AND_SET_NULL(rel->nspname);
1100 : 3597 : FREE_AND_SET_NULL(rel->relname);
1101 : :
1102 : 3597 : return should_processing_continue(res);
1103 : : }
1104 : :
1105 : : /*
1106 : : * verify_btree_slot_handler
1107 : : *
1108 : : * ParallelSlotHandler that receives results from a btree checking command
1109 : : * created by prepare_btree_command and outputs them for the user. The results
1110 : : * from the btree checking command is assumed to be empty, but when the results
1111 : : * are an error code, the useful information about the corruption is expected
1112 : : * in the connection's error message.
1113 : : *
1114 : : * res: result from an executed sql query
1115 : : * conn: connection on which the sql query was executed
1116 : : * context: unused
1117 : : */
1118 : : static bool
1119 : 4605 : verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context)
1120 : : {
1121 : 4605 : RelationInfo *rel = (RelationInfo *) context;
1122 : :
1123 [ + + ]: 4605 : if (PQresultStatus(res) == PGRES_TUPLES_OK)
1124 : : {
1125 : 4571 : int ntups = PQntuples(res);
1126 : :
1127 [ - + ]: 4571 : if (ntups > 1)
1128 : : {
1129 : : /*
1130 : : * We expect the btree checking functions to return one void row
1131 : : * each, or zero rows if the check was skipped due to the object
1132 : : * being in the wrong state to be checked, so we should output
1133 : : * some sort of warning if we get anything more, not because it
1134 : : * indicates corruption, but because it suggests a mismatch
1135 : : * between amcheck and pg_amcheck versions.
1136 : : *
1137 : : * In conjunction with --progress, anything written to stderr at
1138 : : * this time would present strangely to the user without an extra
1139 : : * newline, so we print one. If we were multithreaded, we'd have
1140 : : * to avoid splitting this across multiple calls, but we're in an
1141 : : * event loop, so it doesn't matter.
1142 : : */
1143 [ # # # # ]: 0 : if (opts.show_progress && progress_since_last_stderr)
1144 : 0 : fprintf(stderr, "\n");
1145 : 0 : pg_log_warning("btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d",
1146 : : rel->datinfo->datname, rel->nspname, rel->relname, ntups);
1147 [ # # ]: 0 : if (opts.verbose)
1148 : 0 : pg_log_warning_detail("Query was: %s", rel->sql);
1149 : 0 : pg_log_warning_hint("Are %s's and amcheck's versions compatible?",
1150 : : progname);
1151 : 0 : progress_since_last_stderr = false;
1152 : : }
1153 : : }
1154 : : else
1155 : : {
1156 : 34 : char *msg = indent_lines(PQerrorMessage(conn));
1157 : :
1158 : 34 : all_checks_pass = false;
1159 : 34 : printf(_("btree index \"%s.%s.%s\":\n"),
1160 : : rel->datinfo->datname, rel->nspname, rel->relname);
1161 : 34 : printf("%s", msg);
1162 [ - + ]: 34 : if (opts.verbose)
1163 : 0 : printf(_("query was: %s\n"), rel->sql);
1164 : 34 : FREE_AND_SET_NULL(msg);
1165 : : }
1166 : :
1167 : 4605 : FREE_AND_SET_NULL(rel->sql);
1168 : 4605 : FREE_AND_SET_NULL(rel->nspname);
1169 : 4605 : FREE_AND_SET_NULL(rel->relname);
1170 : :
1171 : 4605 : return should_processing_continue(res);
1172 : : }
1173 : :
1174 : : /*
1175 : : * help
1176 : : *
1177 : : * Prints help page for the program
1178 : : *
1179 : : * progname: the name of the executed program, such as "pg_amcheck"
1180 : : */
1181 : : static void
1182 : 1 : help(const char *progname)
1183 : : {
1184 : 1 : printf(_("%s checks objects in a PostgreSQL database for corruption.\n\n"), progname);
1185 : 1 : printf(_("Usage:\n"));
1186 : 1 : printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
1187 : 1 : printf(_("\nTarget options:\n"));
1188 : 1 : printf(_(" -a, --all check all databases\n"));
1189 : 1 : printf(_(" -d, --database=PATTERN check matching database(s)\n"));
1190 : 1 : printf(_(" -D, --exclude-database=PATTERN do NOT check matching database(s)\n"));
1191 : 1 : printf(_(" -i, --index=PATTERN check matching index(es)\n"));
1192 : 1 : printf(_(" -I, --exclude-index=PATTERN do NOT check matching index(es)\n"));
1193 : 1 : printf(_(" -r, --relation=PATTERN check matching relation(s)\n"));
1194 : 1 : printf(_(" -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"));
1195 : 1 : printf(_(" -s, --schema=PATTERN check matching schema(s)\n"));
1196 : 1 : printf(_(" -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"));
1197 : 1 : printf(_(" -t, --table=PATTERN check matching table(s)\n"));
1198 : 1 : printf(_(" -T, --exclude-table=PATTERN do NOT check matching table(s)\n"));
1199 : 1 : printf(_(" --no-dependent-indexes do NOT expand list of relations to include indexes\n"));
1200 : 1 : printf(_(" --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"));
1201 : 1 : printf(_(" --no-strict-names do NOT require patterns to match objects\n"));
1202 : 1 : printf(_("\nTable checking options:\n"));
1203 : 1 : printf(_(" --exclude-toast-pointers do NOT follow relation TOAST pointers\n"));
1204 : 1 : printf(_(" --on-error-stop stop checking at end of first corrupt page\n"));
1205 : 1 : printf(_(" --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"));
1206 : 1 : printf(_(" --startblock=BLOCK begin checking table(s) at the given block number\n"));
1207 : 1 : printf(_(" --endblock=BLOCK check table(s) only up to the given block number\n"));
1208 : 1 : printf(_("\nB-tree index checking options:\n"));
1209 : 1 : printf(_(" --checkunique check unique constraint if index is unique\n"));
1210 : 1 : printf(_(" --heapallindexed check that all heap tuples are found within indexes\n"));
1211 : 1 : printf(_(" --parent-check check index parent/child relationships\n"));
1212 : 1 : printf(_(" --rootdescend search from root page to refind tuples\n"));
1213 : 1 : printf(_("\nConnection options:\n"));
1214 : 1 : printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
1215 : 1 : printf(_(" -p, --port=PORT database server port\n"));
1216 : 1 : printf(_(" -U, --username=USERNAME user name to connect as\n"));
1217 : 1 : printf(_(" -w, --no-password never prompt for password\n"));
1218 : 1 : printf(_(" -W, --password force password prompt\n"));
1219 : 1 : printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
1220 : 1 : printf(_("\nOther options:\n"));
1221 : 1 : printf(_(" -e, --echo show the commands being sent to the server\n"));
1222 : 1 : printf(_(" -j, --jobs=NUM use this many concurrent connections to the server\n"));
1223 : 1 : printf(_(" -P, --progress show progress information\n"));
1224 : 1 : printf(_(" -v, --verbose write a lot of output\n"));
1225 : 1 : printf(_(" -V, --version output version information, then exit\n"));
1226 : 1 : printf(_(" --install-missing install missing extensions\n"));
1227 : 1 : printf(_(" -?, --help show this help, then exit\n"));
1228 : :
1229 : 1 : printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
1230 : 1 : printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
1231 : 1 : }
1232 : :
1233 : : /*
1234 : : * Print a progress report based on the global variables.
1235 : : *
1236 : : * Progress report is written at maximum once per second, unless the force
1237 : : * parameter is set to true.
1238 : : *
1239 : : * If finished is set to true, this is the last progress report. The cursor
1240 : : * is moved to the next line.
1241 : : */
1242 : : static void
1243 : 8266 : progress_report(uint64 relations_total, uint64 relations_checked,
1244 : : uint64 relpages_total, uint64 relpages_checked,
1245 : : const char *datname, bool force, bool finished)
1246 : : {
1247 : 8266 : int percent_rel = 0;
1248 : 8266 : int percent_pages = 0;
1249 : : char checked_rel[32];
1250 : : char total_rel[32];
1251 : : char checked_pages[32];
1252 : : char total_pages[32];
1253 : : pg_time_t now;
1254 : :
1255 [ + - ]: 8266 : if (!opts.show_progress)
1256 : 8266 : return;
1257 : :
1258 : 0 : now = time(NULL);
1259 [ # # # # : 0 : if (now == last_progress_report && !force && !finished)
# # ]
1260 : 0 : return; /* Max once per second */
1261 : :
1262 : 0 : last_progress_report = now;
1263 [ # # ]: 0 : if (relations_total)
1264 : 0 : percent_rel = (int) (relations_checked * 100 / relations_total);
1265 [ # # ]: 0 : if (relpages_total)
1266 : 0 : percent_pages = (int) (relpages_checked * 100 / relpages_total);
1267 : :
1268 : 0 : snprintf(checked_rel, sizeof(checked_rel), UINT64_FORMAT, relations_checked);
1269 : 0 : snprintf(total_rel, sizeof(total_rel), UINT64_FORMAT, relations_total);
1270 : 0 : snprintf(checked_pages, sizeof(checked_pages), UINT64_FORMAT, relpages_checked);
1271 : 0 : snprintf(total_pages, sizeof(total_pages), UINT64_FORMAT, relpages_total);
1272 : :
1273 : : #define VERBOSE_DATNAME_LENGTH 35
1274 [ # # ]: 0 : if (opts.verbose)
1275 : : {
1276 [ # # ]: 0 : if (!datname)
1277 : :
1278 : : /*
1279 : : * No datname given, so clear the status line (used for first and
1280 : : * last call)
1281 : : */
1282 : 0 : fprintf(stderr,
1283 : 0 : _("%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"),
1284 : 0 : (int) strlen(total_rel),
1285 : : checked_rel, total_rel, percent_rel,
1286 : 0 : (int) strlen(total_pages),
1287 : : checked_pages, total_pages, percent_pages,
1288 : : VERBOSE_DATNAME_LENGTH + 2, "");
1289 : : else
1290 : : {
1291 : 0 : bool truncate = (strlen(datname) > VERBOSE_DATNAME_LENGTH);
1292 : :
1293 [ # # # # : 0 : fprintf(stderr,
# # # # ]
1294 : 0 : _("%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"),
1295 : 0 : (int) strlen(total_rel),
1296 : : checked_rel, total_rel, percent_rel,
1297 : 0 : (int) strlen(total_pages),
1298 : : checked_pages, total_pages, percent_pages,
1299 : : /* Prefix with "..." if we do leading truncation */
1300 : : truncate ? "..." : "",
1301 : : truncate ? VERBOSE_DATNAME_LENGTH - 3 : VERBOSE_DATNAME_LENGTH,
1302 : : truncate ? VERBOSE_DATNAME_LENGTH - 3 : VERBOSE_DATNAME_LENGTH,
1303 : : /* Truncate datname at beginning if it's too long */
1304 : 0 : truncate ? datname + strlen(datname) - VERBOSE_DATNAME_LENGTH + 3 : datname);
1305 : : }
1306 : : }
1307 : : else
1308 : 0 : fprintf(stderr,
1309 : 0 : _("%*s/%s relations (%d%%), %*s/%s pages (%d%%)"),
1310 : 0 : (int) strlen(total_rel),
1311 : : checked_rel, total_rel, percent_rel,
1312 : 0 : (int) strlen(total_pages),
1313 : : checked_pages, total_pages, percent_pages);
1314 : :
1315 : : /*
1316 : : * Stay on the same line if reporting to a terminal and we're not done
1317 : : * yet.
1318 : : */
1319 [ # # # # ]: 0 : if (!finished && isatty(fileno(stderr)))
1320 : : {
1321 : 0 : fputc('\r', stderr);
1322 : 0 : progress_since_last_stderr = true;
1323 : : }
1324 : : else
1325 : 0 : fputc('\n', stderr);
1326 : : }
1327 : :
1328 : : /*
1329 : : * Extend the pattern info array to hold one additional initialized pattern
1330 : : * info entry.
1331 : : *
1332 : : * Returns a pointer to the new entry.
1333 : : */
1334 : : static PatternInfo *
1335 : 102 : extend_pattern_info_array(PatternInfoArray *pia)
1336 : : {
1337 : : PatternInfo *result;
1338 : :
1339 : 102 : pia->len++;
1340 : 102 : pia->data = pg_realloc_array(pia->data, PatternInfo, pia->len);
1341 : 102 : result = &pia->data[pia->len - 1];
1342 : 102 : memset(result, 0, sizeof(*result));
1343 : :
1344 : 102 : return result;
1345 : : }
1346 : :
1347 : : /*
1348 : : * append_database_pattern
1349 : : *
1350 : : * Adds the given pattern interpreted as a database name pattern.
1351 : : *
1352 : : * pia: the pattern info array to be appended
1353 : : * pattern: the database name pattern
1354 : : * encoding: client encoding for parsing the pattern
1355 : : */
1356 : : static void
1357 : 34 : append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
1358 : : {
1359 : : PQExpBufferData buf;
1360 : : int dotcnt;
1361 : 34 : PatternInfo *info = extend_pattern_info_array(pia);
1362 : :
1363 : 34 : initPQExpBuffer(&buf);
1364 : 34 : patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
1365 : : &dotcnt);
1366 [ + + ]: 34 : if (dotcnt > 0)
1367 : : {
1368 : 3 : pg_log_error("improper qualified name (too many dotted names): %s", pattern);
1369 : 3 : exit(2);
1370 : : }
1371 : 31 : info->pattern = pattern;
1372 : 31 : info->db_regex = pstrdup(buf.data);
1373 : :
1374 : 31 : termPQExpBuffer(&buf);
1375 : 31 : }
1376 : :
1377 : : /*
1378 : : * append_schema_pattern
1379 : : *
1380 : : * Adds the given pattern interpreted as a schema name pattern.
1381 : : *
1382 : : * pia: the pattern info array to be appended
1383 : : * pattern: the schema name pattern
1384 : : * encoding: client encoding for parsing the pattern
1385 : : */
1386 : : static void
1387 : 29 : append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
1388 : : {
1389 : : PQExpBufferData dbbuf;
1390 : : PQExpBufferData nspbuf;
1391 : : int dotcnt;
1392 : 29 : PatternInfo *info = extend_pattern_info_array(pia);
1393 : :
1394 : 29 : initPQExpBuffer(&dbbuf);
1395 : 29 : initPQExpBuffer(&nspbuf);
1396 : :
1397 : 29 : patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
1398 : : &dotcnt);
1399 [ + + ]: 29 : if (dotcnt > 1)
1400 : : {
1401 : 3 : pg_log_error("improper qualified name (too many dotted names): %s", pattern);
1402 : 3 : exit(2);
1403 : : }
1404 : 26 : info->pattern = pattern;
1405 [ - + ]: 26 : if (dbbuf.data[0])
1406 : : {
1407 : 0 : opts.dbpattern = true;
1408 : 0 : info->db_regex = pstrdup(dbbuf.data);
1409 : : }
1410 [ + - ]: 26 : if (nspbuf.data[0])
1411 : 26 : info->nsp_regex = pstrdup(nspbuf.data);
1412 : :
1413 : 26 : termPQExpBuffer(&dbbuf);
1414 : 26 : termPQExpBuffer(&nspbuf);
1415 : 26 : }
1416 : :
1417 : : /*
1418 : : * append_relation_pattern_helper
1419 : : *
1420 : : * Adds to a list the given pattern interpreted as a relation pattern.
1421 : : *
1422 : : * pia: the pattern info array to be appended
1423 : : * pattern: the relation name pattern
1424 : : * encoding: client encoding for parsing the pattern
1425 : : * heap_only: whether the pattern should only be matched against heap tables
1426 : : * btree_only: whether the pattern should only be matched against btree indexes
1427 : : */
1428 : : static void
1429 : 39 : append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
1430 : : int encoding, bool heap_only, bool btree_only)
1431 : : {
1432 : : PQExpBufferData dbbuf;
1433 : : PQExpBufferData nspbuf;
1434 : : PQExpBufferData relbuf;
1435 : : int dotcnt;
1436 : 39 : PatternInfo *info = extend_pattern_info_array(pia);
1437 : :
1438 : 39 : initPQExpBuffer(&dbbuf);
1439 : 39 : initPQExpBuffer(&nspbuf);
1440 : 39 : initPQExpBuffer(&relbuf);
1441 : :
1442 : 39 : patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
1443 : : false, &dotcnt);
1444 [ + + ]: 39 : if (dotcnt > 2)
1445 : : {
1446 : 3 : pg_log_error("improper relation name (too many dotted names): %s", pattern);
1447 : 3 : exit(2);
1448 : : }
1449 : 36 : info->pattern = pattern;
1450 [ + + ]: 36 : if (dbbuf.data[0])
1451 : : {
1452 : 14 : opts.dbpattern = true;
1453 : 14 : info->db_regex = pstrdup(dbbuf.data);
1454 : : }
1455 [ + + ]: 36 : if (nspbuf.data[0])
1456 : 20 : info->nsp_regex = pstrdup(nspbuf.data);
1457 [ + - ]: 36 : if (relbuf.data[0])
1458 : 36 : info->rel_regex = pstrdup(relbuf.data);
1459 : :
1460 : 36 : termPQExpBuffer(&dbbuf);
1461 : 36 : termPQExpBuffer(&nspbuf);
1462 : 36 : termPQExpBuffer(&relbuf);
1463 : :
1464 : 36 : info->heap_only = heap_only;
1465 : 36 : info->btree_only = btree_only;
1466 : 36 : }
1467 : :
1468 : : /*
1469 : : * append_relation_pattern
1470 : : *
1471 : : * Adds the given pattern interpreted as a relation pattern, to be matched
1472 : : * against both heap tables and btree indexes.
1473 : : *
1474 : : * pia: the pattern info array to be appended
1475 : : * pattern: the relation name pattern
1476 : : * encoding: client encoding for parsing the pattern
1477 : : */
1478 : : static void
1479 : 7 : append_relation_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
1480 : : {
1481 : 7 : append_relation_pattern_helper(pia, pattern, encoding, false, false);
1482 : 7 : }
1483 : :
1484 : : /*
1485 : : * append_heap_pattern
1486 : : *
1487 : : * Adds the given pattern interpreted as a relation pattern, to be matched only
1488 : : * against heap tables.
1489 : : *
1490 : : * pia: the pattern info array to be appended
1491 : : * pattern: the relation name pattern
1492 : : * encoding: client encoding for parsing the pattern
1493 : : */
1494 : : static void
1495 : 19 : append_heap_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
1496 : : {
1497 : 19 : append_relation_pattern_helper(pia, pattern, encoding, true, false);
1498 : 16 : }
1499 : :
1500 : : /*
1501 : : * append_btree_pattern
1502 : : *
1503 : : * Adds the given pattern interpreted as a relation pattern, to be matched only
1504 : : * against btree indexes.
1505 : : *
1506 : : * pia: the pattern info array to be appended
1507 : : * pattern: the relation name pattern
1508 : : * encoding: client encoding for parsing the pattern
1509 : : */
1510 : : static void
1511 : 13 : append_btree_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
1512 : : {
1513 : 13 : append_relation_pattern_helper(pia, pattern, encoding, false, true);
1514 : 13 : }
1515 : :
1516 : : /*
1517 : : * append_db_pattern_cte
1518 : : *
1519 : : * Appends to the buffer the body of a Common Table Expression (CTE) containing
1520 : : * the database portions filtered from the list of patterns expressed as two
1521 : : * columns:
1522 : : *
1523 : : * pattern_id: the index of this pattern in pia->data[]
1524 : : * rgx: the database regular expression parsed from the pattern
1525 : : *
1526 : : * Patterns without a database portion are skipped. Patterns with more than
1527 : : * just a database portion are optionally skipped, depending on argument
1528 : : * 'inclusive'.
1529 : : *
1530 : : * buf: the buffer to be appended
1531 : : * pia: the array of patterns to be inserted into the CTE
1532 : : * conn: the database connection
1533 : : * inclusive: whether to include patterns with schema and/or relation parts
1534 : : *
1535 : : * Returns whether any database patterns were appended.
1536 : : */
1537 : : static bool
1538 : 65 : append_db_pattern_cte(PQExpBuffer buf, const PatternInfoArray *pia,
1539 : : PGconn *conn, bool inclusive)
1540 : : {
1541 : : const char *comma;
1542 : : bool have_values;
1543 : :
1544 : 65 : comma = "";
1545 : 65 : have_values = false;
1546 [ + + ]: 151 : for (size_t pattern_id = 0; pattern_id < pia->len; pattern_id++)
1547 : : {
1548 : 86 : PatternInfo *info = &pia->data[pattern_id];
1549 : :
1550 [ + + - + ]: 86 : if (info->db_regex != NULL &&
1551 [ # # # # ]: 0 : (inclusive || (info->nsp_regex == NULL && info->rel_regex == NULL)))
1552 : : {
1553 [ + + ]: 45 : if (!have_values)
1554 : 17 : appendPQExpBufferStr(buf, "\nVALUES");
1555 : 45 : have_values = true;
1556 : 45 : appendPQExpBuffer(buf, "%s\n(%zu, ", comma, pattern_id);
1557 : 45 : appendStringLiteralConn(buf, info->db_regex, conn);
1558 : 45 : appendPQExpBufferChar(buf, ')');
1559 : 45 : comma = ",";
1560 : : }
1561 : : }
1562 : :
1563 [ + + ]: 65 : if (!have_values)
1564 : 48 : appendPQExpBufferStr(buf, "\nSELECT NULL, NULL, NULL WHERE false");
1565 : :
1566 : 65 : return have_values;
1567 : : }
1568 : :
1569 : : /*
1570 : : * compile_database_list
1571 : : *
1572 : : * If any database patterns exist, or if --all was given, compiles a distinct
1573 : : * list of databases to check using a SQL query based on the patterns plus the
1574 : : * literal initial database name, if given. If no database patterns exist and
1575 : : * --all was not given, the query is not necessary, and only the initial
1576 : : * database name (if any) is added to the list.
1577 : : *
1578 : : * conn: connection to the initial database
1579 : : * databases: the list onto which databases should be appended
1580 : : * initial_dbname: an optional extra database name to include in the list
1581 : : */
1582 : : static void
1583 : 44 : compile_database_list(PGconn *conn, SimplePtrList *databases,
1584 : : const char *initial_dbname)
1585 : : {
1586 : : PGresult *res;
1587 : : PQExpBufferData sql;
1588 : : int ntups;
1589 : : int i;
1590 : : bool fatal;
1591 : :
1592 [ + + ]: 44 : if (initial_dbname)
1593 : : {
1594 : 23 : DatabaseInfo *dat = pg_malloc0_object(DatabaseInfo);
1595 : :
1596 : : /* This database is included. Add to list */
1597 [ - + ]: 23 : if (opts.verbose)
1598 : 0 : pg_log_info("including database \"%s\"", initial_dbname);
1599 : :
1600 : 23 : dat->datname = pstrdup(initial_dbname);
1601 : 23 : simple_ptr_list_append(databases, dat);
1602 : : }
1603 : :
1604 : 44 : initPQExpBuffer(&sql);
1605 : :
1606 : : /* Append the include patterns CTE. */
1607 : 44 : appendPQExpBufferStr(&sql, "WITH include_raw (pattern_id, rgx) AS (");
1608 [ + + ]: 44 : if (!append_db_pattern_cte(&sql, &opts.include, conn, true) &&
1609 [ + + ]: 27 : !opts.alldb)
1610 : : {
1611 : : /*
1612 : : * None of the inclusion patterns (if any) contain database portions,
1613 : : * so there is no need to query the database to resolve database
1614 : : * patterns.
1615 : : *
1616 : : * Since we're also not operating under --all, we don't need to query
1617 : : * the exhaustive list of connectable databases, either.
1618 : : */
1619 : 23 : termPQExpBuffer(&sql);
1620 : 23 : return;
1621 : : }
1622 : :
1623 : : /* Append the exclude patterns CTE. */
1624 : 21 : appendPQExpBufferStr(&sql, "),\nexclude_raw (pattern_id, rgx) AS (");
1625 : 21 : append_db_pattern_cte(&sql, &opts.exclude, conn, false);
1626 : 21 : appendPQExpBufferStr(&sql, "),");
1627 : :
1628 : : /*
1629 : : * Append the database CTE, which includes whether each database is
1630 : : * connectable and also joins against exclude_raw to determine whether
1631 : : * each database is excluded.
1632 : : */
1633 : 21 : appendPQExpBufferStr(&sql,
1634 : : "\ndatabase (datname) AS ("
1635 : : "\nSELECT d.datname "
1636 : : "FROM pg_catalog.pg_database d "
1637 : : "LEFT OUTER JOIN exclude_raw e "
1638 : : "ON d.datname ~ e.rgx "
1639 : : "\nWHERE d.datallowconn AND datconnlimit != -2 "
1640 : : "AND e.pattern_id IS NULL"
1641 : : "),"
1642 : :
1643 : : /*
1644 : : * Append the include_pat CTE, which joins the include_raw CTE against the
1645 : : * databases CTE to determine if all the inclusion patterns had matches,
1646 : : * and whether each matched pattern had the misfortune of only matching
1647 : : * excluded or unconnectable databases.
1648 : : */
1649 : : "\ninclude_pat (pattern_id, checkable) AS ("
1650 : : "\nSELECT i.pattern_id, "
1651 : : "COUNT(*) FILTER ("
1652 : : "WHERE d IS NOT NULL"
1653 : : ") AS checkable"
1654 : : "\nFROM include_raw i "
1655 : : "LEFT OUTER JOIN database d "
1656 : : "ON d.datname ~ i.rgx"
1657 : : "\nGROUP BY i.pattern_id"
1658 : : "),"
1659 : :
1660 : : /*
1661 : : * Append the filtered_databases CTE, which selects from the database CTE
1662 : : * optionally joined against the include_raw CTE to only select databases
1663 : : * that match an inclusion pattern. This appears to duplicate what the
1664 : : * include_pat CTE already did above, but here we want only databases, and
1665 : : * there we wanted patterns.
1666 : : */
1667 : : "\nfiltered_databases (datname) AS ("
1668 : : "\nSELECT DISTINCT d.datname "
1669 : : "FROM database d");
1670 [ + + ]: 21 : if (!opts.alldb)
1671 : 17 : appendPQExpBufferStr(&sql,
1672 : : " INNER JOIN include_raw i "
1673 : : "ON d.datname ~ i.rgx");
1674 : 21 : appendPQExpBufferStr(&sql,
1675 : : ")"
1676 : :
1677 : : /*
1678 : : * Select the checkable databases and the unmatched inclusion patterns.
1679 : : */
1680 : : "\nSELECT pattern_id, datname FROM ("
1681 : : "\nSELECT pattern_id, NULL::TEXT AS datname "
1682 : : "FROM include_pat "
1683 : : "WHERE checkable = 0 "
1684 : : "UNION ALL"
1685 : : "\nSELECT NULL, datname "
1686 : : "FROM filtered_databases"
1687 : : ") AS combined_records"
1688 : : "\nORDER BY pattern_id NULLS LAST, datname");
1689 : :
1690 : 21 : res = executeQuery(conn, sql.data, opts.echo);
1691 [ - + ]: 21 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
1692 : : {
1693 : 0 : pg_log_error("query failed: %s", PQerrorMessage(conn));
1694 : 0 : pg_log_error_detail("Query was: %s", sql.data);
1695 : 0 : disconnectDatabase(conn);
1696 : 0 : exit(1);
1697 : : }
1698 : 21 : termPQExpBuffer(&sql);
1699 : :
1700 : 21 : ntups = PQntuples(res);
1701 [ + + ]: 74 : for (fatal = false, i = 0; i < ntups; i++)
1702 : : {
1703 : 53 : int pattern_id = -1;
1704 : 53 : const char *datname = NULL;
1705 : :
1706 [ + + ]: 53 : if (!PQgetisnull(res, i, 0))
1707 : 13 : pattern_id = atoi(PQgetvalue(res, i, 0));
1708 [ + + ]: 53 : if (!PQgetisnull(res, i, 1))
1709 : 40 : datname = PQgetvalue(res, i, 1);
1710 : :
1711 [ + + ]: 53 : if (pattern_id >= 0)
1712 : : {
1713 : : /*
1714 : : * Current record pertains to an inclusion pattern that matched no
1715 : : * checkable databases.
1716 : : */
1717 : 13 : fatal = opts.strict_names;
1718 [ - + ]: 13 : if (pattern_id >= opts.include.len)
1719 : 0 : pg_fatal("internal error: received unexpected database pattern_id %d",
1720 : : pattern_id);
1721 [ + + ]: 13 : log_no_match("no connectable databases to check matching \"%s\"",
1722 : : opts.include.data[pattern_id].pattern);
1723 : : }
1724 : : else
1725 : : {
1726 : : DatabaseInfo *dat;
1727 : :
1728 : : /* Current record pertains to a database */
1729 : : Assert(datname != NULL);
1730 : :
1731 : : /* Avoid entering a duplicate entry matching the initial_dbname */
1732 [ - + - - ]: 40 : if (initial_dbname != NULL && strcmp(initial_dbname, datname) == 0)
1733 : 0 : continue;
1734 : :
1735 : : /* This database is included. Add to list */
1736 [ - + ]: 40 : if (opts.verbose)
1737 : 0 : pg_log_info("including database \"%s\"", datname);
1738 : :
1739 : 40 : dat = pg_malloc0_object(DatabaseInfo);
1740 : 40 : dat->datname = pstrdup(datname);
1741 : 40 : simple_ptr_list_append(databases, dat);
1742 : : }
1743 : : }
1744 : 21 : PQclear(res);
1745 : :
1746 [ + + ]: 21 : if (fatal)
1747 : : {
1748 [ + - ]: 7 : if (conn != NULL)
1749 : 7 : disconnectDatabase(conn);
1750 : 7 : exit(1);
1751 : : }
1752 : : }
1753 : :
1754 : : /*
1755 : : * append_rel_pattern_raw_cte
1756 : : *
1757 : : * Appends to the buffer the body of a Common Table Expression (CTE) containing
1758 : : * the given patterns as six columns:
1759 : : *
1760 : : * pattern_id: the index of this pattern in pia->data[]
1761 : : * db_regex: the database regexp parsed from the pattern, or NULL if the
1762 : : * pattern had no database part
1763 : : * nsp_regex: the namespace regexp parsed from the pattern, or NULL if the
1764 : : * pattern had no namespace part
1765 : : * rel_regex: the relname regexp parsed from the pattern, or NULL if the
1766 : : * pattern had no relname part
1767 : : * heap_only: true if the pattern applies only to heap tables (not indexes)
1768 : : * btree_only: true if the pattern applies only to btree indexes (not tables)
1769 : : *
1770 : : * buf: the buffer to be appended
1771 : : * patterns: the array of patterns to be inserted into the CTE
1772 : : * conn: the database connection
1773 : : */
1774 : : static void
1775 : 31 : append_rel_pattern_raw_cte(PQExpBuffer buf, const PatternInfoArray *pia,
1776 : : PGconn *conn)
1777 : : {
1778 : : const char *comma;
1779 : : bool have_values;
1780 : :
1781 : 31 : comma = "";
1782 : 31 : have_values = false;
1783 [ + + ]: 100 : for (size_t pattern_id = 0; pattern_id < pia->len; pattern_id++)
1784 : : {
1785 : 69 : PatternInfo *info = &pia->data[pattern_id];
1786 : :
1787 [ + + ]: 69 : if (!have_values)
1788 : 31 : appendPQExpBufferStr(buf, "\nVALUES");
1789 : 69 : have_values = true;
1790 : 69 : appendPQExpBuffer(buf, "%s\n(%zu::INTEGER, ", comma, pattern_id);
1791 [ + + ]: 69 : if (info->db_regex == NULL)
1792 : 53 : appendPQExpBufferStr(buf, "NULL");
1793 : : else
1794 : 16 : appendStringLiteralConn(buf, info->db_regex, conn);
1795 : 69 : appendPQExpBufferStr(buf, "::TEXT, ");
1796 [ + + ]: 69 : if (info->nsp_regex == NULL)
1797 : 23 : appendPQExpBufferStr(buf, "NULL");
1798 : : else
1799 : 46 : appendStringLiteralConn(buf, info->nsp_regex, conn);
1800 : 69 : appendPQExpBufferStr(buf, "::TEXT, ");
1801 [ + + ]: 69 : if (info->rel_regex == NULL)
1802 : 34 : appendPQExpBufferStr(buf, "NULL");
1803 : : else
1804 : 35 : appendStringLiteralConn(buf, info->rel_regex, conn);
1805 [ + + ]: 69 : if (info->heap_only)
1806 : 13 : appendPQExpBufferStr(buf, "::TEXT, true::BOOLEAN");
1807 : : else
1808 : 56 : appendPQExpBufferStr(buf, "::TEXT, false::BOOLEAN");
1809 [ + + ]: 69 : if (info->btree_only)
1810 : 15 : appendPQExpBufferStr(buf, ", true::BOOLEAN");
1811 : : else
1812 : 54 : appendPQExpBufferStr(buf, ", false::BOOLEAN");
1813 : 69 : appendPQExpBufferChar(buf, ')');
1814 : 69 : comma = ",";
1815 : : }
1816 : :
1817 [ - + ]: 31 : if (!have_values)
1818 : 0 : appendPQExpBufferStr(buf,
1819 : : "\nSELECT NULL::INTEGER, NULL::TEXT, NULL::TEXT, "
1820 : : "NULL::TEXT, NULL::BOOLEAN, NULL::BOOLEAN "
1821 : : "WHERE false");
1822 : 31 : }
1823 : :
1824 : : /*
1825 : : * append_rel_pattern_filtered_cte
1826 : : *
1827 : : * Appends to the buffer a Common Table Expression (CTE) which selects
1828 : : * all patterns from the named raw CTE, filtered by database. All patterns
1829 : : * which have no database portion or whose database portion matches our
1830 : : * connection's database name are selected, with other patterns excluded.
1831 : : *
1832 : : * The basic idea here is that if we're connected to database "foo" and we have
1833 : : * patterns "foo.bar.baz", "alpha.beta" and "one.two.three", we only want to
1834 : : * use the first two while processing relations in this database, as the third
1835 : : * one is not relevant.
1836 : : *
1837 : : * buf: the buffer to be appended
1838 : : * raw: the name of the CTE to select from
1839 : : * filtered: the name of the CTE to create
1840 : : * conn: the database connection
1841 : : */
1842 : : static void
1843 : 31 : append_rel_pattern_filtered_cte(PQExpBuffer buf, const char *raw,
1844 : : const char *filtered, PGconn *conn)
1845 : : {
1846 : 31 : appendPQExpBuffer(buf,
1847 : : "\n%s (pattern_id, nsp_regex, rel_regex, heap_only, btree_only) AS ("
1848 : : "\nSELECT pattern_id, nsp_regex, rel_regex, heap_only, btree_only "
1849 : : "FROM %s r"
1850 : : "\nWHERE (r.db_regex IS NULL "
1851 : : "OR ",
1852 : : filtered, raw);
1853 : 31 : appendStringLiteralConn(buf, PQdb(conn), conn);
1854 : 31 : appendPQExpBufferStr(buf, " ~ r.db_regex)");
1855 : 31 : appendPQExpBufferStr(buf,
1856 : : " AND (r.nsp_regex IS NOT NULL"
1857 : : " OR r.rel_regex IS NOT NULL)"
1858 : : "),");
1859 : 31 : }
1860 : :
1861 : : /*
1862 : : * compile_relation_list_one_db
1863 : : *
1864 : : * Compiles a list of relations to check within the currently connected
1865 : : * database based on the user supplied options, sorted by descending size,
1866 : : * and appends them to the given list of relations.
1867 : : *
1868 : : * The cells of the constructed list contain all information about the relation
1869 : : * necessary to connect to the database and check the object, including which
1870 : : * database to connect to, where contrib/amcheck is installed, and the Oid and
1871 : : * type of object (heap table vs. btree index). Rather than duplicating the
1872 : : * database details per relation, the relation structs use references to the
1873 : : * same database object, provided by the caller.
1874 : : *
1875 : : * conn: connection to this next database, which should be the same as in 'dat'
1876 : : * relations: list onto which the relations information should be appended
1877 : : * dat: the database info struct for use by each relation
1878 : : * pagecount: gets incremented by the number of blocks to check in all
1879 : : * relations added
1880 : : */
1881 : : static void
1882 : 46 : compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
1883 : : const DatabaseInfo *dat,
1884 : : uint64 *pagecount)
1885 : : {
1886 : : PGresult *res;
1887 : : PQExpBufferData sql;
1888 : : int ntups;
1889 : : int i;
1890 : :
1891 : 46 : initPQExpBuffer(&sql);
1892 : 46 : appendPQExpBufferStr(&sql, "WITH");
1893 : :
1894 : : /* Append CTEs for the relation inclusion patterns, if any */
1895 [ + + ]: 46 : if (!opts.allrel)
1896 : : {
1897 : 21 : appendPQExpBufferStr(&sql,
1898 : : " include_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, btree_only) AS (");
1899 : 21 : append_rel_pattern_raw_cte(&sql, &opts.include, conn);
1900 : 21 : appendPQExpBufferStr(&sql, "\n),");
1901 : 21 : append_rel_pattern_filtered_cte(&sql, "include_raw", "include_pat", conn);
1902 : : }
1903 : :
1904 : : /* Append CTEs for the relation exclusion patterns, if any */
1905 [ + + + + : 46 : if (opts.excludetbl || opts.excludeidx || opts.excludensp)
+ + ]
1906 : : {
1907 : 10 : appendPQExpBufferStr(&sql,
1908 : : " exclude_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, btree_only) AS (");
1909 : 10 : append_rel_pattern_raw_cte(&sql, &opts.exclude, conn);
1910 : 10 : appendPQExpBufferStr(&sql, "\n),");
1911 : 10 : append_rel_pattern_filtered_cte(&sql, "exclude_raw", "exclude_pat", conn);
1912 : : }
1913 : :
1914 : : /* Append the relation CTE. */
1915 : 46 : appendPQExpBufferStr(&sql,
1916 : : " relation (pattern_id, oid, nspname, relname, reltoastrelid, relpages, is_heap, is_btree) AS ("
1917 : : "\nSELECT DISTINCT ON (c.oid");
1918 [ + + ]: 46 : if (!opts.allrel)
1919 : 21 : appendPQExpBufferStr(&sql, ", ip.pattern_id) ip.pattern_id,");
1920 : : else
1921 : 25 : appendPQExpBufferStr(&sql, ") NULL::INTEGER AS pattern_id,");
1922 : 46 : appendPQExpBuffer(&sql,
1923 : : "\nc.oid, n.nspname, c.relname, c.reltoastrelid, c.relpages, "
1924 : : "c.relam = %u AS is_heap, "
1925 : : "c.relam = %u AS is_btree"
1926 : : "\nFROM pg_catalog.pg_class c "
1927 : : "INNER JOIN pg_catalog.pg_namespace n "
1928 : : "ON c.relnamespace = n.oid",
1929 : : HEAP_TABLE_AM_OID, BTREE_AM_OID);
1930 [ + + ]: 46 : if (!opts.allrel)
1931 : 21 : appendPQExpBuffer(&sql,
1932 : : "\nINNER JOIN include_pat ip"
1933 : : "\nON (n.nspname ~ ip.nsp_regex OR ip.nsp_regex IS NULL)"
1934 : : "\nAND (c.relname ~ ip.rel_regex OR ip.rel_regex IS NULL)"
1935 : : "\nAND (c.relam = %u OR NOT ip.heap_only)"
1936 : : "\nAND (c.relam = %u OR NOT ip.btree_only)",
1937 : : HEAP_TABLE_AM_OID, BTREE_AM_OID);
1938 [ + + + + : 46 : if (opts.excludetbl || opts.excludeidx || opts.excludensp)
+ + ]
1939 : 10 : appendPQExpBuffer(&sql,
1940 : : "\nLEFT OUTER JOIN exclude_pat ep"
1941 : : "\nON (n.nspname ~ ep.nsp_regex OR ep.nsp_regex IS NULL)"
1942 : : "\nAND (c.relname ~ ep.rel_regex OR ep.rel_regex IS NULL)"
1943 : : "\nAND (c.relam = %u OR NOT ep.heap_only OR ep.rel_regex IS NULL)"
1944 : : "\nAND (c.relam = %u OR NOT ep.btree_only OR ep.rel_regex IS NULL)",
1945 : : HEAP_TABLE_AM_OID, BTREE_AM_OID);
1946 : :
1947 : : /*
1948 : : * Exclude temporary tables and indexes, which must necessarily belong to
1949 : : * other sessions. (We don't create any ourselves.) We must ultimately
1950 : : * exclude indexes marked invalid or not ready, but we delay that decision
1951 : : * until firing off the amcheck command, as the state of an index may
1952 : : * change by then.
1953 : : */
1954 : 46 : appendPQExpBufferStr(&sql, "\nWHERE c.relpersistence != "
1955 : : CppAsString2(RELPERSISTENCE_TEMP));
1956 [ + + + + : 46 : if (opts.excludetbl || opts.excludeidx || opts.excludensp)
+ + ]
1957 : 10 : appendPQExpBufferStr(&sql, "\nAND ep.pattern_id IS NULL");
1958 : :
1959 : : /*
1960 : : * We need to be careful not to break the --no-dependent-toast and
1961 : : * --no-dependent-indexes options. By default, the btree indexes, toast
1962 : : * tables, and toast table btree indexes associated with primary heap
1963 : : * tables are included, using their own CTEs below. We implement the
1964 : : * --exclude-* options by not creating those CTEs, but that's no use if
1965 : : * we've already selected the toast and indexes here. On the other hand,
1966 : : * we want inclusion patterns that match indexes or toast tables to be
1967 : : * honored. So, if inclusion patterns were given, we want to select all
1968 : : * tables, toast tables, or indexes that match the patterns. But if no
1969 : : * inclusion patterns were given, and we're simply matching all relations,
1970 : : * then we only want to match the primary tables here.
1971 : : */
1972 [ + + ]: 46 : if (opts.allrel)
1973 : 25 : appendPQExpBuffer(&sql,
1974 : : " AND c.relam = %u "
1975 : : "AND c.relkind IN ("
1976 : : CppAsString2(RELKIND_RELATION) ", "
1977 : : CppAsString2(RELKIND_SEQUENCE) ", "
1978 : : CppAsString2(RELKIND_MATVIEW) ", "
1979 : : CppAsString2(RELKIND_TOASTVALUE) ") "
1980 : : "AND c.relnamespace != %u",
1981 : : HEAP_TABLE_AM_OID, PG_TOAST_NAMESPACE);
1982 : : else
1983 : 21 : appendPQExpBuffer(&sql,
1984 : : " AND c.relam IN (%u, %u)"
1985 : : "AND c.relkind IN ("
1986 : : CppAsString2(RELKIND_RELATION) ", "
1987 : : CppAsString2(RELKIND_SEQUENCE) ", "
1988 : : CppAsString2(RELKIND_MATVIEW) ", "
1989 : : CppAsString2(RELKIND_TOASTVALUE) ", "
1990 : : CppAsString2(RELKIND_INDEX) ") "
1991 : : "AND ((c.relam = %u AND c.relkind IN ("
1992 : : CppAsString2(RELKIND_RELATION) ", "
1993 : : CppAsString2(RELKIND_SEQUENCE) ", "
1994 : : CppAsString2(RELKIND_MATVIEW) ", "
1995 : : CppAsString2(RELKIND_TOASTVALUE) ")) OR "
1996 : : "(c.relam = %u AND c.relkind = "
1997 : : CppAsString2(RELKIND_INDEX) "))",
1998 : : HEAP_TABLE_AM_OID, BTREE_AM_OID,
1999 : : HEAP_TABLE_AM_OID, BTREE_AM_OID);
2000 : :
2001 : 46 : appendPQExpBufferStr(&sql,
2002 : : "\nORDER BY c.oid)");
2003 : :
2004 [ + + ]: 46 : if (!opts.no_toast_expansion)
2005 : : {
2006 : : /*
2007 : : * Include a CTE for toast tables associated with primary heap tables
2008 : : * selected above, filtering by exclusion patterns (if any) that match
2009 : : * toast table names.
2010 : : */
2011 : 45 : appendPQExpBufferStr(&sql,
2012 : : ", toast (oid, nspname, relname, relpages) AS ("
2013 : : "\nSELECT t.oid, 'pg_toast', t.relname, t.relpages"
2014 : : "\nFROM pg_catalog.pg_class t "
2015 : : "INNER JOIN relation r "
2016 : : "ON r.reltoastrelid = t.oid");
2017 [ + + + + ]: 45 : if (opts.excludetbl || opts.excludensp)
2018 : 9 : appendPQExpBufferStr(&sql,
2019 : : "\nLEFT OUTER JOIN exclude_pat ep"
2020 : : "\nON ('pg_toast' ~ ep.nsp_regex OR ep.nsp_regex IS NULL)"
2021 : : "\nAND (t.relname ~ ep.rel_regex OR ep.rel_regex IS NULL)"
2022 : : "\nAND ep.heap_only"
2023 : : "\nWHERE ep.pattern_id IS NULL"
2024 : : "\nAND t.relpersistence != " CppAsString2(RELPERSISTENCE_TEMP));
2025 : 45 : appendPQExpBufferStr(&sql,
2026 : : "\n)");
2027 : : }
2028 [ + + ]: 46 : if (!opts.no_btree_expansion)
2029 : : {
2030 : : /*
2031 : : * Include a CTE for btree indexes associated with primary heap tables
2032 : : * selected above, filtering by exclusion patterns (if any) that match
2033 : : * btree index names.
2034 : : */
2035 : 42 : appendPQExpBufferStr(&sql,
2036 : : ", index (oid, nspname, relname, relpages) AS ("
2037 : : "\nSELECT c.oid, r.nspname, c.relname, c.relpages "
2038 : : "FROM relation r"
2039 : : "\nINNER JOIN pg_catalog.pg_index i "
2040 : : "ON r.oid = i.indrelid "
2041 : : "INNER JOIN pg_catalog.pg_class c "
2042 : : "ON i.indexrelid = c.oid "
2043 : : "AND c.relpersistence != " CppAsString2(RELPERSISTENCE_TEMP));
2044 [ + + + + ]: 42 : if (opts.excludeidx || opts.excludensp)
2045 : 9 : appendPQExpBufferStr(&sql,
2046 : : "\nINNER JOIN pg_catalog.pg_namespace n "
2047 : : "ON c.relnamespace = n.oid"
2048 : : "\nLEFT OUTER JOIN exclude_pat ep "
2049 : : "ON (n.nspname ~ ep.nsp_regex OR ep.nsp_regex IS NULL) "
2050 : : "AND (c.relname ~ ep.rel_regex OR ep.rel_regex IS NULL) "
2051 : : "AND ep.btree_only"
2052 : : "\nWHERE ep.pattern_id IS NULL");
2053 : : else
2054 : 33 : appendPQExpBufferStr(&sql,
2055 : : "\nWHERE true");
2056 : 42 : appendPQExpBuffer(&sql,
2057 : : " AND c.relam = %u "
2058 : : "AND c.relkind = " CppAsString2(RELKIND_INDEX),
2059 : : BTREE_AM_OID);
2060 [ + + ]: 42 : if (opts.no_toast_expansion)
2061 : 1 : appendPQExpBuffer(&sql,
2062 : : " AND c.relnamespace != %u",
2063 : : PG_TOAST_NAMESPACE);
2064 : 42 : appendPQExpBufferStr(&sql, "\n)");
2065 : : }
2066 : :
2067 [ + + + + ]: 46 : if (!opts.no_toast_expansion && !opts.no_btree_expansion)
2068 : : {
2069 : : /*
2070 : : * Include a CTE for btree indexes associated with toast tables of
2071 : : * primary heap tables selected above, filtering by exclusion patterns
2072 : : * (if any) that match the toast index names.
2073 : : */
2074 : 41 : appendPQExpBufferStr(&sql,
2075 : : ", toast_index (oid, nspname, relname, relpages) AS ("
2076 : : "\nSELECT c.oid, 'pg_toast', c.relname, c.relpages "
2077 : : "FROM toast t "
2078 : : "INNER JOIN pg_catalog.pg_index i "
2079 : : "ON t.oid = i.indrelid"
2080 : : "\nINNER JOIN pg_catalog.pg_class c "
2081 : : "ON i.indexrelid = c.oid "
2082 : : "AND c.relpersistence != " CppAsString2(RELPERSISTENCE_TEMP));
2083 [ + + ]: 41 : if (opts.excludeidx)
2084 : 1 : appendPQExpBufferStr(&sql,
2085 : : "\nLEFT OUTER JOIN exclude_pat ep "
2086 : : "ON ('pg_toast' ~ ep.nsp_regex OR ep.nsp_regex IS NULL) "
2087 : : "AND (c.relname ~ ep.rel_regex OR ep.rel_regex IS NULL) "
2088 : : "AND ep.btree_only "
2089 : : "WHERE ep.pattern_id IS NULL");
2090 : : else
2091 : 40 : appendPQExpBufferStr(&sql,
2092 : : "\nWHERE true");
2093 : 41 : appendPQExpBuffer(&sql,
2094 : : " AND c.relam = %u"
2095 : : " AND c.relkind = " CppAsString2(RELKIND_INDEX) ")",
2096 : : BTREE_AM_OID);
2097 : : }
2098 : :
2099 : : /*
2100 : : * Roll-up distinct rows from CTEs.
2101 : : *
2102 : : * Relations that match more than one pattern may occur more than once in
2103 : : * the list, and indexes and toast for primary relations may also have
2104 : : * matched in their own right, so we rely on UNION to deduplicate the
2105 : : * list.
2106 : : */
2107 : 46 : appendPQExpBufferStr(&sql,
2108 : : "\nSELECT pattern_id, is_heap, is_btree, oid, nspname, relname, relpages "
2109 : : "FROM (");
2110 : 46 : appendPQExpBufferStr(&sql,
2111 : : /* Inclusion patterns that failed to match */
2112 : : "\nSELECT pattern_id, is_heap, is_btree, "
2113 : : "NULL::OID AS oid, "
2114 : : "NULL::TEXT AS nspname, "
2115 : : "NULL::TEXT AS relname, "
2116 : : "NULL::INTEGER AS relpages"
2117 : : "\nFROM relation "
2118 : : "WHERE pattern_id IS NOT NULL "
2119 : : "UNION"
2120 : : /* Primary relations */
2121 : : "\nSELECT NULL::INTEGER AS pattern_id, "
2122 : : "is_heap, is_btree, oid, nspname, relname, relpages "
2123 : : "FROM relation");
2124 [ + + ]: 46 : if (!opts.no_toast_expansion)
2125 : 45 : appendPQExpBufferStr(&sql,
2126 : : " UNION"
2127 : : /* Toast tables for primary relations */
2128 : : "\nSELECT NULL::INTEGER AS pattern_id, TRUE AS is_heap, "
2129 : : "FALSE AS is_btree, oid, nspname, relname, relpages "
2130 : : "FROM toast");
2131 [ + + ]: 46 : if (!opts.no_btree_expansion)
2132 : 42 : appendPQExpBufferStr(&sql,
2133 : : " UNION"
2134 : : /* Indexes for primary relations */
2135 : : "\nSELECT NULL::INTEGER AS pattern_id, FALSE AS is_heap, "
2136 : : "TRUE AS is_btree, oid, nspname, relname, relpages "
2137 : : "FROM index");
2138 [ + + + + ]: 46 : if (!opts.no_toast_expansion && !opts.no_btree_expansion)
2139 : 41 : appendPQExpBufferStr(&sql,
2140 : : " UNION"
2141 : : /* Indexes for toast relations */
2142 : : "\nSELECT NULL::INTEGER AS pattern_id, FALSE AS is_heap, "
2143 : : "TRUE AS is_btree, oid, nspname, relname, relpages "
2144 : : "FROM toast_index");
2145 : 46 : appendPQExpBufferStr(&sql,
2146 : : "\n) AS combined_records "
2147 : : "ORDER BY relpages DESC NULLS FIRST, oid");
2148 : :
2149 : 46 : res = executeQuery(conn, sql.data, opts.echo);
2150 [ - + ]: 46 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
2151 : : {
2152 : 0 : pg_log_error("query failed: %s", PQerrorMessage(conn));
2153 : 0 : pg_log_error_detail("Query was: %s", sql.data);
2154 : 0 : disconnectDatabase(conn);
2155 : 0 : exit(1);
2156 : : }
2157 : 46 : termPQExpBuffer(&sql);
2158 : :
2159 : 46 : ntups = PQntuples(res);
2160 [ + + ]: 8288 : for (i = 0; i < ntups; i++)
2161 : : {
2162 : 8242 : int pattern_id = -1;
2163 : 8242 : bool is_heap = false;
2164 : 8242 : bool is_btree PG_USED_FOR_ASSERTS_ONLY = false;
2165 : 8242 : Oid oid = InvalidOid;
2166 : 8242 : const char *nspname = NULL;
2167 : 8242 : const char *relname = NULL;
2168 : 8242 : int relpages = 0;
2169 : :
2170 [ + + ]: 8242 : if (!PQgetisnull(res, i, 0))
2171 : 40 : pattern_id = atoi(PQgetvalue(res, i, 0));
2172 [ + - ]: 8242 : if (!PQgetisnull(res, i, 1))
2173 : 8242 : is_heap = (PQgetvalue(res, i, 1)[0] == 't');
2174 [ + - ]: 8242 : if (!PQgetisnull(res, i, 2))
2175 : 8242 : is_btree = (PQgetvalue(res, i, 2)[0] == 't');
2176 [ + + ]: 8242 : if (!PQgetisnull(res, i, 3))
2177 : 8202 : oid = atooid(PQgetvalue(res, i, 3));
2178 [ + + ]: 8242 : if (!PQgetisnull(res, i, 4))
2179 : 8202 : nspname = PQgetvalue(res, i, 4);
2180 [ + + ]: 8242 : if (!PQgetisnull(res, i, 5))
2181 : 8202 : relname = PQgetvalue(res, i, 5);
2182 [ + + ]: 8242 : if (!PQgetisnull(res, i, 6))
2183 : 8202 : relpages = atoi(PQgetvalue(res, i, 6));
2184 : :
2185 [ + + ]: 8242 : if (pattern_id >= 0)
2186 : : {
2187 : : /*
2188 : : * Current record pertains to an inclusion pattern. Record that
2189 : : * it matched.
2190 : : */
2191 : :
2192 [ - + ]: 40 : if (pattern_id >= opts.include.len)
2193 : 0 : pg_fatal("internal error: received unexpected relation pattern_id %d",
2194 : : pattern_id);
2195 : :
2196 : 40 : opts.include.data[pattern_id].matched = true;
2197 : : }
2198 : : else
2199 : : {
2200 : : /* Current record pertains to a relation */
2201 : :
2202 : 8202 : RelationInfo *rel = pg_malloc0_object(RelationInfo);
2203 : :
2204 : : Assert(OidIsValid(oid));
2205 : : Assert((is_heap && !is_btree) || (is_btree && !is_heap));
2206 : :
2207 : 8202 : rel->datinfo = dat;
2208 : 8202 : rel->reloid = oid;
2209 : 8202 : rel->is_heap = is_heap;
2210 : 8202 : rel->nspname = pstrdup(nspname);
2211 : 8202 : rel->relname = pstrdup(relname);
2212 : 8202 : rel->relpages = relpages;
2213 : 8202 : rel->blocks_to_check = relpages;
2214 [ + + + - : 8202 : if (is_heap && (opts.startblock >= 0 || opts.endblock >= 0))
- + ]
2215 : : {
2216 : : /*
2217 : : * We apply --startblock and --endblock to heap tables, but
2218 : : * not btree indexes, and for progress purposes we need to
2219 : : * track how many blocks we expect to check.
2220 : : */
2221 [ # # # # ]: 0 : if (opts.endblock >= 0 && rel->blocks_to_check > opts.endblock)
2222 : 0 : rel->blocks_to_check = opts.endblock + 1;
2223 [ # # ]: 0 : if (opts.startblock >= 0)
2224 : : {
2225 [ # # ]: 0 : if (rel->blocks_to_check > opts.startblock)
2226 : 0 : rel->blocks_to_check -= opts.startblock;
2227 : : else
2228 : 0 : rel->blocks_to_check = 0;
2229 : : }
2230 : : }
2231 : 8202 : *pagecount += rel->blocks_to_check;
2232 : :
2233 : 8202 : simple_ptr_list_append(relations, rel);
2234 : : }
2235 : : }
2236 : 46 : PQclear(res);
2237 : 46 : }
|