YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/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], &param) != 0);
555
  }
556
557
  return rc;
558
}