]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
postgres_fdw: Remove SPI from postgresImportForeignStatistics.
authorEtsuro Fujita <efujita@postgresql.org>
Fri, 10 Jul 2026 04:20:00 +0000 (13:20 +0900)
committerEtsuro Fujita <efujita@postgresql.org>
Fri, 10 Jul 2026 04:20:00 +0000 (13:20 +0900)
Previously, this function imported remote statistics by executing SQL
functions like pg_restore_relation_stats and pg_restore_attribute_stats
via SPI (in read-write mode).  As the SQL functions take a schema name
and a relation name as two separate arguments, rather than a single OID
argument, if the containing schema was concurrently renamed, the
callback function would throw an error like this:

  ERROR: schema "foo" does not exist

To fix, 1) provide new interface functions to import remote statistics
that are directly callable from FDWs and take a single OID, and 2)
modify the callback function to use the interface functions instead when
importing remote statistics.

For #1, this commit does a bit of refactoring to
relation_statistics_update and attribute_statistics_update, which are
the workhorse functions for pg_restore_relation_stats and
pg_restore_attribute_stats respectively: since they also take a schema
name and a relation name, separate the guts of them into new functions
so that they take a single OID and are callable not only from the
workhorse functions but from the interface functions introduced by #1.

Oversight in commit 28972b6fc.

Reported-by: Robert Haas <robertmhaas@gmail.com>
Suggested-by: Robert Haas <robertmhaas@gmail.com>
Author: Corey Huinker <corey.huinker@gmail.com>
Co-authored-by: Etsuro Fujita <etsuro.fujita@gmail.com>
Discussion: https://postgr.es/m/CA%2BTgmoYqMtWb4zLUkT98oFnEkJ%3DWz0Pw-ggDJrp9wnSXPzUaeQ%40mail.gmail.com
Backpatch-through: 19

contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/postgres_fdw.c
contrib/postgres_fdw/sql/postgres_fdw.sql
doc/src/sgml/fdwhandler.sgml
src/backend/statistics/attribute_stats.c
src/backend/statistics/relation_stats.c
src/include/statistics/statistics.h

index 5ebae1cedc2f61cf1e9ced92157d947a5a3baa4a..048f624ba0c2d7c2bda4ed2223fa0389b993b30b 100644 (file)
@@ -13067,6 +13067,43 @@ 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_ftable's stats should now exactly match dtest_table's
+-- 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)
+
+-- 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)
+
+-- 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;
index 0ea72a64ce5024f389b8c455d4df368cb8244fc1..e6e0a4ab9b5a03691387ed7b20884fa517cf07a3 100644 (file)
@@ -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"
@@ -47,6 +46,7 @@
 #include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
+#include "utils/fmgroids.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -337,9 +337,9 @@ typedef struct
 {
        PGresult   *rel;
        PGresult   *att;
-       int                     server_version_num;
        double          livetuples;
        double          deadtuples;
+       int                     version;
 } RemoteStatsResults;
 
 /* Column order in relation stats query */
@@ -371,136 +371,6 @@ enum AttStatsColumns
        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,
-};
-
 /*
  * SQL functions
  */
@@ -718,14 +588,18 @@ 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 char *get_opt_value(PGresult *res, int row, int col);
+static void set_text_arg(NullableDatum *arg, const char *s);
+static void set_int32_arg(NullableDatum *arg, const char *s);
+static void set_uint32_arg(NullableDatum *arg, const char *s);
+static void set_float_arg(NullableDatum *arg, const char *s);
+static void set_floatarr_arg(NullableDatum *arg, const char *s);
 static void produce_tuple_asynchronously(AsyncRequest *areq, bool fetch);
 static void fetch_more_data_begin(AsyncRequest *areq);
 static void complete_pending_request(AsyncRequest *areq);
@@ -5668,7 +5542,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 +5612,7 @@ fetch_remote_statistics(Relation relation,
         */
        user = GetUserMapping(GetUserId(), table->serverid);
        conn = GetConnection(user, false, NULL);
-       remstats->server_version_num = server_version_num = PQserverVersion(conn);
+       remstats->version = server_version_num = PQserverVersion(conn);
 
        /* Fetch relation stats. */
        remstats->rel = relstats = fetch_relstats(conn, relation);
@@ -6144,191 +6018,232 @@ match_attrmap(PGresult *res,
  * 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            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] = ' ';
+       PGresult   *res;
+       NullableDatum args[ATTSTATS_NUM_FIELDS];
 
-       SPI_connect();
+       /* Set the 'version' parameter, which is common to both statistics. */
+       args[0].value = UInt32GetDatum(remstats->version);
+       args[0].isnull = false;
 
        /*
         * We import attribute statistics first, if any, because those are more
         * prone to errors.  This avoids making a modification of pg_class that
         * will just get rolled back by a failed attribute import.
         */
-       if (remstats->att != NULL)
+       res = remstats->att;
+       if (res != NULL)
        {
-               Assert(PQnfields(remstats->att) == ATTSTATS_NUM_FIELDS);
-               Assert(PQntuples(remstats->att) >= 1);
-
-               attimport_plan = SPI_prepare(attimport_sql, ATTIMPORT_SQL_NUM_FIELDS,
-                                                                        attimport_argtypes);
-               if (attimport_plan == NULL)
-                       elog(ERROR, "failed to prepare attimport_sql query");
-
-               attclear_plan = SPI_prepare(attclear_sql, ATTCLEAR_SQL_NUM_FIELDS,
-                                                                       attclear_argtypes);
-               if (attclear_plan == NULL)
-                       elog(ERROR, "failed to prepare attclear_sql query");
-
-               nulls[ATTIMPORT_SQL_ATTNUM] = ' ';
+               Assert(PQnfields(res) == ATTSTATS_NUM_FIELDS);
+               Assert(PQntuples(res) >= 1);
 
                for (int mapidx = 0; mapidx < attrcnt; mapidx++)
                {
                        int                     row = remattrmap[mapidx].res_index;
-                       Datum      *values2 = values + 1;
-                       char       *nulls2 = nulls + 1;
+                       AttrNumber      attnum = remattrmap[mapidx].local_attnum;
 
                        /* All mappings should have been assigned a result set row. */
                        Assert(row >= 0);
 
-                       /*
-                        * Check for user-requested abort.
-                        */
+                       /* Check for user-requested abort. */
                        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())
+                       /* Clear existing attribute statistics. */
+                       delete_attribute_statistics(relation, attnum, false);
+
+                       /* Set the remaining parameters. */
+                       set_float_arg(&args[1],
+                                                 get_opt_value(res, row, ATTSTATS_NULL_FRAC));
+                       set_int32_arg(&args[2],
+                                                 get_opt_value(res, row, ATTSTATS_AVG_WIDTH));
+                       set_float_arg(&args[3],
+                                                 get_opt_value(res, row, ATTSTATS_N_DISTINCT));
+                       set_text_arg(&args[4],
+                                                get_opt_value(res, row, ATTSTATS_MOST_COMMON_VALS));
+                       set_floatarr_arg(&args[5],
+                                                        get_opt_value(res, row, ATTSTATS_MOST_COMMON_FREQS));
+                       set_text_arg(&args[6],
+                                                get_opt_value(res, row, ATTSTATS_HISTOGRAM_BOUNDS));
+                       set_float_arg(&args[7],
+                                                 get_opt_value(res, row, ATTSTATS_CORRELATION));
+                       set_text_arg(&args[8],
+                                                get_opt_value(res, row, ATTSTATS_MOST_COMMON_ELEMS));
+                       set_floatarr_arg(&args[9],
+                                                        get_opt_value(res, row, ATTSTATS_MOST_COMMON_ELEM_FREQS));
+                       set_floatarr_arg(&args[10],
+                                                        get_opt_value(res, row, ATTSTATS_ELEM_COUNT_HISTOGRAM));
+                       set_text_arg(&args[11],
+                                                get_opt_value(res, row, ATTSTATS_RANGE_LENGTH_HISTOGRAM));
+                       set_float_arg(&args[12],
+                                                 get_opt_value(res, row, ATTSTATS_RANGE_EMPTY_FRAC));
+                       set_text_arg(&args[13],
+                                                get_opt_value(res, row, ATTSTATS_RANGE_BOUNDS_HISTOGRAM));
+
+                       /* Try to import the statistics. */
+                       if (!import_attribute_statistics(relation, attnum, false,
+                                                                                        &args[0], &args[1], &args[2],
+                                                                                        &args[3], &args[4], &args[5],
+                                                                                        &args[6], &args[7], &args[8],
+                                                                                        &args[9], &args[10], &args[11],
+                                                                                        &args[12], &args[13]))
                        {
                                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));
-                               goto import_cleanup;
+                               return false;
                        }
                }
        }
 
        /*
-        * 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.
-        */
-       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,
-                                                                 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())
+        * Import relation statistics.
+        */
+       res = remstats->rel;
+       Assert(res != NULL);
+       Assert(PQnfields(res) == RELSTATS_NUM_FIELDS);
+       Assert(PQntuples(res) == 1);
+
+       /* Set the remaining parameters. */
+       set_uint32_arg(&args[1], get_opt_value(res, 0, RELSTATS_RELPAGES));
+       Assert(!args[1].isnull);
+       set_float_arg(&args[2], get_opt_value(res, 0, RELSTATS_RELTUPLES));
+       Assert(!args[2].isnull);
+       args[3].value = (Datum) 0;
+       args[3].isnull = true;
+       args[4].value = (Datum) 0;
+       args[4].isnull = true;
+
+       /* Try to import the statistics. */
+       if (!import_relation_statistics(relation, &args[0], &args[1],
+                                                                       &args[2], &args[3], &args[4]))
        {
                ereport(WARNING,
                                errmsg("could not import statistics for foreign table \"%s.%s\" --- relation statistics import failed for this foreign table",
                                           schemaname, relname));
-               goto import_cleanup;
+               return false;
        }
 
-       ok = true;
+       return true;
+}
 
-import_cleanup:
-       if (attimport_plan)
-               SPI_freeplan(attimport_plan);
-       if (attclear_plan)
-               SPI_freeplan(attclear_plan);
-       SPI_finish();
-       return ok;
+/*
+ * Conenience routine to fetch the value for the row/column of the PGresult
+ */
+static char *
+get_opt_value(PGresult *res, int row, int col)
+{
+       if (PQgetisnull(res, row, col))
+               return NULL;
+       return PQgetvalue(res, row, col);
 }
 
 /*
- * Move a string value from a result set to a Text value of a Datum array.
+ * Convenience routine for setting optional text arguments
  */
-static void
-map_field_to_arg(PGresult *res, int row, int field,
-                                int arg, Datum *values, char *nulls)
+void
+set_text_arg(NullableDatum *arg, const char *s)
 {
-       if (PQgetisnull(res, row, field))
+       if (s)
        {
-               values[arg] = (Datum) 0;
-               nulls[arg] = 'n';
+               arg->value = CStringGetTextDatum(s);
+               arg->isnull = false;
        }
        else
        {
-               const char *s = PQgetvalue(res, row, field);
+               arg->value = (Datum) 0;
+               arg->isnull = true;
+       }
+}
 
-               values[arg] = CStringGetTextDatum(s);
-               nulls[arg] = ' ';
+/*
+ * Convenience routine for setting optional int32 arguments
+ */
+void
+set_int32_arg(NullableDatum *arg, const char *s)
+{
+       if (s)
+       {
+               int32           val = pg_strtoint32(s);
+
+               arg->value = Int32GetDatum(val);
+               arg->isnull = false;
+       }
+       else
+       {
+               arg->value = (Datum) 0;
+               arg->isnull = true;
        }
 }
 
 /*
- * Check the 1x1 result set of a pg_restore_*_stats() command for success.
+ * Convenience routine for setting optional uint32 arguments
  */
-static bool
-import_spi_query_ok(void)
+void
+set_uint32_arg(NullableDatum *arg, const char *s)
 {
-       TupleDesc       tupdesc;
-       Datum           dat;
-       bool            isnull;
+       if (s)
+       {
+               uint32          val = uint32in_subr(s, NULL, "uint32", NULL);
 
-       Assert(SPI_tuptable != NULL);
-       Assert(SPI_processed == 1);
+               arg->value = UInt32GetDatum(val);
+               arg->isnull = false;
+       }
+       else
+       {
+               arg->value = (Datum) 0;
+               arg->isnull = true;
+       }
+}
+
+/*
+ * Convenience routine for setting optional float arguments
+ */
+void
+set_float_arg(NullableDatum *arg, const char *s)
+{
+       if (s)
+       {
+               float4          val = float4in_internal((char *) s, NULL, "float", s, NULL);
+
+               arg->value = Float4GetDatum(val);
+               arg->isnull = false;
+       }
+       else
+       {
+               arg->value = (Datum) 0;
+               arg->isnull = true;
+       }
+}
+
+/*
+ * Convenience routine for setting optional float[] arguments
+ */
+void
+set_floatarr_arg(NullableDatum *arg, const char *s)
+{
+       if (s)
+       {
+               FmgrInfo        flinfo;
+               Datum           val;
 
-       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);
+               fmgr_info(F_ARRAY_IN, &flinfo);
+               val = InputFunctionCall(&flinfo, (char *) s, FLOAT4OID, -1);
 
-       return DatumGetBool(dat);
+               arg->value = val;
+               arg->isnull = false;
+       }
+       else
+       {
+               arg->value = (Datum) 0;
+               arg->isnull = true;
+       }
 }
 
 /*
index e868da00ace7f476f6e94f80db5a5dc39fd273d4..ed2c8b58e60d39105df315dcaf8847db46ade767 100644 (file)
@@ -4652,6 +4652,34 @@ ANALYZE dtest_table;
 
 ANALYZE VERBOSE dtest_ftable;             -- should work
 
+-- dtest_ftable's stats should now exactly match dtest_table's
+-- 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;
+
+-- 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';
+
+-- 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;
index 8685a078c5257c0a412d14db9158c5816bf9ced8..0103fdacfdf6bd7d912ca9908033c02677b68f59 100644 (file)
@@ -1438,8 +1438,9 @@ ImportForeignStatistics(Relation relation,
      <productname>PostgreSQL</productname> is found in
      <filename>src/backend/command/analyze.c</filename>.
      It's recommended to import table-level and column-level statistics for the
-     foreign table using <function>pg_restore_relation_stats</function> and
-     <function>pg_restore_attribute_stats</function>, respectively.
+     foreign table using <function>import_relation_statistics</function>,
+     <function>import_attribute_statistics</function>, and
+     <function>delete_attribute_statistics</function>.
     </para>
 
     <para>
index 6d99850105d903c80c4954df673cc45f9130bda4..2efb74b95a633af087c9c44d1a10edcb4670b6ad 100644 (file)
@@ -105,6 +105,11 @@ static struct StatsArgInfo cleararginfo[] =
 };
 
 static bool attribute_statistics_update(FunctionCallInfo fcinfo);
+static bool attribute_statistics_update_internal(Oid reloid,
+                                                                                                const char *attname,
+                                                                                                AttrNumber attnum,
+                                                                                                bool inherited,
+                                                                                                FunctionCallInfo fcinfo);
 static void upsert_pg_statistic(Relation starel, HeapTuple oldtup,
                                                                const Datum *values, const bool *nulls, const bool *replaces);
 static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
@@ -136,38 +141,6 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
        bool            inherited;
        Oid                     locked_table = InvalidOid;
 
-       Relation        starel;
-       HeapTuple       statup;
-
-       Oid                     atttypid = InvalidOid;
-       int32           atttypmod;
-       char            atttyptype;
-       Oid                     atttypcoll = InvalidOid;
-       Oid                     eq_opr = InvalidOid;
-       Oid                     lt_opr = InvalidOid;
-
-       Oid                     elemtypid = InvalidOid;
-       Oid                     elem_eq_opr = InvalidOid;
-
-       FmgrInfo        array_in_fn;
-
-       bool            do_mcv = !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
-               !PG_ARGISNULL(MOST_COMMON_VALS_ARG);
-       bool            do_histogram = !PG_ARGISNULL(HISTOGRAM_BOUNDS_ARG);
-       bool            do_correlation = !PG_ARGISNULL(CORRELATION_ARG);
-       bool            do_mcelem = !PG_ARGISNULL(MOST_COMMON_ELEMS_ARG) &&
-               !PG_ARGISNULL(MOST_COMMON_ELEM_FREQS_ARG);
-       bool            do_dechist = !PG_ARGISNULL(ELEM_COUNT_HISTOGRAM_ARG);
-       bool            do_bounds_histogram = !PG_ARGISNULL(RANGE_BOUNDS_HISTOGRAM_ARG);
-       bool            do_range_length_histogram = !PG_ARGISNULL(RANGE_LENGTH_HISTOGRAM_ARG) &&
-               !PG_ARGISNULL(RANGE_EMPTY_FRAC_ARG);
-
-       Datum           values[Natts_pg_statistic] = {0};
-       bool            nulls[Natts_pg_statistic] = {0};
-       bool            replaces[Natts_pg_statistic] = {0};
-
-       bool            result = true;
-
        stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
        stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
 
@@ -231,6 +204,50 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
        stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
        inherited = PG_GETARG_BOOL(INHERITED_ARG);
 
+       return attribute_statistics_update_internal(reloid, attname, attnum,
+                                                                                               inherited, fcinfo);
+}
+
+/*
+ * Workhorse function for attribute_statistics_update.
+ */
+static bool
+attribute_statistics_update_internal(Oid reloid,
+                                                                        const char *attname, AttrNumber attnum,
+                                                                        bool inherited, FunctionCallInfo fcinfo)
+{
+       Relation        starel;
+       HeapTuple       statup;
+
+       Oid                     atttypid = InvalidOid;
+       int32           atttypmod;
+       char            atttyptype;
+       Oid                     atttypcoll = InvalidOid;
+       Oid                     eq_opr = InvalidOid;
+       Oid                     lt_opr = InvalidOid;
+
+       Oid                     elemtypid = InvalidOid;
+       Oid                     elem_eq_opr = InvalidOid;
+
+       FmgrInfo        array_in_fn;
+
+       bool            do_mcv = !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
+               !PG_ARGISNULL(MOST_COMMON_VALS_ARG);
+       bool            do_histogram = !PG_ARGISNULL(HISTOGRAM_BOUNDS_ARG);
+       bool            do_correlation = !PG_ARGISNULL(CORRELATION_ARG);
+       bool            do_mcelem = !PG_ARGISNULL(MOST_COMMON_ELEMS_ARG) &&
+               !PG_ARGISNULL(MOST_COMMON_ELEM_FREQS_ARG);
+       bool            do_dechist = !PG_ARGISNULL(ELEM_COUNT_HISTOGRAM_ARG);
+       bool            do_bounds_histogram = !PG_ARGISNULL(RANGE_BOUNDS_HISTOGRAM_ARG);
+       bool            do_range_length_histogram = !PG_ARGISNULL(RANGE_LENGTH_HISTOGRAM_ARG) &&
+               !PG_ARGISNULL(RANGE_EMPTY_FRAC_ARG);
+
+       Datum           values[Natts_pg_statistic] = {0};
+       bool            nulls[Natts_pg_statistic] = {0};
+       bool            replaces[Natts_pg_statistic] = {0};
+
+       bool            result = true;
+
        /*
         * Check argument sanity. If some arguments are unusable, emit a WARNING
         * and set the corresponding argument to NULL in fcinfo.
@@ -689,3 +706,96 @@ pg_restore_attribute_stats(PG_FUNCTION_ARGS)
 
        PG_RETURN_BOOL(result);
 }
+
+/*
+ * Import attribute statistics from NullableDatum inputs for all statitical
+ * values.
+ *
+ * For now, the 'version' argument is ignored. In the future it can be used
+ * to interpret older statistics properly.
+ */
+bool
+import_attribute_statistics(Relation rel, AttrNumber attnum, bool inherited,
+                                                       const NullableDatum *version,
+                                                       const NullableDatum *null_frac,
+                                                       const NullableDatum *avg_width,
+                                                       const NullableDatum *n_distinct,
+                                                       const NullableDatum *most_common_vals,
+                                                       const NullableDatum *most_common_freqs,
+                                                       const NullableDatum *histogram_bounds,
+                                                       const NullableDatum *correlation,
+                                                       const NullableDatum *most_common_elems,
+                                                       const NullableDatum *most_common_elem_freqs,
+                                                       const NullableDatum *elem_count_histogram,
+                                                       const NullableDatum *range_length_histogram,
+                                                       const NullableDatum *range_empty_frac,
+                                                       const NullableDatum *range_bounds_histogram)
+{
+       LOCAL_FCINFO(newfcinfo, NUM_ATTRIBUTE_STATS_ARGS);
+       Oid                     reloid = RelationGetRelid(rel);
+       char       *relname = RelationGetRelationName(rel);
+       char       *attname = get_attname(reloid, attnum, true);
+
+       Assert(null_frac);
+       Assert(avg_width);
+       Assert(n_distinct);
+       Assert(most_common_vals);
+       Assert(most_common_freqs);
+       Assert(histogram_bounds);
+       Assert(correlation);
+       Assert(most_common_elems);
+       Assert(most_common_elem_freqs);
+       Assert(elem_count_histogram);
+       Assert(range_length_histogram);
+       Assert(range_empty_frac);
+       Assert(range_bounds_histogram);
+
+       /* annoyingly, get_attname doesn't check attisdropped */
+       if (attname == NULL ||
+               !SearchSysCacheExistsAttName(reloid, attname))
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                errmsg("column %d of relation \"%s\" does not exist",
+                                               attnum, relname)));
+
+       InitFunctionCallInfoData(*newfcinfo, NULL, NUM_ATTRIBUTE_STATS_ARGS,
+                                                        InvalidOid, NULL, NULL);
+
+       newfcinfo->args[ATTRELSCHEMA_ARG].value =
+               CStringGetTextDatum(get_namespace_name(RelationGetNamespace(rel)));
+       newfcinfo->args[ATTRELSCHEMA_ARG].isnull = false;
+       newfcinfo->args[ATTRELNAME_ARG].value = CStringGetTextDatum(relname);
+       newfcinfo->args[ATTRELNAME_ARG].isnull = false;
+       newfcinfo->args[ATTNAME_ARG].value = CStringGetTextDatum(attname);
+       newfcinfo->args[ATTNAME_ARG].isnull = false;
+       newfcinfo->args[ATTNUM_ARG].value = Int16GetDatum(attnum);
+       newfcinfo->args[ATTNUM_ARG].isnull = false;
+       newfcinfo->args[INHERITED_ARG].value = BoolGetDatum(inherited);
+       newfcinfo->args[INHERITED_ARG].isnull = false;
+
+       newfcinfo->args[NULL_FRAC_ARG] = *null_frac;
+       newfcinfo->args[AVG_WIDTH_ARG] = *avg_width;
+       newfcinfo->args[N_DISTINCT_ARG] = *n_distinct;
+       newfcinfo->args[MOST_COMMON_VALS_ARG] = *most_common_vals;
+       newfcinfo->args[MOST_COMMON_FREQS_ARG] = *most_common_freqs;
+       newfcinfo->args[HISTOGRAM_BOUNDS_ARG] = *histogram_bounds;
+       newfcinfo->args[CORRELATION_ARG] = *correlation;
+       newfcinfo->args[MOST_COMMON_ELEMS_ARG] = *most_common_elems;
+       newfcinfo->args[MOST_COMMON_ELEM_FREQS_ARG] = *most_common_elem_freqs;
+       newfcinfo->args[ELEM_COUNT_HISTOGRAM_ARG] = *elem_count_histogram;
+       newfcinfo->args[RANGE_LENGTH_HISTOGRAM_ARG] = *range_length_histogram;
+       newfcinfo->args[RANGE_EMPTY_FRAC_ARG] = *range_empty_frac;
+       newfcinfo->args[RANGE_BOUNDS_HISTOGRAM_ARG] = *range_bounds_histogram;
+
+       return attribute_statistics_update_internal(reloid, attname, attnum,
+                                                                                               inherited, newfcinfo);
+}
+
+/*
+ * Delete attribute statistics.
+ */
+bool
+delete_attribute_statistics(Relation rel, AttrNumber attnum, bool inherited)
+{
+       return delete_pg_statistic(RelationGetRelid(rel), attnum, inherited);
+}
index d6631e9a9a47cad38f2ad901e6c8ca6ea8074263..990a7511d040957e19d6cab7c9cdb72c871fb02a 100644 (file)
@@ -21,6 +21,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "nodes/makefuncs.h"
+#include "statistics/statistics.h"
 #include "statistics/stat_utils.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -57,6 +58,8 @@ static struct StatsArgInfo relarginfo[] =
 };
 
 static bool relation_statistics_update(FunctionCallInfo fcinfo);
+static bool relation_statistics_update_internal(Oid reloid,
+                                                                                               FunctionCallInfo fcinfo);
 
 /*
  * Internal function for modifying statistics for a relation.
@@ -64,25 +67,9 @@ static bool relation_statistics_update(FunctionCallInfo fcinfo);
 static bool
 relation_statistics_update(FunctionCallInfo fcinfo)
 {
-       bool            result = true;
        char       *nspname;
        char       *relname;
        Oid                     reloid;
-       Relation        crel;
-       BlockNumber relpages = 0;
-       bool            update_relpages = false;
-       float           reltuples = 0;
-       bool            update_reltuples = false;
-       BlockNumber relallvisible = 0;
-       bool            update_relallvisible = false;
-       BlockNumber relallfrozen = 0;
-       bool            update_relallfrozen = false;
-       HeapTuple       ctup;
-       Form_pg_class pgcform;
-       int                     replaces[4] = {0};
-       Datum           values[4] = {0};
-       bool            nulls[4] = {0};
-       int                     nreplaces = 0;
        Oid                     locked_table = InvalidOid;
 
        stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
@@ -101,6 +88,32 @@ relation_statistics_update(FunctionCallInfo fcinfo)
                                                                          ShareUpdateExclusiveLock, 0,
                                                                          RangeVarCallbackForStats, &locked_table);
 
+       return relation_statistics_update_internal(reloid, fcinfo);
+}
+
+/*
+ * Workhorse function for relation_statistics_update.
+ */
+static bool
+relation_statistics_update_internal(Oid reloid, FunctionCallInfo fcinfo)
+{
+       BlockNumber relpages = 0;
+       bool            update_relpages = false;
+       float           reltuples = 0;
+       bool            update_reltuples = false;
+       BlockNumber relallvisible = 0;
+       bool            update_relallvisible = false;
+       BlockNumber relallfrozen = 0;
+       bool            update_relallfrozen = false;
+       Relation        crel;
+       HeapTuple       ctup;
+       Form_pg_class pgcform;
+       int                     replaces[4] = {0};
+       Datum           values[4] = {0};
+       bool            nulls[4] = {0};
+       int                     nreplaces = 0;
+       bool            result = true;
+
        if (!PG_ARGISNULL(RELPAGES_ARG))
        {
                relpages = PG_GETARG_UINT32(RELPAGES_ARG);
@@ -241,3 +254,44 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS)
 
        PG_RETURN_BOOL(result);
 }
+
+/*
+ * Import relation statistics from NullableDatum inputs for all statitical
+ * values.
+ *
+ * For now, the 'version' argument is ignored. In the future it can be used
+ * to interpret older statistics properly.
+ */
+bool
+import_relation_statistics(Relation rel,
+                                                  const NullableDatum *version,
+                                                  const NullableDatum *relpages,
+                                                  const NullableDatum *reltuples,
+                                                  const NullableDatum *relallvisible,
+                                                  const NullableDatum *relallfrozen)
+{
+       LOCAL_FCINFO(newfcinfo, NUM_RELATION_STATS_ARGS);
+
+       Assert(relpages);
+       Assert(reltuples);
+       Assert(relallvisible);
+       Assert(relallfrozen);
+
+       InitFunctionCallInfoData(*newfcinfo, NULL, NUM_RELATION_STATS_ARGS,
+                                                        InvalidOid, NULL, NULL);
+
+       newfcinfo->args[RELSCHEMA_ARG].value =
+               CStringGetTextDatum(get_namespace_name(RelationGetNamespace(rel)));
+       newfcinfo->args[RELSCHEMA_ARG].isnull = false;
+       newfcinfo->args[RELNAME_ARG].value =
+               CStringGetTextDatum(RelationGetRelationName(rel));
+       newfcinfo->args[RELNAME_ARG].isnull = false;
+
+       newfcinfo->args[RELPAGES_ARG] = *relpages;
+       newfcinfo->args[RELTUPLES_ARG] = *reltuples;
+       newfcinfo->args[RELALLVISIBLE_ARG] = *relallvisible;
+       newfcinfo->args[RELALLFROZEN_ARG] = *relallfrozen;
+
+       return relation_statistics_update_internal(RelationGetRelid(rel),
+                                                                                          newfcinfo);
+}
index 8f9b9d237fd5d8cc2da0fcdb185849df36449662..0b163103a7293343bcfe1e1c5343f531d9f0195d 100644 (file)
@@ -128,4 +128,29 @@ extern StatisticExtInfo *choose_best_statistics(List *stats, char requiredkind,
                                                                                                int nclauses);
 extern HeapTuple statext_expressions_load(Oid stxoid, bool inh, int idx);
 
+extern bool import_relation_statistics(Relation rel,
+                                                                          const NullableDatum *version,
+                                                                          const NullableDatum *relpages,
+                                                                          const NullableDatum *reltuples,
+                                                                          const NullableDatum *relallvisible,
+                                                                          const NullableDatum *relallfrozen);
+extern bool import_attribute_statistics(Relation rel,
+                                                                               AttrNumber attnum, bool inherited,
+                                                                               const NullableDatum *version,
+                                                                               const NullableDatum *null_frac,
+                                                                               const NullableDatum *avg_width,
+                                                                               const NullableDatum *n_distinct,
+                                                                               const NullableDatum *most_common_vals,
+                                                                               const NullableDatum *most_common_freqs,
+                                                                               const NullableDatum *histogram_bounds,
+                                                                               const NullableDatum *correlation,
+                                                                               const NullableDatum *most_common_elems,
+                                                                               const NullableDatum *most_common_elem_freqs,
+                                                                               const NullableDatum *elem_count_histogram,
+                                                                               const NullableDatum *range_length_histogram,
+                                                                               const NullableDatum *range_empty_frac,
+                                                                               const NullableDatum *range_bounds_histogram);
+extern bool delete_attribute_statistics(Relation rel,
+                                                                               AttrNumber attnum, bool inherited);
+
 #endif                                                 /* STATISTICS_H */