~vcs-imports/mammoth-replicator/trunk

« back to all changes in this revision

Viewing changes to contrib/vacuumlo/vacuumlo.c

  • Committer: alvherre
  • Date: 2005-12-16 21:24:52 UTC
  • Revision ID: svn-v4:db760fc0-0f08-0410-9d63-cc6633f64896:trunk:1
Initial import of the REL8_0_3 sources from the Pgsql CVS repository.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*-------------------------------------------------------------------------
 
2
 *
 
3
 * vacuumlo.c
 
4
 *        This removes orphaned large objects from a database.
 
5
 *
 
6
 * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
 
7
 * Portions Copyright (c) 1994, Regents of the University of California
 
8
 *
 
9
 *
 
10
 * IDENTIFICATION
 
11
 *        $PostgreSQL: pgsql/contrib/vacuumlo/vacuumlo.c,v 1.29 2004-12-31 21:58:50 pgsql 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
void            usage(void);
 
48
 
 
49
 
 
50
 
 
51
/*
 
52
 * This vacuums LOs of one database. It returns 0 on success, -1 on failure.
 
53
 */
 
54
int
 
55
vacuumlo(char *database, struct _param * param)
 
56
{
 
57
        PGconn     *conn;
 
58
        PGresult   *res,
 
59
                           *res2;
 
60
        char            buf[BUFSIZE];
 
61
        int                     matched;
 
62
        int                     deleted;
 
63
        int                     i;
 
64
        char       *password = NULL;
 
65
 
 
66
        if (param->pg_prompt)
 
67
        {
 
68
                password = simple_prompt("Password: ", 32, 0);
 
69
                if (!password)
 
70
                {
 
71
                        fprintf(stderr, "failed to get password\n");
 
72
                        exit(1);
 
73
                }
 
74
        }
 
75
 
 
76
        conn = PQsetdbLogin(param->pg_host,
 
77
                                                param->pg_port,
 
78
                                                NULL,
 
79
                                                NULL,
 
80
                                                database,
 
81
                                                param->pg_user,
 
82
                                                password
 
83
                );
 
84
 
 
85
        /* check to see that the backend connection was successfully made */
 
86
        if (PQstatus(conn) == CONNECTION_BAD)
 
87
        {
 
88
                fprintf(stderr, "Connection to database '%s' failed:\n", database);
 
89
                fprintf(stderr, "%s", PQerrorMessage(conn));
 
90
                PQfinish(conn);
 
91
                return -1;
 
92
        }
 
93
 
 
94
        if (param->verbose)
 
95
        {
 
96
                fprintf(stdout, "Connected to %s\n", database);
 
97
                if (param->dry_run)
 
98
                        fprintf(stdout, "Test run: no large objects will be removed!\n");
 
99
        }
 
100
 
 
101
        /*
 
102
         * Don't get fooled by any non-system catalogs
 
103
         */
 
104
        res = PQexec(conn, "SET search_path = pg_catalog");
 
105
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
 
106
        {
 
107
                fprintf(stderr, "Failed to set search_path:\n");
 
108
                fprintf(stderr, "%s", PQerrorMessage(conn));
 
109
                PQclear(res);
 
110
                PQfinish(conn);
 
111
                return -1;
 
112
        }
 
113
        PQclear(res);
 
114
 
 
115
        /*
 
116
         * First we create and populate the LO temp table
 
117
         */
 
118
        buf[0] = '\0';
 
119
        strcat(buf, "CREATE TEMP TABLE vacuum_l AS ");
 
120
        strcat(buf, "SELECT DISTINCT loid AS lo FROM pg_largeobject ");
 
121
        res = PQexec(conn, buf);
 
122
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
 
123
        {
 
124
                fprintf(stderr, "Failed to create temp table:\n");
 
125
                fprintf(stderr, "%s", PQerrorMessage(conn));
 
126
                PQclear(res);
 
127
                PQfinish(conn);
 
128
                return -1;
 
129
        }
 
130
        PQclear(res);
 
131
 
 
132
        /*
 
133
         * Vacuum the temp table so that planner will generate decent plans
 
134
         * for the DELETEs below.
 
135
         */
 
136
        buf[0] = '\0';
 
137
        strcat(buf, "VACUUM ANALYZE vacuum_l");
 
138
        res = PQexec(conn, buf);
 
139
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
 
140
        {
 
141
                fprintf(stderr, "Failed to vacuum temp table:\n");
 
142
                fprintf(stderr, "%s", PQerrorMessage(conn));
 
143
                PQclear(res);
 
144
                PQfinish(conn);
 
145
                return -1;
 
146
        }
 
147
        PQclear(res);
 
148
 
 
149
        /*
 
150
         * Now find any candidate tables that have columns of type oid.
 
151
         *
 
152
         * NOTE: we ignore system tables and temp tables by the expedient of
 
153
         * rejecting tables in schemas named 'pg_*'.  In particular, the temp
 
154
         * table formed above is ignored, and pg_largeobject will be too. If
 
155
         * either of these were scanned, obviously we'd end up with nothing to
 
156
         * delete...
 
157
         *
 
158
         * NOTE: the system oid column is ignored, as it has attnum < 1. This
 
159
         * shouldn't matter for correctness, but it saves time.
 
160
         */
 
161
        buf[0] = '\0';
 
162
        strcat(buf, "SELECT s.nspname, c.relname, a.attname ");
 
163
        strcat(buf, "FROM pg_class c, pg_attribute a, pg_namespace s, pg_type t ");
 
164
        strcat(buf, "WHERE a.attnum > 0 AND NOT a.attisdropped ");
 
165
        strcat(buf, "      AND a.attrelid = c.oid ");
 
166
        strcat(buf, "      AND a.atttypid = t.oid ");
 
167
        strcat(buf, "      AND c.relnamespace = s.oid ");
 
168
        strcat(buf, "      AND t.typname in ('oid', 'lo') ");
 
169
        strcat(buf, "      AND c.relkind = 'r'");
 
170
        strcat(buf, "      AND s.nspname NOT LIKE 'pg\\\\_%'");
 
171
        res = PQexec(conn, buf);
 
172
        if (PQresultStatus(res) != PGRES_TUPLES_OK)
 
173
        {
 
174
                fprintf(stderr, "Failed to find OID columns:\n");
 
175
                fprintf(stderr, "%s", PQerrorMessage(conn));
 
176
                PQclear(res);
 
177
                PQfinish(conn);
 
178
                return -1;
 
179
        }
 
180
 
 
181
        for (i = 0; i < PQntuples(res); i++)
 
182
        {
 
183
                char       *schema,
 
184
                                   *table,
 
185
                                   *field;
 
186
 
 
187
                schema = PQgetvalue(res, i, 0);
 
188
                table = PQgetvalue(res, i, 1);
 
189
                field = PQgetvalue(res, i, 2);
 
190
 
 
191
                if (param->verbose)
 
192
                        fprintf(stdout, "Checking %s in %s.%s\n", field, schema, table);
 
193
 
 
194
                /*
 
195
                 * The "IN" construct used here was horribly inefficient before
 
196
                 * Postgres 7.4, but should be now competitive if not better than
 
197
                 * the bogus join we used before.
 
198
                 */
 
199
                snprintf(buf, BUFSIZE,
 
200
                                 "DELETE FROM vacuum_l "
 
201
                                 "WHERE lo IN (SELECT \"%s\" FROM \"%s\".\"%s\")",
 
202
                                 field, schema, table);
 
203
                res2 = PQexec(conn, buf);
 
204
                if (PQresultStatus(res2) != PGRES_COMMAND_OK)
 
205
                {
 
206
                        fprintf(stderr, "Failed to check %s in table %s.%s:\n",
 
207
                                        field, schema, table);
 
208
                        fprintf(stderr, "%s", PQerrorMessage(conn));
 
209
                        PQclear(res2);
 
210
                        PQclear(res);
 
211
                        PQfinish(conn);
 
212
                        return -1;
 
213
                }
 
214
                PQclear(res2);
 
215
        }
 
216
        PQclear(res);
 
217
 
 
218
        /*
 
219
         * Run the actual deletes in a single transaction.      Note that this
 
220
         * would be a bad idea in pre-7.1 Postgres releases (since rolling
 
221
         * back a table delete used to cause problems), but it should be safe
 
222
         * now.
 
223
         */
 
224
        res = PQexec(conn, "begin");
 
225
        PQclear(res);
 
226
 
 
227
        /*
 
228
         * Finally, those entries remaining in vacuum_l are orphans.
 
229
         */
 
230
        buf[0] = '\0';
 
231
        strcat(buf, "SELECT lo ");
 
232
        strcat(buf, "FROM vacuum_l");
 
233
        res = PQexec(conn, buf);
 
234
        if (PQresultStatus(res) != PGRES_TUPLES_OK)
 
235
        {
 
236
                fprintf(stderr, "Failed to read temp table:\n");
 
237
                fprintf(stderr, "%s", PQerrorMessage(conn));
 
238
                PQclear(res);
 
239
                PQfinish(conn);
 
240
                return -1;
 
241
        }
 
242
 
 
243
        matched = PQntuples(res);
 
244
        deleted = 0;
 
245
        for (i = 0; i < matched; i++)
 
246
        {
 
247
                Oid                     lo = atooid(PQgetvalue(res, i, 0));
 
248
 
 
249
                if (param->verbose)
 
250
                {
 
251
                        fprintf(stdout, "\rRemoving lo %6u   ", lo);
 
252
                        fflush(stdout);
 
253
                }
 
254
 
 
255
                if (param->dry_run == 0)
 
256
                {
 
257
                        if (lo_unlink(conn, lo) < 0)
 
258
                        {
 
259
                                fprintf(stderr, "\nFailed to remove lo %u: ", lo);
 
260
                                fprintf(stderr, "%s", PQerrorMessage(conn));
 
261
                        }
 
262
                        else
 
263
                                deleted++;
 
264
                }
 
265
                else
 
266
                        deleted++;
 
267
        }
 
268
        PQclear(res);
 
269
 
 
270
        /*
 
271
         * That's all folks!
 
272
         */
 
273
        res = PQexec(conn, "end");
 
274
        PQclear(res);
 
275
 
 
276
        PQfinish(conn);
 
277
 
 
278
        if (param->verbose)
 
279
                fprintf(stdout, "\r%s %d large objects from %s.\n",
 
280
                (param->dry_run ? "Would remove" : "Removed"), deleted, database);
 
281
 
 
282
        return 0;
 
283
}
 
284
 
 
285
void
 
286
usage(void)
 
287
{
 
288
        fprintf(stdout, "vacuumlo removes unreferenced large objects from databases\n\n");
 
289
        fprintf(stdout, "Usage:\n  vacuumlo [options] dbname [dbname ...]\n\n");
 
290
        fprintf(stdout, "Options:\n");
 
291
        fprintf(stdout, "  -v\t\tWrite a lot of progress messages\n");
 
292
        fprintf(stdout, "  -n\t\tDon't remove large objects, just show what would be done\n");
 
293
        fprintf(stdout, "  -U username\tUsername to connect as\n");
 
294
        fprintf(stdout, "  -W\t\tPrompt for password\n");
 
295
        fprintf(stdout, "  -h hostname\tDatabase server host\n");
 
296
        fprintf(stdout, "  -p port\tDatabase server port\n\n");
 
297
}
 
298
 
 
299
 
 
300
int
 
301
main(int argc, char **argv)
 
302
{
 
303
        int                     rc = 0;
 
304
        struct _param param;
 
305
        int                     c;
 
306
        int                     port;
 
307
 
 
308
        /* Parameter handling */
 
309
        param.pg_user = NULL;
 
310
        param.pg_prompt = 0;
 
311
        param.pg_host = NULL;
 
312
        param.pg_port = NULL;
 
313
        param.verbose = 0;
 
314
        param.dry_run = 0;
 
315
 
 
316
        while (1)
 
317
        {
 
318
                c = getopt(argc, argv, "?h:U:p:vnW");
 
319
                if (c == -1)
 
320
                        break;
 
321
 
 
322
                switch (c)
 
323
                {
 
324
                        case '?':
 
325
                                if (optopt == '?')
 
326
                                {
 
327
                                        usage();
 
328
                                        exit(0);
 
329
                                }
 
330
                                exit(1);
 
331
                        case ':':
 
332
                                exit(1);
 
333
                        case 'v':
 
334
                                param.verbose = 1;
 
335
                                break;
 
336
                        case 'n':
 
337
                                param.dry_run = 1;
 
338
                                param.verbose = 1;
 
339
                                break;
 
340
                        case 'U':
 
341
                                param.pg_user = strdup(optarg);
 
342
                                break;
 
343
                        case 'W':
 
344
                                param.pg_prompt = 1;
 
345
                                break;
 
346
                        case 'p':
 
347
                                port = strtol(optarg, NULL, 10);
 
348
                                if ((port < 1) || (port > 65535))
 
349
                                {
 
350
                                        fprintf(stderr, "[%s]: invalid port number '%s'\n", argv[0], optarg);
 
351
                                        exit(1);
 
352
                                }
 
353
                                param.pg_port = strdup(optarg);
 
354
                                break;
 
355
                        case 'h':
 
356
                                param.pg_host = strdup(optarg);
 
357
                                break;
 
358
                }
 
359
        }
 
360
 
 
361
        /* No database given? Show usage */
 
362
        if (optind >= argc)
 
363
        {
 
364
                fprintf(stderr, "vacuumlo: missing required argument: database name\n");
 
365
                fprintf(stderr, "Try 'vacuumlo -?' for help.\n");
 
366
                exit(1);
 
367
        }
 
368
 
 
369
        for (c = optind; c < argc; c++)
 
370
        {
 
371
                /* Work on selected database */
 
372
                rc += (vacuumlo(argv[c], &param) != 0);
 
373
        }
 
374
 
 
375
        return rc;
 
376
}