v2-0013-Replace-statistics-import-SPI-calls-with-new-impo.patch
application/octet-stream
Filename: v2-0013-Replace-statistics-import-SPI-calls-with-new-impo.patch
Type: application/octet-stream
Part: 11
Patch
Format: format-patch
Series: patch v2-0013
Subject: Replace statistics import SPI calls with new import functions.
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/expected/postgres_fdw.out | 52 | 0 |
| contrib/postgres_fdw/postgres_fdw.c | 93 | 301 |
| contrib/postgres_fdw/sql/postgres_fdw.sql | 40 | 0 |
From 188b5f7d80cfa5af8affd82f6579591d3f6d43dc Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Mon, 29 Jun 2026 02:15:28 -0500
Subject: [PATCH v2 13/13] Replace statistics import SPI calls with new import
functions.
This patch removes the SPI calls in postgres_fdw.c that were related to
relation statistics and attribute statistics. Instead, the functions
relation_statistics_update() and attribute_statistics_update() have been
create specifically for this purpose.
---
.../postgres_fdw/expected/postgres_fdw.out | 52 +++
contrib/postgres_fdw/postgres_fdw.c | 394 +++++-------------
contrib/postgres_fdw/sql/postgres_fdw.sql | 40 ++
3 files changed, 185 insertions(+), 301 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0805c56cb1b..80b927d7f0b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13027,6 +13027,58 @@ ANALYZE dtest_table;
ANALYZE VERBOSE dtest_ftable; -- should work
INFO: importing statistics for foreign table "public.dtest_ftable"
INFO: finished importing statistics for foreign table "public.dtest_ftable"
+-- dtest_ftables stats should now exactly match dtest_table
+-- compare the rowcounts, should get 0 rows back
+SELECT COUNT(*) FROM pg_stats
+WHERE schemaname = 'public' AND tablename = 'dtest_table'
+EXCEPT
+SELECT COUNT(*) FROM pg_stats
+WHERE schemaname = 'public' AND tablename = 'dtest_ftable';
+ count
+-------
+(0 rows)
+
+-- compare values, should match
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_table'::regclass
+EXCEPT
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_ftable'::regclass;
+ relpages | reltuples
+----------+-----------
+(0 rows)
+
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_ftable'::regclass
+EXCEPT
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_table'::regclass;
+ relpages | reltuples
+----------+-----------
+(0 rows)
+
+-- test only a few stats columns common to integer types
+SELECT attname, inherited, null_frac, avg_width, n_distinct,
+ most_common_vals::text as mcv, most_common_freqs,
+ histogram_bounds::text as hb, correlation
+FROM pg_stats
+WHERE schemaname = 'public'
+AND tablename = 'dtest_table'
+EXCEPT
+SELECT attname, inherited, null_frac, avg_width, n_distinct,
+ most_common_vals::text as mcv, most_common_freqs,
+ histogram_bounds::text as hb, correlation
+FROM pg_stats
+WHERE schemaname = 'public'
+AND tablename = 'dtest_ftable';
+ attname | inherited | null_frac | avg_width | n_distinct | mcv | most_common_freqs | hb | correlation
+---------+-----------+-----------+-----------+------------+-----+-------------------+----+-------------
+(0 rows)
+
-- cleanup
DROP FOREIGN TABLE simport_ftable;
DROP FOREIGN TABLE simport_fview;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6dbae583ecc..677a821eb10 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -24,7 +24,6 @@
#include "commands/vacuum.h"
#include "executor/execAsync.h"
#include "executor/instrument.h"
-#include "executor/spi.h"
#include "foreign/fdwapi.h"
#include "funcapi.h"
#include "miscadmin.h"
@@ -43,6 +42,8 @@
#include "parser/parsetree.h"
#include "postgres_fdw.h"
#include "statistics/statistics.h"
+#include "statistics/attribute_stats.h"
+#include "statistics/relation_stats.h"
#include "storage/latch.h"
#include "utils/builtins.h"
#include "utils/float.h"
@@ -341,160 +342,30 @@ typedef struct
/* Column order in relation stats query */
enum RelStatsColumns
{
- RELSTATS_RELPAGES = 0,
- RELSTATS_RELTUPLES,
- RELSTATS_RELKIND,
- RELSTATS_NUM_FIELDS,
+ RELCOL_RELPAGES = 0,
+ RELCOL_RELTUPLES,
+ RELCOL_RELKIND,
+ RELCOL_NUM_FIELDS,
};
/* Column order in attribute stats query */
enum AttStatsColumns
{
- ATTSTATS_ATTNAME = 0,
- ATTSTATS_NULL_FRAC,
- ATTSTATS_AVG_WIDTH,
- ATTSTATS_N_DISTINCT,
- ATTSTATS_MOST_COMMON_VALS,
- ATTSTATS_MOST_COMMON_FREQS,
- ATTSTATS_HISTOGRAM_BOUNDS,
- ATTSTATS_CORRELATION,
- ATTSTATS_MOST_COMMON_ELEMS,
- ATTSTATS_MOST_COMMON_ELEM_FREQS,
- ATTSTATS_ELEM_COUNT_HISTOGRAM,
- ATTSTATS_RANGE_LENGTH_HISTOGRAM,
- ATTSTATS_RANGE_EMPTY_FRAC,
- ATTSTATS_RANGE_BOUNDS_HISTOGRAM,
- ATTSTATS_NUM_FIELDS,
-};
-
-/* Relation stats import query */
-static const char *relimport_sql =
-"SELECT pg_catalog.pg_restore_relation_stats(\n"
-"\t'version', $1,\n"
-"\t'schemaname', $2,\n"
-"\t'relname', $3,\n"
-"\t'relpages', $4::integer,\n"
-"\t'reltuples', $5::real)";
-
-/* Argument order in relation stats import query */
-enum RelImportSqlArgs
-{
- RELIMPORT_SQL_VERSION = 0,
- RELIMPORT_SQL_SCHEMANAME,
- RELIMPORT_SQL_RELNAME,
- RELIMPORT_SQL_RELPAGES,
- RELIMPORT_SQL_RELTUPLES,
- RELIMPORT_SQL_NUM_FIELDS
-};
-
-/* Argument types in relation stats import query */
-static const Oid relimport_argtypes[RELIMPORT_SQL_NUM_FIELDS] =
-{
- INT4OID, TEXTOID, TEXTOID, TEXTOID,
- TEXTOID,
-};
-
-/* Attribute stats import query */
-static const char *attimport_sql =
-"SELECT pg_catalog.pg_restore_attribute_stats(\n"
-"\t'version', $1,\n"
-"\t'schemaname', $2,\n"
-"\t'relname', $3,\n"
-"\t'attnum', $4,\n"
-"\t'inherited', false::boolean,\n"
-"\t'null_frac', $5::real,\n"
-"\t'avg_width', $6::integer,\n"
-"\t'n_distinct', $7::real,\n"
-"\t'most_common_vals', $8,\n"
-"\t'most_common_freqs', $9::real[],\n"
-"\t'histogram_bounds', $10,\n"
-"\t'correlation', $11::real,\n"
-"\t'most_common_elems', $12,\n"
-"\t'most_common_elem_freqs', $13::real[],\n"
-"\t'elem_count_histogram', $14::real[],\n"
-"\t'range_length_histogram', $15,\n"
-"\t'range_empty_frac', $16::real,\n"
-"\t'range_bounds_histogram', $17)";
-
-/* Argument order in attribute stats import query */
-enum AttImportSqlArgs
-{
- ATTIMPORT_SQL_VERSION = 0,
- ATTIMPORT_SQL_SCHEMANAME,
- ATTIMPORT_SQL_RELNAME,
- ATTIMPORT_SQL_ATTNUM,
- ATTIMPORT_SQL_NULL_FRAC,
- ATTIMPORT_SQL_AVG_WIDTH,
- ATTIMPORT_SQL_N_DISTINCT,
- ATTIMPORT_SQL_MOST_COMMON_VALS,
- ATTIMPORT_SQL_MOST_COMMON_FREQS,
- ATTIMPORT_SQL_HISTOGRAM_BOUNDS,
- ATTIMPORT_SQL_CORRELATION,
- ATTIMPORT_SQL_MOST_COMMON_ELEMS,
- ATTIMPORT_SQL_MOST_COMMON_ELEM_FREQS,
- ATTIMPORT_SQL_ELEM_COUNT_HISTOGRAM,
- ATTIMPORT_SQL_RANGE_LENGTH_HISTOGRAM,
- ATTIMPORT_SQL_RANGE_EMPTY_FRAC,
- ATTIMPORT_SQL_RANGE_BOUNDS_HISTOGRAM,
- ATTIMPORT_SQL_NUM_FIELDS
-};
-
-/* Argument types in attribute stats import query */
-static const Oid attimport_argtypes[ATTIMPORT_SQL_NUM_FIELDS] =
-{
- INT4OID, TEXTOID, TEXTOID, INT2OID,
- TEXTOID, TEXTOID, TEXTOID, TEXTOID,
- TEXTOID, TEXTOID, TEXTOID, TEXTOID,
- TEXTOID, TEXTOID, TEXTOID, TEXTOID,
- TEXTOID,
-};
-
-/*
- * The mapping of attribute stats query columns to the positional arguments in
- * the prepared pg_restore_attribute_stats() statement.
- */
-typedef struct
-{
- enum AttStatsColumns res_field;
- enum AttImportSqlArgs arg_num;
-} AttrResultArgMap;
-
-#define NUM_MAPPED_ATTIMPORT_ARGS 13
-
-static const AttrResultArgMap attr_result_arg_map[NUM_MAPPED_ATTIMPORT_ARGS] =
-{
- {ATTSTATS_NULL_FRAC, ATTIMPORT_SQL_NULL_FRAC},
- {ATTSTATS_AVG_WIDTH, ATTIMPORT_SQL_AVG_WIDTH},
- {ATTSTATS_N_DISTINCT, ATTIMPORT_SQL_N_DISTINCT},
- {ATTSTATS_MOST_COMMON_VALS, ATTIMPORT_SQL_MOST_COMMON_VALS},
- {ATTSTATS_MOST_COMMON_FREQS, ATTIMPORT_SQL_MOST_COMMON_FREQS},
- {ATTSTATS_HISTOGRAM_BOUNDS, ATTIMPORT_SQL_HISTOGRAM_BOUNDS},
- {ATTSTATS_CORRELATION, ATTIMPORT_SQL_CORRELATION},
- {ATTSTATS_MOST_COMMON_ELEMS, ATTIMPORT_SQL_MOST_COMMON_ELEMS},
- {ATTSTATS_MOST_COMMON_ELEM_FREQS, ATTIMPORT_SQL_MOST_COMMON_ELEM_FREQS},
- {ATTSTATS_ELEM_COUNT_HISTOGRAM, ATTIMPORT_SQL_ELEM_COUNT_HISTOGRAM},
- {ATTSTATS_RANGE_LENGTH_HISTOGRAM, ATTIMPORT_SQL_RANGE_LENGTH_HISTOGRAM},
- {ATTSTATS_RANGE_EMPTY_FRAC, ATTIMPORT_SQL_RANGE_EMPTY_FRAC},
- {ATTSTATS_RANGE_BOUNDS_HISTOGRAM, ATTIMPORT_SQL_RANGE_BOUNDS_HISTOGRAM},
-};
-
-/* Attribute stats clear query */
-static const char *attclear_sql =
-"SELECT pg_catalog.pg_clear_attribute_stats($1, $2, $3, false)";
-
-/* Argument order in attribute stats clear query */
-enum AttClearSqlArgs
-{
- ATTCLEAR_SQL_SCHEMANAME = 0,
- ATTCLEAR_SQL_RELNAME,
- ATTCLEAR_SQL_ATTNAME,
- ATTCLEAR_SQL_NUM_FIELDS
-};
-
-/* Argument types in attribute stats clear query */
-static const Oid attclear_argtypes[ATTCLEAR_SQL_NUM_FIELDS] =
-{
- TEXTOID, TEXTOID, TEXTOID,
+ ATTCOL_ATTNAME = 0,
+ ATTCOL_NULL_FRAC,
+ ATTCOL_AVG_WIDTH,
+ ATTCOL_N_DISTINCT,
+ ATTCOL_MOST_COMMON_VALS,
+ ATTCOL_MOST_COMMON_FREQS,
+ ATTCOL_HISTOGRAM_BOUNDS,
+ ATTCOL_CORRELATION,
+ ATTCOL_MOST_COMMON_ELEMS,
+ ATTCOL_MOST_COMMON_ELEM_FREQS,
+ ATTCOL_ELEM_COUNT_HISTOGRAM,
+ ATTCOL_RANGE_LENGTH_HISTOGRAM,
+ ATTCOL_RANGE_EMPTY_FRAC,
+ ATTCOL_RANGE_BOUNDS_HISTOGRAM,
+ ATTCOL_NUM_FIELDS,
};
/*
@@ -714,14 +585,12 @@ static bool match_attrmap(PGresult *res,
const char *remote_relname,
int attrcnt,
RemoteAttributeMapping *remattrmap);
-static bool import_fetched_statistics(const char *schemaname,
+static bool import_fetched_statistics(Relation relation,
+ const char *schemaname,
const char *relname,
int attrcnt,
const RemoteAttributeMapping *remattrmap,
RemoteStatsResults *remstats);
-static void map_field_to_arg(PGresult *res, int row, int field,
- int arg, Datum *values, char *nulls);
-static bool import_spi_query_ok(void);
static void produce_tuple_asynchronously(AsyncRequest *areq, bool fetch);
static void fetch_more_data_begin(AsyncRequest *areq);
static void complete_pending_request(AsyncRequest *areq);
@@ -5190,11 +5059,11 @@ postgresGetAnalyzeInfoForForeignTable(Relation relation, bool *can_tablesample)
if (PQresultStatus(res) != PGRES_TUPLES_OK)
pgfdw_report_error(res, conn, sql.data);
- if (PQntuples(res) != 1 || PQnfields(res) != RELSTATS_NUM_FIELDS)
+ if (PQntuples(res) != 1 || PQnfields(res) != RELCOL_NUM_FIELDS)
elog(ERROR, "unexpected result from deparseAnalyzeInfoSql query");
/* We don't use relpages here */
- reltuples = strtod(PQgetvalue(res, 0, RELSTATS_RELTUPLES), NULL);
- relkind = *(PQgetvalue(res, 0, RELSTATS_RELKIND));
+ reltuples = strtod(PQgetvalue(res, 0, RELCOL_RELTUPLES), NULL);
+ relkind = *(PQgetvalue(res, 0, RELCOL_RELKIND));
PQclear(res);
ReleaseConnection(conn);
@@ -5661,7 +5530,7 @@ postgresImportForeignStatistics(Relation relation, List *va_cols, int elevel)
&attrcnt, &remattrmap, &remstats);
if (ok)
- ok = import_fetched_statistics(schemaname, relname,
+ ok = import_fetched_statistics(relation, schemaname, relname,
attrcnt, remattrmap, &remstats);
if (ok)
@@ -5738,7 +5607,7 @@ fetch_remote_statistics(Relation relation,
* RELKIND_PARTITIONED_INDEX can have rows in pg_stats, they obviously
* can't support a foreign table.
*/
- relkind = *PQgetvalue(relstats, 0, RELSTATS_RELKIND);
+ relkind = *PQgetvalue(relstats, 0, RELCOL_RELKIND);
switch (relkind)
{
case RELKIND_RELATION:
@@ -5769,7 +5638,7 @@ fetch_remote_statistics(Relation relation,
* way, we wouldn't expect to find the stats for the table, so we fallback
* to sampling.
*/
- reltuples = strtod(PQgetvalue(relstats, 0, RELSTATS_RELTUPLES), NULL);
+ reltuples = strtod(PQgetvalue(relstats, 0, RELCOL_RELTUPLES), NULL);
if (((server_version_num < 140000) && (reltuples == 0)) ||
((server_version_num >= 140000) && (reltuples == -1)))
{
@@ -5830,7 +5699,7 @@ fetch_relstats(PGconn *conn, Relation relation)
if (PQresultStatus(res) != PGRES_TUPLES_OK)
pgfdw_report_error(res, conn, sql.data);
- if (PQntuples(res) != 1 || PQnfields(res) != RELSTATS_NUM_FIELDS)
+ if (PQntuples(res) != 1 || PQnfields(res) != RELCOL_NUM_FIELDS)
elog(ERROR, "unexpected result from deparseAnalyzeInfoSql query");
return res;
@@ -5901,7 +5770,7 @@ fetch_attstats(PGconn *conn, int server_version_num,
if (PQresultStatus(res) != PGRES_TUPLES_OK)
pgfdw_report_error(res, conn, sql.data);
- if (PQnfields(res) != ATTSTATS_NUM_FIELDS)
+ if (PQnfields(res) != ATTCOL_NUM_FIELDS)
elog(ERROR, "unexpected result from fetch_attstats query");
return res;
@@ -6068,7 +5937,7 @@ match_attrmap(PGresult *res,
*/
if (row >= 0 &&
strcmp(remattrmap[mapidx].remote_attname,
- PQgetvalue(res, row, ATTSTATS_ATTNAME)) == 0)
+ PQgetvalue(res, row, ATTCOL_ATTNAME)) == 0)
{
remattrmap[mapidx].res_index = row;
continue;
@@ -6096,7 +5965,7 @@ match_attrmap(PGresult *res,
* row, it means the stats for the entry are missing.
*/
if (strcmp(remattrmap[mapidx].remote_attname,
- PQgetvalue(res, row, ATTSTATS_ATTNAME)) < 0)
+ PQgetvalue(res, row, ATTCOL_ATTNAME)) < 0)
{
ereport(WARNING,
errmsg("could not import statistics for foreign table \"%s.%s\" --- no attribute statistics found for column \"%s\" of remote table \"%s.%s\"",
@@ -6108,12 +5977,12 @@ match_attrmap(PGresult *res,
/* We should not have got a stats row we didn't expect. */
if (strcmp(remattrmap[mapidx].remote_attname,
- PQgetvalue(res, row, ATTSTATS_ATTNAME)) > 0)
+ PQgetvalue(res, row, ATTCOL_ATTNAME)) > 0)
elog(ERROR, "unexpected result from fetch_attstats query");
/* We found a match. */
Assert(strcmp(remattrmap[mapidx].remote_attname,
- PQgetvalue(res, row, ATTSTATS_ATTNAME)) == 0);
+ PQgetvalue(res, row, ATTCOL_ATTNAME)) == 0);
remattrmap[mapidx].res_index = row;
}
@@ -6124,35 +5993,37 @@ match_attrmap(PGresult *res,
return true;
}
+
+/*
+ * Conenience routine to map a given cell of a result set to the input arguments
+ * of a call to relation_statistics_update() or attribute_statistics_update().
+ */
+static void
+map_res_to_stat(PGresult *res, int row, int col,
+ bool *isnull, char **values, int statnum)
+{
+ isnull[statnum] = PQgetisnull(res, row, col);
+ values[statnum] = (isnull[statnum]) ? NULL : PQgetvalue(res, row, col);
+}
+
+#define map_attstat(name) \
+ map_res_to_stat(remstats->att, row, ATTCOL_##name, attisnull, attvalues, ATTSTAT_##name)
+
/*
* Import fetched statistics into the local statistics tables.
*/
static bool
-import_fetched_statistics(const char *schemaname,
+import_fetched_statistics(Relation relation,
+ const char *schemaname,
const char *relname,
int attrcnt,
const RemoteAttributeMapping *remattrmap,
RemoteStatsResults *remstats)
{
- SPIPlanPtr attimport_plan = NULL;
- SPIPlanPtr attclear_plan = NULL;
- Datum values[ATTIMPORT_SQL_NUM_FIELDS];
- char nulls[ATTIMPORT_SQL_NUM_FIELDS];
- int spirc;
+ bool relisnull[RELSTAT_NUM_RELSTATS];
+ char *relvalues[RELSTAT_NUM_RELSTATS];
bool ok = false;
- /* Assign all the invariant parameters common to relation/attribute stats */
- values[ATTIMPORT_SQL_VERSION] = Int32GetDatum(remstats->server_version_num);
- nulls[ATTIMPORT_SQL_VERSION] = ' ';
-
- values[ATTIMPORT_SQL_SCHEMANAME] = CStringGetTextDatum(schemaname);
- nulls[ATTIMPORT_SQL_SCHEMANAME] = ' ';
-
- values[ATTIMPORT_SQL_RELNAME] = CStringGetTextDatum(relname);
- nulls[ATTIMPORT_SQL_RELNAME] = ' ';
-
- SPI_connect();
-
/*
* We import attribute statistics first, if any, because those are more
* prone to errors. This avoids making a modification of pg_class that
@@ -6160,26 +6031,16 @@ import_fetched_statistics(const char *schemaname,
*/
if (remstats->att != NULL)
{
- Assert(PQnfields(remstats->att) == ATTSTATS_NUM_FIELDS);
+ Assert(PQnfields(remstats->att) == ATTCOL_NUM_FIELDS);
Assert(PQntuples(remstats->att) >= 1);
- attimport_plan = SPI_prepare(attimport_sql, ATTIMPORT_SQL_NUM_FIELDS,
- (Oid *) attimport_argtypes);
- if (attimport_plan == NULL)
- elog(ERROR, "failed to prepare attimport_sql query");
-
- attclear_plan = SPI_prepare(attclear_sql, ATTCLEAR_SQL_NUM_FIELDS,
- (Oid *) attclear_argtypes);
- if (attclear_plan == NULL)
- elog(ERROR, "failed to prepare attclear_sql query");
-
- nulls[ATTIMPORT_SQL_ATTNUM] = ' ';
-
for (int mapidx = 0; mapidx < attrcnt; mapidx++)
{
+ bool attisnull[ATTSTAT_NUM_ATTSTATS];
+ char *attvalues[ATTSTAT_NUM_ATTSTATS];
int row = remattrmap[mapidx].res_index;
- Datum *values2 = values + 1;
- char *nulls2 = nulls + 1;
+ AttrNumber attnum = remattrmap[mapidx].local_attnum;
+ const char *attname = remattrmap[mapidx].local_attname;
/* All mappings should have been assigned a result set row. */
Assert(row >= 0);
@@ -6189,71 +6050,50 @@ import_fetched_statistics(const char *schemaname,
*/
CHECK_FOR_INTERRUPTS();
- /*
- * First, clear existing attribute stats.
- *
- * We can re-use the values/nulls because the number of parameters
- * is less and the first two params are the same as the second and
- * third ones in attimport_sql.
- */
- values2[ATTCLEAR_SQL_ATTNAME] =
- CStringGetTextDatum(remattrmap[mapidx].local_attname);
-
- spirc = SPI_execute_plan(attclear_plan, values2, nulls2, false, 1);
- if (spirc != SPI_OK_SELECT)
- elog(ERROR, "failed to execute attclear_sql query for column \"%s\" of foreign table \"%s.%s\"",
- remattrmap[mapidx].local_attname, schemaname, relname);
-
- values[ATTIMPORT_SQL_ATTNUM] =
- Int16GetDatum(remattrmap[mapidx].local_attnum);
-
- /* Loop through all mappable columns to set remaining arguments */
- for (int i = 0; i < NUM_MAPPED_ATTIMPORT_ARGS; i++)
- map_field_to_arg(remstats->att, row,
- attr_result_arg_map[i].res_field,
- attr_result_arg_map[i].arg_num,
- values, nulls);
-
- spirc = SPI_execute_plan(attimport_plan, values, nulls, false, 1);
- if (spirc != SPI_OK_SELECT)
- elog(ERROR, "failed to execute attimport_sql query for column \"%s\" of foreign table \"%s.%s\"",
- remattrmap[mapidx].local_attname, schemaname, relname);
-
- if (!import_spi_query_ok())
+ attribute_statistics_delete(relation, attnum, false);
+
+ map_attstat(NULL_FRAC);
+ map_attstat(AVG_WIDTH);
+ map_attstat(N_DISTINCT);
+ map_attstat(MOST_COMMON_VALS);
+ map_attstat(MOST_COMMON_FREQS);
+ map_attstat(HISTOGRAM_BOUNDS);
+ map_attstat(CORRELATION);
+ map_attstat(MOST_COMMON_ELEMS);
+ map_attstat(MOST_COMMON_ELEM_FREQS);
+ map_attstat(ELEM_COUNT_HISTOGRAM);
+ map_attstat(RANGE_LENGTH_HISTOGRAM);
+ map_attstat(RANGE_EMPTY_FRAC);
+ map_attstat(RANGE_BOUNDS_HISTOGRAM);
+
+ if (!attribute_statistics_update(relation, attname, attnum, false,
+ attisnull,
+ (const char **) attvalues))
{
ereport(WARNING,
errmsg("could not import statistics for foreign table \"%s.%s\" --- attribute statistics import failed for column \"%s\" of this foreign table",
- schemaname, relname,
- remattrmap[mapidx].local_attname));
+ schemaname, relname, attname));
goto import_cleanup;
}
}
}
/*
- * Import relation stats. We only perform this once, so there is no point
- * in preparing the statement.
- *
- * We can re-use the values/nulls because the number of parameters is less
- * and the first three params are the same as attimport_sql.
+ * Import relation stats.
*/
Assert(remstats->rel != NULL);
- Assert(PQnfields(remstats->rel) == RELSTATS_NUM_FIELDS);
- Assert(PQntuples(remstats->rel) == 1);
- map_field_to_arg(remstats->rel, 0, RELSTATS_RELPAGES,
- RELIMPORT_SQL_RELPAGES, values, nulls);
- map_field_to_arg(remstats->rel, 0, RELSTATS_RELTUPLES,
- RELIMPORT_SQL_RELTUPLES, values, nulls);
-
- spirc = SPI_execute_with_args(relimport_sql,
- RELIMPORT_SQL_NUM_FIELDS,
- (Oid *) relimport_argtypes,
- values, nulls, false, 1);
- if (spirc != SPI_OK_SELECT)
- elog(ERROR, "failed to execute relimport_sql query for foreign table \"%s.%s\"",
- schemaname, relname);
-
- if (!import_spi_query_ok())
+
+ map_res_to_stat(remstats->rel, 0, RELCOL_RELPAGES,
+ relisnull, relvalues, RELSTAT_RELPAGES);
+ map_res_to_stat(remstats->rel, 0, RELCOL_RELTUPLES,
+ relisnull, relvalues, RELSTAT_RELTUPLES);
+ relisnull[RELSTAT_RELALLVISIBLE] = true;
+ relvalues[RELSTAT_RELALLVISIBLE] = NULL;
+ relisnull[RELSTAT_RELALLFROZEN] = true;
+ relvalues[RELSTAT_RELALLFROZEN] = NULL;
+
+ if (!relation_statistics_update(relation, relisnull,
+ (const char **) relvalues))
{
ereport(WARNING,
errmsg("could not import statistics for foreign table \"%s.%s\" --- relation statistics import failed for this foreign table",
@@ -6264,57 +6104,9 @@ import_fetched_statistics(const char *schemaname,
ok = true;
import_cleanup:
- if (attimport_plan)
- SPI_freeplan(attimport_plan);
- if (attclear_plan)
- SPI_freeplan(attclear_plan);
- SPI_finish();
return ok;
}
-/*
- * Move a string value from a result set to a Text value of a Datum array.
- */
-static void
-map_field_to_arg(PGresult *res, int row, int field,
- int arg, Datum *values, char *nulls)
-{
- if (PQgetisnull(res, row, field))
- {
- values[arg] = (Datum) 0;
- nulls[arg] = 'n';
- }
- else
- {
- const char *s = PQgetvalue(res, row, field);
-
- values[arg] = CStringGetTextDatum(s);
- nulls[arg] = ' ';
- }
-}
-
-/*
- * Check the 1x1 result set of a pg_restore_*_stats() command for success.
- */
-static bool
-import_spi_query_ok(void)
-{
- TupleDesc tupdesc;
- Datum dat;
- bool isnull;
-
- Assert(SPI_tuptable != NULL);
- Assert(SPI_processed == 1);
-
- tupdesc = SPI_tuptable->tupdesc;
- Assert(tupdesc->natts == 1);
- Assert(TupleDescAttr(tupdesc, 0)->atttypid == BOOLOID);
- dat = SPI_getbinval(SPI_tuptable->vals[0], tupdesc, 1, &isnull);
- Assert(!isnull);
-
- return DatumGetBool(dat);
-}
-
/*
* Import a foreign schema
*/
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 8162c5496bf..201cebfd2c2 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -4642,6 +4642,46 @@ ANALYZE dtest_table;
ANALYZE VERBOSE dtest_ftable; -- should work
+-- dtest_ftables stats should now exactly match dtest_table
+-- compare the rowcounts, should get 0 rows back
+SELECT COUNT(*) FROM pg_stats
+WHERE schemaname = 'public' AND tablename = 'dtest_table'
+EXCEPT
+SELECT COUNT(*) FROM pg_stats
+WHERE schemaname = 'public' AND tablename = 'dtest_ftable';
+
+-- compare values, should match
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_table'::regclass
+EXCEPT
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_ftable'::regclass;
+
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_ftable'::regclass
+EXCEPT
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_table'::regclass;
+
+-- test only a few stats columns common to integer types
+SELECT attname, inherited, null_frac, avg_width, n_distinct,
+ most_common_vals::text as mcv, most_common_freqs,
+ histogram_bounds::text as hb, correlation
+FROM pg_stats
+WHERE schemaname = 'public'
+AND tablename = 'dtest_table'
+EXCEPT
+SELECT attname, inherited, null_frac, avg_width, n_distinct,
+ most_common_vals::text as mcv, most_common_freqs,
+ histogram_bounds::text as hb, correlation
+FROM pg_stats
+WHERE schemaname = 'public'
+AND tablename = 'dtest_ftable';
+
-- cleanup
DROP FOREIGN TABLE simport_ftable;
DROP FOREIGN TABLE simport_fview;
--
2.50.1 (Apple Git-155)