diff --git a/contrib/vacuumlo/vacuumlo.c b/contrib/vacuumlo/vacuumlo.c index 974172e3d74ac7f07e7ba9e4a6f384a1452aa3ae..641a8c3425d97614a14a547f91651f0bbd83eae8 100644 --- a/contrib/vacuumlo/vacuumlo.c +++ b/contrib/vacuumlo/vacuumlo.c @@ -29,8 +29,7 @@ extern char *optarg; extern int optind, - opterr, - optopt; + opterr; enum trivalue { @@ -50,16 +49,16 @@ struct _param long transaction_limit; }; -int vacuumlo(char *, struct _param *); -void usage(const char *progname); +static int vacuumlo(const char *database, const struct _param * param); +static void usage(const char *progname); /* * This vacuums LOs of one database. It returns 0 on success, -1 on failure. */ -int -vacuumlo(char *database, struct _param * param) +static int +vacuumlo(const char *database, const struct _param * param) { PGconn *conn; PGresult *res, @@ -72,6 +71,7 @@ vacuumlo(char *database, struct _param * param) bool new_pass; bool success = true; + /* Note: password can be carried over from a previous call */ if (param->pg_prompt == TRI_YES && password == NULL) password = simple_prompt("Password: ", 100, false); @@ -119,7 +119,7 @@ vacuumlo(char *database, struct _param * param) if (param->verbose) { - fprintf(stdout, "Connected to %s\n", database); + fprintf(stdout, "Connected to database \"%s\"\n", database); if (param->dry_run) fprintf(stdout, "Test run: no large objects will be removed!\n"); } @@ -220,9 +220,21 @@ vacuumlo(char *database, struct _param * param) if (param->verbose) fprintf(stdout, "Checking %s in %s.%s\n", field, schema, table); + schema = PQescapeIdentifier(conn, schema, strlen(schema)); + table = PQescapeIdentifier(conn, table, strlen(table)); + field = PQescapeIdentifier(conn, field, strlen(field)); + + if (!schema || !table || !field) + { + fprintf(stderr, "Out of memory\n"); + PQclear(res); + PQfinish(conn); + return -1; + } + snprintf(buf, BUFSIZE, "DELETE FROM vacuum_l " - "WHERE lo IN (SELECT \"%s\" FROM \"%s\".\"%s\")", + "WHERE lo IN (SELECT %s FROM %s.%s)", field, schema, table); res2 = PQexec(conn, buf); if (PQresultStatus(res2) != PGRES_COMMAND_OK) @@ -236,23 +248,35 @@ vacuumlo(char *database, struct _param * param) return -1; } PQclear(res2); + + PQfreemem(schema); + PQfreemem(table); + PQfreemem(field); } PQclear(res); /* - * Run the actual deletes in a single transaction. Note that this would - * be a bad idea in pre-7.1 Postgres releases (since rolling back a table - * delete used to cause problems), but it should be safe now. + * Now, those entries remaining in vacuum_l are orphans. Delete 'em. + * + * We don't want to run each delete as an individual transaction, because + * the commit overhead would be high. However, since 9.0 the backend will + * acquire a lock per deleted LO, so deleting too many LOs per transaction + * risks running out of room in the shared-memory lock table. + * Accordingly, we delete up to transaction_limit LOs per transaction. */ res = PQexec(conn, "begin"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "Failed to start transaction:\n"); + fprintf(stderr, "%s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } PQclear(res); - /* - * Finally, those entries remaining in vacuum_l are orphans. - */ buf[0] = '\0'; - strcat(buf, "SELECT lo "); - strcat(buf, "FROM vacuum_l"); + strcat(buf, "SELECT lo FROM vacuum_l"); res = PQexec(conn, buf); if (PQresultStatus(res) != PGRES_TUPLES_OK) { @@ -292,15 +316,47 @@ vacuumlo(char *database, struct _param * param) } else deleted++; - if (param->transaction_limit != 0 && deleted >= param->transaction_limit) - break; + if (param->transaction_limit > 0 && + (deleted % param->transaction_limit) == 0) + { + res2 = PQexec(conn, "commit"); + if (PQresultStatus(res2) != PGRES_COMMAND_OK) + { + fprintf(stderr, "Failed to commit transaction:\n"); + fprintf(stderr, "%s", PQerrorMessage(conn)); + PQclear(res2); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res2); + res2 = PQexec(conn, "begin"); + if (PQresultStatus(res2) != PGRES_COMMAND_OK) + { + fprintf(stderr, "Failed to start transaction:\n"); + fprintf(stderr, "%s", PQerrorMessage(conn)); + PQclear(res2); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res2); + } } PQclear(res); /* * That's all folks! */ - res = PQexec(conn, "end"); + res = PQexec(conn, "commit"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + fprintf(stderr, "Failed to commit transaction:\n"); + fprintf(stderr, "%s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } PQclear(res); PQfinish(conn); @@ -308,28 +364,28 @@ vacuumlo(char *database, struct _param * param) if (param->verbose) { if (param->dry_run) - fprintf(stdout, "\rWould remove %ld large objects from %s.\n", + fprintf(stdout, "\rWould remove %ld large objects from database \"%s\".\n", deleted, database); else if (success) fprintf(stdout, - "\rSuccessfully removed %ld large objects from %s.\n", + "\rSuccessfully removed %ld large objects from database \"%s\".\n", deleted, database); else - fprintf(stdout, "\rRemoval from %s failed at object %ld of %ld.\n", + fprintf(stdout, "\rRemoval from database \"%s\" failed at object %ld of %ld.\n", database, deleted, matched); } return ((param->dry_run || success) ? 0 : -1); } -void +static void usage(const char *progname) { printf("%s removes unreferenced large objects from databases.\n\n", progname); printf("Usage:\n %s [OPTION]... DBNAME...\n\n", progname); printf("Options:\n"); printf(" -h HOSTNAME database server host or socket directory\n"); - printf(" -l LIMIT stop after removing LIMIT large objects\n"); + printf(" -l LIMIT commit after removing each LIMIT large objects\n"); printf(" -n don't remove large objects, just show what would be done\n"); printf(" -p PORT database server port\n"); printf(" -U USERNAME user name to connect as\n"); @@ -354,15 +410,16 @@ main(int argc, char **argv) progname = get_progname(argv[0]); - /* Parameter handling */ + /* Set default parameter values */ param.pg_user = NULL; param.pg_prompt = TRI_DEFAULT; param.pg_host = NULL; param.pg_port = NULL; param.verbose = 0; param.dry_run = 0; - param.transaction_limit = 0; + param.transaction_limit = 1000; + /* Process command-line arguments */ if (argc > 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) @@ -397,6 +454,16 @@ main(int argc, char **argv) param.dry_run = 1; param.verbose = 1; break; + case 'l': + param.transaction_limit = strtol(optarg, NULL, 10); + if (param.transaction_limit < 0) + { + fprintf(stderr, + "%s: transaction limit must not be negative (0 disables)\n", + progname); + exit(1); + } + break; case 'U': param.pg_user = strdup(optarg); break; @@ -415,16 +482,6 @@ main(int argc, char **argv) } param.pg_port = strdup(optarg); break; - case 'l': - param.transaction_limit = strtol(optarg, NULL, 10); - if (param.transaction_limit < 0) - { - fprintf(stderr, - "%s: transaction limit must not be negative (0 disables)\n", - progname); - exit(1); - } - break; case 'h': param.pg_host = strdup(optarg); break; @@ -435,7 +492,7 @@ main(int argc, char **argv) if (optind >= argc) { fprintf(stderr, "vacuumlo: missing required argument: database name\n"); - fprintf(stderr, "Try 'vacuumlo -?' for help.\n"); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); exit(1); } diff --git a/doc/src/sgml/vacuumlo.sgml b/doc/src/sgml/vacuumlo.sgml index 0ae39c71d2c00c5c9f27e195bbf5ad1423a945cb..97753de6c0c3ab0b9a045cbd36b89df25bc66a77 100644 --- a/doc/src/sgml/vacuumlo.sgml +++ b/doc/src/sgml/vacuumlo.sgml @@ -50,19 +50,22 @@ vacuumlo [options] database [database2 ... databaseN] </varlistentry> <varlistentry> - <term><option>-U</option> <replaceable>username</></term> + <term><option>-l</option> <replaceable>limit</></term> <listitem> - <para>User name to connect as.</para> + <para> + Remove no more than <replaceable>limit</> large objects per + transaction (default 1000). Since the server acquires a lock per LO + removed, removing too many LOs in one transaction risks exceeding + <xref linkend="guc-max-locks-per-transaction">. Set the limit to + zero if you want all removals done in a single transaction. + </para> </listitem> </varlistentry> <varlistentry> - <term><option>-l</option> <replaceable>limit</></term> + <term><option>-U</option> <replaceable>username</></term> <listitem> - <para> - Stop after removing LIMIT large objects. Useful to avoid - exceeding <xref linkend="guc-max-locks-per-transaction">. - </para> + <para>User name to connect as.</para> </listitem> </varlistentry> @@ -120,18 +123,19 @@ vacuumlo [options] database [database2 ... databaseN] <title>Method</title> <para> - First, it builds a temporary table which contains all of the OIDs of the - large objects in that database. + First, <application>vacuumlo</> builds a temporary table which contains all + of the OIDs of the large objects in the selected database. </para> <para> It then scans through all columns in the database that are of type <type>oid</> or <type>lo</>, and removes matching entries from the - temporary table. + temporary table. (Note: only types with these names are considered; + in particular, domains over them are not considered.) </para> <para> - The remaining entries in the temp table identify orphaned LOs. + The remaining entries in the temporary table identify orphaned LOs. These are removed. </para> </sect2>