public inbox for [email protected]  
help / color / mirror / Atom feed
From: Etsuro Fujita <[email protected]>
To: Corey Huinker <[email protected]>
Cc: Robert Haas <[email protected]>
Cc: [email protected] <[email protected]>
Subject: Re: use of SPI by postgresImportForeignStatistics
Date: Wed, 8 Jul 2026 21:04:51 +0900
Message-ID: <CAPmGK16-=HvkijvSiDfo8=JmrHQ4ra79gSnJbKzW4Oob9yPfdQ@mail.gmail.com> (raw)
In-Reply-To: <CAPmGK15eTnEtn_tPm+o0QA-jEnJUW+B2FPAF=vGNkFAGMApWNQ@mail.gmail.com>
References: <CA+TgmoYqMtWb4zLUkT98oFnEkJ=Wz0Pw-ggDJrp9wnSXPzUaeQ@mail.gmail.com>
	<CAPmGK161TJ4G01eptv3sBqgxa4SNHDE9VRqoC6jy5Y6G3D8yDw@mail.gmail.com>
	<CA+TgmoZpwfTGqqp=0efV99e_BMYWdLFq2XKRWVSDcukifkZMeA@mail.gmail.com>
	<CADkLM=dxe8NbQu4RNi2OFXpaRKdpS0-CHGm6Mqtf_rW155BM4g@mail.gmail.com>
	<CADkLM=cV=uezAKyZdyyxnZOBRhGLUSbeYfDj5_jbDCLP9LP24Q@mail.gmail.com>
	<CA+TgmoaVcgZJpwxyi7HEfNMJhmm7BXkTQ468jvxkW2QzQZRkvg@mail.gmail.com>
	<CADkLM=dy18dDJ+j3-_Ofn11vt5J7zF33oTWnE6N=ePc83uS91A@mail.gmail.com>
	<CAPmGK15MfVT2H+PUvA_nAJvySGr_pAVkVx3KcnVXWGO36acWLA@mail.gmail.com>
	<CADkLM=dr5bfB8YN-bV1U0NDvyVO7a6uuz0BsAi89a1Md4A==8g@mail.gmail.com>
	<CAPmGK14P2GRe78hp6fZ3vExU_9RRDjMsYVOx7AALvUQ-hKTUiQ@mail.gmail.com>
	<CADkLM=cL9ZGvO-72QWyXA5Wqv-4P3T1fp+VFJy5e6k3Hf=POMw@mail.gmail.com>
	<CA+TgmoZdTVeXRQo__cEr_1y+mUO9nubOX=6PEfw7Cr0UfZQn6w@mail.gmail.com>
	<CADkLM=fe=ycPdwT2wE_iYwK8e8viMbWvwWhXR9vDe+55KKF-KQ@mail.gmail.com>
	<CADkLM=dWJaw04u9FU=WkykOy7bu7B5eKmoDkfVz6JXh7mdemkw@mail.gmail.com>
	<CA+TgmoY3a_S0munZ0wg4hQpMKVMyM_jpi6aGhFR0gAm1jLukag@mail.gmail.com>
	<CADkLM=cdKjgphHpFW8kKukHF+DVYKogHcqLXO=BA8MGfiRFqQA@mail.gmail.com>
	<CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com>
	<CAPmGK17W1sUB3JnkRoBpgO0_iSH5HRcc0_nxk8Us4KYsQhuQxg@mail.gmail.com>
	<CA+TgmoZd5MDhcx6uav6MWxhM42yWcfaRAHYKNtNkuog=yrKAgw@mail.gmail.com>
	<CADkLM=f_c7Xoq-bSzw28LgvxPa+OQ3rVow=xwkTyjz1J0=pMdw@mail.gmail.com>
	<CAPmGK14dY-Cex2rc08TinQjHR4Q1L6nqPBfdEBRZusP7n_TZSA@mail.gmail.com>
	<CADkLM=enKc-3-uTjUSXXhoRhSKDHVdb_ate+S2dtkEcscW5bvA@mail.gmail.com>
	<CAPmGK17rNA=dknxHCXbUoQ_xe2PQcPq0d-bhTr47wrUfXrDLFA@mail.gmail.com>
	<CADkLM=dYZ2XJ=nUMSYgA6D3VEFQ4rvpyDLKD-RrnfESUGxktiA@mail.gmail.com>
	<CAPmGK15eTnEtn_tPm+o0QA-jEnJUW+B2FPAF=vGNkFAGMApWNQ@mail.gmail.com>

On Fri, Jul 3, 2026 at 6:47 PM Etsuro Fujita <[email protected]> wrote:
> On Thu, Jul 2, 2026 at 5:35 AM Corey Huinker <[email protected]> wrote:
> > On Wed, Jul 1, 2026 at 8:10 AM Etsuro Fujita <[email protected]> wrote:
> >> On Wed, Jul 1, 2026 at 12:55 AM Corey Huinker <[email protected]> wrote:
>
> >> >> In relation to this change, I moved helper functions set_XXX_arg()
> >> >> that you added to relation_stats.c and attribute_stats.c to
> >> >> postgres_fdw.c.  The helper functions had soft error handling, but in
> >> >> the postgres_fdw use, there is no need for that, so I removed that
> >> >> handling as well.
> >> >
> >> > My inclination would be to move the set_*_arg functions could be moved to some common utility file in v20, as other FDWs will find themselves with the same need.
> >>
> >> Seems like a good idea.  I moved the set_*_arg functions to
> >> stat_utils.c, and renamed them to stats_set_*_arg.  Attached is a new
> >> version of the patch.
> >
> > I'm concerned that by making these non-error-safe function calls available, we create a barrier to making those same functions error-safe in the future.
> >
> >> > As for removing the error-handling, it might still be handy because it would allow us to give a more context-aware error message, rather than the very narrow "this_string is an invalid this_type", for string that the user most certainly never saw. Having said that, it's something we could easily change back to error-safe if we wanted to.
> >>
> >> Ok, I think we could do so later if needed.
> >
> > I agree we could do it later if they were private functions, no problem. But if they're available outside of postgres_fdw then changing them to be error-safe potentially disrupts other callers. Someone please let me know if I'm being unnecessarily cautious here.
>
> Ah, I misunderstood your comments.  I agree with you here, so I'll
> move back the functions to postgres_fdw.c.

Done.

> > Other Thoughts:
> >
> > 1. The internal functions should accept a server_version_number parameter, and we should document that setting that parameter to 0 mean that we can assume the current version. I know that we presently have no translation issues going forward with statistics, but someday that won't be the case, and if we don't have this parameter in place then it'll be too late to fix it.
>
> Good idea!  Will do.

I used the name "version" for the consistency with
pg_restore_relation_stats/pg_restore_attribute_stats.  Also, as I
couldn't find a note about the arrangement in the source code, I added
this comment to match a comment in stats_fill_fcinfo_from_arg_pairs:

 * For now, the 'version' argument is ignored. In the future it can be used
 * to interpret older statistics properly.

> > 3. The import_stats_functions in their current "bridging" form should do null checks on all of the NullableDatum pointers, or at least Assert()s on them.
>
> Will do.

Done.  As the functions are provided for developers, I just added the
assertions.

Also, I updated comments/docs a little bit.  Attached is a new version
of the patch.  I'm planning to push and back-patch it, if no
objections.

Best regards,
Etsuro Fujita


Attachments:

  [application/octet-stream] v3-Remove-SPI-from-stat-import-in-postgres-fdw-efujita.patch (34.0K, ../CAPmGK16-=HvkijvSiDfo8=JmrHQ4ra79gSnJbKzW4Oob9yPfdQ@mail.gmail.com/2-v3-Remove-SPI-from-stat-import-in-postgres-fdw-efujita.patch)
  download | inline diff:
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 5ebae1cedc2..048f624ba0c 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -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;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 0ea72a64ce5..e6e0a4ab9b5 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"
@@ -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;
+	}
 }
 
 /*
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index e868da00ace..ed2c8b58e60 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -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;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 8685a078c52..0103fdacfdf 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -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>
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 1cc4d657231..ba22671cbf9 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -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.
@@ -688,3 +705,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);
+}
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index d6631e9a9a4..990a7511d04 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -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);
+}
diff --git a/src/include/statistics/statistics.h b/src/include/statistics/statistics.h
index 8f9b9d237fd..0b163103a72 100644
--- a/src/include/statistics/statistics.h
+++ b/src/include/statistics/statistics.h
@@ -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 */


view thread (33+ messages)

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected]
  Subject: Re: use of SPI by postgresImportForeignStatistics
  In-Reply-To: <CAPmGK16-=HvkijvSiDfo8=JmrHQ4ra79gSnJbKzW4Oob9yPfdQ@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox