* if it has one. When (and if) the next demand for a cached plan occurs,
* parse analysis and rewrite is repeated to build a new valid query tree,
* and then planning is performed as normal. We also force re-analysis and
- * re-planning if the active search_path is different from the previous time.
+ * re-planning if the active search_path is different from the previous time
+ * or, if RLS is involved, if the user changes or the RLS environment changes.
*
* Note that if the sinval was a result of user DDL actions, parse analysis
* could throw an error, for example if a column referenced by the query is
plansource->total_custom_cost = 0;
plansource->num_custom_plans = 0;
plansource->hasRowSecurity = false;
- plansource->row_security_env = row_security;
plansource->planUserId = InvalidOid;
+ plansource->row_security_env = false;
MemoryContextSwitchTo(oldcxt);
plansource->generic_cost = -1;
plansource->total_custom_cost = 0;
plansource->num_custom_plans = 0;
+ plansource->planUserId = InvalidOid;
+ plansource->row_security_env = false;
return 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);
return NIL;
}
- /*
- * If this is a new cached plan, then set the user id it was planned by
- * and under what row security settings; these are needed to determine
- * plan invalidation when RLS is involved or foreign joins are pushed
- * down.
- */
- if (!OidIsValid(plansource->planUserId))
- {
- plansource->planUserId = GetUserId();
- plansource->row_security_env = row_security;
- }
-
/*
* 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.
+ * to force replan. We should also have a valid planUserId.
*/
if (plansource->is_valid)
{
+ Assert(OidIsValid(plansource->planUserId));
+
Assert(plansource->search_path != NULL);
if (!OverrideSearchPathMatchesCurrent(plansource->search_path))
{
plansource->invalItems = NIL;
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
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;
CREATE POLICY p1 ON t1 TO rls_regress_user1 USING ((a % 2) = 0);
CREATE POLICY p2 ON t1 TO rls_regress_user2 USING ((a % 4) = 0);
ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+-- Prepare as rls_regress_user1
SET ROLE rls_regress_user1;
PREPARE role_inval AS SELECT * FROM t1;
+-- Check plan
EXPLAIN (COSTS OFF) EXECUTE role_inval;
QUERY PLAN
-------------------------
Filter: ((a % 2) = 0)
(2 rows)
+-- Change to rls_regress_user2
SET ROLE rls_regress_user2;
+-- Check plan- should be different
EXPLAIN (COSTS OFF) EXECUTE role_inval;
QUERY PLAN
-------------------------
Filter: ((a % 4) = 0)
(2 rows)
+-- Change back to rls_regress_user1
+SET ROLE rls_regress_user1;
+-- Check plan- should be back to original
+EXPLAIN (COSTS OFF) EXECUTE role_inval;
+ QUERY PLAN
+-------------------------
+ Seq Scan on t1
+ Filter: ((a % 2) = 0)
+(2 rows)
+
--
-- CTE and RLS
--