diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/executor/execParallel.c | 14 | ||||
-rw-r--r-- | src/backend/foreign/foreign.c | 38 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 5 | ||||
-rw-r--r-- | src/backend/nodes/outfuncs.c | 12 | ||||
-rw-r--r-- | src/backend/nodes/readfuncs.c | 5 | ||||
-rw-r--r-- | src/backend/optimizer/path/joinpath.c | 4 | ||||
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 11 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 25 | ||||
-rw-r--r-- | src/backend/optimizer/plan/setrefs.c | 20 | ||||
-rw-r--r-- | src/backend/optimizer/util/relnode.c | 82 | ||||
-rw-r--r-- | src/backend/utils/cache/plancache.c | 178 | ||||
-rw-r--r-- | src/include/foreign/foreign.h | 2 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 4 | ||||
-rw-r--r-- | src/include/nodes/plannodes.h | 9 | ||||
-rw-r--r-- | src/include/nodes/relation.h | 17 | ||||
-rw-r--r-- | src/include/utils/plancache.h | 9 |
16 files changed, 173 insertions, 262 deletions
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index 6de90705e48..380d743f6ce 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -145,10 +145,12 @@ ExecSerializePlan(Plan *plan, EState *estate) pstmt = makeNode(PlannedStmt); pstmt->commandType = CMD_SELECT; pstmt->queryId = 0; - pstmt->hasReturning = 0; - pstmt->hasModifyingCTE = 0; - pstmt->canSetTag = 1; - pstmt->transientPlan = 0; + pstmt->hasReturning = false; + pstmt->hasModifyingCTE = false; + pstmt->canSetTag = true; + pstmt->transientPlan = false; + pstmt->dependsOnRole = false; + pstmt->parallelModeNeeded = false; pstmt->planTree = plan; pstmt->rtable = estate->es_range_table; pstmt->resultRelations = NIL; @@ -156,11 +158,9 @@ ExecSerializePlan(Plan *plan, EState *estate) pstmt->subplans = NIL; pstmt->rewindPlanIDs = NULL; pstmt->rowMarks = NIL; - pstmt->nParamExec = estate->es_plannedstmt->nParamExec; pstmt->relationOids = NIL; pstmt->invalItems = NIL; /* workers can't replan anyway... */ - pstmt->hasRowSecurity = false; - pstmt->hasForeignJoin = false; + pstmt->nParamExec = estate->es_plannedstmt->nParamExec; /* Return serialized copy of our dummy PlannedStmt. */ return nodeToString(pstmt); diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index 633b9832e54..66f98f1c7ec 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -31,7 +31,8 @@ extern Datum pg_options_to_table(PG_FUNCTION_ARGS); extern Datum postgresql_fdw_validator(PG_FUNCTION_ARGS); -static HeapTuple find_user_mapping(Oid userid, Oid serverid, bool missing_ok); +static HeapTuple find_user_mapping(Oid userid, Oid serverid); + /* * GetForeignDataWrapper - look up the foreign-data wrapper by OID. @@ -223,7 +224,7 @@ GetUserMapping(Oid userid, Oid serverid) bool isnull; UserMapping *um; - tp = find_user_mapping(userid, serverid, false); + tp = find_user_mapping(userid, serverid); um = (UserMapping *) palloc(sizeof(UserMapping)); um->umid = HeapTupleGetOid(tp); @@ -250,23 +251,14 @@ GetUserMapping(Oid userid, Oid serverid) * * If no mapping is found for the supplied user, we also look for * PUBLIC mappings (userid == InvalidOid). - * - * If missing_ok is true, the function returns InvalidOid when it does not find - * required user mapping. Otherwise, find_user_mapping() throws error if it - * does not find required user mapping. */ Oid -GetUserMappingId(Oid userid, Oid serverid, bool missing_ok) +GetUserMappingId(Oid userid, Oid serverid) { HeapTuple tp; Oid umid; - tp = find_user_mapping(userid, serverid, missing_ok); - - Assert(missing_ok || tp); - - if (!tp && missing_ok) - return InvalidOid; + tp = find_user_mapping(userid, serverid); /* Extract the Oid */ umid = HeapTupleGetOid(tp); @@ -276,19 +268,14 @@ GetUserMappingId(Oid userid, Oid serverid, bool missing_ok) return umid; } - /* * find_user_mapping - Guts of GetUserMapping family. * * If no mapping is found for the supplied user, we also look for * PUBLIC mappings (userid == InvalidOid). - * - * If missing_ok is true, the function returns NULL, if it does not find - * the required user mapping. Otherwise, it throws error if it does not - * find the required user mapping. */ static HeapTuple -find_user_mapping(Oid userid, Oid serverid, bool missing_ok) +find_user_mapping(Oid userid, Oid serverid) { HeapTuple tp; @@ -305,15 +292,10 @@ find_user_mapping(Oid userid, Oid serverid, bool missing_ok) ObjectIdGetDatum(serverid)); if (!HeapTupleIsValid(tp)) - { - if (missing_ok) - return NULL; - else - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("user mapping not found for \"%s\"", - MappingUserName(userid)))); - } + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("user mapping not found for \"%s\"", + MappingUserName(userid)))); return tp; } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index d2786575d98..3244c76ddcc 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -85,6 +85,8 @@ _copyPlannedStmt(const PlannedStmt *from) COPY_SCALAR_FIELD(hasModifyingCTE); COPY_SCALAR_FIELD(canSetTag); COPY_SCALAR_FIELD(transientPlan); + COPY_SCALAR_FIELD(dependsOnRole); + COPY_SCALAR_FIELD(parallelModeNeeded); COPY_NODE_FIELD(planTree); COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(resultRelations); @@ -95,9 +97,6 @@ _copyPlannedStmt(const PlannedStmt *from) COPY_NODE_FIELD(relationOids); COPY_NODE_FIELD(invalItems); COPY_SCALAR_FIELD(nParamExec); - COPY_SCALAR_FIELD(hasRowSecurity); - COPY_SCALAR_FIELD(parallelModeNeeded); - COPY_SCALAR_FIELD(hasForeignJoin); return newnode; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index c43ebe1a50b..e1966227379 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -261,6 +261,8 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) WRITE_BOOL_FIELD(hasModifyingCTE); WRITE_BOOL_FIELD(canSetTag); WRITE_BOOL_FIELD(transientPlan); + WRITE_BOOL_FIELD(dependsOnRole); + WRITE_BOOL_FIELD(parallelModeNeeded); WRITE_NODE_FIELD(planTree); WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(resultRelations); @@ -271,9 +273,6 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) WRITE_NODE_FIELD(relationOids); WRITE_NODE_FIELD(invalItems); WRITE_INT_FIELD(nParamExec); - WRITE_BOOL_FIELD(hasRowSecurity); - WRITE_BOOL_FIELD(parallelModeNeeded); - WRITE_BOOL_FIELD(hasForeignJoin); } /* @@ -2014,11 +2013,11 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node) WRITE_INT_FIELD(nParamExec); WRITE_UINT_FIELD(lastPHId); WRITE_UINT_FIELD(lastRowMarkId); + WRITE_INT_FIELD(lastPlanNodeId); WRITE_BOOL_FIELD(transientPlan); - WRITE_BOOL_FIELD(hasRowSecurity); + WRITE_BOOL_FIELD(dependsOnRole); WRITE_BOOL_FIELD(parallelModeOK); WRITE_BOOL_FIELD(parallelModeNeeded); - WRITE_BOOL_FIELD(hasForeignJoin); } static void @@ -2106,7 +2105,10 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_FLOAT_FIELD(allvisfrac, "%.6f"); WRITE_NODE_FIELD(subroot); WRITE_NODE_FIELD(subplan_params); + WRITE_INT_FIELD(rel_parallel_workers); WRITE_OID_FIELD(serverid); + WRITE_OID_FIELD(userid); + WRITE_BOOL_FIELD(useridiscurrent); /* we don't try to print fdwroutine or fdw_private */ WRITE_NODE_FIELD(baserestrictinfo); WRITE_NODE_FIELD(joininfo); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 491d3e08edc..94954dcc722 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1390,6 +1390,8 @@ _readPlannedStmt(void) READ_BOOL_FIELD(hasModifyingCTE); READ_BOOL_FIELD(canSetTag); READ_BOOL_FIELD(transientPlan); + READ_BOOL_FIELD(dependsOnRole); + READ_BOOL_FIELD(parallelModeNeeded); READ_NODE_FIELD(planTree); READ_NODE_FIELD(rtable); READ_NODE_FIELD(resultRelations); @@ -1400,9 +1402,6 @@ _readPlannedStmt(void) READ_NODE_FIELD(relationOids); READ_NODE_FIELD(invalItems); READ_INT_FIELD(nParamExec); - READ_BOOL_FIELD(hasRowSecurity); - READ_BOOL_FIELD(parallelModeNeeded); - READ_BOOL_FIELD(hasForeignJoin); READ_DONE(); } diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 9d06fb2637e..cc7384f7e5e 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -213,8 +213,8 @@ add_paths_to_joinrel(PlannerInfo *root, /* * 5. If inner and outer relations are foreign tables (or joins) belonging - * to the same server and using the same user mapping, give the FDW a - * chance to push down joins. + * to the same server and assigned to the same user to check access + * permissions as, give the FDW a chance to push down joins. */ if (joinrel->fdwroutine && joinrel->fdwroutine->GetForeignJoinPaths) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index cebfe197346..54d601fc47d 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -3247,13 +3247,12 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, scan_plan->fs_relids = best_path->path.parent->relids; /* - * If a join between foreign relations was pushed down, remember it. The - * push-down safety of the join depends upon the server and user mapping - * being same. That can change between planning and execution time, in - * which case the plan should be invalidated. + * If this is a foreign join, and to make it valid to push down we had to + * assume that the current user is the same as some user explicitly named + * in the query, mark the finished plan as depending on the current user. */ - if (scan_relid == 0) - root->glob->hasForeignJoin = true; + if (rel->useridiscurrent) + root->glob->dependsOnRole = true; /* * Replace any outer-relation variables with nestloop params in the qual, diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index f484fb91c11..b2656283251 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -219,8 +219,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) glob->lastRowMarkId = 0; glob->lastPlanNodeId = 0; glob->transientPlan = false; - glob->hasRowSecurity = false; - glob->hasForeignJoin = false; + glob->dependsOnRole = false; /* * Assess whether it's feasible to use parallel mode for this query. We @@ -405,6 +404,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) result->hasModifyingCTE = parse->hasModifyingCTE; result->canSetTag = parse->canSetTag; result->transientPlan = glob->transientPlan; + result->dependsOnRole = glob->dependsOnRole; + result->parallelModeNeeded = glob->parallelModeNeeded; result->planTree = top_plan; result->rtable = glob->finalrtable; result->resultRelations = glob->resultRelations; @@ -415,9 +416,6 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) result->relationOids = glob->relationOids; result->invalItems = glob->invalItems; result->nParamExec = glob->nParamExec; - result->hasRowSecurity = glob->hasRowSecurity; - result->parallelModeNeeded = glob->parallelModeNeeded; - result->hasForeignJoin = glob->hasForeignJoin; return result; } @@ -1628,8 +1626,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * This may add new security barrier subquery RTEs to the rangetable. */ expand_security_quals(root, tlist); - if (parse->hasRowSecurity) - root->glob->hasRowSecurity = true; /* * We are now done hacking up the query's targetlist. Most of the @@ -1960,7 +1956,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * If the current_rel belongs to a single FDW, so does the final_rel. */ final_rel->serverid = current_rel->serverid; - final_rel->umid = current_rel->umid; + final_rel->userid = current_rel->userid; + final_rel->useridiscurrent = current_rel->useridiscurrent; final_rel->fdwroutine = current_rel->fdwroutine; /* @@ -3337,7 +3334,8 @@ create_grouping_paths(PlannerInfo *root, * If the input rel belongs to a single FDW, so does the grouped rel. */ grouped_rel->serverid = input_rel->serverid; - grouped_rel->umid = input_rel->umid; + grouped_rel->userid = input_rel->userid; + grouped_rel->useridiscurrent = input_rel->useridiscurrent; grouped_rel->fdwroutine = input_rel->fdwroutine; /* @@ -3891,7 +3889,8 @@ create_window_paths(PlannerInfo *root, * If the input rel belongs to a single FDW, so does the window rel. */ window_rel->serverid = input_rel->serverid; - window_rel->umid = input_rel->umid; + window_rel->userid = input_rel->userid; + window_rel->useridiscurrent = input_rel->useridiscurrent; window_rel->fdwroutine = input_rel->fdwroutine; /* @@ -4071,7 +4070,8 @@ create_distinct_paths(PlannerInfo *root, * If the input rel belongs to a single FDW, so does the distinct_rel. */ distinct_rel->serverid = input_rel->serverid; - distinct_rel->umid = input_rel->umid; + distinct_rel->userid = input_rel->userid; + distinct_rel->useridiscurrent = input_rel->useridiscurrent; distinct_rel->fdwroutine = input_rel->fdwroutine; /* Estimate number of distinct rows there will be */ @@ -4279,7 +4279,8 @@ create_ordered_paths(PlannerInfo *root, * If the input rel belongs to a single FDW, so does the ordered_rel. */ ordered_rel->serverid = input_rel->serverid; - ordered_rel->umid = input_rel->umid; + ordered_rel->userid = input_rel->userid; + ordered_rel->useridiscurrent = input_rel->useridiscurrent; ordered_rel->fdwroutine = input_rel->fdwroutine; foreach(lc, input_rel->pathlist) diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index ffff6db2490..6ea46c46812 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -2432,9 +2432,10 @@ record_plan_function_dependency(PlannerInfo *root, Oid funcid) /* * extract_query_dependencies - * Given a not-yet-planned query or queries (i.e. a Query node or list - * of Query nodes), extract dependencies just as set_plan_references - * would do. + * Given a rewritten, but not yet planned, query or queries + * (i.e. a Query node or list of Query nodes), extract dependencies + * just as set_plan_references would do. Also detect whether any + * rewrite steps were affected by RLS. * * This is needed by plancache.c to handle invalidation of cached unplanned * queries. @@ -2453,7 +2454,8 @@ extract_query_dependencies(Node *query, glob.type = T_PlannerGlobal; glob.relationOids = NIL; glob.invalItems = NIL; - glob.hasRowSecurity = false; + /* Hack: we use glob.dependsOnRole to collect hasRowSecurity flags */ + glob.dependsOnRole = false; MemSet(&root, 0, sizeof(root)); root.type = T_PlannerInfo; @@ -2463,7 +2465,7 @@ extract_query_dependencies(Node *query, *relationOids = glob.relationOids; *invalItems = glob.invalItems; - *hasRowSecurity = glob.hasRowSecurity; + *hasRowSecurity = glob.dependsOnRole; } static bool @@ -2479,10 +2481,6 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context) Query *query = (Query *) node; ListCell *lc; - /* Collect row security information */ - if (query->hasRowSecurity) - context->glob->hasRowSecurity = true; - if (query->commandType == CMD_UTILITY) { /* @@ -2494,6 +2492,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context) return false; } + /* Remember if any Query has RLS quals applied by rewriter */ + if (query->hasRowSecurity) + context->glob->dependsOnRole = true; + /* Collect relation OIDs in this Query's rtable */ foreach(lc, query->rtable) { diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index a0a284b901b..806600ed107 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -15,8 +15,6 @@ #include "postgres.h" #include "miscadmin.h" -#include "catalog/pg_class.h" -#include "foreign/foreign.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -107,7 +105,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) rel->consider_startup = (root->tuple_fraction > 0); rel->consider_param_startup = false; /* might get changed later */ rel->consider_parallel = false; /* might get changed later */ - rel->rel_parallel_workers = -1; /* set up in GetRelationInfo */ rel->reltarget = create_empty_pathtarget(); rel->pathlist = NIL; rel->ppilist = NIL; @@ -129,8 +126,10 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) rel->allvisfrac = 0; rel->subroot = NULL; rel->subplan_params = NIL; + rel->rel_parallel_workers = -1; /* set up in GetRelationInfo */ rel->serverid = InvalidOid; - rel->umid = InvalidOid; + rel->userid = rte->checkAsUser; + rel->useridiscurrent = false; rel->fdwroutine = NULL; rel->fdw_private = NULL; rel->baserestrictinfo = NIL; @@ -170,30 +169,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) break; } - /* For foreign tables get the user mapping */ - if (rte->relkind == RELKIND_FOREIGN_TABLE) - { - /* - * This should match what ExecCheckRTEPerms() does. - * - * Note that if the plan ends up depending on the user OID in any way - * - e.g. if it depends on the computed user mapping OID - we must - * ensure that it gets invalidated in the case of a user OID change. - * See RevalidateCachedQuery and more generally the hasForeignJoin - * flags in PlannerGlobal and PlannedStmt. - * - * It's possible, and not necessarily an error, for rel->umid to be - * InvalidOid even though rel->serverid is set. That just means there - * is a server with no user mapping. - */ - Oid userid; - - userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId(); - rel->umid = GetUserMappingId(userid, rel->serverid, true); - } - else - rel->umid = InvalidOid; - /* Save the finished struct in the query's simple_rel_array */ root->simple_rel_array[relid] = rel; @@ -423,8 +398,10 @@ build_join_rel(PlannerInfo *root, joinrel->allvisfrac = 0; joinrel->subroot = NULL; joinrel->subplan_params = NIL; + joinrel->rel_parallel_workers = -1; joinrel->serverid = InvalidOid; - joinrel->umid = InvalidOid; + joinrel->userid = InvalidOid; + joinrel->useridiscurrent = false; joinrel->fdwroutine = NULL; joinrel->fdw_private = NULL; joinrel->baserestrictinfo = NIL; @@ -435,24 +412,43 @@ build_join_rel(PlannerInfo *root, /* * Set up foreign-join fields if outer and inner relation are foreign - * tables (or joins) belonging to the same server and using the same user - * mapping. - * - * Otherwise those fields are left invalid, so FDW API will not be called - * for the join relation. + * tables (or joins) belonging to the same server and assigned to the same + * user to check access permissions as. In addition to an exact match of + * userid, we allow the case where one side has zero userid (implying + * current user) and the other side has explicit userid that happens to + * equal the current user; but in that case, pushdown of the join is only + * valid for the current user. The useridiscurrent field records whether + * we had to make such an assumption for this join or any sub-join. * - * For FDWs like file_fdw, which ignore user mapping, the user mapping id - * associated with the joining relation may be invalid. A valid serverid - * distinguishes between a pushed down join with no user mapping and a - * join which can not be pushed down because of user mapping mismatch. + * Otherwise these fields are left invalid, so GetForeignJoinPaths will + * not be called for the join relation. */ if (OidIsValid(outer_rel->serverid) && - inner_rel->serverid == outer_rel->serverid && - inner_rel->umid == outer_rel->umid) + inner_rel->serverid == outer_rel->serverid) { - joinrel->serverid = outer_rel->serverid; - joinrel->umid = outer_rel->umid; - joinrel->fdwroutine = outer_rel->fdwroutine; + if (inner_rel->userid == outer_rel->userid) + { + joinrel->serverid = outer_rel->serverid; + joinrel->userid = outer_rel->userid; + joinrel->useridiscurrent = outer_rel->useridiscurrent || inner_rel->useridiscurrent; + joinrel->fdwroutine = outer_rel->fdwroutine; + } + else if (!OidIsValid(inner_rel->userid) && + outer_rel->userid == GetUserId()) + { + joinrel->serverid = outer_rel->serverid; + joinrel->userid = outer_rel->userid; + joinrel->useridiscurrent = true; + joinrel->fdwroutine = outer_rel->fdwroutine; + } + else if (!OidIsValid(outer_rel->userid) && + inner_rel->userid == GetUserId()) + { + joinrel->serverid = outer_rel->serverid; + joinrel->userid = inner_rel->userid; + joinrel->useridiscurrent = true; + joinrel->fdwroutine = outer_rel->fdwroutine; + } } /* diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 005e4b7f1c3..f42a62d5000 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -100,13 +100,10 @@ static void AcquireExecutorLocks(List *stmt_list, bool acquire); static void AcquirePlannerLocks(List *stmt_list, bool acquire); static void ScanQueryForLocks(Query *parsetree, bool acquire); static bool ScanQueryWalker(Node *node, bool *acquire); -static bool plan_list_is_transient(List *stmt_list); static TupleDesc PlanCacheComputeResultDesc(List *stmt_list); static void PlanCacheRelCallback(Datum arg, Oid relid); static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); -static void PlanCacheUserMappingCallback(Datum arg, int cacheid, - uint32 hashvalue); /* @@ -122,8 +119,6 @@ InitPlanCache(void) CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0); CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0); - /* User mapping change may invalidate plans with pushed down foreign join */ - CacheRegisterSyscacheCallback(USERMAPPINGOID, PlanCacheUserMappingCallback, (Datum) 0); } /* @@ -198,6 +193,9 @@ CreateCachedPlan(Node *raw_parse_tree, plansource->invalItems = NIL; plansource->search_path = NULL; plansource->query_context = NULL; + plansource->rewriteRoleId = InvalidOid; + plansource->rewriteRowSecurity = false; + plansource->dependsOnRLS = false; plansource->gplan = NULL; plansource->is_oneshot = false; plansource->is_complete = false; @@ -208,9 +206,6 @@ CreateCachedPlan(Node *raw_parse_tree, plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_custom_plans = 0; - plansource->hasRowSecurity = false; - plansource->planUserId = InvalidOid; - plansource->row_security_env = false; MemoryContextSwitchTo(oldcxt); @@ -266,6 +261,9 @@ CreateOneShotCachedPlan(Node *raw_parse_tree, plansource->invalItems = NIL; plansource->search_path = NULL; plansource->query_context = NULL; + plansource->rewriteRoleId = InvalidOid; + plansource->rewriteRowSecurity = false; + plansource->dependsOnRLS = false; plansource->gplan = NULL; plansource->is_oneshot = true; plansource->is_complete = false; @@ -276,8 +274,6 @@ CreateOneShotCachedPlan(Node *raw_parse_tree, plansource->generic_cost = -1; plansource->total_custom_cost = 0; plansource->num_custom_plans = 0; - plansource->planUserId = InvalidOid; - plansource->row_security_env = false; return plansource; } @@ -384,7 +380,11 @@ CompleteCachedPlan(CachedPlanSource *plansource, extract_query_dependencies((Node *) querytree_list, &plansource->relationOids, &plansource->invalItems, - &plansource->hasRowSecurity); + &plansource->dependsOnRLS); + + /* Update RLS info as well. */ + plansource->rewriteRoleId = GetUserId(); + plansource->rewriteRowSecurity = row_security; /* * Also save the current search_path in the query_context. (This @@ -416,8 +416,6 @@ CompleteCachedPlan(CachedPlanSource *plansource, plansource->cursor_options = cursor_options; plansource->fixed_result = fixed_result; plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list); - plansource->planUserId = GetUserId(); - plansource->row_security_env = row_security; MemoryContextSwitchTo(oldcxt); @@ -583,12 +581,10 @@ RevalidateCachedQuery(CachedPlanSource *plansource) /* * If the query is currently valid, we should have a saved search_path --- * check to see if that matches the current environment. If not, we want - * to force replan. We should also have a valid planUserId. + * to force replan. */ if (plansource->is_valid) { - Assert(OidIsValid(plansource->planUserId)); - Assert(plansource->search_path != NULL); if (!OverrideSearchPathMatchesCurrent(plansource->search_path)) { @@ -600,29 +596,15 @@ RevalidateCachedQuery(CachedPlanSource *plansource) } /* - * If the plan has a possible RLS dependency, force a replan if either the - * role or the row_security setting has changed. + * If the query rewrite phase had a possible RLS dependency, we must redo + * it if either the role or the row_security setting has changed. */ - if (plansource->is_valid - && plansource->hasRowSecurity - && (plansource->planUserId != GetUserId() - || plansource->row_security_env != row_security)) + if (plansource->is_valid && plansource->dependsOnRLS && + (plansource->rewriteRoleId != GetUserId() || + plansource->rewriteRowSecurity != row_security)) plansource->is_valid = false; /* - * If we have a join pushed down to the foreign server and the current - * user is different from the one for which the plan was created, - * invalidate the generic plan since user mapping for the new user might - * make the join unsafe to push down, or change which user mapping is - * used. - */ - if (plansource->is_valid && - plansource->gplan && - plansource->gplan->has_foreign_join && - plansource->planUserId != GetUserId()) - plansource->gplan->is_valid = false; - - /* * If the query is currently valid, acquire locks on the referenced * objects; then check again. We need to do it this way to cover the race * condition that an invalidation message arrives before we get the locks. @@ -657,14 +639,6 @@ RevalidateCachedQuery(CachedPlanSource *plansource) plansource->search_path = NULL; /* - * The plan is invalid, possibly due to row security, so we need to reset - * row_security_env and planUserId as we're about to re-plan with the - * current settings. - */ - plansource->row_security_env = row_security; - plansource->planUserId = GetUserId(); - - /* * Free the query_context. We don't really expect MemoryContextDelete to * fail, but just in case, make sure the CachedPlanSource is left in a * reasonably sane state. (The generic plan won't get unlinked yet, but @@ -774,7 +748,11 @@ RevalidateCachedQuery(CachedPlanSource *plansource) extract_query_dependencies((Node *) qlist, &plansource->relationOids, &plansource->invalItems, - &plansource->hasRowSecurity); + &plansource->dependsOnRLS); + + /* Update RLS info as well. */ + plansource->rewriteRoleId = GetUserId(); + plansource->rewriteRowSecurity = row_security; /* * Also save the current search_path in the query_context. (This should @@ -832,6 +810,13 @@ CheckCachedPlan(CachedPlanSource *plansource) Assert(!plan->is_oneshot); /* + * If plan isn't valid for current role, we can't use it. + */ + if (plan->is_valid && plan->dependsOnRole && + plan->planRoleId != GetUserId()) + plan->is_valid = false; + + /* * If it appears valid, acquire locks and recheck; this is much the same * logic as in RevalidateCachedQuery, but for a plan. */ @@ -900,6 +885,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, List *plist; bool snapshot_set; bool spi_pushed; + bool is_transient; MemoryContext plan_context; MemoryContext oldcxt = CurrentMemoryContext; ListCell *lc; @@ -997,7 +983,28 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, plan = (CachedPlan *) palloc(sizeof(CachedPlan)); plan->magic = CACHEDPLAN_MAGIC; plan->stmt_list = plist; - if (plan_list_is_transient(plist)) + + /* + * CachedPlan is dependent on role either if RLS affected the rewrite + * phase or if a role dependency was injected during planning. And it's + * transient if any plan is marked so. + */ + plan->planRoleId = GetUserId(); + plan->dependsOnRole = plansource->dependsOnRLS; + is_transient = false; + foreach(lc, plist) + { + PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); + + if (!IsA(plannedstmt, PlannedStmt)) + continue; /* Ignore utility statements */ + + if (plannedstmt->transientPlan) + is_transient = true; + if (plannedstmt->dependsOnRole) + plan->dependsOnRole = true; + } + if (is_transient) { Assert(TransactionIdIsNormal(TransactionXmin)); plan->saved_xmin = TransactionXmin; @@ -1010,20 +1017,6 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, plan->is_saved = false; plan->is_valid = true; - /* - * Walk through the plist and set hasForeignJoin if any of the plans have - * it set. - */ - plan->has_foreign_join = false; - foreach(lc, plist) - { - PlannedStmt *plan_stmt = (PlannedStmt *) lfirst(lc); - - if (IsA(plan_stmt, PlannedStmt)) - plan->has_foreign_join = - plan->has_foreign_join || plan_stmt->hasForeignJoin; - } - /* assign generation number to new plan */ plan->generation = ++(plansource->generation); @@ -1401,6 +1394,9 @@ CopyCachedPlan(CachedPlanSource *plansource) if (plansource->search_path) newsource->search_path = CopyOverrideSearchPath(plansource->search_path); newsource->query_context = querytree_context; + newsource->rewriteRoleId = plansource->rewriteRoleId; + newsource->rewriteRowSecurity = plansource->rewriteRowSecurity; + newsource->dependsOnRLS = plansource->dependsOnRLS; newsource->gplan = NULL; @@ -1416,14 +1412,6 @@ CopyCachedPlan(CachedPlanSource *plansource) newsource->total_custom_cost = plansource->total_custom_cost; newsource->num_custom_plans = plansource->num_custom_plans; - /* - * Copy over the user the query was planned as, and under what RLS - * environment. We will check during RevalidateCachedQuery() if the user - * or environment has changed and, if so, will force a re-plan. - */ - newsource->planUserId = plansource->planUserId; - newsource->row_security_env = plansource->row_security_env; - MemoryContextSwitchTo(oldcxt); return newsource; @@ -1668,28 +1656,6 @@ ScanQueryWalker(Node *node, bool *acquire) } /* - * plan_list_is_transient: check if any of the plans in the list are transient. - */ -static bool -plan_list_is_transient(List *stmt_list) -{ - ListCell *lc; - - foreach(lc, stmt_list) - { - PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); - - if (!IsA(plannedstmt, PlannedStmt)) - continue; /* Ignore utility statements */ - - if (plannedstmt->transientPlan) - return true; - } - - return false; -} - -/* * PlanCacheComputeResultDesc: given a list of analyzed-and-rewritten Queries, * determine the result tupledesc it will produce. Returns NULL if the * execution will not return tuples. @@ -1888,40 +1854,6 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue) } /* - * PlanCacheUserMappingCallback - * Syscache inval callback function for user mapping cache invalidation. - * - * Invalidates plans which have pushed down foreign joins. - */ -static void -PlanCacheUserMappingCallback(Datum arg, int cacheid, uint32 hashvalue) -{ - CachedPlanSource *plansource; - - for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) - { - 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; - - /* - * If the plan has pushed down foreign joins, those join may become - * unsafe to push down because of user mapping changes. Invalidate - * only the generic plan, since changes to user mapping do not - * invalidate the parse tree. - */ - if (plansource->gplan && plansource->gplan->has_foreign_join) - plansource->gplan->is_valid = false; - } -} - -/* * ResetPlanCache: invalidate all cached plans. */ void diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h index f45873f4ee9..ad586e446a9 100644 --- a/src/include/foreign/foreign.h +++ b/src/include/foreign/foreign.h @@ -72,7 +72,7 @@ typedef struct ForeignTable extern ForeignServer *GetForeignServer(Oid serverid); extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok); extern UserMapping *GetUserMapping(Oid userid, Oid serverid); -extern Oid GetUserMappingId(Oid userid, Oid serverid, bool missing_ok); +extern Oid GetUserMappingId(Oid userid, Oid serverid); extern UserMapping *GetUserMappingById(Oid umid); extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid); extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index d36d9c6d019..3773dd9c2ff 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -121,7 +121,7 @@ typedef struct Query bool hasRecursive; /* WITH RECURSIVE was specified */ bool hasModifyingCTE; /* has INSERT/UPDATE/DELETE in WITH */ bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */ - bool hasRowSecurity; /* row security applied? */ + bool hasRowSecurity; /* rewriter has applied some RLS policy */ List *cteList; /* WITH list (of CommonTableExpr's) */ @@ -2823,7 +2823,7 @@ typedef enum VacuumOption VACOPT_FULL = 1 << 4, /* FULL (non-concurrent) vacuum */ VACOPT_NOWAIT = 1 << 5, /* don't wait to get lock (autovacuum only) */ VACOPT_SKIPTOAST = 1 << 6, /* don't process the TOAST table, if any */ - VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */ + VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */ } VacuumOption; typedef struct VacuumStmt diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index b375870e199..369179f2912 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -49,6 +49,10 @@ typedef struct PlannedStmt bool transientPlan; /* redo plan when TransactionXmin changes? */ + bool dependsOnRole; /* is plan specific to current role? */ + + bool parallelModeNeeded; /* parallel mode required to execute? */ + struct Plan *planTree; /* tree of Plan nodes */ List *rtable; /* list of RangeTblEntry nodes */ @@ -69,11 +73,6 @@ typedef struct PlannedStmt List *invalItems; /* other dependencies, as PlanInvalItems */ int nParamExec; /* number of PARAM_EXEC Params used */ - - bool hasRowSecurity; /* row security applied? */ - - bool parallelModeNeeded; /* parallel mode required to execute? */ - bool hasForeignJoin; /* Plan has a pushed down foreign join */ } PlannedStmt; /* macro for fetching the Plan associated with a SubPlan node */ diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index cd463535760..2be8908445b 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -121,13 +121,11 @@ typedef struct PlannerGlobal bool transientPlan; /* redo plan when TransactionXmin changes? */ - bool hasRowSecurity; /* row security applied? */ + bool dependsOnRole; /* is plan specific to current role? */ bool parallelModeOK; /* parallel mode potentially OK? */ bool parallelModeNeeded; /* parallel mode actually required? */ - - bool hasForeignJoin; /* does have a pushed down foreign join */ } PlannerGlobal; /* macro for fetching the Plan associated with a SubPlan node */ @@ -426,11 +424,12 @@ typedef struct PlannerInfo * in just as for a baserel, except we don't bother with lateral_vars. * * If the relation is either a foreign table or a join of foreign tables that - * all belong to the same foreign server and use the same user mapping, these - * fields will be set: + * all belong to the same foreign server and are assigned to the same user to + * check access permissions as (cf checkAsUser), these fields will be set: * * serverid - OID of foreign server, if foreign table (else InvalidOid) - * umid - OID of user mapping, if foreign table (else InvalidOid) + * userid - OID of user to check access as (InvalidOid means current user) + * useridiscurrent - we've assumed that userid equals current user * fdwroutine - function hooks for FDW, if foreign table (else NULL) * fdw_private - private state for FDW, if foreign table (else NULL) * @@ -528,8 +527,8 @@ typedef struct RelOptInfo /* Information about foreign tables and foreign joins */ Oid serverid; /* identifies server for the table or join */ - Oid umid; /* identifies user mapping for the table or - * join */ + Oid userid; /* identifies user to check access as */ + bool useridiscurrent; /* join is only valid for current user */ /* use "struct FdwRoutine" to avoid including fdwapi.h here */ struct FdwRoutine *fdwroutine; void *fdw_private; @@ -653,7 +652,7 @@ typedef struct ForeignKeyOptInfo struct EquivalenceClass *eclass[INDEX_MAX_KEYS]; /* List of non-EC RestrictInfos matching each column's condition */ List *rinfos[INDEX_MAX_KEYS]; -} ForeignKeyOptInfo; +} ForeignKeyOptInfo; /* diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 251f2d71862..938c4afc8be 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -93,8 +93,10 @@ typedef struct CachedPlanSource List *invalItems; /* other dependencies, as PlanInvalItems */ struct OverrideSearchPath *search_path; /* search_path used for * parsing and planning */ - Oid planUserId; /* User-id that the plan depends on */ MemoryContext query_context; /* context holding the above, or NULL */ + Oid rewriteRoleId; /* Role ID we did rewriting for */ + bool rewriteRowSecurity; /* row_security used during rewrite */ + bool dependsOnRLS; /* is rewritten query specific to the above? */ /* If we have a generic plan, this is a reference-counted link to it: */ struct CachedPlan *gplan; /* generic plan, or NULL if not valid */ /* Some state flags: */ @@ -109,8 +111,6 @@ typedef struct CachedPlanSource double generic_cost; /* cost of generic plan, or -1 if not known */ double total_custom_cost; /* total cost of custom plans so far */ int num_custom_plans; /* number of plans included in total */ - bool hasRowSecurity; /* planned with row security? */ - bool row_security_env; /* row security setting when planned */ } CachedPlanSource; /* @@ -131,11 +131,12 @@ typedef struct CachedPlan bool is_oneshot; /* is it a "oneshot" plan? */ bool is_saved; /* is CachedPlan in a long-lived context? */ bool is_valid; /* is the stmt_list currently valid? */ + Oid planRoleId; /* Role ID the plan was created for */ + bool dependsOnRole; /* is plan specific to that role? */ TransactionId saved_xmin; /* if valid, replan when TransactionXmin * changes from this value */ int generation; /* parent's generation number for this plan */ int refcount; /* count of live references to this struct */ - bool has_foreign_join; /* plan has pushed down a foreign join */ MemoryContext context; /* context containing this CachedPlan */ } CachedPlan; |