PageRenderTime 54ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/contrib/vacuumlo/vacuumlo.c

https://github.com/bbt123/postgres
C | 546 lines | 429 code | 55 blank | 62 comment | 77 complexity | 6873a9a97fb0f0e928f5445920d0a172 MD5 | raw file
Possible License(s): AGPL-3.0
  1. /*-------------------------------------------------------------------------
  2. *
  3. * vacuumlo.c
  4. * This removes orphaned large objects from a database.
  5. *
  6. * Portions Copyright (c) 1996-2014, 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. #include <sys/stat.h>
  17. #include <fcntl.h>
  18. #include <unistd.h>
  19. #ifdef HAVE_TERMIOS_H
  20. #include <termios.h>
  21. #endif
  22. #include "libpq-fe.h"
  23. #include "pg_getopt.h"
  24. #define atooid(x) ((Oid) strtoul((x), NULL, 10))
  25. #define BUFSIZE 1024
  26. enum trivalue
  27. {
  28. TRI_DEFAULT,
  29. TRI_NO,
  30. TRI_YES
  31. };
  32. struct _param
  33. {
  34. char *pg_user;
  35. enum trivalue pg_prompt;
  36. char *pg_port;
  37. char *pg_host;
  38. const char *progname;
  39. int verbose;
  40. int dry_run;
  41. long transaction_limit;
  42. };
  43. static int vacuumlo(const char *database, const struct _param * param);
  44. static void usage(const char *progname);
  45. /*
  46. * This vacuums LOs of one database. It returns 0 on success, -1 on failure.
  47. */
  48. static int
  49. vacuumlo(const char *database, const struct _param * param)
  50. {
  51. PGconn *conn;
  52. PGresult *res,
  53. *res2;
  54. char buf[BUFSIZE];
  55. long matched;
  56. long deleted;
  57. int i;
  58. static char *password = NULL;
  59. bool new_pass;
  60. bool success = true;
  61. /* Note: password can be carried over from a previous call */
  62. if (param->pg_prompt == TRI_YES && password == NULL)
  63. password = simple_prompt("Password: ", 100, false);
  64. /*
  65. * Start the connection. Loop until we have a password if requested by
  66. * backend.
  67. */
  68. do
  69. {
  70. #define PARAMS_ARRAY_SIZE 7
  71. const char *keywords[PARAMS_ARRAY_SIZE];
  72. const char *values[PARAMS_ARRAY_SIZE];
  73. keywords[0] = "host";
  74. values[0] = param->pg_host;
  75. keywords[1] = "port";
  76. values[1] = param->pg_port;
  77. keywords[2] = "user";
  78. values[2] = param->pg_user;
  79. keywords[3] = "password";
  80. values[3] = password;
  81. keywords[4] = "dbname";
  82. values[4] = database;
  83. keywords[5] = "fallback_application_name";
  84. values[5] = param->progname;
  85. keywords[6] = NULL;
  86. values[6] = NULL;
  87. new_pass = false;
  88. conn = PQconnectdbParams(keywords, values, true);
  89. if (!conn)
  90. {
  91. fprintf(stderr, "Connection to database \"%s\" failed\n",
  92. database);
  93. return -1;
  94. }
  95. if (PQstatus(conn) == CONNECTION_BAD &&
  96. PQconnectionNeedsPassword(conn) &&
  97. password == NULL &&
  98. param->pg_prompt != TRI_NO)
  99. {
  100. PQfinish(conn);
  101. password = simple_prompt("Password: ", 100, false);
  102. new_pass = true;
  103. }
  104. } while (new_pass);
  105. /* check to see that the backend connection was successfully made */
  106. if (PQstatus(conn) == CONNECTION_BAD)
  107. {
  108. fprintf(stderr, "Connection to database \"%s\" failed:\n%s",
  109. database, PQerrorMessage(conn));
  110. PQfinish(conn);
  111. return -1;
  112. }
  113. if (param->verbose)
  114. {
  115. fprintf(stdout, "Connected to database \"%s\"\n", database);
  116. if (param->dry_run)
  117. fprintf(stdout, "Test run: no large objects will be removed!\n");
  118. }
  119. /*
  120. * Don't get fooled by any non-system catalogs
  121. */
  122. res = PQexec(conn, "SET search_path = pg_catalog");
  123. if (PQresultStatus(res) != PGRES_COMMAND_OK)
  124. {
  125. fprintf(stderr, "Failed to set search_path:\n");
  126. fprintf(stderr, "%s", PQerrorMessage(conn));
  127. PQclear(res);
  128. PQfinish(conn);
  129. return -1;
  130. }
  131. PQclear(res);
  132. /*
  133. * First we create and populate the LO temp table
  134. */
  135. buf[0] = '\0';
  136. strcat(buf, "CREATE TEMP TABLE vacuum_l AS ");
  137. if (PQserverVersion(conn) >= 90000)
  138. strcat(buf, "SELECT oid AS lo FROM pg_largeobject_metadata");
  139. else
  140. strcat(buf, "SELECT DISTINCT loid AS lo FROM pg_largeobject");
  141. res = PQexec(conn, buf);
  142. if (PQresultStatus(res) != PGRES_COMMAND_OK)
  143. {
  144. fprintf(stderr, "Failed to create temp table:\n");
  145. fprintf(stderr, "%s", PQerrorMessage(conn));
  146. PQclear(res);
  147. PQfinish(conn);
  148. return -1;
  149. }
  150. PQclear(res);
  151. /*
  152. * Analyze the temp table so that planner will generate decent plans for
  153. * the DELETEs below.
  154. */
  155. buf[0] = '\0';
  156. strcat(buf, "ANALYZE vacuum_l");
  157. res = PQexec(conn, buf);
  158. if (PQresultStatus(res) != PGRES_COMMAND_OK)
  159. {
  160. fprintf(stderr, "Failed to vacuum temp table:\n");
  161. fprintf(stderr, "%s", PQerrorMessage(conn));
  162. PQclear(res);
  163. PQfinish(conn);
  164. return -1;
  165. }
  166. PQclear(res);
  167. /*
  168. * Now find any candidate tables that have columns of type oid.
  169. *
  170. * NOTE: we ignore system tables and temp tables by the expedient of
  171. * rejecting tables in schemas named 'pg_*'. In particular, the temp
  172. * table formed above is ignored, and pg_largeobject will be too. If
  173. * either of these were scanned, obviously we'd end up with nothing to
  174. * delete...
  175. *
  176. * NOTE: the system oid column is ignored, as it has attnum < 1. This
  177. * shouldn't matter for correctness, but it saves time.
  178. */
  179. buf[0] = '\0';
  180. strcat(buf, "SELECT s.nspname, c.relname, a.attname ");
  181. strcat(buf, "FROM pg_class c, pg_attribute a, pg_namespace s, pg_type t ");
  182. strcat(buf, "WHERE a.attnum > 0 AND NOT a.attisdropped ");
  183. strcat(buf, " AND a.attrelid = c.oid ");
  184. strcat(buf, " AND a.atttypid = t.oid ");
  185. strcat(buf, " AND c.relnamespace = s.oid ");
  186. strcat(buf, " AND t.typname in ('oid', 'lo') ");
  187. strcat(buf, " AND c.relkind in ('r', 'm')");
  188. strcat(buf, " AND s.nspname !~ '^pg_'");
  189. res = PQexec(conn, buf);
  190. if (PQresultStatus(res) != PGRES_TUPLES_OK)
  191. {
  192. fprintf(stderr, "Failed to find OID columns:\n");
  193. fprintf(stderr, "%s", PQerrorMessage(conn));
  194. PQclear(res);
  195. PQfinish(conn);
  196. return -1;
  197. }
  198. for (i = 0; i < PQntuples(res); i++)
  199. {
  200. char *schema,
  201. *table,
  202. *field;
  203. schema = PQgetvalue(res, i, 0);
  204. table = PQgetvalue(res, i, 1);
  205. field = PQgetvalue(res, i, 2);
  206. if (param->verbose)
  207. fprintf(stdout, "Checking %s in %s.%s\n", field, schema, table);
  208. schema = PQescapeIdentifier(conn, schema, strlen(schema));
  209. table = PQescapeIdentifier(conn, table, strlen(table));
  210. field = PQescapeIdentifier(conn, field, strlen(field));
  211. if (!schema || !table || !field)
  212. {
  213. fprintf(stderr, "Out of memory\n");
  214. PQclear(res);
  215. PQfinish(conn);
  216. return -1;
  217. }
  218. snprintf(buf, BUFSIZE,
  219. "DELETE FROM vacuum_l "
  220. "WHERE lo IN (SELECT %s FROM %s.%s)",
  221. field, schema, table);
  222. res2 = PQexec(conn, buf);
  223. if (PQresultStatus(res2) != PGRES_COMMAND_OK)
  224. {
  225. fprintf(stderr, "Failed to check %s in table %s.%s:\n",
  226. field, schema, table);
  227. fprintf(stderr, "%s", PQerrorMessage(conn));
  228. PQclear(res2);
  229. PQclear(res);
  230. PQfinish(conn);
  231. return -1;
  232. }
  233. PQclear(res2);
  234. PQfreemem(schema);
  235. PQfreemem(table);
  236. PQfreemem(field);
  237. }
  238. PQclear(res);
  239. /*
  240. * Now, those entries remaining in vacuum_l are orphans. Delete 'em.
  241. *
  242. * We don't want to run each delete as an individual transaction, because
  243. * the commit overhead would be high. However, since 9.0 the backend will
  244. * acquire a lock per deleted LO, so deleting too many LOs per transaction
  245. * risks running out of room in the shared-memory lock table. Accordingly,
  246. * we delete up to transaction_limit LOs per transaction.
  247. */
  248. res = PQexec(conn, "begin");
  249. if (PQresultStatus(res) != PGRES_COMMAND_OK)
  250. {
  251. fprintf(stderr, "Failed to start transaction:\n");
  252. fprintf(stderr, "%s", PQerrorMessage(conn));
  253. PQclear(res);
  254. PQfinish(conn);
  255. return -1;
  256. }
  257. PQclear(res);
  258. buf[0] = '\0';
  259. strcat(buf,
  260. "DECLARE myportal CURSOR WITH HOLD FOR SELECT lo FROM vacuum_l");
  261. res = PQexec(conn, buf);
  262. if (PQresultStatus(res) != PGRES_COMMAND_OK)
  263. {
  264. fprintf(stderr, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
  265. PQclear(res);
  266. PQfinish(conn);
  267. return -1;
  268. }
  269. PQclear(res);
  270. snprintf(buf, BUFSIZE, "FETCH FORWARD %ld IN myportal",
  271. param->transaction_limit > 0 ? param->transaction_limit : 1000L);
  272. deleted = 0;
  273. while (1)
  274. {
  275. res = PQexec(conn, buf);
  276. if (PQresultStatus(res) != PGRES_TUPLES_OK)
  277. {
  278. fprintf(stderr, "FETCH FORWARD failed: %s", PQerrorMessage(conn));
  279. PQclear(res);
  280. PQfinish(conn);
  281. return -1;
  282. }
  283. matched = PQntuples(res);
  284. if (matched <= 0)
  285. {
  286. /* at end of resultset */
  287. PQclear(res);
  288. break;
  289. }
  290. for (i = 0; i < matched; i++)
  291. {
  292. Oid lo = atooid(PQgetvalue(res, i, 0));
  293. if (param->verbose)
  294. {
  295. fprintf(stdout, "\rRemoving lo %6u ", lo);
  296. fflush(stdout);
  297. }
  298. if (param->dry_run == 0)
  299. {
  300. if (lo_unlink(conn, lo) < 0)
  301. {
  302. fprintf(stderr, "\nFailed to remove lo %u: ", lo);
  303. fprintf(stderr, "%s", PQerrorMessage(conn));
  304. if (PQtransactionStatus(conn) == PQTRANS_INERROR)
  305. {
  306. success = false;
  307. PQclear(res);
  308. break;
  309. }
  310. }
  311. else
  312. deleted++;
  313. }
  314. else
  315. deleted++;
  316. if (param->transaction_limit > 0 &&
  317. (deleted % param->transaction_limit) == 0)
  318. {
  319. res2 = PQexec(conn, "commit");
  320. if (PQresultStatus(res2) != PGRES_COMMAND_OK)
  321. {
  322. fprintf(stderr, "Failed to commit transaction:\n");
  323. fprintf(stderr, "%s", PQerrorMessage(conn));
  324. PQclear(res2);
  325. PQclear(res);
  326. PQfinish(conn);
  327. return -1;
  328. }
  329. PQclear(res2);
  330. res2 = PQexec(conn, "begin");
  331. if (PQresultStatus(res2) != PGRES_COMMAND_OK)
  332. {
  333. fprintf(stderr, "Failed to start transaction:\n");
  334. fprintf(stderr, "%s", PQerrorMessage(conn));
  335. PQclear(res2);
  336. PQclear(res);
  337. PQfinish(conn);
  338. return -1;
  339. }
  340. PQclear(res2);
  341. }
  342. }
  343. PQclear(res);
  344. }
  345. /*
  346. * That's all folks!
  347. */
  348. res = PQexec(conn, "commit");
  349. if (PQresultStatus(res) != PGRES_COMMAND_OK)
  350. {
  351. fprintf(stderr, "Failed to commit transaction:\n");
  352. fprintf(stderr, "%s", PQerrorMessage(conn));
  353. PQclear(res);
  354. PQfinish(conn);
  355. return -1;
  356. }
  357. PQclear(res);
  358. PQfinish(conn);
  359. if (param->verbose)
  360. {
  361. if (param->dry_run)
  362. fprintf(stdout, "\rWould remove %ld large objects from database \"%s\".\n",
  363. deleted, database);
  364. else if (success)
  365. fprintf(stdout,
  366. "\rSuccessfully removed %ld large objects from database \"%s\".\n",
  367. deleted, database);
  368. else
  369. fprintf(stdout, "\rRemoval from database \"%s\" failed at object %ld of %ld.\n",
  370. database, deleted, matched);
  371. }
  372. return ((param->dry_run || success) ? 0 : -1);
  373. }
  374. static void
  375. usage(const char *progname)
  376. {
  377. printf("%s removes unreferenced large objects from databases.\n\n", progname);
  378. printf("Usage:\n %s [OPTION]... DBNAME...\n\n", progname);
  379. printf("Options:\n");
  380. printf(" -l LIMIT commit after removing each LIMIT large objects\n");
  381. printf(" -n don't remove large objects, just show what would be done\n");
  382. printf(" -v write a lot of progress messages\n");
  383. printf(" -V, --version output version information, then exit\n");
  384. printf(" -?, --help show this help, then exit\n");
  385. printf("\nConnection options:\n");
  386. printf(" -h HOSTNAME database server host or socket directory\n");
  387. printf(" -p PORT database server port\n");
  388. printf(" -U USERNAME user name to connect as\n");
  389. printf(" -w never prompt for password\n");
  390. printf(" -W force password prompt\n");
  391. printf("\n");
  392. printf("Report bugs to <pgsql-bugs@postgresql.org>.\n");
  393. }
  394. int
  395. main(int argc, char **argv)
  396. {
  397. int rc = 0;
  398. struct _param param;
  399. int c;
  400. int port;
  401. const char *progname;
  402. progname = get_progname(argv[0]);
  403. /* Set default parameter values */
  404. param.pg_user = NULL;
  405. param.pg_prompt = TRI_DEFAULT;
  406. param.pg_host = NULL;
  407. param.pg_port = NULL;
  408. param.progname = progname;
  409. param.verbose = 0;
  410. param.dry_run = 0;
  411. param.transaction_limit = 1000;
  412. /* Process command-line arguments */
  413. if (argc > 1)
  414. {
  415. if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
  416. {
  417. usage(progname);
  418. exit(0);
  419. }
  420. if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
  421. {
  422. puts("vacuumlo (PostgreSQL) " PG_VERSION);
  423. exit(0);
  424. }
  425. }
  426. while (1)
  427. {
  428. c = getopt(argc, argv, "h:l:U:p:vnwW");
  429. if (c == -1)
  430. break;
  431. switch (c)
  432. {
  433. case '?':
  434. fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
  435. exit(1);
  436. case ':':
  437. exit(1);
  438. case 'v':
  439. param.verbose = 1;
  440. break;
  441. case 'n':
  442. param.dry_run = 1;
  443. param.verbose = 1;
  444. break;
  445. case 'l':
  446. param.transaction_limit = strtol(optarg, NULL, 10);
  447. if (param.transaction_limit < 0)
  448. {
  449. fprintf(stderr,
  450. "%s: transaction limit must not be negative (0 disables)\n",
  451. progname);
  452. exit(1);
  453. }
  454. break;
  455. case 'U':
  456. param.pg_user = strdup(optarg);
  457. break;
  458. case 'w':
  459. param.pg_prompt = TRI_NO;
  460. break;
  461. case 'W':
  462. param.pg_prompt = TRI_YES;
  463. break;
  464. case 'p':
  465. port = strtol(optarg, NULL, 10);
  466. if ((port < 1) || (port > 65535))
  467. {
  468. fprintf(stderr, "%s: invalid port number: %s\n", progname, optarg);
  469. exit(1);
  470. }
  471. param.pg_port = strdup(optarg);
  472. break;
  473. case 'h':
  474. param.pg_host = strdup(optarg);
  475. break;
  476. }
  477. }
  478. /* No database given? Show usage */
  479. if (optind >= argc)
  480. {
  481. fprintf(stderr, "vacuumlo: missing required argument: database name\n");
  482. fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
  483. exit(1);
  484. }
  485. for (c = optind; c < argc; c++)
  486. {
  487. /* Work on selected database */
  488. rc += (vacuumlo(argv[c], &param) != 0);
  489. }
  490. return rc;
  491. }