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