foreign_plan_cache_inval_v4.patch
binary/octet-stream
Filename: foreign_plan_cache_inval_v4.patch
Type: binary/octet-stream
Part: 0
Patch
Format: context
Series: patch v4
| 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 | 0 |
| src/backend/utils/cache/plancache.c | 13 | 9 |
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2487,2498 **** EXECUTE st5('foo', 1);
--- 2487,2615 ----
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;
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 577,582 **** EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
--- 577,602 ----
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,589 **** DEALLOCATE st2;
--- 604,613 ----
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)
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 16,22 ****
--- 16,24 ----
#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,142 **** static List *set_returning_clause_references(PlannerInfo *root,
--- 139,146 ----
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,181 **** 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.
*
* 8. We assign every plan node in the tree a unique ID.
*
--- 178,185 ----
* 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, 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,432 **** add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
--- 430,442 ----
* 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,2444 **** 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);
}
}
/*
* 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
--- 2433,2487 ----
*/
if (funcid >= (Oid) FirstBootstrapObjectId)
{
/*
* 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.
*/
! 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,2517 **** extract_query_dependencies_walker(Node *node, PlannerInfo *context)
--- 2553,2566 ----
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 */
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
***************
*** 27,41 ****
* 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.
*
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
--- 27,42 ----
* 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,
! * user-defined functions and FDW-related objects. On relcache invalidation
! * events or the relevant 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,121 **** InitPlanCache(void)
--- 117,125 ----
{
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);