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"));

Reply via email to