]> granicus.if.org Git - postgresql/blob - contrib/vacuumlo/vacuumlo.c
Following patch avoids removing oids in the temporary table from the
[postgresql] / contrib / vacuumlo / vacuumlo.c
1 /*-------------------------------------------------------------------------
2  *
3  * vacuumlo.c
4  *        This removes orphaned large objects from a database.
5  *
6  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/contrib/vacuumlo/vacuumlo.c,v 1.19 2002/12/10 01:57:16 momjian Exp $
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 "libpq-fe.h"
25 #include "libpq/libpq-fs.h"
26
27 #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
28
29 #define BUFSIZE                 1024
30
31 extern char *optarg;
32 extern int      optind,
33                         opterr,
34                         optopt;
35
36 struct _param
37 {
38         char       *pg_user;
39         int                     pg_prompt;
40         char       *pg_port;
41         char       *pg_host;
42         int                     verbose;
43         int                     dry_run;
44 };
45
46 int                     vacuumlo(char *, struct _param *);
47 char       *simple_prompt(const char *prompt, int, int);
48 void            usage(void);
49
50
51 /*
52  * simple_prompt
53  *
54  * Generalized function especially intended for reading in usernames and
55  * password interactively. Reads from /dev/tty or stdin/stderr.
56  *
57  * prompt:              The prompt to print
58  * maxlen:              How many characters to accept
59  * echo:                Set to 0 if you want to hide what is entered (for passwords)
60  *
61  * Returns a malloc()'ed string with the input (w/o trailing newline).
62  */
63 static int      prompt_state = 0;
64
65 char *
66 simple_prompt(const char *prompt, int maxlen, int echo)
67 {
68         int                     length;
69         char       *destination;
70         FILE       *termin,
71                            *termout;
72
73 #ifdef HAVE_TERMIOS_H
74         struct termios t_orig,
75                                 t;
76 #endif
77
78         destination = (char *) malloc(maxlen + 2);
79         if (!destination)
80                 return NULL;
81
82         prompt_state = 1;                       /* disable SIGINT */
83
84         /*
85          * Do not try to collapse these into one "w+" mode file. Doesn't work
86          * on some platforms (eg, HPUX 10.20).
87          */
88         termin = fopen("/dev/tty", "r");
89         termout = fopen("/dev/tty", "w");
90         if (!termin || !termout)
91         {
92                 if (termin)
93                         fclose(termin);
94                 if (termout)
95                         fclose(termout);
96                 termin = stdin;
97                 termout = stderr;
98         }
99
100 #ifdef HAVE_TERMIOS_H
101         if (!echo)
102         {
103                 tcgetattr(fileno(termin), &t);
104                 t_orig = t;
105                 t.c_lflag &= ~ECHO;
106                 tcsetattr(fileno(termin), TCSAFLUSH, &t);
107         }
108 #endif
109
110         if (prompt)
111         {
112                 fputs(prompt, termout);
113                 fflush(termout);
114         }
115
116         if (fgets(destination, maxlen, termin) == NULL)
117                 destination[0] = '\0';
118
119         length = strlen(destination);
120         if (length > 0 && destination[length - 1] != '\n')
121         {
122                 /* eat rest of the line */
123                 char            buf[128];
124                 int                     buflen;
125
126                 do
127                 {
128                         if (fgets(buf, sizeof(buf), termin) == NULL)
129                                 break;
130                         buflen = strlen(buf);
131                 } while (buflen > 0 && buf[buflen - 1] != '\n');
132         }
133
134         if (length > 0 && destination[length - 1] == '\n')
135                 /* remove trailing newline */
136                 destination[length - 1] = '\0';
137
138 #ifdef HAVE_TERMIOS_H
139         if (!echo)
140         {
141                 tcsetattr(fileno(termin), TCSAFLUSH, &t_orig);
142                 fputs("\n", termout);
143                 fflush(termout);
144         }
145 #endif
146
147         if (termin != stdin)
148         {
149                 fclose(termin);
150                 fclose(termout);
151         }
152
153         prompt_state = 0;                       /* SIGINT okay again */
154
155         return destination;
156 }
157
158
159
160 /*
161  * This vacuums LOs of one database. It returns 0 on success, -1 on failure.
162  */
163 int
164 vacuumlo(char *database, struct _param * param)
165 {
166         PGconn     *conn;
167         PGresult   *res,
168                            *res2;
169         char            buf[BUFSIZE];
170         int                     matched;
171         int                     deleted;
172         int                     i;
173         char       *password = NULL;
174
175         if (param->pg_prompt)
176         {
177                 password = simple_prompt("Password: ", 32, 0);
178                 if (!password)
179                 {
180                         fprintf(stderr, "failed to get password\n");
181                         exit(1);
182                 }
183         }
184
185         conn = PQsetdbLogin(param->pg_host,
186                                                 param->pg_port,
187                                                 NULL,
188                                                 NULL,
189                                                 database,
190                                                 param->pg_user,
191                                                 password
192                 );
193
194         /* check to see that the backend connection was successfully made */
195         if (PQstatus(conn) == CONNECTION_BAD)
196         {
197                 fprintf(stderr, "Connection to database '%s' failed:\n", database);
198                 fprintf(stderr, "%s", PQerrorMessage(conn));
199                 PQfinish(conn);
200                 return -1;
201         }
202
203         if (param->verbose)
204         {
205                 fprintf(stdout, "Connected to %s\n", database);
206                 if (param->dry_run)
207                         fprintf(stdout, "Test run: no large objects will be removed!\n");
208         }
209
210         res = PQexec(conn, "SET search_path = public");
211         if (PQresultStatus(res) != PGRES_COMMAND_OK)
212         {
213                 fprintf(stderr, "Failed to set search_path on:\n");
214                 fprintf(stderr, "%s", PQerrorMessage(conn));
215                 PQclear(res);
216                 PQfinish(conn);
217                 return -1;
218         }
219         PQclear(res);
220
221         res = PQexec(conn, "SET autocommit TO 'on'");
222         if (PQresultStatus(res) != PGRES_COMMAND_OK)
223         {
224                 fprintf(stderr, "Failed to set autocommit on:\n");
225                 fprintf(stderr, "%s", PQerrorMessage(conn));
226                 PQclear(res);
227                 PQfinish(conn);
228                 return -1;
229         }
230         PQclear(res);
231
232         /*
233          * First we create and populate the LO temp table
234          */
235         buf[0] = '\0';
236         strcat(buf, "SELECT DISTINCT loid AS lo ");
237         strcat(buf, "INTO TEMP TABLE vacuum_l ");
238         strcat(buf, "FROM pg_largeobject ");
239         res = PQexec(conn, buf);
240         if (PQresultStatus(res) != PGRES_COMMAND_OK)
241         {
242                 fprintf(stderr, "Failed to create temp table:\n");
243                 fprintf(stderr, "%s", PQerrorMessage(conn));
244                 PQclear(res);
245                 PQfinish(conn);
246                 return -1;
247         }
248         PQclear(res);
249
250         /*
251          * Vacuum the temp table so that planner will generate decent plans
252          * for the DELETEs below.
253          */
254         buf[0] = '\0';
255         strcat(buf, "VACUUM ANALYZE vacuum_l ");
256         res = PQexec(conn, buf);
257         if (PQresultStatus(res) != PGRES_COMMAND_OK)
258         {
259                 fprintf(stderr, "Failed to vacuum temp table:\n");
260                 fprintf(stderr, "%s", PQerrorMessage(conn));
261                 PQclear(res);
262                 PQfinish(conn);
263                 return -1;
264         }
265         PQclear(res);
266
267         /*
268          * Now find any candidate tables who have columns of type oid.
269          *
270          * NOTE: the temp table formed above is ignored, because its real table
271          * name will be pg_something.  Also, pg_largeobject will be ignored.
272          * If either of these were scanned, obviously we'd end up with nothing
273          * to delete...
274          *
275          * NOTE: the system oid column is ignored, as it has attnum < 1. This
276          * shouldn't matter for correctness, but it saves time.
277          */
278         buf[0] = '\0';
279         strcat(buf, "SELECT c.relname, a.attname ");
280         strcat(buf, "FROM pg_class c, pg_attribute a, pg_type t ");
281         strcat(buf, "WHERE a.attnum > 0 ");
282         strcat(buf, "      AND a.attrelid = c.oid ");
283         strcat(buf, "      AND a.atttypid = t.oid ");
284         strcat(buf, "      AND t.typname in ('oid', 'lo') ");
285         strcat(buf, "      AND c.relkind = 'r'");
286         strcat(buf, "      AND c.relname NOT LIKE 'pg_%'");
287         strcat(buf, "      AND c.relname != 'vacuum_l'");
288         res = PQexec(conn, buf);
289         if (PQresultStatus(res) != PGRES_TUPLES_OK)
290         {
291                 fprintf(stderr, "Failed to find OID columns:\n");
292                 fprintf(stderr, "%s", PQerrorMessage(conn));
293                 PQclear(res);
294                 PQfinish(conn);
295                 return -1;
296         }
297
298         for (i = 0; i < PQntuples(res); i++)
299         {
300                 char       *table,
301                                    *field;
302
303                 table = PQgetvalue(res, i, 0);
304                 field = PQgetvalue(res, i, 1);
305
306                 if (param->verbose)
307                         fprintf(stdout, "Checking %s in %s\n", field, table);
308
309                 /*
310                  * We use a DELETE with implicit join for efficiency.  This is a
311                  * Postgres-ism and not portable to other DBMSs, but then this
312                  * whole program is a Postgres-ism.
313                  */
314                 snprintf(buf, BUFSIZE, "DELETE FROM vacuum_l WHERE lo = \"%s\".\"%s\" ",
315                                  table, field);
316                 res2 = PQexec(conn, buf);
317                 if (PQresultStatus(res2) != PGRES_COMMAND_OK)
318                 {
319                         fprintf(stderr, "Failed to check %s in table %s:\n",
320                                         field, table);
321                         fprintf(stderr, "%s", PQerrorMessage(conn));
322                         PQclear(res2);
323                         PQclear(res);
324                         PQfinish(conn);
325                         return -1;
326                 }
327                 PQclear(res2);
328         }
329         PQclear(res);
330
331         /*
332          * Run the actual deletes in a single transaction.      Note that this
333          * would be a bad idea in pre-7.1 Postgres releases (since rolling
334          * back a table delete used to cause problems), but it should be safe
335          * now.
336          */
337         res = PQexec(conn, "begin");
338         PQclear(res);
339
340         /*
341          * Finally, those entries remaining in vacuum_l are orphans.
342          */
343         buf[0] = '\0';
344         strcat(buf, "SELECT lo ");
345         strcat(buf, "FROM vacuum_l");
346         res = PQexec(conn, buf);
347         if (PQresultStatus(res) != PGRES_TUPLES_OK)
348         {
349                 fprintf(stderr, "Failed to read temp table:\n");
350                 fprintf(stderr, "%s", PQerrorMessage(conn));
351                 PQclear(res);
352                 PQfinish(conn);
353                 return -1;
354         }
355
356         matched = PQntuples(res);
357         deleted = 0;
358         for (i = 0; i < matched; i++)
359         {
360                 Oid                     lo = atooid(PQgetvalue(res, i, 0));
361
362                 if (param->verbose)
363                 {
364                         fprintf(stdout, "\rRemoving lo %6u   ", lo);
365                         fflush(stdout);
366                 }
367
368                 if (param->dry_run == 0)
369                 {
370                         if (lo_unlink(conn, lo) < 0)
371                         {
372                                 fprintf(stderr, "\nFailed to remove lo %u: ", lo);
373                                 fprintf(stderr, "%s", PQerrorMessage(conn));
374                         }
375                         else
376                                 deleted++;
377                 }
378                 else
379                         deleted++;
380         }
381         PQclear(res);
382
383         /*
384          * That's all folks!
385          */
386         res = PQexec(conn, "end");
387         PQclear(res);
388
389         PQfinish(conn);
390
391         if (param->verbose)
392                 fprintf(stdout, "\r%s %d large objects from %s.\n",
393                 (param->dry_run ? "Would remove" : "Removed"), deleted, database);
394
395         return 0;
396 }
397
398 void
399 usage(void)
400 {
401         fprintf(stdout, "vacuumlo removes unreferenced large objects from databases\n\n");
402         fprintf(stdout, "Usage:\n  vacuumlo [options] dbname [dbnames...]\n\n");
403         fprintf(stdout, "Options:\n");
404         fprintf(stdout, "  -v\t\tWrite a lot of output\n");
405         fprintf(stdout, "  -n\t\tDon't remove any large object, just show what would be done\n");
406         fprintf(stdout, "  -U username\tUsername to connect as\n");
407         fprintf(stdout, "  -W\t\tPrompt for password\n");
408         fprintf(stdout, "  -h hostname\tDatabase server host\n");
409         fprintf(stdout, "  -p port\tDatabase server port\n\n");
410 }
411
412
413 int
414 main(int argc, char **argv)
415 {
416         int                     rc = 0;
417         struct _param param;
418         int                     c;
419         int                     port;
420
421         /* Parameter handling */
422         param.pg_user = NULL;
423         param.pg_prompt = 0;
424         param.pg_host = NULL;
425         param.pg_port = 0;
426         param.verbose = 0;
427         param.dry_run = 0;
428
429         while (1)
430         {
431                 c = getopt(argc, argv, "?h:U:p:vnW");
432                 if (c == -1)
433                         break;
434
435                 switch (c)
436                 {
437                         case '?':
438                                 if (optopt == '?')
439                                 {
440                                         usage();
441                                         exit(0);
442                                 }
443                                 exit(1);
444                         case ':':
445                                 exit(1);
446                         case 'v':
447                                 param.verbose = 1;
448                                 break;
449                         case 'n':
450                                 param.dry_run = 1;
451                                 param.verbose = 1;
452                                 break;
453                         case 'U':
454                                 param.pg_user = strdup(optarg);
455                                 break;
456                         case 'W':
457                                 param.pg_prompt = 1;
458                                 break;
459                         case 'p':
460                                 port = strtol(optarg, NULL, 10);
461                                 if ((port < 1) || (port > 65535))
462                                 {
463                                         fprintf(stderr, "[%s]: invalid port number '%s'\n", argv[0], optarg);
464                                         exit(1);
465                                 }
466                                 param.pg_port = strdup(optarg);
467                                 break;
468                         case 'h':
469                                 param.pg_host = strdup(optarg);
470                                 break;
471                 }
472         }
473
474         /* No database given? Show usage */
475         if (optind >= argc)
476         {
477                 fprintf(stderr, "vacuumlo: missing required argument: database name\n");
478                 fprintf(stderr, "Try 'vacuumlo -?' for help.\n");
479                 exit(1);
480         }
481
482         for (c = optind; c < argc; c++)
483         {
484                 /* Work on selected database */
485                 rc += (vacuumlo(argv[c], &param) != 0);
486         }
487
488         return rc;
489 }