1 /*-------------------------------------------------------------------------
4 * This removes orphaned large objects from a database.
6 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
13 *-------------------------------------------------------------------------
15 #include "postgres_fe.h"
25 #include "libpq/libpq-fs.h"
27 #define atooid(x) ((Oid) strtoul((x), NULL, 10))
46 int vacuumlo(char *, struct _param
*);
52 * This vacuums LOs of one database. It returns 0 on success, -1 on failure.
55 vacuumlo(char *database
, struct _param
* param
)
64 static char *password
= NULL
;
67 if (param
->pg_prompt
&& password
== NULL
)
68 password
= simple_prompt("Password: ", 100, false);
71 * Start the connection. Loop until we have a password if requested by
78 conn
= PQsetdbLogin(param
->pg_host
,
87 fprintf(stderr
, "Connection to database \"%s\" failed\n",
92 if (PQstatus(conn
) == CONNECTION_BAD
&&
93 PQconnectionNeedsPassword(conn
) &&
98 password
= simple_prompt("Password: ", 100, false);
103 /* check to see that the backend connection was successfully made */
104 if (PQstatus(conn
) == CONNECTION_BAD
)
106 fprintf(stderr
, "Connection to database \"%s\" failed:\n%s",
107 database
, PQerrorMessage(conn
));
114 fprintf(stdout
, "Connected to %s\n", database
);
116 fprintf(stdout
, "Test run: no large objects will be removed!\n");
120 * Don't get fooled by any non-system catalogs
122 res
= PQexec(conn
, "SET search_path = pg_catalog");
123 if (PQresultStatus(res
) != PGRES_COMMAND_OK
)
125 fprintf(stderr
, "Failed to set search_path:\n");
126 fprintf(stderr
, "%s", PQerrorMessage(conn
));
134 * First we create and populate the LO temp table
137 strcat(buf
, "CREATE TEMP TABLE vacuum_l AS ");
138 strcat(buf
, "SELECT DISTINCT loid AS lo FROM pg_largeobject ");
139 res
= PQexec(conn
, buf
);
140 if (PQresultStatus(res
) != PGRES_COMMAND_OK
)
142 fprintf(stderr
, "Failed to create temp table:\n");
143 fprintf(stderr
, "%s", PQerrorMessage(conn
));
151 * Vacuum the temp table so that planner will generate decent plans for
155 strcat(buf
, "VACUUM ANALYZE vacuum_l");
156 res
= PQexec(conn
, buf
);
157 if (PQresultStatus(res
) != PGRES_COMMAND_OK
)
159 fprintf(stderr
, "Failed to vacuum temp table:\n");
160 fprintf(stderr
, "%s", PQerrorMessage(conn
));
168 * Now find any candidate tables that have columns of type oid.
170 * NOTE: we ignore system tables and temp tables by the expedient of
171 * rejecting tables in schemas named 'pg_*'. In particular, the temp
172 * table formed above is ignored, and pg_largeobject will be too. If
173 * either of these were scanned, obviously we'd end up with nothing to
176 * NOTE: the system oid column is ignored, as it has attnum < 1. This
177 * shouldn't matter for correctness, but it saves time.
180 strcat(buf
, "SELECT s.nspname, c.relname, a.attname ");
181 strcat(buf
, "FROM pg_class c, pg_attribute a, pg_namespace s, pg_type t ");
182 strcat(buf
, "WHERE a.attnum > 0 AND NOT a.attisdropped ");
183 strcat(buf
, " AND a.attrelid = c.oid ");
184 strcat(buf
, " AND a.atttypid = t.oid ");
185 strcat(buf
, " AND c.relnamespace = s.oid ");
186 strcat(buf
, " AND t.typname in ('oid', 'lo') ");
187 strcat(buf
, " AND c.relkind = 'r'");
188 strcat(buf
, " AND s.nspname !~ '^pg_'");
189 res
= PQexec(conn
, buf
);
190 if (PQresultStatus(res
) != PGRES_TUPLES_OK
)
192 fprintf(stderr
, "Failed to find OID columns:\n");
193 fprintf(stderr
, "%s", PQerrorMessage(conn
));
199 for (i
= 0; i
< PQntuples(res
); i
++)
205 schema
= PQgetvalue(res
, i
, 0);
206 table
= PQgetvalue(res
, i
, 1);
207 field
= PQgetvalue(res
, i
, 2);
210 fprintf(stdout
, "Checking %s in %s.%s\n", field
, schema
, table
);
213 * The "IN" construct used here was horribly inefficient before
214 * Postgres 7.4, but should be now competitive if not better than the
215 * bogus join we used before.
217 snprintf(buf
, BUFSIZE
,
218 "DELETE FROM vacuum_l "
219 "WHERE lo IN (SELECT \"%s\" FROM \"%s\".\"%s\")",
220 field
, schema
, table
);
221 res2
= PQexec(conn
, buf
);
222 if (PQresultStatus(res2
) != PGRES_COMMAND_OK
)
224 fprintf(stderr
, "Failed to check %s in table %s.%s:\n",
225 field
, schema
, table
);
226 fprintf(stderr
, "%s", PQerrorMessage(conn
));
237 * Run the actual deletes in a single transaction. Note that this would
238 * be a bad idea in pre-7.1 Postgres releases (since rolling back a table
239 * delete used to cause problems), but it should be safe now.
241 res
= PQexec(conn
, "begin");
245 * Finally, those entries remaining in vacuum_l are orphans.
248 strcat(buf
, "SELECT lo ");
249 strcat(buf
, "FROM vacuum_l");
250 res
= PQexec(conn
, buf
);
251 if (PQresultStatus(res
) != PGRES_TUPLES_OK
)
253 fprintf(stderr
, "Failed to read temp table:\n");
254 fprintf(stderr
, "%s", PQerrorMessage(conn
));
260 matched
= PQntuples(res
);
262 for (i
= 0; i
< matched
; i
++)
264 Oid lo
= atooid(PQgetvalue(res
, i
, 0));
268 fprintf(stdout
, "\rRemoving lo %6u ", lo
);
272 if (param
->dry_run
== 0)
274 if (lo_unlink(conn
, lo
) < 0)
276 fprintf(stderr
, "\nFailed to remove lo %u: ", lo
);
277 fprintf(stderr
, "%s", PQerrorMessage(conn
));
290 res
= PQexec(conn
, "end");
296 fprintf(stdout
, "\r%s %d large objects from %s.\n",
297 (param
->dry_run
? "Would remove" : "Removed"), deleted
, database
);
305 fprintf(stdout
, "vacuumlo removes unreferenced large objects from databases\n\n");
306 fprintf(stdout
, "Usage:\n vacuumlo [options] dbname [dbname ...]\n\n");
307 fprintf(stdout
, "Options:\n");
308 fprintf(stdout
, " -v\t\tWrite a lot of progress messages\n");
309 fprintf(stdout
, " -n\t\tDon't remove large objects, just show what would be done\n");
310 fprintf(stdout
, " -U username\tUsername to connect as\n");
311 fprintf(stdout
, " -W\t\tForce password prompt\n");
312 fprintf(stdout
, " -h hostname\tDatabase server host\n");
313 fprintf(stdout
, " -p port\tDatabase server port\n\n");
318 main(int argc
, char **argv
)
325 /* Parameter handling */
326 param
.pg_user
= NULL
;
328 param
.pg_host
= NULL
;
329 param
.pg_port
= NULL
;
335 c
= getopt(argc
, argv
, "?h:U:p:vnW");
358 param
.pg_user
= strdup(optarg
);
364 port
= strtol(optarg
, NULL
, 10);
365 if ((port
< 1) || (port
> 65535))
367 fprintf(stderr
, "[%s]: invalid port number '%s'\n", argv
[0], optarg
);
370 param
.pg_port
= strdup(optarg
);
373 param
.pg_host
= strdup(optarg
);
378 /* No database given? Show usage */
381 fprintf(stderr
, "vacuumlo: missing required argument: database name\n");
382 fprintf(stderr
, "Try 'vacuumlo -?' for help.\n");
386 for (c
= optind
; c
< argc
; c
++)
388 /* Work on selected database */
389 rc
+= (vacuumlo(argv
[c
], ¶m
) != 0);