aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2016-07-15 17:22:56 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2016-07-15 17:23:02 -0400
commit45639a0525a58a2700cf46d4c934d6de78349dac (patch)
treea4333eca63b541394555355bdfbea0fb95fbd35e /src
parent533e9c6b0628f6557ddcaf3e5177081878ea7cb6 (diff)
downloadpostgresql-45639a0525a58a2700cf46d4c934d6de78349dac.tar.gz
postgresql-45639a0525a58a2700cf46d4c934d6de78349dac.zip
Avoid invalidating all foreign-join cached plans when user mappings change.
We must not push down a foreign join when the foreign tables involved should be accessed under different user mappings. Previously we tried to enforce that rule literally during planning, but that meant that the resulting plans were dependent on the current contents of the pg_user_mapping catalog, and we had to blow away all cached plans containing any remote join when anything at all changed in pg_user_mapping. This could have been improved somewhat, but the fact that a syscache inval callback has very limited info about what changed made it hard to do better within that design. Instead, let's change the planner to not consider user mappings per se, but to allow a foreign join if both RTEs have the same checkAsUser value. If they do, then they necessarily will use the same user mapping at runtime, and we don't need to know specifically which one that is. Post-plan-time changes in pg_user_mapping no longer require any plan invalidation. This rule does give up some optimization ability, to wit where two foreign table references come from views with different owners or one's from a view and one's directly in the query, but nonetheless the same user mapping would have applied. We'll sacrifice the first case, but to not regress more than we have to in the second case, allow a foreign join involving both zero and nonzero checkAsUser values if the nonzero one is the same as the prevailing effective userID. In that case, mark the plan as only runnable by that userID. The plancache code already had a notion of plans being userID-specific, in order to support RLS. It was a little confused though, in particular lacking clarity of thought as to whether it was the rewritten query or just the finished plan that's dependent on the userID. Rearrange that code so that it's clearer what depends on which, and so that the same logic applies to both RLS-injected role dependency and foreign-join-injected role dependency. Note that this patch doesn't remove the other issue mentioned in the original complaint, which is that while we'll reliably stop using a foreign join if it's disallowed in a new context, we might fail to start using a foreign join if it's now allowed, but we previously created a generic cached plan that didn't use one. It was agreed that the chance of winning that way was not high enough to justify the much larger number of plan invalidations that would have to occur if we tried to cause it to happen. In passing, clean up randomly-varying spelling of EXPLAIN commands in postgres_fdw.sql, and fix a COSTS ON example that had been allowed to leak into the committed tests. This reverts most of commits fbe5a3fb7 and 5d4171d1c, which were the previous attempt at ensuring we wouldn't push down foreign joins that span permissions contexts. Etsuro Fujita and Tom Lane Discussion: <d49c1e5b-f059-20f4-c132-e9752ee0113e@lab.ntt.co.jp>
Diffstat (limited to 'src')
-rw-r--r--src/backend/executor/execParallel.c14
-rw-r--r--src/backend/foreign/foreign.c38
-rw-r--r--src/backend/nodes/copyfuncs.c5
-rw-r--r--src/backend/nodes/outfuncs.c12
-rw-r--r--src/backend/nodes/readfuncs.c5
-rw-r--r--src/backend/optimizer/path/joinpath.c4
-rw-r--r--src/backend/optimizer/plan/createplan.c11
-rw-r--r--src/backend/optimizer/plan/planner.c25
-rw-r--r--src/backend/optimizer/plan/setrefs.c20
-rw-r--r--src/backend/optimizer/util/relnode.c82
-rw-r--r--src/backend/utils/cache/plancache.c178
-rw-r--r--src/include/foreign/foreign.h2
-rw-r--r--src/include/nodes/parsenodes.h4
-rw-r--r--src/include/nodes/plannodes.h9
-rw-r--r--src/include/nodes/relation.h17
-rw-r--r--src/include/utils/plancache.h9
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;