From 188b5f7d80cfa5af8affd82f6579591d3f6d43dc Mon Sep 17 00:00:00 2001 From: Corey Huinker 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)