On Sun, Mar 06, 2022 at 09:39:37AM +0100, Gilles Darold wrote: > Attached a new patch version that adds the -N | --exclude-schema option > to the vacuumdb command as suggested. Documentation updated too. > > + pg_log_error("cannot vacuum all tables in schema(s) and and > exclude specific schema(s) at the same time");
and and It's odd that schema_exclusion is a global var, but schemas/excluded are not. Also, it seems unnecessary to have two schemas vars, since they can't be used together. Maybe there's a better way than what I did in 003. > + for (cell = schemas ? schemas->head : NULL; cell; cell = > cell->next) It's preferred to write cell != NULL > + bool schemas_listed = false; ... > + for (cell = schemas ? schemas->head : NULL; cell; cell = > cell->next) > + { > + if (!schemas_listed) { > + appendPQExpBufferStr(&catalog_query, > + " AND > pg_catalog.quote_ident(ns.nspname)"); > + if (schema_exclusion) > + appendPQExpBufferStr(&catalog_query, " > NOT IN ("); > + else > + appendPQExpBufferStr(&catalog_query, " > IN ("); > + > + schemas_listed = true; > + } > + else > + appendPQExpBufferStr(&catalog_query, ", "); > + > + appendStringLiteralConn(&catalog_query, cell->val, > conn); > + appendPQExpBufferStr(&catalog_query, > "::pg_catalog.regnamespace::pg_catalog.name"); > + > + } > + /* Finish formatting schema filter */ > + if (schemas_listed) > + appendPQExpBufferStr(&catalog_query, ")\n"); > } Maybe it's clearer to write this with =ANY() / != ALL() ? See 002. -- Justin
>From 9f1b7f2fb0849a810dc1fa5c1c03d5ff4e2e7d55 Mon Sep 17 00:00:00 2001 From: Gilles Darold <gil...@migops.com> Date: Sun, 6 Mar 2022 09:39:37 +0100 Subject: [PATCH 1/3] vacuumdb --schema only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le 04/03/2022 à 11:56, Justin Pryzby a écrit : > On Fri, Mar 04, 2022 at 10:11:28AM +0100, Gilles Darold wrote: >> The attached patch implements that. Option -n | --schema can be used >> multiple time and can not be used together with options -a or -t. > Yes, thanks. > > I suggest there should also be an --exclude-schema. > >> I do not propose to extend the VACUUM and ANALYZE commands because their >> current syntax doesn't allow me to see an easy way to do that > I think this would be easy with the parenthesized syntax. > I'm not suggesting to do it there, though. > >> + /* >> + * When filtereing on schema name, filter by table is not allowed. >> + * The schema name can already be set in a fqdn table name. > set *to* > Attached a new patch version that adds the -N | --exclude-schema option to the vacuumdb command as suggested. Documentation updated too. I will add this patch to the commitfest unless there is cons about adding these options. -- Gilles Darold --- doc/src/sgml/ref/vacuumdb.sgml | 64 ++++++++++++++++++++++ src/bin/scripts/t/100_vacuumdb.pl | 9 +++ src/bin/scripts/t/101_vacuumdb_all.pl | 3 + src/bin/scripts/vacuumdb.c | 79 ++++++++++++++++++++++++++- 4 files changed, 154 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml index 956c0f01cbc..378328afb3d 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 96a818a3c16..4c4f47e32a4 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,12 @@ $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".*/, + 'vacuumdb --schema schema only'); +$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'); done_testing(); diff --git a/src/bin/scripts/t/101_vacuumdb_all.pl b/src/bin/scripts/t/101_vacuumdb_all.pl index 1dcf4117671..b122c995b15 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 4f6917fd392..3dca22e1c88 100644 --- a/src/bin/scripts/vacuumdb.c +++ b/src/bin/scripts/vacuumdb.c @@ -46,10 +46,12 @@ typedef struct vacuumingOptions bool process_toast; } vacuumingOptions; +static bool schema_exclusion = false; static void vacuum_one_database(ConnParams *cparams, vacuumingOptions *vacopts, int stage, + SimpleStringList *schemas, SimpleStringList *tables, int concurrentCons, const char *progname, bool echo, bool quiet); @@ -94,6 +96,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}, @@ -125,6 +129,8 @@ main(int argc, char *argv[]) SimpleStringList tables = {NULL, NULL}; int concurrentCons = 1; int tbl_count = 0; + SimpleStringList schemas = {NULL, NULL}; + SimpleStringList schemas_excluded = {NULL, NULL}; /* initialize options */ memset(&vacopts, 0, sizeof(vacopts)); @@ -140,7 +146,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) { @@ -202,6 +208,13 @@ main(int argc, char *argv[]) &vacopts.parallel_workers)) exit(1); break; + case 'n': /* include schema(s) */ + simple_string_list_append(&schemas, optarg); + break; + case 'N': /* exclude schema(s) */ + simple_string_list_append(&schemas_excluded, optarg); + schema_exclusion = true; + break; case 2: maintenance_db = pg_strdup(optarg); break; @@ -341,6 +354,26 @@ main(int argc, char *argv[]) setup_cancel_handler(NULL); + /* + * When filtereing on schema name, filter by table is not allowed. + * The schema name can already be set to a fqdn table name. + */ + if (tbl_count && (schemas.head != NULL || schemas_excluded.head != NULL)) + { + pg_log_error("cannot vacuum all tables in schema(s) and specific table(s) at the same time"); + exit(1); + } + + /* + * Options -n | --schema and -N | --exclude-schema + * can not be used together + */ + if (schemas.head != NULL && schemas_excluded.head != NULL) + { + pg_log_error("cannot vacuum all tables in schema(s) and and exclude specific schema(s) at the same time"); + exit(1); + } + /* Avoid opening extra connections. */ if (tbl_count && (concurrentCons > tbl_count)) concurrentCons = tbl_count; @@ -352,6 +385,19 @@ main(int argc, char *argv[]) pg_log_error("cannot vacuum all databases and a specific one at the same time"); exit(1); } + + if (schemas.head != NULL) + { + pg_log_error("cannot vacuum specific schema(s) in all databases"); + exit(1); + } + + if (schemas_excluded.head != NULL) + { + pg_log_error("cannot exclude from vacuum specific schema(s) in all databases"); + exit(1); + } + if (tables.head != NULL) { pg_log_error("cannot vacuum specific table(s) in all databases"); @@ -387,6 +433,7 @@ main(int argc, char *argv[]) { vacuum_one_database(&cparams, &vacopts, stage, + (schema_exclusion) ? &schemas_excluded : &schemas, &tables, concurrentCons, progname, echo, quiet); @@ -395,6 +442,7 @@ main(int argc, char *argv[]) else vacuum_one_database(&cparams, &vacopts, ANALYZE_NO_STAGE, + (schema_exclusion) ? &schemas_excluded : &schemas, &tables, concurrentCons, progname, echo, quiet); @@ -420,6 +468,7 @@ static void vacuum_one_database(ConnParams *cparams, vacuumingOptions *vacopts, int stage, + SimpleStringList *schemas, SimpleStringList *tables, int concurrentCons, const char *progname, bool echo, bool quiet) @@ -436,6 +485,7 @@ vacuum_one_database(ConnParams *cparams, int ntups; bool failed = false; bool tables_listed = false; + bool schemas_listed = false; bool has_where = false; const char *initcmd; const char *stage_commands[] = { @@ -618,6 +668,29 @@ vacuum_one_database(ConnParams *cparams, CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) "])\n"); has_where = true; + + for (cell = schemas ? schemas->head : NULL; cell; cell = cell->next) + { + if (!schemas_listed) { + appendPQExpBufferStr(&catalog_query, + " AND pg_catalog.quote_ident(ns.nspname)"); + if (schema_exclusion) + appendPQExpBufferStr(&catalog_query, " NOT IN ("); + else + appendPQExpBufferStr(&catalog_query, " IN ("); + + schemas_listed = true; + } + else + appendPQExpBufferStr(&catalog_query, ", "); + + appendStringLiteralConn(&catalog_query, cell->val, conn); + appendPQExpBufferStr(&catalog_query, "::pg_catalog.regnamespace::pg_catalog.name"); + + } + /* Finish formatting schema filter */ + if (schemas_listed) + appendPQExpBufferStr(&catalog_query, ")\n"); } /* @@ -814,6 +887,7 @@ vacuum_all_databases(ConnParams *cparams, vacuum_one_database(cparams, vacopts, stage, NULL, + NULL, concurrentCons, progname, echo, quiet); } @@ -828,6 +902,7 @@ vacuum_all_databases(ConnParams *cparams, vacuum_one_database(cparams, vacopts, ANALYZE_NO_STAGE, NULL, + NULL, concurrentCons, progname, echo, quiet); } @@ -1027,6 +1102,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")); -- 2.17.1
>From 14d518ae31e0110a4325f922882955ead554ca64 Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Sun, 6 Mar 2022 08:18:20 -0600 Subject: [PATCH 2/3] f1 --- src/bin/scripts/vacuumdb.c | 48 ++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c index 3dca22e1c88..f76d8472c1f 100644 --- a/src/bin/scripts/vacuumdb.c +++ b/src/bin/scripts/vacuumdb.c @@ -485,7 +485,6 @@ vacuum_one_database(ConnParams *cparams, int ntups; bool failed = false; bool tables_listed = false; - bool schemas_listed = false; bool has_where = false; const char *initcmd; const char *stage_commands[] = { @@ -655,42 +654,41 @@ vacuum_one_database(ConnParams *cparams, if (tables_listed) appendPQExpBufferStr(&catalog_query, " JOIN listed_tables" " ON listed_tables.table_oid OPERATOR(pg_catalog.=) c.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 (!tables_listed) + else { + /* + * 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. + */ appendPQExpBufferStr(&catalog_query, " WHERE c.relkind OPERATOR(pg_catalog.=) ANY (array[" CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) "])\n"); has_where = true; - for (cell = schemas ? schemas->head : NULL; cell; cell = cell->next) + if (schemas != NULL && schemas->head != NULL) { - if (!schemas_listed) { + appendPQExpBufferStr(&catalog_query, + " AND c.relnamespace"); + if (schema_exclusion) appendPQExpBufferStr(&catalog_query, - " AND pg_catalog.quote_ident(ns.nspname)"); - if (schema_exclusion) - appendPQExpBufferStr(&catalog_query, " NOT IN ("); - else - appendPQExpBufferStr(&catalog_query, " IN ("); - - schemas_listed = true; - } + " OPERATOR(pg_catalog.!=) ALL (ARRAY["); else - appendPQExpBufferStr(&catalog_query, ", "); + appendPQExpBufferStr(&catalog_query, + " OPERATOR(pg_catalog.=) ANY (ARRAY["); - appendStringLiteralConn(&catalog_query, cell->val, conn); - appendPQExpBufferStr(&catalog_query, "::pg_catalog.regnamespace::pg_catalog.name"); + for (cell = schemas->head; cell != NULL; cell = cell->next) + { + appendStringLiteralConn(&catalog_query, cell->val, conn); + + if (cell->next != NULL) + appendPQExpBufferStr(&catalog_query, ", "); + } + /* Finish formatting schema filter */ + appendPQExpBufferStr(&catalog_query, "]::pg_catalog.regnamespace[])\n"); } - /* Finish formatting schema filter */ - if (schemas_listed) - appendPQExpBufferStr(&catalog_query, ")\n"); } /* -- 2.17.1
>From 139a9cb7acd969848ee55fc4c08619cff4376c66 Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Sun, 6 Mar 2022 09:02:13 -0600 Subject: [PATCH 3/3] f2 --- src/bin/scripts/vacuumdb.c | 51 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c index f76d8472c1f..9ed2b95d7a5 100644 --- a/src/bin/scripts/vacuumdb.c +++ b/src/bin/scripts/vacuumdb.c @@ -46,7 +46,7 @@ typedef struct vacuumingOptions bool process_toast; } vacuumingOptions; -static bool schema_exclusion = false; +enum trivalue schema_is_exclude = TRI_DEFAULT; static void vacuum_one_database(ConnParams *cparams, vacuumingOptions *vacopts, @@ -130,7 +130,6 @@ main(int argc, char *argv[]) int concurrentCons = 1; int tbl_count = 0; SimpleStringList schemas = {NULL, NULL}; - SimpleStringList schemas_excluded = {NULL, NULL}; /* initialize options */ memset(&vacopts, 0, sizeof(vacopts)); @@ -209,11 +208,24 @@ main(int argc, char *argv[]) exit(1); break; case 'n': /* include schema(s) */ + if (schema_is_exclude == TRI_YES) + { + pg_log_error("cannot vacuum all tables in schema(s) and exclude specific schema(s) at the same time"); + exit(1); + } + simple_string_list_append(&schemas, optarg); + schema_is_exclude = TRI_NO; break; case 'N': /* exclude schema(s) */ - simple_string_list_append(&schemas_excluded, optarg); - schema_exclusion = true; + if (schema_is_exclude == TRI_NO) + { + pg_log_error("cannot vacuum all tables in schema(s) and exclude specific schema(s) at the same time"); + exit(1); + } + + simple_string_list_append(&schemas, optarg); + schema_is_exclude = TRI_YES; break; case 2: maintenance_db = pg_strdup(optarg); @@ -358,22 +370,12 @@ main(int argc, char *argv[]) * When filtereing on schema name, filter by table is not allowed. * The schema name can already be set to a fqdn table name. */ - if (tbl_count && (schemas.head != NULL || schemas_excluded.head != NULL)) + if (tbl_count && (schemas.head != NULL)) { pg_log_error("cannot vacuum all tables in schema(s) and specific table(s) at the same time"); exit(1); } - /* - * Options -n | --schema and -N | --exclude-schema - * can not be used together - */ - if (schemas.head != NULL && schemas_excluded.head != NULL) - { - pg_log_error("cannot vacuum all tables in schema(s) and and exclude specific schema(s) at the same time"); - exit(1); - } - /* Avoid opening extra connections. */ if (tbl_count && (concurrentCons > tbl_count)) concurrentCons = tbl_count; @@ -388,13 +390,10 @@ main(int argc, char *argv[]) if (schemas.head != NULL) { - pg_log_error("cannot vacuum specific schema(s) in all databases"); - exit(1); - } - - if (schemas_excluded.head != NULL) - { - pg_log_error("cannot exclude from vacuum specific schema(s) in all databases"); + if (schema_is_exclude == TRI_YES) + pg_log_error("cannot exclude from vacuum specific schema(s) in all databases"); + else if (schema_is_exclude == TRI_NO) + pg_log_error("cannot vacuum specific schema(s) in all databases"); exit(1); } @@ -433,7 +432,7 @@ main(int argc, char *argv[]) { vacuum_one_database(&cparams, &vacopts, stage, - (schema_exclusion) ? &schemas_excluded : &schemas, + &schemas, &tables, concurrentCons, progname, echo, quiet); @@ -442,7 +441,7 @@ main(int argc, char *argv[]) else vacuum_one_database(&cparams, &vacopts, ANALYZE_NO_STAGE, - (schema_exclusion) ? &schemas_excluded : &schemas, + &schemas, &tables, concurrentCons, progname, echo, quiet); @@ -671,10 +670,10 @@ vacuum_one_database(ConnParams *cparams, { appendPQExpBufferStr(&catalog_query, " AND c.relnamespace"); - if (schema_exclusion) + if (schema_is_exclude == TRI_YES) appendPQExpBufferStr(&catalog_query, " OPERATOR(pg_catalog.!=) ALL (ARRAY["); - else + else if (schema_is_exclude == TRI_NO) appendPQExpBufferStr(&catalog_query, " OPERATOR(pg_catalog.=) ANY (ARRAY["); -- 2.17.1