summaryrefslogtreecommitdiffstats
path: root/src/bin/pg_amcheck
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/pg_amcheck')
-rw-r--r--src/bin/pg_amcheck/.gitignore3
-rw-r--r--src/bin/pg_amcheck/Makefile51
-rw-r--r--src/bin/pg_amcheck/README19
-rw-r--r--src/bin/pg_amcheck/nls.mk12
-rw-r--r--src/bin/pg_amcheck/pg_amcheck.c2216
-rw-r--r--src/bin/pg_amcheck/po/de.po523
-rw-r--r--src/bin/pg_amcheck/po/el.po516
-rw-r--r--src/bin/pg_amcheck/po/es.po523
-rw-r--r--src/bin/pg_amcheck/po/fr.po575
-rw-r--r--src/bin/pg_amcheck/po/ja.po527
-rw-r--r--src/bin/pg_amcheck/po/ru.po591
-rw-r--r--src/bin/pg_amcheck/po/sv.po520
-rw-r--r--src/bin/pg_amcheck/po/uk.po506
-rw-r--r--src/bin/pg_amcheck/po/zh_CN.po500
-rw-r--r--src/bin/pg_amcheck/t/001_basic.pl12
-rw-r--r--src/bin/pg_amcheck/t/002_nonesuch.pl359
-rw-r--r--src/bin/pg_amcheck/t/003_check.pl518
-rw-r--r--src/bin/pg_amcheck/t/004_verify_heapam.pl529
-rw-r--r--src/bin/pg_amcheck/t/005_opclass_damage.pl59
19 files changed, 8559 insertions, 0 deletions
diff --git a/src/bin/pg_amcheck/.gitignore b/src/bin/pg_amcheck/.gitignore
new file mode 100644
index 0000000..c21a14d
--- /dev/null
+++ b/src/bin/pg_amcheck/.gitignore
@@ -0,0 +1,3 @@
+pg_amcheck
+
+/tmp_check/
diff --git a/src/bin/pg_amcheck/Makefile b/src/bin/pg_amcheck/Makefile
new file mode 100644
index 0000000..6192523
--- /dev/null
+++ b/src/bin/pg_amcheck/Makefile
@@ -0,0 +1,51 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/bin/pg_amcheck
+#
+# Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/bin/pg_amcheck/Makefile
+#
+#-------------------------------------------------------------------------
+
+PGFILEDESC = "pg_amcheck - detect corruption within database relations"
+PGAPPICON=win32
+
+EXTRA_INSTALL=contrib/amcheck contrib/pageinspect
+
+subdir = src/bin/pg_amcheck
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
+
+OBJS = \
+ $(WIN32RES) \
+ pg_amcheck.o
+
+all: pg_amcheck
+
+pg_amcheck: $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
+ $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+
+install: all installdirs
+ $(INSTALL_PROGRAM) pg_amcheck$(X) '$(DESTDIR)$(bindir)/pg_amcheck$(X)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(bindir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(bindir)/pg_amcheck$(X)'
+
+clean distclean maintainer-clean:
+ rm -f pg_amcheck$(X) $(OBJS)
+ rm -rf tmp_check
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/pg_amcheck/README b/src/bin/pg_amcheck/README
new file mode 100644
index 0000000..950f8a7
--- /dev/null
+++ b/src/bin/pg_amcheck/README
@@ -0,0 +1,19 @@
+src/bin/pg_amcheck/README
+
+pg_amcheck is a command-line tool for running the amcheck extension.
+
+Running the regression tests
+============================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+Also, to use "make installcheck", you must have built and installed
+contrib/amcheck and contrib/pageinspect in addition to the core code.
+
+Run
+ make check
+or
+ make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested. With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
diff --git a/src/bin/pg_amcheck/nls.mk b/src/bin/pg_amcheck/nls.mk
new file mode 100644
index 0000000..e8b71ce
--- /dev/null
+++ b/src/bin/pg_amcheck/nls.mk
@@ -0,0 +1,12 @@
+# src/bin/pg_amcheck/nls.mk
+CATALOG_NAME = pg_amcheck
+AVAIL_LANGUAGES = de el es fr ja ru sv uk zh_CN
+GETTEXT_FILES = $(FRONTEND_COMMON_GETTEXT_FILES) \
+ pg_amcheck.c \
+ ../../fe_utils/cancel.c \
+ ../../fe_utils/connect_utils.c \
+ ../../fe_utils/query_utils.c
+GETTEXT_TRIGGERS = $(FRONTEND_COMMON_GETTEXT_TRIGGERS) \
+ log_no_match
+GETTEXT_FLAGS = $(FRONTEND_COMMON_GETTEXT_FLAGS) \
+ log_no_match:1:c-format
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
new file mode 100644
index 0000000..2c86dda
--- /dev/null
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -0,0 +1,2216 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_amcheck.c
+ * Detects corruption within database relations.
+ *
+ * Copyright (c) 2017-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/bin/pg_amcheck/pg_amcheck.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include <time.h>
+
+#include "catalog/pg_am_d.h"
+#include "catalog/pg_namespace_d.h"
+#include "common/logging.h"
+#include "common/username.h"
+#include "fe_utils/cancel.h"
+#include "fe_utils/option_utils.h"
+#include "fe_utils/parallel_slot.h"
+#include "fe_utils/query_utils.h"
+#include "fe_utils/simple_list.h"
+#include "fe_utils/string_utils.h"
+#include "getopt_long.h" /* pgrminclude ignore */
+#include "pgtime.h"
+#include "storage/block.h"
+
+typedef struct PatternInfo
+{
+ const char *pattern; /* Unaltered pattern from the command line */
+ char *db_regex; /* Database regexp parsed from pattern, or
+ * NULL */
+ char *nsp_regex; /* Schema regexp parsed from pattern, or NULL */
+ char *rel_regex; /* Relation regexp parsed from pattern, or
+ * NULL */
+ bool heap_only; /* true if rel_regex should only match heap
+ * tables */
+ bool btree_only; /* true if rel_regex should only match btree
+ * indexes */
+ bool matched; /* true if the pattern matched in any database */
+} PatternInfo;
+
+typedef struct PatternInfoArray
+{
+ PatternInfo *data;
+ size_t len;
+} PatternInfoArray;
+
+/* pg_amcheck command line options controlled by user flags */
+typedef struct AmcheckOptions
+{
+ bool dbpattern;
+ bool alldb;
+ bool echo;
+ bool verbose;
+ bool strict_names;
+ bool show_progress;
+ int jobs;
+
+ /*
+ * Whether to install missing extensions, and optionally the name of the
+ * schema in which to install the extension's objects.
+ */
+ bool install_missing;
+ char *install_schema;
+
+ /* Objects to check or not to check, as lists of PatternInfo structs. */
+ PatternInfoArray include;
+ PatternInfoArray exclude;
+
+ /*
+ * As an optimization, if any pattern in the exclude list applies to heap
+ * tables, or similarly if any such pattern applies to btree indexes, or
+ * to schemas, then these will be true, otherwise false. These should
+ * always agree with what you'd conclude by grep'ing through the exclude
+ * list.
+ */
+ bool excludetbl;
+ bool excludeidx;
+ bool excludensp;
+
+ /*
+ * If any inclusion pattern exists, then we should only be checking
+ * matching relations rather than all relations, so this is true iff
+ * include is empty.
+ */
+ bool allrel;
+
+ /* heap table checking options */
+ bool no_toast_expansion;
+ bool reconcile_toast;
+ bool on_error_stop;
+ int64 startblock;
+ int64 endblock;
+ const char *skip;
+
+ /* btree index checking options */
+ bool parent_check;
+ bool rootdescend;
+ bool heapallindexed;
+
+ /* heap and btree hybrid option */
+ bool no_btree_expansion;
+} AmcheckOptions;
+
+static AmcheckOptions opts = {
+ .dbpattern = false,
+ .alldb = false,
+ .echo = false,
+ .verbose = false,
+ .strict_names = true,
+ .show_progress = false,
+ .jobs = 1,
+ .install_missing = false,
+ .install_schema = "pg_catalog",
+ .include = {NULL, 0},
+ .exclude = {NULL, 0},
+ .excludetbl = false,
+ .excludeidx = false,
+ .excludensp = false,
+ .allrel = true,
+ .no_toast_expansion = false,
+ .reconcile_toast = true,
+ .on_error_stop = false,
+ .startblock = -1,
+ .endblock = -1,
+ .skip = "none",
+ .parent_check = false,
+ .rootdescend = false,
+ .heapallindexed = false,
+ .no_btree_expansion = false
+};
+
+static const char *progname = NULL;
+
+/* Whether all relations have so far passed their corruption checks */
+static bool all_checks_pass = true;
+
+/* Time last progress report was displayed */
+static pg_time_t last_progress_report = 0;
+static bool progress_since_last_stderr = false;
+
+typedef struct DatabaseInfo
+{
+ char *datname;
+ char *amcheck_schema; /* escaped, quoted literal */
+} DatabaseInfo;
+
+typedef struct RelationInfo
+{
+ const DatabaseInfo *datinfo; /* shared by other relinfos */
+ Oid reloid;
+ bool is_heap; /* true if heap, false if btree */
+ char *nspname;
+ char *relname;
+ int relpages;
+ int blocks_to_check;
+ char *sql; /* set during query run, pg_free'd after */
+} RelationInfo;
+
+/*
+ * Query for determining if contrib's amcheck is installed. If so, selects the
+ * namespace name where amcheck's functions can be found.
+ */
+static const char *amcheck_sql =
+"SELECT n.nspname, x.extversion FROM pg_catalog.pg_extension x"
+"\nJOIN pg_catalog.pg_namespace n ON x.extnamespace = n.oid"
+"\nWHERE x.extname = 'amcheck'";
+
+static void prepare_heap_command(PQExpBuffer sql, RelationInfo *rel,
+ PGconn *conn);
+static void prepare_btree_command(PQExpBuffer sql, RelationInfo *rel,
+ PGconn *conn);
+static void run_command(ParallelSlot *slot, const char *sql);
+static bool verify_heap_slot_handler(PGresult *res, PGconn *conn,
+ void *context);
+static bool verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context);
+static void help(const char *progname);
+static void progress_report(uint64 relations_total, uint64 relations_checked,
+ uint64 relpages_total, uint64 relpages_checked,
+ const char *datname, bool force, bool finished);
+
+static void append_database_pattern(PatternInfoArray *pia, const char *pattern,
+ int encoding);
+static void append_schema_pattern(PatternInfoArray *pia, const char *pattern,
+ int encoding);
+static void append_relation_pattern(PatternInfoArray *pia, const char *pattern,
+ int encoding);
+static void append_heap_pattern(PatternInfoArray *pia, const char *pattern,
+ int encoding);
+static void append_btree_pattern(PatternInfoArray *pia, const char *pattern,
+ int encoding);
+static void compile_database_list(PGconn *conn, SimplePtrList *databases,
+ const char *initial_dbname);
+static void compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
+ const DatabaseInfo *datinfo,
+ uint64 *pagecount);
+
+#define log_no_match(...) do { \
+ if (opts.strict_names) \
+ pg_log_generic(PG_LOG_ERROR, __VA_ARGS__); \
+ else \
+ pg_log_generic(PG_LOG_WARNING, __VA_ARGS__); \
+ } while(0)
+
+#define FREE_AND_SET_NULL(x) do { \
+ pg_free(x); \
+ (x) = NULL; \
+ } while (0)
+
+int
+main(int argc, char *argv[])
+{
+ PGconn *conn = NULL;
+ SimplePtrListCell *cell;
+ SimplePtrList databases = {NULL, NULL};
+ SimplePtrList relations = {NULL, NULL};
+ bool failed = false;
+ const char *latest_datname;
+ int parallel_workers;
+ ParallelSlotArray *sa;
+ PQExpBufferData sql;
+ uint64 reltotal = 0;
+ uint64 pageschecked = 0;
+ uint64 pagestotal = 0;
+ uint64 relprogress = 0;
+ int pattern_id;
+
+ static struct option long_options[] = {
+ /* Connection options */
+ {"host", required_argument, NULL, 'h'},
+ {"port", required_argument, NULL, 'p'},
+ {"username", required_argument, NULL, 'U'},
+ {"no-password", no_argument, NULL, 'w'},
+ {"password", no_argument, NULL, 'W'},
+ {"maintenance-db", required_argument, NULL, 1},
+
+ /* check options */
+ {"all", no_argument, NULL, 'a'},
+ {"database", required_argument, NULL, 'd'},
+ {"exclude-database", required_argument, NULL, 'D'},
+ {"echo", no_argument, NULL, 'e'},
+ {"index", required_argument, NULL, 'i'},
+ {"exclude-index", required_argument, NULL, 'I'},
+ {"jobs", required_argument, NULL, 'j'},
+ {"progress", no_argument, NULL, 'P'},
+ {"relation", required_argument, NULL, 'r'},
+ {"exclude-relation", required_argument, NULL, 'R'},
+ {"schema", required_argument, NULL, 's'},
+ {"exclude-schema", required_argument, NULL, 'S'},
+ {"table", required_argument, NULL, 't'},
+ {"exclude-table", required_argument, NULL, 'T'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"no-dependent-indexes", no_argument, NULL, 2},
+ {"no-dependent-toast", no_argument, NULL, 3},
+ {"exclude-toast-pointers", no_argument, NULL, 4},
+ {"on-error-stop", no_argument, NULL, 5},
+ {"skip", required_argument, NULL, 6},
+ {"startblock", required_argument, NULL, 7},
+ {"endblock", required_argument, NULL, 8},
+ {"rootdescend", no_argument, NULL, 9},
+ {"no-strict-names", no_argument, NULL, 10},
+ {"heapallindexed", no_argument, NULL, 11},
+ {"parent-check", no_argument, NULL, 12},
+ {"install-missing", optional_argument, NULL, 13},
+
+ {NULL, 0, NULL, 0}
+ };
+
+ int optindex;
+ int c;
+
+ const char *db = NULL;
+ const char *maintenance_db = NULL;
+
+ const char *host = NULL;
+ const char *port = NULL;
+ const char *username = NULL;
+ enum trivalue prompt_password = TRI_DEFAULT;
+ int encoding = pg_get_encoding_from_locale(NULL, false);
+ ConnParams cparams;
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_amcheck"));
+
+ handle_help_version_opts(argc, argv, progname, help);
+
+ /* process command-line options */
+ while ((c = getopt_long(argc, argv, "ad:D:eh:Hi:I:j:p:Pr:R:s:S:t:T:U:wWv",
+ long_options, &optindex)) != -1)
+ {
+ char *endptr;
+ unsigned long optval;
+
+ switch (c)
+ {
+ case 'a':
+ opts.alldb = true;
+ break;
+ case 'd':
+ opts.dbpattern = true;
+ append_database_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'D':
+ opts.dbpattern = true;
+ append_database_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 'e':
+ opts.echo = true;
+ break;
+ case 'h':
+ host = pg_strdup(optarg);
+ break;
+ case 'i':
+ opts.allrel = false;
+ append_btree_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'I':
+ opts.excludeidx = true;
+ append_btree_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 'j':
+ opts.jobs = atoi(optarg);
+ if (opts.jobs < 1)
+ {
+ pg_log_error("number of parallel jobs must be at least 1");
+ exit(1);
+ }
+ break;
+ case 'p':
+ port = pg_strdup(optarg);
+ break;
+ case 'P':
+ opts.show_progress = true;
+ break;
+ case 'r':
+ opts.allrel = false;
+ append_relation_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'R':
+ opts.excludeidx = true;
+ opts.excludetbl = true;
+ append_relation_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 's':
+ opts.allrel = false;
+ append_schema_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'S':
+ opts.excludensp = true;
+ append_schema_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 't':
+ opts.allrel = false;
+ append_heap_pattern(&opts.include, optarg, encoding);
+ break;
+ case 'T':
+ opts.excludetbl = true;
+ append_heap_pattern(&opts.exclude, optarg, encoding);
+ break;
+ case 'U':
+ username = pg_strdup(optarg);
+ break;
+ case 'w':
+ prompt_password = TRI_NO;
+ break;
+ case 'W':
+ prompt_password = TRI_YES;
+ break;
+ case 'v':
+ opts.verbose = true;
+ pg_logging_increase_verbosity();
+ break;
+ case 1:
+ maintenance_db = pg_strdup(optarg);
+ break;
+ case 2:
+ opts.no_btree_expansion = true;
+ break;
+ case 3:
+ opts.no_toast_expansion = true;
+ break;
+ case 4:
+ opts.reconcile_toast = false;
+ break;
+ case 5:
+ opts.on_error_stop = true;
+ break;
+ case 6:
+ if (pg_strcasecmp(optarg, "all-visible") == 0)
+ opts.skip = "all-visible";
+ else if (pg_strcasecmp(optarg, "all-frozen") == 0)
+ opts.skip = "all-frozen";
+ else if (pg_strcasecmp(optarg, "none") == 0)
+ opts.skip = "none";
+ else
+ {
+ pg_log_error("invalid argument for option %s", "--skip");
+ exit(1);
+ }
+ break;
+ case 7:
+ errno = 0;
+ optval = strtoul(optarg, &endptr, 10);
+ if (endptr == optarg || *endptr != '\0' || errno != 0)
+ {
+ pg_log_error("invalid start block");
+ exit(1);
+ }
+ if (optval > MaxBlockNumber)
+ {
+ pg_log_error("start block out of bounds");
+ exit(1);
+ }
+ opts.startblock = optval;
+ break;
+ case 8:
+ errno = 0;
+ optval = strtoul(optarg, &endptr, 10);
+ if (endptr == optarg || *endptr != '\0' || errno != 0)
+ {
+ pg_log_error("invalid end block");
+ exit(1);
+ }
+ if (optval > MaxBlockNumber)
+ {
+ pg_log_error("end block out of bounds");
+ exit(1);
+ }
+ opts.endblock = optval;
+ break;
+ case 9:
+ opts.rootdescend = true;
+ opts.parent_check = true;
+ break;
+ case 10:
+ opts.strict_names = false;
+ break;
+ case 11:
+ opts.heapallindexed = true;
+ break;
+ case 12:
+ opts.parent_check = true;
+ break;
+ case 13:
+ opts.install_missing = true;
+ if (optarg)
+ opts.install_schema = pg_strdup(optarg);
+ break;
+ default:
+ fprintf(stderr,
+ _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+ }
+
+ if (opts.endblock >= 0 && opts.endblock < opts.startblock)
+ {
+ pg_log_error("end block precedes start block");
+ exit(1);
+ }
+
+ /*
+ * A single non-option arguments specifies a database name or connection
+ * string.
+ */
+ if (optind < argc)
+ {
+ db = argv[optind];
+ optind++;
+ }
+
+ if (optind < argc)
+ {
+ pg_log_error("too many command-line arguments (first is \"%s\")",
+ argv[optind]);
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+ exit(1);
+ }
+
+ /* fill cparams except for dbname, which is set below */
+ cparams.pghost = host;
+ cparams.pgport = port;
+ cparams.pguser = username;
+ cparams.prompt_password = prompt_password;
+ cparams.dbname = NULL;
+ cparams.override_dbname = NULL;
+
+ setup_cancel_handler(NULL);
+
+ /* choose the database for our initial connection */
+ if (opts.alldb)
+ {
+ if (db != NULL)
+ {
+ pg_log_error("cannot specify a database name with --all");
+ exit(1);
+ }
+ cparams.dbname = maintenance_db;
+ }
+ else if (db != NULL)
+ {
+ if (opts.dbpattern)
+ {
+ pg_log_error("cannot specify both a database name and database patterns");
+ exit(1);
+ }
+ cparams.dbname = db;
+ }
+
+ if (opts.alldb || opts.dbpattern)
+ {
+ conn = connectMaintenanceDatabase(&cparams, progname, opts.echo);
+ compile_database_list(conn, &databases, NULL);
+ }
+ else
+ {
+ if (cparams.dbname == NULL)
+ {
+ if (getenv("PGDATABASE"))
+ cparams.dbname = getenv("PGDATABASE");
+ else if (getenv("PGUSER"))
+ cparams.dbname = getenv("PGUSER");
+ else
+ cparams.dbname = get_user_name_or_exit(progname);
+ }
+ conn = connectDatabase(&cparams, progname, opts.echo, false, true);
+ compile_database_list(conn, &databases, PQdb(conn));
+ }
+
+ if (databases.head == NULL)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ pg_log_error("no databases to check");
+ exit(0);
+ }
+
+ /*
+ * Compile a list of all relations spanning all databases to be checked.
+ */
+ for (cell = databases.head; cell; cell = cell->next)
+ {
+ PGresult *result;
+ int ntups;
+ const char *amcheck_schema = NULL;
+ DatabaseInfo *dat = (DatabaseInfo *) cell->ptr;
+
+ cparams.override_dbname = dat->datname;
+ if (conn == NULL || strcmp(PQdb(conn), dat->datname) != 0)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ conn = connectDatabase(&cparams, progname, opts.echo, false, true);
+ }
+
+ /*
+ * Optionally install amcheck if not already installed in this
+ * database.
+ */
+ if (opts.install_missing)
+ {
+ char *schema;
+ char *install_sql;
+
+ /*
+ * Must re-escape the schema name for each database, as the
+ * escaping rules may change.
+ */
+ schema = PQescapeIdentifier(conn, opts.install_schema,
+ strlen(opts.install_schema));
+ install_sql = psprintf("CREATE EXTENSION IF NOT EXISTS amcheck WITH SCHEMA %s",
+ schema);
+
+ executeCommand(conn, install_sql, opts.echo);
+ pfree(install_sql);
+ pfree(schema);
+ }
+
+ /*
+ * Verify that amcheck is installed for this next database. User
+ * error could result in a database not having amcheck that should
+ * have it, but we also could be iterating over multiple databases
+ * where not all of them have amcheck installed (for example,
+ * 'template1').
+ */
+ result = executeQuery(conn, amcheck_sql, opts.echo);
+ if (PQresultStatus(result) != PGRES_TUPLES_OK)
+ {
+ /* Querying the catalog failed. */
+ pg_log_error("database \"%s\": %s",
+ PQdb(conn), PQerrorMessage(conn));
+ pg_log_info("query was: %s", amcheck_sql);
+ PQclear(result);
+ disconnectDatabase(conn);
+ exit(1);
+ }
+ ntups = PQntuples(result);
+ if (ntups == 0)
+ {
+ /* Querying the catalog succeeded, but amcheck is missing. */
+ pg_log_warning("skipping database \"%s\": amcheck is not installed",
+ PQdb(conn));
+ disconnectDatabase(conn);
+ conn = NULL;
+ continue;
+ }
+ amcheck_schema = PQgetvalue(result, 0, 0);
+ if (opts.verbose)
+ pg_log_info("in database \"%s\": using amcheck version \"%s\" in schema \"%s\"",
+ PQdb(conn), PQgetvalue(result, 0, 1), amcheck_schema);
+ dat->amcheck_schema = PQescapeIdentifier(conn, amcheck_schema,
+ strlen(amcheck_schema));
+ PQclear(result);
+
+ compile_relation_list_one_db(conn, &relations, dat, &pagestotal);
+ }
+
+ /*
+ * Check that all inclusion patterns matched at least one schema or
+ * relation that we can check.
+ */
+ for (pattern_id = 0; pattern_id < opts.include.len; pattern_id++)
+ {
+ PatternInfo *pat = &opts.include.data[pattern_id];
+
+ if (!pat->matched && (pat->nsp_regex != NULL || pat->rel_regex != NULL))
+ {
+ failed = opts.strict_names;
+
+ if (pat->heap_only)
+ log_no_match("no heap tables to check matching \"%s\"",
+ pat->pattern);
+ else if (pat->btree_only)
+ log_no_match("no btree indexes to check matching \"%s\"",
+ pat->pattern);
+ else if (pat->rel_regex == NULL)
+ log_no_match("no relations to check in schemas matching \"%s\"",
+ pat->pattern);
+ else
+ log_no_match("no relations to check matching \"%s\"",
+ pat->pattern);
+ }
+ }
+
+ if (failed)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ exit(1);
+ }
+
+ /*
+ * Set parallel_workers to the lesser of opts.jobs and the number of
+ * relations.
+ */
+ parallel_workers = 0;
+ for (cell = relations.head; cell; cell = cell->next)
+ {
+ reltotal++;
+ if (parallel_workers < opts.jobs)
+ parallel_workers++;
+ }
+
+ if (reltotal == 0)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ pg_log_error("no relations to check");
+ exit(1);
+ }
+ progress_report(reltotal, relprogress, pagestotal, pageschecked,
+ NULL, true, false);
+
+ /*
+ * Main event loop.
+ *
+ * We use server-side parallelism to check up to parallel_workers
+ * relations in parallel. The list of relations was computed in database
+ * order, which minimizes the number of connects and disconnects as we
+ * process the list.
+ */
+ latest_datname = NULL;
+ sa = ParallelSlotsSetup(parallel_workers, &cparams, progname, opts.echo,
+ NULL);
+ if (conn != NULL)
+ {
+ ParallelSlotsAdoptConn(sa, conn);
+ conn = NULL;
+ }
+
+ initPQExpBuffer(&sql);
+ for (relprogress = 0, cell = relations.head; cell; cell = cell->next)
+ {
+ ParallelSlot *free_slot;
+ RelationInfo *rel;
+
+ rel = (RelationInfo *) cell->ptr;
+
+ if (CancelRequested)
+ {
+ failed = true;
+ break;
+ }
+
+ /*
+ * The list of relations is in database sorted order. If this next
+ * relation is in a different database than the last one seen, we are
+ * about to start checking this database. Note that other slots may
+ * still be working on relations from prior databases.
+ */
+ latest_datname = rel->datinfo->datname;
+
+ progress_report(reltotal, relprogress, pagestotal, pageschecked,
+ latest_datname, false, false);
+
+ relprogress++;
+ pageschecked += rel->blocks_to_check;
+
+ /*
+ * Get a parallel slot for the next amcheck command, blocking if
+ * necessary until one is available, or until a previously issued slot
+ * command fails, indicating that we should abort checking the
+ * remaining objects.
+ */
+ free_slot = ParallelSlotsGetIdle(sa, rel->datinfo->datname);
+ if (!free_slot)
+ {
+ /*
+ * Something failed. We don't need to know what it was, because
+ * the handler should already have emitted the necessary error
+ * messages.
+ */
+ failed = true;
+ break;
+ }
+
+ if (opts.verbose)
+ PQsetErrorVerbosity(free_slot->connection, PQERRORS_VERBOSE);
+
+ /*
+ * Execute the appropriate amcheck command for this relation using our
+ * slot's database connection. We do not wait for the command to
+ * complete, nor do we perform any error checking, as that is done by
+ * the parallel slots and our handler callback functions.
+ */
+ if (rel->is_heap)
+ {
+ if (opts.verbose)
+ {
+ if (opts.show_progress && progress_since_last_stderr)
+ fprintf(stderr, "\n");
+ pg_log_info("checking heap table \"%s.%s.%s\"",
+ rel->datinfo->datname, rel->nspname, rel->relname);
+ progress_since_last_stderr = false;
+ }
+ prepare_heap_command(&sql, rel, free_slot->connection);
+ rel->sql = pstrdup(sql.data); /* pg_free'd after command */
+ ParallelSlotSetHandler(free_slot, verify_heap_slot_handler, rel);
+ run_command(free_slot, rel->sql);
+ }
+ else
+ {
+ if (opts.verbose)
+ {
+ if (opts.show_progress && progress_since_last_stderr)
+ fprintf(stderr, "\n");
+
+ pg_log_info("checking btree index \"%s.%s.%s\"",
+ rel->datinfo->datname, rel->nspname, rel->relname);
+ progress_since_last_stderr = false;
+ }
+ prepare_btree_command(&sql, rel, free_slot->connection);
+ rel->sql = pstrdup(sql.data); /* pg_free'd after command */
+ ParallelSlotSetHandler(free_slot, verify_btree_slot_handler, rel);
+ run_command(free_slot, rel->sql);
+ }
+ }
+ termPQExpBuffer(&sql);
+
+ if (!failed)
+ {
+
+ /*
+ * Wait for all slots to complete, or for one to indicate that an
+ * error occurred. Like above, we rely on the handler emitting the
+ * necessary error messages.
+ */
+ if (sa && !ParallelSlotsWaitCompletion(sa))
+ failed = true;
+
+ progress_report(reltotal, relprogress, pagestotal, pageschecked, NULL, true, true);
+ }
+
+ if (sa)
+ {
+ ParallelSlotsTerminate(sa);
+ FREE_AND_SET_NULL(sa);
+ }
+
+ if (failed)
+ exit(1);
+
+ if (!all_checks_pass)
+ exit(2);
+}
+
+/*
+ * prepare_heap_command
+ *
+ * Creates a SQL command for running amcheck checking on the given heap
+ * relation. The command is phrased as a SQL query, with column order and
+ * names matching the expectations of verify_heap_slot_handler, which will
+ * receive and handle each row returned from the verify_heapam() function.
+ *
+ * The constructed SQL command will silently skip temporary tables, as checking
+ * them would needlessly draw errors from the underlying amcheck function.
+ *
+ * sql: buffer into which the heap table checking command will be written
+ * rel: relation information for the heap table to be checked
+ * conn: the connection to be used, for string escaping purposes
+ */
+static void
+prepare_heap_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
+{
+ resetPQExpBuffer(sql);
+ appendPQExpBuffer(sql,
+ "SELECT v.blkno, v.offnum, v.attnum, v.msg "
+ "FROM pg_catalog.pg_class c, %s.verify_heapam("
+ "\nrelation := c.oid, on_error_stop := %s, check_toast := %s, skip := '%s'",
+ rel->datinfo->amcheck_schema,
+ opts.on_error_stop ? "true" : "false",
+ opts.reconcile_toast ? "true" : "false",
+ opts.skip);
+
+ if (opts.startblock >= 0)
+ appendPQExpBuffer(sql, ", startblock := " INT64_FORMAT, opts.startblock);
+ if (opts.endblock >= 0)
+ appendPQExpBuffer(sql, ", endblock := " INT64_FORMAT, opts.endblock);
+
+ appendPQExpBuffer(sql,
+ "\n) v WHERE c.oid = %u "
+ "AND c.relpersistence != 't'",
+ rel->reloid);
+}
+
+/*
+ * prepare_btree_command
+ *
+ * Creates a SQL command for running amcheck checking on the given btree index
+ * relation. The command does not select any columns, as btree checking
+ * functions do not return any, but rather return corruption information by
+ * raising errors, which verify_btree_slot_handler expects.
+ *
+ * The constructed SQL command will silently skip temporary indexes, and
+ * indexes being reindexed concurrently, as checking them would needlessly draw
+ * errors from the underlying amcheck functions.
+ *
+ * sql: buffer into which the heap table checking command will be written
+ * rel: relation information for the index to be checked
+ * conn: the connection to be used, for string escaping purposes
+ */
+static void
+prepare_btree_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
+{
+ resetPQExpBuffer(sql);
+
+ if (opts.parent_check)
+ appendPQExpBuffer(sql,
+ "SELECT %s.bt_index_parent_check("
+ "index := c.oid, heapallindexed := %s, rootdescend := %s)"
+ "\nFROM pg_catalog.pg_class c, pg_catalog.pg_index i "
+ "WHERE c.oid = %u "
+ "AND c.oid = i.indexrelid "
+ "AND c.relpersistence != 't' "
+ "AND i.indisready AND i.indisvalid AND i.indislive",
+ rel->datinfo->amcheck_schema,
+ (opts.heapallindexed ? "true" : "false"),
+ (opts.rootdescend ? "true" : "false"),
+ rel->reloid);
+ else
+ appendPQExpBuffer(sql,
+ "SELECT %s.bt_index_check("
+ "index := c.oid, heapallindexed := %s)"
+ "\nFROM pg_catalog.pg_class c, pg_catalog.pg_index i "
+ "WHERE c.oid = %u "
+ "AND c.oid = i.indexrelid "
+ "AND c.relpersistence != 't' "
+ "AND i.indisready AND i.indisvalid AND i.indislive",
+ rel->datinfo->amcheck_schema,
+ (opts.heapallindexed ? "true" : "false"),
+ rel->reloid);
+}
+
+/*
+ * run_command
+ *
+ * Sends a command to the server without waiting for the command to complete.
+ * Logs an error if the command cannot be sent, but otherwise any errors are
+ * expected to be handled by a ParallelSlotHandler.
+ *
+ * If reconnecting to the database is necessary, the cparams argument may be
+ * modified.
+ *
+ * slot: slot with connection to the server we should use for the command
+ * sql: query to send
+ */
+static void
+run_command(ParallelSlot *slot, const char *sql)
+{
+ if (opts.echo)
+ printf("%s\n", sql);
+
+ if (PQsendQuery(slot->connection, sql) == 0)
+ {
+ pg_log_error("error sending command to database \"%s\": %s",
+ PQdb(slot->connection),
+ PQerrorMessage(slot->connection));
+ pg_log_error("command was: %s", sql);
+ exit(1);
+ }
+}
+
+/*
+ * should_processing_continue
+ *
+ * Checks a query result returned from a query (presumably issued on a slot's
+ * connection) to determine if parallel slots should continue issuing further
+ * commands.
+ *
+ * Note: Heap relation corruption is reported by verify_heapam() via the result
+ * set, rather than an ERROR, but running verify_heapam() on a corrupted heap
+ * table may still result in an error being returned from the server due to
+ * missing relation files, bad checksums, etc. The btree corruption checking
+ * functions always use errors to communicate corruption messages. We can't
+ * just abort processing because we got a mere ERROR.
+ *
+ * res: result from an executed sql query
+ */
+static bool
+should_processing_continue(PGresult *res)
+{
+ const char *severity;
+
+ switch (PQresultStatus(res))
+ {
+ /* These are expected and ok */
+ case PGRES_COMMAND_OK:
+ case PGRES_TUPLES_OK:
+ case PGRES_NONFATAL_ERROR:
+ break;
+
+ /* This is expected but requires closer scrutiny */
+ case PGRES_FATAL_ERROR:
+ severity = PQresultErrorField(res, PG_DIAG_SEVERITY_NONLOCALIZED);
+ if (severity == NULL)
+ return false; /* libpq failure, probably lost connection */
+ if (strcmp(severity, "FATAL") == 0)
+ return false;
+ if (strcmp(severity, "PANIC") == 0)
+ return false;
+ break;
+
+ /* These are unexpected */
+ case PGRES_BAD_RESPONSE:
+ case PGRES_EMPTY_QUERY:
+ case PGRES_COPY_OUT:
+ case PGRES_COPY_IN:
+ case PGRES_COPY_BOTH:
+ case PGRES_SINGLE_TUPLE:
+ case PGRES_PIPELINE_SYNC:
+ case PGRES_PIPELINE_ABORTED:
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Returns a copy of the argument string with all lines indented four spaces.
+ *
+ * The caller should pg_free the result when finished with it.
+ */
+static char *
+indent_lines(const char *str)
+{
+ PQExpBufferData buf;
+ const char *c;
+ char *result;
+
+ initPQExpBuffer(&buf);
+ appendPQExpBufferStr(&buf, " ");
+ for (c = str; *c; c++)
+ {
+ appendPQExpBufferChar(&buf, *c);
+ if (c[0] == '\n' && c[1] != '\0')
+ appendPQExpBufferStr(&buf, " ");
+ }
+ result = pstrdup(buf.data);
+ termPQExpBuffer(&buf);
+
+ return result;
+}
+
+/*
+ * verify_heap_slot_handler
+ *
+ * ParallelSlotHandler that receives results from a heap table checking command
+ * created by prepare_heap_command and outputs the results for the user.
+ *
+ * res: result from an executed sql query
+ * conn: connection on which the sql query was executed
+ * context: the sql query being handled, as a cstring
+ */
+static bool
+verify_heap_slot_handler(PGresult *res, PGconn *conn, void *context)
+{
+ RelationInfo *rel = (RelationInfo *) context;
+
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ int i;
+ int ntups = PQntuples(res);
+
+ if (ntups > 0)
+ all_checks_pass = false;
+
+ for (i = 0; i < ntups; i++)
+ {
+ const char *msg;
+
+ /* The message string should never be null, but check */
+ if (PQgetisnull(res, i, 3))
+ msg = "NO MESSAGE";
+ else
+ msg = PQgetvalue(res, i, 3);
+
+ if (!PQgetisnull(res, i, 2))
+ printf(_("heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname,
+ PQgetvalue(res, i, 0), /* blkno */
+ PQgetvalue(res, i, 1), /* offnum */
+ PQgetvalue(res, i, 2)); /* attnum */
+
+ else if (!PQgetisnull(res, i, 1))
+ printf(_("heap table \"%s.%s.%s\", block %s, offset %s:\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname,
+ PQgetvalue(res, i, 0), /* blkno */
+ PQgetvalue(res, i, 1)); /* offnum */
+
+ else if (!PQgetisnull(res, i, 0))
+ printf(_("heap table \"%s.%s.%s\", block %s:\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname,
+ PQgetvalue(res, i, 0)); /* blkno */
+
+ else
+ printf(_("heap table \"%s.%s.%s\":\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname);
+
+ printf(" %s\n", msg);
+ }
+ }
+ else if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ char *msg = indent_lines(PQerrorMessage(conn));
+
+ all_checks_pass = false;
+ printf(_("heap table \"%s.%s.%s\":\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname);
+ printf("%s", msg);
+ if (opts.verbose)
+ printf(_("query was: %s\n"), rel->sql);
+ FREE_AND_SET_NULL(msg);
+ }
+
+ FREE_AND_SET_NULL(rel->sql);
+ FREE_AND_SET_NULL(rel->nspname);
+ FREE_AND_SET_NULL(rel->relname);
+
+ return should_processing_continue(res);
+}
+
+/*
+ * verify_btree_slot_handler
+ *
+ * ParallelSlotHandler that receives results from a btree checking command
+ * created by prepare_btree_command and outputs them for the user. The results
+ * from the btree checking command is assumed to be empty, but when the results
+ * are an error code, the useful information about the corruption is expected
+ * in the connection's error message.
+ *
+ * res: result from an executed sql query
+ * conn: connection on which the sql query was executed
+ * context: unused
+ */
+static bool
+verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context)
+{
+ RelationInfo *rel = (RelationInfo *) context;
+
+ if (PQresultStatus(res) == PGRES_TUPLES_OK)
+ {
+ int ntups = PQntuples(res);
+
+ if (ntups > 1)
+ {
+ /*
+ * We expect the btree checking functions to return one void row
+ * each, or zero rows if the check was skipped due to the object
+ * being in the wrong state to be checked, so we should output some
+ * sort of warning if we get anything more, not because it
+ * indicates corruption, but because it suggests a mismatch between
+ * amcheck and pg_amcheck versions.
+ *
+ * In conjunction with --progress, anything written to stderr at
+ * this time would present strangely to the user without an extra
+ * newline, so we print one. If we were multithreaded, we'd have
+ * to avoid splitting this across multiple calls, but we're in an
+ * event loop, so it doesn't matter.
+ */
+ if (opts.show_progress && progress_since_last_stderr)
+ fprintf(stderr, "\n");
+ pg_log_warning("btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d",
+ rel->datinfo->datname, rel->nspname, rel->relname, ntups);
+ if (opts.verbose)
+ pg_log_info("query was: %s", rel->sql);
+ pg_log_warning("Are %s's and amcheck's versions compatible?",
+ progname);
+ progress_since_last_stderr = false;
+ }
+ }
+ else
+ {
+ char *msg = indent_lines(PQerrorMessage(conn));
+
+ all_checks_pass = false;
+ printf(_("btree index \"%s.%s.%s\":\n"),
+ rel->datinfo->datname, rel->nspname, rel->relname);
+ printf("%s", msg);
+ if (opts.verbose)
+ printf(_("query was: %s\n"), rel->sql);
+ FREE_AND_SET_NULL(msg);
+ }
+
+ FREE_AND_SET_NULL(rel->sql);
+ FREE_AND_SET_NULL(rel->nspname);
+ FREE_AND_SET_NULL(rel->relname);
+
+ return should_processing_continue(res);
+}
+
+/*
+ * help
+ *
+ * Prints help page for the program
+ *
+ * progname: the name of the executed program, such as "pg_amcheck"
+ */
+static void
+help(const char *progname)
+{
+ printf(_("%s checks objects in a PostgreSQL database for corruption.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
+ printf(_("\nTarget options:\n"));
+ printf(_(" -a, --all check all databases\n"));
+ printf(_(" -d, --database=PATTERN check matching database(s)\n"));
+ printf(_(" -D, --exclude-database=PATTERN do NOT check matching database(s)\n"));
+ printf(_(" -i, --index=PATTERN check matching index(es)\n"));
+ printf(_(" -I, --exclude-index=PATTERN do NOT check matching index(es)\n"));
+ printf(_(" -r, --relation=PATTERN check matching relation(s)\n"));
+ printf(_(" -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"));
+ printf(_(" -s, --schema=PATTERN check matching schema(s)\n"));
+ printf(_(" -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"));
+ printf(_(" -t, --table=PATTERN check matching table(s)\n"));
+ printf(_(" -T, --exclude-table=PATTERN do NOT check matching table(s)\n"));
+ printf(_(" --no-dependent-indexes do NOT expand list of relations to include indexes\n"));
+ printf(_(" --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"));
+ printf(_(" --no-strict-names do NOT require patterns to match objects\n"));
+ printf(_("\nTable checking options:\n"));
+ printf(_(" --exclude-toast-pointers do NOT follow relation TOAST pointers\n"));
+ printf(_(" --on-error-stop stop checking at end of first corrupt page\n"));
+ printf(_(" --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"));
+ printf(_(" --startblock=BLOCK begin checking table(s) at the given block number\n"));
+ printf(_(" --endblock=BLOCK check table(s) only up to the given block number\n"));
+ printf(_("\nB-tree index checking options:\n"));
+ printf(_(" --heapallindexed check that all heap tuples are found within indexes\n"));
+ printf(_(" --parent-check check index parent/child relationships\n"));
+ printf(_(" --rootdescend search from root page to refind tuples\n"));
+ printf(_("\nConnection options:\n"));
+ printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
+ printf(_(" -p, --port=PORT database server port\n"));
+ printf(_(" -U, --username=USERNAME user name to connect as\n"));
+ printf(_(" -w, --no-password never prompt for password\n"));
+ printf(_(" -W, --password force password prompt\n"));
+ printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
+ printf(_("\nOther options:\n"));
+ printf(_(" -e, --echo show the commands being sent to the server\n"));
+ printf(_(" -j, --jobs=NUM use this many concurrent connections to the server\n"));
+ printf(_(" -P, --progress show progress information\n"));
+ printf(_(" -v, --verbose write a lot of output\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_(" --install-missing install missing extensions\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+
+ printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
+
+/*
+ * Print a progress report based on the global variables.
+ *
+ * Progress report is written at maximum once per second, unless the force
+ * parameter is set to true.
+ *
+ * If finished is set to true, this is the last progress report. The cursor
+ * is moved to the next line.
+ */
+static void
+progress_report(uint64 relations_total, uint64 relations_checked,
+ uint64 relpages_total, uint64 relpages_checked,
+ const char *datname, bool force, bool finished)
+{
+ int percent_rel = 0;
+ int percent_pages = 0;
+ char checked_rel[32];
+ char total_rel[32];
+ char checked_pages[32];
+ char total_pages[32];
+ pg_time_t now;
+
+ if (!opts.show_progress)
+ return;
+
+ now = time(NULL);
+ if (now == last_progress_report && !force && !finished)
+ return; /* Max once per second */
+
+ last_progress_report = now;
+ if (relations_total)
+ percent_rel = (int) (relations_checked * 100 / relations_total);
+ if (relpages_total)
+ percent_pages = (int) (relpages_checked * 100 / relpages_total);
+
+ /*
+ * Separate step to keep platform-dependent format code out of fprintf
+ * calls. We only test for INT64_FORMAT availability in snprintf, not
+ * fprintf.
+ */
+ snprintf(checked_rel, sizeof(checked_rel), INT64_FORMAT, relations_checked);
+ snprintf(total_rel, sizeof(total_rel), INT64_FORMAT, relations_total);
+ snprintf(checked_pages, sizeof(checked_pages), INT64_FORMAT, relpages_checked);
+ snprintf(total_pages, sizeof(total_pages), INT64_FORMAT, relpages_total);
+
+#define VERBOSE_DATNAME_LENGTH 35
+ if (opts.verbose)
+ {
+ if (!datname)
+
+ /*
+ * No datname given, so clear the status line (used for first and
+ * last call)
+ */
+ fprintf(stderr,
+ _("%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"),
+ (int) strlen(total_rel),
+ checked_rel, total_rel, percent_rel,
+ (int) strlen(total_pages),
+ checked_pages, total_pages, percent_pages,
+ VERBOSE_DATNAME_LENGTH + 2, "");
+ else
+ {
+ bool truncate = (strlen(datname) > VERBOSE_DATNAME_LENGTH);
+
+ fprintf(stderr,
+ _("%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"),
+ (int) strlen(total_rel),
+ checked_rel, total_rel, percent_rel,
+ (int) strlen(total_pages),
+ checked_pages, total_pages, percent_pages,
+ /* Prefix with "..." if we do leading truncation */
+ truncate ? "..." : "",
+ truncate ? VERBOSE_DATNAME_LENGTH - 3 : VERBOSE_DATNAME_LENGTH,
+ truncate ? VERBOSE_DATNAME_LENGTH - 3 : VERBOSE_DATNAME_LENGTH,
+ /* Truncate datname at beginning if it's too long */
+ truncate ? datname + strlen(datname) - VERBOSE_DATNAME_LENGTH + 3 : datname);
+ }
+ }
+ else
+ fprintf(stderr,
+ _("%*s/%s relations (%d%%), %*s/%s pages (%d%%)"),
+ (int) strlen(total_rel),
+ checked_rel, total_rel, percent_rel,
+ (int) strlen(total_pages),
+ checked_pages, total_pages, percent_pages);
+
+ /*
+ * Stay on the same line if reporting to a terminal and we're not done
+ * yet.
+ */
+ if (!finished && isatty(fileno(stderr)))
+ {
+ fputc('\r', stderr);
+ progress_since_last_stderr = true;
+ }
+ else
+ fputc('\n', stderr);
+}
+
+/*
+ * Extend the pattern info array to hold one additional initialized pattern
+ * info entry.
+ *
+ * Returns a pointer to the new entry.
+ */
+static PatternInfo *
+extend_pattern_info_array(PatternInfoArray *pia)
+{
+ PatternInfo *result;
+
+ pia->len++;
+ pia->data = (PatternInfo *) pg_realloc(pia->data, pia->len * sizeof(PatternInfo));
+ result = &pia->data[pia->len - 1];
+ memset(result, 0, sizeof(*result));
+
+ return result;
+}
+
+/*
+ * append_database_pattern
+ *
+ * Adds the given pattern interpreted as a database name pattern.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the database name pattern
+ * encoding: client encoding for parsing the pattern
+ */
+static void
+append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+{
+ PQExpBufferData buf;
+ int dotcnt;
+ PatternInfo *info = extend_pattern_info_array(pia);
+
+ initPQExpBuffer(&buf);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
+ info->pattern = pattern;
+ info->db_regex = pstrdup(buf.data);
+
+ termPQExpBuffer(&buf);
+}
+
+/*
+ * append_schema_pattern
+ *
+ * Adds the given pattern interpreted as a schema name pattern.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the schema name pattern
+ * encoding: client encoding for parsing the pattern
+ */
+static void
+append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+{
+ PQExpBufferData dbbuf;
+ PQExpBufferData nspbuf;
+ int dotcnt;
+ PatternInfo *info = extend_pattern_info_array(pia);
+
+ initPQExpBuffer(&dbbuf);
+ initPQExpBuffer(&nspbuf);
+
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
+ info->pattern = pattern;
+ if (dbbuf.data[0])
+ {
+ opts.dbpattern = true;
+ info->db_regex = pstrdup(dbbuf.data);
+ }
+ if (nspbuf.data[0])
+ info->nsp_regex = pstrdup(nspbuf.data);
+
+ termPQExpBuffer(&dbbuf);
+ termPQExpBuffer(&nspbuf);
+}
+
+/*
+ * append_relation_pattern_helper
+ *
+ * Adds to a list the given pattern interpreted as a relation pattern.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the relation name pattern
+ * encoding: client encoding for parsing the pattern
+ * heap_only: whether the pattern should only be matched against heap tables
+ * btree_only: whether the pattern should only be matched against btree indexes
+ */
+static void
+append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
+ int encoding, bool heap_only, bool btree_only)
+{
+ PQExpBufferData dbbuf;
+ PQExpBufferData nspbuf;
+ PQExpBufferData relbuf;
+ int dotcnt;
+ PatternInfo *info = extend_pattern_info_array(pia);
+
+ initPQExpBuffer(&dbbuf);
+ initPQExpBuffer(&nspbuf);
+ initPQExpBuffer(&relbuf);
+
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
+ info->pattern = pattern;
+ if (dbbuf.data[0])
+ {
+ opts.dbpattern = true;
+ info->db_regex = pstrdup(dbbuf.data);
+ }
+ if (nspbuf.data[0])
+ info->nsp_regex = pstrdup(nspbuf.data);
+ if (relbuf.data[0])
+ info->rel_regex = pstrdup(relbuf.data);
+
+ termPQExpBuffer(&dbbuf);
+ termPQExpBuffer(&nspbuf);
+ termPQExpBuffer(&relbuf);
+
+ info->heap_only = heap_only;
+ info->btree_only = btree_only;
+}
+
+/*
+ * append_relation_pattern
+ *
+ * Adds the given pattern interpreted as a relation pattern, to be matched
+ * against both heap tables and btree indexes.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the relation name pattern
+ * encoding: client encoding for parsing the pattern
+ */
+static void
+append_relation_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+{
+ append_relation_pattern_helper(pia, pattern, encoding, false, false);
+}
+
+/*
+ * append_heap_pattern
+ *
+ * Adds the given pattern interpreted as a relation pattern, to be matched only
+ * against heap tables.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the relation name pattern
+ * encoding: client encoding for parsing the pattern
+ */
+static void
+append_heap_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+{
+ append_relation_pattern_helper(pia, pattern, encoding, true, false);
+}
+
+/*
+ * append_btree_pattern
+ *
+ * Adds the given pattern interpreted as a relation pattern, to be matched only
+ * against btree indexes.
+ *
+ * pia: the pattern info array to be appended
+ * pattern: the relation name pattern
+ * encoding: client encoding for parsing the pattern
+ */
+static void
+append_btree_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
+{
+ append_relation_pattern_helper(pia, pattern, encoding, false, true);
+}
+
+/*
+ * append_db_pattern_cte
+ *
+ * Appends to the buffer the body of a Common Table Expression (CTE) containing
+ * the database portions filtered from the list of patterns expressed as two
+ * columns:
+ *
+ * pattern_id: the index of this pattern in pia->data[]
+ * rgx: the database regular expression parsed from the pattern
+ *
+ * Patterns without a database portion are skipped. Patterns with more than
+ * just a database portion are optionally skipped, depending on argument
+ * 'inclusive'.
+ *
+ * buf: the buffer to be appended
+ * pia: the array of patterns to be inserted into the CTE
+ * conn: the database connection
+ * inclusive: whether to include patterns with schema and/or relation parts
+ *
+ * Returns whether any database patterns were appended.
+ */
+static bool
+append_db_pattern_cte(PQExpBuffer buf, const PatternInfoArray *pia,
+ PGconn *conn, bool inclusive)
+{
+ int pattern_id;
+ const char *comma;
+ bool have_values;
+
+ comma = "";
+ have_values = false;
+ for (pattern_id = 0; pattern_id < pia->len; pattern_id++)
+ {
+ PatternInfo *info = &pia->data[pattern_id];
+
+ if (info->db_regex != NULL &&
+ (inclusive || (info->nsp_regex == NULL && info->rel_regex == NULL)))
+ {
+ if (!have_values)
+ appendPQExpBufferStr(buf, "\nVALUES");
+ have_values = true;
+ appendPQExpBuffer(buf, "%s\n(%d, ", comma, pattern_id);
+ appendStringLiteralConn(buf, info->db_regex, conn);
+ appendPQExpBufferStr(buf, ")");
+ comma = ",";
+ }
+ }
+
+ if (!have_values)
+ appendPQExpBufferStr(buf, "\nSELECT NULL, NULL, NULL WHERE false");
+
+ return have_values;
+}
+
+/*
+ * compile_database_list
+ *
+ * If any database patterns exist, or if --all was given, compiles a distinct
+ * list of databases to check using a SQL query based on the patterns plus the
+ * literal initial database name, if given. If no database patterns exist and
+ * --all was not given, the query is not necessary, and only the initial
+ * database name (if any) is added to the list.
+ *
+ * conn: connection to the initial database
+ * databases: the list onto which databases should be appended
+ * initial_dbname: an optional extra database name to include in the list
+ */
+static void
+compile_database_list(PGconn *conn, SimplePtrList *databases,
+ const char *initial_dbname)
+{
+ PGresult *res;
+ PQExpBufferData sql;
+ int ntups;
+ int i;
+ bool fatal;
+
+ if (initial_dbname)
+ {
+ DatabaseInfo *dat = (DatabaseInfo *) pg_malloc0(sizeof(DatabaseInfo));
+
+ /* This database is included. Add to list */
+ if (opts.verbose)
+ pg_log_info("including database \"%s\"", initial_dbname);
+
+ dat->datname = pstrdup(initial_dbname);
+ simple_ptr_list_append(databases, dat);
+ }
+
+ initPQExpBuffer(&sql);
+
+ /* Append the include patterns CTE. */
+ appendPQExpBufferStr(&sql, "WITH include_raw (pattern_id, rgx) AS (");
+ if (!append_db_pattern_cte(&sql, &opts.include, conn, true) &&
+ !opts.alldb)
+ {
+ /*
+ * None of the inclusion patterns (if any) contain database portions,
+ * so there is no need to query the database to resolve database
+ * patterns.
+ *
+ * Since we're also not operating under --all, we don't need to query
+ * the exhaustive list of connectable databases, either.
+ */
+ termPQExpBuffer(&sql);
+ return;
+ }
+
+ /* Append the exclude patterns CTE. */
+ appendPQExpBufferStr(&sql, "),\nexclude_raw (pattern_id, rgx) AS (");
+ append_db_pattern_cte(&sql, &opts.exclude, conn, false);
+ appendPQExpBufferStr(&sql, "),");
+
+ /*
+ * Append the database CTE, which includes whether each database is
+ * connectable and also joins against exclude_raw to determine whether
+ * each database is excluded.
+ */
+ appendPQExpBufferStr(&sql,
+ "\ndatabase (datname) AS ("
+ "\nSELECT d.datname "
+ "FROM pg_catalog.pg_database d "
+ "LEFT OUTER JOIN exclude_raw e "
+ "ON d.datname ~ e.rgx "
+ "\nWHERE d.datallowconn "
+ "AND e.pattern_id IS NULL"
+ "),"
+
+ /*
+ * Append the include_pat CTE, which joins the include_raw CTE against the
+ * databases CTE to determine if all the inclusion patterns had matches,
+ * and whether each matched pattern had the misfortune of only matching
+ * excluded or unconnectable databases.
+ */
+ "\ninclude_pat (pattern_id, checkable) AS ("
+ "\nSELECT i.pattern_id, "
+ "COUNT(*) FILTER ("
+ "WHERE d IS NOT NULL"
+ ") AS checkable"
+ "\nFROM include_raw i "
+ "LEFT OUTER JOIN database d "
+ "ON d.datname ~ i.rgx"
+ "\nGROUP BY i.pattern_id"
+ "),"
+
+ /*
+ * Append the filtered_databases CTE, which selects from the database CTE
+ * optionally joined against the include_raw CTE to only select databases
+ * that match an inclusion pattern. This appears to duplicate what the
+ * include_pat CTE already did above, but here we want only databases, and
+ * there we wanted patterns.
+ */
+ "\nfiltered_databases (datname) AS ("
+ "\nSELECT DISTINCT d.datname "
+ "FROM database d");
+ if (!opts.alldb)
+ appendPQExpBufferStr(&sql,
+ " INNER JOIN include_raw i "
+ "ON d.datname ~ i.rgx");
+ appendPQExpBufferStr(&sql,
+ ")"
+
+ /*
+ * Select the checkable databases and the unmatched inclusion patterns.
+ */
+ "\nSELECT pattern_id, datname FROM ("
+ "\nSELECT pattern_id, NULL::TEXT AS datname "
+ "FROM include_pat "
+ "WHERE checkable = 0 "
+ "UNION ALL"
+ "\nSELECT NULL, datname "
+ "FROM filtered_databases"
+ ") AS combined_records"
+ "\nORDER BY pattern_id NULLS LAST, datname");
+
+ res = executeQuery(conn, sql.data, opts.echo);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ pg_log_error("query failed: %s", PQerrorMessage(conn));
+ pg_log_info("query was: %s", sql.data);
+ disconnectDatabase(conn);
+ exit(1);
+ }
+ termPQExpBuffer(&sql);
+
+ ntups = PQntuples(res);
+ for (fatal = false, i = 0; i < ntups; i++)
+ {
+ int pattern_id = -1;
+ const char *datname = NULL;
+
+ if (!PQgetisnull(res, i, 0))
+ pattern_id = atoi(PQgetvalue(res, i, 0));
+ if (!PQgetisnull(res, i, 1))
+ datname = PQgetvalue(res, i, 1);
+
+ if (pattern_id >= 0)
+ {
+ /*
+ * Current record pertains to an inclusion pattern that matched no
+ * checkable databases.
+ */
+ fatal = opts.strict_names;
+ if (pattern_id >= opts.include.len)
+ {
+ pg_log_error("internal error: received unexpected database pattern_id %d",
+ pattern_id);
+ exit(1);
+ }
+ log_no_match("no connectable databases to check matching \"%s\"",
+ opts.include.data[pattern_id].pattern);
+ }
+ else
+ {
+ DatabaseInfo *dat;
+
+ /* Current record pertains to a database */
+ Assert(datname != NULL);
+
+ /* Avoid entering a duplicate entry matching the initial_dbname */
+ if (initial_dbname != NULL && strcmp(initial_dbname, datname) == 0)
+ continue;
+
+ /* This database is included. Add to list */
+ if (opts.verbose)
+ pg_log_info("including database \"%s\"", datname);
+
+ dat = (DatabaseInfo *) pg_malloc0(sizeof(DatabaseInfo));
+ dat->datname = pstrdup(datname);
+ simple_ptr_list_append(databases, dat);
+ }
+ }
+ PQclear(res);
+
+ if (fatal)
+ {
+ if (conn != NULL)
+ disconnectDatabase(conn);
+ exit(1);
+ }
+}
+
+/*
+ * append_rel_pattern_raw_cte
+ *
+ * Appends to the buffer the body of a Common Table Expression (CTE) containing
+ * the given patterns as six columns:
+ *
+ * pattern_id: the index of this pattern in pia->data[]
+ * db_regex: the database regexp parsed from the pattern, or NULL if the
+ * pattern had no database part
+ * nsp_regex: the namespace regexp parsed from the pattern, or NULL if the
+ * pattern had no namespace part
+ * rel_regex: the relname regexp parsed from the pattern, or NULL if the
+ * pattern had no relname part
+ * heap_only: true if the pattern applies only to heap tables (not indexes)
+ * btree_only: true if the pattern applies only to btree indexes (not tables)
+ *
+ * buf: the buffer to be appended
+ * patterns: the array of patterns to be inserted into the CTE
+ * conn: the database connection
+ */
+static void
+append_rel_pattern_raw_cte(PQExpBuffer buf, const PatternInfoArray *pia,
+ PGconn *conn)
+{
+ int pattern_id;
+ const char *comma;
+ bool have_values;
+
+ comma = "";
+ have_values = false;
+ for (pattern_id = 0; pattern_id < pia->len; pattern_id++)
+ {
+ PatternInfo *info = &pia->data[pattern_id];
+
+ if (!have_values)
+ appendPQExpBufferStr(buf, "\nVALUES");
+ have_values = true;
+ appendPQExpBuffer(buf, "%s\n(%d::INTEGER, ", comma, pattern_id);
+ if (info->db_regex == NULL)
+ appendPQExpBufferStr(buf, "NULL");
+ else
+ appendStringLiteralConn(buf, info->db_regex, conn);
+ appendPQExpBufferStr(buf, "::TEXT, ");
+ if (info->nsp_regex == NULL)
+ appendPQExpBufferStr(buf, "NULL");
+ else
+ appendStringLiteralConn(buf, info->nsp_regex, conn);
+ appendPQExpBufferStr(buf, "::TEXT, ");
+ if (info->rel_regex == NULL)
+ appendPQExpBufferStr(buf, "NULL");
+ else
+ appendStringLiteralConn(buf, info->rel_regex, conn);
+ if (info->heap_only)
+ appendPQExpBufferStr(buf, "::TEXT, true::BOOLEAN");
+ else
+ appendPQExpBufferStr(buf, "::TEXT, false::BOOLEAN");
+ if (info->btree_only)
+ appendPQExpBufferStr(buf, ", true::BOOLEAN");
+ else
+ appendPQExpBufferStr(buf, ", false::BOOLEAN");
+ appendPQExpBufferStr(buf, ")");
+ comma = ",";
+ }
+
+ if (!have_values)
+ appendPQExpBufferStr(buf,
+ "\nSELECT NULL::INTEGER, NULL::TEXT, NULL::TEXT, "
+ "NULL::TEXT, NULL::BOOLEAN, NULL::BOOLEAN "
+ "WHERE false");
+}
+
+/*
+ * append_rel_pattern_filtered_cte
+ *
+ * Appends to the buffer a Common Table Expression (CTE) which selects
+ * all patterns from the named raw CTE, filtered by database. All patterns
+ * which have no database portion or whose database portion matches our
+ * connection's database name are selected, with other patterns excluded.
+ *
+ * The basic idea here is that if we're connected to database "foo" and we have
+ * patterns "foo.bar.baz", "alpha.beta" and "one.two.three", we only want to
+ * use the first two while processing relations in this database, as the third
+ * one is not relevant.
+ *
+ * buf: the buffer to be appended
+ * raw: the name of the CTE to select from
+ * filtered: the name of the CTE to create
+ * conn: the database connection
+ */
+static void
+append_rel_pattern_filtered_cte(PQExpBuffer buf, const char *raw,
+ const char *filtered, PGconn *conn)
+{
+ appendPQExpBuffer(buf,
+ "\n%s (pattern_id, nsp_regex, rel_regex, heap_only, btree_only) AS ("
+ "\nSELECT pattern_id, nsp_regex, rel_regex, heap_only, btree_only "
+ "FROM %s r"
+ "\nWHERE (r.db_regex IS NULL "
+ "OR ",
+ filtered, raw);
+ appendStringLiteralConn(buf, PQdb(conn), conn);
+ appendPQExpBufferStr(buf, " ~ r.db_regex)");
+ appendPQExpBufferStr(buf,
+ " AND (r.nsp_regex IS NOT NULL"
+ " OR r.rel_regex IS NOT NULL)"
+ "),");
+}
+
+/*
+ * compile_relation_list_one_db
+ *
+ * Compiles a list of relations to check within the currently connected
+ * database based on the user supplied options, sorted by descending size,
+ * and appends them to the given list of relations.
+ *
+ * The cells of the constructed list contain all information about the relation
+ * necessary to connect to the database and check the object, including which
+ * database to connect to, where contrib/amcheck is installed, and the Oid and
+ * type of object (heap table vs. btree index). Rather than duplicating the
+ * database details per relation, the relation structs use references to the
+ * same database object, provided by the caller.
+ *
+ * conn: connection to this next database, which should be the same as in 'dat'
+ * relations: list onto which the relations information should be appended
+ * dat: the database info struct for use by each relation
+ * pagecount: gets incremented by the number of blocks to check in all
+ * relations added
+ */
+static void
+compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
+ const DatabaseInfo *dat,
+ uint64 *pagecount)
+{
+ PGresult *res;
+ PQExpBufferData sql;
+ int ntups;
+ int i;
+
+ initPQExpBuffer(&sql);
+ appendPQExpBufferStr(&sql, "WITH");
+
+ /* Append CTEs for the relation inclusion patterns, if any */
+ if (!opts.allrel)
+ {
+ appendPQExpBufferStr(&sql,
+ " include_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, btree_only) AS (");
+ append_rel_pattern_raw_cte(&sql, &opts.include, conn);
+ appendPQExpBufferStr(&sql, "\n),");
+ append_rel_pattern_filtered_cte(&sql, "include_raw", "include_pat", conn);
+ }
+
+ /* Append CTEs for the relation exclusion patterns, if any */
+ if (opts.excludetbl || opts.excludeidx || opts.excludensp)
+ {
+ appendPQExpBufferStr(&sql,
+ " exclude_raw (pattern_id, db_regex, nsp_regex, rel_regex, heap_only, btree_only) AS (");
+ append_rel_pattern_raw_cte(&sql, &opts.exclude, conn);
+ appendPQExpBufferStr(&sql, "\n),");
+ append_rel_pattern_filtered_cte(&sql, "exclude_raw", "exclude_pat", conn);
+ }
+
+ /* Append the relation CTE. */
+ appendPQExpBufferStr(&sql,
+ " relation (pattern_id, oid, nspname, relname, reltoastrelid, relpages, is_heap, is_btree) AS ("
+ "\nSELECT DISTINCT ON (c.oid");
+ if (!opts.allrel)
+ appendPQExpBufferStr(&sql, ", ip.pattern_id) ip.pattern_id,");
+ else
+ appendPQExpBufferStr(&sql, ") NULL::INTEGER AS pattern_id,");
+ appendPQExpBuffer(&sql,
+ "\nc.oid, n.nspname, c.relname, c.reltoastrelid, c.relpages, "
+ "c.relam = %u AS is_heap, "
+ "c.relam = %u AS is_btree"
+ "\nFROM pg_catalog.pg_class c "
+ "INNER JOIN pg_catalog.pg_namespace n "
+ "ON c.relnamespace = n.oid",
+ HEAP_TABLE_AM_OID, BTREE_AM_OID);
+ if (!opts.allrel)
+ appendPQExpBuffer(&sql,
+ "\nINNER JOIN include_pat ip"
+ "\nON (n.nspname ~ ip.nsp_regex OR ip.nsp_regex IS NULL)"
+ "\nAND (c.relname ~ ip.rel_regex OR ip.rel_regex IS NULL)"
+ "\nAND (c.relam = %u OR NOT ip.heap_only)"
+ "\nAND (c.relam = %u OR NOT ip.btree_only)",
+ HEAP_TABLE_AM_OID, BTREE_AM_OID);
+ if (opts.excludetbl || opts.excludeidx || opts.excludensp)
+ appendPQExpBuffer(&sql,
+ "\nLEFT OUTER JOIN exclude_pat ep"
+ "\nON (n.nspname ~ ep.nsp_regex OR ep.nsp_regex IS NULL)"
+ "\nAND (c.relname ~ ep.rel_regex OR ep.rel_regex IS NULL)"
+ "\nAND (c.relam = %u OR NOT ep.heap_only OR ep.rel_regex IS NULL)"
+ "\nAND (c.relam = %u OR NOT ep.btree_only OR ep.rel_regex IS NULL)",
+ HEAP_TABLE_AM_OID, BTREE_AM_OID);
+
+ /*
+ * Exclude temporary tables and indexes, which must necessarily belong to
+ * other sessions. (We don't create any ourselves.) We must ultimately
+ * exclude indexes marked invalid or not ready, but we delay that decision
+ * until firing off the amcheck command, as the state of an index may
+ * change by then.
+ */
+ appendPQExpBufferStr(&sql, "\nWHERE c.relpersistence != 't'");
+ if (opts.excludetbl || opts.excludeidx || opts.excludensp)
+ appendPQExpBufferStr(&sql, "\nAND ep.pattern_id IS NULL");
+
+ /*
+ * We need to be careful not to break the --no-dependent-toast and
+ * --no-dependent-indexes options. By default, the btree indexes, toast
+ * tables, and toast table btree indexes associated with primary heap
+ * tables are included, using their own CTEs below. We implement the
+ * --exclude-* options by not creating those CTEs, but that's no use if
+ * we've already selected the toast and indexes here. On the other hand,
+ * we want inclusion patterns that match indexes or toast tables to be
+ * honored. So, if inclusion patterns were given, we want to select all
+ * tables, toast tables, or indexes that match the patterns. But if no
+ * inclusion patterns were given, and we're simply matching all relations,
+ * then we only want to match the primary tables here.
+ */
+ if (opts.allrel)
+ appendPQExpBuffer(&sql,
+ " AND c.relam = %u "
+ "AND c.relkind IN ('r', 'm', 't') "
+ "AND c.relnamespace != %u",
+ HEAP_TABLE_AM_OID, PG_TOAST_NAMESPACE);
+ else
+ appendPQExpBuffer(&sql,
+ " AND c.relam IN (%u, %u)"
+ "AND c.relkind IN ('r', 'm', 't', 'i') "
+ "AND ((c.relam = %u AND c.relkind IN ('r', 'm', 't')) OR "
+ "(c.relam = %u AND c.relkind = 'i'))",
+ HEAP_TABLE_AM_OID, BTREE_AM_OID,
+ HEAP_TABLE_AM_OID, BTREE_AM_OID);
+
+ appendPQExpBufferStr(&sql,
+ "\nORDER BY c.oid)");
+
+ if (!opts.no_toast_expansion)
+ {
+ /*
+ * Include a CTE for toast tables associated with primary heap tables
+ * selected above, filtering by exclusion patterns (if any) that match
+ * toast table names.
+ */
+ appendPQExpBufferStr(&sql,
+ ", toast (oid, nspname, relname, relpages) AS ("
+ "\nSELECT t.oid, 'pg_toast', t.relname, t.relpages"
+ "\nFROM pg_catalog.pg_class t "
+ "INNER JOIN relation r "
+ "ON r.reltoastrelid = t.oid");
+ if (opts.excludetbl || opts.excludensp)
+ appendPQExpBufferStr(&sql,
+ "\nLEFT OUTER JOIN exclude_pat ep"
+ "\nON ('pg_toast' ~ ep.nsp_regex OR ep.nsp_regex IS NULL)"
+ "\nAND (t.relname ~ ep.rel_regex OR ep.rel_regex IS NULL)"
+ "\nAND ep.heap_only"
+ "\nWHERE ep.pattern_id IS NULL"
+ "\nAND t.relpersistence != 't'");
+ appendPQExpBufferStr(&sql,
+ "\n)");
+ }
+ if (!opts.no_btree_expansion)
+ {
+ /*
+ * Include a CTE for btree indexes associated with primary heap tables
+ * selected above, filtering by exclusion patterns (if any) that match
+ * btree index names.
+ */
+ appendPQExpBuffer(&sql,
+ ", index (oid, nspname, relname, relpages) AS ("
+ "\nSELECT c.oid, r.nspname, c.relname, c.relpages "
+ "FROM relation r"
+ "\nINNER JOIN pg_catalog.pg_index i "
+ "ON r.oid = i.indrelid "
+ "INNER JOIN pg_catalog.pg_class c "
+ "ON i.indexrelid = c.oid "
+ "AND c.relpersistence != 't'");
+ if (opts.excludeidx || opts.excludensp)
+ appendPQExpBufferStr(&sql,
+ "\nINNER JOIN pg_catalog.pg_namespace n "
+ "ON c.relnamespace = n.oid"
+ "\nLEFT OUTER JOIN exclude_pat ep "
+ "ON (n.nspname ~ ep.nsp_regex OR ep.nsp_regex IS NULL) "
+ "AND (c.relname ~ ep.rel_regex OR ep.rel_regex IS NULL) "
+ "AND ep.btree_only"
+ "\nWHERE ep.pattern_id IS NULL");
+ else
+ appendPQExpBufferStr(&sql,
+ "\nWHERE true");
+ appendPQExpBuffer(&sql,
+ " AND c.relam = %u "
+ "AND c.relkind = 'i'",
+ BTREE_AM_OID);
+ if (opts.no_toast_expansion)
+ appendPQExpBuffer(&sql,
+ " AND c.relnamespace != %u",
+ PG_TOAST_NAMESPACE);
+ appendPQExpBufferStr(&sql, "\n)");
+ }
+
+ if (!opts.no_toast_expansion && !opts.no_btree_expansion)
+ {
+ /*
+ * Include a CTE for btree indexes associated with toast tables of
+ * primary heap tables selected above, filtering by exclusion patterns
+ * (if any) that match the toast index names.
+ */
+ appendPQExpBuffer(&sql,
+ ", toast_index (oid, nspname, relname, relpages) AS ("
+ "\nSELECT c.oid, 'pg_toast', c.relname, c.relpages "
+ "FROM toast t "
+ "INNER JOIN pg_catalog.pg_index i "
+ "ON t.oid = i.indrelid"
+ "\nINNER JOIN pg_catalog.pg_class c "
+ "ON i.indexrelid = c.oid "
+ "AND c.relpersistence != 't'");
+ if (opts.excludeidx)
+ appendPQExpBufferStr(&sql,
+ "\nLEFT OUTER JOIN exclude_pat ep "
+ "ON ('pg_toast' ~ ep.nsp_regex OR ep.nsp_regex IS NULL) "
+ "AND (c.relname ~ ep.rel_regex OR ep.rel_regex IS NULL) "
+ "AND ep.btree_only "
+ "WHERE ep.pattern_id IS NULL");
+ else
+ appendPQExpBufferStr(&sql,
+ "\nWHERE true");
+ appendPQExpBuffer(&sql,
+ " AND c.relam = %u"
+ " AND c.relkind = 'i')",
+ BTREE_AM_OID);
+ }
+
+ /*
+ * Roll-up distinct rows from CTEs.
+ *
+ * Relations that match more than one pattern may occur more than once in
+ * the list, and indexes and toast for primary relations may also have
+ * matched in their own right, so we rely on UNION to deduplicate the
+ * list.
+ */
+ appendPQExpBuffer(&sql,
+ "\nSELECT pattern_id, is_heap, is_btree, oid, nspname, relname, relpages "
+ "FROM (");
+ appendPQExpBufferStr(&sql,
+ /* Inclusion patterns that failed to match */
+ "\nSELECT pattern_id, is_heap, is_btree, "
+ "NULL::OID AS oid, "
+ "NULL::TEXT AS nspname, "
+ "NULL::TEXT AS relname, "
+ "NULL::INTEGER AS relpages"
+ "\nFROM relation "
+ "WHERE pattern_id IS NOT NULL "
+ "UNION"
+ /* Primary relations */
+ "\nSELECT NULL::INTEGER AS pattern_id, "
+ "is_heap, is_btree, oid, nspname, relname, relpages "
+ "FROM relation");
+ if (!opts.no_toast_expansion)
+ appendPQExpBufferStr(&sql,
+ " UNION"
+ /* Toast tables for primary relations */
+ "\nSELECT NULL::INTEGER AS pattern_id, TRUE AS is_heap, "
+ "FALSE AS is_btree, oid, nspname, relname, relpages "
+ "FROM toast");
+ if (!opts.no_btree_expansion)
+ appendPQExpBufferStr(&sql,
+ " UNION"
+ /* Indexes for primary relations */
+ "\nSELECT NULL::INTEGER AS pattern_id, FALSE AS is_heap, "
+ "TRUE AS is_btree, oid, nspname, relname, relpages "
+ "FROM index");
+ if (!opts.no_toast_expansion && !opts.no_btree_expansion)
+ appendPQExpBufferStr(&sql,
+ " UNION"
+ /* Indexes for toast relations */
+ "\nSELECT NULL::INTEGER AS pattern_id, FALSE AS is_heap, "
+ "TRUE AS is_btree, oid, nspname, relname, relpages "
+ "FROM toast_index");
+ appendPQExpBufferStr(&sql,
+ "\n) AS combined_records "
+ "ORDER BY relpages DESC NULLS FIRST, oid");
+
+ res = executeQuery(conn, sql.data, opts.echo);
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ pg_log_error("query failed: %s", PQerrorMessage(conn));
+ pg_log_info("query was: %s", sql.data);
+ disconnectDatabase(conn);
+ exit(1);
+ }
+ termPQExpBuffer(&sql);
+
+ ntups = PQntuples(res);
+ for (i = 0; i < ntups; i++)
+ {
+ int pattern_id = -1;
+ bool is_heap = false;
+ bool is_btree PG_USED_FOR_ASSERTS_ONLY = false;
+ Oid oid = InvalidOid;
+ const char *nspname = NULL;
+ const char *relname = NULL;
+ int relpages = 0;
+
+ if (!PQgetisnull(res, i, 0))
+ pattern_id = atoi(PQgetvalue(res, i, 0));
+ if (!PQgetisnull(res, i, 1))
+ is_heap = (PQgetvalue(res, i, 1)[0] == 't');
+ if (!PQgetisnull(res, i, 2))
+ is_btree = (PQgetvalue(res, i, 2)[0] == 't');
+ if (!PQgetisnull(res, i, 3))
+ oid = atooid(PQgetvalue(res, i, 3));
+ if (!PQgetisnull(res, i, 4))
+ nspname = PQgetvalue(res, i, 4);
+ if (!PQgetisnull(res, i, 5))
+ relname = PQgetvalue(res, i, 5);
+ if (!PQgetisnull(res, i, 6))
+ relpages = atoi(PQgetvalue(res, i, 6));
+
+ if (pattern_id >= 0)
+ {
+ /*
+ * Current record pertains to an inclusion pattern. Record that
+ * it matched.
+ */
+
+ if (pattern_id >= opts.include.len)
+ {
+ pg_log_error("internal error: received unexpected relation pattern_id %d",
+ pattern_id);
+ exit(1);
+ }
+
+ opts.include.data[pattern_id].matched = true;
+ }
+ else
+ {
+ /* Current record pertains to a relation */
+
+ RelationInfo *rel = (RelationInfo *) pg_malloc0(sizeof(RelationInfo));
+
+ Assert(OidIsValid(oid));
+ Assert((is_heap && !is_btree) || (is_btree && !is_heap));
+
+ rel->datinfo = dat;
+ rel->reloid = oid;
+ rel->is_heap = is_heap;
+ rel->nspname = pstrdup(nspname);
+ rel->relname = pstrdup(relname);
+ rel->relpages = relpages;
+ rel->blocks_to_check = relpages;
+ if (is_heap && (opts.startblock >= 0 || opts.endblock >= 0))
+ {
+ /*
+ * We apply --startblock and --endblock to heap tables, but
+ * not btree indexes, and for progress purposes we need to
+ * track how many blocks we expect to check.
+ */
+ if (opts.endblock >= 0 && rel->blocks_to_check > opts.endblock)
+ rel->blocks_to_check = opts.endblock + 1;
+ if (opts.startblock >= 0)
+ {
+ if (rel->blocks_to_check > opts.startblock)
+ rel->blocks_to_check -= opts.startblock;
+ else
+ rel->blocks_to_check = 0;
+ }
+ }
+ *pagecount += rel->blocks_to_check;
+
+ simple_ptr_list_append(relations, rel);
+ }
+ }
+ PQclear(res);
+}
diff --git a/src/bin/pg_amcheck/po/de.po b/src/bin/pg_amcheck/po/de.po
new file mode 100644
index 0000000..7bb2111
--- /dev/null
+++ b/src/bin/pg_amcheck/po/de.po
@@ -0,0 +1,523 @@
+# German message translation file for pg_amcheck
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_amcheck (PostgreSQL) package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pg_amcheck (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-05-05 22:49+0000\n"
+"PO-Revision-Date: 2022-05-06 10:33+0200\n"
+"Last-Translator: Peter Eisentraut <peter@eisentraut.org>\n"
+"Language-Team: German <pgsql-translators@postgresql.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../src/common/logging.c:259
+#, c-format
+msgid "fatal: "
+msgstr "Fatal: "
+
+#: ../../../src/common/logging.c:266
+#, c-format
+msgid "error: "
+msgstr "Fehler: "
+
+#: ../../../src/common/logging.c:273
+#, c-format
+msgid "warning: "
+msgstr "Warnung: "
+
+#: ../../fe_utils/cancel.c:189 ../../fe_utils/cancel.c:238
+msgid "Cancel request sent\n"
+msgstr "Abbruchsanforderung gesendet\n"
+
+#: ../../fe_utils/cancel.c:190 ../../fe_utils/cancel.c:239
+msgid "Could not send cancel request: "
+msgstr "Konnte Abbruchsanforderung nicht senden: "
+
+#: ../../fe_utils/connect_utils.c:92
+#, c-format
+msgid "could not connect to database %s: out of memory"
+msgstr "konnte nicht mit Datenbank %s verbinden: Speicher aufgebraucht"
+
+#: ../../fe_utils/connect_utils.c:120
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: ../../fe_utils/query_utils.c:33 ../../fe_utils/query_utils.c:58
+#: pg_amcheck.c:1678 pg_amcheck.c:2126
+#, c-format
+msgid "query failed: %s"
+msgstr "Anfrage fehlgeschlagen: %s"
+
+#: ../../fe_utils/query_utils.c:34 ../../fe_utils/query_utils.c:59
+#: pg_amcheck.c:598 pg_amcheck.c:1128 pg_amcheck.c:1679 pg_amcheck.c:2127
+#, c-format
+msgid "query was: %s"
+msgstr "Anfrage war: %s"
+
+#: pg_amcheck.c:330
+#, c-format
+msgid "number of parallel jobs must be at least 1"
+msgstr "Anzahl paralleler Jobs muss mindestens 1 sein"
+
+#: pg_amcheck.c:402
+#, c-format
+msgid "invalid argument for option %s"
+msgstr "ungültiges Argument für Option %s"
+
+#: pg_amcheck.c:411
+#, c-format
+msgid "invalid start block"
+msgstr "ungültiger Startblock"
+
+#: pg_amcheck.c:416
+#, c-format
+msgid "start block out of bounds"
+msgstr "Startblock außerhalb des gültigen Bereichs"
+
+#: pg_amcheck.c:426
+#, c-format
+msgid "invalid end block"
+msgstr "ungültiger Endblock"
+
+#: pg_amcheck.c:431
+#, c-format
+msgid "end block out of bounds"
+msgstr "Endblock außerhalb des gültigen Bereichs"
+
+#: pg_amcheck.c:456 pg_amcheck.c:482
+#, c-format
+msgid "Try \"%s --help\" for more information.\n"
+msgstr "Versuchen Sie »%s --help« für weitere Informationen.\n"
+
+#: pg_amcheck.c:464
+#, c-format
+msgid "end block precedes start block"
+msgstr "Endblock kommt vor dem Startblock"
+
+#: pg_amcheck.c:480
+#, c-format
+msgid "too many command-line arguments (first is \"%s\")"
+msgstr "zu viele Kommandozeilenargumente (das erste ist »%s«)"
+
+#: pg_amcheck.c:501
+#, c-format
+msgid "cannot specify a database name with --all"
+msgstr "ein Datenbankname kann nicht mit --all angegeben werden"
+
+#: pg_amcheck.c:510
+#, c-format
+msgid "cannot specify both a database name and database patterns"
+msgstr "Datenbankname und Datenbankmuster können nicht zusammen angegeben werden"
+
+#: pg_amcheck.c:540
+#, c-format
+msgid "no databases to check"
+msgstr "keine zu prüfenden Datenbanken"
+
+#: pg_amcheck.c:596
+#, c-format
+msgid "database \"%s\": %s"
+msgstr "Datenbank »%s«: %s"
+
+#: pg_amcheck.c:607
+#, c-format
+msgid "skipping database \"%s\": amcheck is not installed"
+msgstr "Datenbank »%s« übersprungen: amcheck nicht installiert"
+
+#: pg_amcheck.c:615
+#, c-format
+msgid "in database \"%s\": using amcheck version \"%s\" in schema \"%s\""
+msgstr "in Datenbank »%s«: verwende amcheck Version »%s« in Schema »%s«"
+
+#: pg_amcheck.c:637
+#, c-format
+msgid "no heap tables to check matching \"%s\""
+msgstr "keine zu prüfenden Tabellen, die mit »%s« übereinstimmen"
+
+#: pg_amcheck.c:640
+#, c-format
+msgid "no btree indexes to check matching \"%s\""
+msgstr "keine zu prüfenden B-Tree-Indexe, die mit »%s« übereinstimmen"
+
+#: pg_amcheck.c:643
+#, c-format
+msgid "no relations to check in schemas matching \"%s\""
+msgstr "keine zu prüfenden Relationen in Schemas, die mit »%s« übereinstimmen"
+
+#: pg_amcheck.c:646
+#, c-format
+msgid "no relations to check matching \"%s\""
+msgstr "keine zu prüfenden Relationen, die mit »%s« übereinstimmen"
+
+#: pg_amcheck.c:674
+#, c-format
+msgid "no relations to check"
+msgstr "keine zu prüfenden Relationen"
+
+#: pg_amcheck.c:758
+#, c-format
+msgid "checking heap table \"%s.%s.%s\""
+msgstr "prüfe Heap-Tabelle »%s.%s.%s«"
+
+#: pg_amcheck.c:774
+#, c-format
+msgid "checking btree index \"%s.%s.%s\""
+msgstr "prüfe B-Tree-Index »%s.%s.%s«"
+
+#: pg_amcheck.c:921
+#, c-format
+msgid "error sending command to database \"%s\": %s"
+msgstr "Fehler beim Senden von Befehl an Datenbank »%s«: %s"
+
+#: pg_amcheck.c:924
+#, c-format
+msgid "command was: %s"
+msgstr "Befehl war: %s"
+
+#: pg_amcheck.c:1041
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"
+msgstr "Heap-Tabelle »%s.%s.%s«, Block %s, Offset %s, Attribut %s:\n"
+
+#: pg_amcheck.c:1048
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s:\n"
+msgstr "Heap-Tabelle »%s.%s.%s«, Block %s, Offset %s:\n"
+
+#: pg_amcheck.c:1054
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s:\n"
+msgstr "Heap-Tabelle »%s.%s.%s«, Block %s:\n"
+
+#: pg_amcheck.c:1059 pg_amcheck.c:1070
+#, c-format
+msgid "heap table \"%s.%s.%s\":\n"
+msgstr "Heap-Tabelle »%s.%s.%s«:\n"
+
+#: pg_amcheck.c:1074 pg_amcheck.c:1143
+#, c-format
+msgid "query was: %s\n"
+msgstr "Anfrage war: %s\n"
+
+#: pg_amcheck.c:1125
+#, c-format
+msgid "btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d"
+msgstr "B-Tree-Index »%s.%s.%s«: B-Tree-Prüffunktion gab unerwartete Anzahl Zeilen zurück: %d"
+
+#: pg_amcheck.c:1129
+#, c-format
+msgid "Are %s's and amcheck's versions compatible?"
+msgstr "Sind die Versionen von %s und amcheck kompatibel?"
+
+#: pg_amcheck.c:1139
+#, c-format
+msgid "btree index \"%s.%s.%s\":\n"
+msgstr "B-Tree-Index »%s.%s.%s«:\n"
+
+#: pg_amcheck.c:1164
+#, c-format
+msgid ""
+"%s checks objects in a PostgreSQL database for corruption.\n"
+"\n"
+msgstr ""
+"%s prüft Objekte in einer PostgreSQL-Datenbank auf Beschädigung.\n"
+"\n"
+
+#: pg_amcheck.c:1165
+#, c-format
+msgid "Usage:\n"
+msgstr "Aufruf:\n"
+
+#: pg_amcheck.c:1166
+#, c-format
+msgid " %s [OPTION]... [DBNAME]\n"
+msgstr " %s [OPTION]... [DBNAME]\n"
+
+#: pg_amcheck.c:1167
+#, c-format
+msgid ""
+"\n"
+"Target options:\n"
+msgstr ""
+"\n"
+"Zieloptionen:\n"
+
+#: pg_amcheck.c:1168
+#, c-format
+msgid " -a, --all check all databases\n"
+msgstr " -a, --all alle Datenbanken prüfen\n"
+
+#: pg_amcheck.c:1169
+#, c-format
+msgid " -d, --database=PATTERN check matching database(s)\n"
+msgstr " -d, --database=MUSTER übereinstimmende Datenbanken prüfen\n"
+
+#: pg_amcheck.c:1170
+#, c-format
+msgid " -D, --exclude-database=PATTERN do NOT check matching database(s)\n"
+msgstr " -D, --exclude-database=MUSTER übereinstimmende Datenbanken NICHT prüfen\n"
+
+#: pg_amcheck.c:1171
+#, c-format
+msgid " -i, --index=PATTERN check matching index(es)\n"
+msgstr " -i, --index=MUSTER übereinstimmende Indexe prüfen\n"
+
+#: pg_amcheck.c:1172
+#, c-format
+msgid " -I, --exclude-index=PATTERN do NOT check matching index(es)\n"
+msgstr " -I, --exclude-index=MUSTER übereinstimmende Indexe NICHT prüfen\n"
+
+#: pg_amcheck.c:1173
+#, c-format
+msgid " -r, --relation=PATTERN check matching relation(s)\n"
+msgstr " -r, --relation=MUSTER übereinstimmende Relationen prüfen\n"
+
+#: pg_amcheck.c:1174
+#, c-format
+msgid " -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"
+msgstr " -R, --exclude-relation=MUSTER übereinstimmende Relationen NICHT prüfen\n"
+
+#: pg_amcheck.c:1175
+#, c-format
+msgid " -s, --schema=PATTERN check matching schema(s)\n"
+msgstr " -s, --schema=MUSTER übereinstimmende Schemas prüfen\n"
+
+#: pg_amcheck.c:1176
+#, c-format
+msgid " -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"
+msgstr " -S, --exclude-schema=MUSTER übereinstimmende Schemas NICHT prüfen\n"
+
+#: pg_amcheck.c:1177
+#, c-format
+msgid " -t, --table=PATTERN check matching table(s)\n"
+msgstr " -t, --table=MUSTER übereinstimmende Tabellen prüfen\n"
+
+#: pg_amcheck.c:1178
+#, c-format
+msgid " -T, --exclude-table=PATTERN do NOT check matching table(s)\n"
+msgstr " -T, --exclude-table=MUSTER übereinstimmende Tabellen NICHT prüfen\n"
+
+#: pg_amcheck.c:1179
+#, c-format
+msgid " --no-dependent-indexes do NOT expand list of relations to include indexes\n"
+msgstr " --no-dependent-indexes Liste der Relationen NICHT um Indexe erweitern\n"
+
+#: pg_amcheck.c:1180
+#, c-format
+msgid " --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"
+msgstr " --no-dependent-toast Liste der Relationen NICHT um TOAST-Tabellen erweitern\n"
+
+#: pg_amcheck.c:1181
+#, c-format
+msgid " --no-strict-names do NOT require patterns to match objects\n"
+msgstr " --no-strict-names Muster müssen NICHT mit Objekten übereinstimmen\n"
+
+#: pg_amcheck.c:1182
+#, c-format
+msgid ""
+"\n"
+"Table checking options:\n"
+msgstr ""
+"\n"
+"Optionen für Tabellen:\n"
+
+#: pg_amcheck.c:1183
+#, c-format
+msgid " --exclude-toast-pointers do NOT follow relation TOAST pointers\n"
+msgstr " --exclude-toast-pointers TOAST-Zeigern NICHT folgen\n"
+
+#: pg_amcheck.c:1184
+#, c-format
+msgid " --on-error-stop stop checking at end of first corrupt page\n"
+msgstr " --on-error-stop Prüfung nach der ersten beschädigten Seite beenden\n"
+
+#: pg_amcheck.c:1185
+#, c-format
+msgid " --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"
+msgstr " --skip=OPTION Blöcke mit »all-frozen« oder »all-visible« NICHT prüfen\n"
+
+#: pg_amcheck.c:1186
+#, c-format
+msgid " --startblock=BLOCK begin checking table(s) at the given block number\n"
+msgstr " --startblock=BLOCK Prüfen der Tabelle(n) bei angegebener Blocknummer beginnen\n"
+
+#: pg_amcheck.c:1187
+#, c-format
+msgid " --endblock=BLOCK check table(s) only up to the given block number\n"
+msgstr " --endblock=BLOCK Tabelle(n) nur bis zur angegebenen Blocknummer prüfen\n"
+
+#: pg_amcheck.c:1188
+#, c-format
+msgid ""
+"\n"
+"B-tree index checking options:\n"
+msgstr ""
+"\n"
+"Optionen für B-Tree-Indexe:\n"
+
+#: pg_amcheck.c:1189
+#, c-format
+msgid " --heapallindexed check that all heap tuples are found within indexes\n"
+msgstr " --heapallindexed prüfen, dass alle Heap-Tupel in Indexen zu finden sind\n"
+
+#: pg_amcheck.c:1190
+#, c-format
+msgid " --parent-check check index parent/child relationships\n"
+msgstr " --parent-check Index-Eltern/Kind-Beziehungen prüfen\n"
+
+#: pg_amcheck.c:1191
+#, c-format
+msgid " --rootdescend search from root page to refind tuples\n"
+msgstr " --rootdescend Tupel erneut von der Wurzelseite aus suchen\n"
+
+#: pg_amcheck.c:1192
+#, c-format
+msgid ""
+"\n"
+"Connection options:\n"
+msgstr ""
+"\n"
+"Verbindungsoptionen:\n"
+
+#: pg_amcheck.c:1193
+#, c-format
+msgid " -h, --host=HOSTNAME database server host or socket directory\n"
+msgstr " -h, --host=HOSTNAME Name des Datenbankservers oder Socket-Verzeichnis\n"
+
+#: pg_amcheck.c:1194
+#, c-format
+msgid " -p, --port=PORT database server port\n"
+msgstr " -p, --port=PORT Port des Datenbankservers\n"
+
+#: pg_amcheck.c:1195
+#, c-format
+msgid " -U, --username=USERNAME user name to connect as\n"
+msgstr " -U, --username=NAME Datenbankbenutzername\n"
+
+#: pg_amcheck.c:1196
+#, c-format
+msgid " -w, --no-password never prompt for password\n"
+msgstr " -w, --no-password niemals nach Passwort fragen\n"
+
+#: pg_amcheck.c:1197
+#, c-format
+msgid " -W, --password force password prompt\n"
+msgstr " -W, --password Passwortfrage erzwingen\n"
+
+#: pg_amcheck.c:1198
+#, c-format
+msgid " --maintenance-db=DBNAME alternate maintenance database\n"
+msgstr " --maintenance-db=DBNAME alternative Wartungsdatenbank\n"
+
+#: pg_amcheck.c:1199
+#, c-format
+msgid ""
+"\n"
+"Other options:\n"
+msgstr ""
+"\n"
+"Weitere Optionen:\n"
+
+#: pg_amcheck.c:1200
+#, c-format
+msgid " -e, --echo show the commands being sent to the server\n"
+msgstr ""
+" -e, --echo zeige die Befehle, die an den Server\n"
+" gesendet werden\n"
+
+#: pg_amcheck.c:1201
+#, c-format
+msgid " -j, --jobs=NUM use this many concurrent connections to the server\n"
+msgstr ""
+" -j, --jobs=NUM so viele parallele Verbindungen zum Server\n"
+" verwenden\n"
+
+#: pg_amcheck.c:1202
+#, c-format
+msgid " -P, --progress show progress information\n"
+msgstr " -P, --progress Fortschrittsinformationen zeigen\n"
+
+#: pg_amcheck.c:1203
+#, c-format
+msgid " -v, --verbose write a lot of output\n"
+msgstr " -v, --verbose erzeuge viele Meldungen\n"
+
+#: pg_amcheck.c:1204
+#, c-format
+msgid " -V, --version output version information, then exit\n"
+msgstr " -V, --version Versionsinformationen anzeigen, dann beenden\n"
+
+#: pg_amcheck.c:1205
+#, c-format
+msgid " --install-missing install missing extensions\n"
+msgstr " --install-missing fehlende Erweiterungen installieren\n"
+
+#: pg_amcheck.c:1206
+#, c-format
+msgid " -?, --help show this help, then exit\n"
+msgstr " -?, --help diese Hilfe anzeigen, dann beenden\n"
+
+#: pg_amcheck.c:1208
+#, c-format
+msgid ""
+"\n"
+"Report bugs to <%s>.\n"
+msgstr ""
+"\n"
+"Berichten Sie Fehler an <%s>.\n"
+
+#: pg_amcheck.c:1209
+#, c-format
+msgid "%s home page: <%s>\n"
+msgstr "%s Homepage: <%s>\n"
+
+#: pg_amcheck.c:1267
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"
+msgstr "%*s/%s Relationen (%d%%), %*s/%s Seiten (%d%%) %*s"
+
+#: pg_amcheck.c:1278
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"
+msgstr "%*s/%s Relationen (%d%%), %*s/%s Seiten (%d%%) (%s%-*.*s)"
+
+#: pg_amcheck.c:1293
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%)"
+msgstr "%*s/%s Relationen (%d%%), %*s/%s Seiten (%d%%)"
+
+#: pg_amcheck.c:1352 pg_amcheck.c:1385
+#, c-format
+msgid "improper qualified name (too many dotted names): %s"
+msgstr "falscher qualifizierter Name (zu viele Namensteile): %s"
+
+#: pg_amcheck.c:1430
+#, c-format
+msgid "improper relation name (too many dotted names): %s"
+msgstr "falscher Relationsname (zu viele Namensteile): %s"
+
+#: pg_amcheck.c:1583 pg_amcheck.c:1725
+#, c-format
+msgid "including database \"%s\""
+msgstr "Datenbank »%s« einbezogen"
+
+#: pg_amcheck.c:1705
+#, c-format
+msgid "internal error: received unexpected database pattern_id %d"
+msgstr "interner Fehler: unerwartete pattern_id %d für Datenbank empfangen"
+
+#: pg_amcheck.c:1709
+#, c-format
+msgid "no connectable databases to check matching \"%s\""
+msgstr "keine Datenbanken, mit denen verbunden werden kann und die mit »%s« übereinstimmen"
+
+#: pg_amcheck.c:2168
+#, c-format
+msgid "internal error: received unexpected relation pattern_id %d"
+msgstr "interner Fehler: unerwartete pattern_id %d für Relation empfangen"
diff --git a/src/bin/pg_amcheck/po/el.po b/src/bin/pg_amcheck/po/el.po
new file mode 100644
index 0000000..e9b4fca
--- /dev/null
+++ b/src/bin/pg_amcheck/po/el.po
@@ -0,0 +1,516 @@
+# Greek message translation file for pg_amcheck
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_amcheck (PostgreSQL) package.
+# Georgios Kokolatos <gkokolatos@pm.me>, 2021.
+#
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pg_amcheck (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2021-11-08 10:17+0000\n"
+"PO-Revision-Date: 2021-11-08 11:49+0100\n"
+"Last-Translator: Georgios Kokolatos <gkokolatos@pm.me>\n"
+"Language-Team: \n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.0\n"
+
+#: ../../../src/common/logging.c:259
+#, c-format
+msgid "fatal: "
+msgstr "κρίσιμο: "
+
+#: ../../../src/common/logging.c:266
+#, c-format
+msgid "error: "
+msgstr "σφάλμα: "
+
+#: ../../../src/common/logging.c:273
+#, c-format
+msgid "warning: "
+msgstr "προειδοποίηση: "
+
+#: ../../fe_utils/cancel.c:161 ../../fe_utils/cancel.c:206
+msgid "Cancel request sent\n"
+msgstr "Αίτηση ακύρωσης εστάλη\n"
+
+#: ../../fe_utils/cancel.c:165 ../../fe_utils/cancel.c:210
+msgid "Could not send cancel request: "
+msgstr "Δεν ήταν δυνατή η αποστολή αίτησης ακύρωσης: "
+
+#: ../../fe_utils/connect_utils.c:92
+#, c-format
+msgid "could not connect to database %s: out of memory"
+msgstr "δεν ήταν δυνατή η σύνδεση με τη βάσης δεδομένων %s: έλλειψη μνήμης"
+
+#: ../../fe_utils/connect_utils.c:120
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: ../../fe_utils/query_utils.c:33 ../../fe_utils/query_utils.c:58
+#: pg_amcheck.c:1657 pg_amcheck.c:2105
+#, c-format
+msgid "query failed: %s"
+msgstr "το ερώτημα απέτυχε: %s"
+
+#: ../../fe_utils/query_utils.c:34 ../../fe_utils/query_utils.c:59
+#: pg_amcheck.c:598 pg_amcheck.c:1128 pg_amcheck.c:1658 pg_amcheck.c:2106
+#, c-format
+msgid "query was: %s"
+msgstr "το ερώτημα ήταν: %s"
+
+#: pg_amcheck.c:330
+#, c-format
+msgid "number of parallel jobs must be at least 1"
+msgstr "ο αριθμός παράλληλων εργασιών πρέπει να είναι τουλάχιστον 1"
+
+#: pg_amcheck.c:402
+#, c-format
+msgid "invalid argument for option %s"
+msgstr "μη έγκυρη παράμετρος για την επιλογή %s"
+
+#: pg_amcheck.c:411
+#, c-format
+msgid "invalid start block"
+msgstr "μη έγκυρο μπλοκ εκκίνησης"
+
+#: pg_amcheck.c:416
+#, c-format
+msgid "start block out of bounds"
+msgstr "μπλοκ εκκίνησης εκτός ορίων"
+
+#: pg_amcheck.c:426
+#, c-format
+msgid "invalid end block"
+msgstr "μη έγκυρο μπλοκ τερματισμού"
+
+#: pg_amcheck.c:431
+#, c-format
+msgid "end block out of bounds"
+msgstr "μπλοκ τερματισμού εκτός ορίων"
+
+#: pg_amcheck.c:456 pg_amcheck.c:482
+#, c-format
+msgid "Try \"%s --help\" for more information.\n"
+msgstr "Δοκιμάστε «%s --help» για περισσότερες πληροφορίες.\n"
+
+#: pg_amcheck.c:464
+#, c-format
+msgid "end block precedes start block"
+msgstr "μπλοκ τερματισμού προηγείται του μπλοκ εκκίνησης"
+
+#: pg_amcheck.c:480
+#, c-format
+msgid "too many command-line arguments (first is \"%s\")"
+msgstr "πάρα πολλές παράμετροι εισόδου από την γραμμή εντολών (η πρώτη είναι η «%s»)"
+
+#: pg_amcheck.c:501
+#, c-format
+msgid "cannot specify a database name with --all"
+msgstr "δεν είναι δυνατό να οριστεί ένα όνομα βάσης δεδομένων μαζί με --all"
+
+#: pg_amcheck.c:510
+#, c-format
+msgid "cannot specify both a database name and database patterns"
+msgstr "δεν είναι δυνατός ο καθορισμός τόσο ενός ονόματος βάσης δεδομένων όσο και μοτίβων βάσης δεδομένων"
+
+#: pg_amcheck.c:540
+#, c-format
+msgid "no databases to check"
+msgstr "καθόλου βάσεις δεδομένων για έλεγχο"
+
+#: pg_amcheck.c:596
+#, c-format
+msgid "database \"%s\": %s"
+msgstr "βάση δεδομένων «%s»: %s"
+
+#: pg_amcheck.c:607
+#, c-format
+msgid "skipping database \"%s\": amcheck is not installed"
+msgstr "παρακάμπτει βάση δεδομένων «%s»: το amcheck δεν είναι εγκαταστημένο"
+
+#: pg_amcheck.c:615
+#, c-format
+msgid "in database \"%s\": using amcheck version \"%s\" in schema \"%s\""
+msgstr "στη βάση δεδομένων «%s»: χρησιμοποιώντας την έκδοση «%s» του amcheck στο σχήμα «%s»"
+
+#: pg_amcheck.c:637
+#, c-format
+msgid "no heap tables to check matching \"%s\""
+msgstr "δεν υπάρχουν πίνακες heap για έλεγχο που ταιριάζουν με «%s»"
+
+#: pg_amcheck.c:640
+#, c-format
+msgid "no btree indexes to check matching \"%s\""
+msgstr "δεν υπάρχουν ευρετήρια BTREE για έλεγχο που ταιριάζουν με «%s»"
+
+#: pg_amcheck.c:643
+#, c-format
+msgid "no relations to check in schemas matching \"%s\""
+msgstr "καθόλου σχέσεις για έλεγχο σε σχήματα που ταιριάζουν με «%s»"
+
+#: pg_amcheck.c:646
+#, c-format
+msgid "no relations to check matching \"%s\""
+msgstr "καθόλου σχέσεις για έλεγχο που ταιριάζουν με «%s»"
+
+#: pg_amcheck.c:674
+#, c-format
+msgid "no relations to check"
+msgstr "καθόλου σχέσεις για έλεγχο"
+
+#: pg_amcheck.c:758
+#, c-format
+msgid "checking heap table \"%s.%s.%s\""
+msgstr "ελέγχει τον πίνακα heap «%s.%s.%s»"
+
+#: pg_amcheck.c:774
+#, c-format
+msgid "checking btree index \"%s.%s.%s\""
+msgstr "ελέγχει το ευρετήριο btree «%s.%s.%s»"
+
+#: pg_amcheck.c:921
+#, c-format
+msgid "error sending command to database \"%s\": %s"
+msgstr "εντολή αποστολής σφάλματος στη βάση δεδομένων «%s»: %s"
+
+#: pg_amcheck.c:924
+#, c-format
+msgid "command was: %s"
+msgstr "η εντολή ήταν: %s"
+
+#: pg_amcheck.c:1041
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"
+msgstr "πίνακας heap «%s.%s.%s», μπλοκ %s, μετατόπιση %s, χαρακτηριστικό %s:\n"
+
+#: pg_amcheck.c:1048
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s:\n"
+msgstr "πίνακας heap «%s.%s.%s», μπλοκ %s, μετατόπιση %s:\n"
+
+#: pg_amcheck.c:1054
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s:\n"
+msgstr "πίνακας heap «%s.%s.%s», μπλοκ %s:\n"
+
+#: pg_amcheck.c:1059 pg_amcheck.c:1070
+#, c-format
+msgid "heap table \"%s.%s.%s\":\n"
+msgstr "πίνακας heap «%s.%s.%s»:\n"
+
+#: pg_amcheck.c:1074 pg_amcheck.c:1143
+#, c-format
+msgid "query was: %s\n"
+msgstr "το ερώτημα ήταν: %s\n"
+
+#: pg_amcheck.c:1125
+#, c-format
+msgid "btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d"
+msgstr "ευρετήριο btree «%s.%s.%s»: η συνάρτηση ελέγχου btree επέστρεψε απροσδόκητο αριθμό γραμμών: %d"
+
+#: pg_amcheck.c:1129
+#, c-format
+msgid "Are %s's and amcheck's versions compatible?"
+msgstr "Είναι συμβατές οι εκδόσεις του %s και του amcheck;"
+
+#: pg_amcheck.c:1139
+#, c-format
+msgid "btree index \"%s.%s.%s\":\n"
+msgstr "ευρετήριο btree «%s.%s.%s»:\n"
+
+#: pg_amcheck.c:1164
+#, c-format
+msgid ""
+"%s checks objects in a PostgreSQL database for corruption.\n"
+"\n"
+msgstr ""
+"%s ελέγχει αντικείμενα σε μια βάση δεδομένων PostgreSQL για αλλοίωση.\n"
+"\n"
+
+#: pg_amcheck.c:1165
+#, c-format
+msgid "Usage:\n"
+msgstr "Χρήση:\n"
+
+#: pg_amcheck.c:1166
+#, c-format
+msgid " %s [OPTION]... [DBNAME]\n"
+msgstr " %s [ΕΠΙΛΟΓΗ]... [DBNAME]\n"
+
+#: pg_amcheck.c:1167
+#, c-format
+msgid ""
+"\n"
+"Target options:\n"
+msgstr ""
+"\n"
+"Επιλογές στόχου:\n"
+
+#: pg_amcheck.c:1168
+#, c-format
+msgid " -a, --all check all databases\n"
+msgstr " -a, --all έλεγξε όλες τις βάσεις δεδομένων\n"
+
+#: pg_amcheck.c:1169
+#, c-format
+msgid " -d, --database=PATTERN check matching database(s)\n"
+msgstr " -d, --database=PATTERN έλεγξε ταιριαστή(-ες) με το μοτίβο βάση(-εις) δεδομένων\n"
+
+#: pg_amcheck.c:1170
+#, c-format
+msgid " -D, --exclude-database=PATTERN do NOT check matching database(s)\n"
+msgstr " -D, --exclude-database=PATTERN να ΜΗΝ ελέγξει ταιριαστή(-ες) με το μοτίβο βάση(-εις) δεδομένων\n"
+
+#: pg_amcheck.c:1171
+#, c-format
+msgid " -i, --index=PATTERN check matching index(es)\n"
+msgstr " -i, --index=PATTERN έλεγξε ταιριαστό(-ά) με το μοτίβο ευρετήριο(-ά)\n"
+
+#: pg_amcheck.c:1172
+#, c-format
+msgid " -I, --exclude-index=PATTERN do NOT check matching index(es)\n"
+msgstr " -I, --exclude-index=PATTERN να ΜΗΝ ελέγξει ταιριαστό(-ά) με το μοτίβο ευρετήριο(-ά)\n"
+
+#: pg_amcheck.c:1173
+#, c-format
+msgid " -r, --relation=PATTERN check matching relation(s)\n"
+msgstr " -i, --index=PATTERN έλεγξε ταιριαστή(-ές) με το μοτίβο σχέση(-εις)\n"
+
+#: pg_amcheck.c:1174
+#, c-format
+msgid " -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"
+msgstr " -R, --exclude-relation=PATTERN να ΜΗΝ ελέγξει ταιριαστή(-ές) με το μοτίβο σχέση(-εις)\n"
+
+#: pg_amcheck.c:1175
+#, c-format
+msgid " -s, --schema=PATTERN check matching schema(s)\n"
+msgstr " -s, --schema=PATTERN έλεγξε ταιριαστό(-ά) με το μοτίβο σχήμα(-τα)\n"
+
+#: pg_amcheck.c:1176
+#, c-format
+msgid " -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"
+msgstr " -S, --exclude-schema=PATTERN να ΜΗΝ ελέγξει ταιριαστό(-ά) με το μοτίβο σχήμα(-τα)\n"
+
+#: pg_amcheck.c:1177
+#, c-format
+msgid " -t, --table=PATTERN check matching table(s)\n"
+msgstr " -t, --table=PATTERN έλεγξε ταιριαστό(-ούς) με το μοτίβο πίνακα(-ες)\n"
+
+#: pg_amcheck.c:1178
+#, c-format
+msgid " -T, --exclude-table=PATTERN do NOT check matching table(s)\n"
+msgstr " -T, --exclude-table=PATTERN να ΜΗΝ ελέγξει ταιριαστό(-ούς) με το μοτίβο πίνακα(-ες)\n"
+
+#: pg_amcheck.c:1179
+#, c-format
+msgid " --no-dependent-indexes do NOT expand list of relations to include indexes\n"
+msgstr " --no-dependent-indexes να ΜΗΝ επεκτείνεις τη λίστα σχέσεων ώστε να συμπεριλάβει ευρετήρια\n"
+
+#: pg_amcheck.c:1180
+#, c-format
+msgid " --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"
+msgstr " --no-dependent-toast να ΜΗΝ επεκτείνεις τη λίστα σχέσεων ώστε να συμπεριλάβει πίνακες TOAST\n"
+
+#: pg_amcheck.c:1181
+#, c-format
+msgid " --no-strict-names do NOT require patterns to match objects\n"
+msgstr " --no-strict-names να ΜΗΝ απαιτήσει μοτίβα για την αντιστοίχιση αντικειμένων\n"
+
+#: pg_amcheck.c:1182
+#, c-format
+msgid ""
+"\n"
+"Table checking options:\n"
+msgstr ""
+"\n"
+"Επιλογές ελέγχου πίνακα:\n"
+
+#: pg_amcheck.c:1183
+#, c-format
+msgid " --exclude-toast-pointers do NOT follow relation TOAST pointers\n"
+msgstr " --exclude-toast-pointers να ΜΗΝ ακολουθήσει τους δείκτες σχέσεων TOAST\n"
+
+#: pg_amcheck.c:1184
+#, c-format
+msgid " --on-error-stop stop checking at end of first corrupt page\n"
+msgstr " --on-error-stop διακοπή ελέγχου στο τέλος της πρώτης αλλοιωμένης σελίδας\n"
+
+#: pg_amcheck.c:1185
+#, c-format
+msgid " --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"
+msgstr " --skip=OPTION να ΜΗΝ ελέγξει τα «all-frozen» ή «all-visible» μπλοκ\n"
+
+#: pg_amcheck.c:1186
+#, c-format
+msgid " --startblock=BLOCK begin checking table(s) at the given block number\n"
+msgstr " --startblock=BLOCK εκκίνηση του ελέγχου πίνακα(-ων) από τον δοσμένο αριθμό μπλοκ\n"
+
+#: pg_amcheck.c:1187
+#, c-format
+msgid " --endblock=BLOCK check table(s) only up to the given block number\n"
+msgstr " --endblock=BLOCK τερματισμός του ελέγχου πίνακα(-ων) από τον δοσμένο αριθμό μπλοκ\n"
+
+#: pg_amcheck.c:1188
+#, c-format
+msgid ""
+"\n"
+"B-tree index checking options:\n"
+msgstr ""
+"\n"
+"Επιλογές ελέγχου ευρετηρίου B-tree:\n"
+
+#: pg_amcheck.c:1189
+#, c-format
+msgid " --heapallindexed check that all heap tuples are found within indexes\n"
+msgstr " --heapallindexed έλεγξε ότι όλες οι πλειάδες heap περιλαμβάνονται σε ευρετήρια\n"
+
+#: pg_amcheck.c:1190
+#, c-format
+msgid " --parent-check check index parent/child relationships\n"
+msgstr " --parent-check έλεγξε σχέσεις γονέα/απογόνου ευρετηρίου\n"
+
+#: pg_amcheck.c:1191
+#, c-format
+msgid " --rootdescend search from root page to refind tuples\n"
+msgstr " --rootdescend αναζήτησε από τη ριζική σελίδα για την επανεύρεση πλειάδων\n"
+
+#: pg_amcheck.c:1192
+#, c-format
+msgid ""
+"\n"
+"Connection options:\n"
+msgstr ""
+"\n"
+"Επιλογές σύνδεσης:\n"
+
+#: pg_amcheck.c:1193
+#, c-format
+msgid " -h, --host=HOSTNAME database server host or socket directory\n"
+msgstr " -h, --host=HOSTNAME διακομιστής βάσης δεδομένων ή κατάλογος υποδοχών\n"
+
+#: pg_amcheck.c:1194
+#, c-format
+msgid " -p, --port=PORT database server port\n"
+msgstr " -p, --port=PORT θύρα διακομιστή βάσης δεδομένων\n"
+
+#: pg_amcheck.c:1195
+#, c-format
+msgid " -U, --username=USERNAME user name to connect as\n"
+msgstr " -U, --username=USERNAME όνομα χρήστη με το οποίο να συνδεθεί\n"
+
+#: pg_amcheck.c:1196
+#, c-format
+msgid " -w, --no-password never prompt for password\n"
+msgstr " -w, --no-password να μην ζητείται ποτέ κωδικός πρόσβασης\n"
+
+#: pg_amcheck.c:1197
+#, c-format
+msgid " -W, --password force password prompt\n"
+msgstr " -W, --password αναγκαστική προτροπή κωδικού πρόσβασης\n"
+
+#: pg_amcheck.c:1198
+#, c-format
+msgid " --maintenance-db=DBNAME alternate maintenance database\n"
+msgstr " --maintenance-db=DBNAME εναλλακτική βάση δεδομένων συντήρησης\n"
+
+#: pg_amcheck.c:1199
+#, c-format
+msgid ""
+"\n"
+"Other options:\n"
+msgstr ""
+"\n"
+"Άλλες επιλογές:\n"
+
+#: pg_amcheck.c:1200
+#, c-format
+msgid " -e, --echo show the commands being sent to the server\n"
+msgstr " -e, --echo εμφάνισε τις εντολές που αποστέλλονται στο διακομιστή\n"
+
+#: pg_amcheck.c:1201
+#, c-format
+msgid " -j, --jobs=NUM use this many concurrent connections to the server\n"
+msgstr " -j, --jobs=NUM χρησιμοποιήσε τόσες πολλές ταυτόχρονες συνδέσεις με το διακομιστή\n"
+
+#: pg_amcheck.c:1202
+#, c-format
+msgid " -P, --progress show progress information\n"
+msgstr " -P, --progress εμφάνισε πληροφορίες προόδου\n"
+
+#: pg_amcheck.c:1203
+#, c-format
+msgid " -v, --verbose write a lot of output\n"
+msgstr " -v, --verbose γράψε πολλά μηνύματα εξόδου\n"
+
+#: pg_amcheck.c:1204
+#, c-format
+msgid " -V, --version output version information, then exit\n"
+msgstr " -V, --version εμφάνισε πληροφορίες έκδοσης, στη συνέχεια έξοδος\n"
+
+#: pg_amcheck.c:1205
+#, c-format
+msgid " --install-missing install missing extensions\n"
+msgstr " --install-missing εγκατάστησε επεκτάσεις που λείπουν\n"
+
+#: pg_amcheck.c:1206
+#, c-format
+msgid " -?, --help show this help, then exit\n"
+msgstr " -?, --help εμφάνισε αυτό το μήνυμα βοήθειας, στη συνέχεια έξοδος\n"
+
+#: pg_amcheck.c:1208
+#, c-format
+msgid ""
+"\n"
+"Report bugs to <%s>.\n"
+msgstr ""
+"\n"
+"Υποβάλετε αναφορές σφάλματων σε <%s>.\n"
+
+#: pg_amcheck.c:1209
+#, c-format
+msgid "%s home page: <%s>\n"
+msgstr "%s αρχική σελίδα: <%s>\n"
+
+#: pg_amcheck.c:1267
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"
+msgstr "%*s/%s σχέσεις (%d%%), %*s/%s σελίδες (%d%%) %*s"
+
+#: pg_amcheck.c:1278
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"
+msgstr "%*s/%s σχέσεις (%d%%), %*s/%s σελίδες (%d%%) (%s%-*.*s)"
+
+#: pg_amcheck.c:1293
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%)"
+msgstr "%*s/%s σχέσεις (%d%%), %*s/%s σελίδες (%d%%)"
+
+#: pg_amcheck.c:1562 pg_amcheck.c:1704
+#, c-format
+msgid "including database \"%s\""
+msgstr "συμπεριλαμβανομένης της βάσης δεδομένων «%s»"
+
+#: pg_amcheck.c:1684
+#, c-format
+msgid "internal error: received unexpected database pattern_id %d"
+msgstr "εσωτερικό σφάλμα: ελήφθη μη αναμενόμενο pattern_id %d"
+
+#: pg_amcheck.c:1688
+#, c-format
+msgid "no connectable databases to check matching \"%s\""
+msgstr "δεν υπάρχουν βάσεις δεδομένων με δυνατότητα σύνδεσης για έλεγχο που να ταιριάζουν με «%s»"
+
+#: pg_amcheck.c:2147
+#, c-format
+msgid "internal error: received unexpected relation pattern_id %d"
+msgstr "εσωτερικό σφάλμα: ελήφθη μη αναμενόμενο pattern_id σχέσης %d"
+
+#~ msgid " -q, --quiet don't write any messages\n"
+#~ msgstr " -q, --quiet να μην γράψεις κανένα μήνυμα\n"
diff --git a/src/bin/pg_amcheck/po/es.po b/src/bin/pg_amcheck/po/es.po
new file mode 100644
index 0000000..b5df139
--- /dev/null
+++ b/src/bin/pg_amcheck/po/es.po
@@ -0,0 +1,523 @@
+# Spanish translation file for pg_amcheck
+#
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_amcheck (PostgreSQL) package.
+#
+# Carlos Chapi <carloswaldo@babelruins.org>, 2021.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pg_amcheck (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-08-07 20:35+0000\n"
+"PO-Revision-Date: 2021-10-13 23:57-0500\n"
+"Last-Translator: Carlos Chapi <carloswaldo@babelruins.org>\n"
+"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: BlackCAT 1.1\n"
+
+#: ../../../src/common/logging.c:259
+#, c-format
+msgid "fatal: "
+msgstr "fatal: "
+
+#: ../../../src/common/logging.c:266
+#, c-format
+msgid "error: "
+msgstr "error: "
+
+#: ../../../src/common/logging.c:273
+#, c-format
+msgid "warning: "
+msgstr "precaución: "
+
+#: ../../fe_utils/cancel.c:189 ../../fe_utils/cancel.c:238
+msgid "Cancel request sent\n"
+msgstr "Petición de cancelación enviada\n"
+
+#: ../../fe_utils/cancel.c:190 ../../fe_utils/cancel.c:239
+msgid "Could not send cancel request: "
+msgstr "No se pudo enviar la petición de cancelación: "
+
+#: ../../fe_utils/connect_utils.c:92
+#, c-format
+msgid "could not connect to database %s: out of memory"
+msgstr "no se pudo conectar a la base de datos %s: memoria agotada"
+
+#: ../../fe_utils/connect_utils.c:120
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: ../../fe_utils/query_utils.c:33 ../../fe_utils/query_utils.c:58
+#: pg_amcheck.c:1680 pg_amcheck.c:2128
+#, c-format
+msgid "query failed: %s"
+msgstr "la consulta falló: %s"
+
+#: ../../fe_utils/query_utils.c:34 ../../fe_utils/query_utils.c:59
+#: pg_amcheck.c:598 pg_amcheck.c:1130 pg_amcheck.c:1681 pg_amcheck.c:2129
+#, c-format
+msgid "query was: %s"
+msgstr "la consulta era: %s"
+
+#: pg_amcheck.c:330
+#, c-format
+msgid "number of parallel jobs must be at least 1"
+msgstr "número de trabajos en paralelo debe ser al menos 1"
+
+#: pg_amcheck.c:402
+#, c-format
+msgid "invalid argument for option %s"
+msgstr "argumento no válido para la opción %s"
+
+#: pg_amcheck.c:411
+#, c-format
+msgid "invalid start block"
+msgstr "bloque de inicio no válido"
+
+#: pg_amcheck.c:416
+#, c-format
+msgid "start block out of bounds"
+msgstr "bloque de inicio fuera de rango"
+
+#: pg_amcheck.c:426
+#, c-format
+msgid "invalid end block"
+msgstr "bloque final no válido"
+
+#: pg_amcheck.c:431
+#, c-format
+msgid "end block out of bounds"
+msgstr "bloque final fuera de rango"
+
+#: pg_amcheck.c:456 pg_amcheck.c:482
+#, c-format
+msgid "Try \"%s --help\" for more information.\n"
+msgstr "Pruebe «%s --help» para mayor información.\n"
+
+#: pg_amcheck.c:464
+#, c-format
+msgid "end block precedes start block"
+msgstr "bloque final precede al bloque de inicio"
+
+#: pg_amcheck.c:480
+#, c-format
+msgid "too many command-line arguments (first is \"%s\")"
+msgstr "demasiados argumentos en la línea de órdenes (el primero es «%s»)"
+
+#: pg_amcheck.c:501
+#, c-format
+msgid "cannot specify a database name with --all"
+msgstr "no se puede especificar un nombre de base de datos al usar --all"
+
+#: pg_amcheck.c:510
+#, c-format
+msgid "cannot specify both a database name and database patterns"
+msgstr "no se puede especificar al mismo tiempo un nombre de base de datos junto con patrones de bases de datos"
+
+#: pg_amcheck.c:540
+#, c-format
+msgid "no databases to check"
+msgstr "no hay bases de datos para revisar"
+
+#: pg_amcheck.c:596
+#, c-format
+msgid "database \"%s\": %s"
+msgstr "base de datos «%s»: %s"
+
+#: pg_amcheck.c:607
+#, c-format
+msgid "skipping database \"%s\": amcheck is not installed"
+msgstr "omitiendo la base de datos «%s»: amcheck no está instalado"
+
+#: pg_amcheck.c:615
+#, c-format
+msgid "in database \"%s\": using amcheck version \"%s\" in schema \"%s\""
+msgstr "en base de datos «%s»: usando amcheck versión «%s» en esquema «%s»"
+
+#: pg_amcheck.c:637
+#, c-format
+msgid "no heap tables to check matching \"%s\""
+msgstr "no hay tablas heap para revisar que coincidan con «%s»"
+
+#: pg_amcheck.c:640
+#, c-format
+msgid "no btree indexes to check matching \"%s\""
+msgstr "no hay índices btree para revisar que coincidan con «%s»"
+
+#: pg_amcheck.c:643
+#, c-format
+msgid "no relations to check in schemas matching \"%s\""
+msgstr "no hay relaciones para revisar en esquemas que coincidan con «%s»"
+
+#: pg_amcheck.c:646
+#, c-format
+msgid "no relations to check matching \"%s\""
+msgstr "no hay relaciones para revisar que coincidan con «%s»"
+
+#: pg_amcheck.c:674
+#, c-format
+msgid "no relations to check"
+msgstr "no hay relaciones para revisar"
+
+#: pg_amcheck.c:758
+#, c-format
+msgid "checking heap table \"%s.%s.%s\""
+msgstr "revisando tabla heap «%s.%s.%s»"
+
+#: pg_amcheck.c:774
+#, c-format
+msgid "checking btree index \"%s.%s.%s\""
+msgstr "revisando índice btree «%s.%s.%s»"
+
+#: pg_amcheck.c:921
+#, c-format
+msgid "error sending command to database \"%s\": %s"
+msgstr "error al enviar orden a la base de datos «%s»: %s"
+
+#: pg_amcheck.c:924
+#, c-format
+msgid "command was: %s"
+msgstr "la orden era: %s"
+
+#: pg_amcheck.c:1043
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"
+msgstr "tabla heap «%s.%s.%s», bloque %s, posición %s, atributo %s:\n"
+
+#: pg_amcheck.c:1050
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s:\n"
+msgstr "tabla heap «%s.%s.%s», bloque %s, posición %s:\n"
+
+#: pg_amcheck.c:1056
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s:\n"
+msgstr "tabla heap «%s.%s.%s», bloque %s:\n"
+
+#: pg_amcheck.c:1061 pg_amcheck.c:1072
+#, c-format
+msgid "heap table \"%s.%s.%s\":\n"
+msgstr "tabla heap «%s.%s.%s»:\n"
+
+#: pg_amcheck.c:1076 pg_amcheck.c:1145
+#, c-format
+msgid "query was: %s\n"
+msgstr "la consulta era: %s\n"
+
+#: pg_amcheck.c:1127
+#, c-format
+msgid "btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d"
+msgstr "índice btree «%s.%s.%s»: la función de comprobación de btree devolvió un número inesperado de registros: %d"
+
+#: pg_amcheck.c:1131
+#, c-format
+msgid "Are %s's and amcheck's versions compatible?"
+msgstr "¿Son compatibles la versión de %s con la de amcheck?"
+
+#: pg_amcheck.c:1141
+#, c-format
+msgid "btree index \"%s.%s.%s\":\n"
+msgstr "índice btree «%s.%s.%s»:\n"
+
+#: pg_amcheck.c:1166
+#, c-format
+msgid ""
+"%s checks objects in a PostgreSQL database for corruption.\n"
+"\n"
+msgstr ""
+"%s busca corrupción en objetos de una base de datos PostgreSQL.\n"
+"\n"
+
+#: pg_amcheck.c:1167
+#, c-format
+msgid "Usage:\n"
+msgstr "Empleo:\n"
+
+#: pg_amcheck.c:1168
+#, c-format
+msgid " %s [OPTION]... [DBNAME]\n"
+msgstr " %s [OPCIÓN]... [BASE-DE-DATOS]\n"
+
+#: pg_amcheck.c:1169
+#, c-format
+msgid ""
+"\n"
+"Target options:\n"
+msgstr ""
+"\n"
+"Opciones de objetivo:\n"
+
+#: pg_amcheck.c:1170
+#, c-format
+msgid " -a, --all check all databases\n"
+msgstr " -a, --all revisar todas las bases de datos\n"
+
+#: pg_amcheck.c:1171
+#, c-format
+msgid " -d, --database=PATTERN check matching database(s)\n"
+msgstr " -d, --database=PATRÓN revisar la(s) base(s) de datos que coincida(n)\n"
+
+#: pg_amcheck.c:1172
+#, c-format
+msgid " -D, --exclude-database=PATTERN do NOT check matching database(s)\n"
+msgstr " -D, --exclude-database=PATRÓN NO revisar la(s) base(s) de datos que coincida(n)\n"
+
+#: pg_amcheck.c:1173
+#, c-format
+msgid " -i, --index=PATTERN check matching index(es)\n"
+msgstr " -i, --index=PATRÓN revisar el(los) índice(s) que coincida(n)\n"
+
+#: pg_amcheck.c:1174
+#, c-format
+msgid " -I, --exclude-index=PATTERN do NOT check matching index(es)\n"
+msgstr " -I, --exclude-index=PATRÓN NO revisar el(los) índice(s) que coincida(n)\n"
+
+#: pg_amcheck.c:1175
+#, c-format
+msgid " -r, --relation=PATTERN check matching relation(s)\n"
+msgstr " -r, --relation=PATRÓN revisar la(s) relación(es) que coincida(n)\n"
+
+#: pg_amcheck.c:1176
+#, c-format
+msgid " -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"
+msgstr " -R, --exclude-relation=PATRÓN NO revisar la(s) relación(es) que coincida(n)\n"
+
+#: pg_amcheck.c:1177
+#, c-format
+msgid " -s, --schema=PATTERN check matching schema(s)\n"
+msgstr " -s, --schema=PATRÓN revisar el(los) esquema(s) que coincida(n)\n"
+
+#: pg_amcheck.c:1178
+#, c-format
+msgid " -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"
+msgstr " -S, --exclude-schema=PATRÓN NO revisar el(los) esquema(s) que coincida(n)\n"
+
+#: pg_amcheck.c:1179
+#, c-format
+msgid " -t, --table=PATTERN check matching table(s)\n"
+msgstr " -t, --table=PATRÓN revisar la(s) tabla(s) que coincida(n)\n"
+
+#: pg_amcheck.c:1180
+#, c-format
+msgid " -T, --exclude-table=PATTERN do NOT check matching table(s)\n"
+msgstr " -T, --exclude-table=PATRÓN NO revisar la(s) tabla(s) que coincida(n)\n"
+
+#: pg_amcheck.c:1181
+#, c-format
+msgid " --no-dependent-indexes do NOT expand list of relations to include indexes\n"
+msgstr " --no-dependent-indexes NO expandir la lista de relaciones para incluir índices\n"
+
+#: pg_amcheck.c:1182
+#, c-format
+msgid " --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"
+msgstr " --no-dependent-toast NO expandir lista de relaciones para incluir tablas TOAST\n"
+
+#: pg_amcheck.c:1183
+#, c-format
+msgid " --no-strict-names do NOT require patterns to match objects\n"
+msgstr " --no-strict-names NO requerir que los patrones coincidan con los objetos\n"
+
+#: pg_amcheck.c:1184
+#, c-format
+msgid ""
+"\n"
+"Table checking options:\n"
+msgstr ""
+"\n"
+"Opciones para revisión de tabla:\n"
+
+#: pg_amcheck.c:1185
+#, c-format
+msgid " --exclude-toast-pointers do NOT follow relation TOAST pointers\n"
+msgstr " --exclude-toast-pointers NO seguir punteros TOAST de la relación\n"
+
+#: pg_amcheck.c:1186
+#, c-format
+msgid " --on-error-stop stop checking at end of first corrupt page\n"
+msgstr " --on-error-stop detener la revisión al final de la primera página corrupta\n"
+
+#: pg_amcheck.c:1187
+#, c-format
+msgid " --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"
+msgstr " --skip=OPTION NO revisar bloques «all-frozen» u «all-visible»\n"
+
+#: pg_amcheck.c:1188
+#, c-format
+msgid " --startblock=BLOCK begin checking table(s) at the given block number\n"
+msgstr " --startblock=BLOQUE empezar la revisión de la(s) tabla(s) en el número de bloque especificado\n"
+
+#: pg_amcheck.c:1189
+#, c-format
+msgid " --endblock=BLOCK check table(s) only up to the given block number\n"
+msgstr " --endblock=BLOQUE solo revisar la(s) tabla(s) hasta el número de bloque especificado\n"
+
+#: pg_amcheck.c:1190
+#, c-format
+msgid ""
+"\n"
+"B-tree index checking options:\n"
+msgstr ""
+"\n"
+"Opciones para revisión de índices B-tree:\n"
+
+#: pg_amcheck.c:1191
+#, c-format
+msgid " --heapallindexed check that all heap tuples are found within indexes\n"
+msgstr " --heapallindexed revisar que todas las tuplas heap se encuentren en los índices\n"
+
+#: pg_amcheck.c:1192
+#, c-format
+msgid " --parent-check check index parent/child relationships\n"
+msgstr " --parent-check revisar relaciones padre/hijo de índice\n"
+
+#: pg_amcheck.c:1193
+#, c-format
+msgid " --rootdescend search from root page to refind tuples\n"
+msgstr " --rootdescend buscar desde la página raíz para volver a encontrar tuplas\n"
+
+#: pg_amcheck.c:1194
+#, c-format
+msgid ""
+"\n"
+"Connection options:\n"
+msgstr ""
+"\n"
+"Opciones de conexión:\n"
+
+#: pg_amcheck.c:1195
+#, c-format
+msgid " -h, --host=HOSTNAME database server host or socket directory\n"
+msgstr " -h, --host=ANFITRIÓN nombre del servidor o directorio del socket\n"
+
+#: pg_amcheck.c:1196
+#, c-format
+msgid " -p, --port=PORT database server port\n"
+msgstr " -p, --port=PUERTO puerto del servidor de base de datos\n"
+
+#: pg_amcheck.c:1197
+#, c-format
+msgid " -U, --username=USERNAME user name to connect as\n"
+msgstr " -U, --username=USUARIO nombre de usuario para la conexión\n"
+
+#: pg_amcheck.c:1198
+#, c-format
+msgid " -w, --no-password never prompt for password\n"
+msgstr " -w, --no-password nunca pedir contraseña\n"
+
+#: pg_amcheck.c:1199
+#, c-format
+msgid " -W, --password force password prompt\n"
+msgstr " -W, --password forzar la petición de contraseña\n"
+
+#: pg_amcheck.c:1200
+#, c-format
+msgid " --maintenance-db=DBNAME alternate maintenance database\n"
+msgstr " --maintenance-db=BASE base de datos de mantención alternativa\n"
+
+#: pg_amcheck.c:1201
+#, c-format
+msgid ""
+"\n"
+"Other options:\n"
+msgstr ""
+"\n"
+"Otras opciones:\n"
+
+#: pg_amcheck.c:1202
+#, c-format
+msgid " -e, --echo show the commands being sent to the server\n"
+msgstr " -e, --echo mostrar las órdenes enviadas al servidor\n"
+
+#: pg_amcheck.c:1203
+#, c-format
+msgid " -j, --jobs=NUM use this many concurrent connections to the server\n"
+msgstr " -j, --jobs=NUM usar esta cantidad de conexiones concurrentes hacia el servidor\n"
+
+#: pg_amcheck.c:1204
+#, c-format
+msgid " -P, --progress show progress information\n"
+msgstr " -P, --progress mostrar información de progreso\n"
+
+#: pg_amcheck.c:1205
+#, c-format
+msgid " -v, --verbose write a lot of output\n"
+msgstr " -v, --verbose desplegar varios mensajes informativos\n"
+
+#: pg_amcheck.c:1206
+#, c-format
+msgid " -V, --version output version information, then exit\n"
+msgstr " -V, --version mostrar información de versión y salir\n"
+
+#: pg_amcheck.c:1207
+#, c-format
+msgid " --install-missing install missing extensions\n"
+msgstr " --install-missing instalar extensiones faltantes\n"
+
+#: pg_amcheck.c:1208
+#, c-format
+msgid " -?, --help show this help, then exit\n"
+msgstr " -?, --help mostrar esta ayuda y salir\n"
+
+#: pg_amcheck.c:1210
+#, c-format
+msgid ""
+"\n"
+"Report bugs to <%s>.\n"
+msgstr ""
+"\n"
+"Reporte errores a <%s>.\n"
+
+#: pg_amcheck.c:1211
+#, c-format
+msgid "%s home page: <%s>\n"
+msgstr "Sitio web de %s: <%s>\n"
+
+#: pg_amcheck.c:1269
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"
+msgstr "%*s/%s relaciones (%d%%), %*s/%s páginas (%d%%) %*s"
+
+#: pg_amcheck.c:1280
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"
+msgstr "%*s/%s relaciones (%d%%), %*s/%s páginas (%d%%), (%s%-*.*s)"
+
+#: pg_amcheck.c:1295
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%)"
+msgstr "%*s/%s relaciones (%d%%), %*s/%s páginas (%d%%)"
+
+#: pg_amcheck.c:1354 pg_amcheck.c:1387
+#, c-format
+msgid "improper qualified name (too many dotted names): %s"
+msgstr "el nombre no es válido (demasiados puntos): %s"
+
+#: pg_amcheck.c:1432
+#, c-format
+msgid "improper relation name (too many dotted names): %s"
+msgstr "el nombre de relación no es válido (demasiados puntos): %s"
+
+#: pg_amcheck.c:1585 pg_amcheck.c:1727
+#, c-format
+msgid "including database \"%s\""
+msgstr "incluyendo base de datos «%s»"
+
+#: pg_amcheck.c:1707
+#, c-format
+msgid "internal error: received unexpected database pattern_id %d"
+msgstr "error interno: se recibió pattern_id de base de datos inesperado (%d)"
+
+#: pg_amcheck.c:1711
+#, c-format
+msgid "no connectable databases to check matching \"%s\""
+msgstr "no hay bases de datos a las que se pueda conectar que coincidan con «%s»"
+
+#: pg_amcheck.c:2170
+#, c-format
+msgid "internal error: received unexpected relation pattern_id %d"
+msgstr "error interno: se recibió pattern_id de relación inesperado (%d)"
diff --git a/src/bin/pg_amcheck/po/fr.po b/src/bin/pg_amcheck/po/fr.po
new file mode 100644
index 0000000..04731bd
--- /dev/null
+++ b/src/bin/pg_amcheck/po/fr.po
@@ -0,0 +1,575 @@
+# LANGUAGE message translation file for pg_amcheck
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_amcheck (PostgreSQL) package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pg_amcheck (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-05-03 03:04+0000\n"
+"PO-Revision-Date: 2021-08-31 17:05+0200\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.0\n"
+
+#: ../../../src/common/logging.c:259
+#, c-format
+msgid "fatal: "
+msgstr "fatal : "
+
+#: ../../../src/common/logging.c:266
+#, c-format
+msgid "error: "
+msgstr "erreur : "
+
+#: ../../../src/common/logging.c:273
+#, c-format
+msgid "warning: "
+msgstr "attention : "
+
+#: ../../fe_utils/cancel.c:189 ../../fe_utils/cancel.c:238
+msgid "Cancel request sent\n"
+msgstr "Requête d'annulation envoyée\n"
+
+#: ../../fe_utils/cancel.c:190 ../../fe_utils/cancel.c:239
+msgid "Could not send cancel request: "
+msgstr "N'a pas pu envoyer la requête d'annulation : "
+
+#: ../../fe_utils/connect_utils.c:92
+#, c-format
+msgid "could not connect to database %s: out of memory"
+msgstr "n'a pas pu se connecter à la base de données %s : plus de mémoire"
+
+#: ../../fe_utils/connect_utils.c:120
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: ../../fe_utils/query_utils.c:33 ../../fe_utils/query_utils.c:58
+#: pg_amcheck.c:1678 pg_amcheck.c:2126
+#, c-format
+msgid "query failed: %s"
+msgstr "échec de la requête : %s"
+
+#: ../../fe_utils/query_utils.c:34 ../../fe_utils/query_utils.c:59
+#: pg_amcheck.c:598 pg_amcheck.c:1128 pg_amcheck.c:1679 pg_amcheck.c:2127
+#, c-format
+msgid "query was: %s"
+msgstr "la requête était : %s"
+
+#: pg_amcheck.c:330
+#, c-format
+msgid "number of parallel jobs must be at least 1"
+msgstr "le nombre maximum de jobs en parallèle doit être au moins de 1"
+
+#: pg_amcheck.c:402
+#, c-format
+msgid "invalid argument for option %s"
+msgstr "argument invalide pour l'option %s"
+
+#: pg_amcheck.c:411
+#, c-format
+msgid "invalid start block"
+msgstr "bloc de début invalide"
+
+#: pg_amcheck.c:416
+#, c-format
+msgid "start block out of bounds"
+msgstr "bloc de début hors des limites"
+
+#: pg_amcheck.c:426
+#, c-format
+msgid "invalid end block"
+msgstr "bloc de fin invalide"
+
+#: pg_amcheck.c:431
+#, c-format
+msgid "end block out of bounds"
+msgstr "bloc de fin hors des limites"
+
+#: pg_amcheck.c:456 pg_amcheck.c:482
+#, c-format
+msgid "Try \"%s --help\" for more information.\n"
+msgstr "Essayez « %s --help » pour plus d'informations.\n"
+
+#: pg_amcheck.c:464
+#, c-format
+msgid "end block precedes start block"
+msgstr "le bloc de fin précède le bloc de début"
+
+#: pg_amcheck.c:480
+#, c-format
+msgid "too many command-line arguments (first is \"%s\")"
+msgstr "trop d'arguments en ligne de commande (le premier étant « %s »)"
+
+#: pg_amcheck.c:501
+#, c-format
+msgid "cannot specify a database name with --all"
+msgstr "ne peut pas spécifier un nom de base de données avec --all"
+
+#: pg_amcheck.c:510
+#, c-format
+msgid "cannot specify both a database name and database patterns"
+msgstr "ne peut pas spécifier à la fois le nom d'une base de données et des motifs de noms de base"
+
+#: pg_amcheck.c:540
+#, c-format
+msgid "no databases to check"
+msgstr "aucune base de données à vérifier"
+
+#: pg_amcheck.c:596
+#, c-format
+msgid "database \"%s\": %s"
+msgstr "base de données « %s » : %s"
+
+#: pg_amcheck.c:607
+#, c-format
+msgid "skipping database \"%s\": amcheck is not installed"
+msgstr "ignore la base « %s » : amcheck n'est pas installé"
+
+#: pg_amcheck.c:615
+#, c-format
+msgid "in database \"%s\": using amcheck version \"%s\" in schema \"%s\""
+msgstr "dans la base de données « %s » : utilisation de la version « %s » d'amcheck dans le schéma « %s »"
+
+#: pg_amcheck.c:637
+#, c-format
+msgid "no heap tables to check matching \"%s\""
+msgstr "aucune table heap à vérifier correspondant à « %s »"
+
+#: pg_amcheck.c:640
+#, c-format
+msgid "no btree indexes to check matching \"%s\""
+msgstr "aucun index btree à vérifier correspondant à « %s »"
+
+#: pg_amcheck.c:643
+#, c-format
+msgid "no relations to check in schemas matching \"%s\""
+msgstr "aucune relation à vérifier dans les schémas correspondant à « %s »"
+
+#: pg_amcheck.c:646
+#, c-format
+msgid "no relations to check matching \"%s\""
+msgstr "aucune relation à vérifier correspondant à « %s »"
+
+#: pg_amcheck.c:674
+#, c-format
+msgid "no relations to check"
+msgstr "aucune relation à vérifier"
+
+#: pg_amcheck.c:758
+#, c-format
+msgid "checking heap table \"%s.%s.%s\""
+msgstr "vérification de la table heap « %s %s.%s »"
+
+#: pg_amcheck.c:774
+#, c-format
+msgid "checking btree index \"%s.%s.%s\""
+msgstr "vérification de l'index btree « %s %s.%s »"
+
+#: pg_amcheck.c:921
+#, c-format
+msgid "error sending command to database \"%s\": %s"
+msgstr "erreur de l'envoi d'une commande à la base de données « %s » : %s"
+
+#: pg_amcheck.c:924
+#, c-format
+msgid "command was: %s"
+msgstr "la commande était : %s"
+
+#: pg_amcheck.c:1041
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"
+msgstr "table heap « %s.%s.%s », bloc %s, décalage %s, attribut %s :\n"
+
+#: pg_amcheck.c:1048
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s:\n"
+msgstr "table heap « %s.%s.%s », bloc %s, décalage %s :\n"
+
+#: pg_amcheck.c:1054
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s:\n"
+msgstr "table heap « %s %s.%s », bloc %s :\n"
+
+#: pg_amcheck.c:1059 pg_amcheck.c:1070
+#, c-format
+msgid "heap table \"%s.%s.%s\":\n"
+msgstr "table heap « %s %s.%s » :\n"
+
+#: pg_amcheck.c:1074 pg_amcheck.c:1143
+#, c-format
+msgid "query was: %s\n"
+msgstr "la requête était : %s\n"
+
+#: pg_amcheck.c:1125
+#, c-format
+msgid "btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d"
+msgstr "index btree « %s.%s.%s » : la fonction de vérification des index btree a renvoyé un nombre de lignes inattendu : %d"
+
+#: pg_amcheck.c:1129
+#, c-format
+msgid "Are %s's and amcheck's versions compatible?"
+msgstr "est-ce que les versions de %s et d'amcheck sont compatibles ?"
+
+#: pg_amcheck.c:1139
+#, c-format
+msgid "btree index \"%s.%s.%s\":\n"
+msgstr "vérification de l'index btree« %s %s.%s » :\n"
+
+#: pg_amcheck.c:1164
+#, c-format
+msgid ""
+"%s checks objects in a PostgreSQL database for corruption.\n"
+"\n"
+msgstr ""
+"%s utilise le module amcheck pour vérifier si les objets d' une base\n"
+"PostgreSQL sont corrompus.\n"
+"\n"
+
+#: pg_amcheck.c:1165
+#, c-format
+msgid "Usage:\n"
+msgstr "Usage :\n"
+
+#: pg_amcheck.c:1166
+#, c-format
+msgid " %s [OPTION]... [DBNAME]\n"
+msgstr " %s [OPTION]... [BASE]\n"
+
+#: pg_amcheck.c:1167
+#, c-format
+msgid ""
+"\n"
+"Target options:\n"
+msgstr ""
+"\n"
+"Options de la cible :\n"
+
+#: pg_amcheck.c:1168
+#, c-format
+msgid " -a, --all check all databases\n"
+msgstr " -a, --all vérifie toutes les bases\n"
+
+#: pg_amcheck.c:1169
+#, c-format
+msgid " -d, --database=PATTERN check matching database(s)\n"
+msgstr " -d, --database=MOTIF vérifie les bases correspondantes\n"
+
+#: pg_amcheck.c:1170
+#, c-format
+msgid " -D, --exclude-database=PATTERN do NOT check matching database(s)\n"
+msgstr " -D, --exclude-database=MOTIF ne vérifie PAS les bases correspondantes\n"
+
+#: pg_amcheck.c:1171
+#, c-format
+msgid " -i, --index=PATTERN check matching index(es)\n"
+msgstr " -i, --index=MOTIF vérifie les index correspondants\n"
+
+#: pg_amcheck.c:1172
+#, c-format
+msgid " -I, --exclude-index=PATTERN do NOT check matching index(es)\n"
+msgstr " -I, --exclude-index=MOTIF ne vérifie PAS les index correspondants\n"
+
+#: pg_amcheck.c:1173
+#, c-format
+msgid " -r, --relation=PATTERN check matching relation(s)\n"
+msgstr " -r, --relation=MOTIF vérifie les relations correspondantes\n"
+
+#: pg_amcheck.c:1174
+#, c-format
+msgid " -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"
+msgstr " -R, --exclude-relation=MOTIF ne vérifie PAS les relations correspondantes\n"
+
+#: pg_amcheck.c:1175
+#, c-format
+msgid " -s, --schema=PATTERN check matching schema(s)\n"
+msgstr " -s, --schema=MOTIF vérifie les schémas correspondants\n"
+
+#: pg_amcheck.c:1176
+#, c-format
+msgid " -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"
+msgstr " -S, --exclude-schema=MOTIF ne vérifie PAS les schémas correspondants\n"
+
+#: pg_amcheck.c:1177
+#, c-format
+msgid " -t, --table=PATTERN check matching table(s)\n"
+msgstr " -t, --table=MOTIF vérifie les tables correspondantes\n"
+
+#: pg_amcheck.c:1178
+#, c-format
+msgid " -T, --exclude-table=PATTERN do NOT check matching table(s)\n"
+msgstr " -T, --exclude-table=MOTIF ne vérifie PAS les tables correspondantes\n"
+
+#: pg_amcheck.c:1179
+#, c-format
+msgid " --no-dependent-indexes do NOT expand list of relations to include indexes\n"
+msgstr ""
+" --no-dependent-indexes n'étend PAS la liste des relations pour inclure\n"
+" les index\n"
+
+#: pg_amcheck.c:1180
+#, c-format
+msgid " --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"
+msgstr ""
+" --no-dependent-toast n'étend PAS la liste des relations pour inclure\n"
+" les TOAST\n"
+
+#: pg_amcheck.c:1181
+#, c-format
+msgid " --no-strict-names do NOT require patterns to match objects\n"
+msgstr ""
+" --no-strict-names ne requiert PAS que les motifs correspondent à\n"
+" des objets\n"
+
+#: pg_amcheck.c:1182
+#, c-format
+msgid ""
+"\n"
+"Table checking options:\n"
+msgstr ""
+"\n"
+"Options de vérification des tables :\n"
+
+#: pg_amcheck.c:1183
+#, c-format
+msgid " --exclude-toast-pointers do NOT follow relation TOAST pointers\n"
+msgstr " --exclude-toast-pointers ne suit PAS les pointeurs de TOAST\n"
+
+#: pg_amcheck.c:1184
+#, c-format
+msgid " --on-error-stop stop checking at end of first corrupt page\n"
+msgstr ""
+" --on-error-stop arrête la vérification à la fin du premier bloc\n"
+" corrompu\n"
+
+#: pg_amcheck.c:1185
+#, c-format
+msgid " --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"
+msgstr ""
+" --skip=OPTION ne vérifie PAS les blocs « all-frozen » et\n"
+" « all-visible »\n"
+
+#: pg_amcheck.c:1186
+#, c-format
+msgid " --startblock=BLOCK begin checking table(s) at the given block number\n"
+msgstr ""
+" --startblock=BLOC commence la vérification des tables au numéro\n"
+" de bloc indiqué\n"
+
+#: pg_amcheck.c:1187
+#, c-format
+msgid " --endblock=BLOCK check table(s) only up to the given block number\n"
+msgstr ""
+" --endblock=BLOC vérifie les tables jusqu'au numéro de bloc\n"
+" indiqué\n"
+
+#: pg_amcheck.c:1188
+#, c-format
+msgid ""
+"\n"
+"B-tree index checking options:\n"
+msgstr ""
+"\n"
+"Options de vérification des index Btree :\n"
+
+#: pg_amcheck.c:1189
+#, c-format
+msgid " --heapallindexed check that all heap tuples are found within indexes\n"
+msgstr ""
+" --heapallindexed vérifie que tous les enregistrements de la\n"
+" table sont référencés dans les index\n"
+
+#: pg_amcheck.c:1190
+#, c-format
+msgid " --parent-check check index parent/child relationships\n"
+msgstr ""
+" --parent-check vérifie les relations parent/enfants dans les\n"
+" index\n"
+
+#: pg_amcheck.c:1191
+#, c-format
+msgid " --rootdescend search from root page to refind tuples\n"
+msgstr ""
+" --rootdescend recherche à partir de la racine pour trouver\n"
+" les lignes\n"
+
+#: pg_amcheck.c:1192
+#, c-format
+msgid ""
+"\n"
+"Connection options:\n"
+msgstr ""
+"\n"
+"Options de connexion :\n"
+
+#: pg_amcheck.c:1193
+#, c-format
+msgid " -h, --host=HOSTNAME database server host or socket directory\n"
+msgstr " -h, --host=HÔTE IP/alias du serveur ou répertoire du socket\n"
+
+#: pg_amcheck.c:1194
+#, c-format
+msgid " -p, --port=PORT database server port\n"
+msgstr " -p, --port=PORT port du serveur de bases de données\n"
+
+#: pg_amcheck.c:1195
+#, c-format
+msgid " -U, --username=USERNAME user name to connect as\n"
+msgstr " -U, --username=UTILISATEUR nom d'utilisateur pour la connexion\n"
+
+#: pg_amcheck.c:1196
+#, c-format
+msgid " -w, --no-password never prompt for password\n"
+msgstr " -w, --no-password ne demande jamais un mot de passe\n"
+
+#: pg_amcheck.c:1197
+#, c-format
+msgid " -W, --password force password prompt\n"
+msgstr " -W, --password force la saisie d'un mot de passe\n"
+
+#: pg_amcheck.c:1198
+#, c-format
+msgid " --maintenance-db=DBNAME alternate maintenance database\n"
+msgstr " --maintenance-db=BASE change la base de maintenance\n"
+
+#: pg_amcheck.c:1199
+#, c-format
+msgid ""
+"\n"
+"Other options:\n"
+msgstr ""
+"\n"
+"Autres options :\n"
+
+#: pg_amcheck.c:1200
+#, c-format
+msgid " -e, --echo show the commands being sent to the server\n"
+msgstr " -e, --echo affiche les commandes envoyées au serveur\n"
+
+#: pg_amcheck.c:1201
+#, c-format
+msgid " -j, --jobs=NUM use this many concurrent connections to the server\n"
+msgstr ""
+" -j, --jobs=NOMBRE utilise ce nombre de connexions simultanées au\n"
+" serveur\n"
+
+#: pg_amcheck.c:1202
+#, c-format
+msgid " -P, --progress show progress information\n"
+msgstr " -P, --progress affiche la progression\n"
+
+#: pg_amcheck.c:1203
+#, c-format
+msgid " -v, --verbose write a lot of output\n"
+msgstr " -v, --verbose mode verbeux\n"
+
+#: pg_amcheck.c:1204
+#, c-format
+msgid " -V, --version output version information, then exit\n"
+msgstr " -V, --version affiche la version puis quitte\n"
+
+#: pg_amcheck.c:1205
+#, c-format
+msgid " --install-missing install missing extensions\n"
+msgstr " --install-missing installe les extensions manquantes\n"
+
+#: pg_amcheck.c:1206
+#, c-format
+msgid " -?, --help show this help, then exit\n"
+msgstr " -?, --help affiche cette aide puis quitte\n"
+
+#: pg_amcheck.c:1208
+#, c-format
+msgid ""
+"\n"
+"Report bugs to <%s>.\n"
+msgstr ""
+"\n"
+"Rapporter les bogues à <%s>.\n"
+
+#: pg_amcheck.c:1209
+#, c-format
+msgid "%s home page: <%s>\n"
+msgstr "Page d'accueil de %s : <%s>\n"
+
+#: pg_amcheck.c:1267
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"
+msgstr "relations %*s/%s (%d%%), blocs %*s/%s (%d%%) %*s"
+
+#: pg_amcheck.c:1278
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"
+msgstr "relations %*s/%s (%d%%), blocs %*s/%s (%d%%) (%s%-*.*s)"
+
+#: pg_amcheck.c:1293
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%)"
+msgstr "relations %*s/%s (%d%%), blocs %*s/%s (%d%%)"
+
+#: pg_amcheck.c:1352 pg_amcheck.c:1385
+#, c-format
+msgid "improper qualified name (too many dotted names): %s"
+msgstr "mauvaise qualification du nom (trop de points entre les noms) : %s"
+
+#: pg_amcheck.c:1430
+#, c-format
+msgid "improper relation name (too many dotted names): %s"
+msgstr "nom de relation incorrecte (trop de points entre les noms) : %s"
+
+#: pg_amcheck.c:1583 pg_amcheck.c:1725
+#, c-format
+msgid "including database \"%s\""
+msgstr "en incluant la base de données : « %s »"
+
+#: pg_amcheck.c:1705
+#, c-format
+msgid "internal error: received unexpected database pattern_id %d"
+msgstr "erreur interne : a reçu un pattern_id %d inattendu de la base"
+
+#: pg_amcheck.c:1709
+#, c-format
+msgid "no connectable databases to check matching \"%s\""
+msgstr "aucune base de données connectable à vérifier correspondant à « %s »"
+
+#: pg_amcheck.c:2168
+#, c-format
+msgid "internal error: received unexpected relation pattern_id %d"
+msgstr "erreur interne : a reçu un pattern_id %d inattendu de la relation"
+
+#~ msgid ""
+#~ "\n"
+#~ "Other Options:\n"
+#~ msgstr ""
+#~ "\n"
+#~ "Autres options:\n"
+
+#~ msgid " -?, --help show this help, then exit\n"
+#~ msgstr " -?, --help affiche cette aide, puis quitte\n"
+
+#~ msgid " -V, --version output version information, then exit\n"
+#~ msgstr " -V, --version affiche la version, puis quitte\n"
+
+#~ msgid " -e, --echo show the commands being sent to the server\n"
+#~ msgstr " -e, --echo affiche les commandes envoyées au serveur\n"
+
+#~ msgid " -q, --quiet don't write any messages\n"
+#~ msgstr " -q, --quiet n'écrit aucun message\n"
+
+#~ msgid " -q, --quiet don't write any messages\n"
+#~ msgstr " -q, --quiet n'écrit aucun message\n"
+
+#~ msgid " -v, --verbose write a lot of output\n"
+#~ msgstr " -v, --verbose mode verbeux\n"
+
+#~ msgid "invalid skip option"
+#~ msgstr "option skip invalide"
+
+#~ msgid "number of parallel jobs must be at least 1\n"
+#~ msgstr "le nombre de jobs parallèles doit être au moins de 1\n"
diff --git a/src/bin/pg_amcheck/po/ja.po b/src/bin/pg_amcheck/po/ja.po
new file mode 100644
index 0000000..84a9fb0
--- /dev/null
+++ b/src/bin/pg_amcheck/po/ja.po
@@ -0,0 +1,527 @@
+# LANGUAGE message translation file for pg_amcheck
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_amcheck (PostgreSQL) package.
+# Kyotaro Horiguchi <horikyota.ntt@gmail.com>, 2021.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pg_amcheck (PostgreSQL 14)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2021-08-25 17:21+0900\n"
+"PO-Revision-Date: 2021-08-25 11:19+0900\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.13\n"
+
+#: ../../../src/common/logging.c:259
+#, c-format
+msgid "fatal: "
+msgstr "致命的エラー: "
+
+#: ../../../src/common/logging.c:266
+#, c-format
+msgid "error: "
+msgstr "エラー: "
+
+#: ../../../src/common/logging.c:273
+#, c-format
+msgid "warning: "
+msgstr "警告: "
+
+#: ../../fe_utils/cancel.c:161 ../../fe_utils/cancel.c:206
+msgid "Cancel request sent\n"
+msgstr "キャンセル要求を送信しました\n"
+
+#: ../../fe_utils/cancel.c:165 ../../fe_utils/cancel.c:210
+msgid "Could not send cancel request: "
+msgstr "キャンセル要求を送信できませんでした: "
+
+#: ../../fe_utils/connect_utils.c:92
+#, c-format
+msgid "could not connect to database %s: out of memory"
+msgstr "データベース%sに接続できませんでした: メモリ不足"
+
+#: ../../fe_utils/connect_utils.c:120
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: ../../fe_utils/query_utils.c:33 ../../fe_utils/query_utils.c:58
+#: pg_amcheck.c:1641 pg_amcheck.c:2080
+#, c-format
+msgid "query failed: %s"
+msgstr "問い合わせが失敗しました: %s"
+
+#: ../../fe_utils/query_utils.c:34 ../../fe_utils/query_utils.c:59
+#: pg_amcheck.c:598 pg_amcheck.c:1112 pg_amcheck.c:1642 pg_amcheck.c:2081
+#, c-format
+msgid "query was: %s"
+msgstr "問い合わせ: %s"
+
+#: pg_amcheck.c:330
+#, c-format
+msgid "number of parallel jobs must be at least 1"
+msgstr "並列ジョブの数は最低でも1以上でなければなりません"
+
+#: pg_amcheck.c:402
+#, c-format
+msgid "invalid argument for option %s"
+msgstr "オプション%sの引数が不正です"
+
+#: pg_amcheck.c:411
+#, c-format
+msgid "invalid start block"
+msgstr "不正な開始ブロック"
+
+#: pg_amcheck.c:416
+#, c-format
+msgid "start block out of bounds"
+msgstr "開始ブロックが範囲外"
+
+#: pg_amcheck.c:426
+#, c-format
+msgid "invalid end block"
+msgstr "不正な終了ブロック"
+
+#: pg_amcheck.c:431
+#, c-format
+msgid "end block out of bounds"
+msgstr "終了ブロックが範囲外"
+
+#: pg_amcheck.c:456 pg_amcheck.c:482
+#, c-format
+msgid "Try \"%s --help\" for more information.\n"
+msgstr "詳細は\"%s --help\"で確認してください。\n"
+
+#: pg_amcheck.c:464
+#, c-format
+msgid "end block precedes start block"
+msgstr "終了ブロックが開始ブロックの前にあります"
+
+#: pg_amcheck.c:480
+#, c-format
+msgid "too many command-line arguments (first is \"%s\")"
+msgstr "コマンドライン引数が多すぎます (先頭は\"%s\")"
+
+#: pg_amcheck.c:501
+#, c-format
+msgid "cannot specify a database name with --all"
+msgstr "データベース名を -all と同時に指定することはできません"
+
+#: pg_amcheck.c:510
+#, c-format
+msgid "cannot specify both a database name and database patterns"
+msgstr "データベース名とデータベースパターンは同時に指定できません"
+
+#: pg_amcheck.c:540
+#, c-format
+msgid "no databases to check"
+msgstr "検査すべきデータベースがありません"
+
+#: pg_amcheck.c:596
+#, c-format
+msgid "database \"%s\": %s"
+msgstr "データベース\"%s\": %s"
+
+#: pg_amcheck.c:607
+#, c-format
+msgid "skipping database \"%s\": amcheck is not installed"
+msgstr "データベース\"%s\"をスキップします: amcheckがインストールされていません"
+
+#: pg_amcheck.c:615
+#, c-format
+msgid "in database \"%s\": using amcheck version \"%s\" in schema \"%s\""
+msgstr "データベース\"%1$s\": スキーマ%3$sのamcheckバージョン\"%2$s\"を使用"
+
+#: pg_amcheck.c:637
+#, c-format
+msgid "no heap tables to check matching \"%s\""
+msgstr "\"%s\"に合致する検査すべきヒープテーブルがありません"
+
+#: pg_amcheck.c:640
+#, c-format
+msgid "no btree indexes to check matching \"%s\""
+msgstr "\"%s\"に合致する検査すべきbtreeインデックスがありません"
+
+#: pg_amcheck.c:643
+#, c-format
+msgid "no relations to check in schemas matching \"%s\""
+msgstr "\"%s\"に合致するスキーマに検査すべきリレーションがありません"
+
+#: pg_amcheck.c:646
+#, c-format
+msgid "no relations to check matching \"%s\""
+msgstr "\"%s\"に合致する検査すべきリレーションがありません"
+
+#: pg_amcheck.c:674
+#, c-format
+msgid "no relations to check"
+msgstr "検査すべきリレーションがありません"
+
+#: pg_amcheck.c:758
+#, c-format
+msgid "checking heap table \"%s.%s.%s\""
+msgstr "ヒープテーブル\"%s.%s.%s\"の検査中"
+
+#: pg_amcheck.c:774
+#, c-format
+msgid "checking btree index \"%s.%s.%s\""
+msgstr "btreeインデックス\"%s.%s.%s\"の検査中"
+
+#: pg_amcheck.c:907
+#, c-format
+msgid "error sending command to database \"%s\": %s"
+msgstr "データベース\"%s\"へのコマンド送出中のエラー: %s"
+
+#: pg_amcheck.c:910
+#, c-format
+msgid "command was: %s"
+msgstr "コマンド: %s"
+
+#: pg_amcheck.c:1027
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"
+msgstr "ヒープテーブル\"%s.%s.%s\"、ブロック%s、オフセット%s、属性%s:\n"
+
+#: pg_amcheck.c:1034
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s:\n"
+msgstr "ヒープテーブル\"%s.%s.%s\"、ブロック%s、オフセット%s:\n"
+
+#: pg_amcheck.c:1040
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s:\n"
+msgstr "ヒープテーブル\"%s.%s.%s\"、ブロック%sの検査中\n"
+
+#: pg_amcheck.c:1045 pg_amcheck.c:1056
+#, c-format
+msgid "heap table \"%s.%s.%s\":\n"
+msgstr "ヒープテーブル\"%s.%s.%s\"の検査中\n"
+
+#: pg_amcheck.c:1060 pg_amcheck.c:1127
+#, c-format
+msgid "query was: %s\n"
+msgstr "問い合わせ: %s\n"
+
+#: pg_amcheck.c:1109
+#, c-format
+msgid "btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d"
+msgstr "btreeインデックス\"%s.%s.%s\": btree検査関数が予期しない数の行を返却しました: %d"
+
+#: pg_amcheck.c:1113
+#, c-format
+msgid "Are %s's and amcheck's versions compatible?"
+msgstr "%sとamcheckのバージョンは合っていますか?"
+
+#: pg_amcheck.c:1123
+#, c-format
+msgid "btree index \"%s.%s.%s\":\n"
+msgstr "btreeインデックス\"%s.%s.%s\"の検査中\n"
+
+#: pg_amcheck.c:1148
+#, c-format
+msgid ""
+"%s checks objects in a PostgreSQL database for corruption.\n"
+"\n"
+msgstr ""
+"%sはPostgreSQLデータベース内のオブジェクトの破損の検査を行います。\n"
+"\n"
+
+#: pg_amcheck.c:1149
+#, c-format
+msgid "Usage:\n"
+msgstr "使用方法:\n"
+
+#: pg_amcheck.c:1150
+#, c-format
+msgid " %s [OPTION]... [DBNAME]\n"
+msgstr " %s [オプション]... [データベース名]\n"
+
+#: pg_amcheck.c:1151
+#, c-format
+msgid ""
+"\n"
+"Target options:\n"
+msgstr ""
+"\n"
+"対象指定オプション:\n"
+
+#: pg_amcheck.c:1152
+#, c-format
+msgid " -a, --all check all databases\n"
+msgstr " -a, --all すべてのデータベースを検査する\n"
+
+#: pg_amcheck.c:1153
+#, c-format
+msgid " -d, --database=PATTERN check matching database(s)\n"
+msgstr " -d, --database=PATTERN 合致するデータベースを検査する\n"
+
+#: pg_amcheck.c:1154
+#, c-format
+msgid " -D, --exclude-database=PATTERN do NOT check matching database(s)\n"
+msgstr " -D, --exclude-database=PATTERN 合致するデータベースを検査「しない」\n"
+
+#: pg_amcheck.c:1155
+#, c-format
+msgid " -i, --index=PATTERN check matching index(es)\n"
+msgstr " -i, --index=PATTERN 合致するインデックスを検査する\n"
+
+#: pg_amcheck.c:1156
+#, c-format
+msgid " -I, --exclude-index=PATTERN do NOT check matching index(es)\n"
+msgstr " -I, --exclude-index=PATTERN 合致するインデックスを検査「しない」\n"
+
+#: pg_amcheck.c:1157
+#, c-format
+msgid " -r, --relation=PATTERN check matching relation(s)\n"
+msgstr " -r, --relation=PATTERN 合致するリレーションを検査する\n"
+
+#: pg_amcheck.c:1158
+#, c-format
+msgid " -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"
+msgstr " -R, --exclude-relation=PATTERN 合致するリレーションを検査「しない」\n"
+
+#: pg_amcheck.c:1159
+#, c-format
+msgid " -s, --schema=PATTERN check matching schema(s)\n"
+msgstr " -s, --schema=PATTERN 合致するスキーマを検査する\n"
+
+#: pg_amcheck.c:1160
+#, c-format
+msgid " -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"
+msgstr " -S, --exclude-schema=PATTERN 合致するスキーマを検査「しない」\n"
+
+#: pg_amcheck.c:1161
+#, c-format
+msgid " -t, --table=PATTERN check matching table(s)\n"
+msgstr " -t, --table=PATTERN 合致するテーブルを検査する\n"
+
+#: pg_amcheck.c:1162
+#, c-format
+msgid " -T, --exclude-table=PATTERN do NOT check matching table(s)\n"
+msgstr " -T, --exclude-table=PATTERN 合致するテーブルを検査「しない」\n"
+
+#: pg_amcheck.c:1163
+#, c-format
+msgid " --no-dependent-indexes do NOT expand list of relations to include indexes\n"
+msgstr ""
+" --no-dependent-indexes リレーションのリストをインデックスを含むように\n"
+" 拡張「しない」\n"
+
+#: pg_amcheck.c:1164
+#, c-format
+msgid " --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"
+msgstr ""
+" --no-dependent-toast リレーションのリストをTOASTテーブルを含むよう\n"
+" に拡張「しない」\n"
+
+#: pg_amcheck.c:1165
+#, c-format
+msgid " --no-strict-names do NOT require patterns to match objects\n"
+msgstr ""
+" --no-strict-names パターンにオブジェクトが合致しないことを許容\n"
+" する\n"
+
+#: pg_amcheck.c:1166
+#, c-format
+msgid ""
+"\n"
+"Table checking options:\n"
+msgstr ""
+"\n"
+"テーブル検査オプション:\n"
+
+#: pg_amcheck.c:1167
+#, c-format
+msgid " --exclude-toast-pointers do NOT follow relation TOAST pointers\n"
+msgstr " --exclude-toast-pointers リレーションTOASTポインタを追跡「しない」\n"
+
+#: pg_amcheck.c:1168
+#, c-format
+msgid " --on-error-stop stop checking at end of first corrupt page\n"
+msgstr " --on-error-stop 検査を最初の破壊ページで停止する\n"
+
+#: pg_amcheck.c:1169
+#, c-format
+msgid " --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"
+msgstr ""
+" --skip=OPTION \"all-frozen\"あるいは\"all-visible\"である\n"
+" ブロックを検査「しない」\n"
+
+#: pg_amcheck.c:1170
+#, c-format
+msgid " --startblock=BLOCK begin checking table(s) at the given block number\n"
+msgstr ""
+" --startblock=BLOCK テーブルの検索を指定したブロック番号から開始\n"
+" する\n"
+
+#: pg_amcheck.c:1171
+#, c-format
+msgid " --endblock=BLOCK check table(s) only up to the given block number\n"
+msgstr ""
+" --endblock=BLOCK テーブルの検索を指定したブロック番号までで停止\n"
+" する\n"
+
+#: pg_amcheck.c:1172
+#, c-format
+msgid ""
+"\n"
+"B-tree index checking options:\n"
+msgstr ""
+"\n"
+"B-treeインデックス検査オプション:\n"
+
+#: pg_amcheck.c:1173
+#, c-format
+msgid " --heapallindexed check that all heap tuples are found within indexes\n"
+msgstr ""
+" --heapallindexed すべてのヒープタプルがインデックス中に見つか\n"
+" ることを検証する\n"
+
+#: pg_amcheck.c:1174
+#, c-format
+msgid " --parent-check check index parent/child relationships\n"
+msgstr " --parent-check インデックスの親子関係を検査する\n"
+
+#: pg_amcheck.c:1175
+#, c-format
+msgid " --rootdescend search from root page to refind tuples\n"
+msgstr " --rootdescend タプルの再探索をルートページから行う\n"
+
+#: pg_amcheck.c:1176
+#, c-format
+msgid ""
+"\n"
+"Connection options:\n"
+msgstr ""
+"\n"
+"接続オプション:\n"
+
+#: pg_amcheck.c:1177
+#, c-format
+msgid " -h, --host=HOSTNAME database server host or socket directory\n"
+msgstr ""
+" -h, --host=HOSTNAME データベースサーバーのホストまたはソケット\n"
+" ディレクトリ\n"
+
+#: pg_amcheck.c:1178
+#, c-format
+msgid " -p, --port=PORT database server port\n"
+msgstr " -p, --port=PORT データベースサーバーのポート\n"
+
+#: pg_amcheck.c:1179
+#, c-format
+msgid " -U, --username=USERNAME user name to connect as\n"
+msgstr " -U, --username=USERNAME 接続に用いるユーザー名\n"
+
+#: pg_amcheck.c:1180
+#, c-format
+msgid " -w, --no-password never prompt for password\n"
+msgstr " -w, --no-password パスワードを要求しない\n"
+
+#: pg_amcheck.c:1181
+#, c-format
+msgid " -W, --password force password prompt\n"
+msgstr " -W, --password パスワード要求を強制する\n"
+
+#: pg_amcheck.c:1182
+#, c-format
+msgid " --maintenance-db=DBNAME alternate maintenance database\n"
+msgstr " --maintenance-db=DBNAME 代替の保守データベース\n"
+
+#: pg_amcheck.c:1183
+#, c-format
+msgid ""
+"\n"
+"Other options:\n"
+msgstr ""
+"\n"
+"その他のオプション:\n"
+
+#: pg_amcheck.c:1184
+#, c-format
+msgid " -e, --echo show the commands being sent to the server\n"
+msgstr " -e, --echo サーバーに送られるコマンドを表示\n"
+
+#: pg_amcheck.c:1185
+#, c-format
+msgid " -j, --jobs=NUM use this many concurrent connections to the server\n"
+msgstr " -j, --jobs=NUM 使用するサーバー接続数を指定する\n"
+
+#: pg_amcheck.c:1186
+#, c-format
+msgid " -P, --progress show progress information\n"
+msgstr " -P, --progress 進捗状況を表示する\n"
+
+#: pg_amcheck.c:1187
+#, c-format
+msgid " -v, --verbose write a lot of output\n"
+msgstr " -v, --verbose 多くのメッセージを出力します\n"
+
+#: pg_amcheck.c:1188
+#, c-format
+msgid " -V, --version output version information, then exit\n"
+msgstr " -V, --version バージョン情報を表示して終了\n"
+
+#: pg_amcheck.c:1189
+#, c-format
+msgid " --install-missing install missing extensions\n"
+msgstr " --install-missing 足りない機能拡張をインストールする\n"
+
+#: pg_amcheck.c:1190
+#, c-format
+msgid " -?, --help show this help, then exit\n"
+msgstr " -?, --help このヘルプを表示して終了\n"
+
+#: pg_amcheck.c:1192
+#, c-format
+msgid ""
+"\n"
+"Report bugs to <%s>.\n"
+msgstr ""
+"\n"
+"バグは<%s>に報告してください。\n"
+
+#: pg_amcheck.c:1193
+#, c-format
+msgid "%s home page: <%s>\n"
+msgstr "%s ホームページ: <%s>\n"
+
+#: pg_amcheck.c:1251
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"
+msgstr "%*s/%s リレーション (%d%%)、 %*s/%s ページ (%d%%) %*s"
+
+#: pg_amcheck.c:1262
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"
+msgstr "%*s/%s リレーション (%d%%)、 %*s/%s ページ (%d%%), (%s%-*.*s)"
+
+#: pg_amcheck.c:1277
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%)"
+msgstr "%*s/%s リレーション (%d%%)、 %*s/%s ページ (%d%%)"
+
+#: pg_amcheck.c:1546 pg_amcheck.c:1688
+#, c-format
+msgid "including database \"%s\""
+msgstr "データベース\"%s\"を含めます"
+
+#: pg_amcheck.c:1668
+#, c-format
+msgid "internal error: received unexpected database pattern_id %d"
+msgstr "内部エラー: 予期しないデータベースパターンID%dを受信しました"
+
+#: pg_amcheck.c:1672
+#, c-format
+msgid "no connectable databases to check matching \"%s\""
+msgstr "\"%s”に合致する検査すべき接続可能なデータベースがありません"
+
+#: pg_amcheck.c:2122
+#, c-format
+msgid "internal error: received unexpected relation pattern_id %d"
+msgstr "内部エラー: 予期しないリレーションパターンID%dを受信しました"
diff --git a/src/bin/pg_amcheck/po/ru.po b/src/bin/pg_amcheck/po/ru.po
new file mode 100644
index 0000000..ec5b01d
--- /dev/null
+++ b/src/bin/pg_amcheck/po/ru.po
@@ -0,0 +1,591 @@
+# Alexander Lakhin <a.lakhin@postgrespro.ru>, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: pg_amcheck (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-05-07 06:06+0300\n"
+"PO-Revision-Date: 2022-05-07 06:16+0300\n"
+"Last-Translator: Alexander Lakhin <exclusion@gmail.com>\n"
+"Language-Team: Russian <pgsql-ru-general@postgresql.org>\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../src/common/logging.c:259
+#, c-format
+msgid "fatal: "
+msgstr "важно: "
+
+#: ../../../src/common/logging.c:266
+#, c-format
+msgid "error: "
+msgstr "ошибка: "
+
+#: ../../../src/common/logging.c:273
+#, c-format
+msgid "warning: "
+msgstr "предупреждение: "
+
+#: ../../fe_utils/cancel.c:189 ../../fe_utils/cancel.c:238
+msgid "Cancel request sent\n"
+msgstr "Сигнал отмены отправлен\n"
+
+#: ../../fe_utils/cancel.c:190 ../../fe_utils/cancel.c:239
+msgid "Could not send cancel request: "
+msgstr "Отправить сигнал отмены не удалось: "
+
+#: ../../fe_utils/connect_utils.c:92
+#, c-format
+msgid "could not connect to database %s: out of memory"
+msgstr "не удалось подключиться к базе %s (нехватка памяти)"
+
+#: ../../fe_utils/connect_utils.c:120
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: ../../fe_utils/query_utils.c:33 ../../fe_utils/query_utils.c:58
+#: pg_amcheck.c:1678 pg_amcheck.c:2126
+#, c-format
+msgid "query failed: %s"
+msgstr "ошибка при выполнении запроса: %s"
+
+#: ../../fe_utils/query_utils.c:34 ../../fe_utils/query_utils.c:59
+#: pg_amcheck.c:598 pg_amcheck.c:1128 pg_amcheck.c:1679 pg_amcheck.c:2127
+#, c-format
+msgid "query was: %s"
+msgstr "запрос: %s"
+
+#: pg_amcheck.c:330
+#, c-format
+msgid "number of parallel jobs must be at least 1"
+msgstr "число параллельных заданий должно быть не меньше 1"
+
+#: pg_amcheck.c:402
+#, c-format
+msgid "invalid argument for option %s"
+msgstr "недопустимый аргумент параметра %s"
+
+#: pg_amcheck.c:411
+#, c-format
+msgid "invalid start block"
+msgstr "неверный начальный блок"
+
+#: pg_amcheck.c:416
+#, c-format
+msgid "start block out of bounds"
+msgstr "начальный блок вне допустимых пределов"
+
+#: pg_amcheck.c:426
+#, c-format
+msgid "invalid end block"
+msgstr "неверный конечный блок"
+
+#: pg_amcheck.c:431
+#, c-format
+msgid "end block out of bounds"
+msgstr "конечный блок вне допустимых пределов"
+
+#: pg_amcheck.c:456 pg_amcheck.c:482
+#, c-format
+msgid "Try \"%s --help\" for more information.\n"
+msgstr "Для дополнительной информации попробуйте \"%s --help\".\n"
+
+#: pg_amcheck.c:464
+#, c-format
+msgid "end block precedes start block"
+msgstr "конечный блок предшествует начальному"
+
+#: pg_amcheck.c:480
+#, c-format
+msgid "too many command-line arguments (first is \"%s\")"
+msgstr "слишком много аргументов командной строки (первый: \"%s\")"
+
+#: pg_amcheck.c:501
+#, c-format
+msgid "cannot specify a database name with --all"
+msgstr "имя базы данных нельзя задавать с --all"
+
+#: pg_amcheck.c:510
+#, c-format
+msgid "cannot specify both a database name and database patterns"
+msgstr "нельзя задавать одновременно имя базы данных и шаблоны имён"
+
+#: pg_amcheck.c:540
+#, c-format
+msgid "no databases to check"
+msgstr "не указаны базы для проверки"
+
+#: pg_amcheck.c:596
+#, c-format
+msgid "database \"%s\": %s"
+msgstr "база данных \"%s\": %s"
+
+#: pg_amcheck.c:607
+#, c-format
+msgid "skipping database \"%s\": amcheck is not installed"
+msgstr "база \"%s\" пропускается: расширение amcheck не установлено"
+
+#: pg_amcheck.c:615
+#, c-format
+msgid "in database \"%s\": using amcheck version \"%s\" in schema \"%s\""
+msgstr "база \"%s\": используется amcheck версии \"%s\" в схеме \"%s\""
+
+#: pg_amcheck.c:637
+#, c-format
+msgid "no heap tables to check matching \"%s\""
+msgstr "не найдены подлежащие проверке базовые таблицы, соответствующие \"%s\""
+
+#: pg_amcheck.c:640
+#, c-format
+msgid "no btree indexes to check matching \"%s\""
+msgstr "не найдены подлежащие проверке индексы btree, соответствующие \"%s\""
+
+#: pg_amcheck.c:643
+#, c-format
+msgid "no relations to check in schemas matching \"%s\""
+msgstr ""
+"не найдены подлежащие проверке отношения в схемах, соответствующих \"%s\""
+
+#: pg_amcheck.c:646
+#, c-format
+msgid "no relations to check matching \"%s\""
+msgstr "не найдены подлежащие проверке отношения, соответствующие \"%s\""
+
+#: pg_amcheck.c:674
+#, c-format
+msgid "no relations to check"
+msgstr "не найдены отношения для проверки"
+
+#: pg_amcheck.c:758
+#, c-format
+msgid "checking heap table \"%s.%s.%s\""
+msgstr "проверка базовой таблицы \"%s.%s.%s\""
+
+#: pg_amcheck.c:774
+#, c-format
+msgid "checking btree index \"%s.%s.%s\""
+msgstr "проверка индекса btree \"%s.%s.%s\""
+
+#: pg_amcheck.c:921
+#, c-format
+msgid "error sending command to database \"%s\": %s"
+msgstr "ошибка передачи команды базе \"%s\": %s"
+
+#: pg_amcheck.c:924
+#, c-format
+msgid "command was: %s"
+msgstr "команда: %s"
+
+#: pg_amcheck.c:1041
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"
+msgstr "базовая таблица \"%s.%s.%s\", блок %s, смещение %s, атрибут %s:\n"
+
+#: pg_amcheck.c:1048
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s:\n"
+msgstr "базовая таблица \"%s.%s.%s\", блок %s, смещение %s:\n"
+
+#: pg_amcheck.c:1054
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s:\n"
+msgstr "базовая таблица \"%s.%s.%s\", блок %s:\n"
+
+#: pg_amcheck.c:1059 pg_amcheck.c:1070
+#, c-format
+msgid "heap table \"%s.%s.%s\":\n"
+msgstr "базовая таблица \"%s.%s.%s\":\n"
+
+#: pg_amcheck.c:1074 pg_amcheck.c:1143
+#, c-format
+msgid "query was: %s\n"
+msgstr "запрос: %s\n"
+
+#: pg_amcheck.c:1125
+#, c-format
+msgid ""
+"btree index \"%s.%s.%s\": btree checking function returned unexpected number "
+"of rows: %d"
+msgstr ""
+"индекс btree \"%s.%s.%s\": функция проверки btree выдала неожиданное "
+"количество строк: %d"
+
+#: pg_amcheck.c:1129
+#, c-format
+msgid "Are %s's and amcheck's versions compatible?"
+msgstr "Совместимы ли версии %s и amcheck?"
+
+#: pg_amcheck.c:1139
+#, c-format
+msgid "btree index \"%s.%s.%s\":\n"
+msgstr "индекс btree \"%s.%s.%s\":\n"
+
+#: pg_amcheck.c:1164
+#, c-format
+msgid ""
+"%s checks objects in a PostgreSQL database for corruption.\n"
+"\n"
+msgstr ""
+"%s проверяет объекты в базе данных PostgreSQL на предмет повреждений.\n"
+"\n"
+
+#: pg_amcheck.c:1165
+#, c-format
+msgid "Usage:\n"
+msgstr "Использование:\n"
+
+#: pg_amcheck.c:1166
+#, c-format
+msgid " %s [OPTION]... [DBNAME]\n"
+msgstr " %s [ПАРАМЕТР]... [ИМЯ_БД]\n"
+
+#: pg_amcheck.c:1167
+#, c-format
+msgid ""
+"\n"
+"Target options:\n"
+msgstr ""
+"\n"
+"Параметры выбора объектов:\n"
+
+#: pg_amcheck.c:1168
+#, c-format
+msgid " -a, --all check all databases\n"
+msgstr " -a, --all проверить все базы\n"
+
+#: pg_amcheck.c:1169
+#, c-format
+msgid " -d, --database=PATTERN check matching database(s)\n"
+msgstr ""
+" -d, --database=ШАБЛОН проверить соответствующие шаблону базы\n"
+
+#: pg_amcheck.c:1170
+#, c-format
+msgid " -D, --exclude-database=PATTERN do NOT check matching database(s)\n"
+msgstr ""
+" -D, --exclude-database=ШАБЛОН не проверять соответствующие шаблону базы\n"
+
+#: pg_amcheck.c:1171
+#, c-format
+msgid " -i, --index=PATTERN check matching index(es)\n"
+msgstr ""
+" -i, --index=ШАБЛОН проверить соответствующие шаблону индексы\n"
+
+#: pg_amcheck.c:1172
+#, c-format
+msgid " -I, --exclude-index=PATTERN do NOT check matching index(es)\n"
+msgstr ""
+" -I, --exclude-index=ШАБЛОН не проверять соответствующие шаблону "
+"индексы\n"
+
+#: pg_amcheck.c:1173
+#, c-format
+msgid " -r, --relation=PATTERN check matching relation(s)\n"
+msgstr ""
+" -r, --relation=ШАБЛОН проверить соответствующие шаблону "
+"отношения\n"
+
+#: pg_amcheck.c:1174
+#, c-format
+msgid " -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"
+msgstr ""
+" -R, --exclude-relation=ШАБЛОН не проверять соответствующие шаблону "
+"отношения\n"
+
+#: pg_amcheck.c:1175
+#, c-format
+msgid " -s, --schema=PATTERN check matching schema(s)\n"
+msgstr ""
+" -s, --schema=ШАБЛОН проверить соответствующие шаблону схемы\n"
+
+#: pg_amcheck.c:1176
+#, c-format
+msgid " -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"
+msgstr ""
+" -S, --exclude-schema=ШАБЛОН не проверять соответствующие шаблону "
+"схемы\n"
+
+#: pg_amcheck.c:1177
+#, c-format
+msgid " -t, --table=PATTERN check matching table(s)\n"
+msgstr ""
+" -t, --table=ШАБЛОН проверить соответствующие шаблону таблицы\n"
+
+#: pg_amcheck.c:1178
+#, c-format
+msgid " -T, --exclude-table=PATTERN do NOT check matching table(s)\n"
+msgstr ""
+" -T, --exclude-table=ШАБЛОН не проверять соответствующие шаблону "
+"таблицы\n"
+
+#: pg_amcheck.c:1179
+#, c-format
+msgid ""
+" --no-dependent-indexes do NOT expand list of relations to include "
+"indexes\n"
+msgstr ""
+" --no-dependent-indexes не включать в список проверяемых отношений "
+"индексы\n"
+
+#: pg_amcheck.c:1180
+#, c-format
+msgid ""
+" --no-dependent-toast do NOT expand list of relations to include "
+"TOAST tables\n"
+msgstr ""
+" --no-dependent-toast не включать в список проверяемых отношений "
+"TOAST-таблицы\n"
+
+#: pg_amcheck.c:1181
+#, c-format
+msgid ""
+" --no-strict-names do NOT require patterns to match objects\n"
+msgstr ""
+" --no-strict-names не требовать наличия объектов, "
+"соответствующих шаблонам\n"
+
+#: pg_amcheck.c:1182
+#, c-format
+msgid ""
+"\n"
+"Table checking options:\n"
+msgstr ""
+"\n"
+"Параметры проверки таблиц:\n"
+
+#: pg_amcheck.c:1183
+#, c-format
+msgid ""
+" --exclude-toast-pointers do NOT follow relation TOAST pointers\n"
+msgstr ""
+" --exclude-toast-pointers не переходить по указателям в TOAST\n"
+
+#: pg_amcheck.c:1184
+#, c-format
+msgid ""
+" --on-error-stop stop checking at end of first corrupt "
+"page\n"
+msgstr ""
+" --on-error-stop прекратить проверку по достижении конца "
+"первой повреждённой страницы\n"
+
+#: pg_amcheck.c:1185
+#, c-format
+msgid ""
+" --skip=OPTION do NOT check \"all-frozen\" or \"all-"
+"visible\" blocks\n"
+msgstr ""
+" --skip=ТИП_БЛОКА не проверять блоки типа \"all-frozen\" или "
+"\"all-visible\"\n"
+
+#: pg_amcheck.c:1186
+#, c-format
+msgid ""
+" --startblock=BLOCK begin checking table(s) at the given block "
+"number\n"
+msgstr ""
+" --startblock=БЛОК начать проверку таблиц(ы) с блока с "
+"заданным номером\n"
+
+# skip-rule: no-space-before-parentheses
+#: pg_amcheck.c:1187
+#, c-format
+msgid ""
+" --endblock=BLOCK check table(s) only up to the given block "
+"number\n"
+msgstr ""
+" --endblock=БЛОК проверить таблицы(у) до блока с заданным "
+"номером\n"
+
+#: pg_amcheck.c:1188
+#, c-format
+msgid ""
+"\n"
+"B-tree index checking options:\n"
+msgstr ""
+"\n"
+"Параметры проверки индексов-B-деревьев:\n"
+
+#: pg_amcheck.c:1189
+#, c-format
+msgid ""
+" --heapallindexed check that all heap tuples are found "
+"within indexes\n"
+msgstr ""
+" --heapallindexed проверить, что всем кортежам кучи "
+"находится соответствие в индексах\n"
+
+#: pg_amcheck.c:1190
+#, c-format
+msgid ""
+" --parent-check check index parent/child relationships\n"
+msgstr ""
+" --parent-check проверить связи родитель/потомок в "
+"индексах\n"
+
+#: pg_amcheck.c:1191
+#, c-format
+msgid ""
+" --rootdescend search from root page to refind tuples\n"
+msgstr ""
+" --rootdescend перепроверять поиск кортежей от корневой "
+"страницы\n"
+
+#: pg_amcheck.c:1192
+#, c-format
+msgid ""
+"\n"
+"Connection options:\n"
+msgstr ""
+"\n"
+"Параметры подключения:\n"
+
+#: pg_amcheck.c:1193
+#, c-format
+msgid ""
+" -h, --host=HOSTNAME database server host or socket directory\n"
+msgstr ""
+" -h, --host=ИМЯ имя сервера баз данных или каталог "
+"сокетов\n"
+
+#: pg_amcheck.c:1194
+#, c-format
+msgid " -p, --port=PORT database server port\n"
+msgstr " -p, --port=ПОРТ порт сервера баз данных\n"
+
+#: pg_amcheck.c:1195
+#, c-format
+msgid " -U, --username=USERNAME user name to connect as\n"
+msgstr ""
+" -U, --username=ИМЯ имя пользователя для подключения к "
+"серверу\n"
+
+#: pg_amcheck.c:1196
+#, c-format
+msgid " -w, --no-password never prompt for password\n"
+msgstr " -w, --no-password не запрашивать пароль\n"
+
+#: pg_amcheck.c:1197
+#, c-format
+msgid " -W, --password force password prompt\n"
+msgstr " -W, --password запросить пароль\n"
+
+#: pg_amcheck.c:1198
+#, c-format
+msgid " --maintenance-db=DBNAME alternate maintenance database\n"
+msgstr " --maintenance-db=ИМЯ_БД другая опорная база данных\n"
+
+#: pg_amcheck.c:1199
+#, c-format
+msgid ""
+"\n"
+"Other options:\n"
+msgstr ""
+"\n"
+"Другие параметры:\n"
+
+#: pg_amcheck.c:1200
+#, c-format
+msgid ""
+" -e, --echo show the commands being sent to the "
+"server\n"
+msgstr ""
+" -e, --echo отображать команды, отправляемые серверу\n"
+
+#: pg_amcheck.c:1201
+#, c-format
+msgid ""
+" -j, --jobs=NUM use this many concurrent connections to "
+"the server\n"
+msgstr ""
+" -j, --jobs=ЧИСЛО устанавливать заданное число подключений к "
+"серверу\n"
+
+#: pg_amcheck.c:1202
+#, c-format
+msgid " -P, --progress show progress information\n"
+msgstr " -P, --progress показывать прогресс операции\n"
+
+#: pg_amcheck.c:1203
+#, c-format
+msgid " -v, --verbose write a lot of output\n"
+msgstr " -v, --verbose выводить исчерпывающие сообщения\n"
+
+#: pg_amcheck.c:1204
+#, c-format
+msgid ""
+" -V, --version output version information, then exit\n"
+msgstr " -V, --version показать версию и выйти\n"
+
+#: pg_amcheck.c:1205
+#, c-format
+msgid " --install-missing install missing extensions\n"
+msgstr " --install-missing установить недостающие расширения\n"
+
+#: pg_amcheck.c:1206
+#, c-format
+msgid " -?, --help show this help, then exit\n"
+msgstr " -?, --help показать эту справку и выйти\n"
+
+#: pg_amcheck.c:1208
+#, c-format
+msgid ""
+"\n"
+"Report bugs to <%s>.\n"
+msgstr ""
+"\n"
+"Об ошибках сообщайте по адресу <%s>.\n"
+
+#: pg_amcheck.c:1209
+#, c-format
+msgid "%s home page: <%s>\n"
+msgstr "Домашняя страница %s: <%s>\n"
+
+#: pg_amcheck.c:1267
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"
+msgstr "отношений: %*s/%s (%d%%), страниц: %*s/%s (%d%%) %*s"
+
+#: pg_amcheck.c:1278
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"
+msgstr "отношений: %*s/%s (%d%%), страниц: %*s/%s (%d%%) (%s%-*.*s)"
+
+#: pg_amcheck.c:1293
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%)"
+msgstr "отношений: %*s/%s (%d%%), страниц: %*s/%s (%d%%)"
+
+#: pg_amcheck.c:1352 pg_amcheck.c:1385
+#, c-format
+msgid "improper qualified name (too many dotted names): %s"
+msgstr "неверное полное имя (слишком много компонентов): %s"
+
+#: pg_amcheck.c:1430
+#, c-format
+msgid "improper relation name (too many dotted names): %s"
+msgstr "неверное имя отношения (слишком много компонентов): %s"
+
+#: pg_amcheck.c:1583 pg_amcheck.c:1725
+#, c-format
+msgid "including database \"%s\""
+msgstr "выбирается база \"%s\""
+
+#: pg_amcheck.c:1705
+#, c-format
+msgid "internal error: received unexpected database pattern_id %d"
+msgstr "внутренняя ошибка: получен неожиданный идентификатор шаблона базы %d"
+
+#: pg_amcheck.c:1709
+#, c-format
+msgid "no connectable databases to check matching \"%s\""
+msgstr ""
+"не найдены подлежащие проверке доступные базы, соответствующие шаблону \"%s\""
+
+#: pg_amcheck.c:2168
+#, c-format
+msgid "internal error: received unexpected relation pattern_id %d"
+msgstr ""
+"внутренняя ошибка: получен неожиданный идентификатор шаблона отношения %d"
diff --git a/src/bin/pg_amcheck/po/sv.po b/src/bin/pg_amcheck/po/sv.po
new file mode 100644
index 0000000..407b26d
--- /dev/null
+++ b/src/bin/pg_amcheck/po/sv.po
@@ -0,0 +1,520 @@
+# SWEDISH message translation file for pg_amcheck
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_amcheck (PostgreSQL) package.
+# Dennis Björklund <db@zigo.dhs.org>, 2021.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pg_amcheck (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-21 22:34+0000\n"
+"PO-Revision-Date: 2021-11-07 06:41+0100\n"
+"Last-Translator: Dennis Björklund <db@zigo.dhs.org>\n"
+"Language-Team: Swedish <pgsql-translators@postgresql.org>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../src/common/logging.c:259
+#, c-format
+msgid "fatal: "
+msgstr "fatalt: "
+
+#: ../../../src/common/logging.c:266
+#, c-format
+msgid "error: "
+msgstr "fel: "
+
+#: ../../../src/common/logging.c:273
+#, c-format
+msgid "warning: "
+msgstr "varning: "
+
+#: ../../fe_utils/cancel.c:189 ../../fe_utils/cancel.c:238
+msgid "Cancel request sent\n"
+msgstr "Förfrågan om avbrytning skickad\n"
+
+#: ../../fe_utils/cancel.c:190 ../../fe_utils/cancel.c:239
+msgid "Could not send cancel request: "
+msgstr "Kunde inte skicka förfrågan om avbrytning: "
+
+#: ../../fe_utils/connect_utils.c:92
+#, c-format
+msgid "could not connect to database %s: out of memory"
+msgstr "kunde inte ansluta till databas %s: slut på minne"
+
+#: ../../fe_utils/connect_utils.c:120
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: ../../fe_utils/query_utils.c:33 ../../fe_utils/query_utils.c:58
+#: pg_amcheck.c:1678 pg_amcheck.c:2126
+#, c-format
+msgid "query failed: %s"
+msgstr "fråga misslyckades: %s"
+
+#: ../../fe_utils/query_utils.c:34 ../../fe_utils/query_utils.c:59
+#: pg_amcheck.c:598 pg_amcheck.c:1128 pg_amcheck.c:1679 pg_amcheck.c:2127
+#, c-format
+msgid "query was: %s"
+msgstr "frågan var: %s"
+
+#: pg_amcheck.c:330
+#, c-format
+msgid "number of parallel jobs must be at least 1"
+msgstr "antalet parallella jobb måste vara minst 1"
+
+#: pg_amcheck.c:402
+#, c-format
+msgid "invalid argument for option %s"
+msgstr "ogiltigt argument för flaggan %s"
+
+#: pg_amcheck.c:411
+#, c-format
+msgid "invalid start block"
+msgstr "ogiltigt startblock"
+
+#: pg_amcheck.c:416
+#, c-format
+msgid "start block out of bounds"
+msgstr "startblocket utanför giltig gräns"
+
+#: pg_amcheck.c:426
+#, c-format
+msgid "invalid end block"
+msgstr "ogiltigt slutblock"
+
+#: pg_amcheck.c:431
+#, c-format
+msgid "end block out of bounds"
+msgstr "slutblocket utanför giltig gräns"
+
+#: pg_amcheck.c:456 pg_amcheck.c:482
+#, c-format
+msgid "Try \"%s --help\" for more information.\n"
+msgstr "Försök med \"%s --help\" för mer information.\n"
+
+#: pg_amcheck.c:464
+#, c-format
+msgid "end block precedes start block"
+msgstr "slutblocket kommer före startblocket"
+
+#: pg_amcheck.c:480
+#, c-format
+msgid "too many command-line arguments (first is \"%s\")"
+msgstr "för många kommandoradsargument (första är \"%s\")"
+
+#: pg_amcheck.c:501
+#, c-format
+msgid "cannot specify a database name with --all"
+msgstr "kan inte ange databasnamn tillsammans med --all"
+
+#: pg_amcheck.c:510
+#, c-format
+msgid "cannot specify both a database name and database patterns"
+msgstr "kan inte ange både ett databasnamn och ett databasmönster"
+
+#: pg_amcheck.c:540
+#, c-format
+msgid "no databases to check"
+msgstr "inga databaser att kontrollera"
+
+#: pg_amcheck.c:596
+#, c-format
+msgid "database \"%s\": %s"
+msgstr "databas \"%s\": %s"
+
+#: pg_amcheck.c:607
+#, c-format
+msgid "skipping database \"%s\": amcheck is not installed"
+msgstr "hoppar över databas \"%s\": amcheck är inte installerad"
+
+#: pg_amcheck.c:615
+#, c-format
+msgid "in database \"%s\": using amcheck version \"%s\" in schema \"%s\""
+msgstr "i databas \"%s\": använder amcheck version \"%s\" i schema \"%s\""
+
+#: pg_amcheck.c:637
+#, c-format
+msgid "no heap tables to check matching \"%s\""
+msgstr "finns inga heap-tabeller för att kontrollera matchning \"%s\""
+
+#: pg_amcheck.c:640
+#, c-format
+msgid "no btree indexes to check matching \"%s\""
+msgstr "finns inga btree-index för att kontrollera matching \"%s\""
+
+#: pg_amcheck.c:643
+#, c-format
+msgid "no relations to check in schemas matching \"%s\""
+msgstr "finns inga relationer att kontrollera i schemamatchning \"%s\""
+
+#: pg_amcheck.c:646
+#, c-format
+msgid "no relations to check matching \"%s\""
+msgstr "finns inga relations för att kontrollera matching \"%s\""
+
+#: pg_amcheck.c:674
+#, c-format
+msgid "no relations to check"
+msgstr "finns inga relationer att kontrollera"
+
+#: pg_amcheck.c:758
+#, c-format
+msgid "checking heap table \"%s.%s.%s\""
+msgstr "kontrollerar heap-tabell \"%s.%s.%s\""
+
+#: pg_amcheck.c:774
+#, c-format
+msgid "checking btree index \"%s.%s.%s\""
+msgstr "kontrollerar btree-index \"%s.%s.%s\""
+
+#: pg_amcheck.c:921
+#, c-format
+msgid "error sending command to database \"%s\": %s"
+msgstr "fel vid skickande av kommando till databas \"%s\": %s"
+
+#: pg_amcheck.c:924
+#, c-format
+msgid "command was: %s"
+msgstr "kommandot var: %s"
+
+#: pg_amcheck.c:1041
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"
+msgstr "heap-tabell \"%s.%s.%s\", block %s, offset %s, attribut %s:\n"
+
+#: pg_amcheck.c:1048
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s:\n"
+msgstr "heap-tabell \"%s.%s.%s\", block %s, offset %s:\n"
+
+#: pg_amcheck.c:1054
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s:\n"
+msgstr "heap-tabell \"%s.%s.%s\", block %s:\n"
+
+#: pg_amcheck.c:1059 pg_amcheck.c:1070
+#, c-format
+msgid "heap table \"%s.%s.%s\":\n"
+msgstr "heap-tabell \"%s.%s.%s\":\n"
+
+#: pg_amcheck.c:1074 pg_amcheck.c:1143
+#, c-format
+msgid "query was: %s\n"
+msgstr "frågan var: %s\n"
+
+#: pg_amcheck.c:1125
+#, c-format
+msgid "btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d"
+msgstr "btree-index \"%s.%s.%s\": kontrollfunktion för btree returnerade oväntat antal rader: %d"
+
+#: pg_amcheck.c:1129
+#, c-format
+msgid "Are %s's and amcheck's versions compatible?"
+msgstr "Är versionerna på %s och amcheck kompatibla?"
+
+#: pg_amcheck.c:1139
+#, c-format
+msgid "btree index \"%s.%s.%s\":\n"
+msgstr "btree-index \"%s.%s.%s\":\n"
+
+#: pg_amcheck.c:1164
+#, c-format
+msgid ""
+"%s checks objects in a PostgreSQL database for corruption.\n"
+"\n"
+msgstr ""
+"%s kontrollerar objekt i en PostgreSQL-database för att hitta korruption.\n"
+"\n"
+
+#: pg_amcheck.c:1165
+#, c-format
+msgid "Usage:\n"
+msgstr "Användning:\n"
+
+#: pg_amcheck.c:1166
+#, c-format
+msgid " %s [OPTION]... [DBNAME]\n"
+msgstr " %s [FLAGGA]... [DBNAMN]\n"
+
+#: pg_amcheck.c:1167
+#, c-format
+msgid ""
+"\n"
+"Target options:\n"
+msgstr ""
+"\n"
+"Flaggor för destinationen:\n"
+
+#: pg_amcheck.c:1168
+#, c-format
+msgid " -a, --all check all databases\n"
+msgstr " -a, --all kontrollera alla databaser\n"
+
+#: pg_amcheck.c:1169
+#, c-format
+msgid " -d, --database=PATTERN check matching database(s)\n"
+msgstr " -d, --database=MALL kontrollera matchande databas(er)\n"
+
+#: pg_amcheck.c:1170
+#, c-format
+msgid " -D, --exclude-database=PATTERN do NOT check matching database(s)\n"
+msgstr " -D, --exclude-database=MALL kontrollera INTE matchande databas(er)\n"
+
+#: pg_amcheck.c:1171
+#, c-format
+msgid " -i, --index=PATTERN check matching index(es)\n"
+msgstr " -i, --index=MALL kontrollera matchande index\n"
+
+#: pg_amcheck.c:1172
+#, c-format
+msgid " -I, --exclude-index=PATTERN do NOT check matching index(es)\n"
+msgstr " -I, --exclude-index=MALL kontrollera INTE matchande index\n"
+
+#: pg_amcheck.c:1173
+#, c-format
+msgid " -r, --relation=PATTERN check matching relation(s)\n"
+msgstr " -r, --relation=MALL kontrollera matchande relation(er)\n"
+
+#: pg_amcheck.c:1174
+#, c-format
+msgid " -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"
+msgstr " -R, --exclude-relation=MALL kontrollera INTE matchande relation(er)\n"
+
+#: pg_amcheck.c:1175
+#, c-format
+msgid " -s, --schema=PATTERN check matching schema(s)\n"
+msgstr " -s, --schema=MALL kontrollera matchande schema(n)\n"
+
+#: pg_amcheck.c:1176
+#, c-format
+msgid " -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"
+msgstr " -S, --exclude-schema=MALL kontrollera INTE matchande schema(n)\n"
+
+#: pg_amcheck.c:1177
+#, c-format
+msgid " -t, --table=PATTERN check matching table(s)\n"
+msgstr " -t, --table=MALL kontollera matchande tabell(er)\n"
+
+#: pg_amcheck.c:1178
+#, c-format
+msgid " -T, --exclude-table=PATTERN do NOT check matching table(s)\n"
+msgstr " -T, --exclude-table=MALL kontollera INTE matchande tabell(er)\n"
+
+#: pg_amcheck.c:1179
+#, c-format
+msgid " --no-dependent-indexes do NOT expand list of relations to include indexes\n"
+msgstr " --no-dependent-indexes expandera INTE listan med relationer för att inkludera index\n"
+
+#: pg_amcheck.c:1180
+#, c-format
+msgid " --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"
+msgstr " --no-dependent-toast expandera inte listan av relationer för att inkludera TOAST-tabeller\n"
+
+#: pg_amcheck.c:1181
+#, c-format
+msgid " --no-strict-names do NOT require patterns to match objects\n"
+msgstr " --no-strict-names kräv INTE mallar för matcha objekt\n"
+
+#: pg_amcheck.c:1182
+#, c-format
+msgid ""
+"\n"
+"Table checking options:\n"
+msgstr ""
+"\n"
+"Flaggor för kontroll av tabeller:\n"
+
+#: pg_amcheck.c:1183
+#, c-format
+msgid " --exclude-toast-pointers do NOT follow relation TOAST pointers\n"
+msgstr " --exclude-toast-pointers följ INTE relationers TOAST-pekare\n"
+
+#: pg_amcheck.c:1184
+#, c-format
+msgid " --on-error-stop stop checking at end of first corrupt page\n"
+msgstr " --on-error-stop sluta kontrollera efter första korrupta sidan\n"
+
+#: pg_amcheck.c:1185
+#, c-format
+msgid " --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"
+msgstr " --skip=FLAGGA kontrollera INTE block som är \"all-frozen\" eller \"all-visible\"\n"
+
+#: pg_amcheck.c:1186
+#, c-format
+msgid " --startblock=BLOCK begin checking table(s) at the given block number\n"
+msgstr " --startblock=BLOCK börja kontollera tabell(er) vid angivet blocknummer\n"
+
+#: pg_amcheck.c:1187
+#, c-format
+msgid " --endblock=BLOCK check table(s) only up to the given block number\n"
+msgstr " --endblock=BLOCK kontrollera tabell(er) fram till angivet blocknummer\n"
+
+#: pg_amcheck.c:1188
+#, c-format
+msgid ""
+"\n"
+"B-tree index checking options:\n"
+msgstr ""
+"\n"
+"Flaggor för kontroll av B-tree-index:\n"
+
+#: pg_amcheck.c:1189
+#, c-format
+msgid " --heapallindexed check that all heap tuples are found within indexes\n"
+msgstr " --heapallindexed kontrollera att alla heap-tupler hittas i index\n"
+
+#: pg_amcheck.c:1190
+#, c-format
+msgid " --parent-check check index parent/child relationships\n"
+msgstr " --parent-check kontrollera förhållandet mellan barn/förälder i index\n"
+
+#: pg_amcheck.c:1191
+#, c-format
+msgid " --rootdescend search from root page to refind tuples\n"
+msgstr " --rootdescend sök från root-sidan för att återfinna tupler\n"
+
+#: pg_amcheck.c:1192
+#, c-format
+msgid ""
+"\n"
+"Connection options:\n"
+msgstr ""
+"\n"
+"Flaggor för anslutning:\n"
+
+#: pg_amcheck.c:1193
+#, c-format
+msgid " -h, --host=HOSTNAME database server host or socket directory\n"
+msgstr " -h, --host=VÄRDNAMN databasens värdnamn eller socketkatalog\n"
+
+#: pg_amcheck.c:1194
+#, c-format
+msgid " -p, --port=PORT database server port\n"
+msgstr " -p, --port=PORT databasserverns port\n"
+
+#: pg_amcheck.c:1195
+#, c-format
+msgid " -U, --username=USERNAME user name to connect as\n"
+msgstr " -U, --username=ANVÄNDARE användarnamn att ansluta som\n"
+
+#: pg_amcheck.c:1196
+#, c-format
+msgid " -w, --no-password never prompt for password\n"
+msgstr " -w, --no-password fråga ej efter lösenord\n"
+
+#: pg_amcheck.c:1197
+#, c-format
+msgid " -W, --password force password prompt\n"
+msgstr " -W, --password tvinga fram lösenordsfråga\n"
+
+#: pg_amcheck.c:1198
+#, c-format
+msgid " --maintenance-db=DBNAME alternate maintenance database\n"
+msgstr " --maintenance-db=DBNAMN val av underhållsdatabas\n"
+
+#: pg_amcheck.c:1199
+#, c-format
+msgid ""
+"\n"
+"Other options:\n"
+msgstr ""
+"\n"
+"Andra flaggor:\n"
+
+#: pg_amcheck.c:1200
+#, c-format
+msgid " -e, --echo show the commands being sent to the server\n"
+msgstr " -e, --echo visa kommandon som skickas till servern\n"
+
+#: pg_amcheck.c:1201
+#, c-format
+msgid " -j, --jobs=NUM use this many concurrent connections to the server\n"
+msgstr " -j, --jobs=NUM antal samtidiga anslutningar till servern\n"
+
+#: pg_amcheck.c:1202
+#, c-format
+msgid " -P, --progress show progress information\n"
+msgstr " -P, --progress visa förloppsinformation\n"
+
+#: pg_amcheck.c:1203
+#, c-format
+msgid " -v, --verbose write a lot of output\n"
+msgstr " -v, --verbose skriv massor med utdata\n"
+
+#: pg_amcheck.c:1204
+#, c-format
+msgid " -V, --version output version information, then exit\n"
+msgstr " -V, --version visa versionsinformation, avsluta sedan\n"
+
+#: pg_amcheck.c:1205
+#, c-format
+msgid " --install-missing install missing extensions\n"
+msgstr " --install-missing installera utökningar som saknas\n"
+
+#: pg_amcheck.c:1206
+#, c-format
+msgid " -?, --help show this help, then exit\n"
+msgstr " -?, --help visa denna hjälp, avsluta sedan\n"
+
+#: pg_amcheck.c:1208
+#, c-format
+msgid ""
+"\n"
+"Report bugs to <%s>.\n"
+msgstr ""
+"\n"
+"Rapportera fel till <%s>.\n"
+
+#: pg_amcheck.c:1209
+#, c-format
+msgid "%s home page: <%s>\n"
+msgstr "hemsida för %s: <%s>\n"
+
+#: pg_amcheck.c:1267
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"
+msgstr "%*s/%s relationer (%d%%), %*s/%s sidor (%d%%) %*s"
+
+#: pg_amcheck.c:1278
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"
+msgstr "%*s/%s relationer (%d%%), %*s/%s sidor (%d%%) (%s%-*.*s)"
+
+#: pg_amcheck.c:1293
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%)"
+msgstr "%*s/%s relationer (%d%%), %*s/%s sidor (%d%%)"
+
+#: pg_amcheck.c:1352 pg_amcheck.c:1385
+#, c-format
+msgid "improper qualified name (too many dotted names): %s"
+msgstr "ej korrekt kvalificerat namn (för många namn med punkt): %s"
+
+#: pg_amcheck.c:1430
+#, c-format
+msgid "improper relation name (too many dotted names): %s"
+msgstr "ej korrekt relationsnamn (för många namn med punkt): %s"
+
+#: pg_amcheck.c:1583 pg_amcheck.c:1725
+#, c-format
+msgid "including database \"%s\""
+msgstr "inkludera databas \"%s\""
+
+#: pg_amcheck.c:1705
+#, c-format
+msgid "internal error: received unexpected database pattern_id %d"
+msgstr "internt fel: tog emot oväntat pattern_id %d för databas"
+
+#: pg_amcheck.c:1709
+#, c-format
+msgid "no connectable databases to check matching \"%s\""
+msgstr "finns inga anslutningsbara databaser att kontrollera som matchar \"%s\""
+
+#: pg_amcheck.c:2168
+#, c-format
+msgid "internal error: received unexpected relation pattern_id %d"
+msgstr "internt fel: tog emot oväntat pattern_id %d för relation"
diff --git a/src/bin/pg_amcheck/po/uk.po b/src/bin/pg_amcheck/po/uk.po
new file mode 100644
index 0000000..4cd6bac
--- /dev/null
+++ b/src/bin/pg_amcheck/po/uk.po
@@ -0,0 +1,506 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: postgresql\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-06-16 04:04+0000\n"
+"PO-Revision-Date: 2022-06-19 10:10\n"
+"Last-Translator: \n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+"X-Crowdin-Project: postgresql\n"
+"X-Crowdin-Project-ID: 324573\n"
+"X-Crowdin-Language: uk\n"
+"X-Crowdin-File: /REL_14_STABLE/pg_amcheck.pot\n"
+"X-Crowdin-File-ID: 786\n"
+
+#: ../../../src/common/logging.c:259
+#, c-format
+msgid "fatal: "
+msgstr "збій: "
+
+#: ../../../src/common/logging.c:266
+#, c-format
+msgid "error: "
+msgstr "помилка: "
+
+#: ../../../src/common/logging.c:273
+#, c-format
+msgid "warning: "
+msgstr "попередження: "
+
+#: ../../fe_utils/cancel.c:189 ../../fe_utils/cancel.c:238
+msgid "Cancel request sent\n"
+msgstr "Запит на скасування відправлений\n"
+
+#: ../../fe_utils/cancel.c:190 ../../fe_utils/cancel.c:239
+msgid "Could not send cancel request: "
+msgstr "Не вдалося надіслати запит на скасування: "
+
+#: ../../fe_utils/connect_utils.c:92
+#, c-format
+msgid "could not connect to database %s: out of memory"
+msgstr "не можливо під'єднатися до бази даних %s: не вистачає пам'яті"
+
+#: ../../fe_utils/connect_utils.c:120
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: ../../fe_utils/query_utils.c:33 ../../fe_utils/query_utils.c:58
+#: pg_amcheck.c:1680 pg_amcheck.c:2128
+#, c-format
+msgid "query failed: %s"
+msgstr "запит не вдався: %s"
+
+#: ../../fe_utils/query_utils.c:34 ../../fe_utils/query_utils.c:59
+#: pg_amcheck.c:598 pg_amcheck.c:1130 pg_amcheck.c:1681 pg_amcheck.c:2129
+#, c-format
+msgid "query was: %s"
+msgstr "запит був: %s"
+
+#: pg_amcheck.c:330
+#, c-format
+msgid "number of parallel jobs must be at least 1"
+msgstr "число паралельних завдань повинно бути не менше 1"
+
+#: pg_amcheck.c:402
+#, c-format
+msgid "invalid argument for option %s"
+msgstr "неприпустимий аргумент для параметру %s"
+
+#: pg_amcheck.c:411
+#, c-format
+msgid "invalid start block"
+msgstr "неприпустимий початковий блок"
+
+#: pg_amcheck.c:416
+#, c-format
+msgid "start block out of bounds"
+msgstr "початковий блок поза межами"
+
+#: pg_amcheck.c:426
+#, c-format
+msgid "invalid end block"
+msgstr "неприпустимий кінцевий блок"
+
+#: pg_amcheck.c:431
+#, c-format
+msgid "end block out of bounds"
+msgstr "кінцевий блок поза межами"
+
+#: pg_amcheck.c:456 pg_amcheck.c:482
+#, c-format
+msgid "Try \"%s --help\" for more information.\n"
+msgstr "Спробуйте \"%s --help\" для додаткової інформації.\n"
+
+#: pg_amcheck.c:464
+#, c-format
+msgid "end block precedes start block"
+msgstr "кінцевий блок передує початковому блоку"
+
+#: pg_amcheck.c:480
+#, c-format
+msgid "too many command-line arguments (first is \"%s\")"
+msgstr "забагато аргументів у командному рядку (перший \"%s\")"
+
+#: pg_amcheck.c:501
+#, c-format
+msgid "cannot specify a database name with --all"
+msgstr "не можна вказати назву бази даних з --all"
+
+#: pg_amcheck.c:510
+#, c-format
+msgid "cannot specify both a database name and database patterns"
+msgstr "не можна вказати одночасно ім'я бази даних і шаблони бази даних"
+
+#: pg_amcheck.c:540
+#, c-format
+msgid "no databases to check"
+msgstr "немає баз даних для перевірки"
+
+#: pg_amcheck.c:596
+#, c-format
+msgid "database \"%s\": %s"
+msgstr "база даних \"%s\": %s"
+
+#: pg_amcheck.c:607
+#, c-format
+msgid "skipping database \"%s\": amcheck is not installed"
+msgstr "пропуск бази даних \"%s\": amcheck не встановлено"
+
+#: pg_amcheck.c:615
+#, c-format
+msgid "in database \"%s\": using amcheck version \"%s\" in schema \"%s\""
+msgstr "у базі даних \"%s\": використовується amcheck версії \"%s\" у схемі \"%s\""
+
+#: pg_amcheck.c:637
+#, c-format
+msgid "no heap tables to check matching \"%s\""
+msgstr "немає таблиць в динамічній пам'яті для перевірки відповідності \"%s\""
+
+#: pg_amcheck.c:640
+#, c-format
+msgid "no btree indexes to check matching \"%s\""
+msgstr "немає індексів btree для перевірки відповідності \"%s\""
+
+#: pg_amcheck.c:643
+#, c-format
+msgid "no relations to check in schemas matching \"%s\""
+msgstr "немає відношень для перевірки в схемах, відповідних \"%s\""
+
+#: pg_amcheck.c:646
+#, c-format
+msgid "no relations to check matching \"%s\""
+msgstr "немає відношень для перевірки відповідності \"%s\""
+
+#: pg_amcheck.c:674
+#, c-format
+msgid "no relations to check"
+msgstr "немає зв'язків для перевірки"
+
+#: pg_amcheck.c:758
+#, c-format
+msgid "checking heap table \"%s.%s.%s\""
+msgstr "перевірка таблиць динамічної пам'яті \"%s.%s.%s\""
+
+#: pg_amcheck.c:774
+#, c-format
+msgid "checking btree index \"%s.%s.%s\""
+msgstr "перевірка індексу btree \"%s.%s.%s\""
+
+#: pg_amcheck.c:921
+#, c-format
+msgid "error sending command to database \"%s\": %s"
+msgstr "помилка надсилання команди до бази даних \"%s: %s"
+
+#: pg_amcheck.c:924
+#, c-format
+msgid "command was: %s"
+msgstr "команда була: %s"
+
+#: pg_amcheck.c:1043
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"
+msgstr "таблиця динамічної пам'яті \"%s.%s.%s\", блок %s, зсув %s, атрибут %s:\n"
+
+#: pg_amcheck.c:1050
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s:\n"
+msgstr "таблиця динамічної пам'яті \"%s.%s.%s\", блок %s, зсув %s:\n"
+
+#: pg_amcheck.c:1056
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s:\n"
+msgstr "таблиця динамічної пам'яті \"%s.%s.%s\", блок %s:\n"
+
+#: pg_amcheck.c:1061 pg_amcheck.c:1072
+#, c-format
+msgid "heap table \"%s.%s.%s\":\n"
+msgstr "таблиця динамічної пам'яті \"%s.%s.%s\":\n"
+
+#: pg_amcheck.c:1076 pg_amcheck.c:1145
+#, c-format
+msgid "query was: %s\n"
+msgstr "запит був: %s\n"
+
+#: pg_amcheck.c:1127
+#, c-format
+msgid "btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d"
+msgstr "індекс btree \"%s.%s.%s\": функція перевірки btree повернула неочікувану кількість рядків: %d"
+
+#: pg_amcheck.c:1131
+#, c-format
+msgid "Are %s's and amcheck's versions compatible?"
+msgstr "Чи сумісні версії %s і amcheck?"
+
+#: pg_amcheck.c:1141
+#, c-format
+msgid "btree index \"%s.%s.%s\":\n"
+msgstr "індекс btree \"%s.%s.%s\":\n"
+
+#: pg_amcheck.c:1166
+#, c-format
+msgid "%s checks objects in a PostgreSQL database for corruption.\n\n"
+msgstr "%s перевіряє об'єкти бази даних PostgreSQL на пошкодження.\n\n"
+
+#: pg_amcheck.c:1167
+#, c-format
+msgid "Usage:\n"
+msgstr "Використання:\n"
+
+#: pg_amcheck.c:1168
+#, c-format
+msgid " %s [OPTION]... [DBNAME]\n"
+msgstr " %s [OPTION]... [DBNAME]\n"
+
+#: pg_amcheck.c:1169
+#, c-format
+msgid "\n"
+"Target options:\n"
+msgstr "\n"
+"Цільові параметри:\n"
+
+#: pg_amcheck.c:1170
+#, c-format
+msgid " -a, --all check all databases\n"
+msgstr " -a, --all перевірити всі бази даних\n"
+
+#: pg_amcheck.c:1171
+#, c-format
+msgid " -d, --database=PATTERN check matching database(s)\n"
+msgstr " -d, --database=PATTERN перевірити відповідні бази даних\n"
+
+#: pg_amcheck.c:1172
+#, c-format
+msgid " -D, --exclude-database=PATTERN do NOT check matching database(s)\n"
+msgstr " -D, --exclude-database=PATTERN НЕ перевіряти відповідні бази даних\n"
+
+#: pg_amcheck.c:1173
+#, c-format
+msgid " -i, --index=PATTERN check matching index(es)\n"
+msgstr " -i, --index=PATTERN перевірити відповідні індекси\n"
+
+#: pg_amcheck.c:1174
+#, c-format
+msgid " -I, --exclude-index=PATTERN do NOT check matching index(es)\n"
+msgstr " -I, --exclude-index=PATTERN НЕ перевіряти відповідні індекси\n"
+
+#: pg_amcheck.c:1175
+#, c-format
+msgid " -r, --relation=PATTERN check matching relation(s)\n"
+msgstr " -r, --relation=PATTERN перевірити відповідні відношення\n"
+
+#: pg_amcheck.c:1176
+#, c-format
+msgid " -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"
+msgstr " -R, --exclude-relation=PATTERN НЕ перевіряти відповідні відношення\n"
+
+#: pg_amcheck.c:1177
+#, c-format
+msgid " -s, --schema=PATTERN check matching schema(s)\n"
+msgstr " -s, --schema=PATTERN перевірити відповідні схеми\n"
+
+#: pg_amcheck.c:1178
+#, c-format
+msgid " -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"
+msgstr " -S, --exclude-schema=PATTERN НЕ перевіряти відповідні схеми\n"
+
+#: pg_amcheck.c:1179
+#, c-format
+msgid " -t, --table=PATTERN check matching table(s)\n"
+msgstr " -t, --table=PATTERN перевірити відповідні таблиці\n"
+
+#: pg_amcheck.c:1180
+#, c-format
+msgid " -T, --exclude-table=PATTERN do NOT check matching table(s)\n"
+msgstr " -T, --exclude-table=PATTERN НЕ перевіряти відповідні таблиці\n"
+
+#: pg_amcheck.c:1181
+#, c-format
+msgid " --no-dependent-indexes do NOT expand list of relations to include indexes\n"
+msgstr " --no-dependent-indexes НЕ розширювати список відносин, щоб включити індекси\n"
+
+#: pg_amcheck.c:1182
+#, c-format
+msgid " --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"
+msgstr " --no-dependent-toast НЕ розширювати список відносин, щоб включити таблиці TOAST\n"
+
+#: pg_amcheck.c:1183
+#, c-format
+msgid " --no-strict-names do NOT require patterns to match objects\n"
+msgstr " --no-strict-names НЕ вимагати відповідності шаблонів об'єктам\n"
+
+#: pg_amcheck.c:1184
+#, c-format
+msgid "\n"
+"Table checking options:\n"
+msgstr "\n"
+"Параметри перевірки таблиць:\n"
+
+#: pg_amcheck.c:1185
+#, c-format
+msgid " --exclude-toast-pointers do NOT follow relation TOAST pointers\n"
+msgstr " --exclude-toast-pointers НЕ слідувати покажчикам відношень TOAST\n"
+
+#: pg_amcheck.c:1186
+#, c-format
+msgid " --on-error-stop stop checking at end of first corrupt page\n"
+msgstr " --on-error-stop зупинити перевірку наприкінці першої пошкодженої сторінки\n"
+
+#: pg_amcheck.c:1187
+#, c-format
+msgid " --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"
+msgstr " --skip=OPTION НЕ перевіряти \"всі заморожені\" або \"всі видимі\" блоки\n"
+
+#: pg_amcheck.c:1188
+#, c-format
+msgid " --startblock=BLOCK begin checking table(s) at the given block number\n"
+msgstr " --startblock=BLOCK почати перевірку таблиць за поданим номером блоку\n"
+
+#: pg_amcheck.c:1189
+#, c-format
+msgid " --endblock=BLOCK check table(s) only up to the given block number\n"
+msgstr " --endblock=BLOCK перевіряти таблиці лише до поданого номеру блоку\n"
+
+#: pg_amcheck.c:1190
+#, c-format
+msgid "\n"
+"B-tree index checking options:\n"
+msgstr "\n"
+"Параметри перевірки індексів B-tree:\n"
+
+#: pg_amcheck.c:1191
+#, c-format
+msgid " --heapallindexed check that all heap tuples are found within indexes\n"
+msgstr " --heapallindexed перевірити чи всі кортежі динамічної пам'яті містяться в індексах\n"
+
+#: pg_amcheck.c:1192
+#, c-format
+msgid " --parent-check check index parent/child relationships\n"
+msgstr " --parent-check перевірити індекс батьківських/дочірніх відносин\n"
+
+#: pg_amcheck.c:1193
+#, c-format
+msgid " --rootdescend search from root page to refind tuples\n"
+msgstr " --rootdescend шукати з кореневої сторінки, для повторного пошуку кортежів\n"
+
+#: pg_amcheck.c:1194
+#, c-format
+msgid "\n"
+"Connection options:\n"
+msgstr "\n"
+"Налаштування з'єднання:\n"
+
+#: pg_amcheck.c:1195
+#, c-format
+msgid " -h, --host=HOSTNAME database server host or socket directory\n"
+msgstr " -h, --host=HOSTNAME хост сервера бази даних або каталог сокетів\n"
+
+#: pg_amcheck.c:1196
+#, c-format
+msgid " -p, --port=PORT database server port\n"
+msgstr " -p, --port=PORT порт серверу бази даних\n"
+
+#: pg_amcheck.c:1197
+#, c-format
+msgid " -U, --username=USERNAME user name to connect as\n"
+msgstr " -U, --username=USERNAME ім'я користувача для з'єднання з сервером\n"
+
+#: pg_amcheck.c:1198
+#, c-format
+msgid " -w, --no-password never prompt for password\n"
+msgstr " -w, --no-password ніколи не запитувати пароль\n"
+
+#: pg_amcheck.c:1199
+#, c-format
+msgid " -W, --password force password prompt\n"
+msgstr " -W, --password примусовий запит пароля\n"
+
+#: pg_amcheck.c:1200
+#, c-format
+msgid " --maintenance-db=DBNAME alternate maintenance database\n"
+msgstr " --maintenance-db=DBNAME база даних альтернативного обслуговування\n"
+
+#: pg_amcheck.c:1201
+#, c-format
+msgid "\n"
+"Other options:\n"
+msgstr "\n"
+"Інші параметри:\n"
+
+#: pg_amcheck.c:1202
+#, c-format
+msgid " -e, --echo show the commands being sent to the server\n"
+msgstr " -e, --echo показати команди, надіслані серверу\n"
+
+#: pg_amcheck.c:1203
+#, c-format
+msgid " -j, --jobs=NUM use this many concurrent connections to the server\n"
+msgstr " -j, --jobs=NUM використати цю кількість одночасних з'єднань з сервером\n"
+
+#: pg_amcheck.c:1204
+#, c-format
+msgid " -P, --progress show progress information\n"
+msgstr " -P, --progress показати інформацію про прогрес\n"
+
+#: pg_amcheck.c:1205
+#, c-format
+msgid " -v, --verbose write a lot of output\n"
+msgstr " -v, --verbose виводити багато інформації\n"
+
+#: pg_amcheck.c:1206
+#, c-format
+msgid " -V, --version output version information, then exit\n"
+msgstr " -V, --version вивести інформацію про версію і вийти\n"
+
+#: pg_amcheck.c:1207
+#, c-format
+msgid " --install-missing install missing extensions\n"
+msgstr " --install-missing встановити відсутні розширення\n"
+
+#: pg_amcheck.c:1208
+#, c-format
+msgid " -?, --help show this help, then exit\n"
+msgstr " -?, --help показати цю справку, потім вийти\n"
+
+#: pg_amcheck.c:1210
+#, c-format
+msgid "\n"
+"Report bugs to <%s>.\n"
+msgstr "\n"
+"Повідомляти про помилки на <%s>.\n"
+
+#: pg_amcheck.c:1211
+#, c-format
+msgid "%s home page: <%s>\n"
+msgstr "Домашня сторінка %s: <%s>\n"
+
+#: pg_amcheck.c:1269
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"
+msgstr "%*s/%s відношень (%d%%), %*s/%s сторінок (%d%%) %*s"
+
+#: pg_amcheck.c:1280
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"
+msgstr "%*s/%s відношень (%d%%), %*s/%s сторінок (%d%%) (%s%-*.*s)"
+
+#: pg_amcheck.c:1295
+#, c-format
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%)"
+msgstr "%*s/%s відношень (%d%%), %*s/%s сторінок (%d%%)"
+
+#: pg_amcheck.c:1354 pg_amcheck.c:1387
+#, c-format
+msgid "improper qualified name (too many dotted names): %s"
+msgstr "неправильне повне ім'я (забагато компонентів): %s"
+
+#: pg_amcheck.c:1432
+#, c-format
+msgid "improper relation name (too many dotted names): %s"
+msgstr "неправильне ім'я зв'язку (забагато компонентів): %s"
+
+#: pg_amcheck.c:1585 pg_amcheck.c:1727
+#, c-format
+msgid "including database \"%s\""
+msgstr "включаючи базу даних \"%s\""
+
+#: pg_amcheck.c:1707
+#, c-format
+msgid "internal error: received unexpected database pattern_id %d"
+msgstr "внутрішня помилка: отримано неочікувану помилку шаблону бази даних %d"
+
+#: pg_amcheck.c:1711
+#, c-format
+msgid "no connectable databases to check matching \"%s\""
+msgstr "немає бази даних для підключення, щоб перевірити відповідність\"%s\""
+
+#: pg_amcheck.c:2170
+#, c-format
+msgid "internal error: received unexpected relation pattern_id %d"
+msgstr "внутрішня помилка: отримано неочікувану помилку шаблону відношення %d"
+
diff --git a/src/bin/pg_amcheck/po/zh_CN.po b/src/bin/pg_amcheck/po/zh_CN.po
new file mode 100644
index 0000000..5ca9f01
--- /dev/null
+++ b/src/bin/pg_amcheck/po/zh_CN.po
@@ -0,0 +1,500 @@
+# LANGUAGE message translation file for pg_amcheck
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_amcheck (PostgreSQL) package.
+# Jie Zhang <zhangjie2@fujitsu.com>, 2021.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pg_amcheck (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2021-08-14 05:48+0000\n"
+"PO-Revision-Date: 2021-08-15 18:00+0800\n"
+"Last-Translator: Jie Zhang <zhangjie2@fujitsu.com>\n"
+"Language-Team: Chinese (Simplified) <zhangjie2@fujitsu.com>\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../src/common/logging.c:259
+#, c-format
+msgid "fatal: "
+msgstr "致命的:"
+
+#: ../../../src/common/logging.c:266
+#, c-format
+msgid "error: "
+msgstr "错误: "
+
+#: ../../../src/common/logging.c:273
+#, c-format
+msgid "warning: "
+msgstr "警告: "
+
+#: ../../fe_utils/cancel.c:161 ../../fe_utils/cancel.c:206
+msgid "Cancel request sent\n"
+msgstr "取消发送的请求\n"
+
+#: ../../fe_utils/cancel.c:165 ../../fe_utils/cancel.c:210
+msgid "Could not send cancel request: "
+msgstr "无法发送取消请求: "
+
+#: ../../fe_utils/connect_utils.c:92
+#, c-format
+msgid "could not connect to database %s: out of memory"
+msgstr "无法连接到数据库 %s:内存不足"
+
+#: ../../fe_utils/connect_utils.c:120
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: ../../fe_utils/query_utils.c:33 ../../fe_utils/query_utils.c:58
+#: pg_amcheck.c:1646 pg_amcheck.c:2085
+#, c-format
+msgid "query failed: %s"
+msgstr "查询失败: %s"
+
+#: ../../fe_utils/query_utils.c:34 ../../fe_utils/query_utils.c:59
+#: pg_amcheck.c:597 pg_amcheck.c:1116 pg_amcheck.c:1647 pg_amcheck.c:2086
+#, c-format
+msgid "query was: %s"
+msgstr "查询是: %s"
+
+#: pg_amcheck.c:332
+#, c-format
+msgid "number of parallel jobs must be at least 1"
+msgstr "并行工作的数量必须至少为1"
+
+#: pg_amcheck.c:405
+#, c-format
+msgid "invalid argument for option %s"
+msgstr "选项%s的参数无效"
+
+#: pg_amcheck.c:413
+#, c-format
+msgid "invalid start block"
+msgstr "起始块无效"
+
+#: pg_amcheck.c:418
+#, c-format
+msgid "start block out of bounds"
+msgstr "起始块超出范围"
+
+#: pg_amcheck.c:426
+#, c-format
+msgid "invalid end block"
+msgstr "无效的结束块"
+
+#: pg_amcheck.c:431
+#, c-format
+msgid "end block out of bounds"
+msgstr "结束块超出范围"
+
+#: pg_amcheck.c:455 pg_amcheck.c:481
+#, c-format
+msgid "Try \"%s --help\" for more information.\n"
+msgstr "请用 \"%s --help\" 获取更多的信息.\n"
+
+#: pg_amcheck.c:463
+#, c-format
+msgid "end block precedes start block"
+msgstr "结束块在开始块之前"
+
+#: pg_amcheck.c:479
+#, c-format
+msgid "too many command-line arguments (first is \"%s\")"
+msgstr "命令行参数太多 (第一个是 \"%s\")"
+
+#: pg_amcheck.c:500
+#, c-format
+msgid "cannot specify a database name with --all"
+msgstr "无法使用--all指定数据库名称"
+
+#: pg_amcheck.c:509
+#, c-format
+msgid "cannot specify both a database name and database patterns"
+msgstr "不能同时指定数据库名称和数据库模式"
+
+#: pg_amcheck.c:539
+#, c-format
+msgid "no databases to check"
+msgstr "没有要检查的数据库"
+
+#: pg_amcheck.c:595
+#, c-format
+msgid "database \"%s\": %s"
+msgstr "数据库 \"%s\": %s"
+
+#: pg_amcheck.c:606
+#, c-format
+msgid "skipping database \"%s\": amcheck is not installed"
+msgstr "正在跳过数据库\"%s\":未安装amcheck"
+
+#: pg_amcheck.c:614
+#, c-format
+msgid "in database \"%s\": using amcheck version \"%s\" in schema \"%s\""
+msgstr "在数据库\"%1$s\"中:在模式\"%3$s\"中使用amcheck版本\"%2$s\""
+
+#: pg_amcheck.c:638
+msgid "no heap tables to check matching \"%s\""
+msgstr "没有要检查匹配\"%s\"的堆表"
+
+#: pg_amcheck.c:641
+#, c-format
+msgid "no btree indexes to check matching \"%s\""
+msgstr "没有要检查匹配\"%s\"的B树索引"
+
+#: pg_amcheck.c:644
+msgid "no relations to check in schemas matching \"%s\""
+msgstr "在模式中没有要检查匹配\"%s\"的关系"
+
+#: pg_amcheck.c:647
+msgid "no relations to check matching \"%s\""
+msgstr "没有要检查匹配\"%s\"的关系"
+
+#: pg_amcheck.c:676
+#, c-format
+msgid "no relations to check"
+msgstr "没有要检查的关系"
+
+#: pg_amcheck.c:762
+msgid "checking heap table \"%s.%s.%s\""
+msgstr "正在检查堆表\"%s.%s.%s\""
+
+#: pg_amcheck.c:778
+msgid "checking btree index \"%s.%s.%s\""
+msgstr "检查B树索引\"%s.%s.%s\""
+
+#: pg_amcheck.c:911
+#, c-format
+msgid "error sending command to database \"%s\": %s"
+msgstr "向数据库\"%s\"发送命令时出错: %s"
+
+#: pg_amcheck.c:914
+#, c-format
+msgid "command was: %s"
+msgstr "命令是: %s"
+
+#: pg_amcheck.c:1031
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s, attribute %s:\n"
+msgstr "堆表\"%s.%s.%s\",块%s,偏移量%s,属性%s:\n"
+
+#: pg_amcheck.c:1038
+#, c-format
+msgid "heap table \"%s.%s.%s\", block %s, offset %s:\n"
+msgstr "堆表\"%s.%s.%s\",块%s,偏移量%s:\n"
+
+#: pg_amcheck.c:1044
+msgid "heap table \"%s.%s.%s\", block %s:\n"
+msgstr "堆表\"%s.%s.%s\",块%s:\n"
+
+#: pg_amcheck.c:1049 pg_amcheck.c:1060
+msgid "heap table \"%s.%s.%s\":\n"
+msgstr "堆表\"%s.%s.%s\":\n"
+
+#: pg_amcheck.c:1064 pg_amcheck.c:1131
+msgid "query was: %s\n"
+msgstr "查询是: %s\n"
+
+#: pg_amcheck.c:1113
+msgid "btree index \"%s.%s.%s\": btree checking function returned unexpected number of rows: %d"
+msgstr "B树索引\"%s.%s.%s\":B树检查函数返回了意外的行数: %d"
+
+#: pg_amcheck.c:1117
+#, c-format
+msgid "Are %s's and amcheck's versions compatible?"
+msgstr "%s和amcheck的版本兼容吗?"
+
+#: pg_amcheck.c:1127
+msgid "btree index \"%s.%s.%s\":\n"
+msgstr "B树索引\"%s.%s.%s\":\n"
+
+#: pg_amcheck.c:1152
+#, c-format
+msgid ""
+"%s checks objects in a PostgreSQL database for corruption.\n"
+"\n"
+msgstr ""
+"%s检查PostgreSQL数据库中的对象是否损坏.\n"
+"\n"
+
+#: pg_amcheck.c:1153
+#, c-format
+msgid "Usage:\n"
+msgstr "使用方法:\n"
+
+#: pg_amcheck.c:1154
+#, c-format
+msgid " %s [OPTION]... [DBNAME]\n"
+msgstr " %s [选项]... [数据库名字]\n"
+
+#: pg_amcheck.c:1155
+#, c-format
+msgid ""
+"\n"
+"Target options:\n"
+msgstr ""
+"\n"
+"目标选项:\n"
+
+#: pg_amcheck.c:1156
+#, c-format
+msgid " -a, --all check all databases\n"
+msgstr " -a, --all 检查所有数据库\n"
+
+#: pg_amcheck.c:1157
+#, c-format
+msgid " -d, --database=PATTERN check matching database(s)\n"
+msgstr " -d, --database=PATTERN 检查匹配的数据库\n"
+
+#: pg_amcheck.c:1158
+#, c-format
+msgid " -D, --exclude-database=PATTERN do NOT check matching database(s)\n"
+msgstr " -D, --exclude-database=PATTERN 不检查匹配的数据库\n"
+
+#: pg_amcheck.c:1159
+#, c-format
+msgid " -i, --index=PATTERN check matching index(es)\n"
+msgstr " -i, --index=PATTERN 检查匹配的索引\n"
+
+#: pg_amcheck.c:1160
+#, c-format
+msgid " -I, --exclude-index=PATTERN do NOT check matching index(es)\n"
+msgstr " -I, --exclude-index=PATTERN 不检查匹配的索引\n"
+
+#: pg_amcheck.c:1161
+#, c-format
+msgid " -r, --relation=PATTERN check matching relation(s)\n"
+msgstr " -r, --relation=PATTERN 检查匹配的关系\n"
+
+#: pg_amcheck.c:1162
+#, c-format
+msgid " -R, --exclude-relation=PATTERN do NOT check matching relation(s)\n"
+msgstr " -R, --exclude-relation=PATTERN 不检查匹配的关系\n"
+
+#: pg_amcheck.c:1163
+#, c-format
+msgid " -s, --schema=PATTERN check matching schema(s)\n"
+msgstr " -s, --schema=PATTERN 检查匹配的模式\n"
+
+#: pg_amcheck.c:1164
+#, c-format
+msgid " -S, --exclude-schema=PATTERN do NOT check matching schema(s)\n"
+msgstr " -S, --exclude-schema=PATTERN 不检查匹配模式\n"
+
+#: pg_amcheck.c:1165
+#, c-format
+msgid " -t, --table=PATTERN check matching table(s)\n"
+msgstr " -t, --table=PATTERN 检查匹配的表\n"
+
+#: pg_amcheck.c:1166
+#, c-format
+msgid " -T, --exclude-table=PATTERN do NOT check matching table(s)\n"
+msgstr " -T, --exclude-table=PATTERN 不检查匹的配表\n"
+
+#: pg_amcheck.c:1167
+#, c-format
+msgid " --no-dependent-indexes do NOT expand list of relations to include indexes\n"
+msgstr " --no-dependent-indexes 不要展开关系列表以包含索引\n"
+
+#: pg_amcheck.c:1168
+#, c-format
+msgid " --no-dependent-toast do NOT expand list of relations to include TOAST tables\n"
+msgstr " --no-dependent-toast 不要展开关系列表以包括TOAST表\n"
+
+#: pg_amcheck.c:1169
+#, c-format
+msgid " --no-strict-names do NOT require patterns to match objects\n"
+msgstr " --no-strict-names 不需要模式来匹配对象\n"
+
+#: pg_amcheck.c:1170
+#, c-format
+msgid ""
+"\n"
+"Table checking options:\n"
+msgstr ""
+"\n"
+"表检查选项:\n"
+
+#: pg_amcheck.c:1171
+#, c-format
+msgid " --exclude-toast-pointers do NOT follow relation TOAST pointers\n"
+msgstr " --exclude-toast-pointers 不要遵循关系TOAST指示\n"
+
+#: pg_amcheck.c:1172
+#, c-format
+msgid " --on-error-stop stop checking at end of first corrupt page\n"
+msgstr " --on-error-stop 在第一个损坏页的末尾停止检查\n"
+
+#: pg_amcheck.c:1173
+#, c-format
+msgid " --skip=OPTION do NOT check \"all-frozen\" or \"all-visible\" blocks\n"
+msgstr " --skip=OPTION 不要检查\"all-frozen\"或\"all-visible\"块\n"
+
+#: pg_amcheck.c:1174
+#, c-format
+msgid " --startblock=BLOCK begin checking table(s) at the given block number\n"
+msgstr " --startblock=BLOCK 在给定的块编号处开始检查表\n"
+
+#: pg_amcheck.c:1175
+#, c-format
+msgid " --endblock=BLOCK check table(s) only up to the given block number\n"
+msgstr " --endblock=BLOCK 检查表仅限于给定的块编号\n"
+
+#: pg_amcheck.c:1176
+#, c-format
+msgid ""
+"\n"
+"B-tree index checking options:\n"
+msgstr ""
+"\n"
+"B树索引检查选项:\n"
+
+#: pg_amcheck.c:1177
+msgid " --heapallindexed check that all heap tuples are found within indexes\n"
+msgstr " --heapallindexed 检查是否在索引中找到所有堆元组\n"
+
+#: pg_amcheck.c:1178
+#, c-format
+msgid " --parent-check check index parent/child relationships\n"
+msgstr " --parent-check 检查索引父/子关系\n"
+
+#: pg_amcheck.c:1179
+#, c-format
+msgid " --rootdescend search from root page to refind tuples\n"
+msgstr " --rootdescend 从根页搜索到重新填充元组\n"
+
+#: pg_amcheck.c:1180
+#, c-format
+msgid ""
+"\n"
+"Connection options:\n"
+msgstr ""
+"\n"
+"联接选项:\n"
+
+#: pg_amcheck.c:1181
+#, c-format
+msgid " -h, --host=HOSTNAME database server host or socket directory\n"
+msgstr " -h, --host=HOSTNAME 数据库服务器主机或套接字目录\n"
+
+#: pg_amcheck.c:1182
+#, c-format
+msgid " -p, --port=PORT database server port\n"
+msgstr " -p, --port=PORT 数据库服务器端口\n"
+
+#: pg_amcheck.c:1183
+#, c-format
+msgid " -U, --username=USERNAME user name to connect as\n"
+msgstr " -U, --username=USERNAME 要连接的用户名\n"
+
+#: pg_amcheck.c:1184
+#, c-format
+msgid " -w, --no-password never prompt for password\n"
+msgstr " -w, --no-password 从不提示输入密码\n"
+
+#: pg_amcheck.c:1185
+#, c-format
+msgid " -W, --password force password prompt\n"
+msgstr " -W, --password 强制密码提示\n"
+
+#: pg_amcheck.c:1186
+#, c-format
+msgid " --maintenance-db=DBNAME alternate maintenance database\n"
+msgstr " --maintenance-db=DBNAME 备用维护数据库\n"
+
+#: pg_amcheck.c:1187
+#, c-format
+msgid ""
+"\n"
+"Other options:\n"
+msgstr ""
+"\n"
+"其它选项:\n"
+
+#: pg_amcheck.c:1188
+#, c-format
+msgid " -e, --echo show the commands being sent to the server\n"
+msgstr " -e, --echo 显示发送到服务端的命令\n"
+
+#: pg_amcheck.c:1189
+#, c-format
+msgid " -j, --jobs=NUM use this many concurrent connections to the server\n"
+msgstr " -j, --jobs=NUM 使用这么多到服务器的并发连接\n"
+
+#: pg_amcheck.c:1190
+#, c-format
+msgid " -q, --quiet don't write any messages\n"
+msgstr " -q, --quiet 不写任何信息\n"
+
+#: pg_amcheck.c:1191
+#, c-format
+msgid " -P, --progress show progress information\n"
+msgstr " -P, --progress 显示进度信息\n"
+
+#: pg_amcheck.c:1192
+#, c-format
+msgid " -v, --verbose write a lot of output\n"
+msgstr " -v, --verbose 写大量的输出\n"
+
+#: pg_amcheck.c:1193
+#, c-format
+msgid " -V, --version output version information, then exit\n"
+msgstr " -V, --version 输出版本信息, 然后退出\n"
+
+#: pg_amcheck.c:1194
+#, c-format
+msgid " --install-missing install missing extensions\n"
+msgstr " --install-missing 安装缺少的扩展\n"
+
+#: pg_amcheck.c:1195
+#, c-format
+msgid " -?, --help show this help, then exit\n"
+msgstr " -?, --help 显示此帮助信息, 然后退出\n"
+
+#: pg_amcheck.c:1197
+#, c-format
+msgid ""
+"\n"
+"Report bugs to <%s>.\n"
+msgstr ""
+"\n"
+"臭虫报告至<%s>.\n"
+
+#: pg_amcheck.c:1198
+#, c-format
+msgid "%s home page: <%s>\n"
+msgstr "%s 主页: <%s>\n"
+
+#: pg_amcheck.c:1256
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) %*s"
+msgstr "%*s/%s 关系 (%d%%), %*s/%s 页 (%d%%) %*s"
+
+#: pg_amcheck.c:1267
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%) (%s%-*.*s)"
+msgstr "%*s/%s 关系 (%d%%), %*s/%s 页 (%d%%) (%s%-*.*s)"
+
+#: pg_amcheck.c:1282
+msgid "%*s/%s relations (%d%%), %*s/%s pages (%d%%)"
+msgstr "%*s/%s 关系 (%d%%), %*s/%s 页 (%d%%)"
+
+#: pg_amcheck.c:1551 pg_amcheck.c:1693
+#, c-format
+msgid "including database \"%s\""
+msgstr "包含的数据库\"%s\""
+
+#: pg_amcheck.c:1673
+#, c-format
+msgid "internal error: received unexpected database pattern_id %d"
+msgstr "内部错误:收到意外的数据库pattern_id %d"
+
+#: pg_amcheck.c:1677
+msgid "no connectable databases to check matching \"%s\""
+msgstr "没有可连接的数据库来检查匹配的\"%s\""
+
+#: pg_amcheck.c:2127
+#, c-format
+msgid "internal error: received unexpected relation pattern_id %d"
+msgstr "内部错误:收到意外的关系pattern_id %d"
diff --git a/src/bin/pg_amcheck/t/001_basic.pl b/src/bin/pg_amcheck/t/001_basic.pl
new file mode 100644
index 0000000..6f60e3e
--- /dev/null
+++ b/src/bin/pg_amcheck/t/001_basic.pl
@@ -0,0 +1,12 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use TestLib;
+use Test::More tests => 8;
+
+program_help_ok('pg_amcheck');
+program_version_ok('pg_amcheck');
+program_options_handling_ok('pg_amcheck');
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
new file mode 100644
index 0000000..df0cb03
--- /dev/null
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -0,0 +1,359 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 100;
+
+# Test set-up
+my ($node, $port);
+$node = get_new_node('test');
+$node->init;
+$node->start;
+$port = $node->port;
+
+# Load the amcheck extension, upon which pg_amcheck depends
+$node->safe_psql('postgres', q(CREATE EXTENSION amcheck));
+
+#########################################
+# Test non-existent databases
+
+# Failing to connect to the initial database is an error.
+$node->command_checks_all(
+ [ 'pg_amcheck', 'qqq' ],
+ 1, [qr/^$/],
+ [qr/FATAL: database "qqq" does not exist/],
+ 'checking a non-existent database');
+
+# Failing to resolve a database pattern is an error by default.
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'qqq', '-d', 'postgres' ],
+ 1,
+ [qr/^$/],
+ [qr/pg_amcheck: error: no connectable databases to check matching "qqq"/],
+ 'checking an unresolvable database pattern');
+
+# But only a warning under --no-strict-names
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-d', 'qqq', '-d', 'postgres' ],
+ 0,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: warning: no connectable databases to check matching "qqq"/
+ ],
+ 'checking an unresolvable database pattern under --no-strict-names');
+
+# Check that a substring of an existent database name does not get interpreted
+# as a matching pattern.
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'post', '-d', 'postgres' ],
+ 1,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: no connectable databases to check matching "post"/
+ ],
+ 'checking an unresolvable database pattern (substring of existent database)'
+);
+
+# Check that a superstring of an existent database name does not get interpreted
+# as a matching pattern.
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'postgresql', '-d', 'postgres' ],
+ 1,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: no connectable databases to check matching "postgresql"/
+ ],
+ 'checking an unresolvable database pattern (superstring of existent database)'
+);
+
+#########################################
+# Test connecting with a non-existent user
+
+# Failing to connect to the initial database due to bad username is an error.
+$node->command_checks_all([ 'pg_amcheck', '-U', 'no_such_user', 'postgres' ],
+ 1, [qr/^$/], [], 'checking with a non-existent user');
+
+#########################################
+# Test checking databases without amcheck installed
+
+# Attempting to check a database by name where amcheck is not installed should
+# raise a warning. If all databases are skipped, having no relations to check
+# raises an error.
+$node->command_checks_all(
+ [ 'pg_amcheck', 'template1' ],
+ 1,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/,
+ qr/pg_amcheck: error: no relations to check/
+ ],
+ 'checking a database by name without amcheck installed, no other databases'
+);
+
+# Again, but this time with another database to check, so no error is raised.
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'template1', '-d', 'postgres' ],
+ 0,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/
+ ],
+ 'checking a database by name without amcheck installed, with other databases'
+);
+
+# Again, but by way of checking all databases
+$node->command_checks_all(
+ [ 'pg_amcheck', '--all' ],
+ 0,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/
+ ],
+ 'checking a database by pattern without amcheck installed, with other databases'
+);
+
+#########################################
+# Test unreasonable patterns
+
+# Check three-part unreasonable pattern that has zero-length names
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'postgres', '-t', '..' ],
+ 1,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: no connectable databases to check matching "\.\."/
+ ],
+ 'checking table pattern ".."');
+
+# Again, but with non-trivial schema and relation parts
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'postgres', '-t', '.foo.bar' ],
+ 1,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: no connectable databases to check matching "\.foo\.bar"/
+ ],
+ 'checking table pattern ".foo.bar"');
+
+# Check two-part unreasonable pattern that has zero-length names
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'postgres', '-t', '.' ],
+ 1,
+ [qr/^$/],
+ [qr/pg_amcheck: error: no heap tables to check matching "\."/],
+ 'checking table pattern "."');
+
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
+# Check that too many dotted names still draws an error under --no-strict-names
+# That flag means that it is ok for the object to be missing, not that it is ok
+# for the object name to be ungrammatical
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-t', 'this.is.a.really.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): this\.is\.a\.really\.long\.dotted\.string/
+ ],
+ 'ungrammatical table names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-s', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical schema names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-d', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical database names still draw errors under --no-strict-names'
+);
+
+# Likewise for exclusion patterns
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-T', 'a.b.c.d' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): a\.b\.c\.d/
+ ],
+ 'ungrammatical table exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-S', 'a.b.c' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b\.c/
+ ],
+ 'ungrammatical schema exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-D', 'a.b' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b/
+ ],
+ 'ungrammatical database exclusions still draw errors under --no-strict-names'
+);
+
+
+#########################################
+# Test checking non-existent databases, schemas, tables, and indexes
+
+# Use --no-strict-names and a single existent table so we only get warnings
+# about the failed pattern matches
+$node->command_checks_all(
+ [
+ 'pg_amcheck', '--no-strict-names',
+ '-t', 'no_such_table',
+ '-t', 'no*such*table',
+ '-i', 'no_such_index',
+ '-i', 'no*such*index',
+ '-r', 'no_such_relation',
+ '-r', 'no*such*relation',
+ '-d', 'no_such_database',
+ '-d', 'no*such*database',
+ '-r', 'none.none',
+ '-r', 'none.none.none',
+ '-r', 'postgres.none.none',
+ '-r', 'postgres.pg_catalog.none',
+ '-r', 'postgres.none.pg_class',
+ '-t', 'postgres.pg_catalog.pg_class', # This exists
+ ],
+ 0,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: warning: no heap tables to check matching "no_such_table"/,
+ qr/pg_amcheck: warning: no heap tables to check matching "no\*such\*table"/,
+ qr/pg_amcheck: warning: no btree indexes to check matching "no_such_index"/,
+ qr/pg_amcheck: warning: no btree indexes to check matching "no\*such\*index"/,
+ qr/pg_amcheck: warning: no relations to check matching "no_such_relation"/,
+ qr/pg_amcheck: warning: no relations to check matching "no\*such\*relation"/,
+ qr/pg_amcheck: warning: no heap tables to check matching "no\*such\*table"/,
+ qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
+ qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
+ qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
+ qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
+ qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
+ qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
+ qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
+ qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
+ qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
+ qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
+ ],
+ 'many unmatched patterns and one matched pattern under --no-strict-names'
+);
+
+#########################################
+# Test checking otherwise existent objects but in databases where they do not exist
+
+$node->safe_psql(
+ 'postgres', q(
+ CREATE TABLE public.foo (f integer);
+ CREATE INDEX foo_idx ON foo(f);
+));
+$node->safe_psql('postgres', q(CREATE DATABASE another_db));
+
+$node->command_checks_all(
+ [
+ 'pg_amcheck', '-d',
+ 'postgres', '--no-strict-names',
+ '-t', 'template1.public.foo',
+ '-t', 'another_db.public.foo',
+ '-t', 'no_such_database.public.foo',
+ '-i', 'template1.public.foo_idx',
+ '-i', 'another_db.public.foo_idx',
+ '-i', 'no_such_database.public.foo_idx',
+ ],
+ 1,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/,
+ qr/pg_amcheck: warning: no heap tables to check matching "template1\.public\.foo"/,
+ qr/pg_amcheck: warning: no heap tables to check matching "another_db\.public\.foo"/,
+ qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database\.public\.foo"/,
+ qr/pg_amcheck: warning: no btree indexes to check matching "template1\.public\.foo_idx"/,
+ qr/pg_amcheck: warning: no btree indexes to check matching "another_db\.public\.foo_idx"/,
+ qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database\.public\.foo_idx"/,
+ qr/pg_amcheck: error: no relations to check/,
+ ],
+ 'checking otherwise existent objets in the wrong databases');
+
+
+#########################################
+# Test schema exclusion patterns
+
+# Check with only schema exclusion patterns
+$node->command_checks_all(
+ [
+ 'pg_amcheck', '--all', '--no-strict-names', '-S',
+ 'public', '-S', 'pg_catalog', '-S',
+ 'pg_toast', '-S', 'information_schema',
+ ],
+ 1,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/,
+ qr/pg_amcheck: error: no relations to check/
+ ],
+ 'schema exclusion patterns exclude all relations');
+
+# Check with schema exclusion patterns overriding relation and schema inclusion patterns
+$node->command_checks_all(
+ [
+ 'pg_amcheck', '--all', '--no-strict-names', '-s',
+ 'public', '-s', 'pg_catalog', '-s',
+ 'pg_toast', '-s', 'information_schema', '-t',
+ 'pg_catalog.pg_class', '-S*'
+ ],
+ 1,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/,
+ qr/pg_amcheck: error: no relations to check/
+ ],
+ 'schema exclusion pattern overrides all inclusion patterns');
diff --git a/src/bin/pg_amcheck/t/003_check.pl b/src/bin/pg_amcheck/t/003_check.pl
new file mode 100644
index 0000000..4122d72
--- /dev/null
+++ b/src/bin/pg_amcheck/t/003_check.pl
@@ -0,0 +1,518 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+
+use Test::More tests => 63;
+
+my ($node, $port, %corrupt_page, %remove_relation);
+
+# Returns the filesystem path for the named relation.
+#
+# Assumes the test node is running
+sub relation_filepath
+{
+ my ($dbname, $relname) = @_;
+
+ my $pgdata = $node->data_dir;
+ my $rel =
+ $node->safe_psql($dbname, qq(SELECT pg_relation_filepath('$relname')));
+ die "path not found for relation $relname" unless defined $rel;
+ return "$pgdata/$rel";
+}
+
+# Returns the name of the toast relation associated with the named relation.
+#
+# Assumes the test node is running
+sub relation_toast
+{
+ my ($dbname, $relname) = @_;
+
+ my $rel = $node->safe_psql(
+ $dbname, qq(
+ SELECT c.reltoastrelid::regclass
+ FROM pg_catalog.pg_class c
+ WHERE c.oid = '$relname'::regclass
+ AND c.reltoastrelid != 0
+ ));
+ return $rel;
+}
+
+# Adds the relation file for the given (dbname, relname) to the list
+# to be corrupted by means of overwriting junk in the first page.
+#
+# Assumes the test node is running.
+sub plan_to_corrupt_first_page
+{
+ my ($dbname, $relname) = @_;
+ my $relpath = relation_filepath($dbname, $relname);
+ $corrupt_page{$relpath} = 1;
+}
+
+# Adds the relation file for the given (dbname, relname) to the list
+# to be corrupted by means of removing the file..
+#
+# Assumes the test node is running
+sub plan_to_remove_relation_file
+{
+ my ($dbname, $relname) = @_;
+ my $relpath = relation_filepath($dbname, $relname);
+ $remove_relation{$relpath} = 1;
+}
+
+# For the given (dbname, relname), if a corresponding toast table
+# exists, adds that toast table's relation file to the list to be
+# corrupted by means of removing the file.
+#
+# Assumes the test node is running.
+sub plan_to_remove_toast_file
+{
+ my ($dbname, $relname) = @_;
+ my $toastname = relation_toast($dbname, $relname);
+ plan_to_remove_relation_file($dbname, $toastname) if ($toastname);
+}
+
+# Corrupts the first page of the given file path
+sub corrupt_first_page
+{
+ my ($relpath) = @_;
+
+ my $fh;
+ open($fh, '+<', $relpath)
+ or BAIL_OUT("open failed: $!");
+ binmode $fh;
+
+ # Corrupt some line pointers. The values are chosen to hit the
+ # various line-pointer-corruption checks in verify_heapam.c
+ # on both little-endian and big-endian architectures.
+ sysseek($fh, 32, 0)
+ or BAIL_OUT("sysseek failed: $!");
+ syswrite(
+ $fh,
+ pack("L*",
+ 0xAAA15550, 0xAAA0D550, 0x00010000, 0x00008000,
+ 0x0000800F, 0x001e8000, 0xFFFFFFFF)
+ ) or BAIL_OUT("syswrite failed: $!");
+ close($fh)
+ or BAIL_OUT("close failed: $!");
+}
+
+# Stops the node, performs all the corruptions previously planned, and
+# starts the node again.
+#
+sub perform_all_corruptions()
+{
+ $node->stop();
+ for my $relpath (keys %corrupt_page)
+ {
+ corrupt_first_page($relpath);
+ }
+ for my $relpath (keys %remove_relation)
+ {
+ unlink($relpath);
+ }
+ $node->start;
+}
+
+# Test set-up
+$node = get_new_node('test');
+$node->init;
+$node->append_conf('postgresql.conf', 'autovacuum=off');
+$node->start;
+$port = $node->port;
+
+for my $dbname (qw(db1 db2 db3))
+{
+ # Create the database
+ $node->safe_psql('postgres', qq(CREATE DATABASE $dbname));
+
+ # Load the amcheck extension, upon which pg_amcheck depends. Put the
+ # extension in an unexpected location to test that pg_amcheck finds it
+ # correctly. Create tables with names that look like pg_catalog names to
+ # check that pg_amcheck does not get confused by them. Create functions in
+ # schema public that look like amcheck functions to check that pg_amcheck
+ # does not use them.
+ $node->safe_psql(
+ $dbname, q(
+ CREATE SCHEMA amcheck_schema;
+ CREATE EXTENSION amcheck WITH SCHEMA amcheck_schema;
+ CREATE TABLE amcheck_schema.pg_database (junk text);
+ CREATE TABLE amcheck_schema.pg_namespace (junk text);
+ CREATE TABLE amcheck_schema.pg_class (junk text);
+ CREATE TABLE amcheck_schema.pg_operator (junk text);
+ CREATE TABLE amcheck_schema.pg_proc (junk text);
+ CREATE TABLE amcheck_schema.pg_tablespace (junk text);
+
+ CREATE FUNCTION public.bt_index_check(index regclass,
+ heapallindexed boolean default false)
+ RETURNS VOID AS $$
+ BEGIN
+ RAISE EXCEPTION 'Invoked wrong bt_index_check!';
+ END;
+ $$ LANGUAGE plpgsql;
+
+ CREATE FUNCTION public.bt_index_parent_check(index regclass,
+ heapallindexed boolean default false,
+ rootdescend boolean default false)
+ RETURNS VOID AS $$
+ BEGIN
+ RAISE EXCEPTION 'Invoked wrong bt_index_parent_check!';
+ END;
+ $$ LANGUAGE plpgsql;
+
+ CREATE FUNCTION public.verify_heapam(relation regclass,
+ on_error_stop boolean default false,
+ check_toast boolean default false,
+ skip text default 'none',
+ startblock bigint default null,
+ endblock bigint default null,
+ blkno OUT bigint,
+ offnum OUT integer,
+ attnum OUT integer,
+ msg OUT text)
+ RETURNS SETOF record AS $$
+ BEGIN
+ RAISE EXCEPTION 'Invoked wrong verify_heapam!';
+ END;
+ $$ LANGUAGE plpgsql;
+ ));
+
+ # Create schemas, tables and indexes in five separate
+ # schemas. The schemas are all identical to start, but
+ # we will corrupt them differently later.
+ #
+ for my $schema (qw(s1 s2 s3 s4 s5))
+ {
+ $node->safe_psql(
+ $dbname, qq(
+ CREATE SCHEMA $schema;
+ CREATE SEQUENCE $schema.seq1;
+ CREATE SEQUENCE $schema.seq2;
+ CREATE TABLE $schema.t1 (
+ i INTEGER,
+ b BOX,
+ ia int4[],
+ ir int4range,
+ t TEXT
+ );
+ CREATE TABLE $schema.t2 (
+ i INTEGER,
+ b BOX,
+ ia int4[],
+ ir int4range,
+ t TEXT
+ );
+ CREATE VIEW $schema.t2_view AS (
+ SELECT i*2, t FROM $schema.t2
+ );
+ ALTER TABLE $schema.t2
+ ALTER COLUMN t
+ SET STORAGE EXTERNAL;
+
+ INSERT INTO $schema.t1 (i, b, ia, ir, t)
+ (SELECT gs::INTEGER AS i,
+ box(point(gs,gs+5),point(gs*2,gs*3)) AS b,
+ array[gs, gs + 1]::int4[] AS ia,
+ int4range(gs, gs+100) AS ir,
+ repeat('foo', gs) AS t
+ FROM generate_series(1,10000,3000) AS gs);
+
+ INSERT INTO $schema.t2 (i, b, ia, ir, t)
+ (SELECT gs::INTEGER AS i,
+ box(point(gs,gs+5),point(gs*2,gs*3)) AS b,
+ array[gs, gs + 1]::int4[] AS ia,
+ int4range(gs, gs+100) AS ir,
+ repeat('foo', gs) AS t
+ FROM generate_series(1,10000,3000) AS gs);
+
+ CREATE MATERIALIZED VIEW $schema.t1_mv AS SELECT * FROM $schema.t1;
+ CREATE MATERIALIZED VIEW $schema.t2_mv AS SELECT * FROM $schema.t2;
+
+ create table $schema.p1 (a int, b int) PARTITION BY list (a);
+ create table $schema.p2 (a int, b int) PARTITION BY list (a);
+
+ create table $schema.p1_1 partition of $schema.p1 for values in (1, 2, 3);
+ create table $schema.p1_2 partition of $schema.p1 for values in (4, 5, 6);
+ create table $schema.p2_1 partition of $schema.p2 for values in (1, 2, 3);
+ create table $schema.p2_2 partition of $schema.p2 for values in (4, 5, 6);
+
+ CREATE INDEX t1_btree ON $schema.t1 USING BTREE (i);
+ CREATE INDEX t2_btree ON $schema.t2 USING BTREE (i);
+
+ CREATE INDEX t1_hash ON $schema.t1 USING HASH (i);
+ CREATE INDEX t2_hash ON $schema.t2 USING HASH (i);
+
+ CREATE INDEX t1_brin ON $schema.t1 USING BRIN (i);
+ CREATE INDEX t2_brin ON $schema.t2 USING BRIN (i);
+
+ CREATE INDEX t1_gist ON $schema.t1 USING GIST (b);
+ CREATE INDEX t2_gist ON $schema.t2 USING GIST (b);
+
+ CREATE INDEX t1_gin ON $schema.t1 USING GIN (ia);
+ CREATE INDEX t2_gin ON $schema.t2 USING GIN (ia);
+
+ CREATE INDEX t1_spgist ON $schema.t1 USING SPGIST (ir);
+ CREATE INDEX t2_spgist ON $schema.t2 USING SPGIST (ir);
+ ));
+ }
+}
+
+# Database 'db1' corruptions
+#
+
+# Corrupt indexes in schema "s1"
+plan_to_remove_relation_file('db1', 's1.t1_btree');
+plan_to_corrupt_first_page('db1', 's1.t2_btree');
+
+# Corrupt tables in schema "s2"
+plan_to_remove_relation_file('db1', 's2.t1');
+plan_to_corrupt_first_page('db1', 's2.t2');
+
+# Corrupt tables, partitions, matviews, and btrees in schema "s3"
+plan_to_remove_relation_file('db1', 's3.t1');
+plan_to_corrupt_first_page('db1', 's3.t2');
+
+plan_to_remove_relation_file('db1', 's3.t1_mv');
+plan_to_remove_relation_file('db1', 's3.p1_1');
+
+plan_to_corrupt_first_page('db1', 's3.t2_mv');
+plan_to_corrupt_first_page('db1', 's3.p2_1');
+
+plan_to_remove_relation_file('db1', 's3.t1_btree');
+plan_to_corrupt_first_page('db1', 's3.t2_btree');
+
+# Corrupt toast table, partitions, and materialized views in schema "s4"
+plan_to_remove_toast_file('db1', 's4.t2');
+
+# Corrupt all other object types in schema "s5". We don't have amcheck support
+# for these types, but we check that their corruption does not trigger any
+# errors in pg_amcheck
+plan_to_remove_relation_file('db1', 's5.seq1');
+plan_to_remove_relation_file('db1', 's5.t1_hash');
+plan_to_remove_relation_file('db1', 's5.t1_gist');
+plan_to_remove_relation_file('db1', 's5.t1_gin');
+plan_to_remove_relation_file('db1', 's5.t1_brin');
+plan_to_remove_relation_file('db1', 's5.t1_spgist');
+
+plan_to_corrupt_first_page('db1', 's5.seq2');
+plan_to_corrupt_first_page('db1', 's5.t2_hash');
+plan_to_corrupt_first_page('db1', 's5.t2_gist');
+plan_to_corrupt_first_page('db1', 's5.t2_gin');
+plan_to_corrupt_first_page('db1', 's5.t2_brin');
+plan_to_corrupt_first_page('db1', 's5.t2_spgist');
+
+
+# Database 'db2' corruptions
+#
+plan_to_remove_relation_file('db2', 's1.t1');
+plan_to_remove_relation_file('db2', 's1.t1_btree');
+
+
+# Leave 'db3' uncorrupted
+#
+
+# Standard first arguments to TestLib functions
+my @cmd = ('pg_amcheck', '-p', $port);
+
+# Regular expressions to match various expected output
+my $no_output_re = qr/^$/;
+my $line_pointer_corruption_re = qr/line pointer/;
+my $missing_file_re = qr/could not open file ".*": No such file or directory/;
+my $index_missing_relation_fork_re =
+ qr/index ".*" lacks a main relation fork/;
+
+# We have created test databases with tables populated with data, but have not
+# yet corrupted anything. As such, we expect no corruption and verify that
+# none is reported
+#
+$node->command_checks_all([ @cmd, '-d', 'db1', '-d', 'db2', '-d', 'db3' ],
+ 0, [$no_output_re], [$no_output_re], 'pg_amcheck prior to corruption');
+
+# Perform the corruptions we planned above using only a single database restart.
+#
+perform_all_corruptions();
+
+
+# Checking databases with amcheck installed and corrupt relations, pg_amcheck
+# command itself should return exit status = 2, because tables and indexes are
+# corrupt, not exit status = 1, which would mean the pg_amcheck command itself
+# failed. Corruption messages should go to stdout, and nothing to stderr.
+#
+$node->command_checks_all(
+ [ @cmd, 'db1' ],
+ 2,
+ [
+ $index_missing_relation_fork_re, $line_pointer_corruption_re,
+ $missing_file_re,
+ ],
+ [$no_output_re],
+ 'pg_amcheck all schemas, tables and indexes in database db1');
+
+$node->command_checks_all(
+ [ @cmd, '-d', 'db1', '-d', 'db2', '-d', 'db3' ],
+ 2,
+ [
+ $index_missing_relation_fork_re, $line_pointer_corruption_re,
+ $missing_file_re,
+ ],
+ [$no_output_re],
+ 'pg_amcheck all schemas, tables and indexes in databases db1, db2, and db3'
+);
+
+# Scans of indexes in s1 should detect the specific corruption that we created
+# above. For missing relation forks, we know what the error message looks
+# like. For corrupted index pages, the error might vary depending on how the
+# page was formatted on disk, including variations due to alignment differences
+# between platforms, so we accept any non-empty error message.
+#
+# If we don't limit the check to databases with amcheck installed, we expect
+# complaint on stderr, but otherwise stderr should be quiet.
+#
+$node->command_checks_all(
+ [ @cmd, '--all', '-s', 's1', '-i', 't1_btree' ],
+ 2,
+ [$index_missing_relation_fork_re],
+ [
+ qr/pg_amcheck: warning: skipping database "postgres": amcheck is not installed/
+ ],
+ 'pg_amcheck index s1.t1_btree reports missing main relation fork');
+
+$node->command_checks_all(
+ [ @cmd, '-d', 'db1', '-s', 's1', '-i', 't2_btree' ],
+ 2,
+ [qr/.+/], # Any non-empty error message is acceptable
+ [$no_output_re],
+ 'pg_amcheck index s1.s2 reports index corruption');
+
+# Checking db1.s1 with indexes excluded should show no corruptions because we
+# did not corrupt any tables in db1.s1. Verify that both stdout and stderr
+# are quiet.
+#
+$node->command_checks_all(
+ [ @cmd, '-t', 's1.*', '--no-dependent-indexes', 'db1' ],
+ 0, [$no_output_re], [$no_output_re],
+ 'pg_amcheck of db1.s1 excluding indexes');
+
+# Checking db2.s1 should show table corruptions if indexes are excluded
+#
+$node->command_checks_all(
+ [ @cmd, '-t', 's1.*', '--no-dependent-indexes', 'db2' ],
+ 2, [$missing_file_re], [$no_output_re],
+ 'pg_amcheck of db2.s1 excluding indexes');
+
+# In schema db1.s3, the tables and indexes are both corrupt. We should see
+# corruption messages on stdout, and nothing on stderr.
+#
+$node->command_checks_all(
+ [ @cmd, '-s', 's3', 'db1' ],
+ 2,
+ [
+ $index_missing_relation_fork_re, $line_pointer_corruption_re,
+ $missing_file_re,
+ ],
+ [$no_output_re],
+ 'pg_amcheck schema s3 reports table and index errors');
+
+# In schema db1.s4, only toast tables are corrupt. Check that under default
+# options the toast corruption is reported, but when excluding toast we get no
+# error reports.
+$node->command_checks_all([ @cmd, '-s', 's4', 'db1' ],
+ 2, [$missing_file_re], [$no_output_re],
+ 'pg_amcheck in schema s4 reports toast corruption');
+
+$node->command_checks_all(
+ [
+ @cmd, '--no-dependent-toast', '--exclude-toast-pointers', '-s', 's4',
+ 'db1'
+ ],
+ 0,
+ [$no_output_re],
+ [$no_output_re],
+ 'pg_amcheck in schema s4 excluding toast reports no corruption');
+
+# Check that no corruption is reported in schema db1.s5
+$node->command_checks_all([ @cmd, '-s', 's5', 'db1' ],
+ 0, [$no_output_re], [$no_output_re],
+ 'pg_amcheck over schema s5 reports no corruption');
+
+# In schema db1.s1, only indexes are corrupt. Verify that when we exclude
+# the indexes, no corruption is reported about the schema.
+#
+$node->command_checks_all(
+ [ @cmd, '-s', 's1', '-I', 't1_btree', '-I', 't2_btree', 'db1' ],
+ 0,
+ [$no_output_re],
+ [$no_output_re],
+ 'pg_amcheck over schema s1 with corrupt indexes excluded reports no corruption'
+);
+
+# In schema db1.s1, only indexes are corrupt. Verify that when we provide only
+# table inclusions, and disable index expansion, no corruption is reported
+# about the schema.
+#
+$node->command_checks_all(
+ [ @cmd, '-t', 's1.*', '--no-dependent-indexes', 'db1' ],
+ 0,
+ [$no_output_re],
+ [$no_output_re],
+ 'pg_amcheck over schema s1 with all indexes excluded reports no corruption'
+);
+
+# In schema db1.s2, only tables are corrupt. Verify that when we exclude those
+# tables that no corruption is reported.
+#
+$node->command_checks_all(
+ [ @cmd, '-s', 's2', '-T', 't1', '-T', 't2', 'db1' ],
+ 0,
+ [$no_output_re],
+ [$no_output_re],
+ 'pg_amcheck over schema s2 with corrupt tables excluded reports no corruption'
+);
+
+# Check errors about bad block range command line arguments. We use schema s5
+# to avoid getting messages about corrupt tables or indexes.
+#
+command_fails_like(
+ [ @cmd, '-s', 's5', '--startblock', 'junk', 'db1' ],
+ qr/invalid start block/,
+ 'pg_amcheck rejects garbage startblock');
+
+command_fails_like(
+ [ @cmd, '-s', 's5', '--endblock', '1234junk', 'db1' ],
+ qr/invalid end block/,
+ 'pg_amcheck rejects garbage endblock');
+
+command_fails_like(
+ [ @cmd, '-s', 's5', '--startblock', '5', '--endblock', '4', 'db1' ],
+ qr/end block precedes start block/,
+ 'pg_amcheck rejects invalid block range');
+
+# Check bt_index_parent_check alternates. We don't create any index corruption
+# that would behave differently under these modes, so just smoke test that the
+# arguments are handled sensibly.
+#
+$node->command_checks_all(
+ [ @cmd, '-s', 's1', '-i', 't1_btree', '--parent-check', 'db1' ],
+ 2,
+ [$index_missing_relation_fork_re],
+ [$no_output_re],
+ 'pg_amcheck smoke test --parent-check');
+
+$node->command_checks_all(
+ [
+ @cmd, '-s', 's1', '-i', 't1_btree', '--heapallindexed',
+ '--rootdescend', 'db1'
+ ],
+ 2,
+ [$index_missing_relation_fork_re],
+ [$no_output_re],
+ 'pg_amcheck smoke test --heapallindexed --rootdescend');
+
+$node->command_checks_all(
+ [ @cmd, '-d', 'db1', '-d', 'db2', '-d', 'db3', '-S', 's*' ],
+ 0, [$no_output_re], [$no_output_re],
+ 'pg_amcheck excluding all corrupt schemas');
diff --git a/src/bin/pg_amcheck/t/004_verify_heapam.pl b/src/bin/pg_amcheck/t/004_verify_heapam.pl
new file mode 100644
index 0000000..b603efa
--- /dev/null
+++ b/src/bin/pg_amcheck/t/004_verify_heapam.pl
@@ -0,0 +1,529 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+
+use Test::More;
+
+# This regression test demonstrates that the pg_amcheck binary correctly
+# identifies specific kinds of corruption within pages. To test this, we need
+# a mechanism to create corrupt pages with predictable, repeatable corruption.
+# The postgres backend cannot be expected to help us with this, as its design
+# is not consistent with the goal of intentionally corrupting pages.
+#
+# Instead, we create a table to corrupt, and with careful consideration of how
+# postgresql lays out heap pages, we seek to offsets within the page and
+# overwrite deliberately chosen bytes with specific values calculated to
+# corrupt the page in expected ways. We then verify that pg_amcheck reports
+# the corruption, and that it runs without crashing. Note that the backend
+# cannot simply be started to run queries against the corrupt table, as the
+# backend will crash, at least for some of the corruption types we generate.
+#
+# Autovacuum potentially touching the table in the background makes the exact
+# behavior of this test harder to reason about. We turn it off to keep things
+# simpler. We use a "belt and suspenders" approach, turning it off for the
+# system generally in postgresql.conf, and turning it off specifically for the
+# test table.
+#
+# This test depends on the table being written to the heap file exactly as we
+# expect it to be, so we take care to arrange the columns of the table, and
+# insert rows of the table, that give predictable sizes and locations within
+# the table page.
+#
+# The HeapTupleHeaderData has 23 bytes of fixed size fields before the variable
+# length t_bits[] array. We have exactly 3 columns in the table, so natts = 3,
+# t_bits is 1 byte long, and t_hoff = MAXALIGN(23 + 1) = 24.
+#
+# We're not too fussy about which datatypes we use for the test, but we do care
+# about some specific properties. We'd like to test both fixed size and
+# varlena types. We'd like some varlena data inline and some toasted. And
+# we'd like the layout of the table such that the datums land at predictable
+# offsets within the tuple. We choose a structure without padding on all
+# supported architectures:
+#
+# a BIGINT
+# b TEXT
+# c TEXT
+#
+# We always insert a 7-ascii character string into field 'b', which with a
+# 1-byte varlena header gives an 8 byte inline value. We always insert a long
+# text string in field 'c', long enough to force toast storage.
+#
+# We choose to read and write binary copies of our table's tuples, using perl's
+# pack() and unpack() functions. Perl uses a packing code system in which:
+#
+# l = "signed 32-bit Long",
+# L = "Unsigned 32-bit Long",
+# S = "Unsigned 16-bit Short",
+# C = "Unsigned 8-bit Octet",
+#
+# Each tuple in our table has a layout as follows:
+#
+# xx xx xx xx t_xmin: xxxx offset = 0 L
+# xx xx xx xx t_xmax: xxxx offset = 4 L
+# xx xx xx xx t_field3: xxxx offset = 8 L
+# xx xx bi_hi: xx offset = 12 S
+# xx xx bi_lo: xx offset = 14 S
+# xx xx ip_posid: xx offset = 16 S
+# xx xx t_infomask2: xx offset = 18 S
+# xx xx t_infomask: xx offset = 20 S
+# xx t_hoff: x offset = 22 C
+# xx t_bits: x offset = 23 C
+# xx xx xx xx xx xx xx xx 'a': xxxxxxxx offset = 24 LL
+# xx xx xx xx xx xx xx xx 'b': xxxxxxxx offset = 32 CCCCCCCC
+# xx xx xx xx xx xx xx xx 'c': xxxxxxxx offset = 40 CCllLL
+# xx xx xx xx xx xx xx xx : xxxxxxxx ...continued
+# xx xx : xx ...continued
+#
+# We could choose to read and write columns 'b' and 'c' in other ways, but
+# it is convenient enough to do it this way. We define packing code
+# constants here, where they can be compared easily against the layout.
+
+use constant HEAPTUPLE_PACK_CODE => 'LLLSSSSSCCLLCCCCCCCCCCllLL';
+use constant HEAPTUPLE_PACK_LENGTH => 58; # Total size
+
+# Read a tuple of our table from a heap page.
+#
+# Takes an open filehandle to the heap file, and the offset of the tuple.
+#
+# Rather than returning the binary data from the file, unpacks the data into a
+# perl hash with named fields. These fields exactly match the ones understood
+# by write_tuple(), below. Returns a reference to this hash.
+#
+sub read_tuple
+{
+ my ($fh, $offset) = @_;
+ my ($buffer, %tup);
+ sysseek($fh, $offset, 0)
+ or BAIL_OUT("sysseek failed: $!");
+ defined(sysread($fh, $buffer, HEAPTUPLE_PACK_LENGTH))
+ or BAIL_OUT("sysread failed: $!");
+
+ @_ = unpack(HEAPTUPLE_PACK_CODE, $buffer);
+ %tup = (
+ t_xmin => shift,
+ t_xmax => shift,
+ t_field3 => shift,
+ bi_hi => shift,
+ bi_lo => shift,
+ ip_posid => shift,
+ t_infomask2 => shift,
+ t_infomask => shift,
+ t_hoff => shift,
+ t_bits => shift,
+ a_1 => shift,
+ a_2 => shift,
+ b_header => shift,
+ b_body1 => shift,
+ b_body2 => shift,
+ b_body3 => shift,
+ b_body4 => shift,
+ b_body5 => shift,
+ b_body6 => shift,
+ b_body7 => shift,
+ c_va_header => shift,
+ c_va_vartag => shift,
+ c_va_rawsize => shift,
+ c_va_extinfo => shift,
+ c_va_valueid => shift,
+ c_va_toastrelid => shift);
+ # Stitch together the text for column 'b'
+ $tup{b} = join('', map { chr($tup{"b_body$_"}) } (1 .. 7));
+ return \%tup;
+}
+
+# Write a tuple of our table to a heap page.
+#
+# Takes an open filehandle to the heap file, the offset of the tuple, and a
+# reference to a hash with the tuple values, as returned by read_tuple().
+# Writes the tuple fields from the hash into the heap file.
+#
+# The purpose of this function is to write a tuple back to disk with some
+# subset of fields modified. The function does no error checking. Use
+# cautiously.
+#
+sub write_tuple
+{
+ my ($fh, $offset, $tup) = @_;
+ my $buffer = pack(
+ HEAPTUPLE_PACK_CODE,
+ $tup->{t_xmin}, $tup->{t_xmax},
+ $tup->{t_field3}, $tup->{bi_hi},
+ $tup->{bi_lo}, $tup->{ip_posid},
+ $tup->{t_infomask2}, $tup->{t_infomask},
+ $tup->{t_hoff}, $tup->{t_bits},
+ $tup->{a_1}, $tup->{a_2},
+ $tup->{b_header}, $tup->{b_body1},
+ $tup->{b_body2}, $tup->{b_body3},
+ $tup->{b_body4}, $tup->{b_body5},
+ $tup->{b_body6}, $tup->{b_body7},
+ $tup->{c_va_header}, $tup->{c_va_vartag},
+ $tup->{c_va_rawsize}, $tup->{c_va_extinfo},
+ $tup->{c_va_valueid}, $tup->{c_va_toastrelid});
+ sysseek($fh, $offset, 0)
+ or BAIL_OUT("sysseek failed: $!");
+ defined(syswrite($fh, $buffer, HEAPTUPLE_PACK_LENGTH))
+ or BAIL_OUT("syswrite failed: $!");
+ return;
+}
+
+# Set umask so test directories and files are created with default permissions
+umask(0077);
+
+# Set up the node. Once we create and corrupt the table,
+# autovacuum workers visiting the table could crash the backend.
+# Disable autovacuum so that won't happen.
+my $node = get_new_node('test');
+$node->init;
+$node->append_conf('postgresql.conf', 'autovacuum=off');
+
+# Start the node and load the extensions. We depend on both
+# amcheck and pageinspect for this test.
+$node->start;
+my $port = $node->port;
+my $pgdata = $node->data_dir;
+$node->safe_psql('postgres', "CREATE EXTENSION amcheck");
+$node->safe_psql('postgres', "CREATE EXTENSION pageinspect");
+
+# Get a non-zero datfrozenxid
+$node->safe_psql('postgres', qq(VACUUM FREEZE));
+
+# Create the test table with precisely the schema that our corruption function
+# expects.
+$node->safe_psql(
+ 'postgres', qq(
+ CREATE TABLE public.test (a BIGINT, b TEXT, c TEXT);
+ ALTER TABLE public.test SET (autovacuum_enabled=false);
+ ALTER TABLE public.test ALTER COLUMN c SET STORAGE EXTERNAL;
+ CREATE INDEX test_idx ON public.test(a, b);
+ ));
+
+# We want (0 < datfrozenxid < test.relfrozenxid). To achieve this, we freeze
+# an otherwise unused table, public.junk, prior to inserting data and freezing
+# public.test
+$node->safe_psql(
+ 'postgres', qq(
+ CREATE TABLE public.junk AS SELECT 'junk'::TEXT AS junk_column;
+ ALTER TABLE public.junk SET (autovacuum_enabled=false);
+ VACUUM FREEZE public.junk
+ ));
+
+my $rel = $node->safe_psql('postgres',
+ qq(SELECT pg_relation_filepath('public.test')));
+my $relpath = "$pgdata/$rel";
+
+# Insert data and freeze public.test
+use constant ROWCOUNT => 16;
+$node->safe_psql(
+ 'postgres', qq(
+ INSERT INTO public.test (a, b, c)
+ VALUES (
+ x'DEADF9F9DEADF9F9'::bigint,
+ 'abcdefg',
+ repeat('w', 10000)
+ );
+ VACUUM FREEZE public.test
+ )) for (1 .. ROWCOUNT);
+
+my $relfrozenxid = $node->safe_psql('postgres',
+ q(select relfrozenxid from pg_class where relname = 'test'));
+my $datfrozenxid = $node->safe_psql('postgres',
+ q(select datfrozenxid from pg_database where datname = 'postgres'));
+
+# Sanity check that our 'test' table has a relfrozenxid newer than the
+# datfrozenxid for the database, and that the datfrozenxid is greater than the
+# first normal xid. We rely on these invariants in some of our tests.
+if ($datfrozenxid <= 3 || $datfrozenxid >= $relfrozenxid)
+{
+ $node->clean_node;
+ plan skip_all =>
+ "Xid thresholds not as expected: got datfrozenxid = $datfrozenxid, relfrozenxid = $relfrozenxid";
+ exit;
+}
+
+# Find where each of the tuples is located on the page.
+my @lp_off;
+for my $tup (0 .. ROWCOUNT - 1)
+{
+ push(
+ @lp_off,
+ $node->safe_psql(
+ 'postgres', qq(
+select lp_off from heap_page_items(get_raw_page('test', 'main', 0))
+ offset $tup limit 1)));
+}
+
+# Sanity check that our 'test' table on disk layout matches expectations. If
+# this is not so, we will have to skip the test until somebody updates the test
+# to work on this platform.
+$node->stop;
+my $file;
+open($file, '+<', $relpath)
+ or BAIL_OUT("open failed: $!");
+binmode $file;
+
+my $ENDIANNESS;
+for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
+{
+ my $offnum = $tupidx + 1; # offnum is 1-based, not zero-based
+ my $offset = $lp_off[$tupidx];
+ my $tup = read_tuple($file, $offset);
+
+ # Sanity-check that the data appears on the page where we expect.
+ my $a_1 = $tup->{a_1};
+ my $a_2 = $tup->{a_2};
+ my $b = $tup->{b};
+ if ($a_1 != 0xDEADF9F9 || $a_2 != 0xDEADF9F9 || $b ne 'abcdefg')
+ {
+ close($file); # ignore errors on close; we're exiting anyway
+ $node->clean_node;
+ plan skip_all =>
+ sprintf(
+ "Page layout differs from our expectations: expected (%x, %x, \"%s\"), got (%x, %x, \"%s\")",
+ 0xDEADF9F9, 0xDEADF9F9, "abcdefg", $a_1, $a_2, $b);
+ exit;
+ }
+
+ # Determine endianness of current platform from the 1-byte varlena header
+ $ENDIANNESS = $tup->{b_header} == 0x11 ? "little" : "big";
+}
+close($file)
+ or BAIL_OUT("close failed: $!");
+$node->start;
+
+# Ok, Xids and page layout look ok. We can run corruption tests.
+plan tests => 19;
+
+# Check that pg_amcheck runs against the uncorrupted table without error.
+$node->command_ok(
+ [ 'pg_amcheck', '-p', $port, 'postgres' ],
+ 'pg_amcheck test table, prior to corruption');
+
+# Check that pg_amcheck runs against the uncorrupted table and index without error.
+$node->command_ok([ 'pg_amcheck', '-p', $port, 'postgres' ],
+ 'pg_amcheck test table and index, prior to corruption');
+
+$node->stop;
+
+# Some #define constants from access/htup_details.h for use while corrupting.
+use constant HEAP_HASNULL => 0x0001;
+use constant HEAP_XMAX_LOCK_ONLY => 0x0080;
+use constant HEAP_XMIN_COMMITTED => 0x0100;
+use constant HEAP_XMIN_INVALID => 0x0200;
+use constant HEAP_XMAX_COMMITTED => 0x0400;
+use constant HEAP_XMAX_INVALID => 0x0800;
+use constant HEAP_NATTS_MASK => 0x07FF;
+use constant HEAP_XMAX_IS_MULTI => 0x1000;
+use constant HEAP_KEYS_UPDATED => 0x2000;
+
+# Helper function to generate a regular expression matching the header we
+# expect verify_heapam() to return given which fields we expect to be non-null.
+sub header
+{
+ my ($blkno, $offnum, $attnum) = @_;
+ return
+ qr/heap table "postgres\.public\.test", block $blkno, offset $offnum, attribute $attnum:\s+/ms
+ if (defined $attnum);
+ return
+ qr/heap table "postgres\.public\.test", block $blkno, offset $offnum:\s+/ms
+ if (defined $offnum);
+ return qr/heap table "postgres\.public\.test", block $blkno:\s+/ms
+ if (defined $blkno);
+ return qr/heap table "postgres\.public\.test":\s+/ms;
+}
+
+# Corrupt the tuples, one type of corruption per tuple. Some types of
+# corruption cause verify_heapam to skip to the next tuple without
+# performing any remaining checks, so we can't exercise the system properly if
+# we focus all our corruption on a single tuple.
+#
+my @expected;
+open($file, '+<', $relpath)
+ or BAIL_OUT("open failed: $!");
+binmode $file;
+
+for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
+{
+ my $offnum = $tupidx + 1; # offnum is 1-based, not zero-based
+ my $offset = $lp_off[$tupidx];
+ my $tup = read_tuple($file, $offset);
+
+ my $header = header(0, $offnum, undef);
+ if ($offnum == 1)
+ {
+ # Corruptly set xmin < relfrozenxid
+ my $xmin = $relfrozenxid - 1;
+ $tup->{t_xmin} = $xmin;
+ $tup->{t_infomask} &= ~HEAP_XMIN_COMMITTED;
+ $tup->{t_infomask} &= ~HEAP_XMIN_INVALID;
+
+ # Expected corruption report
+ push @expected,
+ qr/${header}xmin $xmin precedes relation freeze threshold 0:\d+/;
+ }
+ if ($offnum == 2)
+ {
+ # Corruptly set xmin < datfrozenxid
+ my $xmin = 3;
+ $tup->{t_xmin} = $xmin;
+ $tup->{t_infomask} &= ~HEAP_XMIN_COMMITTED;
+ $tup->{t_infomask} &= ~HEAP_XMIN_INVALID;
+
+ push @expected,
+ qr/${$header}xmin $xmin precedes oldest valid transaction ID 0:\d+/;
+ }
+ elsif ($offnum == 3)
+ {
+ # Corruptly set xmin < datfrozenxid, further back, noting circularity
+ # of xid comparison. For a new cluster with epoch = 0, the corrupt
+ # xmin will be interpreted as in the future
+ $tup->{t_xmin} = 4026531839;
+ $tup->{t_infomask} &= ~HEAP_XMIN_COMMITTED;
+ $tup->{t_infomask} &= ~HEAP_XMIN_INVALID;
+
+ push @expected,
+ qr/${$header}xmin 4026531839 equals or exceeds next valid transaction ID 0:\d+/;
+ }
+ elsif ($offnum == 4)
+ {
+ # Corruptly set xmax < relminmxid;
+ $tup->{t_xmax} = 4026531839;
+ $tup->{t_infomask} &= ~HEAP_XMAX_INVALID;
+
+ push @expected,
+ qr/${$header}xmax 4026531839 equals or exceeds next valid transaction ID 0:\d+/;
+ }
+ elsif ($offnum == 5)
+ {
+ # Corrupt the tuple t_hoff, but keep it aligned properly
+ $tup->{t_hoff} += 128;
+
+ push @expected,
+ qr/${$header}data begins at offset 152 beyond the tuple length 58/,
+ qr/${$header}tuple data should begin at byte 24, but actually begins at byte 152 \(3 attributes, no nulls\)/;
+ }
+ elsif ($offnum == 6)
+ {
+ # Corrupt the tuple t_hoff, wrong alignment
+ $tup->{t_hoff} += 3;
+
+ push @expected,
+ qr/${$header}tuple data should begin at byte 24, but actually begins at byte 27 \(3 attributes, no nulls\)/;
+ }
+ elsif ($offnum == 7)
+ {
+ # Corrupt the tuple t_hoff, underflow but correct alignment
+ $tup->{t_hoff} -= 8;
+
+ push @expected,
+ qr/${$header}tuple data should begin at byte 24, but actually begins at byte 16 \(3 attributes, no nulls\)/;
+ }
+ elsif ($offnum == 8)
+ {
+ # Corrupt the tuple t_hoff, underflow and wrong alignment
+ $tup->{t_hoff} -= 3;
+
+ push @expected,
+ qr/${$header}tuple data should begin at byte 24, but actually begins at byte 21 \(3 attributes, no nulls\)/;
+ }
+ elsif ($offnum == 9)
+ {
+ # Corrupt the tuple to look like it has lots of attributes, not just 3
+ $tup->{t_infomask2} |= HEAP_NATTS_MASK;
+
+ push @expected,
+ qr/${$header}number of attributes 2047 exceeds maximum expected for table 3/;
+ }
+ elsif ($offnum == 10)
+ {
+ # Corrupt the tuple to look like it has lots of attributes, some of
+ # them null. This falsely creates the impression that the t_bits
+ # array is longer than just one byte, but t_hoff still says otherwise.
+ $tup->{t_infomask} |= HEAP_HASNULL;
+ $tup->{t_infomask2} |= HEAP_NATTS_MASK;
+ $tup->{t_bits} = 0xAA;
+
+ push @expected,
+ qr/${$header}tuple data should begin at byte 280, but actually begins at byte 24 \(2047 attributes, has nulls\)/;
+ }
+ elsif ($offnum == 11)
+ {
+ # Same as above, but this time t_hoff plays along
+ $tup->{t_infomask} |= HEAP_HASNULL;
+ $tup->{t_infomask2} |= (HEAP_NATTS_MASK & 0x40);
+ $tup->{t_bits} = 0xAA;
+ $tup->{t_hoff} = 32;
+
+ push @expected,
+ qr/${$header}number of attributes 67 exceeds maximum expected for table 3/;
+ }
+ elsif ($offnum == 12)
+ {
+ # Overwrite column 'b' 1-byte varlena header and initial characters to
+ # look like a long 4-byte varlena
+ #
+ # On little endian machines, bytes ending in two zero bits (xxxxxx00 bytes)
+ # are 4-byte length word, aligned, uncompressed data (up to 1G). We set the
+ # high six bits to 111111 and the lower two bits to 00, then the next three
+ # bytes with 0xFF using 0xFCFFFFFF.
+ #
+ # On big endian machines, bytes starting in two zero bits (00xxxxxx bytes)
+ # are 4-byte length word, aligned, uncompressed data (up to 1G). We set the
+ # low six bits to 111111 and the high two bits to 00, then the next three
+ # bytes with 0xFF using 0x3FFFFFFF.
+ #
+ $tup->{b_header} = $ENDIANNESS eq 'little' ? 0xFC : 0x3F;
+ $tup->{b_body1} = 0xFF;
+ $tup->{b_body2} = 0xFF;
+ $tup->{b_body3} = 0xFF;
+
+ $header = header(0, $offnum, 1);
+ push @expected,
+ qr/${header}attribute with length \d+ ends at offset \d+ beyond total tuple length \d+/;
+ }
+ elsif ($offnum == 13)
+ {
+ # Corrupt the bits in column 'c' toast pointer
+ $tup->{c_va_valueid} = 0xFFFFFFFF;
+
+ $header = header(0, $offnum, 2);
+ push @expected, qr/${header}toast value \d+ not found in toast table/;
+ }
+ elsif ($offnum == 14)
+ {
+ # Set both HEAP_XMAX_COMMITTED and HEAP_XMAX_IS_MULTI
+ $tup->{t_infomask} |= HEAP_XMAX_COMMITTED;
+ $tup->{t_infomask} |= HEAP_XMAX_IS_MULTI;
+ $tup->{t_xmax} = 4;
+
+ push @expected,
+ qr/${header}multitransaction ID 4 equals or exceeds next valid multitransaction ID 1/;
+ }
+ elsif ($offnum == 15) # Last offnum must equal ROWCOUNT
+ {
+ # Set both HEAP_XMAX_COMMITTED and HEAP_XMAX_IS_MULTI
+ $tup->{t_infomask} |= HEAP_XMAX_COMMITTED;
+ $tup->{t_infomask} |= HEAP_XMAX_IS_MULTI;
+ $tup->{t_xmax} = 4000000000;
+
+ push @expected,
+ qr/${header}multitransaction ID 4000000000 precedes relation minimum multitransaction ID threshold 1/;
+ }
+ write_tuple($file, $offset, $tup);
+}
+close($file)
+ or BAIL_OUT("close failed: $!");
+$node->start;
+
+# Run pg_amcheck against the corrupt table with epoch=0, comparing actual
+# corruption messages against the expected messages
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-dependent-indexes', '-p', $port, 'postgres' ],
+ 2, [@expected], [], 'Expected corruption message output');
+
+$node->teardown_node;
+$node->clean_node;
diff --git a/src/bin/pg_amcheck/t/005_opclass_damage.pl b/src/bin/pg_amcheck/t/005_opclass_damage.pl
new file mode 100644
index 0000000..30be684
--- /dev/null
+++ b/src/bin/pg_amcheck/t/005_opclass_damage.pl
@@ -0,0 +1,59 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# This regression test checks the behavior of the btree validation in the
+# presence of breaking sort order changes.
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 5;
+
+my $node = get_new_node('test');
+$node->init;
+$node->start;
+
+# Create a custom operator class and an index which uses it.
+$node->safe_psql(
+ 'postgres', q(
+ CREATE EXTENSION amcheck;
+
+ CREATE FUNCTION int4_asc_cmp (a int4, b int4) RETURNS int LANGUAGE sql AS $$
+ SELECT CASE WHEN $1 = $2 THEN 0 WHEN $1 > $2 THEN 1 ELSE -1 END; $$;
+
+ CREATE OPERATOR CLASS int4_fickle_ops FOR TYPE int4 USING btree AS
+ OPERATOR 1 < (int4, int4), OPERATOR 2 <= (int4, int4),
+ OPERATOR 3 = (int4, int4), OPERATOR 4 >= (int4, int4),
+ OPERATOR 5 > (int4, int4), FUNCTION 1 int4_asc_cmp(int4, int4);
+
+ CREATE TABLE int4tbl (i int4);
+ INSERT INTO int4tbl (SELECT * FROM generate_series(1,1000) gs);
+ CREATE INDEX fickleidx ON int4tbl USING btree (i int4_fickle_ops);
+));
+
+# We have not yet broken the index, so we should get no corruption
+$node->command_like(
+ [ 'pg_amcheck', '-p', $node->port, 'postgres' ],
+ qr/^$/,
+ 'pg_amcheck all schemas, tables and indexes reports no corruption');
+
+# Change the operator class to use a function which sorts in a different
+# order to corrupt the btree index
+$node->safe_psql(
+ 'postgres', q(
+ CREATE FUNCTION int4_desc_cmp (int4, int4) RETURNS int LANGUAGE sql AS $$
+ SELECT CASE WHEN $1 = $2 THEN 0 WHEN $1 > $2 THEN -1 ELSE 1 END; $$;
+ UPDATE pg_catalog.pg_amproc
+ SET amproc = 'int4_desc_cmp'::regproc
+ WHERE amproc = 'int4_asc_cmp'::regproc
+));
+
+# Index corruption should now be reported
+$node->command_checks_all(
+ [ 'pg_amcheck', '-p', $node->port, 'postgres' ],
+ 2,
+ [qr/item order invariant violated for index "fickleidx"/],
+ [],
+ 'pg_amcheck all schemas, tables and indexes reports fickleidx corruption'
+);