v2-0004-Add-pg_export_stats-pg_import_stats.patch
text/x-patch
Filename: v2-0004-Add-pg_export_stats-pg_import_stats.patch
Type: text/x-patch
Part: 3
Message:
Re: Statistics Import and Export
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0004
Subject: Add pg_export_stats, pg_import_stats.
| File | + | − |
|---|---|---|
| src/bin/scripts/Makefile | 5 | 1 |
| src/bin/scripts/pg_export_stats.c | 946 | 0 |
| src/bin/scripts/pg_import_stats.c | 303 | 0 |
From da9b9a4012757a8bb08711d4479ce5ebb38e974d Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Tue, 31 Oct 2023 03:13:24 -0400
Subject: [PATCH v2 4/4] Add pg_export_stats, pg_import_stats.
pg_export_stats is used to export stats from databases as far back as
v10. The output is currently only to stdout and should be redirected to
a file in most use cases.
pg_import_stats is used to import stats to any version that has the
function pg_import_rel_stats().
---
src/bin/scripts/Makefile | 6 +-
src/bin/scripts/pg_export_stats.c | 946 ++++++++++++++++++++++++++++++
src/bin/scripts/pg_import_stats.c | 303 ++++++++++
3 files changed, 1254 insertions(+), 1 deletion(-)
create mode 100644 src/bin/scripts/pg_export_stats.c
create mode 100644 src/bin/scripts/pg_import_stats.c
diff --git a/src/bin/scripts/Makefile b/src/bin/scripts/Makefile
index a7a9d0fea5..da45fcfa6a 100644
--- a/src/bin/scripts/Makefile
+++ b/src/bin/scripts/Makefile
@@ -16,7 +16,7 @@ subdir = src/bin/scripts
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-PROGRAMS = createdb createuser dropdb dropuser clusterdb vacuumdb reindexdb pg_isready
+PROGRAMS = createdb createuser dropdb dropuser clusterdb vacuumdb reindexdb pg_isready pg_export_stats pg_import_stats
override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
@@ -31,6 +31,8 @@ clusterdb: clusterdb.o common.o $(WIN32RES) | submake-libpq submake-libpgport su
vacuumdb: vacuumdb.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
reindexdb: reindexdb.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
pg_isready: pg_isready.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
+pg_export_stats: pg_export_stats.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
+pg_import_stats: pg_import_stats.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
install: all installdirs
$(INSTALL_PROGRAM) createdb$(X) '$(DESTDIR)$(bindir)'/createdb$(X)
@@ -41,6 +43,8 @@ install: all installdirs
$(INSTALL_PROGRAM) vacuumdb$(X) '$(DESTDIR)$(bindir)'/vacuumdb$(X)
$(INSTALL_PROGRAM) reindexdb$(X) '$(DESTDIR)$(bindir)'/reindexdb$(X)
$(INSTALL_PROGRAM) pg_isready$(X) '$(DESTDIR)$(bindir)'/pg_isready$(X)
+ $(INSTALL_PROGRAM) pg_export_stats$(X) '$(DESTDIR)$(bindir)'/pg_export_stats$(X)
+ $(INSTALL_PROGRAM) pg_import_stats$(X) '$(DESTDIR)$(bindir)'/pg_import_stats$(X)
installdirs:
$(MKDIR_P) '$(DESTDIR)$(bindir)'
diff --git a/src/bin/scripts/pg_export_stats.c b/src/bin/scripts/pg_export_stats.c
new file mode 100644
index 0000000000..b9fc192e8f
--- /dev/null
+++ b/src/bin/scripts/pg_export_stats.c
@@ -0,0 +1,946 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_export_stats
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/scripts/pg_export_stats.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+#include "common.h"
+#include "common/logging.h"
+#include "fe_utils/cancel.h"
+#include "fe_utils/option_utils.h"
+#include "fe_utils/query_utils.h"
+#include "fe_utils/simple_list.h"
+#include "fe_utils/string_utils.h"
+
+static void help(const char *progname);
+
+/* view definition introduced in 17 */
+const char *export_query_v17 =
+ "SELECT schemaname, relname, server_version_num, n_tuples, "
+ "n_pages, stats FROM pg_statistic_export ";
+
+/* v15-v16 have the same stats layout, but lacks view definition */
+const char *export_query_v15 =
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " current_setting('server_version_num')::integer AS server_version_num, "
+ " r.reltuples::float4 AS n_tuples, "
+ " r.relpages::integer AS n_pages, "
+ " ( "
+ " WITH per_column_stats AS "
+ " ( "
+ " SELECT "
+ " s.stainherit, "
+ " a.attname, "
+ " jsonb_build_object( "
+ " 'stanullfrac', s.stanullfrac::text, "
+ " 'stawidth', s.stawidth::text, "
+ " 'stadistinct', s.stadistinct::text, "
+ " 'stakinds', "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " CASE kind.kind "
+ " WHEN 0 THEN 'TRIVIAL' "
+ " WHEN 1 THEN 'MCV' "
+ " WHEN 2 THEN 'HISTOGRAM' "
+ " WHEN 3 THEN 'CORRELATION' "
+ " WHEN 4 THEN 'MCELEM' "
+ " WHEN 5 THEN 'DECHIST' "
+ " WHEN 6 THEN 'RANGE_LENGTH_HISTOGRAM' "
+ " WHEN 7 THEN 'BOUNDS_HISTOGRAM' "
+ " END::text "
+ " ORDER BY kind.ord) "
+ " FROM unnest(ARRAY[s.stakind1, s.stakind2, "
+ " s.stakind3, stakind4, "
+ " s.stakind5]) "
+ " WITH ORDINALITY AS kind(kind, ord) "
+ " ), "
+ " 'stanumbers', "
+ " jsonb_build_array( "
+ " s.stanumbers1::text::text[], "
+ " s.stanumbers2::text::text[], "
+ " s.stanumbers3::text::text[], "
+ " s.stanumbers4::text::text[], "
+ " s.stanumbers5::text::text[]), "
+ " 'stavalues', "
+ " jsonb_build_array( "
+ " s.stavalues1::text::text[], "
+ " s.stavalues2::text::text[], "
+ " s.stavalues3::text::text[], "
+ " s.stavalues4::text::text[], "
+ " s.stavalues5::text::text[]) "
+ " ) AS stats "
+ " FROM pg_attribute AS a "
+ " JOIN pg_statistic AS s "
+ " ON s.starelid = a.attrelid "
+ " AND s.staattnum = a.attnum "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " AND has_column_privilege(a.attrelid, a.attnum, 'SELECT') "
+ " ), "
+ " attagg AS "
+ " ( "
+ " SELECT "
+ " pcs.stainherit, "
+ " jsonb_build_object( "
+ " 'columns', "
+ " jsonb_object_agg( "
+ " pcs.attname, "
+ " pcs.stats "
+ " ) "
+ " ) AS stats "
+ " FROM per_column_stats AS pcs "
+ " GROUP BY pcs.stainherit "
+ " ), "
+ " extended_object_stats AS "
+ " ( "
+ " SELECT "
+ " sd.stxdinherit, "
+ " e.stxname, "
+ " jsonb_build_object( "
+ " 'stxkinds', "
+ " to_jsonb(e.stxkind), "
+ " 'stxdndistinct', "
+ " ndist.stxdndistinct, "
+ " 'stxdndependencies', "
+ " ndep.stxdndependencies, "
+ " 'stxdmcv', "
+ " mcv.stxdmcv, "
+ " 'stxdexprs', "
+ " x.stdxdexprs "
+ " ) AS stats "
+ " FROM pg_statistic_ext AS e "
+ " JOIN pg_statistic_ext_data AS sd "
+ " ON sd.stxoid = e.oid "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'attnums', string_to_array(nd.attnums, ', '), "
+ " 'ndistinct', nd.ndistinct "
+ " ) "
+ " ORDER BY nd.ord "
+ " ) "
+ " FROM json_each_text(sd.stxdndistinct::text::json) "
+ " WITH ORDINALITY AS nd(attnums, ndistinct, ord) "
+ " ) AS ndist(stxdndistinct) ON sd.stxdndistinct IS NOT NULL "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'attnums', "
+ " string_to_array( replace(dep.attrs, ' => ', ', '), ', '), "
+ " 'degree', "
+ " dep.degree "
+ " ) "
+ " ORDER BY dep.ord "
+ " ) "
+ " FROM json_each_text(sd.stxddependencies::text::json) "
+ " WITH ORDINALITY AS dep(attrs, degree, ord) "
+ " ) AS ndep(stxdndependencies) ON sd.stxddependencies IS NOT NULL "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT NULL AS stxdmcv "
+ " ) AS mcv(stxdmcv) ON sd.stxdmcv IS NOT NULL "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'stanullfrac', s.stanullfrac::text, "
+ " 'stawidth', s.stawidth::text, "
+ " 'stadistinct', s.stadistinct::text, "
+ " 'stakinds', "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " CASE kind.kind "
+ " WHEN 0 THEN 'TRIVIAL' "
+ " WHEN 1 THEN 'MCV' "
+ " WHEN 2 THEN 'HISTOGRAM' "
+ " WHEN 3 THEN 'CORRELATION' "
+ " WHEN 4 THEN 'MCELEM' "
+ " WHEN 5 THEN 'DECHIST' "
+ " WHEN 6 THEN 'RANGE_LENGTH_HISTOGRAM' "
+ " WHEN 7 THEN 'BOUNDS_HISTOGRAM' "
+ " END::text "
+ " ORDER BY kind.ord) "
+ " FROM unnest(ARRAY[s.stakind1, s.stakind2, "
+ " s.stakind3, stakind4, "
+ " s.stakind5]) WITH ORDINALITY AS kind(kind, ord) "
+ " ), "
+ " 'stanumbers', "
+ " jsonb_build_array( "
+ " s.stanumbers1::text::text[], "
+ " s.stanumbers2::text::text[], "
+ " s.stanumbers3::text::text[], "
+ " s.stanumbers4::text::text[], "
+ " s.stanumbers5::text::text[]), "
+ " 'stavalues', "
+ " jsonb_build_array( "
+ " s.stavalues1::text::text[], "
+ " s.stavalues2::text::text[], "
+ " s.stavalues3::text::text[], "
+ " s.stavalues4::text::text[], "
+ " s.stavalues5::text::text[]) "
+ " ) "
+ " ORDER BY s.ordinality "
+ " ) "
+ " FROM unnest(sd.stxdexpr) WITH ORDINALITY AS s "
+ " ) AS x(stdxdexprs) ON sd.stxdexpr IS NOT NULL "
+ " WHERE e.stxrelid = r.oid "
+ " ), "
+ " extagg AS "
+ " ( "
+ " SELECT "
+ " eos.stxdinherit, "
+ " jsonb_build_object( "
+ " 'extended', "
+ " jsonb_object_agg( "
+ " eos.stxname, "
+ " eos.stats "
+ " ) "
+ " ) AS stats "
+ " FROM extended_object_stats AS eos "
+ " GROUP BY eos.stxdinherit "
+ " ) "
+ " SELECT "
+ " jsonb_object_agg( "
+ " CASE coalesce(a.stainherit, e.stxdinherit) "
+ " WHEN TRUE THEN 'inherited' "
+ " ELSE 'regular' "
+ " END, "
+ " coalesce(a.stats, '{}'::jsonb) || coalesce(e.stats, '{}'::jsonb) "
+ " ) "
+ " FROM attagg AS a "
+ " FULL OUTER JOIN extagg e ON a.stainherit = e.stxdinherit "
+ " ) AS stats "
+ "FROM pg_class AS r "
+ "JOIN pg_namespace AS n "
+ " ON n.oid = r.relnamespace "
+ "WHERE relkind IN ('r', 'm', 'f', 'p') "
+ "AND n.nspname NOT IN ('pg_catalog', 'information_schema')";
+
+/* v14 is like v15, but lacks stxdinherit on ext_data */
+const char *export_query_v14 =
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " current_setting('server_version_num')::integer AS server_version_num, "
+ " r.reltuples::float4 AS n_tuples, "
+ " r.relpages::integer AS n_pages, "
+ " ( "
+ " WITH per_column_stats AS "
+ " ( "
+ " SELECT "
+ " s.stainherit, "
+ " a.attname, "
+ " jsonb_build_object( "
+ " 'stanullfrac', s.stanullfrac::text, "
+ " 'stawidth', s.stawidth::text, "
+ " 'stadistinct', s.stadistinct::text, "
+ " 'stakinds', "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " CASE kind.kind "
+ " WHEN 0 THEN 'TRIVIAL' "
+ " WHEN 1 THEN 'MCV' "
+ " WHEN 2 THEN 'HISTOGRAM' "
+ " WHEN 3 THEN 'CORRELATION' "
+ " WHEN 4 THEN 'MCELEM' "
+ " WHEN 5 THEN 'DECHIST' "
+ " WHEN 6 THEN 'RANGE_LENGTH_HISTOGRAM' "
+ " WHEN 7 THEN 'BOUNDS_HISTOGRAM' "
+ " END::text "
+ " ORDER BY kind.ord) "
+ " FROM unnest(ARRAY[s.stakind1, s.stakind2, "
+ " s.stakind3, stakind4, "
+ " s.stakind5]) "
+ " WITH ORDINALITY AS kind(kind, ord) "
+ " ), "
+ " 'stanumbers', "
+ " jsonb_build_array( "
+ " s.stanumbers1::text::text[], "
+ " s.stanumbers2::text::text[], "
+ " s.stanumbers3::text::text[], "
+ " s.stanumbers4::text::text[], "
+ " s.stanumbers5::text::text[]), "
+ " 'stavalues', "
+ " jsonb_build_array( "
+ " s.stavalues1::text::text[], "
+ " s.stavalues2::text::text[], "
+ " s.stavalues3::text::text[], "
+ " s.stavalues4::text::text[], "
+ " s.stavalues5::text::text[]) "
+ " ) AS stats "
+ " FROM pg_attribute AS a "
+ " JOIN pg_statistic AS s "
+ " ON s.starelid = a.attrelid "
+ " AND s.staattnum = a.attnum "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " AND has_column_privilege(a.attrelid, a.attnum, 'SELECT') "
+ " ), "
+ " attagg AS "
+ " ( "
+ " SELECT "
+ " pcs.stainherit, "
+ " jsonb_build_object( "
+ " 'columns', "
+ " jsonb_object_agg( "
+ " pcs.attname, "
+ " pcs.stats "
+ " ) "
+ " ) AS stats "
+ " FROM per_column_stats AS pcs "
+ " GROUP BY pcs.stainherit "
+ " ), "
+ " extended_object_stats AS "
+ " ( "
+ " SELECT "
+ " false AS stxdinherit, "
+ " e.stxname, "
+ " jsonb_build_object( "
+ " 'stxkinds', "
+ " to_jsonb(e.stxkind), "
+ " 'stxdndistinct', "
+ " ndist.stxdndistinct, "
+ " 'stxdndependencies', "
+ " ndep.stxdndependencies, "
+ " 'stxdmcv', "
+ " mcv.stxdmcv, "
+ " 'stxdexprs', "
+ " x.stdxdexprs "
+ " ) AS stats "
+ " FROM pg_statistic_ext AS e "
+ " JOIN pg_statistic_ext_data AS sd "
+ " ON sd.stxoid = e.oid "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'attnums', string_to_array(nd.attnums, ', '), "
+ " 'ndistinct', nd.ndistinct "
+ " ) "
+ " ORDER BY nd.ord "
+ " ) "
+ " FROM json_each_text(sd.stxdndistinct::text::json) "
+ " WITH ORDINALITY AS nd(attnums, ndistinct, ord) "
+ " ) AS ndist(stxdndistinct) ON sd.stxdndistinct IS NOT NULL "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'attnums', "
+ " string_to_array( replace(dep.attrs, ' => ', ', '), ', '), "
+ " 'degree', "
+ " dep.degree "
+ " ) "
+ " ORDER BY dep.ord "
+ " ) "
+ " FROM json_each_text(sd.stxddependencies::text::json) "
+ " WITH ORDINALITY AS dep(attrs, degree, ord) "
+ " ) AS ndep(stxdndependencies) ON sd.stxddependencies IS NOT NULL "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT NULL AS stxdmcv "
+ " ) AS mcv(stxdmcv) ON sd.stxdmcv IS NOT NULL "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'stanullfrac', s.stanullfrac::text, "
+ " 'stawidth', s.stawidth::text, "
+ " 'stadistinct', s.stadistinct::text, "
+ " 'stakinds', "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " CASE kind.kind "
+ " WHEN 0 THEN 'TRIVIAL' "
+ " WHEN 1 THEN 'MCV' "
+ " WHEN 2 THEN 'HISTOGRAM' "
+ " WHEN 3 THEN 'CORRELATION' "
+ " WHEN 4 THEN 'MCELEM' "
+ " WHEN 5 THEN 'DECHIST' "
+ " WHEN 6 THEN 'RANGE_LENGTH_HISTOGRAM' "
+ " WHEN 7 THEN 'BOUNDS_HISTOGRAM' "
+ " END::text "
+ " ORDER BY kind.ord) "
+ " FROM unnest(ARRAY[s.stakind1, s.stakind2, "
+ " s.stakind3, stakind4, "
+ " s.stakind5]) WITH ORDINALITY AS kind(kind, ord) "
+ " ), "
+ " 'stanumbers', "
+ " jsonb_build_array( "
+ " s.stanumbers1::text::text[], "
+ " s.stanumbers2::text::text[], "
+ " s.stanumbers3::text::text[], "
+ " s.stanumbers4::text::text[], "
+ " s.stanumbers5::text::text[]), "
+ " 'stavalues', "
+ " jsonb_build_array( "
+ " s.stavalues1::text::text[], "
+ " s.stavalues2::text::text[], "
+ " s.stavalues3::text::text[], "
+ " s.stavalues4::text::text[], "
+ " s.stavalues5::text::text[]) "
+ " ) "
+ " ORDER BY s.ordinality "
+ " ) "
+ " FROM unnest(sd.stxdexpr) WITH ORDINALITY AS s "
+ " ) AS x(stdxdexprs) ON sd.stxdexpr IS NOT NULL "
+ " WHERE e.stxrelid = r.oid "
+ " ), "
+ " extagg AS "
+ " ( "
+ " SELECT "
+ " eos.stxdinherit, "
+ " jsonb_build_object( "
+ " 'extended', "
+ " jsonb_object_agg( "
+ " eos.stxname, "
+ " eos.stats "
+ " ) "
+ " ) AS stats "
+ " FROM extended_object_stats AS eos "
+ " GROUP BY eos.stxdinherit "
+ " ) "
+ " SELECT "
+ " jsonb_object_agg( "
+ " CASE coalesce(a.stainherit, e.stxdinherit) "
+ " WHEN TRUE THEN 'inherited' "
+ " ELSE 'regular' "
+ " END, "
+ " coalesce(a.stats, '{}'::jsonb) || coalesce(e.stats, '{}'::jsonb) "
+ " ) "
+ " FROM attagg AS a "
+ " FULL OUTER JOIN extagg e ON a.stainherit = e.stxdinherit "
+ " ) AS stats "
+ "FROM pg_class AS r "
+ "JOIN pg_namespace AS n "
+ " ON n.oid = r.relnamespace "
+ "WHERE relkind IN ('r', 'm', 'f', 'p') "
+ "AND n.nspname NOT IN ('pg_catalog', 'information_schema')";
+
+/* v12-v13 are like v14, but lacks stxdexpr on ext_data */
+const char *export_query_v12 =
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " current_setting('server_version_num')::integer AS server_version_num, "
+ " r.reltuples::float4 AS n_tuples, "
+ " r.relpages::integer AS n_pages, "
+ " ( "
+ " WITH per_column_stats AS "
+ " ( "
+ " SELECT "
+ " s.stainherit, "
+ " a.attname, "
+ " jsonb_build_object( "
+ " 'stanullfrac', s.stanullfrac::text, "
+ " 'stawidth', s.stawidth::text, "
+ " 'stadistinct', s.stadistinct::text, "
+ " 'stakinds', "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " CASE kind.kind "
+ " WHEN 0 THEN 'TRIVIAL' "
+ " WHEN 1 THEN 'MCV' "
+ " WHEN 2 THEN 'HISTOGRAM' "
+ " WHEN 3 THEN 'CORRELATION' "
+ " WHEN 4 THEN 'MCELEM' "
+ " WHEN 5 THEN 'DECHIST' "
+ " WHEN 6 THEN 'RANGE_LENGTH_HISTOGRAM' "
+ " WHEN 7 THEN 'BOUNDS_HISTOGRAM' "
+ " END::text "
+ " ORDER BY kind.ord) "
+ " FROM unnest(ARRAY[s.stakind1, s.stakind2, "
+ " s.stakind3, stakind4, "
+ " s.stakind5]) "
+ " WITH ORDINALITY AS kind(kind, ord) "
+ " ), "
+ " 'stanumbers', "
+ " jsonb_build_array( "
+ " s.stanumbers1::text::text[], "
+ " s.stanumbers2::text::text[], "
+ " s.stanumbers3::text::text[], "
+ " s.stanumbers4::text::text[], "
+ " s.stanumbers5::text::text[]), "
+ " 'stavalues', "
+ " jsonb_build_array( "
+ " s.stavalues1::text::text[], "
+ " s.stavalues2::text::text[], "
+ " s.stavalues3::text::text[], "
+ " s.stavalues4::text::text[], "
+ " s.stavalues5::text::text[]) "
+ " ) AS stats "
+ " FROM pg_attribute AS a "
+ " JOIN pg_statistic AS s "
+ " ON s.starelid = a.attrelid "
+ " AND s.staattnum = a.attnum "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " AND has_column_privilege(a.attrelid, a.attnum, 'SELECT') "
+ " ), "
+ " attagg AS "
+ " ( "
+ " SELECT "
+ " pcs.stainherit, "
+ " jsonb_build_object( "
+ " 'columns', "
+ " jsonb_object_agg( "
+ " pcs.attname, "
+ " pcs.stats "
+ " ) "
+ " ) AS stats "
+ " FROM per_column_stats AS pcs "
+ " GROUP BY pcs.stainherit "
+ " ), "
+ " extended_object_stats AS "
+ " ( "
+ " SELECT "
+ " false AS stxdinherit, "
+ " e.stxname, "
+ " jsonb_build_object( "
+ " 'stxkinds', "
+ " to_jsonb(e.stxkind), "
+ " 'stxdndistinct', "
+ " ndist.stxdndistinct, "
+ " 'stxdndependencies', "
+ " ndep.stxdndependencies, "
+ " 'stxdmcv', "
+ " mcv.stxdmcv, "
+ " ) AS stats "
+ " FROM pg_statistic_ext AS e "
+ " JOIN pg_statistic_ext_data AS sd "
+ " ON sd.stxoid = e.oid "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'attnums', string_to_array(nd.attnums, ', '), "
+ " 'ndistinct', nd.ndistinct "
+ " ) "
+ " ORDER BY nd.ord "
+ " ) "
+ " FROM json_each_text(sd.stxdndistinct::text::json) "
+ " WITH ORDINALITY AS nd(attnums, ndistinct, ord) "
+ " ) AS ndist(stxdndistinct) ON sd.stxdndistinct IS NOT NULL "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'attnums', "
+ " string_to_array( replace(dep.attrs, ' => ', ', '), ', '), "
+ " 'degree', "
+ " dep.degree "
+ " ) "
+ " ORDER BY dep.ord "
+ " ) "
+ " FROM json_each_text(sd.stxddependencies::text::json) "
+ " WITH ORDINALITY AS dep(attrs, degree, ord) "
+ " ) AS ndep(stxdndependencies) ON sd.stxddependencies IS NOT NULL "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT NULL AS stxdmcv "
+ " ) AS mcv(stxdmcv) ON sd.stxdmcv IS NOT NULL "
+ " WHERE e.stxrelid = r.oid "
+ " ), "
+ " extagg AS "
+ " ( "
+ " SELECT "
+ " eos.stxdinherit, "
+ " jsonb_build_object( "
+ " 'extended', "
+ " jsonb_object_agg( "
+ " eos.stxname, "
+ " eos.stats "
+ " ) "
+ " ) AS stats "
+ " FROM extended_object_stats AS eos "
+ " GROUP BY eos.stxdinherit "
+ " ) "
+ " SELECT "
+ " jsonb_object_agg( "
+ " CASE coalesce(a.stainherit, e.stxdinherit) "
+ " WHEN TRUE THEN 'inherited' "
+ " ELSE 'regular' "
+ " END, "
+ " coalesce(a.stats, '{}'::jsonb) || coalesce(e.stats, '{}'::jsonb) "
+ " ) "
+ " FROM attagg AS a "
+ " FULL OUTER JOIN extagg e ON a.stainherit = e.stxdinherit "
+ " ) AS stats "
+ "FROM pg_class AS r "
+ "JOIN pg_namespace AS n "
+ " ON n.oid = r.relnamespace "
+ "WHERE relkind IN ('r', 'm', 'f', 'p') "
+ "AND n.nspname NOT IN ('pg_catalog', 'information_schema')";
+
+/* v10-v11 are like v12, but ext_data is gone and ndistinct and dependencies are on ext */
+const char *export_query_v10 =
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " current_setting('server_version_num')::integer AS server_version_num, "
+ " r.reltuples::float4 AS n_tuples, "
+ " r.relpages::integer AS n_pages, "
+ " ( "
+ " WITH per_column_stats AS "
+ " ( "
+ " SELECT "
+ " s.stainherit, "
+ " a.attname, "
+ " jsonb_build_object( "
+ " 'stanullfrac', s.stanullfrac::text, "
+ " 'stawidth', s.stawidth::text, "
+ " 'stadistinct', s.stadistinct::text, "
+ " 'stakinds', "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " CASE kind.kind "
+ " WHEN 0 THEN 'TRIVIAL' "
+ " WHEN 1 THEN 'MCV' "
+ " WHEN 2 THEN 'HISTOGRAM' "
+ " WHEN 3 THEN 'CORRELATION' "
+ " WHEN 4 THEN 'MCELEM' "
+ " WHEN 5 THEN 'DECHIST' "
+ " WHEN 6 THEN 'RANGE_LENGTH_HISTOGRAM' "
+ " WHEN 7 THEN 'BOUNDS_HISTOGRAM' "
+ " END::text "
+ " ORDER BY kind.ord) "
+ " FROM unnest(ARRAY[s.stakind1, s.stakind2, "
+ " s.stakind3, stakind4, "
+ " s.stakind5]) "
+ " WITH ORDINALITY AS kind(kind, ord) "
+ " ), "
+ " 'stanumbers', "
+ " jsonb_build_array( "
+ " s.stanumbers1::text::text[], "
+ " s.stanumbers2::text::text[], "
+ " s.stanumbers3::text::text[], "
+ " s.stanumbers4::text::text[], "
+ " s.stanumbers5::text::text[]), "
+ " 'stavalues', "
+ " jsonb_build_array( "
+ " s.stavalues1::text::text[], "
+ " s.stavalues2::text::text[], "
+ " s.stavalues3::text::text[], "
+ " s.stavalues4::text::text[], "
+ " s.stavalues5::text::text[]) "
+ " ) AS stats "
+ " FROM pg_attribute AS a "
+ " JOIN pg_statistic AS s "
+ " ON s.starelid = a.attrelid "
+ " AND s.staattnum = a.attnum "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " AND has_column_privilege(a.attrelid, a.attnum, 'SELECT') "
+ " ), "
+ " attagg AS "
+ " ( "
+ " SELECT "
+ " pcs.stainherit, "
+ " jsonb_build_object( "
+ " 'columns', "
+ " jsonb_object_agg( "
+ " pcs.attname, "
+ " pcs.stats "
+ " ) "
+ " ) AS stats "
+ " FROM per_column_stats AS pcs "
+ " GROUP BY pcs.stainherit "
+ " ), "
+ " extended_object_stats AS "
+ " ( "
+ " SELECT "
+ " false AS stxdinherit, "
+ " e.stxname, "
+ " jsonb_build_object( "
+ " 'stxkinds', "
+ " to_jsonb(e.stxkind), "
+ " 'stxdndistinct', "
+ " ndist.stxdndistinct, "
+ " 'stxdndependencies', "
+ " ndep.stxdndependencies, "
+ " ) AS stats "
+ " FROM pg_statistic_ext AS e "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'attnums', string_to_array(nd.attnums, ', '), "
+ " 'ndistinct', nd.ndistinct "
+ " ) "
+ " ORDER BY nd.ord "
+ " ) "
+ " FROM json_each_text(e.stxndistinct::text::json) "
+ " WITH ORDINALITY AS nd(attnums, ndistinct, ord) "
+ " ) AS ndist(stxdndistinct) ON e.stxndistinct IS NOT NULL "
+ " LEFT JOIN LATERAL "
+ " ( "
+ " SELECT "
+ " jsonb_agg( "
+ " jsonb_build_object( "
+ " 'attnums', "
+ " string_to_array( replace(dep.attrs, ' => ', ', '), ', '), "
+ " 'degree', "
+ " dep.degree "
+ " ) "
+ " ORDER BY dep.ord "
+ " ) "
+ " FROM json_each_text(.stxdependencies::text::json) "
+ " WITH ORDINALITY AS dep(attrs, degree, ord) "
+ " ) AS ndep(stxdndependencies) ON e.stxdependencies IS NOT NULL "
+ " WHERE e.stxrelid = r.oid "
+ " ), "
+ " extagg AS "
+ " ( "
+ " SELECT "
+ " eos.stxdinherit, "
+ " jsonb_build_object( "
+ " 'extended', "
+ " jsonb_object_agg( "
+ " eos.stxname, "
+ " eos.stats "
+ " ) "
+ " ) AS stats "
+ " FROM extended_object_stats AS eos "
+ " GROUP BY eos.stxdinherit "
+ " ) "
+ " SELECT "
+ " jsonb_object_agg( "
+ " CASE coalesce(a.stainherit, e.stxdinherit) "
+ " WHEN TRUE THEN 'inherited' "
+ " ELSE 'regular' "
+ " END, "
+ " coalesce(a.stats, '{}'::jsonb) || coalesce(e.stats, '{}'::jsonb) "
+ " ) "
+ " FROM attagg AS a "
+ " FULL OUTER JOIN extagg e ON a.stainherit = e.stxdinherit "
+ " ) AS stats "
+ "FROM pg_class AS r "
+ "JOIN pg_namespace AS n "
+ " ON n.oid = r.relnamespace "
+ "WHERE relkind IN ('r', 'm', 'f', 'p') "
+ "AND n.nspname NOT IN ('pg_catalog', 'information_schema')";
+
+
+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'},
+ {"no-password", no_argument, NULL, 'w'},
+ {"password", no_argument, NULL, 'W'},
+ {"echo", no_argument, NULL, 'e'},
+ {"dbname", required_argument, NULL, 'd'},
+ {NULL, 0, NULL, 0}
+ };
+
+ const char *progname;
+ int optindex;
+ int c;
+
+ const char *dbname = NULL;
+ char *host = NULL;
+ char *port = NULL;
+ char *username = NULL;
+ enum trivalue prompt_password = TRI_DEFAULT;
+ ConnParams cparams;
+ bool echo = false;
+
+ PQExpBufferData sql;
+
+ PGconn *conn;
+
+ FILE *copystream = stdout;
+
+ PGresult *result;
+
+ ExecStatusType result_status;
+
+ char *buf;
+ int ret;
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
+
+ handle_help_version_opts(argc, argv, "clusterdb", help);
+
+ while ((c = getopt_long(argc, argv, "d:eh:p:U:wW", long_options, &optindex)) != -1)
+ {
+ switch (c)
+ {
+ case 'd':
+ dbname = pg_strdup(optarg);
+ break;
+ case 'e':
+ echo = true;
+ break;
+ case 'h':
+ host = pg_strdup(optarg);
+ break;
+ case 'p':
+ port = pg_strdup(optarg);
+ break;
+ case 'U':
+ username = pg_strdup(optarg);
+ break;
+ case 'w':
+ prompt_password = TRI_NO;
+ break;
+ case 'W':
+ prompt_password = TRI_YES;
+ break;
+ default:
+ /* getopt_long already emitted a complaint */
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+ }
+
+ /*
+ * Non-option argument specifies database name as long as it wasn't
+ * already specified with -d / --dbname
+ */
+ if (optind < argc && dbname == NULL)
+ {
+ dbname = argv[optind];
+ optind++;
+ }
+
+ if (optind < argc)
+ {
+ pg_log_error("too many command-line arguments (first is \"%s\")",
+ argv[optind]);
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+
+ /* 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;
+
+ setup_cancel_handler(NULL);
+
+ if (dbname == NULL)
+ {
+ if (getenv("PGDATABASE"))
+ dbname = getenv("PGDATABASE");
+ else if (getenv("PGUSER"))
+ dbname = getenv("PGUSER");
+ else
+ dbname = get_user_name_or_exit(progname);
+ }
+
+ cparams.dbname = dbname;
+
+ conn = connectDatabase(&cparams, progname, echo, false, true);
+
+ initPQExpBuffer(&sql);
+
+ appendPQExpBufferStr(&sql, "COPY (");
+
+ if (PQserverVersion(conn) >= 170000)
+ appendPQExpBufferStr(&sql, export_query_v17);
+ else if (PQserverVersion(conn) >= 150000)
+ appendPQExpBufferStr(&sql, export_query_v15);
+ else if (PQserverVersion(conn) >= 140000)
+ appendPQExpBufferStr(&sql, export_query_v14);
+ else if (PQserverVersion(conn) >= 120000)
+ appendPQExpBufferStr(&sql, export_query_v12);
+ else if (PQserverVersion(conn) >= 100000)
+ appendPQExpBufferStr(&sql, export_query_v10);
+ else
+ pg_fatal("exporting statistics from databases prior to version 10 not supported");
+
+ appendPQExpBufferStr(&sql, ") TO STDOUT");
+
+ result = PQexec(conn, sql.data);
+ result_status = PQresultStatus(result);
+
+ if (result_status != PGRES_COPY_OUT)
+ pg_fatal("malformed copy command");
+
+ for (;;)
+ {
+ ret = PQgetCopyData(conn, &buf, 0);
+
+ if (ret < 0)
+ break; /* done or server/connection error */
+
+ if (buf)
+ {
+ if (copystream && fwrite(buf, 1, ret, copystream) != ret)
+ pg_fatal("could not write COPY data: %m");
+ PQfreemem(buf);
+ }
+ }
+
+ if (copystream && fflush(copystream))
+ pg_fatal("could not write COPY data: %m");
+
+ if (ret == -2)
+ pg_fatal("COPY data transfer failed: %s", PQerrorMessage(conn));
+
+ PQfinish(conn);
+ termPQExpBuffer(&sql);
+ exit(0);
+}
+
+
+static void
+help(const char *progname)
+{
+ printf(_("%s clusters all previously clustered tables in a database.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
+ printf(_("\nOptions:\n"));
+ printf(_(" -d, --dbname=DBNAME database to cluster\n"));
+ printf(_(" -e, --echo show the commands being sent to the server\n"));
+ printf(_(" -t, --table=TABLE cluster specific table(s) only\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+ 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"));
+ printf(_(" -w, --no-password never prompt for password\n"));
+ printf(_(" -W, --password force password prompt\n"));
+ printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
+ printf(_("\nRead the description of the SQL command CLUSTER for details.\n"));
+ printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
diff --git a/src/bin/scripts/pg_import_stats.c b/src/bin/scripts/pg_import_stats.c
new file mode 100644
index 0000000000..cfcadff769
--- /dev/null
+++ b/src/bin/scripts/pg_import_stats.c
@@ -0,0 +1,303 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_import_stats
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/scripts/pg_import_stats.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+#include "common.h"
+#include "common/logging.h"
+#include "fe_utils/cancel.h"
+#include "fe_utils/option_utils.h"
+#include "fe_utils/query_utils.h"
+#include "fe_utils/simple_list.h"
+#include "fe_utils/string_utils.h"
+
+#define COPY_BUF_LEN 8192
+
+static void help(const char *progname);
+
+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'},
+ {"no-password", no_argument, NULL, 'w'},
+ {"password", no_argument, NULL, 'W'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"dbname", required_argument, NULL, 'd'},
+ {NULL, 0, NULL, 0}
+ };
+
+ const char *progname;
+ int optindex;
+ int c;
+
+ const char *dbname = NULL;
+ char *host = NULL;
+ char *port = NULL;
+ char *username = NULL;
+ enum trivalue prompt_password = TRI_DEFAULT;
+ ConnParams cparams;
+ bool quiet = false;
+
+ PGconn *conn;
+
+ FILE *copysrc= stdin;
+
+ PGresult *result;
+
+ int i;
+ int numtables;
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
+
+ handle_help_version_opts(argc, argv, "clusterdb", help);
+
+ while ((c = getopt_long(argc, argv, "d:h:p:qU:wW", long_options, &optindex)) != -1)
+ {
+ switch (c)
+ {
+ case 'd':
+ dbname = pg_strdup(optarg);
+ break;
+ case 'h':
+ host = pg_strdup(optarg);
+ break;
+ case 'p':
+ port = pg_strdup(optarg);
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'U':
+ username = pg_strdup(optarg);
+ break;
+ case 'w':
+ prompt_password = TRI_NO;
+ break;
+ case 'W':
+ prompt_password = TRI_YES;
+ break;
+ default:
+ /* getopt_long already emitted a complaint */
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+ }
+
+ /*
+ * Non-option argument specifies database name as long as it wasn't
+ * already specified with -d / --dbname
+ */
+ if (optind < argc && dbname == NULL)
+ {
+ dbname = argv[optind];
+ optind++;
+ }
+
+ if (optind < argc)
+ {
+ pg_log_error("too many command-line arguments (first is \"%s\")",
+ argv[optind]);
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+
+ /* 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;
+
+ setup_cancel_handler(NULL);
+
+ if (dbname == NULL)
+ {
+ if (getenv("PGDATABASE"))
+ dbname = getenv("PGDATABASE");
+ else if (getenv("PGUSER"))
+ dbname = getenv("PGUSER");
+ else
+ dbname = get_user_name_or_exit(progname);
+ }
+
+ cparams.dbname = dbname;
+
+ conn = connectDatabase(&cparams, progname, false, false, true);
+
+ /* open file */
+
+ /* iterate over records */
+
+
+ result = PQexec(conn,
+ "CREATE TEMPORARY TABLE import_stats ( "
+ "id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, "
+ "schemaname text, relname text, server_version_num integer, "
+ "n_tuples float4, n_pages integer, stats jsonb )");
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("could not create temporary file: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+
+ result = PQexec(conn,
+ "COPY import_stats(schemaname, relname, server_version_num, n_tuples, "
+ "n_pages, stats) FROM STDIN");
+
+ if (PQresultStatus(result) != PGRES_COPY_IN)
+ pg_fatal("error copying data to import_stats: %s", PQerrorMessage(conn));
+
+ for (;;)
+ {
+ char copybuf[COPY_BUF_LEN];
+
+ int numread = fread(copybuf, 1, COPY_BUF_LEN, copysrc);
+
+ if (ferror(copysrc))
+ pg_fatal("error reading from source");
+
+ if (numread == 0)
+ break;
+
+ if (PQputCopyData(conn, copybuf, numread) == -1)
+ pg_fatal("eror during copy: %s", PQerrorMessage(conn));
+ }
+
+ if (PQputCopyEnd(conn, NULL) == -1)
+ pg_fatal("eror during copy: %s", PQerrorMessage(conn));
+ fclose(copysrc);
+
+ result = PQgetResult(conn);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("error copying data to import_stats: %s", PQerrorMessage(conn));
+
+ numtables = atol(PQcmdTuples(result));
+
+ PQclear(result);
+
+ result = PQprepare(conn, "import",
+ "SELECT pg_import_rel_stats(c.oid, s.server_version_num, "
+ " s.n_tuples, s.n_pages, s.stats) as import_result "
+ "FROM import_stats AS s "
+ "JOIN pg_namespace AS n ON n.nspname = s.schemaname "
+ "JOIN pg_class AS c ON c.relnamespace = n.oid "
+ " AND c.relname = s.relname "
+ "WHERE s.id = $1::bigint ",
+ 1, NULL);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("error in PREPARE: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+
+ if (!quiet)
+ {
+ result = PQprepare(conn, "echo",
+ "SELECT s.schemaname, s.relname "
+ "FROM import_stats AS s "
+ "WHERE s.id = $1::bigint ",
+ 1, NULL);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("error in PREPARE: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+ }
+
+ for (i = 1; i <= numtables; i++)
+ {
+ char istr[32];
+ char *schema = NULL;
+ char *table = NULL;
+
+ const char *const values[] = {istr};
+
+ snprintf(istr, 32, "%d", i);
+
+ if (!quiet)
+ {
+ result = PQexecPrepared(conn, "echo", 1, values, NULL, NULL, 0);
+ schema = pg_strdup(PQgetvalue(result, 0, 0));
+ table = pg_strdup(PQgetvalue(result, 0, 1));
+ }
+
+ PQclear(result);
+
+ result = PQexecPrepared(conn, "import", 1, values, NULL, NULL, 0);
+
+ if (quiet)
+ {
+ PQclear(result);
+ continue;
+ }
+
+ if (PQresultStatus(result) == PGRES_TUPLES_OK)
+ {
+ int rows = PQntuples(result);
+
+ if (rows == 1)
+ {
+ char *retval = PQgetvalue(result, 0, 0);
+ if (*retval == 't')
+ printf("%s.%s: imported\n", schema, table);
+ else
+ printf("%s.%s: failed\n", schema, table);
+ }
+ else if (rows == 0)
+ printf("%s.%s: not found\n", schema, table);
+ else
+ pg_fatal("import function must return 0 or 1 rows");
+ }
+ else
+ printf("%s.%s: error: %s\n", schema, table, PQerrorMessage(conn));
+
+ if (schema != NULL)
+ pfree(schema);
+
+ if (table != NULL)
+ pfree(table);
+
+ PQclear(result);
+ }
+
+ exit(0);
+}
+
+
+static void
+help(const char *progname)
+{
+ printf(_("%s clusters all previously clustered tables in a database.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
+ printf(_("\nOptions:\n"));
+ printf(_(" -d, --dbname=DBNAME database to cluster\n"));
+ printf(_(" -q, --quiet don't write any messages\n"));
+ printf(_(" -t, --table=TABLE cluster specific table(s) only\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+ 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"));
+ printf(_(" -w, --no-password never prompt for password\n"));
+ printf(_(" -W, --password force password prompt\n"));
+ printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
+ printf(_("\nRead the description of the SQL command CLUSTER for details.\n"));
+ printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
--
2.41.0