Branch data 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
67 : 74 : 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 : 74 : setup_cancel_handler(NULL);
74 : :
75 : : /* Avoid opening extra connections. */
76 [ + + - + ]: 74 : if (tbl_count > 0 && (concurrentCons > tbl_count))
77 : 0 : concurrentCons = tbl_count;
78 : :
79 [ + + ]: 74 : if (vacopts->objfilter & OBJFILTER_ALL_DBS)
80 : : {
81 : 28 : cparams->dbname = maintenance_db;
82 : :
83 : 28 : return vacuum_all_databases(cparams, vacopts,
84 : : objects,
85 : : concurrentCons,
86 : : progname);
87 : : }
88 : : else
89 : : {
90 [ - + ]: 46 : if (dbname == NULL)
91 : : {
92 [ # # ]: 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 : :
100 : 46 : cparams->dbname = dbname;
101 : :
102 [ + + ]: 46 : if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
103 : : {
104 : 6 : RetrievedObjects *found_objs = NULL;
105 : :
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 : : {
118 : 0 : free_retrieved_objects(found_objs);
119 : 0 : return ret;
120 : : }
121 : : }
122 : :
123 : 6 : free_retrieved_objects(found_objs);
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 : 123 : 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 : 123 : int ntups = 0;
188 : : const char *initcmd;
189 : 123 : RetrievedObjects *retobjs = NULL;
190 : 123 : bool free_retobjs = false;
191 : 123 : int ret = EXIT_SUCCESS;
192 : 123 : 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 : 123 : 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 : : Assert(stage == ANALYZE_NO_STAGE ||
204 : : (stage >= 0 && stage < ANALYZE_NUM_STAGES));
205 : :
206 : 123 : conn = connectDatabase(cparams, progname, vacopts->echo, false, true);
207 : :
208 [ + + - + ]: 122 : if (vacopts->disable_page_skipping && PQserverVersion(conn) < 90600)
209 : : {
210 : 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 : :
215 [ + + - + ]: 122 : if (vacopts->no_index_cleanup && PQserverVersion(conn) < 120000)
216 : : {
217 : 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 : :
222 [ - + - - ]: 122 : if (vacopts->force_index_cleanup && PQserverVersion(conn) < 120000)
223 : : {
224 : 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 : :
229 [ + + - + ]: 122 : if (!vacopts->do_truncate && PQserverVersion(conn) < 120000)
230 : : {
231 : 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 : :
236 [ + + - + ]: 122 : if (!vacopts->process_main && PQserverVersion(conn) < 160000)
237 : : {
238 : 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 : :
243 [ + + - + ]: 122 : if (!vacopts->process_toast && PQserverVersion(conn) < 140000)
244 : : {
245 : 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 : :
250 [ + + - + ]: 122 : if (vacopts->skip_locked && PQserverVersion(conn) < 120000)
251 : : {
252 : 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 : :
257 [ + + - + ]: 122 : if (vacopts->min_xid_age != 0 && PQserverVersion(conn) < 90600)
258 : : {
259 : 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 : :
264 [ + + - + ]: 122 : if (vacopts->min_mxid_age != 0 && PQserverVersion(conn) < 90600)
265 : : {
266 : 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 : :
271 [ + + - + ]: 122 : if (vacopts->parallel_workers >= 0 && PQserverVersion(conn) < 130000)
272 : : {
273 : 0 : PQfinish(conn);
274 : 0 : pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
275 : : "--parallel", "13");
276 : : }
277 : :
278 [ - + - - ]: 122 : if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
279 : : {
280 : 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 : :
285 [ + + - + ]: 122 : if (vacopts->missing_stats_only && PQserverVersion(conn) < 150000)
286 : : {
287 : 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 */
293 : 122 : vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
294 : :
295 [ + - ]: 122 : if (!vacopts->quiet)
296 : : {
297 [ + + ]: 122 : 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 : 98 : printf(_("%s: vacuuming database \"%s\"\n"),
302 : : progname, PQdb(conn));
303 : 122 : 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 [ + + + + ]: 122 : if (found_objs && *found_objs)
313 : 8 : retobjs = *found_objs;
314 : : else
315 : : {
316 : 114 : retobjs = retrieve_objects(conn, vacopts, objects);
317 [ + + ]: 113 : if (found_objs)
318 : 4 : *found_objs = retobjs;
319 : : else
320 : 109 : free_retobjs = true;
321 : : }
322 : :
323 : 121 : ntups = retobjs->num_objects;
324 : :
325 [ + + ]: 121 : if (ntups == 0)
326 : : {
327 : 11 : PQfinish(conn);
328 [ + + ]: 11 : if (free_retobjs)
329 : 5 : free_retrieved_objects(retobjs);
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 [ - + ]: 110 : if (concurrentCons > ntups)
338 : 0 : concurrentCons = ntups;
339 [ - + ]: 110 : if (concurrentCons <= 0)
340 : 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 : : */
347 [ + + ]: 110 : if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
348 : : {
349 : 18 : initcmd = stage_commands[stage];
350 : :
351 [ - + ]: 18 : if (vacopts->dry_run)
352 : 0 : printf("%s\n", initcmd);
353 : : else
354 : 18 : executeCommand(conn, initcmd, vacopts->echo);
355 : : }
356 : : else
357 : 92 : 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 : : */
364 : 110 : sa = ParallelSlotsSetup(concurrentCons, cparams, progname,
365 : 110 : vacopts->echo, initcmd);
366 : 110 : ParallelSlotsAdoptConn(sa, conn);
367 : :
368 : 110 : initPQExpBuffer(&sql);
369 : :
370 [ + + ]: 6406 : for (int i = 0; i < ntups; i++)
371 : : {
372 : 6296 : RetrievedObject *object = &retobjs->objects[i];
373 : 6296 : const char *tabname = object->target;
374 : : ParallelSlot *free_slot;
375 : :
376 [ - + ]: 6296 : if (CancelRequested)
377 : : {
378 : 0 : ret = EXIT_FAILURE;
379 : 0 : goto finish;
380 : : }
381 : :
382 : 6296 : free_slot = ParallelSlotsGetIdle(sa, NULL);
383 [ - + ]: 6296 : if (!free_slot)
384 : : {
385 : 0 : ret = EXIT_FAILURE;
386 : 0 : goto finish;
387 : : }
388 : :
389 : 6296 : prepare_vacuum_command(free_slot->connection, &sql,
390 : 6296 : vacopts, tabname, object->use_only);
391 : :
392 : : /*
393 : : * Execute the vacuum. All errors are handled in processQueryResult
394 : : * through ParallelSlotsGetIdle.
395 : : */
396 : 6296 : ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
397 : 6296 : run_vacuum_command(free_slot, vacopts, sql.data, tabname);
398 : :
399 : : }
400 : :
401 [ + + ]: 110 : 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 [ + + - + ]: 109 : if (vacopts->mode == MODE_VACUUM && vacopts->skip_database_stats)
409 : : {
410 : 72 : const char *cmd = "VACUUM (ONLY_DATABASE_STATS);";
411 : 72 : ParallelSlot *free_slot = ParallelSlotsGetIdle(sa, NULL);
412 : :
413 [ - + ]: 72 : if (!free_slot)
414 : : {
415 : 0 : ret = EXIT_FAILURE;
416 : 0 : goto finish;
417 : : }
418 : :
419 : 72 : ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
420 : 72 : run_vacuum_command(free_slot, vacopts, cmd, NULL);
421 : :
422 [ + - ]: 72 : if (!ParallelSlotsWaitCompletion(sa))
423 : 0 : ret = EXIT_FAILURE; /* error already reported by handler */
424 : : }
425 : :
426 : 109 : finish:
427 : 110 : ParallelSlotsTerminate(sa);
428 : 110 : pg_free(sa);
429 : 110 : termPQExpBuffer(&sql);
430 [ + + ]: 110 : if (free_retobjs)
431 : 104 : free_retrieved_objects(retobjs);
432 : :
433 : 110 : 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 : 28 : vacuum_all_databases(ConnParams *cparams,
445 : : vacuumingOptions *vacopts,
446 : : SimpleStringList *objects,
447 : : int concurrentCons,
448 : : const char *progname)
449 : : {
450 : 28 : int ret = EXIT_SUCCESS;
451 : : PGconn *conn;
452 : : PGresult *result;
453 : : int numdbs;
454 : :
455 : 28 : conn = connectMaintenanceDatabase(cparams, progname, vacopts->echo);
456 : 28 : result = executeQuery(conn,
457 : : "SELECT datname FROM pg_database WHERE datallowconn AND datconnlimit <> -2 ORDER BY 1;",
458 : 28 : vacopts->echo);
459 : 28 : numdbs = PQntuples(result);
460 : 28 : PQfinish(conn);
461 : :
462 [ + + ]: 28 : if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
463 : : {
464 : 1 : RetrievedObjects **found_objs = NULL;
465 : :
466 [ - + ]: 1 : if (vacopts->missing_stats_only)
467 : 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 : : */
477 [ + + ]: 4 : for (int stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
478 : : {
479 [ + + ]: 9 : for (int i = 0; i < numdbs; i++)
480 : : {
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)
488 : 0 : break;
489 : : }
490 [ - + ]: 3 : if (ret != EXIT_SUCCESS)
491 : 0 : break;
492 : : }
493 : :
494 [ - + ]: 1 : if (vacopts->missing_stats_only)
495 : : {
496 [ # # ]: 0 : for (int i = 0; i < numdbs; i++)
497 : 0 : free_retrieved_objects(found_objs[i]);
498 : 0 : pfree(found_objs);
499 : : }
500 : : }
501 : : else
502 : : {
503 [ + + ]: 86 : for (int i = 0; i < numdbs; i++)
504 : : {
505 : 59 : cparams->override_dbname = PQgetvalue(result, i, 0);
506 : 59 : ret = vacuum_one_database(cparams, vacopts,
507 : : ANALYZE_NO_STAGE,
508 : : objects,
509 : : NULL,
510 : : concurrentCons,
511 : : progname);
512 [ - + ]: 59 : if (ret != EXIT_SUCCESS)
513 : 0 : break;
514 : : }
515 : : }
516 : :
517 : 28 : PQclear(result);
518 : :
519 : 28 : 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 *
535 : 114 : 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 : 114 : bool objects_listed = false;
544 : : int ntups;
545 : :
546 : 114 : initPQExpBuffer(&catalog_query);
547 [ + - + + ]: 148 : 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 [ + + ]: 114 : if (objects_listed)
595 : 32 : appendPQExpBufferStr(&catalog_query, "\n)\n");
596 : :
597 : 114 : appendPQExpBufferStr(&catalog_query, "SELECT c.relname, ns.nspname, c.relkind");
598 : :
599 [ + + ]: 114 : if (objects_listed)
600 : 32 : appendPQExpBufferStr(&catalog_query, ", listed_objects.column_list");
601 : :
602 : 114 : 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 [ + + ]: 114 : 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 : 114 : 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 [ + + ]: 114 : 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 [ + + ]: 114 : 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 : : */
666 [ + + ]: 91 : if (vacopts->mode == MODE_ANALYZE ||
667 [ + + ]: 81 : vacopts->mode == MODE_ANALYZE_IN_STAGES)
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 : 69 : 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 [ + + ]: 114 : 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 [ + + ]: 114 : 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 [ + + ]: 114 : 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 : 114 : appendPQExpBufferStr(&catalog_query, " ORDER BY c.relpages DESC;");
791 : 114 : executeCommand(conn, "RESET search_path;", vacopts->echo);
792 : 114 : res = executeQuery(conn, catalog_query.data, vacopts->echo);
793 : 113 : termPQExpBuffer(&catalog_query);
794 : 113 : 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 : : */
800 : 113 : ntups = PQntuples(res);
801 : 113 : found_objs = palloc0(add_size(offsetof(RetrievedObjects, objects),
802 : : mul_size(sizeof(RetrievedObject), ntups)));
803 : 113 : found_objs->num_objects = ntups;
804 : :
805 : 113 : initPQExpBuffer(&buf);
806 [ + + ]: 6405 : 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 : 18115 : use_only = ((vacopts->mode == MODE_ANALYZE ||
818 [ + + ]: 5531 : vacopts->mode == MODE_ANALYZE_IN_STAGES) &&
819 [ + + + + ]: 13484 : (vacopts->objfilter & OBJFILTER_TABLE) == 0 &&
820 [ + + ]: 1661 : PQgetvalue(res, i, 2)[0] == RELKIND_PARTITIONED_TABLE);
821 : :
822 : 12584 : appendPQExpBufferStr(&buf,
823 : 6292 : fmtQualifiedIdEnc(PQgetvalue(res, i, 1),
824 : 6292 : PQgetvalue(res, i, 0),
825 : : PQclientEncoding(conn)));
826 : :
827 [ + + + + ]: 6292 : if (objects_listed && !PQgetisnull(res, i, 3))
828 : 5 : appendPQExpBufferStr(&buf, PQgetvalue(res, i, 3));
829 : :
830 : 6292 : object = &found_objs->objects[i];
831 : 6292 : object->target = pg_strdup(buf.data);
832 : 6292 : object->use_only = use_only;
833 : 6292 : resetPQExpBuffer(&buf);
834 : : }
835 : 113 : termPQExpBuffer(&buf);
836 : 113 : PQclear(res);
837 : :
838 : 113 : 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
848 : 115 : free_retrieved_objects(RetrievedObjects *objs)
849 : : {
850 [ + + ]: 115 : if (objs)
851 : : {
852 [ + + ]: 6405 : for (int i = 0; i < objs->num_objects; i++)
853 : 6292 : pg_free(objs->objects[i].target);
854 : :
855 : 113 : pg_free(objs);
856 : : }
857 : 115 : }
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
869 : 6296 : prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
870 : : vacuumingOptions *vacopts, const char *table,
871 : : bool use_only)
872 : : {
873 : 6296 : int serverVersion = PQserverVersion(conn);
874 : 6296 : const char *paren = " (";
875 : 6296 : const char *comma = ", ";
876 : 6296 : const char *sep = paren;
877 : :
878 : : Assert(!use_only ||
879 : : vacopts->mode == MODE_ANALYZE ||
880 : : vacopts->mode == MODE_ANALYZE_IN_STAGES);
881 : :
882 : 6296 : resetPQExpBuffer(sql);
883 : :
884 [ + + ]: 6296 : if (vacopts->mode == MODE_ANALYZE ||
885 [ + + ]: 5535 : 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 : : Assert(serverVersion >= 120000);
896 : 73 : appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
897 : 73 : sep = comma;
898 : : }
899 [ - + ]: 1676 : if (vacopts->verbose)
900 : : {
901 : 0 : appendPQExpBuffer(sql, "%sVERBOSE", sep);
902 : 0 : sep = comma;
903 : : }
904 [ - + ]: 1676 : if (vacopts->buffer_usage_limit)
905 : : {
906 : : Assert(serverVersion >= 160000);
907 : 0 : appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
908 : : vacopts->buffer_usage_limit);
909 : 0 : sep = comma;
910 : : }
911 [ + + ]: 1676 : if (sep != paren)
912 : 73 : appendPQExpBufferChar(sql, ')');
913 : : }
914 : : else
915 : : {
916 [ # # ]: 0 : if (vacopts->verbose)
917 : 0 : appendPQExpBufferStr(sql, " VERBOSE");
918 : : }
919 : :
920 : : /* ANALYZE ONLY is supported since v18 */
921 [ + + + - ]: 1676 : if (use_only && serverVersion >= 180000)
922 : 10 : appendPQExpBufferStr(sql, " ONLY");
923 : : }
924 : : else
925 : : {
926 : 4620 : appendPQExpBufferStr(sql, "VACUUM");
927 : :
928 : : /* parenthesized grammar of VACUUM is supported since v9.0 */
929 [ + - ]: 4620 : if (serverVersion >= 90000)
930 : : {
931 [ + + ]: 4620 : if (vacopts->disable_page_skipping)
932 : : {
933 : : /* DISABLE_PAGE_SKIPPING is supported since v9.6 */
934 : : Assert(serverVersion >= 90600);
935 : 73 : appendPQExpBuffer(sql, "%sDISABLE_PAGE_SKIPPING", sep);
936 : 73 : sep = comma;
937 : : }
938 [ + + ]: 4620 : if (vacopts->no_index_cleanup)
939 : : {
940 : : /* "INDEX_CLEANUP FALSE" has been supported since v12 */
941 : : Assert(serverVersion >= 120000);
942 : : Assert(!vacopts->force_index_cleanup);
943 : 73 : appendPQExpBuffer(sql, "%sINDEX_CLEANUP FALSE", sep);
944 : 73 : sep = comma;
945 : : }
946 [ - + ]: 4620 : if (vacopts->force_index_cleanup)
947 : : {
948 : : /* "INDEX_CLEANUP TRUE" has been supported since v12 */
949 : : Assert(serverVersion >= 120000);
950 : : Assert(!vacopts->no_index_cleanup);
951 : 0 : appendPQExpBuffer(sql, "%sINDEX_CLEANUP TRUE", sep);
952 : 0 : sep = comma;
953 : : }
954 [ + + ]: 4620 : if (!vacopts->do_truncate)
955 : : {
956 : : /* TRUNCATE is supported since v12 */
957 : : Assert(serverVersion >= 120000);
958 : 73 : appendPQExpBuffer(sql, "%sTRUNCATE FALSE", sep);
959 : 73 : sep = comma;
960 : : }
961 [ + + ]: 4620 : if (!vacopts->process_main)
962 : : {
963 : : /* PROCESS_MAIN is supported since v16 */
964 : : Assert(serverVersion >= 160000);
965 : 73 : appendPQExpBuffer(sql, "%sPROCESS_MAIN FALSE", sep);
966 : 73 : sep = comma;
967 : : }
968 [ + + ]: 4620 : if (!vacopts->process_toast)
969 : : {
970 : : /* PROCESS_TOAST is supported since v14 */
971 : : Assert(serverVersion >= 140000);
972 : 73 : appendPQExpBuffer(sql, "%sPROCESS_TOAST FALSE", sep);
973 : 73 : sep = comma;
974 : : }
975 [ + - ]: 4620 : if (vacopts->skip_database_stats)
976 : : {
977 : : /* SKIP_DATABASE_STATS is supported since v16 */
978 : : Assert(serverVersion >= 160000);
979 : 4620 : appendPQExpBuffer(sql, "%sSKIP_DATABASE_STATS", sep);
980 : 4620 : sep = comma;
981 : : }
982 [ + + ]: 4620 : if (vacopts->skip_locked)
983 : : {
984 : : /* SKIP_LOCKED is supported since v12 */
985 : : Assert(serverVersion >= 120000);
986 : 73 : appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
987 : 73 : sep = comma;
988 : : }
989 [ + + ]: 4620 : if (vacopts->full)
990 : : {
991 : 73 : appendPQExpBuffer(sql, "%sFULL", sep);
992 : 73 : sep = comma;
993 : : }
994 [ + + ]: 4620 : if (vacopts->freeze)
995 : : {
996 : 1752 : appendPQExpBuffer(sql, "%sFREEZE", sep);
997 : 1752 : sep = comma;
998 : : }
999 [ - + ]: 4620 : if (vacopts->verbose)
1000 : : {
1001 : 0 : appendPQExpBuffer(sql, "%sVERBOSE", sep);
1002 : 0 : sep = comma;
1003 : : }
1004 [ + + ]: 4620 : if (vacopts->and_analyze)
1005 : : {
1006 : 1536 : appendPQExpBuffer(sql, "%sANALYZE", sep);
1007 : 1536 : sep = comma;
1008 : : }
1009 [ + + ]: 4620 : if (vacopts->parallel_workers >= 0)
1010 : : {
1011 : : /* PARALLEL is supported since v13 */
1012 : : Assert(serverVersion >= 130000);
1013 : 146 : appendPQExpBuffer(sql, "%sPARALLEL %d", sep,
1014 : : vacopts->parallel_workers);
1015 : 146 : sep = comma;
1016 : : }
1017 [ - + ]: 4620 : if (vacopts->buffer_usage_limit)
1018 : : {
1019 : : Assert(serverVersion >= 160000);
1020 : 0 : appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
1021 : : vacopts->buffer_usage_limit);
1022 : 0 : sep = comma;
1023 : : }
1024 [ + - ]: 4620 : if (sep != paren)
1025 : 4620 : appendPQExpBufferChar(sql, ')');
1026 : : }
1027 : : else
1028 : : {
1029 [ # # ]: 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 : :
1040 : 6296 : appendPQExpBuffer(sql, " %s;", table);
1041 : 6296 : }
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
1051 : 6368 : run_vacuum_command(ParallelSlot *free_slot, vacuumingOptions *vacopts,
1052 : : const char *sql, const char *table)
1053 : : {
1054 : 6368 : bool status = true;
1055 : 6368 : PGconn *conn = free_slot->connection;
1056 : :
1057 [ + + + + ]: 6368 : if (vacopts->echo || vacopts->dry_run)
1058 : 441 : printf("%s\n", sql);
1059 : :
1060 [ + + ]: 6368 : if (vacopts->dry_run)
1061 : 3 : ParallelSlotSetIdle(free_slot);
1062 : : else
1063 : 6365 : status = PQsendQuery(conn, sql) == 1;
1064 : :
1065 [ - + ]: 6368 : if (!status)
1066 : : {
1067 [ # # ]: 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 : : }
1078 : 6368 : }
1079 : :
1080 : : /*
1081 : : * Returns a newly malloc'd version of 'src' with escaped single quotes and
1082 : : * backslashes.
1083 : : */
1084 : : char *
1085 : 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 : : }
|