LCOV - code coverage report
Current view: top level - src/bin/scripts - vacuumdb.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 408 501 81.4 %
Date: 2024-11-21 08:14:44 Functions: 7 8 87.5 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14