3
3
* vacuumlo.c
4
4
* This removes orphaned large objects from a database.
5
5
*
6
- * Portions Copyright (c) 1996-2011 , 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
*
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 todatabase \"%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 -?' forhelp .\n" );
495
+ fprintf (stderr ,_ ( "Try\"%s --help\" formore information .\n" ), progname );
409
496
exit (1 );
410
497
}
411
498