diff --git a/doc/pgprobackup.xml b/doc/pgprobackup.xml
index 76ec2cd76..76333b116 100644
--- a/doc/pgprobackup.xml
+++ b/doc/pgprobackup.xml
@@ -4176,7 +4176,7 @@ pg_probackup restore -B backup_dir --instance backup_dir] [--instance instance_name] [-D data_dir]
[--help] [-j num_threads] [--progress]
-[--skip-block-validation] [--amcheck] [--heapallindexed]
+[--skip-block-validation] [--amcheck [--checkunique] [--heapallindexed]]
[connection_options] [logging_options]
@@ -4195,17 +4195,24 @@ pg_probackup checkdb
extension or the amcheck_next extension
installed in the database to check its indexes. For databases
without amcheck, index verification will be skipped.
+ Additional options and
+ are effective depending on the version of amcheck installed.
-
+
- Skip validation of data files. You can use this flag only
- together with the flag, so that only logical
- verification of indexes is performed.
+ Verifies unique constraints during logical verification of indexes.
+ You can use this flag only together with the flag when
+ the amcheck extension is
+ installed in the database.
+
+
+ This verification is only possible if it is supported by the version of the
+ amcheck extension you are using.
@@ -4219,12 +4226,24 @@ pg_probackup checkdb
flag.
- This check is only possible if you are using the
- amcheck extension of version 2.0 or higher, or
- the amcheck_next extension of any version.
+ This check is only possible if it is supported by the version of the
+ amcheck extension you are using or
+ if the amcheck_next extension is used instead.
+
+
+
+
+
+
+
+
+ Skip validation of data files. You can use this flag only
+ together with the flag, so that only logical
+ verification of indexes is performed.
+
diff --git a/src/checkdb.c b/src/checkdb.c
index e3f2df538..177fc3cc7 100644
--- a/src/checkdb.c
+++ b/src/checkdb.c
@@ -83,6 +83,7 @@ typedef struct pg_indexEntry
char *name;
char *namespace;
bool heapallindexed_is_supported;
+ bool checkunique_is_supported;
/* schema where amcheck extension is located */
char *amcheck_nspname;
/* lock for synchronization of parallel threads */
@@ -351,10 +352,14 @@ get_index_list(const char *dbname, bool first_db_with_amcheck,
{
PGresult *res;
char *amcheck_nspname = NULL;
+ char *amcheck_extname = NULL;
+ char *amcheck_extversion = NULL;
int i;
bool heapallindexed_is_supported = false;
+ bool checkunique_is_supported = false;
parray *index_list = NULL;
+ /* Check amcheck extension version */
res = pgut_execute(db_conn, "SELECT "
"extname, nspname, extversion "
"FROM pg_catalog.pg_namespace n "
@@ -379,24 +384,68 @@ get_index_list(const char *dbname, bool first_db_with_amcheck,
return NULL;
}
+ amcheck_extname = pgut_malloc(strlen(PQgetvalue(res, 0, 0)) + 1);
+ strcpy(amcheck_extname, PQgetvalue(res, 0, 0));
amcheck_nspname = pgut_malloc(strlen(PQgetvalue(res, 0, 1)) + 1);
strcpy(amcheck_nspname, PQgetvalue(res, 0, 1));
+ amcheck_extversion = pgut_malloc(strlen(PQgetvalue(res, 0, 2)) + 1);
+ strcpy(amcheck_extversion, PQgetvalue(res, 0, 2));
+ PQclear(res);
/* heapallindexed_is_supported is database specific */
- if (strcmp(PQgetvalue(res, 0, 2), "1.0") != 0 &&
- strcmp(PQgetvalue(res, 0, 2), "1") != 0)
+ /* TODO this is wrong check, heapallindexed supported also in 1.1.1, 1.2 and 1.2.1... */
+ if (strcmp(amcheck_extversion, "1.0") != 0 &&
+ strcmp(amcheck_extversion, "1") != 0)
heapallindexed_is_supported = true;
elog(INFO, "Amchecking database '%s' using extension '%s' "
"version %s from schema '%s'",
- dbname, PQgetvalue(res, 0, 0),
- PQgetvalue(res, 0, 2), PQgetvalue(res, 0, 1));
+ dbname, amcheck_extname,
+ amcheck_extversion, amcheck_nspname);
if (!heapallindexed_is_supported && heapallindexed)
elog(WARNING, "Extension '%s' version %s in schema '%s'"
"do not support 'heapallindexed' option",
- PQgetvalue(res, 0, 0), PQgetvalue(res, 0, 2),
- PQgetvalue(res, 0, 1));
+ amcheck_extname, amcheck_extversion,
+ amcheck_nspname);
+
+#ifndef PGPRO_EE
+ /*
+ * Will support when the vanilla patch will commited https://commitfest.postgresql.org/32/2976/
+ */
+ checkunique_is_supported = false;
+#else
+ /*
+ * Check bt_index_check function signature to determine support of checkunique parameter
+ * This can't be exactly checked by checking extension version,
+ * For example, 1.1.1 and 1.2.1 supports this parameter, but 1.2 doesn't (PGPROEE-12.4.1)
+ */
+ res = pgut_execute(db_conn, "SELECT "
+ " oid "
+ "FROM pg_catalog.pg_proc "
+ "WHERE "
+ " pronamespace = $1::regnamespace "
+ "AND proname = 'bt_index_check' "
+ "AND 'checkunique' = ANY(proargnames) "
+ "AND (pg_catalog.string_to_array(proargtypes::text, ' ')::regtype[])[pg_catalog.array_position(proargnames, 'checkunique')] = 'bool'::regtype",
+ 1, (const char **) &amcheck_nspname);
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ PQclear(res);
+ elog(ERROR, "Cannot check 'checkunique' option is supported in bt_index_check function %s: %s",
+ dbname, PQerrorMessage(db_conn));
+ }
+
+ checkunique_is_supported = PQntuples(res) >= 1;
+ PQclear(res);
+#endif
+
+ if (!checkunique_is_supported && checkunique)
+ elog(WARNING, "Extension '%s' version %s in schema '%s' "
+ "do not support 'checkunique' parameter",
+ amcheck_extname, amcheck_extversion,
+ amcheck_nspname);
/*
* In order to avoid duplicates, select global indexes
@@ -453,6 +502,7 @@ get_index_list(const char *dbname, bool first_db_with_amcheck,
strcpy(ind->namespace, namespace); /* enough buffer size guaranteed */
ind->heapallindexed_is_supported = heapallindexed_is_supported;
+ ind->checkunique_is_supported = checkunique_is_supported;
ind->amcheck_nspname = pgut_malloc(strlen(amcheck_nspname) + 1);
strcpy(ind->amcheck_nspname, amcheck_nspname);
pg_atomic_clear_flag(&ind->lock);
@@ -464,6 +514,9 @@ get_index_list(const char *dbname, bool first_db_with_amcheck,
}
PQclear(res);
+ free(amcheck_extversion);
+ free(amcheck_nspname);
+ free(amcheck_extname);
return index_list;
}
@@ -473,38 +526,46 @@ static bool
amcheck_one_index(check_indexes_arg *arguments,
pg_indexEntry *ind)
{
- PGresult *res;
- char *params[2];
+ PGresult *res;
+ char *params[3];
+ static const char *queries[] = {
+ "SELECT %s.bt_index_check(index => $1)",
+ "SELECT %s.bt_index_check(index => $1, heapallindexed => $2)",
+ "SELECT %s.bt_index_check(index => $1, heapallindexed => $2, checkunique => $3)",
+ };
+ int params_count;
char *query = NULL;
- params[0] = palloc(64);
+ if (interrupted)
+ elog(ERROR, "Interrupted");
+#define INDEXRELID 0
+#define HEAPALLINDEXED 1
+#define CHECKUNIQUE 2
/* first argument is index oid */
- sprintf(params[0], "%u", ind->indexrelid);
+ params[INDEXRELID] = palloc(64);
+ sprintf(params[INDEXRELID], "%u", ind->indexrelid);
/* second argument is heapallindexed */
- params[1] = heapallindexed ? "true" : "false";
+ params[HEAPALLINDEXED] = heapallindexed ? "true" : "false";
+ /* third optional argument is checkunique */
+ params[CHECKUNIQUE] = checkunique ? "true" : "false";
+#undef CHECKUNIQUE
+#undef HEAPALLINDEXED
- if (interrupted)
- elog(ERROR, "Interrupted");
-
- if (ind->heapallindexed_is_supported)
- {
- query = palloc(strlen(ind->amcheck_nspname)+strlen("SELECT .bt_index_check($1, $2)")+1);
- sprintf(query, "SELECT %s.bt_index_check($1, $2)", ind->amcheck_nspname);
+ params_count = ind->checkunique_is_supported ?
+ 3 :
+ ( ind->heapallindexed_is_supported ? 2 : 1 );
- res = pgut_execute_parallel(arguments->conn_arg.conn,
- arguments->conn_arg.cancel_conn,
- query, 2, (const char **)params, true, true, true);
- }
- else
- {
- query = palloc(strlen(ind->amcheck_nspname)+strlen("SELECT .bt_index_check($1)")+1);
- sprintf(query, "SELECT %s.bt_index_check($1)", ind->amcheck_nspname);
+ /*
+ * Prepare query text with schema name
+ * +1 for \0 and -2 for %s
+ */
+ query = palloc(strlen(ind->amcheck_nspname) + strlen(queries[params_count - 1]) + 1 - 2);
+ sprintf(query, queries[params_count - 1], ind->amcheck_nspname);
- res = pgut_execute_parallel(arguments->conn_arg.conn,
+ res = pgut_execute_parallel(arguments->conn_arg.conn,
arguments->conn_arg.cancel_conn,
- query, 1, (const char **)params, true, true, true);
- }
+ query, params_count, (const char **)params, true, true, true);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
@@ -512,7 +573,7 @@ amcheck_one_index(check_indexes_arg *arguments,
arguments->thread_num, arguments->conn_opt.pgdatabase,
ind->namespace, ind->name, PQresultErrorMessage(res));
- pfree(params[0]);
+ pfree(params[INDEXRELID]);
pfree(query);
PQclear(res);
return false;
@@ -522,7 +583,8 @@ amcheck_one_index(check_indexes_arg *arguments,
arguments->thread_num,
arguments->conn_opt.pgdatabase, ind->namespace, ind->name);
- pfree(params[0]);
+ pfree(params[INDEXRELID]);
+#undef INDEXRELID
pfree(query);
PQclear(res);
return true;
diff --git a/src/help.c b/src/help.c
index a6530fc0e..a494ab209 100644
--- a/src/help.c
+++ b/src/help.c
@@ -190,7 +190,7 @@ help_pg_probackup(void)
printf(_("\n %s checkdb [-B backup-path] [--instance=instance_name]\n"), PROGRAM_NAME);
printf(_(" [-D pgdata-path] [--progress] [-j num-threads]\n"));
printf(_(" [--amcheck] [--skip-block-validation]\n"));
- printf(_(" [--heapallindexed]\n"));
+ printf(_(" [--heapallindexed] [--checkunique]\n"));
printf(_(" [--help]\n"));
printf(_("\n %s show -B backup-path\n"), PROGRAM_NAME);
@@ -601,7 +601,7 @@ help_checkdb(void)
printf(_("\n%s checkdb [-B backup-path] [--instance=instance_name]\n"), PROGRAM_NAME);
printf(_(" [-D pgdata-path] [-j num-threads] [--progress]\n"));
printf(_(" [--amcheck] [--skip-block-validation]\n"));
- printf(_(" [--heapallindexed]\n\n"));
+ printf(_(" [--heapallindexed] [--checkunique]\n\n"));
printf(_(" -B, --backup-path=backup-path location of the backup storage area\n"));
printf(_(" --instance=instance_name name of the instance\n"));
@@ -616,6 +616,8 @@ help_checkdb(void)
printf(_(" using 'amcheck' or 'amcheck_next' extensions\n"));
printf(_(" --heapallindexed also check that heap is indexed\n"));
printf(_(" can be used only with '--amcheck' option\n"));
+ printf(_(" --checkunique also check unique constraints\n"));
+ printf(_(" can be used only with '--amcheck' option\n"));
printf(_("\n Logging options:\n"));
printf(_(" --log-level-console=log-level-console\n"));
diff --git a/src/pg_probackup.c b/src/pg_probackup.c
index 49e226ace..c5ed13175 100644
--- a/src/pg_probackup.c
+++ b/src/pg_probackup.c
@@ -126,6 +126,7 @@ static parray *exclude_relative_paths_list = NULL;
/* checkdb options */
bool need_amcheck = false;
bool heapallindexed = false;
+bool checkunique = false;
bool amcheck_parent = false;
/* delete options */
@@ -240,6 +241,7 @@ static ConfigOption cmd_options[] =
/* checkdb options */
{ 'b', 195, "amcheck", &need_amcheck, SOURCE_CMD_STRICT },
{ 'b', 196, "heapallindexed", &heapallindexed, SOURCE_CMD_STRICT },
+ { 'b', 198, "checkunique", &checkunique, SOURCE_CMD_STRICT },
{ 'b', 197, "parent", &amcheck_parent, SOURCE_CMD_STRICT },
/* delete options */
{ 'b', 145, "wal", &delete_wal, SOURCE_CMD_STRICT },
@@ -596,6 +598,16 @@ main(int argc, char *argv[])
instance_config.pgdata == NULL)
elog(ERROR, "required parameter not specified: --instance");
+ /* Check checkdb command options consistency */
+ if (backup_subcmd == CHECKDB_CMD &&
+ !need_amcheck)
+ {
+ if (heapallindexed)
+ elog(ERROR, "--heapallindexed can only be used with --amcheck option");
+ if (checkunique)
+ elog(ERROR, "--checkunique can only be used with --amcheck option");
+ }
+
/* Usually checkdb for file logging requires log_directory
* to be specified explicitly, but if backup_dir and instance name are provided,
* checkdb can use the usual default values or values from config
diff --git a/src/pg_probackup.h b/src/pg_probackup.h
index b828343dc..aed8fde86 100644
--- a/src/pg_probackup.h
+++ b/src/pg_probackup.h
@@ -829,6 +829,7 @@ extern ShowFormat show_format;
/* checkdb options */
extern bool heapallindexed;
+extern bool checkunique;
extern bool skip_block_validation;
/* current settings */
diff --git a/tests/checkdb.py b/tests/checkdb.py
index 044c057f6..9b7adcd71 100644
--- a/tests/checkdb.py
+++ b/tests/checkdb.py
@@ -211,6 +211,7 @@ def test_checkdb_amcheck_only_sanity(self):
# Clean after yourself
gdb.kill()
+ node.stop()
self.del_test_dir(module_name, fname)
# @unittest.skip("skip")
@@ -349,6 +350,7 @@ def test_basic_checkdb_amcheck_only_sanity(self):
log_file_content)
# Clean after yourself
+ node.stop()
self.del_test_dir(module_name, fname)
# @unittest.skip("skip")
@@ -445,6 +447,98 @@ def test_checkdb_block_validation_sanity(self):
e.message)
# Clean after yourself
+ node.stop()
+ self.del_test_dir(module_name, fname)
+
+ def test_checkdb_checkunique(self):
+ """Test checkunique parameter of amcheck.bt_index_check function"""
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ initdb_params=['--data-checksums'])
+ node.slow_start()
+
+ try:
+ node.safe_psql(
+ "postgres",
+ "create extension amcheck")
+ except QueryException as e:
+ node.safe_psql(
+ "postgres",
+ "create extension amcheck_next")
+
+ # Part of https://commitfest.postgresql.org/32/2976/ patch test
+ node.safe_psql(
+ "postgres",
+ "CREATE TABLE bttest_unique(a varchar(50), b varchar(1500), c bytea, d varchar(50)); "
+ "ALTER TABLE bttest_unique SET (autovacuum_enabled = false); "
+ "CREATE UNIQUE INDEX bttest_unique_idx ON bttest_unique(a,b); "
+ "UPDATE pg_catalog.pg_index SET indisunique = false "
+ "WHERE indrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'bttest_unique'); "
+ "INSERT INTO bttest_unique "
+ " SELECT i::text::varchar, "
+ " array_to_string(array( "
+ " SELECT substr('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', ((random()*(36-1)+1)::integer), 1) "
+ " FROM generate_series(1,1300)),'')::varchar, "
+ " i::text::bytea, i::text::varchar "
+ " FROM generate_series(0,1) AS i, generate_series(0,30) AS x; "
+ "UPDATE pg_catalog.pg_index SET indisunique = true "
+ "WHERE indrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = 'bttest_unique'); "
+ "DELETE FROM bttest_unique WHERE ctid::text='(0,2)'; "
+ "DELETE FROM bttest_unique WHERE ctid::text='(4,2)'; "
+ "DELETE FROM bttest_unique WHERE ctid::text='(4,3)'; "
+ "DELETE FROM bttest_unique WHERE ctid::text='(9,3)';")
+
+ # run without checkunique option (error will not detected)
+ output = self.checkdb_node(
+ options=[
+ '--amcheck',
+ '--skip-block-validation',
+ '-d', 'postgres', '-p', str(node.port)])
+
+ self.assertIn(
+ 'INFO: checkdb --amcheck finished successfully',
+ output)
+ self.assertIn(
+ 'All checked indexes are valid',
+ output)
+
+ # run with checkunique option
+ try:
+ self.checkdb_node(
+ options=[
+ '--amcheck',
+ '--skip-block-validation',
+ '--checkunique',
+ '-d', 'postgres', '-p', str(node.port)])
+ if (ProbackupTest.enterprise and
+ (self.get_version(node) >= 111300 and self.get_version(node) < 120000
+ or self.get_version(node) >= 120800 and self.get_version(node) < 130000
+ or self.get_version(node) >= 130400)):
+ # we should die here because exception is what we expect to happen
+ self.assertEqual(
+ 1, 0,
+ "Expecting Error because of index corruption\n"
+ " Output: {0} \n CMD: {1}".format(
+ repr(self.output), self.cmd))
+ else:
+ self.assertRegex(
+ self.output,
+ r"WARNING: Extension 'amcheck(|_next)' version [\d.]* in schema 'public' do not support 'checkunique' parameter")
+ except ProbackupException as e:
+ self.assertIn(
+ "ERROR: checkdb --amcheck finished with failure. Not all checked indexes are valid. All databases were amchecked.",
+ e.message,
+ "\n Unexpected Error Message: {0}\n CMD: {1}".format(
+ repr(e.message), self.cmd))
+
+ self.assertIn(
+ "Amcheck failed in database 'postgres' for index: 'public.bttest_unique_idx': ERROR: index \"bttest_unique_idx\" is corrupted. There are tuples violating UNIQUE constraint",
+ e.message)
+
+ # Clean after yourself
+ node.stop()
self.del_test_dir(module_name, fname)
# @unittest.skip("skip")
@@ -502,6 +596,7 @@ def test_checkdb_sigint_handling(self):
# Clean after yourself
gdb.kill()
+ node.stop()
self.del_test_dir(module_name, fname)
# @unittest.skip("skip")
@@ -563,12 +658,15 @@ def test_checkdb_with_least_privileges(self):
'GRANT SELECT ON TABLE pg_catalog.pg_namespace TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.current_setting(text) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.set_config(text, text, boolean) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.texteq(text, text) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.nameeq(name, name) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.namene(name, name) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.int8(integer) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.oideq(oid, oid) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.charne("char", "char") TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.pg_is_in_recovery() TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.string_to_array(text, text) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.array_position(anyarray, anyelement) TO backup; '
'GRANT EXECUTE ON FUNCTION bt_index_check(regclass, bool) TO backup;' # amcheck-next function
)
# PG 9.6
@@ -588,6 +686,7 @@ def test_checkdb_with_least_privileges(self):
'GRANT SELECT ON TABLE pg_catalog.pg_namespace TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.current_setting(text) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.set_config(text, text, boolean) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.texteq(text, text) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.nameeq(name, name) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.namene(name, name) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.int8(integer) TO backup; '
@@ -595,6 +694,8 @@ def test_checkdb_with_least_privileges(self):
'GRANT EXECUTE ON FUNCTION pg_catalog.charne("char", "char") TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.pg_is_in_recovery() TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.pg_control_system() TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.string_to_array(text, text) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.array_position(anyarray, anyelement) TO backup; '
# 'GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO backup; '
'GRANT EXECUTE ON FUNCTION bt_index_check(regclass, bool) TO backup;'
)
@@ -615,13 +716,16 @@ def test_checkdb_with_least_privileges(self):
'GRANT SELECT ON TABLE pg_catalog.pg_namespace TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.current_setting(text) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.set_config(text, text, boolean) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.texteq(text, text) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.nameeq(name, name) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.namene(name, name) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.int8(integer) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.oideq(oid, oid) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.charne("char", "char") TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.pg_is_in_recovery() TO backup; '
- 'GRANT EXECUTE ON FUNCTION pg_catalog.pg_control_system() TO backup;'
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.pg_control_system() TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.string_to_array(text, text) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.array_position(anyarray, anyelement) TO backup;'
)
if ProbackupTest.enterprise:
# amcheck-1.1
@@ -633,7 +737,45 @@ def test_checkdb_with_least_privileges(self):
node.safe_psql(
'backupdb',
'GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO backup')
- # >= 11
+ # >= 11 < 14
+ elif self.get_version(node) > 110000 and self.get_version(node) < 140000:
+ node.safe_psql(
+ 'backupdb',
+ 'CREATE ROLE backup WITH LOGIN; '
+ 'GRANT CONNECT ON DATABASE backupdb to backup; '
+ 'GRANT USAGE ON SCHEMA pg_catalog TO backup; '
+ 'GRANT USAGE ON SCHEMA public TO backup; '
+ 'GRANT SELECT ON TABLE pg_catalog.pg_proc TO backup; '
+ 'GRANT SELECT ON TABLE pg_catalog.pg_extension TO backup; '
+ 'GRANT SELECT ON TABLE pg_catalog.pg_database TO backup; '
+ 'GRANT SELECT ON TABLE pg_catalog.pg_am TO backup; '
+ 'GRANT SELECT ON TABLE pg_catalog.pg_class TO backup; '
+ 'GRANT SELECT ON TABLE pg_catalog.pg_index TO backup; '
+ 'GRANT SELECT ON TABLE pg_catalog.pg_namespace TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.current_setting(text) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.set_config(text, text, boolean) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.texteq(text, text) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.nameeq(name, name) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.namene(name, name) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.int8(integer) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.oideq(oid, oid) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.charne("char", "char") TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.pg_is_in_recovery() TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.pg_control_system() TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.string_to_array(text, text) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.array_position(anyarray, anyelement) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION bt_index_check(regclass, bool) TO backup;'
+ )
+ # checkunique parameter
+ if ProbackupTest.enterprise:
+ if (self.get_version(node) >= 111300 and self.get_version(node) < 120000
+ or self.get_version(node) >= 120800 and self.get_version(node) < 130000
+ or self.get_version(node) >= 130400):
+ node.safe_psql(
+ "backupdb",
+ "GRANT EXECUTE ON FUNCTION bt_index_check(regclass, bool, bool) TO backup")
+ # >= 14
else:
node.safe_psql(
'backupdb',
@@ -650,6 +792,7 @@ def test_checkdb_with_least_privileges(self):
'GRANT SELECT ON TABLE pg_catalog.pg_namespace TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.current_setting(text) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.set_config(text, text, boolean) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.texteq(text, text) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.nameeq(name, name) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.namene(name, name) TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.int8(integer) TO backup; '
@@ -657,9 +800,16 @@ def test_checkdb_with_least_privileges(self):
'GRANT EXECUTE ON FUNCTION pg_catalog.charne("char", "char") TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.pg_is_in_recovery() TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.pg_control_system() TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.string_to_array(text, text) TO backup; '
+ 'GRANT EXECUTE ON FUNCTION pg_catalog.array_position(anycompatiblearray, anycompatible) TO backup; '
'GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO backup; '
'GRANT EXECUTE ON FUNCTION bt_index_check(regclass, bool) TO backup;'
)
+ # checkunique parameter
+ if ProbackupTest.enterprise:
+ node.safe_psql(
+ "backupdb",
+ "GRANT EXECUTE ON FUNCTION bt_index_check(regclass, bool, bool) TO backup")
if ProbackupTest.enterprise:
node.safe_psql(
@@ -700,4 +850,5 @@ def test_checkdb_with_least_privileges(self):
repr(e.message), self.cmd))
# Clean after yourself
+ node.stop()
self.del_test_dir(module_name, fname)
diff --git a/tests/expected/option_help.out b/tests/expected/option_help.out
index dd3c4e865..a8b4a64b3 100644
--- a/tests/expected/option_help.out
+++ b/tests/expected/option_help.out
@@ -107,7 +107,7 @@ pg_probackup - utility to manage backup/recovery of PostgreSQL database.
pg_probackup checkdb [-B backup-path] [--instance=instance_name]
[-D pgdata-path] [--progress] [-j num-threads]
[--amcheck] [--skip-block-validation]
- [--heapallindexed]
+ [--heapallindexed] [--checkunique]
[--help]
pg_probackup show -B backup-path