3
3
* vacuumlo.c
4
4
* This removes orphaned large objects from a database.
5
5
*
6
- * Portions Copyright (c) 1996-2010 , PostgreSQL Global Development Group
6
+ * Portions Copyright (c) 1996-2012 , PostgreSQL Global Development Group
7
7
* Portions Copyright (c) 1994, Regents of the University of California
8
8
*
9
9
*
10
10
* IDENTIFICATION
11
- * $PostgreSQL: pgsql/ contrib/vacuumlo/vacuumlo.c,v 1.45 2010/02/17 04:19:37 tgl Exp $
11
+ * contrib/vacuumlo/vacuumlo.c
12
12
*
13
13
*-------------------------------------------------------------------------
14
14
*/
22
22
#endif
23
23
24
24
#include "libpq-fe.h"
25
- #include "libpq/libpq-fs.h"
26
25
27
26
#define atooid (x ) ((Oid) strtoul((x), NULL, 10))
28
27
29
28
#define BUFSIZE 1024
30
29
31
30
extern char * optarg ;
32
31
extern int optind ,
33
- opterr ,
34
- optopt ;
32
+ opterr ;
35
33
36
34
enum trivalue
37
35
{
@@ -48,29 +46,32 @@ struct _param
48
46
char * pg_host ;
49
47
int verbose ;
50
48
int dry_run ;
49
+ long transaction_limit ;
51
50
};
52
51
53
- int vacuumlo (char * , struct _param * );
54
- void usage (const char * progname );
52
+ static int vacuumlo (const char * database , const struct _param * param );
53
+ static void usage (const char * progname );
55
54
56
55
57
56
58
57
/*
59
58
* This vacuums LOs of one database. It returns 0 on success, -1 on failure.
60
59
*/
61
- int
62
- vacuumlo (char * database , struct _param * param )
60
+ static int
61
+ vacuumlo (const char * database , const struct _param * param )
63
62
{
64
63
PGconn * conn ;
65
64
PGresult * res ,
66
65
* res2 ;
67
66
char buf [BUFSIZE ];
68
- int matched ;
69
- int deleted ;
67
+ long matched ;
68
+ long deleted ;
70
69
int i ;
71
70
static char * password = NULL ;
72
71
bool new_pass ;
72
+ bool success = true;
73
73
74
+ /* Note: password can be carried over from a previous call */
74
75
if (param -> pg_prompt == TRI_YES && password == NULL )
75
76
password = simple_prompt ("Password: " , 100 , false);
76
77
@@ -118,7 +119,7 @@ vacuumlo(char *database, struct _param * param)
118
119
119
120
if (param -> verbose )
120
121
{
121
- fprintf (stdout , "Connected to %s \n" , database );
122
+ fprintf (stdout , "Connected to database \"%s\" \n" , database );
122
123
if (param -> dry_run )
123
124
fprintf (stdout , "Test run: no large objects will be removed!\n" );
124
125
}
@@ -219,9 +220,21 @@ vacuumlo(char *database, struct _param * param)
219
220
if (param -> verbose )
220
221
fprintf (stdout , "Checking %s in %s.%s\n" , field , schema , table );
221
222
223
+ schema = PQescapeIdentifier (conn , schema , strlen (schema ));
224
+ table = PQescapeIdentifier (conn , table , strlen (table ));
225
+ field = PQescapeIdentifier (conn , field , strlen (field ));
226
+
227
+ if (!schema || !table || !field )
228
+ {
229
+ fprintf (stderr , "Out of memory\n" );
230
+ PQclear (res );
231
+ PQfinish (conn );
232
+ return -1 ;
233
+ }
234
+
222
235
snprintf (buf , BUFSIZE ,
223
236
"DELETE FROM vacuum_l "
224
- "WHERE lo IN (SELECT \"%s\" FROM \"%s\".\"%s\" )" ,
237
+ "WHERE lo IN (SELECT %s FROM %s.%s )" ,
225
238
field , schema , table );
226
239
res2 = PQexec (conn , buf );
227
240
if (PQresultStatus (res2 ) != PGRES_COMMAND_OK )
@@ -235,23 +248,35 @@ vacuumlo(char *database, struct _param * param)
235
248
return -1 ;
236
249
}
237
250
PQclear (res2 );
251
+
252
+ PQfreemem (schema );
253
+ PQfreemem (table );
254
+ PQfreemem (field );
238
255
}
239
256
PQclear (res );
240
257
241
258
/*
242
- * Run the actual deletes in a single transaction. Note that this would
243
- * be a bad idea in pre-7.1 Postgres releases (since rolling back a table
244
- * delete used to cause problems), but it should be safe now.
259
+ * Now, those entries remaining in vacuum_l are orphans. Delete 'em.
260
+ *
261
+ * We don't want to run each delete as an individual transaction, because
262
+ * the commit overhead would be high. However, since 9.0 the backend will
263
+ * acquire a lock per deleted LO, so deleting too many LOs per transaction
264
+ * risks running out of room in the shared-memory lock table.
265
+ * Accordingly, we delete up to transaction_limit LOs per transaction.
245
266
*/
246
267
res = PQexec (conn , "begin" );
268
+ if (PQresultStatus (res ) != PGRES_COMMAND_OK )
269
+ {
270
+ fprintf (stderr , "Failed to start transaction:\n" );
271
+ fprintf (stderr , "%s" , PQerrorMessage (conn ));
272
+ PQclear (res );
273
+ PQfinish (conn );
274
+ return -1 ;
275
+ }
247
276
PQclear (res );
248
277
249
- /*
250
- * Finally, those entries remaining in vacuum_l are orphans.
251
- */
252
278
buf [0 ] = '\0' ;
253
- strcat (buf , "SELECT lo " );
254
- strcat (buf , "FROM vacuum_l" );
279
+ strcat (buf , "SELECT lo FROM vacuum_l" );
255
280
res = PQexec (conn , buf );
256
281
if (PQresultStatus (res ) != PGRES_TUPLES_OK )
257
282
{
@@ -280,37 +305,87 @@ vacuumlo(char *database, struct _param * param)
280
305
{
281
306
fprintf (stderr , "\nFailed to remove lo %u: " , lo );
282
307
fprintf (stderr , "%s" , PQerrorMessage (conn ));
308
+ if (PQtransactionStatus (conn ) == PQTRANS_INERROR )
309
+ {
310
+ success = false;
311
+ break ;
312
+ }
283
313
}
284
314
else
285
315
deleted ++ ;
286
316
}
287
317
else
288
318
deleted ++ ;
319
+ if (param -> transaction_limit > 0 &&
320
+ (deleted % param -> transaction_limit ) == 0 )
321
+ {
322
+ res2 = PQexec (conn , "commit" );
323
+ if (PQresultStatus (res2 ) != PGRES_COMMAND_OK )
324
+ {
325
+ fprintf (stderr , "Failed to commit transaction:\n" );
326
+ fprintf (stderr , "%s" , PQerrorMessage (conn ));
327
+ PQclear (res2 );
328
+ PQclear (res );
329
+ PQfinish (conn );
330
+ return -1 ;
331
+ }
332
+ PQclear (res2 );
333
+ res2 = PQexec (conn , "begin" );
334
+ if (PQresultStatus (res2 ) != PGRES_COMMAND_OK )
335
+ {
336
+ fprintf (stderr , "Failed to start transaction:\n" );
337
+ fprintf (stderr , "%s" , PQerrorMessage (conn ));
338
+ PQclear (res2 );
339
+ PQclear (res );
340
+ PQfinish (conn );
341
+ return -1 ;
342
+ }
343
+ PQclear (res2 );
344
+ }
289
345
}
290
346
PQclear (res );
291
347
292
348
/*
293
349
* That's all folks!
294
350
*/
295
- res = PQexec (conn , "end" );
351
+ res = PQexec (conn , "commit" );
352
+ if (PQresultStatus (res ) != PGRES_COMMAND_OK )
353
+ {
354
+ fprintf (stderr , "Failed to commit transaction:\n" );
355
+ fprintf (stderr , "%s" , PQerrorMessage (conn ));
356
+ PQclear (res );
357
+ PQfinish (conn );
358
+ return -1 ;
359
+ }
296
360
PQclear (res );
297
361
298
362
PQfinish (conn );
299
363
300
364
if (param -> verbose )
301
- fprintf (stdout , "\r%s %d large objects from %s.\n" ,
302
- (param -> dry_run ? "Would remove" : "Removed" ), deleted , database );
365
+ {
366
+ if (param -> dry_run )
367
+ fprintf (stdout , "\rWould remove %ld large objects from database \"%s\".\n" ,
368
+ deleted , database );
369
+ else if (success )
370
+ fprintf (stdout ,
371
+ "\rSuccessfully removed %ld large objects from database \"%s\".\n" ,
372
+ deleted , database );
373
+ else
374
+ fprintf (stdout , "\rRemoval from database \"%s\" failed at object %ld of %ld.\n" ,
375
+ database , deleted , matched );
376
+ }
303
377
304
- return 0 ;
378
+ return (( param -> dry_run || success ) ? 0 : -1 ) ;
305
379
}
306
380
307
- void
381
+ static void
308
382
usage (const char * progname )
309
383
{
310
384
printf ("%s removes unreferenced large objects from databases.\n\n" , progname );
311
385
printf ("Usage:\n %s [OPTION]... DBNAME...\n\n" , progname );
312
386
printf ("Options:\n" );
313
387
printf (" -h HOSTNAME database server host or socket directory\n" );
388
+ printf (" -l LIMIT commit after removing each LIMIT large objects\n" );
314
389
printf (" -n don't remove large objects, just show what would be done\n" );
315
390
printf (" -p PORT database server port\n" );
316
391
printf (" -U USERNAME user name to connect as\n" );
@@ -335,14 +410,16 @@ main(int argc, char **argv)
335
410
336
411
progname = get_progname (argv [0 ]);
337
412
338
- /* Parameter handling */
413
+ /* Set default parameter values */
339
414
param .pg_user = NULL ;
340
415
param .pg_prompt = TRI_DEFAULT ;
341
416
param .pg_host = NULL ;
342
417
param .pg_port = NULL ;
343
418
param .verbose = 0 ;
344
419
param .dry_run = 0 ;
420
+ param .transaction_limit = 1000 ;
345
421
422
+ /* Process command-line arguments */
346
423
if (argc > 1 )
347
424
{
348
425
if (strcmp (argv [1 ], "--help" ) == 0 || strcmp (argv [1 ], "-?" ) == 0 )
@@ -359,7 +436,7 @@ main(int argc, char **argv)
359
436
360
437
while (1 )
361
438
{
362
- c = getopt (argc , argv , "h:U:p:vnwW" );
439
+ c = getopt (argc , argv , "h:l: U:p:vnwW" );
363
440
if (c == -1 )
364
441
break ;
365
442
@@ -377,6 +454,16 @@ main(int argc, char **argv)
377
454
param .dry_run = 1 ;
378
455
param .verbose = 1 ;
379
456
break ;
457
+ case 'l' :
458
+ param .transaction_limit = strtol (optarg , NULL , 10 );
459
+ if (param .transaction_limit < 0 )
460
+ {
461
+ fprintf (stderr ,
462
+ "%s: transaction limit must not be negative (0 disables)\n" ,
463
+ progname );
464
+ exit (1 );
465
+ }
466
+ break ;
380
467
case 'U' :
381
468
param .pg_user = strdup (optarg );
382
469
break ;
@@ -405,7 +492,7 @@ main(int argc, char **argv)
405
492
if (optind >= argc )
406
493
{
407
494
fprintf (stderr , "vacuumlo: missing required argument: database name\n" );
408
- fprintf (stderr , "Try 'vacuumlo -?' for help .\n" );
495
+ fprintf (stderr , _ ( "Try \"%s --help\" for more information .\n" ), progname );
409
496
exit (1 );
410
497
}
411
498
0 commit comments