Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : * vacuuming.c
3 : : * Helper routines for vacuumdb
4 : : *
5 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
6 : : * Portions Copyright (c) 1994, Regents of the University of California
7 : : *
8 : : * src/bin/scripts/vacuuming.c
9 : : *
10 : : *-------------------------------------------------------------------------
11 : : */
12 : :
13 : : #include "postgres_fe.h"
14 : :
15 : : #include "catalog/pg_attribute_d.h"
16 : : #include "catalog/pg_class_d.h"
17 : : #include "common/connect.h"
18 : : #include "common/logging.h"
19 : : #include "fe_utils/cancel.h"
20 : : #include "fe_utils/option_utils.h"
21 : : #include "fe_utils/parallel_slot.h"
22 : : #include "fe_utils/query_utils.h"
23 : : #include "fe_utils/string_utils.h"
24 : : #include "vacuuming.h"
25 : :
26 : : typedef struct RetrievedObject
27 : : {
28 : : char *target;
29 : : bool use_only;
30 : : } RetrievedObject;
31 : :
32 : : typedef struct RetrievedObjects
33 : : {
34 : : int num_objects;
35 : : RetrievedObject objects[FLEXIBLE_ARRAY_MEMBER];
36 : : } RetrievedObjects;
37 : :
38 : : static int vacuum_one_database(ConnParams *cparams,
39 : : vacuumingOptions *vacopts,
40 : : int stage,
41 : : SimpleStringList *objects,
42 : : RetrievedObjects **found_objs,
43 : : int concurrentCons,
44 : : const char *progname);
45 : : static int vacuum_all_databases(ConnParams *cparams,
46 : : vacuumingOptions *vacopts,
47 : : SimpleStringList *objects,
48 : : int concurrentCons,
49 : : const char *progname);
50 : : static RetrievedObjects *retrieve_objects(PGconn *conn,
51 : : vacuumingOptions *vacopts,
52 : : SimpleStringList *objects);
53 : : static void free_retrieved_objects(RetrievedObjects *objs);
54 : : static void prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
55 : : vacuumingOptions *vacopts, const char *table,
56 : : bool use_only);
57 : : static void run_vacuum_command(ParallelSlot *free_slot,
58 : : vacuumingOptions *vacopts, const char *sql,
59 : : const char *table);
60 : :
61 : : /*
62 : : * Executes vacuum/analyze as indicated. Returns 0 if the plan is carried
63 : : * to completion, or -1 in case of certain errors (which should hopefully
64 : : * been already reported.) Other errors are reported via pg_fatal().
65 : : */
66 : : int
335 alvherre@kurilemu.de 67 :CBC 73 : vacuuming_main(ConnParams *cparams, const char *dbname,
68 : : const char *maintenance_db, vacuumingOptions *vacopts,
69 : : SimpleStringList *objects,
70 : : unsigned int tbl_count, int concurrentCons,
71 : : const char *progname)
72 : : {
73 : 73 : setup_cancel_handler(NULL);
74 : :
75 : : /* Avoid opening extra connections. */
76 [ + + - + ]: 73 : if (tbl_count > 0 && (concurrentCons > tbl_count))
335 alvherre@kurilemu.de 77 :UBC 0 : concurrentCons = tbl_count;
78 : :
335 alvherre@kurilemu.de 79 [ + + ]:CBC 73 : if (vacopts->objfilter & OBJFILTER_ALL_DBS)
80 : : {
81 : 27 : cparams->dbname = maintenance_db;
82 : :
83 : 27 : return vacuum_all_databases(cparams, vacopts,
84 : : objects,
85 : : concurrentCons,
86 : : progname);
87 : : }
88 : : else
89 : : {
90 [ - + ]: 46 : if (dbname == NULL)
91 : : {
335 alvherre@kurilemu.de 92 [ # # ]:UBC 0 : if (getenv("PGDATABASE"))
93 : 0 : dbname = getenv("PGDATABASE");
94 [ # # ]: 0 : else if (getenv("PGUSER"))
95 : 0 : dbname = getenv("PGUSER");
96 : : else
97 : 0 : dbname = get_user_name_or_exit(progname);
98 : : }
99 : :
335 alvherre@kurilemu.de 100 :CBC 46 : cparams->dbname = dbname;
101 : :
102 [ + + ]: 46 : if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
103 : : {
14 fujii@postgresql.org 104 : 6 : RetrievedObjects *found_objs = NULL;
105 : :
335 alvherre@kurilemu.de 106 [ + + ]: 24 : for (int stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
107 : : {
108 : : int ret;
109 : :
110 : 18 : ret = vacuum_one_database(cparams, vacopts,
111 : : stage,
112 : : objects,
113 [ + + ]: 18 : vacopts->missing_stats_only ? &found_objs : NULL,
114 : : concurrentCons,
115 : : progname);
116 [ - + ]: 18 : if (ret != 0)
117 : : {
309 tgl@sss.pgh.pa.us 118 :UBC 0 : free_retrieved_objects(found_objs);
335 alvherre@kurilemu.de 119 : 0 : return ret;
120 : : }
121 : : }
122 : :
309 tgl@sss.pgh.pa.us 123 :CBC 6 : free_retrieved_objects(found_objs);
335 alvherre@kurilemu.de 124 : 6 : return EXIT_SUCCESS;
125 : : }
126 : : else
127 : 40 : return vacuum_one_database(cparams, vacopts,
128 : : ANALYZE_NO_STAGE,
129 : : objects, NULL,
130 : : concurrentCons,
131 : : progname);
132 : : }
133 : : }
134 : :
135 : : /*
136 : : * vacuum_one_database
137 : : *
138 : : * Process tables in the given database.
139 : : *
140 : : * There are two ways to specify the list of objects to process:
141 : : *
142 : : * 1) The "found_objs" parameter is a double pointer to a list of fully
143 : : * qualified objects and their command generation metadata, as returned by
144 : : * a previous call to vacuum_one_database().
145 : : *
146 : : * a) If both "found_objs" (the double pointer) and "*found_objs" (the
147 : : * once-dereferenced double pointer) are not NULL, this list takes
148 : : * priority, and anything specified in "objects" is ignored.
149 : : *
150 : : * b) If "found_objs" (the double pointer) is not NULL but "*found_objs"
151 : : * (the once-dereferenced double pointer) _is_ NULL, the "objects"
152 : : * parameter takes priority, and the results of the catalog query
153 : : * described in (2) are stored in "found_objs".
154 : : *
155 : : * c) If "found_objs" (the double pointer) is NULL, the "objects"
156 : : * parameter again takes priority, and the results of the catalog query
157 : : * are not saved.
158 : : *
159 : : * 2) The "objects" parameter is a user-specified list of objects to process.
160 : : * When (1b) or (1c) applies, this function performs a catalog query to
161 : : * retrieve a fully qualified list of objects to process, as described
162 : : * below.
163 : : *
164 : : * a) If "objects" is not NULL, the catalog query gathers only the objects
165 : : * listed in "objects".
166 : : *
167 : : * b) If "objects" is NULL, all tables in the database are gathered.
168 : : *
169 : : * Note that this function is only concerned with running exactly one stage
170 : : * when in analyze-in-stages mode; caller must iterate on us if necessary.
171 : : *
172 : : * If concurrentCons is > 1, multiple connections are used to vacuum tables
173 : : * in parallel.
174 : : */
175 : : static int
176 : 120 : vacuum_one_database(ConnParams *cparams,
177 : : vacuumingOptions *vacopts,
178 : : int stage,
179 : : SimpleStringList *objects,
180 : : RetrievedObjects **found_objs,
181 : : int concurrentCons,
182 : : const char *progname)
183 : : {
184 : : PQExpBufferData sql;
185 : : PGconn *conn;
186 : : ParallelSlotArray *sa;
187 : 120 : int ntups = 0;
188 : : const char *initcmd;
14 fujii@postgresql.org 189 : 120 : RetrievedObjects *retobjs = NULL;
309 tgl@sss.pgh.pa.us 190 : 120 : bool free_retobjs = false;
335 alvherre@kurilemu.de 191 : 120 : int ret = EXIT_SUCCESS;
192 : 120 : const char *stage_commands[] = {
193 : : "SET default_statistics_target=1; SET vacuum_cost_delay=0;",
194 : : "SET default_statistics_target=10; RESET vacuum_cost_delay;",
195 : : "RESET default_statistics_target;"
196 : : };
197 : 120 : const char *stage_messages[] = {
198 : : gettext_noop("Generating minimal optimizer statistics (1 target)"),
199 : : gettext_noop("Generating medium optimizer statistics (10 targets)"),
200 : : gettext_noop("Generating default (full) optimizer statistics")
201 : : };
202 : :
203 [ + + + - : 120 : Assert(stage == ANALYZE_NO_STAGE ||
+ - ]
204 : : (stage >= 0 && stage < ANALYZE_NUM_STAGES));
205 : :
261 nathan@postgresql.or 206 : 120 : conn = connectDatabase(cparams, progname, vacopts->echo, false, true);
207 : :
335 alvherre@kurilemu.de 208 [ + + - + ]: 119 : if (vacopts->disable_page_skipping && PQserverVersion(conn) < 90600)
209 : : {
335 alvherre@kurilemu.de 210 :UBC 0 : PQfinish(conn);
211 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
212 : : "disable-page-skipping", "9.6");
213 : : }
214 : :
335 alvherre@kurilemu.de 215 [ + + - + ]:CBC 119 : if (vacopts->no_index_cleanup && PQserverVersion(conn) < 120000)
216 : : {
335 alvherre@kurilemu.de 217 :UBC 0 : PQfinish(conn);
218 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
219 : : "no-index-cleanup", "12");
220 : : }
221 : :
335 alvherre@kurilemu.de 222 [ - + - - ]:CBC 119 : if (vacopts->force_index_cleanup && PQserverVersion(conn) < 120000)
223 : : {
335 alvherre@kurilemu.de 224 :UBC 0 : PQfinish(conn);
225 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
226 : : "force-index-cleanup", "12");
227 : : }
228 : :
335 alvherre@kurilemu.de 229 [ + + - + ]:CBC 119 : if (!vacopts->do_truncate && PQserverVersion(conn) < 120000)
230 : : {
335 alvherre@kurilemu.de 231 :UBC 0 : PQfinish(conn);
232 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
233 : : "no-truncate", "12");
234 : : }
235 : :
335 alvherre@kurilemu.de 236 [ + + - + ]:CBC 119 : if (!vacopts->process_main && PQserverVersion(conn) < 160000)
237 : : {
335 alvherre@kurilemu.de 238 :UBC 0 : PQfinish(conn);
239 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
240 : : "no-process-main", "16");
241 : : }
242 : :
335 alvherre@kurilemu.de 243 [ + + - + ]:CBC 119 : if (!vacopts->process_toast && PQserverVersion(conn) < 140000)
244 : : {
335 alvherre@kurilemu.de 245 :UBC 0 : PQfinish(conn);
246 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
247 : : "no-process-toast", "14");
248 : : }
249 : :
335 alvherre@kurilemu.de 250 [ + + - + ]:CBC 119 : if (vacopts->skip_locked && PQserverVersion(conn) < 120000)
251 : : {
335 alvherre@kurilemu.de 252 :UBC 0 : PQfinish(conn);
253 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
254 : : "skip-locked", "12");
255 : : }
256 : :
335 alvherre@kurilemu.de 257 [ + + - + ]:CBC 119 : if (vacopts->min_xid_age != 0 && PQserverVersion(conn) < 90600)
258 : : {
335 alvherre@kurilemu.de 259 :UBC 0 : PQfinish(conn);
260 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
261 : : "--min-xid-age", "9.6");
262 : : }
263 : :
335 alvherre@kurilemu.de 264 [ + + - + ]:CBC 119 : if (vacopts->min_mxid_age != 0 && PQserverVersion(conn) < 90600)
265 : : {
335 alvherre@kurilemu.de 266 :UBC 0 : PQfinish(conn);
267 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
268 : : "--min-mxid-age", "9.6");
269 : : }
270 : :
335 alvherre@kurilemu.de 271 [ + + - + ]:CBC 119 : if (vacopts->parallel_workers >= 0 && PQserverVersion(conn) < 130000)
272 : : {
335 alvherre@kurilemu.de 273 :UBC 0 : PQfinish(conn);
274 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
275 : : "--parallel", "13");
276 : : }
277 : :
335 alvherre@kurilemu.de 278 [ - + - - ]:CBC 119 : if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
279 : : {
335 alvherre@kurilemu.de 280 :UBC 0 : PQfinish(conn);
281 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
282 : : "--buffer-usage-limit", "16");
283 : : }
284 : :
335 alvherre@kurilemu.de 285 [ + + - + ]:CBC 119 : if (vacopts->missing_stats_only && PQserverVersion(conn) < 150000)
286 : : {
335 alvherre@kurilemu.de 287 :UBC 0 : PQfinish(conn);
288 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
289 : : "--missing-stats-only", "15");
290 : : }
291 : :
292 : : /* skip_database_stats is used automatically if server supports it */
335 alvherre@kurilemu.de 293 :CBC 119 : vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
294 : :
261 nathan@postgresql.or 295 [ + - ]: 119 : if (!vacopts->quiet)
296 : : {
335 alvherre@kurilemu.de 297 [ + + ]: 119 : if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
298 : 24 : printf(_("%s: processing database \"%s\": %s\n"),
299 : : progname, PQdb(conn), _(stage_messages[stage]));
300 : : else
301 : 95 : printf(_("%s: vacuuming database \"%s\"\n"),
302 : : progname, PQdb(conn));
303 : 119 : fflush(stdout);
304 : : }
305 : :
306 : : /*
307 : : * If the caller provided the results of a previous catalog query, just
308 : : * use that. Otherwise, run the catalog query ourselves and set the
309 : : * return variable if provided. (If it is, then freeing the results
310 : : * becomes the caller's responsibility.)
311 : : */
312 [ + + + + ]: 119 : if (found_objs && *found_objs)
313 : 8 : retobjs = *found_objs;
314 : : else
315 : : {
261 nathan@postgresql.or 316 : 111 : retobjs = retrieve_objects(conn, vacopts, objects);
335 alvherre@kurilemu.de 317 [ + + ]: 110 : if (found_objs)
318 : 4 : *found_objs = retobjs;
319 : : else
309 tgl@sss.pgh.pa.us 320 : 106 : free_retobjs = true;
321 : : }
322 : :
14 fujii@postgresql.org 323 : 118 : ntups = retobjs->num_objects;
324 : :
335 alvherre@kurilemu.de 325 [ + + ]: 118 : if (ntups == 0)
326 : : {
327 : 11 : PQfinish(conn);
309 tgl@sss.pgh.pa.us 328 [ + + ]: 11 : if (free_retobjs)
329 : 5 : free_retrieved_objects(retobjs);
335 alvherre@kurilemu.de 330 : 11 : return EXIT_SUCCESS;
331 : : }
332 : :
333 : : /*
334 : : * Ensure concurrentCons is sane. If there are more connections than
335 : : * vacuumable relations, we don't need to use them all.
336 : : */
337 [ - + ]: 107 : if (concurrentCons > ntups)
335 alvherre@kurilemu.de 338 :UBC 0 : concurrentCons = ntups;
335 alvherre@kurilemu.de 339 [ - + ]:CBC 107 : if (concurrentCons <= 0)
335 alvherre@kurilemu.de 340 :UBC 0 : concurrentCons = 1;
341 : :
342 : : /*
343 : : * All slots need to be prepared to run the appropriate analyze stage, if
344 : : * caller requested that mode. We have to prepare the initial connection
345 : : * ourselves before setting up the slots.
346 : : */
335 alvherre@kurilemu.de 347 [ + + ]:CBC 107 : if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
348 : : {
349 : 18 : initcmd = stage_commands[stage];
350 : :
261 nathan@postgresql.or 351 [ - + ]: 18 : if (vacopts->dry_run)
261 nathan@postgresql.or 352 :UBC 0 : printf("%s\n", initcmd);
353 : : else
261 nathan@postgresql.or 354 :CBC 18 : executeCommand(conn, initcmd, vacopts->echo);
355 : : }
356 : : else
335 alvherre@kurilemu.de 357 : 89 : initcmd = NULL;
358 : :
359 : : /*
360 : : * Setup the database connections. We reuse the connection we already have
361 : : * for the first slot. If not in parallel mode, the first slot in the
362 : : * array contains the connection.
363 : : */
261 nathan@postgresql.or 364 : 107 : sa = ParallelSlotsSetup(concurrentCons, cparams, progname,
365 : 107 : vacopts->echo, initcmd);
335 alvherre@kurilemu.de 366 : 107 : ParallelSlotsAdoptConn(sa, conn);
367 : :
368 : 107 : initPQExpBuffer(&sql);
369 : :
14 fujii@postgresql.org 370 [ + + ]: 6184 : for (int i = 0; i < ntups; i++)
371 : : {
372 : 6077 : RetrievedObject *object = &retobjs->objects[i];
373 : 6077 : const char *tabname = object->target;
374 : : ParallelSlot *free_slot;
375 : :
335 alvherre@kurilemu.de 376 [ - + ]: 6077 : if (CancelRequested)
377 : : {
335 alvherre@kurilemu.de 378 :UBC 0 : ret = EXIT_FAILURE;
379 : 0 : goto finish;
380 : : }
381 : :
335 alvherre@kurilemu.de 382 :CBC 6077 : free_slot = ParallelSlotsGetIdle(sa, NULL);
383 [ - + ]: 6077 : if (!free_slot)
384 : : {
335 alvherre@kurilemu.de 385 :UBC 0 : ret = EXIT_FAILURE;
386 : 0 : goto finish;
387 : : }
388 : :
335 alvherre@kurilemu.de 389 :CBC 6077 : prepare_vacuum_command(free_slot->connection, &sql,
14 fujii@postgresql.org 390 : 6077 : vacopts, tabname, object->use_only);
391 : :
392 : : /*
393 : : * Execute the vacuum. All errors are handled in processQueryResult
394 : : * through ParallelSlotsGetIdle.
395 : : */
335 alvherre@kurilemu.de 396 : 6077 : ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
261 nathan@postgresql.or 397 : 6077 : run_vacuum_command(free_slot, vacopts, sql.data, tabname);
398 : :
399 : : }
400 : :
335 alvherre@kurilemu.de 401 [ + + ]: 107 : if (!ParallelSlotsWaitCompletion(sa))
402 : : {
403 : 1 : ret = EXIT_FAILURE;
404 : 1 : goto finish;
405 : : }
406 : :
407 : : /* If we used SKIP_DATABASE_STATS, mop up with ONLY_DATABASE_STATS */
408 [ + + - + ]: 106 : if (vacopts->mode == MODE_VACUUM && vacopts->skip_database_stats)
409 : : {
410 : 69 : const char *cmd = "VACUUM (ONLY_DATABASE_STATS);";
411 : 69 : ParallelSlot *free_slot = ParallelSlotsGetIdle(sa, NULL);
412 : :
413 [ - + ]: 69 : if (!free_slot)
414 : : {
335 alvherre@kurilemu.de 415 :UBC 0 : ret = EXIT_FAILURE;
416 : 0 : goto finish;
417 : : }
418 : :
335 alvherre@kurilemu.de 419 :CBC 69 : ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
261 nathan@postgresql.or 420 : 69 : run_vacuum_command(free_slot, vacopts, cmd, NULL);
421 : :
335 alvherre@kurilemu.de 422 [ + - ]: 69 : if (!ParallelSlotsWaitCompletion(sa))
335 alvherre@kurilemu.de 423 :UBC 0 : ret = EXIT_FAILURE; /* error already reported by handler */
424 : : }
425 : :
335 alvherre@kurilemu.de 426 :CBC 106 : finish:
427 : 107 : ParallelSlotsTerminate(sa);
428 : 107 : pg_free(sa);
429 : 107 : termPQExpBuffer(&sql);
309 tgl@sss.pgh.pa.us 430 [ + + ]: 107 : if (free_retobjs)
431 : 101 : free_retrieved_objects(retobjs);
432 : :
335 alvherre@kurilemu.de 433 : 107 : return ret;
434 : : }
435 : :
436 : : /*
437 : : * Vacuum/analyze all connectable databases.
438 : : *
439 : : * In analyze-in-stages mode, we process all databases in one stage before
440 : : * moving on to the next stage. That ensure minimal stats are available
441 : : * quickly everywhere before generating more detailed ones.
442 : : */
443 : : static int
444 : 27 : vacuum_all_databases(ConnParams *cparams,
445 : : vacuumingOptions *vacopts,
446 : : SimpleStringList *objects,
447 : : int concurrentCons,
448 : : const char *progname)
449 : : {
309 tgl@sss.pgh.pa.us 450 : 27 : int ret = EXIT_SUCCESS;
451 : : PGconn *conn;
452 : : PGresult *result;
453 : : int numdbs;
454 : :
261 nathan@postgresql.or 455 : 27 : conn = connectMaintenanceDatabase(cparams, progname, vacopts->echo);
335 alvherre@kurilemu.de 456 : 27 : result = executeQuery(conn,
457 : : "SELECT datname FROM pg_database WHERE datallowconn AND datconnlimit <> -2 ORDER BY 1;",
261 nathan@postgresql.or 458 : 27 : vacopts->echo);
309 tgl@sss.pgh.pa.us 459 : 27 : numdbs = PQntuples(result);
335 alvherre@kurilemu.de 460 : 27 : PQfinish(conn);
461 : :
462 [ + + ]: 27 : if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
463 : : {
14 fujii@postgresql.org 464 : 1 : RetrievedObjects **found_objs = NULL;
465 : :
335 alvherre@kurilemu.de 466 [ - + ]: 1 : if (vacopts->missing_stats_only)
14 fujii@postgresql.org 467 :UBC 0 : found_objs = palloc0(numdbs * sizeof(RetrievedObjects *));
468 : :
469 : : /*
470 : : * When analyzing all databases in stages, we analyze them all in the
471 : : * fastest stage first, so that initial statistics become available
472 : : * for all of them as soon as possible.
473 : : *
474 : : * This means we establish several times as many connections, but
475 : : * that's a secondary consideration.
476 : : */
335 alvherre@kurilemu.de 477 [ + + ]:CBC 4 : for (int stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
478 : : {
309 tgl@sss.pgh.pa.us 479 [ + + ]: 9 : for (int i = 0; i < numdbs; i++)
480 : : {
335 alvherre@kurilemu.de 481 : 6 : cparams->override_dbname = PQgetvalue(result, i, 0);
482 : 6 : ret = vacuum_one_database(cparams, vacopts, stage,
483 : : objects,
484 [ - + ]: 6 : vacopts->missing_stats_only ? &found_objs[i] : NULL,
485 : : concurrentCons,
486 : : progname);
487 [ - + ]: 6 : if (ret != EXIT_SUCCESS)
309 tgl@sss.pgh.pa.us 488 :UBC 0 : break;
489 : : }
309 tgl@sss.pgh.pa.us 490 [ - + ]:CBC 3 : if (ret != EXIT_SUCCESS)
309 tgl@sss.pgh.pa.us 491 :UBC 0 : break;
492 : : }
493 : :
309 tgl@sss.pgh.pa.us 494 [ - + ]:CBC 1 : if (vacopts->missing_stats_only)
495 : : {
309 tgl@sss.pgh.pa.us 496 [ # # ]:UBC 0 : for (int i = 0; i < numdbs; i++)
497 : 0 : free_retrieved_objects(found_objs[i]);
57 peter@eisentraut.org 498 :UNC 0 : pfree(found_objs);
499 : : }
500 : : }
501 : : else
502 : : {
309 tgl@sss.pgh.pa.us 503 [ + + ]:CBC 82 : for (int i = 0; i < numdbs; i++)
504 : : {
335 alvherre@kurilemu.de 505 : 56 : cparams->override_dbname = PQgetvalue(result, i, 0);
506 : 56 : ret = vacuum_one_database(cparams, vacopts,
507 : : ANALYZE_NO_STAGE,
508 : : objects,
509 : : NULL,
510 : : concurrentCons,
511 : : progname);
512 [ - + ]: 56 : if (ret != EXIT_SUCCESS)
309 tgl@sss.pgh.pa.us 513 :UBC 0 : break;
514 : : }
515 : : }
516 : :
335 alvherre@kurilemu.de 517 :CBC 27 : PQclear(result);
518 : :
309 tgl@sss.pgh.pa.us 519 : 27 : return ret;
520 : : }
521 : :
522 : : /*
523 : : * Prepare the list of tables to process by querying the catalogs.
524 : : *
525 : : * Since we execute the constructed query with the default search_path (which
526 : : * could be unsafe), everything in this query MUST be fully qualified.
527 : : *
528 : : * First, build a WITH clause for the catalog query if any tables were
529 : : * specified, with a set of values made of relation names and their optional
530 : : * set of columns. This is used to match any provided column lists with the
531 : : * generated qualified identifiers and to filter for the tables provided via
532 : : * --table. If a listed table does not exist, the catalog query will fail.
533 : : */
534 : : static RetrievedObjects *
335 alvherre@kurilemu.de 535 : 111 : retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
536 : : SimpleStringList *objects)
537 : : {
538 : : PQExpBufferData buf;
539 : : PQExpBufferData catalog_query;
540 : : PGresult *res;
541 : : SimpleStringListCell *cell;
542 : : RetrievedObjects *found_objs;
543 : 111 : bool objects_listed = false;
544 : : int ntups;
545 : :
546 : 111 : initPQExpBuffer(&catalog_query);
547 [ + - + + ]: 145 : for (cell = objects ? objects->head : NULL; cell; cell = cell->next)
548 : : {
549 : 34 : char *just_table = NULL;
550 : 34 : const char *just_columns = NULL;
551 : :
552 [ + + ]: 34 : if (!objects_listed)
553 : : {
554 : 32 : appendPQExpBufferStr(&catalog_query,
555 : : "WITH listed_objects (object_oid, column_list) AS (\n"
556 : : " VALUES (");
557 : 32 : objects_listed = true;
558 : : }
559 : : else
560 : 2 : appendPQExpBufferStr(&catalog_query, ",\n (");
561 : :
562 [ + + ]: 34 : if (vacopts->objfilter & (OBJFILTER_SCHEMA | OBJFILTER_SCHEMA_EXCLUDE))
563 : : {
564 : 11 : appendStringLiteralConn(&catalog_query, cell->val, conn);
565 : 11 : appendPQExpBufferStr(&catalog_query, "::pg_catalog.regnamespace, ");
566 : : }
567 : :
568 [ + + ]: 34 : if (vacopts->objfilter & OBJFILTER_TABLE)
569 : : {
570 : : /*
571 : : * Split relation and column names given by the user, this is used
572 : : * to feed the CTE with values on which are performed pre-run
573 : : * validity checks as well. For now these happen only on the
574 : : * relation name.
575 : : */
576 : 23 : splitTableColumnsSpec(cell->val, PQclientEncoding(conn),
577 : : &just_table, &just_columns);
578 : :
579 : 23 : appendStringLiteralConn(&catalog_query, just_table, conn);
580 : 23 : appendPQExpBufferStr(&catalog_query, "::pg_catalog.regclass, ");
581 : : }
582 : :
583 [ + + + + ]: 34 : if (just_columns && just_columns[0] != '\0')
584 : 5 : appendStringLiteralConn(&catalog_query, just_columns, conn);
585 : : else
586 : 29 : appendPQExpBufferStr(&catalog_query, "NULL");
587 : :
588 : 34 : appendPQExpBufferStr(&catalog_query, "::pg_catalog.text)");
589 : :
590 : 34 : pg_free(just_table);
591 : : }
592 : :
593 : : /* Finish formatting the CTE */
594 [ + + ]: 111 : if (objects_listed)
595 : 32 : appendPQExpBufferStr(&catalog_query, "\n)\n");
596 : :
14 fujii@postgresql.org 597 : 111 : appendPQExpBufferStr(&catalog_query, "SELECT c.relname, ns.nspname, c.relkind");
598 : :
335 alvherre@kurilemu.de 599 [ + + ]: 111 : if (objects_listed)
600 : 32 : appendPQExpBufferStr(&catalog_query, ", listed_objects.column_list");
601 : :
602 : 111 : appendPQExpBufferStr(&catalog_query,
603 : : " FROM pg_catalog.pg_class c\n"
604 : : " JOIN pg_catalog.pg_namespace ns"
605 : : " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
606 : : " CROSS JOIN LATERAL (SELECT c.relkind IN ("
607 : : CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
608 : : CppAsString2(RELKIND_PARTITIONED_INDEX) ")) as p (inherited)\n"
609 : : " LEFT JOIN pg_catalog.pg_class t"
610 : : " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
611 : :
612 : : /*
613 : : * Used to match the tables or schemas listed by the user, completing the
614 : : * JOIN clause.
615 : : */
616 [ + + ]: 111 : if (objects_listed)
617 : : {
618 : 32 : appendPQExpBufferStr(&catalog_query, " LEFT JOIN listed_objects"
619 : : " ON listed_objects.object_oid"
620 : : " OPERATOR(pg_catalog.=) ");
621 : :
622 [ + + ]: 32 : if (vacopts->objfilter & OBJFILTER_TABLE)
623 : 23 : appendPQExpBufferStr(&catalog_query, "c.oid\n");
624 : : else
625 : 9 : appendPQExpBufferStr(&catalog_query, "ns.oid\n");
626 : : }
627 : :
628 : : /*
629 : : * Exclude temporary tables, beginning the WHERE clause.
630 : : */
631 : 111 : appendPQExpBufferStr(&catalog_query,
632 : : " WHERE c.relpersistence OPERATOR(pg_catalog.!=) "
633 : : CppAsString2(RELPERSISTENCE_TEMP) "\n");
634 : :
635 : : /*
636 : : * Used to match the tables or schemas listed by the user, for the WHERE
637 : : * clause.
638 : : */
639 [ + + ]: 111 : if (objects_listed)
640 : : {
641 [ + + ]: 32 : if (vacopts->objfilter & OBJFILTER_SCHEMA_EXCLUDE)
642 : 4 : appendPQExpBufferStr(&catalog_query,
643 : : " AND listed_objects.object_oid IS NULL\n");
644 : : else
645 : 28 : appendPQExpBufferStr(&catalog_query,
646 : : " AND listed_objects.object_oid IS NOT NULL\n");
647 : : }
648 : :
649 : : /*
650 : : * If no tables were listed, filter for the relevant relation types. If
651 : : * tables were given via --table, don't bother filtering by relation type.
652 : : * Instead, let the server decide whether a given relation can be
653 : : * processed in which case the user will know about it.
654 : : */
655 [ + + ]: 111 : if ((vacopts->objfilter & OBJFILTER_TABLE) == 0)
656 : : {
657 : : /*
658 : : * vacuumdb should generally follow the behavior of the underlying
659 : : * VACUUM and ANALYZE commands. In MODE_ANALYZE or
660 : : * MODE_ANALYZE_IN_STAGES modes, process regular tables, materialized
661 : : * views, and partitioned tables, just like ANALYZE (with no specific
662 : : * target tables) does. Otherwise, process only regular tables and
663 : : * materialized views, since VACUUM skips partitioned tables when no
664 : : * target tables are specified.
665 : : */
85 fujii@postgresql.org 666 [ + + ]: 88 : if (vacopts->mode == MODE_ANALYZE ||
667 [ + + ]: 78 : vacopts->mode == MODE_ANALYZE_IN_STAGES)
335 alvherre@kurilemu.de 668 : 22 : appendPQExpBufferStr(&catalog_query,
669 : : " AND c.relkind OPERATOR(pg_catalog.=) ANY (array["
670 : : CppAsString2(RELKIND_RELATION) ", "
671 : : CppAsString2(RELKIND_MATVIEW) ", "
672 : : CppAsString2(RELKIND_PARTITIONED_TABLE) "])\n");
673 : : else
674 : 66 : appendPQExpBufferStr(&catalog_query,
675 : : " AND c.relkind OPERATOR(pg_catalog.=) ANY (array["
676 : : CppAsString2(RELKIND_RELATION) ", "
677 : : CppAsString2(RELKIND_MATVIEW) "])\n");
678 : : }
679 : :
680 : : /*
681 : : * For --min-xid-age and --min-mxid-age, the age of the relation is the
682 : : * greatest of the ages of the main relation and its associated TOAST
683 : : * table. The commands generated by vacuumdb will also process the TOAST
684 : : * table for the relation if necessary, so it does not need to be
685 : : * considered separately.
686 : : */
687 [ + + ]: 111 : if (vacopts->min_xid_age != 0)
688 : : {
689 : 1 : appendPQExpBuffer(&catalog_query,
690 : : " AND GREATEST(pg_catalog.age(c.relfrozenxid),"
691 : : " pg_catalog.age(t.relfrozenxid)) "
692 : : " OPERATOR(pg_catalog.>=) '%d'::pg_catalog.int4\n"
693 : : " AND c.relfrozenxid OPERATOR(pg_catalog.!=)"
694 : : " '0'::pg_catalog.xid\n",
695 : : vacopts->min_xid_age);
696 : : }
697 : :
698 [ + + ]: 111 : if (vacopts->min_mxid_age != 0)
699 : : {
700 : 1 : appendPQExpBuffer(&catalog_query,
701 : : " AND GREATEST(pg_catalog.mxid_age(c.relminmxid),"
702 : : " pg_catalog.mxid_age(t.relminmxid)) OPERATOR(pg_catalog.>=)"
703 : : " '%d'::pg_catalog.int4\n"
704 : : " AND c.relminmxid OPERATOR(pg_catalog.!=)"
705 : : " '0'::pg_catalog.xid\n",
706 : : vacopts->min_mxid_age);
707 : : }
708 : :
709 [ + + ]: 111 : if (vacopts->missing_stats_only)
710 : : {
711 : 11 : appendPQExpBufferStr(&catalog_query, " AND (\n");
712 : :
713 : : /* regular stats */
714 : 11 : appendPQExpBufferStr(&catalog_query,
715 : : " EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
716 : : " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
717 : : " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
718 : : " AND NOT a.attisdropped\n"
719 : : " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
720 : : " AND a.attgenerated OPERATOR(pg_catalog.<>) "
721 : : CppAsString2(ATTRIBUTE_GENERATED_VIRTUAL) "\n"
722 : : " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
723 : : " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
724 : : " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
725 : : " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n");
726 : :
727 : : /* extended stats */
728 : 11 : appendPQExpBufferStr(&catalog_query,
729 : : " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
730 : : " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
731 : : " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
732 : : " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
733 : : " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
734 : : " AND d.stxdinherit OPERATOR(pg_catalog.=) p.inherited))\n");
735 : :
736 : : /* expression indexes */
737 : 11 : appendPQExpBufferStr(&catalog_query,
738 : : " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
739 : : " JOIN pg_catalog.pg_index i"
740 : : " ON i.indexrelid OPERATOR(pg_catalog.=) a.attrelid\n"
741 : : " WHERE i.indrelid OPERATOR(pg_catalog.=) c.oid\n"
742 : : " AND i.indkey[a.attnum OPERATOR(pg_catalog.-) 1::pg_catalog.int2]"
743 : : " OPERATOR(pg_catalog.=) 0::pg_catalog.int2\n"
744 : : " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
745 : : " AND NOT a.attisdropped\n"
746 : : " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
747 : : " AND NOT p.inherited\n"
748 : : " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
749 : : " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
750 : : " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum))\n");
751 : :
752 : : /* inheritance and regular stats */
753 : 11 : appendPQExpBufferStr(&catalog_query,
754 : : " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
755 : : " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
756 : : " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
757 : : " AND NOT a.attisdropped\n"
758 : : " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
759 : : " AND a.attgenerated OPERATOR(pg_catalog.<>) "
760 : : CppAsString2(ATTRIBUTE_GENERATED_VIRTUAL) "\n"
761 : : " AND c.relhassubclass\n"
762 : : " AND NOT p.inherited\n"
763 : : " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
764 : : " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
765 : : " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
766 : : " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
767 : : " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
768 : : " AND s.stainherit))\n");
769 : :
770 : : /* inheritance and extended stats */
771 : 11 : appendPQExpBufferStr(&catalog_query,
772 : : " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
773 : : " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
774 : : " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
775 : : " AND c.relhassubclass\n"
776 : : " AND NOT p.inherited\n"
777 : : " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
778 : : " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
779 : : " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
780 : : " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
781 : : " AND d.stxdinherit))\n");
782 : :
783 : 11 : appendPQExpBufferStr(&catalog_query, " )\n");
784 : : }
785 : :
786 : : /*
787 : : * Execute the catalog query. We use the default search_path for this
788 : : * query for consistency with table lookups done elsewhere by the user.
789 : : */
790 : 111 : appendPQExpBufferStr(&catalog_query, " ORDER BY c.relpages DESC;");
261 nathan@postgresql.or 791 : 111 : executeCommand(conn, "RESET search_path;", vacopts->echo);
792 : 111 : res = executeQuery(conn, catalog_query.data, vacopts->echo);
335 alvherre@kurilemu.de 793 : 110 : termPQExpBuffer(&catalog_query);
261 nathan@postgresql.or 794 : 110 : PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, vacopts->echo));
795 : :
796 : : /*
797 : : * Build qualified identifiers for each table, including the column list
798 : : * if given.
799 : : */
14 fujii@postgresql.org 800 : 110 : ntups = PQntuples(res);
801 : 110 : found_objs = palloc0(add_size(offsetof(RetrievedObjects, objects),
802 : : mul_size(sizeof(RetrievedObject), ntups)));
803 : 110 : found_objs->num_objects = ntups;
804 : :
335 alvherre@kurilemu.de 805 : 110 : initPQExpBuffer(&buf);
14 fujii@postgresql.org 806 [ + + ]: 6183 : for (int i = 0; i < found_objs->num_objects; i++)
807 : : {
808 : : RetrievedObject *object;
809 : : bool use_only;
810 : :
811 : : /*
812 : : * For automatically enumerated partitioned tables, use ONLY to
813 : : * collect inherited statistics without recursively updating
814 : : * per-partition statistics. Partitions selected by the current
815 : : * filters are processed as separate targets.
816 : : */
817 : 17458 : use_only = ((vacopts->mode == MODE_ANALYZE ||
818 [ + + ]: 5312 : vacopts->mode == MODE_ANALYZE_IN_STAGES) &&
819 [ + + + + ]: 13046 : (vacopts->objfilter & OBJFILTER_TABLE) == 0 &&
820 [ + + ]: 1661 : PQgetvalue(res, i, 2)[0] == RELKIND_PARTITIONED_TABLE);
821 : :
335 alvherre@kurilemu.de 822 : 12146 : appendPQExpBufferStr(&buf,
823 : 6073 : fmtQualifiedIdEnc(PQgetvalue(res, i, 1),
824 : 6073 : PQgetvalue(res, i, 0),
825 : : PQclientEncoding(conn)));
826 : :
14 fujii@postgresql.org 827 [ + + + + ]: 6073 : if (objects_listed && !PQgetisnull(res, i, 3))
828 : 5 : appendPQExpBufferStr(&buf, PQgetvalue(res, i, 3));
829 : :
830 : 6073 : object = &found_objs->objects[i];
831 : 6073 : object->target = pg_strdup(buf.data);
832 : 6073 : object->use_only = use_only;
335 alvherre@kurilemu.de 833 : 6073 : resetPQExpBuffer(&buf);
834 : : }
835 : 110 : termPQExpBuffer(&buf);
836 : 110 : PQclear(res);
837 : :
838 : 110 : return found_objs;
839 : : }
840 : :
841 : : /*
842 : : * Free the results of retrieve_objects().
843 : : *
844 : : * For caller convenience, we allow the argument to be NULL,
845 : : * although retrieve_objects() will never return that.
846 : : */
847 : : static void
14 fujii@postgresql.org 848 : 112 : free_retrieved_objects(RetrievedObjects *objs)
849 : : {
850 [ + + ]: 112 : if (objs)
851 : : {
852 [ + + ]: 6183 : for (int i = 0; i < objs->num_objects; i++)
853 : 6073 : pg_free(objs->objects[i].target);
854 : :
855 : 110 : pg_free(objs);
856 : : }
309 tgl@sss.pgh.pa.us 857 : 112 : }
858 : :
859 : : /*
860 : : * Construct a vacuum/analyze command to run based on the given
861 : : * options, in the given string buffer, which may contain previous garbage.
862 : : *
863 : : * The table reference used must be already properly quoted. It is usually a
864 : : * table name, but may also include a column list. The command generated
865 : : * depends on the server version involved and it is semicolon-terminated. If
866 : : * use_only is set, the command targets the table with ANALYZE ONLY.
867 : : */
868 : : static void
335 alvherre@kurilemu.de 869 : 6077 : prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
870 : : vacuumingOptions *vacopts, const char *table,
871 : : bool use_only)
872 : : {
873 : 6077 : int serverVersion = PQserverVersion(conn);
874 : 6077 : const char *paren = " (";
875 : 6077 : const char *comma = ", ";
876 : 6077 : const char *sep = paren;
877 : :
14 fujii@postgresql.org 878 [ + + + + : 6077 : Assert(!use_only ||
- + ]
879 : : vacopts->mode == MODE_ANALYZE ||
880 : : vacopts->mode == MODE_ANALYZE_IN_STAGES);
881 : :
335 alvherre@kurilemu.de 882 : 6077 : resetPQExpBuffer(sql);
883 : :
884 [ + + ]: 6077 : if (vacopts->mode == MODE_ANALYZE ||
885 [ + + ]: 5316 : vacopts->mode == MODE_ANALYZE_IN_STAGES)
886 : : {
887 : 1676 : appendPQExpBufferStr(sql, "ANALYZE");
888 : :
889 : : /* parenthesized grammar of ANALYZE is supported since v11 */
890 [ + - ]: 1676 : if (serverVersion >= 110000)
891 : : {
892 [ + + ]: 1676 : if (vacopts->skip_locked)
893 : : {
894 : : /* SKIP_LOCKED is supported since v12 */
895 [ - + ]: 73 : Assert(serverVersion >= 120000);
896 : 73 : appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
897 : 73 : sep = comma;
898 : : }
899 [ - + ]: 1676 : if (vacopts->verbose)
900 : : {
335 alvherre@kurilemu.de 901 :UBC 0 : appendPQExpBuffer(sql, "%sVERBOSE", sep);
902 : 0 : sep = comma;
903 : : }
335 alvherre@kurilemu.de 904 [ - + ]:CBC 1676 : if (vacopts->buffer_usage_limit)
905 : : {
335 alvherre@kurilemu.de 906 [ # # ]:UBC 0 : Assert(serverVersion >= 160000);
907 : 0 : appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
908 : : vacopts->buffer_usage_limit);
909 : 0 : sep = comma;
910 : : }
335 alvherre@kurilemu.de 911 [ + + ]:CBC 1676 : if (sep != paren)
912 : 73 : appendPQExpBufferChar(sql, ')');
913 : : }
914 : : else
915 : : {
335 alvherre@kurilemu.de 916 [ # # ]:UBC 0 : if (vacopts->verbose)
917 : 0 : appendPQExpBufferStr(sql, " VERBOSE");
918 : : }
919 : :
920 : : /* ANALYZE ONLY is supported since v18 */
14 fujii@postgresql.org 921 [ + + + - ]:CBC 1676 : if (use_only && serverVersion >= 180000)
922 : 10 : appendPQExpBufferStr(sql, " ONLY");
923 : : }
924 : : else
925 : : {
335 alvherre@kurilemu.de 926 : 4401 : appendPQExpBufferStr(sql, "VACUUM");
927 : :
928 : : /* parenthesized grammar of VACUUM is supported since v9.0 */
929 [ + - ]: 4401 : if (serverVersion >= 90000)
930 : : {
931 [ + + ]: 4401 : if (vacopts->disable_page_skipping)
932 : : {
933 : : /* DISABLE_PAGE_SKIPPING is supported since v9.6 */
934 [ - + ]: 73 : Assert(serverVersion >= 90600);
935 : 73 : appendPQExpBuffer(sql, "%sDISABLE_PAGE_SKIPPING", sep);
936 : 73 : sep = comma;
937 : : }
938 [ + + ]: 4401 : if (vacopts->no_index_cleanup)
939 : : {
940 : : /* "INDEX_CLEANUP FALSE" has been supported since v12 */
941 [ - + ]: 73 : Assert(serverVersion >= 120000);
942 [ - + ]: 73 : Assert(!vacopts->force_index_cleanup);
943 : 73 : appendPQExpBuffer(sql, "%sINDEX_CLEANUP FALSE", sep);
944 : 73 : sep = comma;
945 : : }
946 [ - + ]: 4401 : if (vacopts->force_index_cleanup)
947 : : {
948 : : /* "INDEX_CLEANUP TRUE" has been supported since v12 */
335 alvherre@kurilemu.de 949 [ # # ]:UBC 0 : Assert(serverVersion >= 120000);
950 [ # # ]: 0 : Assert(!vacopts->no_index_cleanup);
951 : 0 : appendPQExpBuffer(sql, "%sINDEX_CLEANUP TRUE", sep);
952 : 0 : sep = comma;
953 : : }
335 alvherre@kurilemu.de 954 [ + + ]:CBC 4401 : if (!vacopts->do_truncate)
955 : : {
956 : : /* TRUNCATE is supported since v12 */
957 [ - + ]: 73 : Assert(serverVersion >= 120000);
958 : 73 : appendPQExpBuffer(sql, "%sTRUNCATE FALSE", sep);
959 : 73 : sep = comma;
960 : : }
961 [ + + ]: 4401 : if (!vacopts->process_main)
962 : : {
963 : : /* PROCESS_MAIN is supported since v16 */
964 [ - + ]: 73 : Assert(serverVersion >= 160000);
965 : 73 : appendPQExpBuffer(sql, "%sPROCESS_MAIN FALSE", sep);
966 : 73 : sep = comma;
967 : : }
968 [ + + ]: 4401 : if (!vacopts->process_toast)
969 : : {
970 : : /* PROCESS_TOAST is supported since v14 */
971 [ - + ]: 73 : Assert(serverVersion >= 140000);
972 : 73 : appendPQExpBuffer(sql, "%sPROCESS_TOAST FALSE", sep);
973 : 73 : sep = comma;
974 : : }
975 [ + - ]: 4401 : if (vacopts->skip_database_stats)
976 : : {
977 : : /* SKIP_DATABASE_STATS is supported since v16 */
978 [ - + ]: 4401 : Assert(serverVersion >= 160000);
979 : 4401 : appendPQExpBuffer(sql, "%sSKIP_DATABASE_STATS", sep);
980 : 4401 : sep = comma;
981 : : }
982 [ + + ]: 4401 : if (vacopts->skip_locked)
983 : : {
984 : : /* SKIP_LOCKED is supported since v12 */
985 [ - + ]: 73 : Assert(serverVersion >= 120000);
986 : 73 : appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
987 : 73 : sep = comma;
988 : : }
989 [ + + ]: 4401 : if (vacopts->full)
990 : : {
991 : 73 : appendPQExpBuffer(sql, "%sFULL", sep);
992 : 73 : sep = comma;
993 : : }
994 [ + + ]: 4401 : if (vacopts->freeze)
995 : : {
996 : 1533 : appendPQExpBuffer(sql, "%sFREEZE", sep);
997 : 1533 : sep = comma;
998 : : }
999 [ - + ]: 4401 : if (vacopts->verbose)
1000 : : {
335 alvherre@kurilemu.de 1001 :UBC 0 : appendPQExpBuffer(sql, "%sVERBOSE", sep);
1002 : 0 : sep = comma;
1003 : : }
335 alvherre@kurilemu.de 1004 [ + + ]:CBC 4401 : if (vacopts->and_analyze)
1005 : : {
1006 : 1536 : appendPQExpBuffer(sql, "%sANALYZE", sep);
1007 : 1536 : sep = comma;
1008 : : }
1009 [ + + ]: 4401 : if (vacopts->parallel_workers >= 0)
1010 : : {
1011 : : /* PARALLEL is supported since v13 */
1012 [ - + ]: 146 : Assert(serverVersion >= 130000);
1013 : 146 : appendPQExpBuffer(sql, "%sPARALLEL %d", sep,
1014 : : vacopts->parallel_workers);
1015 : 146 : sep = comma;
1016 : : }
1017 [ - + ]: 4401 : if (vacopts->buffer_usage_limit)
1018 : : {
335 alvherre@kurilemu.de 1019 [ # # ]:UBC 0 : Assert(serverVersion >= 160000);
1020 : 0 : appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
1021 : : vacopts->buffer_usage_limit);
1022 : 0 : sep = comma;
1023 : : }
335 alvherre@kurilemu.de 1024 [ + - ]:CBC 4401 : if (sep != paren)
1025 : 4401 : appendPQExpBufferChar(sql, ')');
1026 : : }
1027 : : else
1028 : : {
335 alvherre@kurilemu.de 1029 [ # # ]:UBC 0 : if (vacopts->full)
1030 : 0 : appendPQExpBufferStr(sql, " FULL");
1031 [ # # ]: 0 : if (vacopts->freeze)
1032 : 0 : appendPQExpBufferStr(sql, " FREEZE");
1033 [ # # ]: 0 : if (vacopts->verbose)
1034 : 0 : appendPQExpBufferStr(sql, " VERBOSE");
1035 [ # # ]: 0 : if (vacopts->and_analyze)
1036 : 0 : appendPQExpBufferStr(sql, " ANALYZE");
1037 : : }
1038 : : }
1039 : :
335 alvherre@kurilemu.de 1040 :CBC 6077 : appendPQExpBuffer(sql, " %s;", table);
1041 : 6077 : }
1042 : :
1043 : : /*
1044 : : * Send a vacuum/analyze command to the server, returning after sending the
1045 : : * command. If dry_run is true, the command is printed but not sent to the
1046 : : * server.
1047 : : *
1048 : : * Any errors during command execution are reported to stderr.
1049 : : */
1050 : : static void
261 nathan@postgresql.or 1051 : 6146 : run_vacuum_command(ParallelSlot *free_slot, vacuumingOptions *vacopts,
1052 : : const char *sql, const char *table)
1053 : : {
1054 : 6146 : bool status = true;
1055 : 6146 : PGconn *conn = free_slot->connection;
1056 : :
1057 [ + + + + ]: 6146 : if (vacopts->echo || vacopts->dry_run)
335 alvherre@kurilemu.de 1058 : 441 : printf("%s\n", sql);
1059 : :
261 nathan@postgresql.or 1060 [ + + ]: 6146 : if (vacopts->dry_run)
1061 : 3 : ParallelSlotSetIdle(free_slot);
1062 : : else
1063 : 6143 : status = PQsendQuery(conn, sql) == 1;
1064 : :
335 alvherre@kurilemu.de 1065 [ - + ]: 6146 : if (!status)
1066 : : {
335 alvherre@kurilemu.de 1067 [ # # ]:UBC 0 : if (table)
1068 : : {
1069 : 0 : pg_log_error("vacuuming of table \"%s\" in database \"%s\" failed: %s",
1070 : : table, PQdb(conn), PQerrorMessage(conn));
1071 : : }
1072 : : else
1073 : : {
1074 : 0 : pg_log_error("vacuuming of database \"%s\" failed: %s",
1075 : : PQdb(conn), PQerrorMessage(conn));
1076 : : }
1077 : : }
335 alvherre@kurilemu.de 1078 :CBC 6146 : }
1079 : :
1080 : : /*
1081 : : * Returns a newly malloc'd version of 'src' with escaped single quotes and
1082 : : * backslashes.
1083 : : */
1084 : : char *
335 alvherre@kurilemu.de 1085 :UBC 0 : escape_quotes(const char *src)
1086 : : {
1087 : 0 : char *result = escape_single_quotes_ascii(src);
1088 : :
1089 [ # # ]: 0 : if (!result)
1090 : 0 : pg_fatal("out of memory");
1091 : 0 : return result;
1092 : : }
|