*** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** *** 2487,2498 **** EXECUTE st5('foo', 1); --- 2487,2685 ---- 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 SELECT * FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1); + PREPARE st8 AS SELECT * FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) FOR UPDATE OF t1; + PREPARE st9 AS UPDATE ft4 SET c1 = c1; + PREPARE st10 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 + ------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, t1.c3, t2.c1, t2.c2, t2.c3 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r1.c2, r1.c3, r2.c1, r2.c2, r2.c3 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) + (4 rows) + + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN + -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: t1.c1, t1.c2, t1.c3, t2.c1, t2.c2, t2.c3, t1.*, t2.* + -> Foreign Scan + Output: t1.c1, t1.c2, t1.c3, t2.c1, t2.c2, t2.c3, t1.*, t2.* + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r1.c2, r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r2.c1, r2.c2, r2.c3, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) FOR UPDATE OF r1 + -> Hash Left Join + Output: t1.c1, t1.c2, t1.c3, t1.*, t2.c1, t2.c2, t2.c3, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft4 t1 + Output: t1.c1, t1.c2, t1.c3, t1.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE + -> Hash + Output: t2.c1, t2.c2, t2.c3, t2.* + -> Foreign Scan on public.ft5 t2 + Output: t2.c1, t2.c2, t2.c3, t2.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + (17 rows) + + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9; + 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 st10; + 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'); + ALTER FOREIGN TABLE ft5 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 4" + (3 rows) + + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN + ------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, t1.c3, t2.c1, t2.c2, t2.c3 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r1.c2, r1.c3, r2.c1, r2.c2, r2.c3 FROM ("S 1"."T 4" r1 LEFT JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)))) + (4 rows) + + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN + -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: t1.c1, t1.c2, t1.c3, t2.c1, t2.c2, t2.c3, t1.*, t2.* + -> Foreign Scan + Output: t1.c1, t1.c2, t1.c3, t2.c1, t2.c2, t2.c3, t1.*, t2.* + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r1.c2, r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r2.c1, r2.c2, r2.c3, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END FROM ("S 1"."T 4" r1 LEFT JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)))) FOR UPDATE OF r1 + -> Hash Left Join + Output: t1.c1, t1.c2, t1.c3, t1.*, t2.c1, t2.c2, t2.c3, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft4 t1 + Output: t1.c1, t1.c2, t1.c3, t1.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE + -> Hash + Output: t2.c1, t2.c2, t2.c3, t2.* + -> Foreign Scan on public.ft5 t2 + Output: t2.c1, t2.c2, t2.c3, t2.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + (17 rows) + + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9; + 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 st10; + 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'); + ALTER FOREIGN TABLE ft5 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 3" + (3 rows) + + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN + ------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, t1.c3, t2.c1, t2.c2, t2.c3 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r1.c2, r1.c3, r2.c1, r2.c2, r2.c3 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) + (4 rows) + + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN + -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: t1.c1, t1.c2, t1.c3, t2.c1, t2.c2, t2.c3, t1.*, t2.* + -> Foreign Scan + Output: t1.c1, t1.c2, t1.c3, t2.c1, t2.c2, t2.c3, t1.*, t2.* + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r1.c2, r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r2.c1, r2.c2, r2.c3, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) FOR UPDATE OF r1 + -> Hash Left Join + Output: t1.c1, t1.c2, t1.c3, t1.*, t2.c1, t2.c2, t2.c3, t2.* + Hash Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft4 t1 + Output: t1.c1, t1.c2, t1.c3, t1.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE + -> Hash + Output: t2.c1, t2.c2, t2.c3, t2.* + -> Foreign Scan on public.ft5 t2 + Output: t2.c1, t2.c2, t2.c3, t2.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + (17 rows) + + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9; + 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 st10; + 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; + DEALLOCATE st10; -- 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,608 ---- 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 SELECT * FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1); + PREPARE st8 AS SELECT * FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) FOR UPDATE OF t1; + PREPARE st9 AS UPDATE ft4 SET c1 = c1; + PREPARE st10 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; + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st10; + ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 4'); + ALTER FOREIGN TABLE ft5 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; + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st10; + -- restore the original options and check again + ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 3'); + ALTER FOREIGN TABLE ft5 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; + EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st10; -- cleanup DEALLOCATE st1; *************** *** 584,589 **** DEALLOCATE st2; --- 610,620 ---- DEALLOCATE st3; DEALLOCATE st4; DEALLOCATE st5; + DEALLOCATE st6; + DEALLOCATE st7; + DEALLOCATE st8; + DEALLOCATE st9; + DEALLOCATE st10; -- 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 *************** *** 17,28 **** --- 17,30 ---- #include "access/transam.h" #include "catalog/pg_type.h" + #include "foreign/foreign.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/pathnode.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/tlist.h" + #include "parser/parsetree.h" #include "tcop/utility.h" #include "utils/lsyscache.h" #include "utils/syscache.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(PlannerInfo *root, ForeignScan *scan); + static void add_inval_item(PlannerInfo *root, 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. * *************** *** 1123,1128 **** set_foreignscan_references(PlannerInfo *root, --- 1127,1140 ---- ForeignScan *fscan, int rtoffset) { + /* + * Record dependencies on FDW-related objects. If an outer subplan + * exists, that would be done in the processing of its baserels, so skip + * that. + */ + if (fscan->scan.plan.lefttree == NULL) + record_plan_foreign_dependencies(root, fscan); + /* Adjust scanrelid if it's valid */ if (fscan->scan.scanrelid > 0) fscan->scan.scanrelid += rtoffset; *************** *** 2423,2441 **** 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); } } /* --- 2435,2491 ---- */ 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, 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(PlannerInfo *root, ForeignScan *scan) ! { ! int relid = -1; ! ForeignServer *server = GetForeignServer(scan->fs_server); ! /* Record dependencies on the foreign tables */ ! while ((relid = bms_next_member(scan->fs_relids, relid)) >= 0) ! { ! RangeTblEntry *rte = planner_rt_fetch(relid, root); ! ! add_inval_item(root, FOREIGNTABLEREL, rte->relid); } + + /* Likewise for the foreign server */ + add_inval_item(root, FOREIGNSERVEROID, scan->fs_server); + + /* Likewise for the foreign data wrapper */ + add_inval_item(root, FOREIGNDATAWRAPPEROID, server->fdwid); + } + + /* + * add_inval_item + * Add given dependency to root->glob->invalItems. + */ + static void + add_inval_item(PlannerInfo *root, int cacheid, Oid oid) + { + PlanInvalItem *inval_item = makeNode(PlanInvalItem); + + inval_item->cacheId = cacheid; + inval_item->hashValue = GetSysCacheHashValue1(cacheid, + ObjectIdGetDatum(oid)); + + root->glob->invalItems = lappend(root->glob->invalItems, inval_item); } /* *** 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 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 *************** *** 103,108 **** static bool ScanQueryWalker(Node *node, bool *acquire); --- 104,110 ---- static TupleDesc PlanCacheComputeResultDesc(List *stmt_list); static void PlanCacheRelCallback(Datum arg, Oid relid); static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue); + static void PlanCacheForeignCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); *************** *** 116,121 **** InitPlanCache(void) --- 118,126 ---- { CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0); CacheRegisterSyscacheCallback(PROCOID, PlanCacheFuncCallback, (Datum) 0); + CacheRegisterSyscacheCallback(FOREIGNTABLEREL, PlanCacheForeignCallback, (Datum) 0); + CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheForeignCallback, (Datum) 0); + CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheForeignCallback, (Datum) 0); CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0); *************** *** 1829,1834 **** PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) --- 1834,1903 ---- } /* + * PlanCacheForeignCallback + * Syscache inval callback function for FOREIGNTABLEREL, FOREIGNSERVEROID + * and FOREIGNDATAWRAPPEROID caches + * + * Invalidate all plans mentioning the object with the specified hash value, + * or all plans mentioning any member of the given cache if hashvalue == 0. + * + * Note: any updates on those catalogs don't affect the rewritten querytree, + * but might require replanning, so we invalidate the generic plan only. + */ + static void + PlanCacheForeignCallback(Datum arg, int cacheid, uint32 hashvalue) + { + CachedPlanSource *plansource; + + for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) + { + ListCell *lc; + + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + + /* No work if it's already invalidated */ + if (!plansource->is_valid) + continue; + + /* Never invalidate transaction control commands */ + if (IsTransactionStmtPlan(plansource)) + continue; + + /* + * Check the dependency list for the generic plan. + */ + if (plansource->gplan && plansource->gplan->is_valid) + { + foreach(lc, plansource->gplan->stmt_list) + { + PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); + ListCell *lc3; + + Assert(!IsA(plannedstmt, Query)); + if (!IsA(plannedstmt, PlannedStmt)) + continue; /* Ignore utility statements */ + foreach(lc3, plannedstmt->invalItems) + { + PlanInvalItem *item = (PlanInvalItem *) lfirst(lc3); + + if (item->cacheId != cacheid) + continue; + if (hashvalue == 0 || + item->hashValue == hashvalue) + { + /* Invalidate the generic plan only */ + plansource->gplan->is_valid = false; + break; /* out of invalItems scan */ + } + } + if (!plansource->gplan->is_valid) + break; /* out of stmt_list scan */ + } + } + } + } + + /* * PlanCacheSysCallback * Syscache inval callback function for other caches *