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