On 8/31/20 6:19 PM, Ashutosh Bapat wrote:
On Mon, Aug 31, 2020 at 3:36 PM Andrey V. Lepikhov
<a.lepik...@postgrespro.ru> wrote:

Thanks for this helpful feedback.
I think the patch has some other problems like it works only for
regular tables on foreign server but a foreign table can be pointing
to any relation like a materialized view, partitioned table or a
foreign table on the foreign server all of which have statistics
associated with them. I didn't look closely but it does not consider
that the foreign table may not have all the columns from the relation
on the foreign server or may have different names. But I think those
problems are kind of secondary. We have to agree on the design first.

In accordance with discussion i made some changes in the patch:
1. The extract statistic routine moved into the core.
2. Serialized stat contains 'version' field to indicate format of statistic received. 3. ANALYZE and VACUUM ANALYZE uses this approach only in the case of implicit analysis of the relation.

I am currently keeping limitation of using the approach for regular relations only, because i haven't studied the specifics of another types of relations.
But I don't know any reason to keep this limit in the future.

The patch in attachment is very raw. I publish for further substantive discussion.

--
regards,
Andrey Lepikhov
Postgres Professional
From 9cfd9b8a43691f1dacd0967dcc32fdf8414ddb56 Mon Sep 17 00:00:00 2001
From: "Andrey V. Lepikhov" <a.lepik...@postgrespro.ru>
Date: Tue, 4 Aug 2020 09:29:37 +0500
Subject: [PATCH] Pull statistic for a foreign table from remote server.

Add the extract_relation_statistics() routine that convert statistics
on the relation into json format.
All OIDs - starelid, staop[] stacoll[] is converted to a portable
representation. Operation uniquely defined by set of features:
namespace, operator name, left operator namespace and name, right
operator namespace and name.
Collation uniquely defined by namespace, collation name and encoding.
New fdw API routine GetForeignRelStat() implements access to this
machinery and returns JSON string to the caller.
This function is called by ANALYZE command (without explicit relation
name) as an attempt to reduce the cost of updating statistics.
If attempt fails, ANALYZE go the expensive way. Add this feature into
the VACUUM ANALYZE and autovacuum.
In accordance with discussion [1] move the extract_relation_statistics()
routine into the core.

ToDo: tests on custom operations and collations.

1. https://www.postgresql.org/message-id/flat/1155731.1593832096%40sss.pgh.pa.us
---
 contrib/postgres_fdw/Makefile                 |   2 +-
 contrib/postgres_fdw/deparse.c                |   8 +
 .../postgres_fdw/expected/foreign_stat.out    | 112 +++
 contrib/postgres_fdw/postgres_fdw.c           |  49 ++
 contrib/postgres_fdw/postgres_fdw.h           |   1 +
 contrib/postgres_fdw/sql/foreign_stat.sql     |  46 +
 src/backend/commands/analyze.c                | 794 ++++++++++++++++++
 src/backend/commands/vacuum.c                 |  13 +-
 src/backend/utils/adt/json.c                  |   6 +
 src/backend/utils/cache/lsyscache.c           | 167 ++++
 src/include/catalog/pg_proc.dat               |   3 +
 src/include/catalog/pg_statistic.h            |   1 +
 src/include/foreign/fdwapi.h                  |   2 +
 src/include/utils/json.h                      |   1 +
 src/include/utils/lsyscache.h                 |   8 +
 15 files changed, 1211 insertions(+), 2 deletions(-)
 create mode 100644 contrib/postgres_fdw/expected/foreign_stat.out
 create mode 100644 contrib/postgres_fdw/sql/foreign_stat.sql

diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index ee8a80a392..a5a838b8fc 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -16,7 +16,7 @@ SHLIB_LINK_INTERNAL = $(libpq)
 EXTENSION = postgres_fdw
 DATA = postgres_fdw--1.0.sql
 
-REGRESS = postgres_fdw
+REGRESS = postgres_fdw foreign_stat
 
 ifdef USE_PGXS
 PG_CONFIG = pg_config
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index ad37a74221..e63cb4982f 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -2053,6 +2053,14 @@ deparseAnalyzeSizeSql(StringInfo buf, Relation rel)
 	appendStringInfo(buf, "::pg_catalog.regclass) / %d", BLCKSZ);
 }
 
+void
+deparseGetStatSql(StringInfo buf, Relation rel)
+{
+	appendStringInfo(buf, "SELECT * FROM extract_relation_statistics('");
+	deparseRelation(buf, rel);
+	appendStringInfoString(buf, "');");
+}
+
 /*
  * Construct SELECT statement to acquire sample rows of given relation.
  *
diff --git a/contrib/postgres_fdw/expected/foreign_stat.out b/contrib/postgres_fdw/expected/foreign_stat.out
new file mode 100644
index 0000000000..46fd2f8427
--- /dev/null
+++ b/contrib/postgres_fdw/expected/foreign_stat.out
@@ -0,0 +1,112 @@
+CREATE TABLE ltable (a int, b real);
+CREATE FOREIGN TABLE ftable (a int) server loopback options (table_name 'ltable');
+VACUUM ANALYZE;
+-- Check statistic interface routine on an empty table.
+SELECT * FROM extract_relation_statistics('ltable');
+                                                        extract_relation_statistics                                                        
+-------------------------------------------------------------------------------------------------------------------------------------------
+ {"version" : 1, "namespace" : "public", "relname" : "ltable", "sta_num_slots" : 5, "relpages" : 0, "reltuples" : 0.000000, "attrs" : [ ]}
+(1 row)
+
+SELECT * FROM extract_relation_statistics('ftable');
+ERROR:  Can be used for ordinary relation only. Reltype: f.
+-- Check statistic interface routine on non-empty tables.
+INSERT INTO ltable (a, b) (SELECT *, 1.01 FROM generate_series(1, 1E4));
+-- hereinafter we don't want to depend on analyze order. If table will be
+-- analyzed before ltable than we got out-of-date statistic.
+ANALYZE ltable;
+ANALYZE;
+SELECT * FROM extract_relation_statistics('ltable');
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          extract_relation_statistics                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"version" : 1, "namespace" : "public", "relname" : "ltable", "sta_num_slots" : 5, "relpages" : 45, "reltuples" : 10000.000000, "attrs" : [{"attname" : "a", "inh" : "false", "nullfrac" : 0.000000, "width" : 4, "distinct" : -1.000000, "stakind" : [2,3,0,0,0], "staop" : [" .<. .int4. .int4"," .<. .int4. .int4"," "," "," "], "stacoll" : [" "," "," "," "," "], "nn" : 101, "values1" : [1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000], "values2" : [ ], "values3" : [ ], "values4" : [ ], "values5" : [ ], "numbers1" : [ ], "nn" : 1, "numbers2" : [1], "numbers3" : [ ], "numbers4" : [ ], "numbers5" : [ ]}, {"attname" : "b", "inh" : "false", "nullfrac" : 0.000000, "width" : 4, "distinct" : 1.000000, "stakind" : [1,3,0,0,0], "staop" : [" .=. .float4. .float4"," .<. .float4. .float4"," "," "," "], "stacoll" : [" "," "," "," "," "], "nn" : 1, "values1" : [1.01], "values2" : [ ], "values3" : [ ], "values4" : [ ], "values5" : [ ], "nn" : 1, "numbers1" : [1], "nn" : 1, "numbers2" : [1], "numbers3" : [ ], "numbers4" : [ ], "numbers5" : [ ]}]}
+(1 row)
+
+-- Check ANALYZE on foreign table
+INSERT INTO ltable (a, b) (SELECT *, 2.01 FROM generate_series(1E4, 2E4));
+ANALYZE ltable;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable;
+                                       QUERY PLAN                                       
+----------------------------------------------------------------------------------------
+ Seq Scan on ltable  (cost=0.00..289.01 rows=20001 width=8) (actual rows=20001 loops=1)
+(1 row)
+
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable;
+                                          QUERY PLAN                                          
+----------------------------------------------------------------------------------------------
+ Foreign Scan on ftable  (cost=100.00..445.00 rows=10000 width=4) (actual rows=20001 loops=1)
+(1 row)
+
+ANALYZE;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable;
+                                          QUERY PLAN                                          
+----------------------------------------------------------------------------------------------
+ Foreign Scan on ftable  (cost=100.00..789.03 rows=20001 width=4) (actual rows=20001 loops=1)
+(1 row)
+
+-- Check selectivity
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable WHERE a > 10;
+                                       QUERY PLAN                                       
+----------------------------------------------------------------------------------------
+ Seq Scan on ltable  (cost=0.00..339.01 rows=19991 width=8) (actual rows=19991 loops=1)
+   Filter: (a > 10)
+   Rows Removed by Filter: 10
+(3 rows)
+
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable WHERE a > 10;
+                                          QUERY PLAN                                          
+----------------------------------------------------------------------------------------------
+ Foreign Scan on ftable  (cost=100.00..838.83 rows=19991 width=4) (actual rows=19991 loops=1)
+(1 row)
+
+-- Check new attribute
+ALTER TABLE ltable ADD COLUMN c int DEFAULT 42;
+ALTER TABLE ftable ADD COLUMN c int;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable WHERE a > 10 AND c < 15;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Seq Scan on ltable  (cost=0.00..389.01 rows=6664 width=12) (actual rows=0 loops=1)
+   Filter: ((a > 10) AND (c < 15))
+   Rows Removed by Filter: 20001
+(3 rows)
+
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable WHERE a > 10 AND c < 15;
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Foreign Scan on ftable  (cost=100.00..622.29 rows=6664 width=8) (actual rows=0 loops=1)
+(1 row)
+
+ANALYZE ltable;
+ANALYZE;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable WHERE a > 10 AND c < 15;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Seq Scan on ltable  (cost=0.00..389.01 rows=1 width=12) (actual rows=0 loops=1)
+   Filter: ((a > 10) AND (c < 15))
+   Rows Removed by Filter: 20001
+(3 rows)
+
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable WHERE a > 10 AND c < 15;
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Foreign Scan on ftable  (cost=100.00..489.03 rows=1 width=8) (actual rows=0 loops=1)
+(1 row)
+
+-- Test default vacuum analyzes foreign relation
+INSERT INTO ltable (a, b) (SELECT *, 2.01 FROM generate_series(2E4, 3E4));
+ANALYZE ltable;
+VACUUM ANALYZE;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable WHERE a > 100 AND c < 43;
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Seq Scan on ltable  (cost=0.00..593.03 rows=29902 width=12) (actual rows=29902 loops=1)
+   Filter: ((a > 100) AND (c < 43))
+   Rows Removed by Filter: 100
+(3 rows)
+
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable WHERE a > 100 AND c < 43;
+                                          QUERY PLAN                                           
+-----------------------------------------------------------------------------------------------
+ Foreign Scan on ftable  (cost=100.00..1291.07 rows=29902 width=8) (actual rows=29902 loops=1)
+(1 row)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index a31abce7c9..092da7fd96 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -391,6 +391,7 @@ static void postgresGetForeignUpperPaths(PlannerInfo *root,
 										 RelOptInfo *input_rel,
 										 RelOptInfo *output_rel,
 										 void *extra);
+static char *postgresGetForeignRelStat(Relation rel);
 
 /*
  * Helper functions
@@ -558,6 +559,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	/* Support functions for upper relation push-down */
 	routine->GetForeignUpperPaths = postgresGetForeignUpperPaths;
 
+	routine->GetForeignRelStat = postgresGetForeignRelStat;
 	PG_RETURN_POINTER(routine);
 }
 
@@ -6583,3 +6585,50 @@ find_em_expr_for_input_target(PlannerInfo *root,
 	elog(ERROR, "could not find pathkey item to sort");
 	return NULL;				/* keep compiler quiet */
 }
+
+static char *
+postgresGetForeignRelStat(Relation rel)
+{
+	ForeignTable *table;
+	UserMapping *user;
+	PGconn	   *conn;
+	unsigned int cursor_number;
+	StringInfoData sql;
+	PGresult   *volatile res = NULL;
+	char fetch_sql[64];
+	char *json;
+
+	table = GetForeignTable(RelationGetRelid(rel));
+	user = GetUserMapping(rel->rd_rel->relowner, table->serverid);
+	conn = GetConnection(user, false);
+
+	cursor_number = GetCursorNumber(conn);
+	initStringInfo(&sql);
+	appendStringInfo(&sql, "DECLARE c%u CURSOR FOR ", cursor_number);
+	deparseGetStatSql(&sql, rel);
+
+	res = pgfdw_exec_query(conn, sql.data);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		/*
+		 * TODO: need to process situation of missing statistic serializer at the
+		 * remote end.
+		 */
+		pgfdw_report_error(ERROR, res, conn, false, sql.data);
+
+	PQclear(res);
+
+	snprintf(fetch_sql, sizeof(fetch_sql), "FETCH FROM c%u", cursor_number);
+	res = pgfdw_exec_query(conn, fetch_sql);
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+		pgfdw_report_error(ERROR, res, conn, false, sql.data);
+
+	Assert(PQntuples(res) == 1);
+	Assert(PQnfields(res) == 1);
+
+	json = pstrdup(PQgetvalue(res, 0, 0));
+	PQclear(res);
+	close_cursor(conn, cursor_number);
+	ReleaseConnection(conn);
+
+	return json;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index eef410db39..bb590bfc21 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -190,6 +190,7 @@ extern void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
 extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel);
 extern void deparseAnalyzeSql(StringInfo buf, Relation rel,
 							  List **retrieved_attrs);
+extern void deparseGetStatSql(StringInfo buf, Relation rel);
 extern void deparseStringLiteral(StringInfo buf, const char *val);
 extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
 extern Expr *find_em_expr_for_input_target(PlannerInfo *root,
diff --git a/contrib/postgres_fdw/sql/foreign_stat.sql b/contrib/postgres_fdw/sql/foreign_stat.sql
new file mode 100644
index 0000000000..1fe1f319e3
--- /dev/null
+++ b/contrib/postgres_fdw/sql/foreign_stat.sql
@@ -0,0 +1,46 @@
+CREATE TABLE ltable (a int, b real);
+CREATE FOREIGN TABLE ftable (a int) server loopback options (table_name 'ltable');
+VACUUM ANALYZE;
+
+-- Check statistic interface routine on an empty table.
+SELECT * FROM extract_relation_statistics('ltable');
+SELECT * FROM extract_relation_statistics('ftable');
+
+-- Check statistic interface routine on non-empty tables.
+INSERT INTO ltable (a, b) (SELECT *, 1.01 FROM generate_series(1, 1E4));
+-- hereinafter we don't want to depend on analyze order. If table will be
+-- analyzed before ltable than we got out-of-date statistic.
+ANALYZE ltable;
+ANALYZE;
+SELECT * FROM extract_relation_statistics('ltable');
+
+-- Check ANALYZE on foreign table
+INSERT INTO ltable (a, b) (SELECT *, 2.01 FROM generate_series(1E4, 2E4));
+ANALYZE ltable;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable;
+ANALYZE;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable;
+
+-- Check selectivity
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable WHERE a > 10;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable WHERE a > 10;
+
+-- Check new attribute
+ALTER TABLE ltable ADD COLUMN c int DEFAULT 42;
+ALTER TABLE ftable ADD COLUMN c int;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable WHERE a > 10 AND c < 15;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable WHERE a > 10 AND c < 15;
+
+ANALYZE ltable;
+ANALYZE;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable WHERE a > 10 AND c < 15;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable WHERE a > 10 AND c < 15;
+
+-- Test default vacuum analyzes foreign relation
+INSERT INTO ltable (a, b) (SELECT *, 2.01 FROM generate_series(2E4, 3E4));
+ANALYZE ltable;
+VACUUM ANALYZE;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ltable WHERE a > 100 AND c < 43;
+EXPLAIN (TIMING OFF, SUMMARY OFF, COSTS ON, ANALYZE) SELECT * FROM ftable WHERE a > 100 AND c < 43;
+
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5c6b..6177c21681 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -30,6 +30,7 @@
 #include "catalog/catalog.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
@@ -38,8 +39,10 @@
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
+#include "common/jsonapi.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_oper.h"
@@ -58,9 +61,11 @@
 #include "utils/datum.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
+#include "utils/json.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/pg_rusage.h"
+#include "utils/regproc.h"
 #include "utils/sampling.h"
 #include "utils/sortsupport.h"
 #include "utils/syscache.h"
@@ -107,6 +112,11 @@ static void update_attstats(Oid relid, bool inh,
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 
+static const char *relstat_to_json(const char *relname);
+static bool parse_statjson(Relation rel, char *buffer, size_t size,
+						   VacAttrStats **vacattrstats, int *natts,
+						   BlockNumber *relpages, double *reltuples);
+static bool update_foreign_relation_stat(Relation rel);
 
 /*
  *	analyze_rel() -- analyze one relation
@@ -193,6 +203,23 @@ analyze_rel(Oid relid, RangeVar *relation,
 		return;
 	}
 
+	/*
+	 * Use cheap approach to update foreign relation if user do not explicitly
+	 * mentioned this relation.
+	 * If user wants to ANALYZE this particular relation maybe he probably know
+	 * that foreign statistics are also out of date.
+	 */
+	if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && relation == NULL &&
+		update_foreign_relation_stat(onerel))
+	{
+		/*
+		 * The foreign relation statistics was updated by existed stat tuple
+		 * from remote server.
+		 */
+		relation_close(onerel, ShareUpdateExclusiveLock);
+		return;
+	}
+
 	/*
 	 * Check that it's of an analyzable relkind, and set up appropriately.
 	 */
@@ -1421,6 +1448,773 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	return numrows;
 }
 
+typedef enum
+{
+	JS_EXPECT_STAVER_VALUE,
+	JS_EXPECT_NSPNAME_VALUE,
+	JS_EXPECT_RELNAME_VALUE,
+	JS_EXPECT_SLOTSNUM_VALUE,
+	JS_EXPECT_RELPAGES_VALUE,
+	JS_EXPECT_RELTUPLES_VALUE,
+	JS_EXPECT_ATTNAME_FIELD,
+	JS_EXPECT_ATTNAME_VALUE,
+	JS_EXPECT_INH_VALUE,
+	JS_EXPECT_NULLFRAC_VALUE,
+	JS_EXPECT_WIDTH_VALUE,
+	JS_EXPECT_DISTINCT_VALUE,
+	JS_EXPECT_KIND_VALUE,
+	JS_EXPECT_STAOP_VALUE,
+	JS_EXPECT_STACOLL_VALUE,
+	JS_EXPECT_ARRSIZE_VALUE,
+	JS_EXPECT_NUMBERS_VALUE,
+	JS_EXPECT_VALUES_VALUE,
+	JS_EXPECT_PARAM_FIELD,
+	JS_INVALID_STAT_VERSION
+} JsonStatSemanticState;
+
+typedef struct JsonStatParseState
+{
+	Relation rel;
+	int natts;
+	int state;
+	int arraysize; /* size of the array */
+	int arrayval; /* current position in the array */
+	int numbersN;
+	int valuesN;
+
+	char *nspname;
+	char *relname;
+
+	BlockNumber relpages;
+	double reltuples;
+	VacAttrStats **vas;
+} JsonStatParseState;
+
+static bool skip_attr = false;
+
+static void
+json_stat_object_field_start(void *state, char *fname, bool isnull)
+{
+	JsonStatParseState *parse = (JsonStatParseState *) state;
+	int attnum = parse->natts - 1;
+
+	/* skip processing if incompatible statistic version was detected */
+	if (parse->state == JS_INVALID_STAT_VERSION)
+		return;
+
+	if (skip_attr && strcmp(fname, "attname") != 0)
+		return;
+
+	Assert(parse->state == JS_EXPECT_PARAM_FIELD ||
+		   parse->state == JS_EXPECT_ATTNAME_FIELD);
+	Assert(parse->arrayval < 0);
+	if (strcmp(fname, "version") == 0)
+		parse->state = JS_EXPECT_STAVER_VALUE;
+	else if (strcmp(fname, "namespace") == 0)
+		parse->state = JS_EXPECT_NSPNAME_VALUE;
+	else if (strcmp(fname, "relname") == 0)
+		parse->state = JS_EXPECT_RELNAME_VALUE;
+	else if (strcmp(fname, "sta_num_slots") == 0)
+		parse->state = JS_EXPECT_SLOTSNUM_VALUE;
+	else if (strcmp(fname, "relpages") == 0)
+		parse->state = JS_EXPECT_RELPAGES_VALUE;
+	else if (strcmp(fname, "reltuples") == 0)
+		parse->state = JS_EXPECT_RELTUPLES_VALUE;
+	else if(strcmp(fname, "attrs") == 0)
+		parse->state = JS_EXPECT_ATTNAME_FIELD;
+	else if (strcmp(fname, "attname") == 0)
+	{
+		parse->state = JS_EXPECT_ATTNAME_VALUE;
+		skip_attr = false;
+	}
+	else if (strcmp(fname, "nullfrac") == 0)
+		parse->state = JS_EXPECT_NULLFRAC_VALUE;
+	else if (strcmp(fname, "inh") == 0)
+		parse->state = JS_EXPECT_INH_VALUE;
+		else if (strcmp(fname, "width") == 0)
+		parse->state = JS_EXPECT_WIDTH_VALUE;
+	else if (strcmp(fname, "distinct") == 0)
+		parse->state = JS_EXPECT_DISTINCT_VALUE;
+	else if (strcmp(fname, "nn") == 0)
+	{
+		Assert(parse->arraysize == 0);
+		parse->state = JS_EXPECT_ARRSIZE_VALUE;
+	}
+	else
+	{
+		parse->arrayval = 0;
+		if (strcmp(fname, "stakind") == 0)
+			parse->state = JS_EXPECT_KIND_VALUE;
+		else if (strcmp(fname, "staop") == 0)
+			parse->state = JS_EXPECT_STAOP_VALUE;
+		else if (strcmp(fname, "stacoll") == 0)
+			parse->state = JS_EXPECT_STACOLL_VALUE;
+		else if (strstr(fname, "numbers") != NULL)
+		{
+			parse->numbersN = atoi(&fname[7]);
+			Assert(parse->numbersN > 0 && parse->numbersN <= 5);
+			if (parse->arraysize > 0)
+				parse->vas[attnum]->stanumbers[parse->numbersN-1] =
+							(float4 *) palloc(parse->arraysize * sizeof(float4));
+			else
+				parse->vas[attnum]->stanumbers[parse->numbersN-1] = NULL;
+
+			parse->vas[attnum]->numnumbers[parse->numbersN-1] = parse->arraysize;
+			parse->state = JS_EXPECT_NUMBERS_VALUE;
+		}
+		else if (strstr(fname, "values") != NULL)
+		{
+			parse->valuesN = atoi(&fname[6]);
+			Assert(parse->valuesN > 0 && parse->valuesN <= 5);
+
+			if (parse->arraysize > 0)
+				parse->vas[parse->natts - 1]->stavalues[parse->valuesN-1] =
+							(Datum *) palloc(parse->arraysize * sizeof(Datum));
+			else
+				parse->vas[parse->natts - 1]->stavalues[parse->valuesN-1] = NULL;
+
+			parse->vas[attnum]->numvalues[parse->valuesN-1] = parse->arraysize;
+			parse->state = JS_EXPECT_VALUES_VALUE;
+		}
+		else
+			elog(ERROR, "Unknown stat parameter in JSON string: %s", fname);
+	}
+}
+
+static void
+json_stat_array_end(void *state)
+{
+	JsonStatParseState *parse = (JsonStatParseState *) state;
+
+	/* skip processing if incompatible statistic version was detected */
+	if (parse->state == JS_INVALID_STAT_VERSION)
+		return;
+
+	parse->arrayval = -1;
+	parse->arraysize = 0;
+	parse->numbersN = 0;
+	parse->valuesN = 0;
+}
+
+static void
+json_stat_array_element_end(void *state, bool isnull)
+{
+	JsonStatParseState *parse = (JsonStatParseState *) state;
+
+	/* skip processing if incompatible statistic version was detected */
+	if (parse->state == JS_INVALID_STAT_VERSION)
+		return;
+
+	Assert(!isnull);
+
+	if (parse->arrayval < 0) // Debug
+		return;
+
+	parse->arrayval++;
+}
+
+static void
+json_stat_field_end(void *state, char *fname, bool isnull)
+{
+	JsonStatParseState *parse = (JsonStatParseState *) state;
+
+	/* skip processing if incompatible statistic version was detected */
+	if (parse->state == JS_INVALID_STAT_VERSION)
+		return;
+
+	Assert(!isnull && parse->arrayval < 0);
+	parse->state = JS_EXPECT_PARAM_FIELD;
+}
+
+#include "utils/varlena.h"
+
+#define NSP_OID(name) \
+	(strcmp(name, " ") == 0) ? LookupExplicitNamespace("pg_catalog", false) : \
+		LookupExplicitNamespace(name, false)
+
+static void
+get_string_values(char *str, char **values, int num)
+{
+	int i;
+	int cnt = 0;
+
+	Assert(num > 0);
+
+	values[0] = &str[0];
+	for (i = 1; str[i] != '\0'; i++)
+	{
+		if (str[i] == '.')
+		{
+			Assert(cnt < num - 1);
+		values[++cnt] = &str[i+1];
+			str[i] = '\0';
+		}
+	}
+
+	Assert(cnt == num - 1);
+}
+
+static void
+json_stat_scalar(void *state, char *token, JsonTokenType tokentype)
+{
+	JsonStatParseState *parse = (JsonStatParseState *) state;
+	Datum value;
+	int attnum = parse->natts - 1;
+
+	/* skip processing if incompatible statistic version was detected */
+	if (parse->state == JS_INVALID_STAT_VERSION)
+		return;
+
+	if (skip_attr)
+		return;
+
+	switch (parse->state)
+	{
+	case JS_EXPECT_STAVER_VALUE:
+	{
+		int version;
+		Assert(tokentype == JSON_TOKEN_NUMBER);
+		value = DirectFunctionCall1(int4in, CStringGetDatum(token));
+		version = DatumGetInt32(value);
+		if (version != STATISTIC_VERSION)
+		{
+			parse->state = JS_INVALID_STAT_VERSION;
+			return;
+		}
+	}
+		break;
+
+	case JS_EXPECT_NSPNAME_VALUE:
+		Assert(tokentype == JSON_TOKEN_STRING);
+		parse->nspname = token;
+		break;
+
+	case JS_EXPECT_RELNAME_VALUE:
+		Assert(tokentype == JSON_TOKEN_STRING);
+		parse->relname = token;
+		break;
+
+	case JS_EXPECT_SLOTSNUM_VALUE:
+		Assert(tokentype == JSON_TOKEN_NUMBER);
+		if (strcmp(token, "5") != 0)
+			elog(ERROR, "Incompatible PostgreSQL version");
+		break;
+
+	case JS_EXPECT_RELPAGES_VALUE:
+		Assert(tokentype == JSON_TOKEN_NUMBER);
+		value = DirectFunctionCall1(int4in, CStringGetDatum(token));
+		parse->relpages = DatumGetInt32(value);
+		break;
+
+	case JS_EXPECT_RELTUPLES_VALUE:
+		Assert(tokentype == JSON_TOKEN_NUMBER);
+		value = DirectFunctionCall1(float4in, CStringGetDatum(token));
+		parse->reltuples = DatumGetFloat4(value);
+		break;
+
+	case JS_EXPECT_ATTNAME_FIELD:
+		Assert(0);
+		break;
+
+	case JS_EXPECT_ATTNAME_VALUE:
+	{
+		int i;
+
+		Assert(tokentype == JSON_TOKEN_STRING);
+
+		for (i = 0; i < parse->rel->rd_att->natts; i++)
+		{
+			if (strcmp(NameStr(parse->rel->rd_att->attrs[i].attname), token) == 0)
+				break;
+		}
+
+		if (i == parse->rel->rd_att->natts)
+		{
+			/*
+			 * It is a valid case when a foreign table doesn't have all the
+			 * attributes from the base relation.
+			 */
+			skip_attr = true;
+			parse->state = JS_EXPECT_ATTNAME_FIELD;
+		}
+		else
+		{
+			/* Initialize new storage for the attribute. */
+			parse->natts++;
+			attnum++;
+			parse->vas[attnum] = examine_attribute(parse->rel, i+1, NULL);
+			parse->vas[attnum]->stats_valid = true;
+			Assert(parse->vas[attnum] != NULL);
+		}
+	}
+		break;
+
+	case JS_EXPECT_INH_VALUE:
+		Assert(tokentype == JSON_TOKEN_STRING);
+		/* XXX */
+		break;
+
+	case JS_EXPECT_WIDTH_VALUE:
+		Assert(tokentype == JSON_TOKEN_NUMBER);
+		value = DirectFunctionCall1(int4in, CStringGetDatum(token));
+		parse->vas[attnum]->stawidth = DatumGetInt32(value);
+		break;
+
+	case JS_EXPECT_DISTINCT_VALUE:
+		Assert(tokentype == JSON_TOKEN_NUMBER);
+		value = DirectFunctionCall1(float4in, CStringGetDatum(token));
+		parse->vas[attnum]->stadistinct = DatumGetFloat4(value);
+		break;
+
+	case JS_EXPECT_NULLFRAC_VALUE:
+		Assert(tokentype == JSON_TOKEN_NUMBER);
+		value = DirectFunctionCall1(float4in, CStringGetDatum(token));
+		parse->vas[attnum]->stanullfrac = DatumGetFloat4(value);
+		break;
+
+	case JS_EXPECT_KIND_VALUE:
+		Assert(tokentype == JSON_TOKEN_NUMBER && parse->arrayval >= 0);
+		value = DirectFunctionCall1(int2in, CStringGetDatum(token));
+		parse->vas[attnum]->stakind[parse->arrayval] = DatumGetInt16(value);
+		break;
+
+	case JS_EXPECT_STAOP_VALUE:
+	{
+		char *str;
+		char *values[6];
+		Oid oprleft;
+		Oid oprright;
+
+		Assert(tokentype == JSON_TOKEN_STRING && parse->arrayval >= 0);
+		Assert(strlen(token) > 0);
+
+		if (strcmp(token, " ") == 0)
+		{
+			/* Quick exit on empty field */
+			parse->vas[attnum]->staop[parse->arrayval] = InvalidOid;
+			break;
+		}
+
+		str = pstrdup(token);
+		get_string_values(str, values, 6);
+
+		if (strcmp(values[2], "NDEFINED") == 0)
+			oprleft = InvalidOid;
+		else
+			oprleft = get_typname_typid(values[3], NSP_OID(values[2]));
+
+		if (strcmp(values[4], "NDEFINED") == 0)
+			oprright = InvalidOid;
+		else
+			oprright = get_typname_typid(values[5], NSP_OID(values[4]));
+
+		parse->vas[attnum]->staop[parse->arrayval] = get_operid(values[1],
+																oprleft,
+																oprright,
+																NSP_OID(values[0]));
+		pfree(str);
+	}
+		break;
+
+	case JS_EXPECT_STACOLL_VALUE:
+	{
+		char *str;
+		char *values[3];
+		int32 collencoding;
+		Oid collnsp;
+
+		Assert(tokentype == JSON_TOKEN_STRING && parse->arrayval >= 0);
+		Assert(strlen(token) > 0);
+
+		if (strcmp(token, " ") == 0)
+		{
+			/* Quick exit on empty field */
+			parse->vas[attnum]->stacoll[parse->arrayval] = InvalidOid;
+			break;
+		}
+
+		str = pstrdup(token);
+		get_string_values(str, values, 3);
+
+		collnsp = NSP_OID(values[0]);
+		sscanf(values[2], "%d", &collencoding);
+		parse->vas[attnum]->stacoll[parse->arrayval] =
+								get_colloid(values[1], collencoding, collnsp);
+		pfree(str);
+	}
+		break;
+
+	case JS_EXPECT_ARRSIZE_VALUE:
+		Assert(tokentype == JSON_TOKEN_NUMBER && parse->arrayval < 0);
+		value = DirectFunctionCall1(int4in, CStringGetDatum(token));
+		parse->arraysize = DatumGetInt32(value);
+		Assert(parse->arraysize > 0);
+		break;
+
+	case JS_EXPECT_NUMBERS_VALUE:
+	{
+		int n = parse->numbersN;
+		int m = parse->arrayval;
+
+		Assert(parse->valuesN == 0);
+		Assert(tokentype == JSON_TOKEN_NUMBER && n > 0 && n <= 5);
+		Assert(m >= 0 && m < parse->arraysize);
+
+		value = DirectFunctionCall1(float4in, CStringGetDatum(token));
+		parse->vas[attnum]->stanumbers[n-1][m] = DatumGetFloat4(value);
+	}
+		break;
+
+	case JS_EXPECT_VALUES_VALUE:
+	{
+		int n = parse->valuesN;
+		int m = parse->arrayval;
+		Form_pg_attribute att = parse->vas[attnum]->attr;
+		Oid			typinput;
+		Oid			typioparam;
+
+		Assert(parse->numbersN == 0);
+		Assert(tokentype == JSON_TOKEN_NUMBER && n > 0 && n <= 5);
+		Assert(m >= 0 && m < parse->arraysize);
+
+		getTypeInputInfo(att->atttypid, &typinput, &typioparam);
+		parse->vas[attnum]->stavalues[n-1][m] =
+			OidInputFunctionCall(typinput, token,
+								 typioparam, att->atttypmod);
+	}
+		break;
+
+	default:
+		elog(ERROR, "Unexpected token type: %d. Token: %s. State: %d.",
+			 tokentype, token, parse->state);
+	}
+}
+
+/*
+ * Extract statistics from the JSON string.
+ */
+static bool
+parse_statjson(Relation rel, char *buffer, size_t size,
+			   VacAttrStats **vacattrstats, int *natts,
+			   BlockNumber *relpages, double *reltuples)
+{
+	JsonLexContext *lex;
+	JsonSemAction sem;
+	JsonStatParseState parse;
+
+	/* Set up our private parsing context. */
+	parse.state = JS_EXPECT_PARAM_FIELD;
+	parse.arraysize = -1;
+	parse.arrayval = -1;
+	parse.numbersN = -1;
+	parse.vas = vacattrstats;
+	parse.natts = 0;
+	parse.rel = rel;
+	skip_attr = false;
+
+	/* Create a JSON lexing context. */
+	lex = makeJsonLexContextCstringLen(buffer, size, PG_UTF8, true);
+
+	/* Set up semantic actions. */
+	sem.semstate = &parse;
+	sem.object_start = NULL;
+	sem.object_end = NULL;
+	sem.array_start = NULL;
+	sem.array_end = json_stat_array_end;
+	sem.object_field_start = json_stat_object_field_start;
+	sem.object_field_end = json_stat_field_end;
+	sem.array_element_start = NULL;
+	sem.array_element_end = json_stat_array_element_end;
+	sem.scalar = json_stat_scalar;
+
+	/* Run the actual JSON parser. */
+	pg_parse_json(lex, &sem);
+
+	/* Special case if we can't parse this serialized statistic */
+	if (parse.state == JS_INVALID_STAT_VERSION)
+		return false;
+
+	*natts = parse.natts;
+	*relpages = parse.relpages;
+	*reltuples = parse.reltuples;
+	return true;
+}
+
+static bool
+update_foreign_relation_stat(Relation rel)
+{
+	VacAttrStats **vacattrstats = (VacAttrStats **)
+					palloc0(rel->rd_att->natts * sizeof(VacAttrStats *));
+	char *statstr;
+	int natts = 0;
+	FdwRoutine *fdwroutine;
+	BlockNumber relallvisible;
+	BlockNumber numpages;
+	double numtuples;
+
+	fdwroutine = GetFdwRoutineForRelation(rel, false);
+	Assert(fdwroutine != NULL);
+
+	if (fdwroutine->GetForeignRelStat == NULL || strcmp(NameStr(rel->rd_rel->relname), "ftable") != 0)
+		return false;
+
+	statstr = fdwroutine->GetForeignRelStat(rel);
+
+	if (!statstr || !parse_statjson(rel, statstr, strlen(statstr),
+									vacattrstats, &natts, &numpages,
+									&numtuples))
+		/*
+		 * Return false if we can't get statistics for the relation or can't parse
+		 * it by any reason.
+		 */
+		return false;
+
+	update_attstats(RelationGetRelid(rel), false, natts, vacattrstats);
+
+	visibilitymap_count(rel, &relallvisible, NULL);
+	vac_update_relstats(rel, numpages, numtuples, relallvisible, false,
+						InvalidTransactionId, InvalidMultiXactId, false);
+
+	pfree(vacattrstats);
+	return true;
+}
+
+/*
+ * Get stat tuples for the attributes of relation
+ * See row_to_json()
+ */
+Datum
+extract_relation_statistics(PG_FUNCTION_ARGS)
+{
+	const char *relname = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	const char *json_stat;
+
+	json_stat = relstat_to_json(relname);
+
+	PG_RETURN_TEXT_P(cstring_to_text(json_stat));
+}
+
+static const char *
+nsp_name(Oid oid)
+{
+	const char *nspname;
+
+	if (!OidIsValid(oid))
+		return " ";
+
+	if (isTempNamespace(oid))
+		return "pg_temp";
+
+	nspname = get_namespace_name(oid);
+	if (strcmp(nspname, "pg_catalog") == 0)
+		return " ";
+
+	return nspname;
+}
+/*
+ * relstat_to_json
+ *
+ */
+static const char *
+relstat_to_json(const char *relname)
+{
+	RangeVar	*relvar;
+	Relation	rel;
+	Relation	sd;
+	List		*relname_list;
+	int			attno;
+	StringInfo	str = makeStringInfo();
+	int			attnum = 0;
+
+	relname_list = stringToQualifiedNameList(relname);
+	relvar = makeRangeVarFromNameList(relname_list);
+	rel = relation_openrv(relvar, AccessShareLock);
+
+	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	{
+		relation_close(rel, AccessShareLock);
+		elog(ERROR,
+			 "Can be used for ordinary relation only. Reltype: %c.",
+			 rel->rd_rel->relkind);
+	}
+
+	/* JSON header of overall relation statistics. */
+	appendStringInfoString(str, "{");
+	appendStringInfo(str, "\"version\" : %u, ", STATISTIC_VERSION);
+	appendStringInfo(str, "\"namespace\" : \"%s\", \"relname\" : \"%s\", \"sta_num_slots\" : %d, ",
+					 get_namespace_name(rel->rd_rel->relnamespace),
+					 NameStr(rel->rd_rel->relname), STATISTIC_NUM_SLOTS);
+
+	appendStringInfo(str, "\"relpages\" : %u, \"reltuples\" : %f, ",
+					 rel->rd_rel->relpages,
+					 rel->rd_rel->reltuples);
+
+	appendStringInfo(str, "\"attrs\" : [");
+	sd = table_open(StatisticRelationId, RowExclusiveLock);
+
+	for (attno = 0; attno < rel->rd_att->natts; attno++)
+	{
+		HeapTuple	stup;
+		Datum		values[Natts_pg_statistic];
+		bool		nulls[Natts_pg_statistic];
+		int			i, k;
+
+		stup = SearchSysCache3(STATRELATTINH,
+							   ObjectIdGetDatum(RelationGetRelid(rel)),
+							   Int16GetDatum(rel->rd_att->attrs[attno].attnum),
+							   BoolGetDatum(false));
+
+		if (!HeapTupleIsValid(stup))
+			/* Go to the next attribute, if we haven't statistics for. */
+			continue;
+
+		if (attnum++ > 0)
+			appendStringInfoString(str, ", ");
+
+		heap_deform_tuple(stup, RelationGetDescr(sd), values, nulls);
+
+		/* JSON header of attrribute statistics. */
+		appendStringInfo(str, "{\"attname\" : \"%s\", \"inh\" : \"%s\", \"nullfrac\" : %f, \"width\" : %d, \"distinct\" : %f, ",
+			NameStr(*attnumAttName(rel, rel->rd_att->attrs[attno].attnum)),
+			DatumGetBool(values[Anum_pg_statistic_stainherit - 1]) ? "true" : "false",
+			DatumGetFloat4(values[Anum_pg_statistic_stanullfrac - 1]),
+			DatumGetInt32(values[Anum_pg_statistic_stawidth - 1]),
+			DatumGetFloat4(values[Anum_pg_statistic_stadistinct - 1]));
+
+		appendStringInfoString(str, "\"stakind\" : [");
+		i = Anum_pg_statistic_stakind1 - 1;
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			appendStringInfo(str, "%d", DatumGetInt16(values[i++]));
+			if (k < STATISTIC_NUM_SLOTS - 1)
+				appendStringInfo(str, ",");
+		}
+		appendStringInfoString(str, "], \"staop\" : [");
+
+		i = Anum_pg_statistic_staop1 - 1;
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			Oid opOid = DatumGetObjectId(values[i++]);
+
+			/* Prepare portable representation of the oid */
+			if (OidIsValid(opOid))
+			{
+				Oid lefttype;
+				Oid righttype;
+				const char *ltypname;
+				const char *rtypname;
+
+				op_input_types(opOid, &lefttype, &righttype);
+
+				if (!OidIsValid(lefttype))
+					ltypname = "NDEFINED";
+				else if ((ltypname = get_typ_name(lefttype)) == NULL)
+					elog(ERROR, "Type name search failed for oid %d.", lefttype);
+
+				if (!OidIsValid(righttype))
+					rtypname = "NDEFINED";
+				else if ((rtypname = get_typ_name(righttype)) == NULL)
+					elog(ERROR, "Type name search failed for oid %d.", righttype);
+
+				appendStringInfo(str, "\"%s.%s.%s.%s.%s.%s\"",
+								 nsp_name(get_opnamespace(opOid)),
+								 get_opname(opOid),
+								 nsp_name(get_typ_namespace(lefttype)),
+								 ltypname,
+								 nsp_name(get_typ_namespace(righttype)),
+								 rtypname);
+			}
+			else
+				appendStringInfoString(str, "\" \"");
+
+			if (k < STATISTIC_NUM_SLOTS - 1)
+				appendStringInfoChar(str, ',');
+		}
+		appendStringInfoString(str, "], \"stacoll\" : [");
+
+		i = Anum_pg_statistic_stacoll1 - 1;
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			Oid collOid = DatumGetObjectId(values[i++]);
+
+			/* Prepare portable representation of the collation */
+			if (OidIsValid(collOid))
+				appendStringInfo(str, "\"%s.%s.%d\"",
+								 nsp_name(get_opnamespace(collOid)),
+								 get_collation_name(collOid),
+								  get_collation_encoding(collOid));
+			else
+				appendStringInfoString(str, "\" \"");
+
+			if (k < STATISTIC_NUM_SLOTS - 1)
+				appendStringInfoChar(str, ',');
+		}
+
+		appendStringInfoString(str, "], ");
+
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			bool		isnull;
+			Datum		val;
+
+			val = SysCacheGetAttr(STATRELATTINH, stup,
+							  Anum_pg_statistic_stavalues1 + k,
+							  &isnull);
+
+			if (isnull)
+				appendStringInfo(str, "\"values%d\" : [ ]", k+1);
+			else
+			{
+				ArrayType *v = DatumGetArrayTypeP(val);
+
+				appendStringInfo(str, "\"nn\" : %d, ",
+								 ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v)));
+				appendStringInfo(str, "\"values%d\" : ", k+1);
+				arr_to_json(val, str);
+			}
+			appendStringInfoString(str, ", ");
+		}
+
+		/* --- Extract numbers --- */
+		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+		{
+			bool		isnull;
+			Datum		val;
+
+			if (k > 0)
+				appendStringInfoString(str, ", ");
+
+			val = SysCacheGetAttr(STATRELATTINH, stup,
+							  Anum_pg_statistic_stanumbers1 + k,
+							  &isnull);
+			if (isnull)
+				appendStringInfo(str, "\"numbers%d\" : [ ]", k+1);
+			else
+			{
+				ArrayType *v = DatumGetArrayTypeP(val);
+
+				appendStringInfo(str, "\"nn\" : %d, ",
+								 ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v)));
+				appendStringInfo(str, "\"numbers%d\" : ", k+1);
+				arr_to_json(val, str);
+			}
+		}
+
+		appendStringInfoString(str, "}");
+		ReleaseSysCache(stup);
+	}
+
+	if (attnum == 0)
+		appendStringInfoString(str, " ]");
+	else
+		appendStringInfoChar(str, ']');
+	appendStringInfoString(str, "}");
+
+	table_close(sd, RowExclusiveLock);
+	relation_close(rel, AccessShareLock);
+
+	return str->data;
+}
 
 /*
  *	update_attstats() -- update attribute statistics for one relation
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ddeec870d8..d54a2d51ee 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -882,6 +882,7 @@ get_all_vacuum_rels(int options)
 		 * them.
 		 */
 		if (classForm->relkind != RELKIND_RELATION &&
+			classForm->relkind != RELKIND_FOREIGN_TABLE &&
 			classForm->relkind != RELKIND_MATVIEW &&
 			classForm->relkind != RELKIND_PARTITIONED_TABLE)
 			continue;
@@ -1818,7 +1819,17 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
 	/*
 	 * Check that it's of a vacuumable relkind.
 	 */
-	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+	if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		relation_close(onerel, lmode);
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+		if (params->options & VACOPT_ANALYZE)
+			return true;
+		else
+			return false;
+	}
+	else if (onerel->rd_rel->relkind != RELKIND_RELATION &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE &&
 		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index a7a91b72f6..adea67a18d 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -656,6 +656,12 @@ add_json(Datum val, bool is_null, StringInfo result,
 	datum_to_json(val, is_null, result, tcategory, outfuncoid, key_scalar);
 }
 
+void
+arr_to_json(Datum array, StringInfo result)
+{
+	array_to_json_internal(array, result, false);
+}
+
 /*
  * SQL function array_to_json(row)
  */
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index f3bf413829..4b49a0b915 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1018,6 +1018,31 @@ get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok)
 	return oid;
 }
 
+/*
+ * get_collation_namespace
+ *		Returns the namespace id of a given pg_collation entry.
+ *
+ * Returns an Oid of the collation's namespace.
+ */
+Oid
+get_collation_namespace(Oid colloid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(colloid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_collation colltup = (Form_pg_collation) GETSTRUCT(tp);
+		Oid 		result;
+
+		result = colltup->collnamespace;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
 /*				---------- COLLATION CACHE ----------					 */
 
 /*
@@ -1048,6 +1073,47 @@ get_collation_name(Oid colloid)
 		return NULL;
 }
 
+/*
+ * get_collation_encoding
+ *		Returns the encoding of a given pg_collation entry.
+ *
+ * Returns the collation's encoding, or -1 if entry does not exist.
+ */
+int32
+get_collation_encoding(Oid colloid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(colloid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_collation colltup = (Form_pg_collation) GETSTRUCT(tp);
+		int32 		result;
+
+		result = colltup->collencoding;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return -1;
+}
+
+/*
+ * get_colloid
+ *	  Given a collation name, encoding and namespace OID, look up
+ * the collation OID.
+ *
+ * Returns InvalidOid if there is no such collation
+ */
+Oid
+get_colloid(const char *collname, int32 collencoding, Oid collnsp)
+{
+	return GetSysCacheOid3(COLLNAMEENCNSP, Anum_pg_collation_oid,
+						  CStringGetDatum(collname),
+						  Int32GetDatum(collencoding),
+						  ObjectIdGetDatum(collnsp));
+}
+
 bool
 get_collation_isdeterministic(Oid colloid)
 {
@@ -3393,3 +3459,104 @@ get_index_isclustered(Oid index_oid)
 
 	return isclustered;
 }
+
+/*
+ * get_typ_name
+ *
+ *		Given the type OID, find the type name
+ *		It returns palloc'd copy of the name or NULL if the cache lookup fails.
+ */
+char *
+get_typ_name(Oid typid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
+		char	   *result;
+
+		result = pstrdup(NameStr(typtup->typname));
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return NULL;
+}
+
+/*
+ * get_typ_namespace
+ *
+ *		Given the type OID, find the namespace
+ *		It returns InvalidOid if the cache lookup fails.
+ */
+Oid
+get_typ_namespace(Oid typid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
+		Oid			result;
+
+		result = typtup->typnamespace;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_opnamespace
+ *	  Given an opno, find the namespace
+ *
+ * Returns InvalidOid if there is no such operator
+ */
+Oid
+get_opnamespace(Oid opno)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp);
+		Oid				result;
+
+		result = optup->oprnamespace;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_operid
+ *	  Given an operator name, argument types and namespace OID, look up
+ * the operator OID.
+ *
+ * Returns InvalidOid if there is no such operator
+ */
+Oid
+get_operid(const char *oprname, Oid oprleft, Oid oprright, Oid oprnsp)
+{
+	return GetSysCacheOid4(OPERNAMENSP,
+						  Anum_pg_operator_oid,
+						  CStringGetDatum(oprname),
+						  ObjectIdGetDatum(oprleft),
+						  ObjectIdGetDatum(oprright),
+						  ObjectIdGetDatum(oprnsp));
+}
+
+Oid
+get_typname_typid(const char *typname, Oid typnamespace)
+{
+	return GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
+						  CStringGetDatum(typname),
+						  ObjectIdGetDatum(typnamespace));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 687509ba92..ce4d94d7d4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8383,6 +8383,9 @@
 { oid => '3156', descr => 'map row to json with optional pretty printing',
   proname => 'row_to_json', provolatile => 's', prorettype => 'json',
   proargtypes => 'record bool', prosrc => 'row_to_json_pretty' },
+{ oid => '10001', descr => 'map relation statistics to json',
+  proname => 'extract_relation_statistics', provolatile => 's', prorettype => 'json',
+  proargtypes => 'text', prosrc => 'extract_relation_statistics' },
 { oid => '3173', descr => 'json aggregate transition function',
   proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's',
   prorettype => 'internal', proargtypes => 'internal anyelement',
diff --git a/src/include/catalog/pg_statistic.h b/src/include/catalog/pg_statistic.h
index 40a7260165..6e48a27d6c 100644
--- a/src/include/catalog/pg_statistic.h
+++ b/src/include/catalog/pg_statistic.h
@@ -124,6 +124,7 @@ CATALOG(pg_statistic,2619,StatisticRelationId)
 } FormData_pg_statistic;
 
 #define STATISTIC_NUM_SLOTS  5
+#define STATISTIC_VERSION	 1
 
 
 /* ----------------
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 95556dfb15..51979ab3bf 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -169,6 +169,7 @@ typedef bool (*IsForeignScanParallelSafe_function) (PlannerInfo *root,
 typedef List *(*ReparameterizeForeignPathByChild_function) (PlannerInfo *root,
 															List *fdw_private,
 															RelOptInfo *child_rel);
+typedef char *(*GetForeignRelStat_function) (Relation rel);
 
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -246,6 +247,7 @@ typedef struct FdwRoutine
 
 	/* Support functions for path reparameterization. */
 	ReparameterizeForeignPathByChild_function ReparameterizeForeignPathByChild;
+	GetForeignRelStat_function GetForeignRelStat;
 } FdwRoutine;
 
 
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index 4345fbdc31..a6b51bf191 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -20,5 +20,6 @@
 extern void escape_json(StringInfo buf, const char *str);
 extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
 								const int *tzp);
+extern void arr_to_json(Datum array, StringInfo result);
 
 #endif							/* JSON_H */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index fecfe1f4f6..b8120a754a 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -93,7 +93,10 @@ extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum,
 								  Oid *typid, int32 *typmod, Oid *collid);
 extern Datum get_attoptions(Oid relid, int16 attnum);
 extern Oid	get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok);
+extern Oid get_collation_namespace(Oid colloid);
 extern char *get_collation_name(Oid colloid);
+extern int32 get_collation_encoding(Oid colloid);
+extern Oid get_colloid(const char *collname, int32 collencoding, Oid collnsp);
 extern bool get_collation_isdeterministic(Oid colloid);
 extern char *get_constraint_name(Oid conoid);
 extern char *get_language_name(Oid langoid, bool missing_ok);
@@ -187,6 +190,11 @@ extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
 extern bool get_index_isclustered(Oid index_oid);
+extern char *get_typ_name(Oid typid);
+extern Oid	get_typ_namespace(Oid typid);
+extern Oid get_opnamespace(Oid opno);
+extern Oid get_operid(const char *oprname, Oid oprleft, Oid oprright, Oid oprnsp);
+extern Oid get_typname_typid(const char *typname, Oid typnamespace);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
-- 
2.25.1

Reply via email to