LCOV - code coverage report
Current view: top level - src/bin/scripts - vacuuming.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 264 341 77.4 %
Date: 2025-12-22 14:19:12 Functions: 7 8 87.5 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.16