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