Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * vacuumdb
4 : : *
5 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
6 : : * Portions Copyright (c) 1994, Regents of the University of California
7 : : *
8 : : * src/bin/scripts/vacuumdb.c
9 : : *
10 : : *-------------------------------------------------------------------------
11 : : */
12 : :
13 : : #include "postgres_fe.h"
14 : :
15 : : #include <limits.h>
16 : :
17 : : #include "catalog/pg_class_d.h"
18 : : #include "common.h"
19 : : #include "common/connect.h"
20 : : #include "common/logging.h"
21 : : #include "fe_utils/cancel.h"
22 : : #include "fe_utils/option_utils.h"
23 : : #include "fe_utils/parallel_slot.h"
24 : : #include "fe_utils/query_utils.h"
25 : : #include "fe_utils/simple_list.h"
26 : : #include "fe_utils/string_utils.h"
27 : :
28 : :
29 : : /* vacuum options controlled by user flags */
30 : : typedef struct vacuumingOptions
31 : : {
32 : : bool analyze_only;
33 : : bool verbose;
34 : : bool and_analyze;
35 : : bool full;
36 : : bool freeze;
37 : : bool disable_page_skipping;
38 : : bool skip_locked;
39 : : int min_xid_age;
40 : : int min_mxid_age;
41 : : int parallel_workers; /* >= 0 indicates user specified the
42 : : * parallel degree, otherwise -1 */
43 : : bool no_index_cleanup;
44 : : bool force_index_cleanup;
45 : : bool do_truncate;
46 : : bool process_main;
47 : : bool process_toast;
48 : : bool skip_database_stats;
49 : : char *buffer_usage_limit;
50 : : } vacuumingOptions;
51 : :
52 : : /* object filter options */
53 : : typedef enum
54 : : {
55 : : OBJFILTER_NONE = 0, /* no filter used */
56 : : OBJFILTER_ALL_DBS = (1 << 0), /* -a | --all */
57 : : OBJFILTER_DATABASE = (1 << 1), /* -d | --dbname */
58 : : OBJFILTER_TABLE = (1 << 2), /* -t | --table */
59 : : OBJFILTER_SCHEMA = (1 << 3), /* -n | --schema */
60 : : OBJFILTER_SCHEMA_EXCLUDE = (1 << 4), /* -N | --exclude-schema */
61 : : } VacObjFilter;
62 : :
63 : : VacObjFilter objfilter = OBJFILTER_NONE;
64 : :
65 : : static void vacuum_one_database(ConnParams *cparams,
66 : : vacuumingOptions *vacopts,
67 : : int stage,
68 : : SimpleStringList *objects,
69 : : int concurrentCons,
70 : : const char *progname, bool echo, bool quiet);
71 : :
72 : : static void vacuum_all_databases(ConnParams *cparams,
73 : : vacuumingOptions *vacopts,
74 : : bool analyze_in_stages,
75 : : SimpleStringList *objects,
76 : : int concurrentCons,
77 : : const char *progname, bool echo, bool quiet);
78 : :
79 : : static void prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
80 : : vacuumingOptions *vacopts, const char *table);
81 : :
82 : : static void run_vacuum_command(PGconn *conn, const char *sql, bool echo,
83 : : const char *table);
84 : :
85 : : static void help(const char *progname);
86 : :
87 : : void check_objfilter(void);
88 : :
89 : : static char *escape_quotes(const char *src);
90 : :
91 : : /* For analyze-in-stages mode */
92 : : #define ANALYZE_NO_STAGE -1
93 : : #define ANALYZE_NUM_STAGES 3
94 : :
95 : :
96 : : int
7606 peter_e@gmx.net 97 :CBC 68 : main(int argc, char *argv[])
98 : : {
99 : : static struct option long_options[] = {
100 : : {"host", required_argument, NULL, 'h'},
101 : : {"port", required_argument, NULL, 'p'},
102 : : {"username", required_argument, NULL, 'U'},
103 : : {"no-password", no_argument, NULL, 'w'},
104 : : {"password", no_argument, NULL, 'W'},
105 : : {"echo", no_argument, NULL, 'e'},
106 : : {"quiet", no_argument, NULL, 'q'},
107 : : {"dbname", required_argument, NULL, 'd'},
108 : : {"analyze", no_argument, NULL, 'z'},
109 : : {"analyze-only", no_argument, NULL, 'Z'},
110 : : {"freeze", no_argument, NULL, 'F'},
111 : : {"all", no_argument, NULL, 'a'},
112 : : {"table", required_argument, NULL, 't'},
113 : : {"full", no_argument, NULL, 'f'},
114 : : {"verbose", no_argument, NULL, 'v'},
115 : : {"jobs", required_argument, NULL, 'j'},
116 : : {"parallel", required_argument, NULL, 'P'},
117 : : {"schema", required_argument, NULL, 'n'},
118 : : {"exclude-schema", required_argument, NULL, 'N'},
119 : : {"maintenance-db", required_argument, NULL, 2},
120 : : {"analyze-in-stages", no_argument, NULL, 3},
121 : : {"disable-page-skipping", no_argument, NULL, 4},
122 : : {"skip-locked", no_argument, NULL, 5},
123 : : {"min-xid-age", required_argument, NULL, 6},
124 : : {"min-mxid-age", required_argument, NULL, 7},
125 : : {"no-index-cleanup", no_argument, NULL, 8},
126 : : {"force-index-cleanup", no_argument, NULL, 9},
127 : : {"no-truncate", no_argument, NULL, 10},
128 : : {"no-process-toast", no_argument, NULL, 11},
129 : : {"no-process-main", no_argument, NULL, 12},
130 : : {"buffer-usage-limit", required_argument, NULL, 13},
131 : : {NULL, 0, NULL, 0}
132 : : };
133 : :
134 : : const char *progname;
135 : : int optindex;
136 : : int c;
137 : 68 : const char *dbname = NULL;
4513 rhaas@postgresql.org 138 : 68 : const char *maintenance_db = NULL;
7606 peter_e@gmx.net 139 : 68 : char *host = NULL;
140 : 68 : char *port = NULL;
141 : 68 : char *username = NULL;
5526 142 : 68 : enum trivalue prompt_password = TRI_DEFAULT;
143 : : ConnParams cparams;
7606 144 : 68 : bool echo = false;
145 : 68 : bool quiet = false;
146 : : vacuumingOptions vacopts;
3653 147 : 68 : bool analyze_in_stages = false;
623 andrew@dunslane.net 148 : 68 : SimpleStringList objects = {NULL, NULL};
3369 alvherre@alvh.no-ip. 149 : 68 : int concurrentCons = 1;
150 : 68 : int tbl_count = 0;
151 : :
152 : : /* initialize options */
153 : 68 : memset(&vacopts, 0, sizeof(vacopts));
1537 akapila@postgresql.o 154 : 68 : vacopts.parallel_workers = -1;
373 drowley@postgresql.o 155 : 68 : vacopts.buffer_usage_limit = NULL;
1031 pg@bowt.ie 156 : 68 : vacopts.no_index_cleanup = false;
157 : 68 : vacopts.force_index_cleanup = false;
1392 michael@paquier.xyz 158 : 68 : vacopts.do_truncate = true;
405 159 : 68 : vacopts.process_main = true;
1160 160 : 68 : vacopts.process_toast = true;
161 : :
1840 peter@eisentraut.org 162 : 68 : pg_logging_init(argv[0]);
7606 peter_e@gmx.net 163 : 68 : progname = get_progname(argv[0]);
5603 164 : 68 : set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
165 : :
7606 166 : 68 : handle_help_version_opts(argc, argv, "vacuumdb", help);
167 : :
489 peter@eisentraut.org 168 [ + + ]: 163 : while ((c = getopt_long(argc, argv, "ad:efFh:j:n:N:p:P:qt:U:vwWzZ", long_options, &optindex)) != -1)
169 : : {
7606 peter_e@gmx.net 170 [ + + + + : 110 : switch (c)
+ + + + +
+ + - + +
- - - + +
- + + + +
+ + - + +
+ - + ]
171 : : {
489 peter@eisentraut.org 172 : 15 : case 'a':
173 : 15 : objfilter |= OBJFILTER_ALL_DBS;
7606 peter_e@gmx.net 174 : 15 : break;
175 : 2 : case 'd':
623 andrew@dunslane.net 176 : 2 : objfilter |= OBJFILTER_DATABASE;
4202 bruce@momjian.us 177 : 2 : dbname = pg_strdup(optarg);
7606 peter_e@gmx.net 178 : 2 : break;
489 peter@eisentraut.org 179 : 1 : case 'e':
180 : 1 : echo = true;
5212 bruce@momjian.us 181 : 1 : break;
489 peter@eisentraut.org 182 : 1 : case 'f':
183 : 1 : vacopts.full = true;
7606 peter_e@gmx.net 184 : 1 : break;
5534 bruce@momjian.us 185 : 4 : case 'F':
3369 alvherre@alvh.no-ip. 186 : 4 : vacopts.freeze = true;
5534 bruce@momjian.us 187 : 4 : break;
489 peter@eisentraut.org 188 : 6 : case 'h':
189 : 6 : host = pg_strdup(optarg);
3369 alvherre@alvh.no-ip. 190 : 6 : break;
191 : 1 : case 'j':
995 michael@paquier.xyz 192 [ - + ]: 1 : if (!option_parse_int(optarg, "-j/--jobs", 1, INT_MAX,
193 : : &concurrentCons))
3369 alvherre@alvh.no-ip. 194 :UBC 0 : exit(1);
7606 peter_e@gmx.net 195 :CBC 1 : break;
489 peter@eisentraut.org 196 : 6 : case 'n':
197 : 6 : objfilter |= OBJFILTER_SCHEMA;
198 : 6 : simple_string_list_append(&objects, optarg);
199 : 6 : break;
200 : 6 : case 'N':
201 : 6 : objfilter |= OBJFILTER_SCHEMA_EXCLUDE;
202 : 6 : simple_string_list_append(&objects, optarg);
203 : 6 : break;
204 : 6 : case 'p':
205 : 6 : port = pg_strdup(optarg);
206 : 6 : break;
1537 akapila@postgresql.o 207 : 3 : case 'P':
995 michael@paquier.xyz 208 [ + + ]: 3 : if (!option_parse_int(optarg, "-P/--parallel", 0, INT_MAX,
209 : : &vacopts.parallel_workers))
1537 akapila@postgresql.o 210 : 1 : exit(1);
211 : 2 : break;
489 peter@eisentraut.org 212 :UBC 0 : case 'q':
213 : 0 : quiet = true;
214 : 0 : break;
489 peter@eisentraut.org 215 :CBC 14 : case 't':
216 : 14 : objfilter |= OBJFILTER_TABLE;
217 : 14 : simple_string_list_append(&objects, optarg);
218 : 14 : tbl_count++;
219 : 14 : break;
220 : 6 : case 'U':
221 : 6 : username = pg_strdup(optarg);
222 : 6 : break;
489 peter@eisentraut.org 223 :UBC 0 : case 'v':
224 : 0 : vacopts.verbose = true;
225 : 0 : break;
226 : 0 : case 'w':
227 : 0 : prompt_password = TRI_NO;
228 : 0 : break;
229 : 0 : case 'W':
230 : 0 : prompt_password = TRI_YES;
231 : 0 : break;
489 peter@eisentraut.org 232 :CBC 7 : case 'z':
233 : 7 : vacopts.and_analyze = true;
234 : 7 : break;
235 : 13 : case 'Z':
236 : 13 : vacopts.analyze_only = true;
237 : 13 : break;
4513 rhaas@postgresql.org 238 :UBC 0 : case 2:
4202 bruce@momjian.us 239 : 0 : maintenance_db = pg_strdup(optarg);
4513 rhaas@postgresql.org 240 : 0 : break;
3653 peter_e@gmx.net 241 :CBC 2 : case 3:
3369 alvherre@alvh.no-ip. 242 : 2 : analyze_in_stages = vacopts.analyze_only = true;
3653 peter_e@gmx.net 243 : 2 : break;
1923 michael@paquier.xyz 244 : 2 : case 4:
245 : 2 : vacopts.disable_page_skipping = true;
246 : 2 : break;
247 : 2 : case 5:
248 : 2 : vacopts.skip_locked = true;
249 : 2 : break;
1900 250 : 2 : case 6:
995 251 [ + + ]: 2 : if (!option_parse_int(optarg, "--min-xid-age", 1, INT_MAX,
252 : : &vacopts.min_xid_age))
1900 253 : 1 : exit(1);
254 : 1 : break;
255 : 2 : case 7:
995 256 [ + + ]: 2 : if (!option_parse_int(optarg, "--min-mxid-age", 1, INT_MAX,
257 : : &vacopts.min_mxid_age))
1900 258 : 1 : exit(1);
259 : 1 : break;
1392 260 : 2 : case 8:
1031 pg@bowt.ie 261 : 2 : vacopts.no_index_cleanup = true;
1392 michael@paquier.xyz 262 : 2 : break;
1392 michael@paquier.xyz 263 :UBC 0 : case 9:
1031 pg@bowt.ie 264 : 0 : vacopts.force_index_cleanup = true;
1392 michael@paquier.xyz 265 : 0 : break;
1160 michael@paquier.xyz 266 :CBC 2 : case 10:
1031 pg@bowt.ie 267 : 2 : vacopts.do_truncate = false;
268 : 2 : break;
269 : 2 : case 11:
1160 michael@paquier.xyz 270 : 2 : vacopts.process_toast = false;
271 : 2 : break;
405 272 : 2 : case 12:
273 : 2 : vacopts.process_main = false;
274 : 2 : break;
373 drowley@postgresql.o 275 :UBC 0 : case 13:
206 276 : 0 : vacopts.buffer_usage_limit = escape_quotes(optarg);
373 277 : 0 : break;
7606 peter_e@gmx.net 278 :CBC 1 : default:
279 : : /* getopt_long already emitted a complaint */
737 tgl@sss.pgh.pa.us 280 : 1 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
7606 peter_e@gmx.net 281 : 1 : exit(1);
282 : : }
283 : : }
284 : :
285 : : /*
286 : : * Non-option argument specifies database name as long as it wasn't
287 : : * already specified with -d / --dbname
288 : : */
4380 andrew@dunslane.net 289 [ + + + - ]: 53 : if (optind < argc && dbname == NULL)
290 : : {
623 291 : 38 : objfilter |= OBJFILTER_DATABASE;
4380 292 : 38 : dbname = argv[optind];
293 : 38 : optind++;
294 : : }
295 : :
296 [ - + ]: 53 : if (optind < argc)
297 : : {
1840 peter@eisentraut.org 298 :UBC 0 : pg_log_error("too many command-line arguments (first is \"%s\")",
299 : : argv[optind]);
737 tgl@sss.pgh.pa.us 300 : 0 : pg_log_error_hint("Try \"%s --help\" for more information.", progname);
4380 andrew@dunslane.net 301 : 0 : exit(1);
302 : : }
303 : :
304 : : /*
305 : : * Validate the combination of filters specified in the command-line
306 : : * options.
307 : : */
623 andrew@dunslane.net 308 :CBC 53 : check_objfilter();
309 : :
3369 alvherre@alvh.no-ip. 310 [ + + ]: 48 : if (vacopts.analyze_only)
311 : : {
312 [ - + ]: 15 : if (vacopts.full)
737 tgl@sss.pgh.pa.us 313 :UBC 0 : pg_fatal("cannot use the \"%s\" option when performing only analyze",
314 : : "full");
3369 alvherre@alvh.no-ip. 315 [ - + ]:CBC 15 : if (vacopts.freeze)
737 tgl@sss.pgh.pa.us 316 :UBC 0 : pg_fatal("cannot use the \"%s\" option when performing only analyze",
317 : : "freeze");
1923 michael@paquier.xyz 318 [ + + ]:CBC 15 : if (vacopts.disable_page_skipping)
737 tgl@sss.pgh.pa.us 319 : 1 : pg_fatal("cannot use the \"%s\" option when performing only analyze",
320 : : "disable-page-skipping");
1031 pg@bowt.ie 321 [ + + ]: 14 : if (vacopts.no_index_cleanup)
737 tgl@sss.pgh.pa.us 322 : 1 : pg_fatal("cannot use the \"%s\" option when performing only analyze",
323 : : "no-index-cleanup");
1031 pg@bowt.ie 324 [ - + ]: 13 : if (vacopts.force_index_cleanup)
737 tgl@sss.pgh.pa.us 325 :UBC 0 : pg_fatal("cannot use the \"%s\" option when performing only analyze",
326 : : "force-index-cleanup");
1392 michael@paquier.xyz 327 [ + + ]:CBC 13 : if (!vacopts.do_truncate)
737 tgl@sss.pgh.pa.us 328 : 1 : pg_fatal("cannot use the \"%s\" option when performing only analyze",
329 : : "no-truncate");
405 michael@paquier.xyz 330 [ + + ]: 12 : if (!vacopts.process_main)
331 : 1 : pg_fatal("cannot use the \"%s\" option when performing only analyze",
332 : : "no-process-main");
1160 333 [ + + ]: 11 : if (!vacopts.process_toast)
737 tgl@sss.pgh.pa.us 334 : 1 : pg_fatal("cannot use the \"%s\" option when performing only analyze",
335 : : "no-process-toast");
336 : : /* allow 'and_analyze' with 'analyze_only' */
337 : : }
338 : :
339 : : /* Prohibit full and analyze_only options with parallel option */
1537 akapila@postgresql.o 340 [ + + ]: 43 : if (vacopts.parallel_workers >= 0)
341 : : {
342 [ - + ]: 2 : if (vacopts.analyze_only)
737 tgl@sss.pgh.pa.us 343 :UBC 0 : pg_fatal("cannot use the \"%s\" option when performing only analyze",
344 : : "parallel");
1537 akapila@postgresql.o 345 [ - + ]:CBC 2 : if (vacopts.full)
737 tgl@sss.pgh.pa.us 346 :UBC 0 : pg_fatal("cannot use the \"%s\" option when performing full vacuum",
347 : : "parallel");
348 : : }
349 : :
350 : : /* Prohibit --no-index-cleanup and --force-index-cleanup together */
1031 pg@bowt.ie 351 [ + + - + ]:CBC 43 : if (vacopts.no_index_cleanup && vacopts.force_index_cleanup)
737 tgl@sss.pgh.pa.us 352 :UBC 0 : pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
353 : : "no-index-cleanup", "force-index-cleanup");
354 : :
355 : : /*
356 : : * buffer-usage-limit is not allowed with VACUUM FULL unless ANALYZE is
357 : : * included too.
358 : : */
373 drowley@postgresql.o 359 [ - + - - :CBC 43 : if (vacopts.buffer_usage_limit && vacopts.full && !vacopts.and_analyze)
- - ]
373 drowley@postgresql.o 360 :UBC 0 : pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
361 : : "buffer-usage-limit", "full");
362 : :
363 : : /* fill cparams except for dbname, which is set below */
1273 tgl@sss.pgh.pa.us 364 :CBC 43 : cparams.pghost = host;
365 : 43 : cparams.pgport = port;
366 : 43 : cparams.pguser = username;
367 : 43 : cparams.prompt_password = prompt_password;
368 : 43 : cparams.override_dbname = NULL;
369 : :
1595 michael@paquier.xyz 370 : 43 : setup_cancel_handler(NULL);
371 : :
372 : : /* Avoid opening extra connections. */
3369 alvherre@alvh.no-ip. 373 [ + + - + ]: 43 : if (tbl_count && (concurrentCons > tbl_count))
3369 alvherre@alvh.no-ip. 374 :UBC 0 : concurrentCons = tbl_count;
375 : :
623 andrew@dunslane.net 376 [ + + ]:CBC 43 : if (objfilter & OBJFILTER_ALL_DBS)
377 : : {
1273 tgl@sss.pgh.pa.us 378 : 13 : cparams.dbname = maintenance_db;
379 : :
380 : 13 : vacuum_all_databases(&cparams, &vacopts,
381 : : analyze_in_stages,
382 : : &objects,
383 : : concurrentCons,
384 : : progname, echo, quiet);
385 : : }
386 : : else
387 : : {
7606 peter_e@gmx.net 388 [ - + ]: 30 : if (dbname == NULL)
389 : : {
7606 peter_e@gmx.net 390 [ # # ]:UBC 0 : if (getenv("PGDATABASE"))
391 : 0 : dbname = getenv("PGDATABASE");
392 [ # # ]: 0 : else if (getenv("PGUSER"))
393 : 0 : dbname = getenv("PGUSER");
394 : : else
3770 bruce@momjian.us 395 : 0 : dbname = get_user_name_or_exit(progname);
396 : : }
397 : :
1273 tgl@sss.pgh.pa.us 398 :CBC 30 : cparams.dbname = dbname;
399 : :
3369 alvherre@alvh.no-ip. 400 [ + + ]: 30 : if (analyze_in_stages)
401 : : {
402 : : int stage;
403 : :
404 [ + + ]: 4 : for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
405 : : {
1273 tgl@sss.pgh.pa.us 406 : 3 : vacuum_one_database(&cparams, &vacopts,
407 : : stage,
408 : : &objects,
409 : : concurrentCons,
410 : : progname, echo, quiet);
411 : : }
412 : : }
413 : : else
414 : 29 : vacuum_one_database(&cparams, &vacopts,
415 : : ANALYZE_NO_STAGE,
416 : : &objects,
417 : : concurrentCons,
418 : : progname, echo, quiet);
419 : : }
420 : :
7606 peter_e@gmx.net 421 : 40 : exit(0);
422 : : }
423 : :
424 : : /*
425 : : * Verify that the filters used at command line are compatible.
426 : : */
427 : : void
623 andrew@dunslane.net 428 : 53 : check_objfilter(void)
429 : : {
430 [ + + ]: 53 : if ((objfilter & OBJFILTER_ALL_DBS) &&
431 [ + + ]: 15 : (objfilter & OBJFILTER_DATABASE))
432 : 2 : pg_fatal("cannot vacuum all databases and a specific one at the same time");
433 : :
434 [ + + ]: 51 : if ((objfilter & OBJFILTER_TABLE) &&
435 [ + + ]: 12 : (objfilter & OBJFILTER_SCHEMA))
436 : 1 : pg_fatal("cannot vacuum all tables in schema(s) and specific table(s) at the same time");
437 : :
438 [ + + ]: 50 : if ((objfilter & OBJFILTER_TABLE) &&
439 [ + + ]: 11 : (objfilter & OBJFILTER_SCHEMA_EXCLUDE))
440 : 1 : pg_fatal("cannot vacuum specific table(s) and exclude schema(s) at the same time");
441 : :
442 [ + + ]: 49 : if ((objfilter & OBJFILTER_SCHEMA) &&
443 [ + + ]: 4 : (objfilter & OBJFILTER_SCHEMA_EXCLUDE))
444 : 1 : pg_fatal("cannot vacuum all tables in schema(s) and exclude schema(s) at the same time");
445 : 48 : }
446 : :
447 : : /*
448 : : * Returns a newly malloc'd version of 'src' with escaped single quotes and
449 : : * backslashes.
450 : : */
451 : : static char *
206 drowley@postgresql.o 452 :UBC 0 : escape_quotes(const char *src)
453 : : {
454 : 0 : char *result = escape_single_quotes_ascii(src);
455 : :
456 [ # # ]: 0 : if (!result)
457 : 0 : pg_fatal("out of memory");
458 : 0 : return result;
459 : : }
460 : :
461 : : /*
462 : : * vacuum_one_database
463 : : *
464 : : * Process tables in the given database. If the 'tables' list is empty,
465 : : * process all tables in the database.
466 : : *
467 : : * Note that this function is only concerned with running exactly one stage
468 : : * when in analyze-in-stages mode; caller must iterate on us if necessary.
469 : : *
470 : : * If concurrentCons is > 1, multiple connections are used to vacuum tables
471 : : * in parallel. In this case and if the table list is empty, we first obtain
472 : : * a list of tables from the database.
473 : : */
474 : : static void
1130 rhaas@postgresql.org 475 :CBC 67 : vacuum_one_database(ConnParams *cparams,
476 : : vacuumingOptions *vacopts,
477 : : int stage,
478 : : SimpleStringList *objects,
479 : : int concurrentCons,
480 : : const char *progname, bool echo, bool quiet)
481 : : {
482 : : PQExpBufferData sql;
483 : : PQExpBufferData buf;
484 : : PQExpBufferData catalog_query;
485 : : PGresult *res;
486 : : PGconn *conn;
487 : : SimpleStringListCell *cell;
488 : : ParallelSlotArray *sa;
3369 alvherre@alvh.no-ip. 489 : 67 : SimpleStringList dbtables = {NULL, NULL};
490 : : int i;
491 : : int ntups;
3168 andres@anarazel.de 492 : 67 : bool failed = false;
623 andrew@dunslane.net 493 : 67 : bool objects_listed = false;
1900 michael@paquier.xyz 494 : 67 : bool has_where = false;
495 : : const char *initcmd;
3369 alvherre@alvh.no-ip. 496 : 67 : const char *stage_commands[] = {
497 : : "SET default_statistics_target=1; SET vacuum_cost_delay=0;",
498 : : "SET default_statistics_target=10; RESET vacuum_cost_delay;",
499 : : "RESET default_statistics_target;"
500 : : };
501 : 67 : const char *stage_messages[] = {
502 : : gettext_noop("Generating minimal optimizer statistics (1 target)"),
503 : : gettext_noop("Generating medium optimizer statistics (10 targets)"),
504 : : gettext_noop("Generating default (full) optimizer statistics")
505 : : };
506 : :
507 [ + + + - : 67 : Assert(stage == ANALYZE_NO_STAGE ||
+ - ]
508 : : (stage >= 0 && stage < ANALYZE_NUM_STAGES));
509 : :
1273 tgl@sss.pgh.pa.us 510 : 67 : conn = connectDatabase(cparams, progname, echo, false, true);
511 : :
1923 michael@paquier.xyz 512 [ + + - + ]: 66 : if (vacopts->disable_page_skipping && PQserverVersion(conn) < 90600)
513 : : {
1923 michael@paquier.xyz 514 :UBC 0 : PQfinish(conn);
737 tgl@sss.pgh.pa.us 515 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
516 : : "disable-page-skipping", "9.6");
517 : : }
518 : :
1031 pg@bowt.ie 519 [ + + - + ]:CBC 66 : if (vacopts->no_index_cleanup && PQserverVersion(conn) < 120000)
520 : : {
1392 michael@paquier.xyz 521 :UBC 0 : PQfinish(conn);
737 tgl@sss.pgh.pa.us 522 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
523 : : "no-index-cleanup", "12");
524 : : }
525 : :
1031 pg@bowt.ie 526 [ - + - - ]:CBC 66 : if (vacopts->force_index_cleanup && PQserverVersion(conn) < 120000)
527 : : {
1031 pg@bowt.ie 528 :UBC 0 : PQfinish(conn);
737 tgl@sss.pgh.pa.us 529 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
530 : : "force-index-cleanup", "12");
531 : : }
532 : :
1392 michael@paquier.xyz 533 [ + + - + ]:CBC 66 : if (!vacopts->do_truncate && PQserverVersion(conn) < 120000)
534 : : {
1392 michael@paquier.xyz 535 :UBC 0 : PQfinish(conn);
737 tgl@sss.pgh.pa.us 536 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
537 : : "no-truncate", "12");
538 : : }
539 : :
405 michael@paquier.xyz 540 [ + + - + ]:CBC 66 : if (!vacopts->process_main && PQserverVersion(conn) < 160000)
541 : : {
405 michael@paquier.xyz 542 :UBC 0 : PQfinish(conn);
543 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
544 : : "no-process-main", "16");
545 : : }
546 : :
1160 michael@paquier.xyz 547 [ + + - + ]:CBC 66 : if (!vacopts->process_toast && PQserverVersion(conn) < 140000)
548 : : {
1160 michael@paquier.xyz 549 :UBC 0 : PQfinish(conn);
737 tgl@sss.pgh.pa.us 550 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
551 : : "no-process-toast", "14");
552 : : }
553 : :
1923 michael@paquier.xyz 554 [ + + - + ]:CBC 66 : if (vacopts->skip_locked && PQserverVersion(conn) < 120000)
555 : : {
1923 michael@paquier.xyz 556 :UBC 0 : PQfinish(conn);
737 tgl@sss.pgh.pa.us 557 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
558 : : "skip-locked", "12");
559 : : }
560 : :
1900 michael@paquier.xyz 561 [ + + - + ]:CBC 66 : if (vacopts->min_xid_age != 0 && PQserverVersion(conn) < 90600)
737 tgl@sss.pgh.pa.us 562 :UBC 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
563 : : "--min-xid-age", "9.6");
564 : :
1900 michael@paquier.xyz 565 [ + + - + ]:CBC 66 : if (vacopts->min_mxid_age != 0 && PQserverVersion(conn) < 90600)
737 tgl@sss.pgh.pa.us 566 :UBC 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
567 : : "--min-mxid-age", "9.6");
568 : :
1537 akapila@postgresql.o 569 [ + + - + ]:CBC 66 : if (vacopts->parallel_workers >= 0 && PQserverVersion(conn) < 130000)
737 tgl@sss.pgh.pa.us 570 :UBC 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
571 : : "--parallel", "13");
572 : :
373 drowley@postgresql.o 573 [ - + - - ]:CBC 66 : if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
373 drowley@postgresql.o 574 :UBC 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
575 : : "--buffer-usage-limit", "16");
576 : :
577 : : /* skip_database_stats is used automatically if server supports it */
464 tgl@sss.pgh.pa.us 578 :CBC 66 : vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
579 : :
3369 alvherre@alvh.no-ip. 580 [ + - ]: 66 : if (!quiet)
581 : : {
582 [ + + ]: 66 : if (stage != ANALYZE_NO_STAGE)
2806 noah@leadboat.com 583 : 9 : printf(_("%s: processing database \"%s\": %s\n"),
584 : : progname, PQdb(conn), _(stage_messages[stage]));
585 : : else
586 : 57 : printf(_("%s: vacuuming database \"%s\"\n"),
587 : : progname, PQdb(conn));
3369 alvherre@alvh.no-ip. 588 : 66 : fflush(stdout);
589 : : }
590 : :
591 : : /*
592 : : * Prepare the list of tables to process by querying the catalogs.
593 : : *
594 : : * Since we execute the constructed query with the default search_path
595 : : * (which could be unsafe), everything in this query MUST be fully
596 : : * qualified.
597 : : *
598 : : * First, build a WITH clause for the catalog query if any tables were
599 : : * specified, with a set of values made of relation names and their
600 : : * optional set of columns. This is used to match any provided column
601 : : * lists with the generated qualified identifiers and to filter for the
602 : : * tables provided via --table. If a listed table does not exist, the
603 : : * catalog query will fail.
604 : : */
1902 michael@paquier.xyz 605 : 66 : initPQExpBuffer(&catalog_query);
623 andrew@dunslane.net 606 [ + - + + ]: 87 : for (cell = objects ? objects->head : NULL; cell; cell = cell->next)
607 : : {
608 : 21 : char *just_table = NULL;
609 : 21 : const char *just_columns = NULL;
610 : :
611 [ + + ]: 21 : if (!objects_listed)
612 : : {
1746 drowley@postgresql.o 613 : 19 : appendPQExpBufferStr(&catalog_query,
614 : : "WITH listed_objects (object_oid, column_list) "
615 : : "AS (\n VALUES (");
623 andrew@dunslane.net 616 : 19 : objects_listed = true;
617 : : }
618 : : else
1746 drowley@postgresql.o 619 : 2 : appendPQExpBufferStr(&catalog_query, ",\n (");
620 : :
623 andrew@dunslane.net 621 [ + + ]: 21 : if (objfilter & (OBJFILTER_SCHEMA | OBJFILTER_SCHEMA_EXCLUDE))
622 : : {
623 : 10 : appendStringLiteralConn(&catalog_query, cell->val, conn);
624 : 10 : appendPQExpBufferStr(&catalog_query, "::pg_catalog.regnamespace, ");
625 : : }
626 : :
627 [ + + ]: 21 : if (objfilter & OBJFILTER_TABLE)
628 : : {
629 : : /*
630 : : * Split relation and column names given by the user, this is used
631 : : * to feed the CTE with values on which are performed pre-run
632 : : * validity checks as well. For now these happen only on the
633 : : * relation name.
634 : : */
635 : 11 : splitTableColumnsSpec(cell->val, PQclientEncoding(conn),
636 : : &just_table, &just_columns);
637 : :
638 : 11 : appendStringLiteralConn(&catalog_query, just_table, conn);
639 : 11 : appendPQExpBufferStr(&catalog_query, "::pg_catalog.regclass, ");
640 : : }
641 : :
1902 michael@paquier.xyz 642 [ + + + + ]: 21 : if (just_columns && just_columns[0] != '\0')
643 : 5 : appendStringLiteralConn(&catalog_query, just_columns, conn);
644 : : else
645 : 16 : appendPQExpBufferStr(&catalog_query, "NULL");
646 : :
647 : 21 : appendPQExpBufferStr(&catalog_query, "::pg_catalog.text)");
648 : :
649 : 21 : pg_free(just_table);
650 : : }
651 : :
652 : : /* Finish formatting the CTE */
623 andrew@dunslane.net 653 [ + + ]: 66 : if (objects_listed)
1746 drowley@postgresql.o 654 : 19 : appendPQExpBufferStr(&catalog_query, "\n)\n");
655 : :
656 : 66 : appendPQExpBufferStr(&catalog_query, "SELECT c.relname, ns.nspname");
657 : :
623 andrew@dunslane.net 658 [ + + ]: 66 : if (objects_listed)
659 : 19 : appendPQExpBufferStr(&catalog_query, ", listed_objects.column_list");
660 : :
1746 drowley@postgresql.o 661 : 66 : appendPQExpBufferStr(&catalog_query,
662 : : " FROM pg_catalog.pg_class c\n"
663 : : " JOIN pg_catalog.pg_namespace ns"
664 : : " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
665 : : " LEFT JOIN pg_catalog.pg_class t"
666 : : " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
667 : :
668 : : /* Used to match the tables or schemas listed by the user */
623 andrew@dunslane.net 669 [ + + ]: 66 : if (objects_listed)
670 : : {
202 dgustafsson@postgres 671 : 19 : appendPQExpBufferStr(&catalog_query, " LEFT JOIN listed_objects"
672 : : " ON listed_objects.object_oid"
673 : : " OPERATOR(pg_catalog.=) ");
674 : :
623 andrew@dunslane.net 675 [ + + ]: 19 : if (objfilter & OBJFILTER_TABLE)
676 : 11 : appendPQExpBufferStr(&catalog_query, "c.oid\n");
677 : : else
678 : 8 : appendPQExpBufferStr(&catalog_query, "ns.oid\n");
679 : :
202 dgustafsson@postgres 680 [ + + ]: 19 : if (objfilter & OBJFILTER_SCHEMA_EXCLUDE)
681 : 4 : appendPQExpBuffer(&catalog_query,
682 : : " WHERE listed_objects.object_oid IS NULL\n");
683 : : else
684 : 15 : appendPQExpBuffer(&catalog_query,
685 : : " WHERE listed_objects.object_oid IS NOT NULL\n");
686 : 19 : has_where = true;
687 : : }
688 : :
689 : : /*
690 : : * If no tables were listed, filter for the relevant relation types. If
691 : : * tables were given via --table, don't bother filtering by relation type.
692 : : * Instead, let the server decide whether a given relation can be
693 : : * processed in which case the user will know about it.
694 : : */
623 andrew@dunslane.net 695 [ + + ]: 66 : if ((objfilter & OBJFILTER_TABLE) == 0)
696 : : {
202 dgustafsson@postgres 697 [ + + ]: 55 : appendPQExpBuffer(&catalog_query,
698 : : " %s c.relkind OPERATOR(pg_catalog.=) ANY (array["
699 : : CppAsString2(RELKIND_RELATION) ", "
700 : : CppAsString2(RELKIND_MATVIEW) "])\n",
701 : : has_where ? "AND" : "WHERE");
1900 michael@paquier.xyz 702 : 55 : has_where = true;
703 : : }
704 : :
705 : : /*
706 : : * For --min-xid-age and --min-mxid-age, the age of the relation is the
707 : : * greatest of the ages of the main relation and its associated TOAST
708 : : * table. The commands generated by vacuumdb will also process the TOAST
709 : : * table for the relation if necessary, so it does not need to be
710 : : * considered separately.
711 : : */
712 [ + + ]: 66 : if (vacopts->min_xid_age != 0)
713 : : {
714 [ + - ]: 1 : appendPQExpBuffer(&catalog_query,
715 : : " %s GREATEST(pg_catalog.age(c.relfrozenxid),"
716 : : " pg_catalog.age(t.relfrozenxid)) "
717 : : " OPERATOR(pg_catalog.>=) '%d'::pg_catalog.int4\n"
718 : : " AND c.relfrozenxid OPERATOR(pg_catalog.!=)"
719 : : " '0'::pg_catalog.xid\n",
720 : : has_where ? "AND" : "WHERE", vacopts->min_xid_age);
721 : 1 : has_where = true;
722 : : }
723 : :
724 [ + + ]: 66 : if (vacopts->min_mxid_age != 0)
725 : : {
726 [ + - ]: 1 : appendPQExpBuffer(&catalog_query,
727 : : " %s GREATEST(pg_catalog.mxid_age(c.relminmxid),"
728 : : " pg_catalog.mxid_age(t.relminmxid)) OPERATOR(pg_catalog.>=)"
729 : : " '%d'::pg_catalog.int4\n"
730 : : " AND c.relminmxid OPERATOR(pg_catalog.!=)"
731 : : " '0'::pg_catalog.xid\n",
732 : : has_where ? "AND" : "WHERE", vacopts->min_mxid_age);
733 : 1 : has_where = true;
734 : : }
735 : :
736 : : /*
737 : : * Execute the catalog query. We use the default search_path for this
738 : : * query for consistency with table lookups done elsewhere by the user.
739 : : */
1746 drowley@postgresql.o 740 : 66 : appendPQExpBufferStr(&catalog_query, " ORDER BY c.relpages DESC;");
1731 michael@paquier.xyz 741 : 66 : executeCommand(conn, "RESET search_path;", echo);
742 : 66 : res = executeQuery(conn, catalog_query.data, echo);
1902 743 : 65 : termPQExpBuffer(&catalog_query);
1731 744 : 65 : PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
745 : :
746 : : /*
747 : : * If no rows are returned, there are no matching tables, so we are done.
748 : : */
1902 749 : 65 : ntups = PQntuples(res);
750 [ + + ]: 65 : if (ntups == 0)
751 : : {
752 : 2 : PQclear(res);
753 : 2 : PQfinish(conn);
754 : 2 : return;
755 : : }
756 : :
757 : : /*
758 : : * Build qualified identifiers for each table, including the column list
759 : : * if given.
760 : : */
761 : 63 : initPQExpBuffer(&buf);
762 [ + + ]: 3487 : for (i = 0; i < ntups; i++)
763 : : {
764 : 3424 : appendPQExpBufferStr(&buf,
765 : 3424 : fmtQualifiedId(PQgetvalue(res, i, 1),
766 : 3424 : PQgetvalue(res, i, 0)));
767 : :
623 andrew@dunslane.net 768 [ + + + + ]: 3424 : if (objects_listed && !PQgetisnull(res, i, 2))
1902 michael@paquier.xyz 769 : 5 : appendPQExpBufferStr(&buf, PQgetvalue(res, i, 2));
770 : :
771 : 3424 : simple_string_list_append(&dbtables, buf.data);
772 : 3424 : resetPQExpBuffer(&buf);
773 : : }
774 : 63 : termPQExpBuffer(&buf);
775 : 63 : PQclear(res);
776 : :
777 : : /*
778 : : * Ensure concurrentCons is sane. If there are more connections than
779 : : * vacuumable relations, we don't need to use them all.
780 : : */
1318 tgl@sss.pgh.pa.us 781 [ - + ]: 63 : if (concurrentCons > ntups)
1318 tgl@sss.pgh.pa.us 782 :UBC 0 : concurrentCons = ntups;
1318 tgl@sss.pgh.pa.us 783 [ - + ]:CBC 63 : if (concurrentCons <= 0)
1318 tgl@sss.pgh.pa.us 784 :UBC 0 : concurrentCons = 1;
785 : :
786 : : /*
787 : : * All slots need to be prepared to run the appropriate analyze stage, if
788 : : * caller requested that mode. We have to prepare the initial connection
789 : : * ourselves before setting up the slots.
790 : : */
1130 rhaas@postgresql.org 791 [ + + ]:CBC 63 : if (stage == ANALYZE_NO_STAGE)
792 : 54 : initcmd = NULL;
793 : : else
794 : : {
795 : 9 : initcmd = stage_commands[stage];
796 : 9 : executeCommand(conn, initcmd, echo);
797 : : }
798 : :
799 : : /*
800 : : * Setup the database connections. We reuse the connection we already have
801 : : * for the first slot. If not in parallel mode, the first slot in the
802 : : * array contains the connection.
803 : : */
804 : 63 : sa = ParallelSlotsSetup(concurrentCons, cparams, progname, echo, initcmd);
805 : 63 : ParallelSlotsAdoptConn(sa, conn);
806 : :
1902 michael@paquier.xyz 807 : 63 : initPQExpBuffer(&sql);
808 : :
809 : 63 : cell = dbtables.head;
810 : : do
811 : : {
812 : 3424 : const char *tabname = cell->val;
813 : : ParallelSlot *free_slot;
814 : :
3369 alvherre@alvh.no-ip. 815 [ - + ]: 3424 : if (CancelRequested)
816 : : {
3168 andres@anarazel.de 817 :UBC 0 : failed = true;
3369 alvherre@alvh.no-ip. 818 : 0 : goto finish;
819 : : }
820 : :
1130 rhaas@postgresql.org 821 :CBC 3424 : free_slot = ParallelSlotsGetIdle(sa, NULL);
1731 michael@paquier.xyz 822 [ - + ]: 3424 : if (!free_slot)
823 : : {
1731 michael@paquier.xyz 824 :UBC 0 : failed = true;
825 : 0 : goto finish;
826 : : }
827 : :
1902 michael@paquier.xyz 828 :CBC 3424 : prepare_vacuum_command(&sql, PQserverVersion(free_slot->connection),
829 : : vacopts, tabname);
830 : :
831 : : /*
832 : : * Execute the vacuum. All errors are handled in processQueryResult
833 : : * through ParallelSlotsGetIdle.
834 : : */
1164 rhaas@postgresql.org 835 : 3424 : ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
3369 alvherre@alvh.no-ip. 836 : 3424 : run_vacuum_command(free_slot->connection, sql.data,
837 : : echo, tabname);
838 : :
1902 michael@paquier.xyz 839 : 3424 : cell = cell->next;
3369 alvherre@alvh.no-ip. 840 [ + + ]: 3424 : } while (cell != NULL);
841 : :
1130 rhaas@postgresql.org 842 [ + + ]: 63 : if (!ParallelSlotsWaitCompletion(sa))
843 : : {
1731 michael@paquier.xyz 844 : 1 : failed = true;
464 tgl@sss.pgh.pa.us 845 : 1 : goto finish;
846 : : }
847 : :
848 : : /* If we used SKIP_DATABASE_STATS, mop up with ONLY_DATABASE_STATS */
849 [ - + + + ]: 62 : if (vacopts->skip_database_stats && stage == ANALYZE_NO_STAGE)
850 : : {
851 : 53 : const char *cmd = "VACUUM (ONLY_DATABASE_STATS);";
852 : 53 : ParallelSlot *free_slot = ParallelSlotsGetIdle(sa, NULL);
853 : :
854 [ - + ]: 53 : if (!free_slot)
855 : : {
464 tgl@sss.pgh.pa.us 856 :UBC 0 : failed = true;
857 : 0 : goto finish;
858 : : }
859 : :
464 tgl@sss.pgh.pa.us 860 :CBC 53 : ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
861 : 53 : run_vacuum_command(free_slot->connection, cmd, echo, NULL);
862 : :
863 [ + - ]: 53 : if (!ParallelSlotsWaitCompletion(sa))
464 tgl@sss.pgh.pa.us 864 :UBC 0 : failed = true;
865 : : }
866 : :
3369 alvherre@alvh.no-ip. 867 : 0 : finish:
1130 rhaas@postgresql.org 868 :CBC 63 : ParallelSlotsTerminate(sa);
869 : 63 : pg_free(sa);
870 : :
3369 alvherre@alvh.no-ip. 871 : 63 : termPQExpBuffer(&sql);
872 : :
3168 andres@anarazel.de 873 [ + + ]: 63 : if (failed)
3369 alvherre@alvh.no-ip. 874 : 1 : exit(1);
875 : : }
876 : :
877 : : /*
878 : : * Vacuum/analyze all connectable databases.
879 : : *
880 : : * In analyze-in-stages mode, we process all databases in one stage before
881 : : * moving on to the next stage. That ensure minimal stats are available
882 : : * quickly everywhere before generating more detailed ones.
883 : : */
884 : : static void
1273 tgl@sss.pgh.pa.us 885 : 13 : vacuum_all_databases(ConnParams *cparams,
886 : : vacuumingOptions *vacopts,
887 : : bool analyze_in_stages,
888 : : SimpleStringList *objects,
889 : : int concurrentCons,
890 : : const char *progname, bool echo, bool quiet)
891 : : {
892 : : PGconn *conn;
893 : : PGresult *result;
894 : : int stage;
895 : : int i;
896 : :
897 : 13 : conn = connectMaintenanceDatabase(cparams, progname, echo);
3369 alvherre@alvh.no-ip. 898 : 13 : result = executeQuery(conn,
899 : : "SELECT datname FROM pg_database WHERE datallowconn AND datconnlimit <> -2 ORDER BY 1;",
900 : : echo);
901 : 13 : PQfinish(conn);
902 : :
903 [ + + ]: 13 : if (analyze_in_stages)
904 : : {
905 : : /*
906 : : * When analyzing all databases in stages, we analyze them all in the
907 : : * fastest stage first, so that initial statistics become available
908 : : * for all of them as soon as possible.
909 : : *
910 : : * This means we establish several times as many connections, but
911 : : * that's a secondary consideration.
912 : : */
913 [ + + ]: 4 : for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
914 : : {
915 [ + + ]: 9 : for (i = 0; i < PQntuples(result); i++)
916 : : {
1273 tgl@sss.pgh.pa.us 917 : 6 : cparams->override_dbname = PQgetvalue(result, i, 0);
918 : :
919 : 6 : vacuum_one_database(cparams, vacopts,
920 : : stage,
921 : : objects,
922 : : concurrentCons,
923 : : progname, echo, quiet);
924 : : }
925 : : }
926 : : }
927 : : else
928 : : {
3369 alvherre@alvh.no-ip. 929 [ + + ]: 41 : for (i = 0; i < PQntuples(result); i++)
930 : : {
1273 tgl@sss.pgh.pa.us 931 : 29 : cparams->override_dbname = PQgetvalue(result, i, 0);
932 : :
933 : 29 : vacuum_one_database(cparams, vacopts,
934 : : ANALYZE_NO_STAGE,
935 : : objects,
936 : : concurrentCons,
937 : : progname, echo, quiet);
938 : : }
939 : : }
940 : :
3369 alvherre@alvh.no-ip. 941 : 13 : PQclear(result);
942 : 13 : }
943 : :
944 : : /*
945 : : * Construct a vacuum/analyze command to run based on the given options, in the
946 : : * given string buffer, which may contain previous garbage.
947 : : *
948 : : * The table name used must be already properly quoted. The command generated
949 : : * depends on the server version involved and it is semicolon-terminated.
950 : : */
951 : : static void
1902 michael@paquier.xyz 952 : 3424 : prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
953 : : vacuumingOptions *vacopts, const char *table)
954 : : {
1923 955 : 3424 : const char *paren = " (";
956 : 3424 : const char *comma = ", ";
957 : 3424 : const char *sep = paren;
958 : :
3369 alvherre@alvh.no-ip. 959 : 3424 : resetPQExpBuffer(sql);
960 : :
961 [ + + ]: 3424 : if (vacopts->analyze_only)
962 : : {
963 : 1228 : appendPQExpBufferStr(sql, "ANALYZE");
964 : :
965 : : /* parenthesized grammar of ANALYZE is supported since v11 */
1902 michael@paquier.xyz 966 [ + - ]: 1228 : if (serverVersion >= 110000)
967 : : {
1923 968 [ + + ]: 1228 : if (vacopts->skip_locked)
969 : : {
970 : : /* SKIP_LOCKED is supported since v12 */
1902 971 [ - + ]: 68 : Assert(serverVersion >= 120000);
1923 972 : 68 : appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
973 : 68 : sep = comma;
974 : : }
975 [ - + ]: 1228 : if (vacopts->verbose)
976 : : {
1923 michael@paquier.xyz 977 :UBC 0 : appendPQExpBuffer(sql, "%sVERBOSE", sep);
978 : 0 : sep = comma;
979 : : }
206 drowley@postgresql.o 980 [ - + ]:CBC 1228 : if (vacopts->buffer_usage_limit)
981 : : {
206 drowley@postgresql.o 982 [ # # ]:UBC 0 : Assert(serverVersion >= 160000);
983 : 0 : appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
984 : : vacopts->buffer_usage_limit);
985 : 0 : sep = comma;
986 : : }
1923 michael@paquier.xyz 987 [ + + ]:CBC 1228 : if (sep != paren)
988 : 68 : appendPQExpBufferChar(sql, ')');
989 : : }
990 : : else
991 : : {
1923 michael@paquier.xyz 992 [ # # ]:UBC 0 : if (vacopts->verbose)
993 : 0 : appendPQExpBufferStr(sql, " VERBOSE");
994 : : }
995 : : }
996 : : else
997 : : {
3369 alvherre@alvh.no-ip. 998 :CBC 2196 : appendPQExpBufferStr(sql, "VACUUM");
999 : :
1000 : : /* parenthesized grammar of VACUUM is supported since v9.0 */
1902 michael@paquier.xyz 1001 [ + - ]: 2196 : if (serverVersion >= 90000)
1002 : : {
1923 1003 [ + + ]: 2196 : if (vacopts->disable_page_skipping)
1004 : : {
1005 : : /* DISABLE_PAGE_SKIPPING is supported since v9.6 */
1902 1006 [ - + ]: 68 : Assert(serverVersion >= 90600);
1923 1007 : 68 : appendPQExpBuffer(sql, "%sDISABLE_PAGE_SKIPPING", sep);
1008 : 68 : sep = comma;
1009 : : }
1031 pg@bowt.ie 1010 [ + + ]: 2196 : if (vacopts->no_index_cleanup)
1011 : : {
1012 : : /* "INDEX_CLEANUP FALSE" has been supported since v12 */
1392 michael@paquier.xyz 1013 [ - + ]: 68 : Assert(serverVersion >= 120000);
1031 pg@bowt.ie 1014 [ - + ]: 68 : Assert(!vacopts->force_index_cleanup);
1392 michael@paquier.xyz 1015 : 68 : appendPQExpBuffer(sql, "%sINDEX_CLEANUP FALSE", sep);
1016 : 68 : sep = comma;
1017 : : }
1031 pg@bowt.ie 1018 [ - + ]: 2196 : if (vacopts->force_index_cleanup)
1019 : : {
1020 : : /* "INDEX_CLEANUP TRUE" has been supported since v12 */
1031 pg@bowt.ie 1021 [ # # ]:UBC 0 : Assert(serverVersion >= 120000);
1022 [ # # ]: 0 : Assert(!vacopts->no_index_cleanup);
1023 : 0 : appendPQExpBuffer(sql, "%sINDEX_CLEANUP TRUE", sep);
1024 : 0 : sep = comma;
1025 : : }
1392 michael@paquier.xyz 1026 [ + + ]:CBC 2196 : if (!vacopts->do_truncate)
1027 : : {
1028 : : /* TRUNCATE is supported since v12 */
1029 [ - + ]: 68 : Assert(serverVersion >= 120000);
1030 : 68 : appendPQExpBuffer(sql, "%sTRUNCATE FALSE", sep);
1031 : 68 : sep = comma;
1032 : : }
405 1033 [ + + ]: 2196 : if (!vacopts->process_main)
1034 : : {
1035 : : /* PROCESS_MAIN is supported since v16 */
1036 [ - + ]: 68 : Assert(serverVersion >= 160000);
1037 : 68 : appendPQExpBuffer(sql, "%sPROCESS_MAIN FALSE", sep);
1038 : 68 : sep = comma;
1039 : : }
1160 1040 [ + + ]: 2196 : if (!vacopts->process_toast)
1041 : : {
1042 : : /* PROCESS_TOAST is supported since v14 */
1043 [ - + ]: 68 : Assert(serverVersion >= 140000);
1044 : 68 : appendPQExpBuffer(sql, "%sPROCESS_TOAST FALSE", sep);
1045 : 68 : sep = comma;
1046 : : }
464 tgl@sss.pgh.pa.us 1047 [ + - ]: 2196 : if (vacopts->skip_database_stats)
1048 : : {
1049 : : /* SKIP_DATABASE_STATS is supported since v16 */
1050 [ - + ]: 2196 : Assert(serverVersion >= 160000);
1051 : 2196 : appendPQExpBuffer(sql, "%sSKIP_DATABASE_STATS", sep);
1052 : 2196 : sep = comma;
1053 : : }
1923 michael@paquier.xyz 1054 [ + + ]: 2196 : if (vacopts->skip_locked)
1055 : : {
1056 : : /* SKIP_LOCKED is supported since v12 */
1902 1057 [ - + ]: 68 : Assert(serverVersion >= 120000);
1923 1058 : 68 : appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
1059 : 68 : sep = comma;
1060 : : }
3369 alvherre@alvh.no-ip. 1061 [ + + ]: 2196 : if (vacopts->full)
1062 : : {
1063 : 68 : appendPQExpBuffer(sql, "%sFULL", sep);
5212 itagaki.takahiro@gma 1064 : 68 : sep = comma;
1065 : : }
3369 alvherre@alvh.no-ip. 1066 [ + + ]: 2196 : if (vacopts->freeze)
1067 : : {
1068 : 476 : appendPQExpBuffer(sql, "%sFREEZE", sep);
5212 itagaki.takahiro@gma 1069 : 476 : sep = comma;
1070 : : }
3369 alvherre@alvh.no-ip. 1071 [ - + ]: 2196 : if (vacopts->verbose)
1072 : : {
3369 alvherre@alvh.no-ip. 1073 :UBC 0 : appendPQExpBuffer(sql, "%sVERBOSE", sep);
5212 itagaki.takahiro@gma 1074 : 0 : sep = comma;
1075 : : }
3369 alvherre@alvh.no-ip. 1076 [ + + ]:CBC 2196 : if (vacopts->and_analyze)
1077 : : {
1078 : 479 : appendPQExpBuffer(sql, "%sANALYZE", sep);
5212 itagaki.takahiro@gma 1079 : 479 : sep = comma;
1080 : : }
1537 akapila@postgresql.o 1081 [ + + ]: 2196 : if (vacopts->parallel_workers >= 0)
1082 : : {
1083 : : /* PARALLEL is supported since v13 */
1084 [ - + ]: 136 : Assert(serverVersion >= 130000);
1085 : 136 : appendPQExpBuffer(sql, "%sPARALLEL %d", sep,
1086 : : vacopts->parallel_workers);
1087 : 136 : sep = comma;
1088 : : }
373 drowley@postgresql.o 1089 [ - + ]: 2196 : if (vacopts->buffer_usage_limit)
1090 : : {
373 drowley@postgresql.o 1091 [ # # ]:UBC 0 : Assert(serverVersion >= 160000);
1092 : 0 : appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
1093 : : vacopts->buffer_usage_limit);
1094 : 0 : sep = comma;
1095 : : }
5212 itagaki.takahiro@gma 1096 [ + - ]:CBC 2196 : if (sep != paren)
3209 heikki.linnakangas@i 1097 : 2196 : appendPQExpBufferChar(sql, ')');
1098 : : }
1099 : : else
1100 : : {
3369 alvherre@alvh.no-ip. 1101 [ # # ]:UBC 0 : if (vacopts->full)
1102 : 0 : appendPQExpBufferStr(sql, " FULL");
1103 [ # # ]: 0 : if (vacopts->freeze)
1104 : 0 : appendPQExpBufferStr(sql, " FREEZE");
1105 [ # # ]: 0 : if (vacopts->verbose)
1106 : 0 : appendPQExpBufferStr(sql, " VERBOSE");
1107 [ # # ]: 0 : if (vacopts->and_analyze)
1108 : 0 : appendPQExpBufferStr(sql, " ANALYZE");
1109 : : }
1110 : : }
1111 : :
1902 michael@paquier.xyz 1112 :CBC 3424 : appendPQExpBuffer(sql, " %s;", table);
3369 alvherre@alvh.no-ip. 1113 : 3424 : }
1114 : :
1115 : : /*
1116 : : * Send a vacuum/analyze command to the server, returning after sending the
1117 : : * command.
1118 : : *
1119 : : * Any errors during command execution are reported to stderr.
1120 : : */
1121 : : static void
1122 : 3477 : run_vacuum_command(PGconn *conn, const char *sql, bool echo,
1123 : : const char *table)
1124 : : {
1125 : : bool status;
1126 : :
1731 michael@paquier.xyz 1127 [ + + ]: 3477 : if (echo)
1128 : 483 : printf("%s\n", sql);
1129 : :
1130 : 3477 : status = PQsendQuery(conn, sql) == 1;
1131 : :
3310 alvherre@alvh.no-ip. 1132 [ - + ]: 3477 : if (!status)
1133 : : {
3369 alvherre@alvh.no-ip. 1134 [ # # ]:UBC 0 : if (table)
1840 peter@eisentraut.org 1135 : 0 : pg_log_error("vacuuming of table \"%s\" in database \"%s\" failed: %s",
1136 : : table, PQdb(conn), PQerrorMessage(conn));
1137 : : else
1138 : 0 : pg_log_error("vacuuming of database \"%s\" failed: %s",
1139 : : PQdb(conn), PQerrorMessage(conn));
1140 : : }
3369 alvherre@alvh.no-ip. 1141 :CBC 3477 : }
1142 : :
1143 : : static void
7606 peter_e@gmx.net 1144 : 1 : help(const char *progname)
1145 : : {
1146 : 1 : printf(_("%s cleans and analyzes a PostgreSQL database.\n\n"), progname);
1147 : 1 : printf(_("Usage:\n"));
1148 : 1 : printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
1149 : 1 : printf(_("\nOptions:\n"));
1150 : 1 : printf(_(" -a, --all vacuum all databases\n"));
364 drowley@postgresql.o 1151 : 1 : printf(_(" --buffer-usage-limit=SIZE size of ring buffer used for vacuum\n"));
7606 peter_e@gmx.net 1152 : 1 : printf(_(" -d, --dbname=DBNAME database to vacuum\n"));
1923 michael@paquier.xyz 1153 : 1 : printf(_(" --disable-page-skipping disable all page-skipping behavior\n"));
5527 peter_e@gmx.net 1154 : 1 : printf(_(" -e, --echo show the commands being sent to the server\n"));
7606 1155 : 1 : printf(_(" -f, --full do full vacuuming\n"));
5534 bruce@momjian.us 1156 : 1 : printf(_(" -F, --freeze freeze row transaction information\n"));
1024 peter@eisentraut.org 1157 : 1 : printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
3133 peter_e@gmx.net 1158 : 1 : printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
1900 michael@paquier.xyz 1159 : 1 : printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
1160 : 1 : printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
1392 1161 : 1 : printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
405 1162 : 1 : printf(_(" --no-process-main skip the main relation\n"));
1160 1163 : 1 : printf(_(" --no-process-toast skip the TOAST table associated with the table to vacuum\n"));
1392 1164 : 1 : printf(_(" --no-truncate don't truncate empty pages at the end of the table\n"));
202 dgustafsson@postgres 1165 : 1 : printf(_(" -n, --schema=SCHEMA vacuum tables in the specified schema(s) only\n"));
1166 : 1 : printf(_(" -N, --exclude-schema=SCHEMA do not vacuum tables in the specified schema(s)\n"));
1055 akapila@postgresql.o 1167 : 1 : printf(_(" -P, --parallel=PARALLEL_WORKERS use this many background workers for vacuum, if available\n"));
7606 peter_e@gmx.net 1168 : 1 : printf(_(" -q, --quiet don't write any messages\n"));
1923 michael@paquier.xyz 1169 : 1 : printf(_(" --skip-locked skip relations that cannot be immediately locked\n"));
4105 magnus@hagander.net 1170 : 1 : printf(_(" -t, --table='TABLE[(COLUMNS)]' vacuum specific table(s) only\n"));
7606 peter_e@gmx.net 1171 : 1 : printf(_(" -v, --verbose write a lot of output\n"));
4318 1172 : 1 : printf(_(" -V, --version output version information, then exit\n"));
5161 bruce@momjian.us 1173 : 1 : printf(_(" -z, --analyze update optimizer statistics\n"));
3133 peter_e@gmx.net 1174 : 1 : printf(_(" -Z, --analyze-only only update optimizer statistics; no vacuum\n"));
3653 1175 : 1 : printf(_(" --analyze-in-stages only update optimizer statistics, in multiple\n"
1176 : : " stages for faster results; no vacuum\n"));
4318 1177 : 1 : printf(_(" -?, --help show this help, then exit\n"));
7606 1178 : 1 : printf(_("\nConnection options:\n"));
1179 : 1 : printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
1180 : 1 : printf(_(" -p, --port=PORT database server port\n"));
1181 : 1 : printf(_(" -U, --username=USERNAME user name to connect as\n"));
5526 1182 : 1 : printf(_(" -w, --no-password never prompt for password\n"));
5969 tgl@sss.pgh.pa.us 1183 : 1 : printf(_(" -W, --password force password prompt\n"));
4513 rhaas@postgresql.org 1184 : 1 : printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
7606 peter_e@gmx.net 1185 : 1 : printf(_("\nRead the description of the SQL command VACUUM for details.\n"));
1507 peter@eisentraut.org 1186 : 1 : printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
1187 : 1 : printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
7606 peter_e@gmx.net 1188 : 1 : }
|