foreign_plan_cache_inval_v6.patch
binary/octet-stream
Filename: foreign_plan_cache_inval_v6.patch
Type: binary/octet-stream
Part: 0
Patch
Format: unified
Series: patch v6
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/expected/postgres_fdw.out | 117 | 0 |
| contrib/postgres_fdw/sql/postgres_fdw.sql | 24 | 0 |
| src/backend/optimizer/plan/setrefs.c | 58 | 9 |
| src/backend/utils/cache/plancache.c | 15 | 11 |
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index d97e694..568671e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2487,12 +2487,129 @@ EXECUTE st5('foo', 1);
1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
(1 row)
+-- changing foreign table options should change the plans
+PREPARE st6 AS SELECT * FROM ft4;
+PREPARE st7 AS INSERT INTO ft4 VALUES (101, 102, 'foo');
+PREPARE st8 AS UPDATE ft4 SET c1 = c1;
+PREPARE st9 AS UPDATE ft4 SET c1 = random();
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+ QUERY PLAN
+--------------------------------------------------
+ Foreign Scan on public.ft4
+ Output: c1, c2, c3
+ Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Insert on public.ft4
+ Remote SQL: INSERT INTO "S 1"."T 3"(c1, c2, c3) VALUES ($1, $2, $3)
+ -> Result
+ Output: 101, 102, 'foo'::text
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+ QUERY PLAN
+----------------------------------------------------
+ Update on public.ft4
+ -> Foreign Update on public.ft4
+ Remote SQL: UPDATE "S 1"."T 3" SET c1 = c1
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Update on public.ft4
+ Remote SQL: UPDATE "S 1"."T 3" SET c1 = $2 WHERE ctid = $1
+ -> Foreign Scan on public.ft4
+ Output: random(), c2, c3, ctid
+ Remote SQL: SELECT c2, c3, ctid FROM "S 1"."T 3" FOR UPDATE
+(5 rows)
+
+ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 4');
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+ QUERY PLAN
+--------------------------------------------------
+ Foreign Scan on public.ft4
+ Output: c1, c2, c3
+ Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4"
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Insert on public.ft4
+ Remote SQL: INSERT INTO "S 1"."T 4"(c1, c2, c3) VALUES ($1, $2, $3)
+ -> Result
+ Output: 101, 102, 'foo'::text
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+ QUERY PLAN
+----------------------------------------------------
+ Update on public.ft4
+ -> Foreign Update on public.ft4
+ Remote SQL: UPDATE "S 1"."T 4" SET c1 = c1
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Update on public.ft4
+ Remote SQL: UPDATE "S 1"."T 4" SET c1 = $2 WHERE ctid = $1
+ -> Foreign Scan on public.ft4
+ Output: random(), c2, c3, ctid
+ Remote SQL: SELECT c2, c3, ctid FROM "S 1"."T 4" FOR UPDATE
+(5 rows)
+
+-- restore the original options and check again
+ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 3');
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+ QUERY PLAN
+--------------------------------------------------
+ Foreign Scan on public.ft4
+ Output: c1, c2, c3
+ Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Insert on public.ft4
+ Remote SQL: INSERT INTO "S 1"."T 3"(c1, c2, c3) VALUES ($1, $2, $3)
+ -> Result
+ Output: 101, 102, 'foo'::text
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+ QUERY PLAN
+----------------------------------------------------
+ Update on public.ft4
+ -> Foreign Update on public.ft4
+ Remote SQL: UPDATE "S 1"."T 3" SET c1 = c1
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Update on public.ft4
+ Remote SQL: UPDATE "S 1"."T 3" SET c1 = $2 WHERE ctid = $1
+ -> Foreign Scan on public.ft4
+ Output: random(), c2, c3, ctid
+ Remote SQL: SELECT c2, c3, ctid FROM "S 1"."T 3" FOR UPDATE
+(5 rows)
+
-- cleanup
DEALLOCATE st1;
DEALLOCATE st2;
DEALLOCATE st3;
DEALLOCATE st4;
DEALLOCATE st5;
+DEALLOCATE st6;
+DEALLOCATE st7;
+DEALLOCATE st8;
+DEALLOCATE st9;
-- System columns, except ctid and oid, should not be sent to remote
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 4f68e89..f7eca2d 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -577,6 +577,26 @@ EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
EXECUTE st5('foo', 1);
+-- changing foreign table options should change the plans
+PREPARE st6 AS SELECT * FROM ft4;
+PREPARE st7 AS INSERT INTO ft4 VALUES (101, 102, 'foo');
+PREPARE st8 AS UPDATE ft4 SET c1 = c1;
+PREPARE st9 AS UPDATE ft4 SET c1 = random();
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 4');
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+-- restore the original options and check again
+ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 3');
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
-- cleanup
DEALLOCATE st1;
@@ -584,6 +604,10 @@ DEALLOCATE st2;
DEALLOCATE st3;
DEALLOCATE st4;
DEALLOCATE st5;
+DEALLOCATE st6;
+DEALLOCATE st7;
+DEALLOCATE st8;
+DEALLOCATE st9;
-- System columns, except ctid and oid, should not be sent to remote
EXPLAIN (VERBOSE, COSTS OFF)
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index d10a983..2f6b4a0 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -16,7 +16,9 @@
#include "postgres.h"
#include "access/transam.h"
+#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
+#include "foreign/foreign.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/pathnode.h"
@@ -137,6 +139,8 @@ static List *set_returning_clause_references(PlannerInfo *root,
Plan *topplan,
Index resultRelation,
int rtoffset);
+static void record_plan_foreign_dependencies(PlannerGlobal *glob, Oid relid);
+static void add_inval_item(PlannerGlobal *glob, int cacheid, Oid oid);
static bool extract_query_dependencies_walker(Node *node,
PlannerInfo *context);
@@ -174,8 +178,8 @@ static bool extract_query_dependencies_walker(Node *node,
* This will be used by plancache.c to drive invalidation of cached plans.
* Relation dependencies are represented by OIDs, and everything else by
* PlanInvalItems (this distinction is motivated by the shared-inval APIs).
- * Currently, relations and user-defined functions are the only types of
- * objects that are explicitly tracked this way.
+ * Currently, relations, user-defined functions and FDW-related objects are
+ * the only types of objects that are explicitly tracked this way.
*
* 8. We assign every plan node in the tree a unique ID.
*
@@ -426,7 +430,13 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
* but it would probably cost more cycles than it would save.
*/
if (newrte->rtekind == RTE_RELATION)
+ {
glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+
+ /* Collect dependencies on FDW-related objects too */
+ if (newrte->relkind == RELKIND_FOREIGN_TABLE)
+ record_plan_foreign_dependencies(glob, newrte->relid);
+ }
}
/*
@@ -2423,22 +2433,55 @@ record_plan_function_dependency(PlannerInfo *root, Oid funcid)
*/
if (funcid >= (Oid) FirstBootstrapObjectId)
{
- PlanInvalItem *inval_item = makeNode(PlanInvalItem);
-
/*
* It would work to use any syscache on pg_proc, but the easiest is
* PROCOID since we already have the function's OID at hand. Note
* that plancache.c knows we use PROCOID.
*/
- inval_item->cacheId = PROCOID;
- inval_item->hashValue = GetSysCacheHashValue1(PROCOID,
- ObjectIdGetDatum(funcid));
-
- root->glob->invalItems = lappend(root->glob->invalItems, inval_item);
+ add_inval_item(root->glob, PROCOID, funcid);
}
}
/*
+ * record_plan_foreign_dependencies
+ * Mark the current plan as depending on FDW-related objects.
+ *
+ * This is required since modifications to attributes of FDW-related objects,
+ * such as FDW options, might require replanning.
+ */
+static void
+record_plan_foreign_dependencies(PlannerGlobal *glob, Oid relid)
+{
+ ForeignTable *table = GetForeignTable(relid);
+ ForeignServer *server = GetForeignServer(table->serverid);
+
+ /* Record the dependency on the foreign table */
+ add_inval_item(glob, FOREIGNTABLEREL, relid);
+
+ /* Likewise for the foreign server */
+ add_inval_item(glob, FOREIGNSERVEROID, table->serverid);
+
+ /* Likewise for the foreign data wrapper */
+ add_inval_item(glob, FOREIGNDATAWRAPPEROID, server->fdwid);
+}
+
+/*
+ * add_inval_item
+ * Add given dependency to glob->invalItems.
+ */
+static void
+add_inval_item(PlannerGlobal *glob, int cacheid, Oid oid)
+{
+ PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+
+ inval_item->cacheId = cacheid;
+ inval_item->hashValue = GetSysCacheHashValue1(cacheid,
+ ObjectIdGetDatum(oid));
+
+ glob->invalItems = lappend(glob->invalItems, inval_item);
+}
+
+/*
* extract_query_dependencies
* Given a rewritten, but not yet planned, query or queries
* (i.e. a Query node or list of Query nodes), extract dependencies
@@ -2510,8 +2553,14 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
if (rte->rtekind == RTE_RELATION)
+ {
context->glob->relationOids =
lappend_oid(context->glob->relationOids, rte->relid);
+
+ /* Collect dependencies on FDW-related objects too */
+ if (rte->relkind == RELKIND_FOREIGN_TABLE)
+ record_plan_foreign_dependencies(context->glob, rte->relid);
+ }
}
/* And recurse into the query's subexpressions */
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index c96a865..4637745 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -27,15 +27,15 @@
* query to change output tupdesc on replan --- if so, it's up to the
* caller to notice changes and cope with them.
*
- * Currently, we track exactly the dependencies of plans on relations and
- * user-defined functions. On relcache invalidation events or pg_proc
- * syscache invalidation events, we invalidate just those plans that depend
- * on the particular object being modified. (Note: this scheme assumes
- * that any table modification that requires replanning will generate a
- * relcache inval event.) We also watch for inval events on certain other
- * system catalogs, such as pg_namespace; but for them, our response is
- * just to invalidate all plans. We expect updates on those catalogs to
- * be infrequent enough that more-detailed tracking is not worth the effort.
+ * Currently, we track exactly the dependencies of plans on relations,
+ * user-defined functions and FDW-related objects. On the corresponding
+ * syscache invalidation events, we invalidate just those plans that depend on
+ * the particular object being modified. (Note: this scheme assumes that any
+ * table modification that requires replanning will generate a relcache inval
+ * event.) We also watch for inval events on certain other system catalogs,
+ * such as pg_namespace; but for them, our response is just to invalidate all
+ * plans. We expect updates on those catalogs to be infrequent enough that
+ * more-detailed tracking is not worth the effort.
*
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
@@ -116,6 +116,9 @@ InitPlanCache(void)
{
CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0);
CacheRegisterSyscacheCallback(PROCOID, PlanCacheFuncCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(FOREIGNTABLEREL, PlanCacheFuncCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheFuncCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheFuncCallback, (Datum) 0);
CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
@@ -1751,8 +1754,9 @@ PlanCacheRelCallback(Datum arg, Oid relid)
* Invalidate all plans mentioning the object with the specified hash value,
* or all plans mentioning any member of this cache if hashvalue == 0.
*
- * Note that the coding would support use for multiple caches, but right
- * now only user-defined functions are tracked this way.
+ * Note that the coding would support use for multiple caches, and currently,
+ * in addition to user-defined functions, FDW-related objects are tracked this
+ * way.
*/
static void
PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue)