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