Le 20/04/2022 à 19:38, Nathan Bossart a écrit :
Thanks for the new patch! I think this is on the right track.
On Wed, Apr 20, 2022 at 05:15:02PM +0200, Gilles Darold wrote:
Le 18/04/2022 à 23:56, Nathan Bossart a écrit :
- if (!tables_listed)
+ if (!objects_listed || objfilter == OBJFILTER_SCHEMA)
Do we need to check for objects_listed here? IIUC we can just check for
objfilter != OBJFILTER_TABLE.
Yes we need it otherwise test 'vacuumdb with view' fail because we are not
trying to vacuum the view so the PG doesn't report:
WARNING: cannot vacuum non-tables or special system tables
My point is that the only time we don't want to filter for relevant
relation types is when the user provides a list of tables. So my
suggestion would be to simplify this to the following:
if (objfilter != OBJFILTER_TABLE)
{
appendPQExpBufferStr(...);
has_where = true;
}
Right, I must have gotten mixed up in the test results. Fixed.
Unless I'm missing something, schema_is_exclude appears to only be used for
error checking and doesn't impact the generated catalog query. It looks
like the relevant logic disappeared after v4 of the patch.
Right, removed.
I don't think -N works at the moment. I tested it out, and vacuumdb was
still processing tables in schemas I excluded. Can we add a test case for
this, too?
Fixed and regression tests added as well as some others to test the
filter options compatibility.
+/*
+ * Verify that the filters used at command line are compatible
+ */
+void
+check_objfilter(VacObjectFilter curr_objfilter, VacObjectFilter curr_option)
+{
+ switch (curr_option)
+ {
+ case OBJFILTER_NONE:
+ break;
+ case OBJFILTER_DATABASE:
+ /* When filtering on database name, vacuum on all
database is not allowed. */
+ if (curr_objfilter == OBJFILTER_ALL)
+ pg_fatal("cannot vacuum all databases and a specific
one at the same time");
+ break;
[...]
+ }
+}
I don't think this handles all combinations. For example, the following
command does not fail:
vacuumdb -a -N test postgres
Right, I have fix them all in this new patch.
Furthermore, do you think it'd be possible to dynamically generate the
message? If it doesn't add too much complexity, this might be a nice way
to simplify this function.
I have tried to avoid reusing the same error message several time by
using a new enum and function filter_error(). I also use the same
messages with --schema and --exclude-schema related errors.
Patch v10 attached.
--
Gilles Darold
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 956c0f01cb..0de001ef24 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -39,6 +39,40 @@ PostgreSQL documentation
<arg choice="opt"><replaceable>dbname</replaceable></arg>
</cmdsynopsis>
+ <cmdsynopsis>
+ <command>vacuumdb</command>
+ <arg rep="repeat"><replaceable>connection-option</replaceable></arg>
+ <arg rep="repeat"><replaceable>option</replaceable></arg>
+
+ <arg choice="plain" rep="repeat">
+ <arg choice="opt">
+ <group choice="plain">
+ <arg choice="plain">
+ <arg choice="opt">
+ <group choice="plain">
+ <arg choice="plain"><option>-n</option></arg>
+ <arg choice="plain"><option>--schema</option></arg>
+ </group>
+ <replaceable>schema</replaceable>
+ </arg>
+ </arg>
+
+ <arg choice="plain">
+ <arg choice="opt">
+ <group choice="plain">
+ <arg choice="plain"><option>-N</option></arg>
+ <arg choice="plain"><option>--exclude-schema</option></arg>
+ </group>
+ <replaceable>schema</replaceable>
+ </arg>
+ </arg>
+ </group>
+ </arg>
+ </arg>
+
+ <arg choice="opt"><replaceable>dbname</replaceable></arg>
+ </cmdsynopsis>
+
<cmdsynopsis>
<command>vacuumdb</command>
<arg rep="repeat"><replaceable>connection-option</replaceable></arg>
@@ -244,6 +278,28 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
+ <term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
+ <listitem>
+ <para>
+ Clean or analyze all tables in <replaceable class="parameter">schema</replaceable> only.
+ Multiple schemas can be vacuumed by writing multiple <option>-n</option> switches.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-N <replaceable class="parameter">schema</replaceable></option></term>
+ <term><option>--exclude-schema=<replaceable class="parameter">schema</replaceable></option></term>
+ <listitem>
+ <para>
+ Clean or analyze all tables NOT in <replaceable class="parameter">schema</replaceable>.
+ Multiple schemas can be excluded from the vacuum by writing multiple <option>-N</option> switches.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--no-index-cleanup</option></term>
<listitem>
@@ -619,6 +675,14 @@ PostgreSQL documentation
<prompt>$ </prompt><userinput>vacuumdb --analyze --verbose --table='foo(bar)' xyzzy</userinput>
</screen></para>
+ <para>
+ To clean all tables in the <literal>foo</literal> and <literal>bar</literal> schemas
+ only in a database named <literal>xyzzy</literal>:
+<screen>
+<prompt>$ </prompt><userinput>vacuumdb --schema='foo' --schema='bar' xyzzy</userinput>
+</screen></para>
+
+
</refsect1>
<refsect1>
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index 96a818a3c1..9ef5c789e0 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -103,6 +103,8 @@ $node->safe_psql(
CREATE TABLE funcidx (x int);
INSERT INTO funcidx VALUES (0),(1),(2),(3);
CREATE INDEX i0 ON funcidx ((f1(x)));
+ CREATE SCHEMA "Foo";
+ CREATE TABLE "Foo".bar(id int);
|);
$node->command_ok([qw|vacuumdb -Z --table="need""q(uot"(")x") postgres|],
'column list');
@@ -146,5 +148,38 @@ $node->issues_sql_like(
[ 'vacuumdb', '--min-xid-age', '2147483001', 'postgres' ],
qr/GREATEST.*relfrozenxid.*2147483001/,
'vacuumdb --table --min-xid-age');
+$node->issues_sql_like(
+ [ 'vacuumdb', '--schema', '"Foo"', 'postgres' ],
+ qr/VACUUM "Foo".bar/,
+ 'vacuumdb --schema schema only');
+$node->issues_sql_like(
+ [ 'vacuumdb', '--exclude-schema', '"Foo"', 'postgres' ],
+ qr/(?:(?!VACUUM "Foo".bar).)*/,
+ 'vacuumdb --exclude-schema schema');
+$node->command_fails(
+ [ 'vacuumdb', '-n', 'pg_catalog', '-t', 'pg_class', 'postgres' ],
+ 'cannot vacuum all tables in schema(s) and specific table(s) at the same time');
+$node->command_fails(
+ [ 'vacuumdb', '-n', 'pg_catalog', '-N', '"Foo"', 'postgres' ],
+ 'cannot use option -n | --schema and -N | --exclude-schema at the same time');
+$node->command_fails(
+ [ 'vacuumdb', '-a', '-N', '"Foo"' ],
+ 'cannot use option -a and -N | --exclude-schema at the same time');
+$node->command_fails(
+ [ 'vacuumdb', '-a', '-n', '"Foo"' ],
+ 'cannot use option -a and -n | --schema at the same time');
+$node->command_fails(
+ [ 'vacuumdb', '-a', '-t', '"Foo".bar' ],
+ 'cannot use option -a and -t | --table at the same time');
+$node->command_fails(
+ [ 'vacuumdb', '-a', '-n', '"Foo"', 'postgres' ],
+ 'cannot use option -a and -n | --schema at the same time');
+$node->command_fails(
+ [ 'vacuumdb', '-a', '-d', 'postgres' ],
+ 'cannot use option -a and -d | --dbname at the same time');
+$node->command_fails(
+ [ 'vacuumdb', '-a', 'postgres' ],
+ 'cannot use option -a and a dbname as argument at the same time');
+
done_testing();
diff --git a/src/bin/scripts/t/101_vacuumdb_all.pl b/src/bin/scripts/t/101_vacuumdb_all.pl
index 1dcf411767..b122c995b1 100644
--- a/src/bin/scripts/t/101_vacuumdb_all.pl
+++ b/src/bin/scripts/t/101_vacuumdb_all.pl
@@ -15,5 +15,8 @@ $node->issues_sql_like(
[ 'vacuumdb', '-a' ],
qr/statement: VACUUM.*statement: VACUUM/s,
'vacuum all databases');
+$node->command_fails(
+ [ 'vacuumdb', '-a', '-n', 'pg_catalog' ],
+ 'cannot vacuum specific schema(s) in all databases');
done_testing();
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 92f1ffe147..28408ed36b 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,11 +46,41 @@ typedef struct vacuumingOptions
bool process_toast;
} vacuumingOptions;
+/*
+ * The kind of object use in the command line filter.
+ * OBJFILTER_NONE: no filter used
+ * OBJFILTER_TABLE: -t | --table
+ * OBJFILTER_SCHEMA: -n | --schema
+ * OBJFILTER_SCHEMA_EXCLUDE: -N | --exclude-schema
+ * OBJFILTER_DATABASE: -d | --dbname
+ * OBJFILTER_ALL: -a | --all
+ */
+typedef enum
+{
+ OBJFILTER_NONE,
+ OBJFILTER_ALL,
+ OBJFILTER_DATABASE,
+ OBJFILTER_TABLE,
+ OBJFILTER_SCHEMA,
+ OBJFILTER_SCHEMA_EXCLUDE
+} VacObjectFilter;
+
+VacObjectFilter objfilter = OBJFILTER_NONE;
+
+
+typedef enum
+{
+ FILTER_ERR_ALL,
+ FILTER_ERR_SCHEMA_ALL,
+ FILTER_ERR_TABLE_ALL,
+ FILTER_ERR_SCHEMA_TABLE
+} VacFilterErrorr;
+
static void vacuum_one_database(ConnParams *cparams,
vacuumingOptions *vacopts,
int stage,
- SimpleStringList *tables,
+ SimpleStringList *objects,
int concurrentCons,
const char *progname, bool echo, bool quiet);
@@ -68,11 +98,14 @@ static void run_vacuum_command(PGconn *conn, const char *sql, bool echo,
static void help(const char *progname);
+void check_objfilter(VacObjectFilter curr_objfilter, VacObjectFilter curr_option);
+
+void filter_error(VacFilterErrorr filter_error);
+
/* For analyze-in-stages mode */
#define ANALYZE_NO_STAGE -1
#define ANALYZE_NUM_STAGES 3
-
int
main(int argc, char *argv[])
{
@@ -94,6 +127,8 @@ main(int argc, char *argv[])
{"verbose", no_argument, NULL, 'v'},
{"jobs", required_argument, NULL, 'j'},
{"parallel", required_argument, NULL, 'P'},
+ {"schema", required_argument, NULL, 'n'},
+ {"exclude-schema", required_argument, NULL, 'N'},
{"maintenance-db", required_argument, NULL, 2},
{"analyze-in-stages", no_argument, NULL, 3},
{"disable-page-skipping", no_argument, NULL, 4},
@@ -122,7 +157,7 @@ main(int argc, char *argv[])
vacuumingOptions vacopts;
bool analyze_in_stages = false;
bool alldb = false;
- SimpleStringList tables = {NULL, NULL};
+ SimpleStringList objects = {NULL, NULL};
int concurrentCons = 1;
int tbl_count = 0;
@@ -140,7 +175,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "vacuumdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWeqd:zZFat:fvj:P:", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeqd:zZFat:fvj:P:n:N:", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -166,6 +201,8 @@ main(int argc, char *argv[])
quiet = true;
break;
case 'd':
+ check_objfilter(objfilter, OBJFILTER_DATABASE);
+ objfilter = OBJFILTER_DATABASE;
dbname = pg_strdup(optarg);
break;
case 'z':
@@ -178,11 +215,15 @@ main(int argc, char *argv[])
vacopts.freeze = true;
break;
case 'a':
+ check_objfilter(objfilter, OBJFILTER_ALL);
+ objfilter = OBJFILTER_ALL;
alldb = true;
break;
case 't':
{
- simple_string_list_append(&tables, optarg);
+ check_objfilter(objfilter, OBJFILTER_TABLE);
+ simple_string_list_append(&objects, optarg);
+ objfilter = OBJFILTER_TABLE;
tbl_count++;
break;
}
@@ -202,6 +243,20 @@ main(int argc, char *argv[])
&vacopts.parallel_workers))
exit(1);
break;
+ case 'n': /* include schema(s) */
+ {
+ check_objfilter(objfilter, OBJFILTER_SCHEMA);
+ simple_string_list_append(&objects, optarg);
+ objfilter = OBJFILTER_SCHEMA;
+ break;
+ }
+ case 'N': /* exclude schema(s) */
+ {
+ check_objfilter(objfilter, OBJFILTER_SCHEMA_EXCLUDE);
+ simple_string_list_append(&objects, optarg);
+ objfilter = OBJFILTER_SCHEMA_EXCLUDE;
+ break;
+ }
case 2:
maintenance_db = pg_strdup(optarg);
break;
@@ -249,6 +304,7 @@ main(int argc, char *argv[])
*/
if (optind < argc && dbname == NULL)
{
+ check_objfilter(objfilter, OBJFILTER_DATABASE);
dbname = argv[optind];
optind++;
}
@@ -318,11 +374,6 @@ main(int argc, char *argv[])
if (alldb)
{
- if (dbname)
- pg_fatal("cannot vacuum all databases and a specific one at the same time");
- if (tables.head != NULL)
- pg_fatal("cannot vacuum specific table(s) in all databases");
-
cparams.dbname = maintenance_db;
vacuum_all_databases(&cparams, &vacopts,
@@ -352,7 +403,7 @@ main(int argc, char *argv[])
{
vacuum_one_database(&cparams, &vacopts,
stage,
- &tables,
+ &objects,
concurrentCons,
progname, echo, quiet);
}
@@ -360,7 +411,7 @@ main(int argc, char *argv[])
else
vacuum_one_database(&cparams, &vacopts,
ANALYZE_NO_STAGE,
- &tables,
+ &objects,
concurrentCons,
progname, echo, quiet);
}
@@ -368,6 +419,83 @@ main(int argc, char *argv[])
exit(0);
}
+/*
+ * Verify that the filters used at command line are compatible
+ */
+void
+check_objfilter(VacObjectFilter curr_objfilter, VacObjectFilter curr_option)
+{
+ switch (curr_option)
+ {
+ case OBJFILTER_NONE:
+ break;
+ case OBJFILTER_DATABASE:
+ /* When filtering on database name, vacuum on all database is not allowed. */
+ if (curr_objfilter == OBJFILTER_ALL)
+ filter_error(FILTER_ERR_ALL);
+ break;
+ case OBJFILTER_ALL:
+ /* When vacuuming all database, filter on database name is not allowed. */
+ if (curr_objfilter == OBJFILTER_DATABASE)
+ filter_error(FILTER_ERR_ALL);
+ /* When vacuuming all database, filter on schema name is not allowed. */
+ if (curr_objfilter == OBJFILTER_SCHEMA
+ || curr_objfilter == OBJFILTER_SCHEMA_EXCLUDE)
+ filter_error(FILTER_ERR_SCHEMA_ALL);
+ /* When vacuuming all database, filter on table name is not allowed. */
+ if (curr_objfilter == OBJFILTER_TABLE)
+ filter_error(FILTER_ERR_TABLE_ALL);
+ break;
+ case OBJFILTER_TABLE:
+ /* When filtering on table name, filter by schema is not allowed. */
+ if (curr_objfilter == OBJFILTER_SCHEMA
+ || curr_objfilter == OBJFILTER_SCHEMA_EXCLUDE)
+ filter_error(FILTER_ERR_SCHEMA_TABLE);
+ /* When vacuuming all database, filter on table name is not allowed. */
+ if (curr_objfilter == OBJFILTER_ALL)
+ filter_error(FILTER_ERR_TABLE_ALL);
+ break;
+ case OBJFILTER_SCHEMA:
+ /* When filtering on schema name, filter by table is not allowed. */
+ if (curr_objfilter == OBJFILTER_TABLE
+ || curr_objfilter == OBJFILTER_SCHEMA_EXCLUDE)
+ filter_error(FILTER_ERR_SCHEMA_TABLE);
+ /* filtering on schema name can not be use on all database. */
+ if (curr_objfilter == OBJFILTER_ALL)
+ filter_error(FILTER_ERR_SCHEMA_ALL);
+ break;
+ case OBJFILTER_SCHEMA_EXCLUDE:
+ /* When filtering on schema exclusion, filter by table is not allowed. */
+ if (curr_objfilter == OBJFILTER_TABLE
+ || curr_objfilter == OBJFILTER_SCHEMA)
+ filter_error(FILTER_ERR_SCHEMA_TABLE);
+ /* filtering on schema name can not be use on all database. */
+ if (curr_objfilter == OBJFILTER_ALL)
+ filter_error(FILTER_ERR_SCHEMA_ALL);
+ break;
+ }
+}
+
+void
+filter_error(VacFilterErrorr filter_error)
+{
+ switch (filter_error)
+ {
+ case FILTER_ERR_ALL:
+ pg_fatal("cannot vacuum all databases and a specific one at the same time");
+ break;
+ case FILTER_ERR_SCHEMA_ALL:
+ pg_fatal("cannot vacuum specific schema(s) in all databases");
+ break;
+ case FILTER_ERR_TABLE_ALL:
+ pg_fatal("cannot vacuum specific table(s) in all databases");
+ break;
+ case FILTER_ERR_SCHEMA_TABLE:
+ pg_fatal("cannot vacuum all tables in schema(s) and specific table(s) at the same time");
+ break;
+ }
+}
+
/*
* vacuum_one_database
*
@@ -385,7 +513,7 @@ static void
vacuum_one_database(ConnParams *cparams,
vacuumingOptions *vacopts,
int stage,
- SimpleStringList *tables,
+ SimpleStringList *objects,
int concurrentCons,
const char *progname, bool echo, bool quiet)
{
@@ -400,7 +528,7 @@ vacuum_one_database(ConnParams *cparams,
int i;
int ntups;
bool failed = false;
- bool tables_listed = false;
+ bool objects_listed = false;
bool has_where = false;
const char *initcmd;
const char *stage_commands[] = {
@@ -499,31 +627,41 @@ vacuum_one_database(ConnParams *cparams,
* catalog query will fail.
*/
initPQExpBuffer(&catalog_query);
- for (cell = tables ? tables->head : NULL; cell; cell = cell->next)
+ for (cell = objects ? objects->head : NULL; cell; cell = cell->next)
{
- char *just_table;
- const char *just_columns;
-
- /*
- * Split relation and column names given by the user, this is used to
- * feed the CTE with values on which are performed pre-run validity
- * checks as well. For now these happen only on the relation name.
- */
- splitTableColumnsSpec(cell->val, PQclientEncoding(conn),
- &just_table, &just_columns);
+ char *just_table = NULL;
+ const char *just_columns = NULL;
- if (!tables_listed)
+ if (!objects_listed)
{
appendPQExpBufferStr(&catalog_query,
- "WITH listed_tables (table_oid, column_list) "
+ "WITH listed_objects (object_oid, column_list) "
"AS (\n VALUES (");
- tables_listed = true;
+ objects_listed = true;
}
else
appendPQExpBufferStr(&catalog_query, ",\n (");
- appendStringLiteralConn(&catalog_query, just_table, conn);
- appendPQExpBufferStr(&catalog_query, "::pg_catalog.regclass, ");
+
+ if (objfilter == OBJFILTER_SCHEMA || objfilter == OBJFILTER_SCHEMA_EXCLUDE)
+ {
+ appendStringLiteralConn(&catalog_query, cell->val, conn);
+ appendPQExpBufferStr(&catalog_query, "::pg_catalog.regnamespace::pg_catalog.oid, ");
+ }
+
+ if (objfilter == OBJFILTER_TABLE)
+ {
+ /*
+ * Split relation and column names given by the user, this is used to
+ * feed the CTE with values on which are performed pre-run validity
+ * checks as well. For now these happen only on the relation name.
+ */
+ splitTableColumnsSpec(cell->val, PQclientEncoding(conn),
+ &just_table, &just_columns);
+
+ appendStringLiteralConn(&catalog_query, just_table, conn);
+ appendPQExpBufferStr(&catalog_query, "::pg_catalog.regclass, ");
+ }
if (just_columns && just_columns[0] != '\0')
appendStringLiteralConn(&catalog_query, just_columns, conn);
@@ -536,13 +674,13 @@ vacuum_one_database(ConnParams *cparams,
}
/* Finish formatting the CTE */
- if (tables_listed)
+ if (objects_listed)
appendPQExpBufferStr(&catalog_query, "\n)\n");
appendPQExpBufferStr(&catalog_query, "SELECT c.relname, ns.nspname");
- if (tables_listed)
- appendPQExpBufferStr(&catalog_query, ", listed_tables.column_list");
+ if (objects_listed)
+ appendPQExpBufferStr(&catalog_query, ", listed_objects.column_list");
appendPQExpBufferStr(&catalog_query,
" FROM pg_catalog.pg_class c\n"
@@ -551,18 +689,31 @@ vacuum_one_database(ConnParams *cparams,
" LEFT JOIN pg_catalog.pg_class t"
" ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
- /* Used to match the tables listed by the user */
- if (tables_listed)
- appendPQExpBufferStr(&catalog_query, " JOIN listed_tables"
- " ON listed_tables.table_oid OPERATOR(pg_catalog.=) c.oid\n");
+ /* Used to match the tables or schemas listed by the user */
+ if (objects_listed)
+ {
+ appendPQExpBufferStr(&catalog_query, " JOIN listed_objects"
+ " ON listed_objects.object_oid ");
+
+ if (objfilter == OBJFILTER_SCHEMA_EXCLUDE)
+ appendPQExpBufferStr(&catalog_query, "OPERATOR(pg_catalog.!=) ");
+ else
+ appendPQExpBufferStr(&catalog_query, "OPERATOR(pg_catalog.=) ");
+
+ if (objfilter == OBJFILTER_TABLE)
+ appendPQExpBufferStr(&catalog_query, "c.oid\n");
+ else if (objfilter == OBJFILTER_SCHEMA || objfilter == OBJFILTER_SCHEMA_EXCLUDE)
+ appendPQExpBufferStr(&catalog_query, "ns.oid\n");
+ }
/*
- * If no tables were listed, filter for the relevant relation types. If
- * tables were given via --table, don't bother filtering by relation type.
- * Instead, let the server decide whether a given relation can be
- * processed in which case the user will know about it.
+ * If no tables were listed, filter for the relevant relation types.
+ * If tables were given via --table, don't bother filtering by relation
+ * type. Instead, let the server decide whether a given relation can
+ * be processed in which case the user will know about it.
*/
- if (!tables_listed)
+ if (!objects_listed || objfilter == OBJFILTER_SCHEMA
+ || objfilter == OBJFILTER_SCHEMA_EXCLUDE)
{
appendPQExpBufferStr(&catalog_query, " WHERE c.relkind OPERATOR(pg_catalog.=) ANY (array["
CppAsString2(RELKIND_RELATION) ", "
@@ -633,7 +784,7 @@ vacuum_one_database(ConnParams *cparams,
fmtQualifiedId(PQgetvalue(res, i, 1),
PQgetvalue(res, i, 0)));
- if (tables_listed && !PQgetisnull(res, i, 2))
+ if (objects_listed && !PQgetisnull(res, i, 2))
appendPQExpBufferStr(&buf, PQgetvalue(res, i, 2));
simple_string_list_append(&dbtables, buf.data);
@@ -977,6 +1128,8 @@ help(const char *progname)
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-toast skip the TOAST table associated with the table to vacuum\n"));
printf(_(" --no-truncate don't truncate empty pages at the end of the table\n"));
+ printf(_(" -n, --schema=PATTERN vacuum tables in the specified schema(s) only\n"));
+ printf(_(" -N, --exclude-schema=PATTERN do NOT vacuum tables in the specified schema(s)\n"));
printf(_(" -P, --parallel=PARALLEL_WORKERS use this many background workers for vacuum, if available\n"));
printf(_(" -q, --quiet don't write any messages\n"));
printf(_(" --skip-locked skip relations that cannot be immediately locked\n"));