PostgreSQL Source Code git master
vacuumlo.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * vacuumlo.c
4 * This removes orphaned large objects from a database.
5 *
6 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 *
10 * IDENTIFICATION
11 * contrib/vacuumlo/vacuumlo.c
12 *
13 *-------------------------------------------------------------------------
14 */
15#include "postgres_fe.h"
16
17#include <sys/stat.h>
18#include <fcntl.h>
19#include <unistd.h>
20#ifdef HAVE_TERMIOS_H
21#include <termios.h>
22#endif
23
24#include "catalog/pg_class_d.h"
25#include "common/connect.h"
26#include "common/logging.h"
27#include "common/string.h"
28#include "getopt_long.h"
29#include "libpq-fe.h"
30#include "pg_getopt.h"
31
32#define BUFSIZE 1024
33
35{
39};
40
41struct _param
42{
43 char *pg_user;
45 char *pg_port;
46 char *pg_host;
47 const char *progname;
51};
52
53static int vacuumlo(const char *database, const struct _param *param);
54static void usage(const char *progname);
55
56
57
58/*
59 * This vacuums LOs of one database. It returns 0 on success, -1 on failure.
60 */
61static int
62vacuumlo(const char *database, const struct _param *param)
63{
64 PGconn *conn;
66 *res2;
67 char buf[BUFSIZE];
68 long matched;
69 long deleted;
70 int i;
71 bool new_pass;
72 bool success = true;
73 static char *password = NULL;
74
75 /* Note: password can be carried over from a previous call */
76 if (param->pg_prompt == TRI_YES && !password)
77 password = simple_prompt("Password: ", false);
78
79 /*
80 * Start the connection. Loop until we have a password if requested by
81 * backend.
82 */
83 do
84 {
85#define PARAMS_ARRAY_SIZE 7
86
87 const char *keywords[PARAMS_ARRAY_SIZE];
88 const char *values[PARAMS_ARRAY_SIZE];
89
90 keywords[0] = "host";
91 values[0] = param->pg_host;
92 keywords[1] = "port";
93 values[1] = param->pg_port;
94 keywords[2] = "user";
95 values[2] = param->pg_user;
96 keywords[3] = "password";
97 values[3] = password;
98 keywords[4] = "dbname";
99 values[4] = database;
100 keywords[5] = "fallback_application_name";
101 values[5] = param->progname;
102 keywords[6] = NULL;
103 values[6] = NULL;
104
105 new_pass = false;
107 if (!conn)
108 {
109 pg_log_error("connection to database \"%s\" failed", database);
110 return -1;
111 }
112
113 if (PQstatus(conn) == CONNECTION_BAD &&
115 !password &&
116 param->pg_prompt != TRI_NO)
117 {
118 PQfinish(conn);
119 password = simple_prompt("Password: ", false);
120 new_pass = true;
121 }
122 } while (new_pass);
123
124 /* check to see that the backend connection was successfully made */
126 {
128 PQfinish(conn);
129 return -1;
130 }
131
132 if (param->verbose)
133 {
134 fprintf(stdout, "Connected to database \"%s\"\n", database);
135 if (param->dry_run)
136 fprintf(stdout, "Test run: no large objects will be removed!\n");
137 }
138
141 {
142 pg_log_error("failed to set \"search_path\": %s", PQerrorMessage(conn));
143 PQclear(res);
144 PQfinish(conn);
145 return -1;
146 }
147 PQclear(res);
148
149 /*
150 * First we create and populate the LO temp table
151 */
152 buf[0] = '\0';
153 strcat(buf, "CREATE TEMP TABLE vacuum_l AS ");
154 if (PQserverVersion(conn) >= 90000)
155 strcat(buf, "SELECT oid AS lo FROM pg_largeobject_metadata");
156 else
157 strcat(buf, "SELECT DISTINCT loid AS lo FROM pg_largeobject");
158 res = PQexec(conn, buf);
160 {
161 pg_log_error("failed to create temp table: %s", PQerrorMessage(conn));
162 PQclear(res);
163 PQfinish(conn);
164 return -1;
165 }
166 PQclear(res);
167
168 /*
169 * Analyze the temp table so that planner will generate decent plans for
170 * the DELETEs below.
171 */
172 buf[0] = '\0';
173 strcat(buf, "ANALYZE vacuum_l");
174 res = PQexec(conn, buf);
176 {
177 pg_log_error("failed to vacuum temp table: %s", PQerrorMessage(conn));
178 PQclear(res);
179 PQfinish(conn);
180 return -1;
181 }
182 PQclear(res);
183
184 /*
185 * Now find any candidate tables that have columns of type oid.
186 *
187 * NOTE: we ignore system tables and temp tables by the expedient of
188 * rejecting tables in schemas named 'pg_*'. In particular, the temp
189 * table formed above is ignored, and pg_largeobject will be too. If
190 * either of these were scanned, obviously we'd end up with nothing to
191 * delete...
192 */
193 buf[0] = '\0';
194 strcat(buf, "SELECT s.nspname, c.relname, a.attname ");
195 strcat(buf, "FROM pg_class c, pg_attribute a, pg_namespace s, pg_type t ");
196 strcat(buf, "WHERE a.attnum > 0 AND NOT a.attisdropped ");
197 strcat(buf, " AND a.attrelid = c.oid ");
198 strcat(buf, " AND a.atttypid = t.oid ");
199 strcat(buf, " AND c.relnamespace = s.oid ");
200 strcat(buf, " AND t.typname in ('oid', 'lo') ");
201 strcat(buf, " AND c.relkind in (" CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) ")");
202 strcat(buf, " AND s.nspname !~ '^pg_'");
203 res = PQexec(conn, buf);
205 {
206 pg_log_error("failed to find OID columns: %s", PQerrorMessage(conn));
207 PQclear(res);
208 PQfinish(conn);
209 return -1;
210 }
211
212 for (i = 0; i < PQntuples(res); i++)
213 {
214 char *schema,
215 *table,
216 *field;
217
218 schema = PQgetvalue(res, i, 0);
219 table = PQgetvalue(res, i, 1);
220 field = PQgetvalue(res, i, 2);
221
222 if (param->verbose)
223 fprintf(stdout, "Checking %s in %s.%s\n", field, schema, table);
224
225 schema = PQescapeIdentifier(conn, schema, strlen(schema));
226 table = PQescapeIdentifier(conn, table, strlen(table));
227 field = PQescapeIdentifier(conn, field, strlen(field));
228
229 if (!schema || !table || !field)
230 {
232 PQclear(res);
233 PQfinish(conn);
234 PQfreemem(schema);
235 PQfreemem(table);
236 PQfreemem(field);
237 return -1;
238 }
239
241 "DELETE FROM vacuum_l "
242 "WHERE lo IN (SELECT %s FROM %s.%s)",
243 field, schema, table);
244 res2 = PQexec(conn, buf);
245 if (PQresultStatus(res2) != PGRES_COMMAND_OK)
246 {
247 pg_log_error("failed to check %s in table %s.%s: %s",
248 field, schema, table, PQerrorMessage(conn));
249 PQclear(res2);
250 PQclear(res);
251 PQfinish(conn);
252 PQfreemem(schema);
253 PQfreemem(table);
254 PQfreemem(field);
255 return -1;
256 }
257 PQclear(res2);
258
259 PQfreemem(schema);
260 PQfreemem(table);
261 PQfreemem(field);
262 }
263 PQclear(res);
264
265 /*
266 * Now, those entries remaining in vacuum_l are orphans. Delete 'em.
267 *
268 * We don't want to run each delete as an individual transaction, because
269 * the commit overhead would be high. However, since 9.0 the backend will
270 * acquire a lock per deleted LO, so deleting too many LOs per transaction
271 * risks running out of room in the shared-memory lock table. Accordingly,
272 * we delete up to transaction_limit LOs per transaction.
273 */
274 res = PQexec(conn, "begin");
276 {
277 pg_log_error("failed to start transaction: %s", PQerrorMessage(conn));
278 PQclear(res);
279 PQfinish(conn);
280 return -1;
281 }
282 PQclear(res);
283
284 buf[0] = '\0';
285 strcat(buf,
286 "DECLARE myportal CURSOR WITH HOLD FOR SELECT lo FROM vacuum_l");
287 res = PQexec(conn, buf);
289 {
290 pg_log_error("DECLARE CURSOR failed: %s", PQerrorMessage(conn));
291 PQclear(res);
292 PQfinish(conn);
293 return -1;
294 }
295 PQclear(res);
296
297 snprintf(buf, BUFSIZE, "FETCH FORWARD %ld IN myportal",
298 param->transaction_limit > 0 ? param->transaction_limit : 1000L);
299
300 deleted = 0;
301
302 do
303 {
304 res = PQexec(conn, buf);
306 {
307 pg_log_error("FETCH FORWARD failed: %s", PQerrorMessage(conn));
308 PQclear(res);
309 PQfinish(conn);
310 return -1;
311 }
312
313 matched = PQntuples(res);
314 if (matched <= 0)
315 {
316 /* at end of resultset */
317 PQclear(res);
318 break;
319 }
320
321 for (i = 0; i < matched; i++)
322 {
323 Oid lo = atooid(PQgetvalue(res, i, 0));
324
325 if (param->verbose)
326 {
327 fprintf(stdout, "\rRemoving lo %6u ", lo);
328 fflush(stdout);
329 }
330
331 if (param->dry_run == 0)
332 {
333 if (lo_unlink(conn, lo) < 0)
334 {
335 pg_log_error("failed to remove lo %u: %s", lo,
338 {
339 success = false;
340 break; /* out of inner for-loop */
341 }
342 }
343 else
344 deleted++;
345 }
346 else
347 deleted++;
348
349 if (param->transaction_limit > 0 &&
350 (deleted % param->transaction_limit) == 0)
351 {
352 res2 = PQexec(conn, "commit");
353 if (PQresultStatus(res2) != PGRES_COMMAND_OK)
354 {
355 pg_log_error("failed to commit transaction: %s",
357 PQclear(res2);
358 PQclear(res);
359 PQfinish(conn);
360 return -1;
361 }
362 PQclear(res2);
363 res2 = PQexec(conn, "begin");
364 if (PQresultStatus(res2) != PGRES_COMMAND_OK)
365 {
366 pg_log_error("failed to start transaction: %s",
368 PQclear(res2);
369 PQclear(res);
370 PQfinish(conn);
371 return -1;
372 }
373 PQclear(res2);
374 }
375 }
376
377 PQclear(res);
378 } while (success);
379
380 /*
381 * That's all folks!
382 */
383 res = PQexec(conn, "commit");
385 {
386 pg_log_error("failed to commit transaction: %s",
388 PQclear(res);
389 PQfinish(conn);
390 return -1;
391 }
392 PQclear(res);
393
394 PQfinish(conn);
395
396 if (param->verbose)
397 {
398 if (param->dry_run)
399 fprintf(stdout, "\rWould remove %ld large objects from database \"%s\".\n",
400 deleted, database);
401 else if (success)
403 "\rSuccessfully removed %ld large objects from database \"%s\".\n",
404 deleted, database);
405 else
406 fprintf(stdout, "\rRemoval from database \"%s\" failed at object %ld of %ld.\n",
407 database, deleted, matched);
408 }
409
410 return ((param->dry_run || success) ? 0 : -1);
411}
412
413static void
414usage(const char *progname)
415{
416 printf("%s removes unreferenced large objects from databases.\n\n", progname);
417 printf("Usage:\n %s [OPTION]... DBNAME...\n\n", progname);
418 printf("Options:\n");
419 printf(" -l, --limit=LIMIT commit after removing each LIMIT large objects\n");
420 printf(" -n, --dry-run don't remove large objects, just show what would be done\n");
421 printf(" -v, --verbose write a lot of progress messages\n");
422 printf(" -V, --version output version information, then exit\n");
423 printf(" -?, --help show this help, then exit\n");
424 printf("\nConnection options:\n");
425 printf(" -h, --host=HOSTNAME database server host or socket directory\n");
426 printf(" -p, --port=PORT database server port\n");
427 printf(" -U, --username=USERNAME user name to connect as\n");
428 printf(" -w, --no-password never prompt for password\n");
429 printf(" -W, --password force password prompt\n");
430 printf("\n");
431 printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
432 printf("%s home page: <%s>\n", PACKAGE_NAME, PACKAGE_URL);
433}
434
435
436int
437main(int argc, char **argv)
438{
439 static struct option long_options[] = {
440 {"host", required_argument, NULL, 'h'},
441 {"limit", required_argument, NULL, 'l'},
442 {"dry-run", no_argument, NULL, 'n'},
443 {"port", required_argument, NULL, 'p'},
444 {"username", required_argument, NULL, 'U'},
445 {"verbose", no_argument, NULL, 'v'},
446 {"version", no_argument, NULL, 'V'},
447 {"no-password", no_argument, NULL, 'w'},
448 {"password", no_argument, NULL, 'W'},
449 {"help", no_argument, NULL, '?'},
450 {NULL, 0, NULL, 0}
451 };
452
453 int rc = 0;
454 struct _param param;
455 int c;
456 int port;
457 const char *progname;
458 int optindex;
459
460 pg_logging_init(argv[0]);
461 progname = get_progname(argv[0]);
462
463 /* Set default parameter values */
464 param.pg_user = NULL;
465 param.pg_prompt = TRI_DEFAULT;
466 param.pg_host = NULL;
467 param.pg_port = NULL;
468 param.progname = progname;
469 param.verbose = 0;
470 param.dry_run = 0;
471 param.transaction_limit = 1000;
472
473 /* Process command-line arguments */
474 if (argc > 1)
475 {
476 if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
477 {
479 exit(0);
480 }
481 if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
482 {
483 puts("vacuumlo (PostgreSQL) " PG_VERSION);
484 exit(0);
485 }
486 }
487
488 while ((c = getopt_long(argc, argv, "h:l:np:U:vwW", long_options, &optindex)) != -1)
489 {
490 switch (c)
491 {
492 case 'h':
493 param.pg_host = pg_strdup(optarg);
494 break;
495 case 'l':
496 param.transaction_limit = strtol(optarg, NULL, 10);
497 if (param.transaction_limit < 0)
498 pg_fatal("transaction limit must not be negative (0 disables)");
499 break;
500 case 'n':
501 param.dry_run = 1;
502 param.verbose = 1;
503 break;
504 case 'p':
505 port = strtol(optarg, NULL, 10);
506 if ((port < 1) || (port > 65535))
507 pg_fatal("invalid port number: %s", optarg);
508 param.pg_port = pg_strdup(optarg);
509 break;
510 case 'U':
511 param.pg_user = pg_strdup(optarg);
512 break;
513 case 'v':
514 param.verbose = 1;
515 break;
516 case 'w':
517 param.pg_prompt = TRI_NO;
518 break;
519 case 'W':
520 param.pg_prompt = TRI_YES;
521 break;
522 default:
523 /* getopt_long already emitted a complaint */
524 pg_log_error_hint("Try \"%s --help\" for more information.", progname);
525 exit(1);
526 }
527 }
528
529 /* No database given? Show usage */
530 if (optind >= argc)
531 {
532 pg_log_error("missing required argument: database name");
533 pg_log_error_hint("Try \"%s --help\" for more information.", progname);
534 exit(1);
535 }
536
537 for (c = optind; c < argc; c++)
538 {
539 /* Work on selected database */
540 rc += (vacuumlo(argv[c], &param) != 0);
541 }
542
543 return rc;
544}
static Datum values[MAXATTR]
Definition: bootstrap.c:151
#define CppAsString2(x)
Definition: c.h:349
#define ALWAYS_SECURE_SEARCH_PATH_SQL
Definition: connect.h:25
#define fprintf(file, fmt, msg)
Definition: cubescan.l:21
int PQserverVersion(const PGconn *conn)
Definition: fe-connect.c:7258
PGTransactionStatusType PQtransactionStatus(const PGconn *conn)
Definition: fe-connect.c:7213
int PQconnectionNeedsPassword(const PGconn *conn)
Definition: fe-connect.c:7319
ConnStatusType PQstatus(const PGconn *conn)
Definition: fe-connect.c:7205
void PQfinish(PGconn *conn)
Definition: fe-connect.c:4939
char * PQerrorMessage(const PGconn *conn)
Definition: fe-connect.c:7268
PGconn * PQconnectdbParams(const char *const *keywords, const char *const *values, int expand_dbname)
Definition: fe-connect.c:698
void PQfreemem(void *ptr)
Definition: fe-exec.c:4032
char * PQgetvalue(const PGresult *res, int tup_num, int field_num)
Definition: fe-exec.c:3876
ExecStatusType PQresultStatus(const PGresult *res)
Definition: fe-exec.c:3411
int PQntuples(const PGresult *res)
Definition: fe-exec.c:3481
PGresult * PQexec(PGconn *conn, const char *query)
Definition: fe-exec.c:2262
char * PQescapeIdentifier(PGconn *conn, const char *str, size_t len)
Definition: fe-exec.c:4310
int lo_unlink(PGconn *conn, Oid lobjId)
Definition: fe-lobj.c:589
char * pg_strdup(const char *in)
Definition: fe_memutils.c:85
int getopt_long(int argc, char *const argv[], const char *optstring, const struct option *longopts, int *longindex)
Definition: getopt_long.c:60
#define no_argument
Definition: getopt_long.h:25
#define required_argument
Definition: getopt_long.h:26
static bool success
Definition: initdb.c:186
int i
Definition: isn.c:72
static const JsonPathKeyword keywords[]
@ CONNECTION_BAD
Definition: libpq-fe.h:82
@ PGRES_COMMAND_OK
Definition: libpq-fe.h:120
@ PGRES_TUPLES_OK
Definition: libpq-fe.h:123
@ PQTRANS_INERROR
Definition: libpq-fe.h:145
static void const char fflush(stdout)
exit(1)
void pg_logging_init(const char *argv0)
Definition: logging.c:83
#define pg_log_error(...)
Definition: logging.h:106
#define pg_log_error_hint(...)
Definition: logging.h:112
const char * progname
Definition: main.c:44
#define pg_fatal(...)
PGDLLIMPORT int optind
Definition: getopt.c:51
PGDLLIMPORT char * optarg
Definition: getopt.c:53
static int port
Definition: pg_regress.c:115
static char * buf
Definition: pg_test_fsync.c:72
#define snprintf
Definition: port.h:238
const char * get_progname(const char *argv0)
Definition: path.c:575
#define printf(...)
Definition: port.h:244
unsigned int Oid
Definition: postgres_ext.h:32
#define atooid(x)
Definition: postgres_ext.h:43
char * c
char * simple_prompt(const char *prompt, bool echo)
Definition: sprompt.c:38
static char * password
Definition: streamutil.c:52
PGconn * conn
Definition: streamutil.c:53
long transaction_limit
Definition: vacuumlo.c:50
enum trivalue pg_prompt
Definition: vacuumlo.c:44
char * pg_port
Definition: vacuumlo.c:45
int dry_run
Definition: vacuumlo.c:49
char * pg_user
Definition: vacuumlo.c:43
int verbose
Definition: vacuumlo.c:48
const char * progname
Definition: vacuumlo.c:47
char * pg_host
Definition: vacuumlo.c:46
static void usage(const char *progname)
Definition: vacuumlo.c:414
int main(int argc, char **argv)
Definition: vacuumlo.c:437
#define PARAMS_ARRAY_SIZE
static int vacuumlo(const char *database, const struct _param *param)
Definition: vacuumlo.c:62
trivalue
Definition: vacuumlo.c:35
@ TRI_YES
Definition: vacuumlo.c:38
@ TRI_DEFAULT
Definition: vacuumlo.c:36
@ TRI_NO
Definition: vacuumlo.c:37
#define BUFSIZE
Definition: vacuumlo.c:32