vacuumdb.c 29.2 KB
Newer Older
1 2 3 4
/*-------------------------------------------------------------------------
 *
 * vacuumdb
 *
Bruce Momjian's avatar
Bruce Momjian committed
5
 * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
6 7
 * Portions Copyright (c) 1994, Regents of the University of California
 *
8
 * src/bin/scripts/vacuumdb.c
9 10 11 12 13
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"
14

15
#include "catalog/pg_class_d.h"
16

17
#include "common.h"
18
#include "common/connect.h"
19
#include "common/logging.h"
20
#include "fe_utils/cancel.h"
21 22 23
#include "fe_utils/option_utils.h"
#include "fe_utils/parallel_slot.h"
#include "fe_utils/query_utils.h"
24 25
#include "fe_utils/simple_list.h"
#include "fe_utils/string_utils.h"
26 27


28 29 30 31 32 33 34 35
/* vacuum options controlled by user flags */
typedef struct vacuumingOptions
{
	bool		analyze_only;
	bool		verbose;
	bool		and_analyze;
	bool		full;
	bool		freeze;
36 37
	bool		disable_page_skipping;
	bool		skip_locked;
38 39
	int			min_xid_age;
	int			min_mxid_age;
40 41
	int			parallel_workers;	/* >= 0 indicates user specified the
									 * parallel degree, otherwise -1 */
42 43
	bool		no_index_cleanup;
	bool		force_index_cleanup;
44
	bool		do_truncate;
45
	bool		process_toast;
46 47 48
} vacuumingOptions;


49
static void vacuum_one_database(ConnParams *cparams,
50
								vacuumingOptions *vacopts,
Tom Lane's avatar
Tom Lane committed
51 52 53 54
								int stage,
								SimpleStringList *tables,
								int concurrentCons,
								const char *progname, bool echo, bool quiet);
55

56 57
static void vacuum_all_databases(ConnParams *cparams,
								 vacuumingOptions *vacopts,
Tom Lane's avatar
Tom Lane committed
58 59 60
								 bool analyze_in_stages,
								 int concurrentCons,
								 const char *progname, bool echo, bool quiet);
61

62
static void prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
Tom Lane's avatar
Tom Lane committed
63
								   vacuumingOptions *vacopts, const char *table);
64 65

static void run_vacuum_command(PGconn *conn, const char *sql, bool echo,
66
							   const char *table);
67

68 69
static void help(const char *progname);

70 71 72 73
/* For analyze-in-stages mode */
#define ANALYZE_NO_STAGE	-1
#define ANALYZE_NUM_STAGES	3

74 75 76 77 78 79 80 81

int
main(int argc, char *argv[])
{
	static struct option long_options[] = {
		{"host", required_argument, NULL, 'h'},
		{"port", required_argument, NULL, 'p'},
		{"username", required_argument, NULL, 'U'},
82
		{"no-password", no_argument, NULL, 'w'},
83 84 85 86 87
		{"password", no_argument, NULL, 'W'},
		{"echo", no_argument, NULL, 'e'},
		{"quiet", no_argument, NULL, 'q'},
		{"dbname", required_argument, NULL, 'd'},
		{"analyze", no_argument, NULL, 'z'},
88
		{"analyze-only", no_argument, NULL, 'Z'},
89
		{"freeze", no_argument, NULL, 'F'},
90 91 92 93
		{"all", no_argument, NULL, 'a'},
		{"table", required_argument, NULL, 't'},
		{"full", no_argument, NULL, 'f'},
		{"verbose", no_argument, NULL, 'v'},
94
		{"jobs", required_argument, NULL, 'j'},
95
		{"parallel", required_argument, NULL, 'P'},
96
		{"maintenance-db", required_argument, NULL, 2},
97
		{"analyze-in-stages", no_argument, NULL, 3},
98 99
		{"disable-page-skipping", no_argument, NULL, 4},
		{"skip-locked", no_argument, NULL, 5},
100 101
		{"min-xid-age", required_argument, NULL, 6},
		{"min-mxid-age", required_argument, NULL, 7},
102
		{"no-index-cleanup", no_argument, NULL, 8},
103 104 105
		{"force-index-cleanup", no_argument, NULL, 9},
		{"no-truncate", no_argument, NULL, 10},
		{"no-process-toast", no_argument, NULL, 11},
106 107 108
		{NULL, 0, NULL, 0}
	};

109
	const char *progname;
110 111 112
	int			optindex;
	int			c;
	const char *dbname = NULL;
113
	const char *maintenance_db = NULL;
114 115 116
	char	   *host = NULL;
	char	   *port = NULL;
	char	   *username = NULL;
117
	enum trivalue prompt_password = TRI_DEFAULT;
118
	ConnParams	cparams;
119 120
	bool		echo = false;
	bool		quiet = false;
121
	vacuumingOptions vacopts;
122
	bool		analyze_in_stages = false;
123
	bool		alldb = false;
124
	SimpleStringList tables = {NULL, NULL};
125 126 127
	int			concurrentCons = 1;
	int			tbl_count = 0;

128
	/* initialize options */
129
	memset(&vacopts, 0, sizeof(vacopts));
130
	vacopts.parallel_workers = -1;
131 132
	vacopts.no_index_cleanup = false;
	vacopts.force_index_cleanup = false;
133
	vacopts.do_truncate = true;
134
	vacopts.process_toast = true;
135

136
	pg_logging_init(argv[0]);
137
	progname = get_progname(argv[0]);
138
	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
139

140 141
	handle_help_version_opts(argc, argv, "vacuumdb", help);

142
	while ((c = getopt_long(argc, argv, "h:p:U:wWeqd:zZFat:fvj:P:", long_options, &optindex)) != -1)
143 144 145 146
	{
		switch (c)
		{
			case 'h':
147
				host = pg_strdup(optarg);
148 149
				break;
			case 'p':
150
				port = pg_strdup(optarg);
151 152
				break;
			case 'U':
153
				username = pg_strdup(optarg);
154
				break;
155 156 157
			case 'w':
				prompt_password = TRI_NO;
				break;
158
			case 'W':
159
				prompt_password = TRI_YES;
160 161 162 163 164 165 166 167
				break;
			case 'e':
				echo = true;
				break;
			case 'q':
				quiet = true;
				break;
			case 'd':
168
				dbname = pg_strdup(optarg);
169 170
				break;
			case 'z':
171
				vacopts.and_analyze = true;
Bruce Momjian's avatar
Bruce Momjian committed
172
				break;
173
			case 'Z':
174
				vacopts.analyze_only = true;
175
				break;
176
			case 'F':
177
				vacopts.freeze = true;
178
				break;
179 180 181 182
			case 'a':
				alldb = true;
				break;
			case 't':
183 184 185 186 187
				{
					simple_string_list_append(&tables, optarg);
					tbl_count++;
					break;
				}
188
			case 'f':
189
				vacopts.full = true;
190 191
				break;
			case 'v':
192 193 194 195 196 197
				vacopts.verbose = true;
				break;
			case 'j':
				concurrentCons = atoi(optarg);
				if (concurrentCons <= 0)
				{
198
					pg_log_error("number of parallel jobs must be at least 1");
199 200
					exit(1);
				}
201
				break;
202 203 204 205
			case 'P':
				vacopts.parallel_workers = atoi(optarg);
				if (vacopts.parallel_workers < 0)
				{
206
					pg_log_error("parallel workers for vacuum must be greater than or equal to zero");
207 208 209
					exit(1);
				}
				break;
210
			case 2:
211
				maintenance_db = pg_strdup(optarg);
212
				break;
213
			case 3:
214
				analyze_in_stages = vacopts.analyze_only = true;
215
				break;
216 217 218 219 220 221
			case 4:
				vacopts.disable_page_skipping = true;
				break;
			case 5:
				vacopts.skip_locked = true;
				break;
222 223 224 225
			case 6:
				vacopts.min_xid_age = atoi(optarg);
				if (vacopts.min_xid_age <= 0)
				{
226
					pg_log_error("minimum transaction ID age must be at least 1");
227 228 229 230 231 232 233
					exit(1);
				}
				break;
			case 7:
				vacopts.min_mxid_age = atoi(optarg);
				if (vacopts.min_mxid_age <= 0)
				{
234
					pg_log_error("minimum multixact ID age must be at least 1");
235 236 237
					exit(1);
				}
				break;
238
			case 8:
239
				vacopts.no_index_cleanup = true;
240 241
				break;
			case 9:
242
				vacopts.force_index_cleanup = true;
243
				break;
244
			case 10:
245 246 247
				vacopts.do_truncate = false;
				break;
			case 11:
248 249
				vacopts.process_toast = false;
				break;
250
			default:
251
				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
252 253 254 255
				exit(1);
		}
	}

256 257 258
	/*
	 * Non-option argument specifies database name as long as it wasn't
	 * already specified with -d / --dbname
259 260
	 */
	if (optind < argc && dbname == NULL)
261
	{
262 263 264 265 266 267
		dbname = argv[optind];
		optind++;
	}

	if (optind < argc)
	{
268 269
		pg_log_error("too many command-line arguments (first is \"%s\")",
					 argv[optind]);
270 271
		fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
		exit(1);
272 273
	}

274
	if (vacopts.analyze_only)
Bruce Momjian's avatar
Bruce Momjian committed
275
	{
276
		if (vacopts.full)
Bruce Momjian's avatar
Bruce Momjian committed
277
		{
278 279
			pg_log_error("cannot use the \"%s\" option when performing only analyze",
						 "full");
Bruce Momjian's avatar
Bruce Momjian committed
280 281
			exit(1);
		}
282
		if (vacopts.freeze)
Bruce Momjian's avatar
Bruce Momjian committed
283
		{
284 285
			pg_log_error("cannot use the \"%s\" option when performing only analyze",
						 "freeze");
Bruce Momjian's avatar
Bruce Momjian committed
286 287
			exit(1);
		}
288 289
		if (vacopts.disable_page_skipping)
		{
290 291
			pg_log_error("cannot use the \"%s\" option when performing only analyze",
						 "disable-page-skipping");
292 293
			exit(1);
		}
294
		if (vacopts.no_index_cleanup)
295 296 297 298 299
		{
			pg_log_error("cannot use the \"%s\" option when performing only analyze",
						 "no-index-cleanup");
			exit(1);
		}
300 301 302 303 304 305
		if (vacopts.force_index_cleanup)
		{
			pg_log_error("cannot use the \"%s\" option when performing only analyze",
						 "force-index-cleanup");
			exit(1);
		}
306 307 308 309 310 311
		if (!vacopts.do_truncate)
		{
			pg_log_error("cannot use the \"%s\" option when performing only analyze",
						 "no-truncate");
			exit(1);
		}
312 313 314 315 316 317
		if (!vacopts.process_toast)
		{
			pg_log_error("cannot use the \"%s\" option when performing only analyze",
						 "no-process-toast");
			exit(1);
		}
318
		/* allow 'and_analyze' with 'analyze_only' */
Bruce Momjian's avatar
Bruce Momjian committed
319 320
	}

321 322 323 324 325 326 327 328 329 330 331
	/* Prohibit full and analyze_only options with parallel option */
	if (vacopts.parallel_workers >= 0)
	{
		if (vacopts.analyze_only)
		{
			pg_log_error("cannot use the \"%s\" option when performing only analyze",
						 "parallel");
			exit(1);
		}
		if (vacopts.full)
		{
332
			pg_log_error("cannot use the \"%s\" option when performing full vacuum",
333 334 335 336 337
						 "parallel");
			exit(1);
		}
	}

338 339 340 341 342 343 344 345
	/* Prohibit --no-index-cleanup and --force-index-cleanup together */
	if (vacopts.no_index_cleanup && vacopts.force_index_cleanup)
	{
		pg_log_error("cannot use the \"%s\" option with the \"%s\" option",
					 "no-index-cleanup", "force-index-cleanup");
		exit(1);
	}

346 347 348 349 350 351 352
	/* fill cparams except for dbname, which is set below */
	cparams.pghost = host;
	cparams.pgport = port;
	cparams.pguser = username;
	cparams.prompt_password = prompt_password;
	cparams.override_dbname = NULL;

353
	setup_cancel_handler(NULL);
354

355 356 357 358
	/* Avoid opening extra connections. */
	if (tbl_count && (concurrentCons > tbl_count))
		concurrentCons = tbl_count;

359 360 361 362
	if (alldb)
	{
		if (dbname)
		{
363
			pg_log_error("cannot vacuum all databases and a specific one at the same time");
364 365
			exit(1);
		}
366
		if (tables.head != NULL)
367
		{
368
			pg_log_error("cannot vacuum specific table(s) in all databases");
369 370
			exit(1);
		}
Bruce Momjian's avatar
Bruce Momjian committed
371

372 373 374
		cparams.dbname = maintenance_db;

		vacuum_all_databases(&cparams, &vacopts,
375 376 377
							 analyze_in_stages,
							 concurrentCons,
							 progname, echo, quiet);
378 379 380 381 382 383 384 385 386 387
	}
	else
	{
		if (dbname == NULL)
		{
			if (getenv("PGDATABASE"))
				dbname = getenv("PGDATABASE");
			else if (getenv("PGUSER"))
				dbname = getenv("PGUSER");
			else
388
				dbname = get_user_name_or_exit(progname);
389 390
		}

391 392
		cparams.dbname = dbname;

393
		if (analyze_in_stages)
394
		{
395
			int			stage;
396

397
			for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
398
			{
399
				vacuum_one_database(&cparams, &vacopts,
400 401 402
									stage,
									&tables,
									concurrentCons,
403
									progname, echo, quiet);
404 405 406
			}
		}
		else
407
			vacuum_one_database(&cparams, &vacopts,
408 409 410
								ANALYZE_NO_STAGE,
								&tables,
								concurrentCons,
411
								progname, echo, quiet);
412 413 414 415 416
	}

	exit(0);
}

417 418 419 420 421 422 423 424 425 426 427 428 429
/*
 * vacuum_one_database
 *
 * Process tables in the given database.  If the 'tables' list is empty,
 * process all tables in the database.
 *
 * Note that this function is only concerned with running exactly one stage
 * when in analyze-in-stages mode; caller must iterate on us if necessary.
 *
 * If concurrentCons is > 1, multiple connections are used to vacuum tables
 * in parallel.  In this case and if the table list is empty, we first obtain
 * a list of tables from the database.
 */
430
static void
431
vacuum_one_database(ConnParams *cparams,
432
					vacuumingOptions *vacopts,
433 434 435
					int stage,
					SimpleStringList *tables,
					int concurrentCons,
436
					const char *progname, bool echo, bool quiet)
437
{
438
	PQExpBufferData sql;
439 440 441
	PQExpBufferData buf;
	PQExpBufferData catalog_query;
	PGresult   *res;
442 443
	PGconn	   *conn;
	SimpleStringListCell *cell;
444
	ParallelSlotArray *sa;
445 446
	SimpleStringList dbtables = {NULL, NULL};
	int			i;
447
	int			ntups;
448
	bool		failed = false;
449
	bool		tables_listed = false;
450
	bool		has_where = false;
451
	const char *initcmd;
452 453 454 455 456 457 458 459 460 461 462 463 464 465
	const char *stage_commands[] = {
		"SET default_statistics_target=1; SET vacuum_cost_delay=0;",
		"SET default_statistics_target=10; RESET vacuum_cost_delay;",
		"RESET default_statistics_target;"
	};
	const char *stage_messages[] = {
		gettext_noop("Generating minimal optimizer statistics (1 target)"),
		gettext_noop("Generating medium optimizer statistics (10 targets)"),
		gettext_noop("Generating default (full) optimizer statistics")
	};

	Assert(stage == ANALYZE_NO_STAGE ||
		   (stage >= 0 && stage < ANALYZE_NUM_STAGES));

466
	conn = connectDatabase(cparams, progname, echo, false, true);
467

468 469 470
	if (vacopts->disable_page_skipping && PQserverVersion(conn) < 90600)
	{
		PQfinish(conn);
Alvaro Herrera's avatar
Alvaro Herrera committed
471 472
		pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
					 "disable-page-skipping", "9.6");
473 474 475
		exit(1);
	}

476
	if (vacopts->no_index_cleanup && PQserverVersion(conn) < 120000)
477 478 479 480 481 482 483
	{
		PQfinish(conn);
		pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
					 "no-index-cleanup", "12");
		exit(1);
	}

484 485 486 487 488 489 490 491
	if (vacopts->force_index_cleanup && PQserverVersion(conn) < 120000)
	{
		PQfinish(conn);
		pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
					 "force-index-cleanup", "12");
		exit(1);
	}

492 493 494 495 496 497 498 499
	if (!vacopts->do_truncate && PQserverVersion(conn) < 120000)
	{
		PQfinish(conn);
		pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
					 "no-truncate", "12");
		exit(1);
	}

500 501 502 503 504 505 506 507
	if (!vacopts->process_toast && PQserverVersion(conn) < 140000)
	{
		PQfinish(conn);
		pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
					 "no-process-toast", "14");
		exit(1);
	}

508 509 510
	if (vacopts->skip_locked && PQserverVersion(conn) < 120000)
	{
		PQfinish(conn);
Alvaro Herrera's avatar
Alvaro Herrera committed
511 512
		pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
					 "skip-locked", "12");
513 514 515
		exit(1);
	}

516 517
	if (vacopts->min_xid_age != 0 && PQserverVersion(conn) < 90600)
	{
Alvaro Herrera's avatar
Alvaro Herrera committed
518 519
		pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
					 "--min-xid-age", "9.6");
520 521 522 523 524
		exit(1);
	}

	if (vacopts->min_mxid_age != 0 && PQserverVersion(conn) < 90600)
	{
Alvaro Herrera's avatar
Alvaro Herrera committed
525 526
		pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
					 "--min-mxid-age", "9.6");
527 528 529
		exit(1);
	}

530 531 532 533 534 535 536
	if (vacopts->parallel_workers >= 0 && PQserverVersion(conn) < 130000)
	{
		pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
					 "--parallel", "13");
		exit(1);
	}

537
	if (!quiet)
538
	{
539
		if (stage != ANALYZE_NO_STAGE)
540
			printf(_("%s: processing database \"%s\": %s\n"),
541
				   progname, PQdb(conn), _(stage_messages[stage]));
542
		else
543 544
			printf(_("%s: vacuuming database \"%s\"\n"),
				   progname, PQdb(conn));
545
		fflush(stdout);
546 547
	}

548
	/*
549 550 551 552 553 554 555 556 557 558 559 560
	 * Prepare the list of tables to process by querying the catalogs.
	 *
	 * Since we execute the constructed query with the default search_path
	 * (which could be unsafe), everything in this query MUST be fully
	 * qualified.
	 *
	 * First, build a WITH clause for the catalog query if any tables were
	 * specified, with a set of values made of relation names and their
	 * optional set of columns.  This is used to match any provided column
	 * lists with the generated qualified identifiers and to filter for the
	 * tables provided via --table.  If a listed table does not exist, the
	 * catalog query will fail.
561
	 */
562 563
	initPQExpBuffer(&catalog_query);
	for (cell = tables ? tables->head : NULL; cell; cell = cell->next)
564
	{
565 566
		char	   *just_table;
		const char *just_columns;
567

568 569 570 571 572 573 574 575 576 577
		/*
		 * Split relation and column names given by the user, this is used to
		 * feed the CTE with values on which are performed pre-run validity
		 * checks as well.  For now these happen only on the relation name.
		 */
		splitTableColumnsSpec(cell->val, PQclientEncoding(conn),
							  &just_table, &just_columns);

		if (!tables_listed)
		{
578 579 580
			appendPQExpBufferStr(&catalog_query,
								 "WITH listed_tables (table_oid, column_list) "
								 "AS (\n  VALUES (");
581
			tables_listed = true;
582
		}
583
		else
584
			appendPQExpBufferStr(&catalog_query, ",\n  (");
585

586
		appendStringLiteralConn(&catalog_query, just_table, conn);
587
		appendPQExpBufferStr(&catalog_query, "::pg_catalog.regclass, ");
588

589 590 591 592 593 594 595 596 597 598 599 600
		if (just_columns && just_columns[0] != '\0')
			appendStringLiteralConn(&catalog_query, just_columns, conn);
		else
			appendPQExpBufferStr(&catalog_query, "NULL");

		appendPQExpBufferStr(&catalog_query, "::pg_catalog.text)");

		pg_free(just_table);
	}

	/* Finish formatting the CTE */
	if (tables_listed)
601
		appendPQExpBufferStr(&catalog_query, "\n)\n");
602

603
	appendPQExpBufferStr(&catalog_query, "SELECT c.relname, ns.nspname");
604 605

	if (tables_listed)
606
		appendPQExpBufferStr(&catalog_query, ", listed_tables.column_list");
607

608 609 610 611 612 613
	appendPQExpBufferStr(&catalog_query,
						 " FROM pg_catalog.pg_class c\n"
						 " JOIN pg_catalog.pg_namespace ns"
						 " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
						 " LEFT JOIN pg_catalog.pg_class t"
						 " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
614 615 616

	/* Used to match the tables listed by the user */
	if (tables_listed)
617 618
		appendPQExpBufferStr(&catalog_query, " JOIN listed_tables"
							 " ON listed_tables.table_oid OPERATOR(pg_catalog.=) c.oid\n");
619

620 621 622 623 624 625 626
	/*
	 * If no tables were listed, filter for the relevant relation types.  If
	 * tables were given via --table, don't bother filtering by relation type.
	 * Instead, let the server decide whether a given relation can be
	 * processed in which case the user will know about it.
	 */
	if (!tables_listed)
627
	{
628 629 630
		appendPQExpBufferStr(&catalog_query, " WHERE c.relkind OPERATOR(pg_catalog.=) ANY (array["
							 CppAsString2(RELKIND_RELATION) ", "
							 CppAsString2(RELKIND_MATVIEW) "])\n");
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
		has_where = true;
	}

	/*
	 * For --min-xid-age and --min-mxid-age, the age of the relation is the
	 * greatest of the ages of the main relation and its associated TOAST
	 * table.  The commands generated by vacuumdb will also process the TOAST
	 * table for the relation if necessary, so it does not need to be
	 * considered separately.
	 */
	if (vacopts->min_xid_age != 0)
	{
		appendPQExpBuffer(&catalog_query,
						  " %s GREATEST(pg_catalog.age(c.relfrozenxid),"
						  " pg_catalog.age(t.relfrozenxid)) "
						  " OPERATOR(pg_catalog.>=) '%d'::pg_catalog.int4\n"
						  " AND c.relfrozenxid OPERATOR(pg_catalog.!=)"
						  " '0'::pg_catalog.xid\n",
						  has_where ? "AND" : "WHERE", vacopts->min_xid_age);
		has_where = true;
	}

	if (vacopts->min_mxid_age != 0)
	{
		appendPQExpBuffer(&catalog_query,
						  " %s GREATEST(pg_catalog.mxid_age(c.relminmxid),"
						  " pg_catalog.mxid_age(t.relminmxid)) OPERATOR(pg_catalog.>=)"
						  " '%d'::pg_catalog.int4\n"
						  " AND c.relminmxid OPERATOR(pg_catalog.!=)"
						  " '0'::pg_catalog.xid\n",
						  has_where ? "AND" : "WHERE", vacopts->min_mxid_age);
		has_where = true;
	}
664 665 666 667 668

	/*
	 * Execute the catalog query.  We use the default search_path for this
	 * query for consistency with table lookups done elsewhere by the user.
	 */
669
	appendPQExpBufferStr(&catalog_query, " ORDER BY c.relpages DESC;");
670 671
	executeCommand(conn, "RESET search_path;", echo);
	res = executeQuery(conn, catalog_query.data, echo);
672
	termPQExpBuffer(&catalog_query);
673
	PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706

	/*
	 * If no rows are returned, there are no matching tables, so we are done.
	 */
	ntups = PQntuples(res);
	if (ntups == 0)
	{
		PQclear(res);
		PQfinish(conn);
		return;
	}

	/*
	 * Build qualified identifiers for each table, including the column list
	 * if given.
	 */
	initPQExpBuffer(&buf);
	for (i = 0; i < ntups; i++)
	{
		appendPQExpBufferStr(&buf,
							 fmtQualifiedId(PQgetvalue(res, i, 1),
											PQgetvalue(res, i, 0)));

		if (tables_listed && !PQgetisnull(res, i, 2))
			appendPQExpBufferStr(&buf, PQgetvalue(res, i, 2));

		simple_string_list_append(&dbtables, buf.data);
		resetPQExpBuffer(&buf);
	}
	termPQExpBuffer(&buf);
	PQclear(res);

	/*
707 708
	 * Ensure concurrentCons is sane.  If there are more connections than
	 * vacuumable relations, we don't need to use them all.
709
	 */
710 711 712 713
	if (concurrentCons > ntups)
		concurrentCons = ntups;
	if (concurrentCons <= 0)
		concurrentCons = 1;
714 715

	/*
716 717 718
	 * All slots need to be prepared to run the appropriate analyze stage, if
	 * caller requested that mode.  We have to prepare the initial connection
	 * ourselves before setting up the slots.
719
	 */
720 721 722 723 724 725 726
	if (stage == ANALYZE_NO_STAGE)
		initcmd = NULL;
	else
	{
		initcmd = stage_commands[stage];
		executeCommand(conn, initcmd, echo);
	}
727 728

	/*
729 730 731
	 * Setup the database connections. We reuse the connection we already have
	 * for the first slot.  If not in parallel mode, the first slot in the
	 * array contains the connection.
732
	 */
733 734
	sa = ParallelSlotsSetup(concurrentCons, cparams, progname, echo, initcmd);
	ParallelSlotsAdoptConn(sa, conn);
735

736 737 738
	initPQExpBuffer(&sql);

	cell = dbtables.head;
739 740
	do
	{
741
		const char *tabname = cell->val;
742
		ParallelSlot *free_slot;
743 744 745

		if (CancelRequested)
		{
746
			failed = true;
747 748 749
			goto finish;
		}

750
		free_slot = ParallelSlotsGetIdle(sa, NULL);
751
		if (!free_slot)
752
		{
753 754
			failed = true;
			goto finish;
755 756
		}

757 758
		prepare_vacuum_command(&sql, PQserverVersion(free_slot->connection),
							   vacopts, tabname);
759

760
		/*
761 762
		 * Execute the vacuum.  All errors are handled in processQueryResult
		 * through ParallelSlotsGetIdle.
763
		 */
764
		ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
765
		run_vacuum_command(free_slot->connection, sql.data,
766
						   echo, tabname);
767

768
		cell = cell->next;
769 770
	} while (cell != NULL);

771
	if (!ParallelSlotsWaitCompletion(sa))
772
		failed = true;
773 774

finish:
775 776
	ParallelSlotsTerminate(sa);
	pg_free(sa);
777 778 779

	termPQExpBuffer(&sql);

780
	if (failed)
781 782 783 784 785 786 787 788 789 790
		exit(1);
}

/*
 * Vacuum/analyze all connectable databases.
 *
 * In analyze-in-stages mode, we process all databases in one stage before
 * moving on to the next stage.  That ensure minimal stats are available
 * quickly everywhere before generating more detailed ones.
 */
791
static void
792 793
vacuum_all_databases(ConnParams *cparams,
					 vacuumingOptions *vacopts,
794 795 796
					 bool analyze_in_stages,
					 int concurrentCons,
					 const char *progname, bool echo, bool quiet)
797 798
{
	PGconn	   *conn;
799 800 801
	PGresult   *result;
	int			stage;
	int			i;
802

803
	conn = connectMaintenanceDatabase(cparams, progname, echo);
804
	result = executeQuery(conn,
Tom Lane's avatar
Tom Lane committed
805
						  "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;",
806
						  echo);
807
	PQfinish(conn);
808

809 810 811 812 813 814 815 816 817 818 819 820 821 822
	if (analyze_in_stages)
	{
		/*
		 * When analyzing all databases in stages, we analyze them all in the
		 * fastest stage first, so that initial statistics become available
		 * for all of them as soon as possible.
		 *
		 * This means we establish several times as many connections, but
		 * that's a secondary consideration.
		 */
		for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
		{
			for (i = 0; i < PQntuples(result); i++)
			{
823
				cparams->override_dbname = PQgetvalue(result, i, 0);
824

825
				vacuum_one_database(cparams, vacopts,
826 827 828
									stage,
									NULL,
									concurrentCons,
829
									progname, echo, quiet);
830 831 832 833 834 835 836
			}
		}
	}
	else
	{
		for (i = 0; i < PQntuples(result); i++)
		{
837
			cparams->override_dbname = PQgetvalue(result, i, 0);
838

839
			vacuum_one_database(cparams, vacopts,
840 841 842
								ANALYZE_NO_STAGE,
								NULL,
								concurrentCons,
843
								progname, echo, quiet);
844 845 846 847 848 849 850 851 852 853
		}
	}

	PQclear(result);
}

/*
 * Construct a vacuum/analyze command to run based on the given options, in the
 * given string buffer, which may contain previous garbage.
 *
854 855
 * The table name used must be already properly quoted.  The command generated
 * depends on the server version involved and it is semicolon-terminated.
856 857
 */
static void
858 859
prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
					   vacuumingOptions *vacopts, const char *table)
860
{
861 862 863 864
	const char *paren = " (";
	const char *comma = ", ";
	const char *sep = paren;

865 866 867
	resetPQExpBuffer(sql);

	if (vacopts->analyze_only)
868
	{
869
		appendPQExpBufferStr(sql, "ANALYZE");
870 871

		/* parenthesized grammar of ANALYZE is supported since v11 */
872
		if (serverVersion >= 110000)
873 874 875 876
		{
			if (vacopts->skip_locked)
			{
				/* SKIP_LOCKED is supported since v12 */
877
				Assert(serverVersion >= 120000);
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
				appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
				sep = comma;
			}
			if (vacopts->verbose)
			{
				appendPQExpBuffer(sql, "%sVERBOSE", sep);
				sep = comma;
			}
			if (sep != paren)
				appendPQExpBufferChar(sql, ')');
		}
		else
		{
			if (vacopts->verbose)
				appendPQExpBufferStr(sql, " VERBOSE");
		}
894
	}
Bruce Momjian's avatar
Bruce Momjian committed
895 896
	else
	{
897
		appendPQExpBufferStr(sql, "VACUUM");
898 899

		/* parenthesized grammar of VACUUM is supported since v9.0 */
900
		if (serverVersion >= 90000)
901
		{
902 903 904
			if (vacopts->disable_page_skipping)
			{
				/* DISABLE_PAGE_SKIPPING is supported since v9.6 */
905
				Assert(serverVersion >= 90600);
906 907 908
				appendPQExpBuffer(sql, "%sDISABLE_PAGE_SKIPPING", sep);
				sep = comma;
			}
909
			if (vacopts->no_index_cleanup)
910
			{
911
				/* "INDEX_CLEANUP FALSE" has been supported since v12 */
912
				Assert(serverVersion >= 120000);
913
				Assert(!vacopts->force_index_cleanup);
914 915 916
				appendPQExpBuffer(sql, "%sINDEX_CLEANUP FALSE", sep);
				sep = comma;
			}
917 918
			if (vacopts->force_index_cleanup)
			{
919
				/* "INDEX_CLEANUP TRUE" has been supported since v12 */
920 921 922 923 924
				Assert(serverVersion >= 120000);
				Assert(!vacopts->no_index_cleanup);
				appendPQExpBuffer(sql, "%sINDEX_CLEANUP TRUE", sep);
				sep = comma;
			}
925 926 927 928 929 930 931
			if (!vacopts->do_truncate)
			{
				/* TRUNCATE is supported since v12 */
				Assert(serverVersion >= 120000);
				appendPQExpBuffer(sql, "%sTRUNCATE FALSE", sep);
				sep = comma;
			}
932 933 934 935 936 937 938
			if (!vacopts->process_toast)
			{
				/* PROCESS_TOAST is supported since v14 */
				Assert(serverVersion >= 140000);
				appendPQExpBuffer(sql, "%sPROCESS_TOAST FALSE", sep);
				sep = comma;
			}
939 940 941
			if (vacopts->skip_locked)
			{
				/* SKIP_LOCKED is supported since v12 */
942
				Assert(serverVersion >= 120000);
943 944 945
				appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
				sep = comma;
			}
946
			if (vacopts->full)
947
			{
948
				appendPQExpBuffer(sql, "%sFULL", sep);
949 950
				sep = comma;
			}
951
			if (vacopts->freeze)
952
			{
953
				appendPQExpBuffer(sql, "%sFREEZE", sep);
954 955
				sep = comma;
			}
956
			if (vacopts->verbose)
957
			{
958
				appendPQExpBuffer(sql, "%sVERBOSE", sep);
959 960
				sep = comma;
			}
961
			if (vacopts->and_analyze)
962
			{
963
				appendPQExpBuffer(sql, "%sANALYZE", sep);
964 965
				sep = comma;
			}
966 967 968 969 970 971 972 973
			if (vacopts->parallel_workers >= 0)
			{
				/* PARALLEL is supported since v13 */
				Assert(serverVersion >= 130000);
				appendPQExpBuffer(sql, "%sPARALLEL %d", sep,
								  vacopts->parallel_workers);
				sep = comma;
			}
974
			if (sep != paren)
975
				appendPQExpBufferChar(sql, ')');
976 977 978
		}
		else
		{
979 980 981 982 983 984 985 986
			if (vacopts->full)
				appendPQExpBufferStr(sql, " FULL");
			if (vacopts->freeze)
				appendPQExpBufferStr(sql, " FREEZE");
			if (vacopts->verbose)
				appendPQExpBufferStr(sql, " VERBOSE");
			if (vacopts->and_analyze)
				appendPQExpBufferStr(sql, " ANALYZE");
987
		}
Bruce Momjian's avatar
Bruce Momjian committed
988
	}
989

990
	appendPQExpBuffer(sql, " %s;", table);
991
}
992

993
/*
994 995
 * Send a vacuum/analyze command to the server, returning after sending the
 * command.
996
 *
997
 * Any errors during command execution are reported to stderr.
998 999 1000
 */
static void
run_vacuum_command(PGconn *conn, const char *sql, bool echo,
1001
				   const char *table)
1002
{
Bruce Momjian's avatar
Bruce Momjian committed
1003
	bool		status;
1004

1005 1006
	if (echo)
		printf("%s\n", sql);
1007

1008
	status = PQsendQuery(conn, sql) == 1;
1009 1010

	if (!status)
1011 1012
	{
		if (table)
1013 1014
			pg_log_error("vacuuming of table \"%s\" in database \"%s\" failed: %s",
						 table, PQdb(conn), PQerrorMessage(conn));
1015
		else
1016 1017
			pg_log_error("vacuuming of database \"%s\" failed: %s",
						 PQdb(conn), PQerrorMessage(conn));
1018 1019 1020
	}
}

1021 1022 1023 1024 1025 1026 1027 1028 1029
static void
help(const char *progname)
{
	printf(_("%s cleans and analyzes a PostgreSQL database.\n\n"), progname);
	printf(_("Usage:\n"));
	printf(_("  %s [OPTION]... [DBNAME]\n"), progname);
	printf(_("\nOptions:\n"));
	printf(_("  -a, --all                       vacuum all databases\n"));
	printf(_("  -d, --dbname=DBNAME             database to vacuum\n"));
1030
	printf(_("      --disable-page-skipping     disable all page-skipping behavior\n"));
1031
	printf(_("  -e, --echo                      show the commands being sent to the server\n"));
1032
	printf(_("  -f, --full                      do full vacuuming\n"));
1033
	printf(_("  -F, --freeze                    freeze row transaction information\n"));
1034
	printf(_("      --force-index-cleanup       always remove index entries that point to dead tuples\n"));
1035
	printf(_("  -j, --jobs=NUM                  use this many concurrent connections to vacuum\n"));
1036 1037
	printf(_("      --min-mxid-age=MXID_AGE     minimum multixact ID age of tables to vacuum\n"));
	printf(_("      --min-xid-age=XID_AGE       minimum transaction ID age of tables to vacuum\n"));
1038
	printf(_("      --no-index-cleanup          don't remove index entries that point to dead tuples\n"));
1039
	printf(_("      --no-process-toast          skip the TOAST table associated with the table to vacuum\n"));
1040
	printf(_("      --no-truncate               don't truncate empty pages at the end of the table\n"));
1041
	printf(_("  -P, --parallel=PARALLEL_WORKERS use this many background workers for vacuum, if available\n"));
1042
	printf(_("  -q, --quiet                     don't write any messages\n"));
1043
	printf(_("      --skip-locked               skip relations that cannot be immediately locked\n"));
1044
	printf(_("  -t, --table='TABLE[(COLUMNS)]'  vacuum specific table(s) only\n"));
1045
	printf(_("  -v, --verbose                   write a lot of output\n"));
1046
	printf(_("  -V, --version                   output version information, then exit\n"));
1047
	printf(_("  -z, --analyze                   update optimizer statistics\n"));
1048
	printf(_("  -Z, --analyze-only              only update optimizer statistics; no vacuum\n"));
1049
	printf(_("      --analyze-in-stages         only update optimizer statistics, in multiple\n"
1050
			 "                                  stages for faster results; no vacuum\n"));
1051
	printf(_("  -?, --help                      show this help, then exit\n"));
1052 1053 1054 1055
	printf(_("\nConnection options:\n"));
	printf(_("  -h, --host=HOSTNAME       database server host or socket directory\n"));
	printf(_("  -p, --port=PORT           database server port\n"));
	printf(_("  -U, --username=USERNAME   user name to connect as\n"));
1056
	printf(_("  -w, --no-password         never prompt for password\n"));
1057
	printf(_("  -W, --password            force password prompt\n"));
1058
	printf(_("  --maintenance-db=DBNAME   alternate maintenance database\n"));
1059
	printf(_("\nRead the description of the SQL command VACUUM for details.\n"));
1060
	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
1061
	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
1062
}