mirror of https://github.com/citusdata/citus.git
Address review comments
This commit includes the following 1. Enhanced test coverage with multi-way joins (3-way, 4-way), IN subquery, and additional RLS policies across multiple tables 2. Removed hasRowSecurity field from RelationRestrictionContext, now accessing parse->hasRowSecurity directly from each relation's PlannerInfo. 3. Added em_is_const safety checks in both RLS pattern detection and expression collection phases to specifically target pseudoconstant expressions (RLS functions with no Vars) 4. Changed from array bounds checking to direct index matching (varno == relRestriction->index) for clarity and added DEBUG2 logging for unmatched Vars during Phase 2 processingmuusama/7969
parent
c1bf17d96b
commit
467e74a727
|
|
@ -249,13 +249,6 @@ distributed_planner(Query *parse,
|
||||||
planContext.plannerRestrictionContext = CreateAndPushPlannerRestrictionContext(
|
planContext.plannerRestrictionContext = CreateAndPushPlannerRestrictionContext(
|
||||||
&fastPathContext);
|
&fastPathContext);
|
||||||
|
|
||||||
/*
|
|
||||||
* Set RLS flag from the query. This is used to optimize equivalence class
|
|
||||||
* processing by skipping expensive RLS-specific merging for non-RLS queries.
|
|
||||||
*/
|
|
||||||
planContext.plannerRestrictionContext->relationRestrictionContext->hasRowSecurity =
|
|
||||||
parse->hasRowSecurity;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We keep track of how many times we've recursed into the planner, primarily
|
* We keep track of how many times we've recursed into the planner, primarily
|
||||||
* to detect whether we are in a function call. We need to make sure that the
|
* to detect whether we are in a function call. We need to make sure that the
|
||||||
|
|
@ -2455,9 +2448,6 @@ CreateAndPushPlannerRestrictionContext(
|
||||||
/* we'll apply logical AND as we add tables */
|
/* we'll apply logical AND as we add tables */
|
||||||
plannerRestrictionContext->relationRestrictionContext->allReferenceTables = true;
|
plannerRestrictionContext->relationRestrictionContext->allReferenceTables = true;
|
||||||
|
|
||||||
/* hasRowSecurity will be set later once we have the Query object */
|
|
||||||
plannerRestrictionContext->relationRestrictionContext->hasRowSecurity = false;
|
|
||||||
|
|
||||||
plannerRestrictionContextList = lcons(plannerRestrictionContext,
|
plannerRestrictionContextList = lcons(plannerRestrictionContext,
|
||||||
plannerRestrictionContextList);
|
plannerRestrictionContextList);
|
||||||
|
|
||||||
|
|
@ -2545,9 +2535,6 @@ ResetPlannerRestrictionContext(PlannerRestrictionContext *plannerRestrictionCont
|
||||||
|
|
||||||
/* we'll apply logical AND as we add tables */
|
/* we'll apply logical AND as we add tables */
|
||||||
plannerRestrictionContext->relationRestrictionContext->allReferenceTables = true;
|
plannerRestrictionContext->relationRestrictionContext->allReferenceTables = true;
|
||||||
|
|
||||||
/* hasRowSecurity defaults to false, will be set by caller if needed */
|
|
||||||
plannerRestrictionContext->relationRestrictionContext->hasRowSecurity = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4200,7 +4200,6 @@ CopyRelationRestrictionContext(RelationRestrictionContext *oldContext)
|
||||||
ListCell *relationRestrictionCell = NULL;
|
ListCell *relationRestrictionCell = NULL;
|
||||||
|
|
||||||
newContext->allReferenceTables = oldContext->allReferenceTables;
|
newContext->allReferenceTables = oldContext->allReferenceTables;
|
||||||
newContext->hasRowSecurity = oldContext->hasRowSecurity;
|
|
||||||
newContext->relationRestrictionList = NIL;
|
newContext->relationRestrictionList = NIL;
|
||||||
|
|
||||||
foreach(relationRestrictionCell, oldContext->relationRestrictionList)
|
foreach(relationRestrictionCell, oldContext->relationRestrictionList)
|
||||||
|
|
|
||||||
|
|
@ -850,19 +850,22 @@ GenerateAttributeEquivalencesForRelationRestrictions(RelationRestrictionContext
|
||||||
* This builds the standard attribute equivalence list.
|
* This builds the standard attribute equivalence list.
|
||||||
*
|
*
|
||||||
* Skip RLS pattern detection entirely if the query doesn't
|
* Skip RLS pattern detection entirely if the query doesn't
|
||||||
* use Row Level Security. The hasRowSecurity flag is set during query planning
|
* use Row Level Security. The hasRowSecurity flag is checked from the query's
|
||||||
* when any table has RLS policies active. This allows us to skip both the pattern
|
* parse tree when any table has RLS policies active. This allows us to skip
|
||||||
* detection loop AND the expensive merge pass for non-RLS queries (common case).
|
* both the pattern detection loop AND the expensive merge pass for non-RLS
|
||||||
|
* queries (common case).
|
||||||
*
|
*
|
||||||
* For RLS queries, detect patterns efficiently. We only need
|
* For RLS queries, detect patterns efficiently. We only need
|
||||||
* to find one EC with both Var + non-Var members to justify the merge pass.
|
* to find one EC with both Var + non-Var members to justify the merge pass.
|
||||||
* Once found, skip further pattern checks and focus on building equivalences.
|
* Once found, skip further pattern checks and focus on building equivalences.
|
||||||
*/
|
*/
|
||||||
bool skipRLSProcessing = !restrictionContext->hasRowSecurity;
|
bool skipRLSProcessing = true;
|
||||||
foreach(relationRestrictionCell, restrictionContext->relationRestrictionList)
|
foreach(relationRestrictionCell, restrictionContext->relationRestrictionList)
|
||||||
{
|
{
|
||||||
RelationRestriction *relationRestriction =
|
RelationRestriction *relationRestriction =
|
||||||
(RelationRestriction *) lfirst(relationRestrictionCell);
|
(RelationRestriction *) lfirst(relationRestrictionCell);
|
||||||
|
|
||||||
|
skipRLSProcessing = !relationRestriction->plannerInfo->parse->hasRowSecurity;
|
||||||
List *equivalenceClasses = relationRestriction->plannerInfo->eq_classes;
|
List *equivalenceClasses = relationRestriction->plannerInfo->eq_classes;
|
||||||
ListCell *equivalenceClassCell = NULL;
|
ListCell *equivalenceClassCell = NULL;
|
||||||
|
|
||||||
|
|
@ -890,8 +893,13 @@ GenerateAttributeEquivalencesForRelationRestrictions(RelationRestrictionContext
|
||||||
{
|
{
|
||||||
hasVar = true;
|
hasVar = true;
|
||||||
}
|
}
|
||||||
else if (!IsA(expr, Param) && !IsA(expr, Const))
|
else if (member->em_is_const &&
|
||||||
|
!IsA(expr, Param) && !IsA(expr, Const))
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* Found a pseudoconstant expression (no Vars) that's not a
|
||||||
|
* Param or Const - this is the RLS function pattern.
|
||||||
|
*/
|
||||||
hasNonVar = true;
|
hasNonVar = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1007,10 +1015,10 @@ MergeEquivalenceClassesWithSameFunctions(RelationRestrictionContext *restriction
|
||||||
{
|
{
|
||||||
hasVar = true;
|
hasVar = true;
|
||||||
}
|
}
|
||||||
else if (!IsA(expr, Param) && !IsA(expr, Const))
|
else if (member->em_is_const && !IsA(expr, Param) && !IsA(expr, Const))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Found a non-Var expression (potential RLS function).
|
* Found a pseudoconstant expression (no Vars) - potential RLS function.
|
||||||
* After stripping, this is typically a FUNCEXPR like
|
* After stripping, this is typically a FUNCEXPR like
|
||||||
* current_setting('session.current_tenant_id').
|
* current_setting('session.current_tenant_id').
|
||||||
*/
|
*/
|
||||||
|
|
@ -1123,37 +1131,49 @@ MergeEquivalenceClassesWithSameFunctions(RelationRestrictionContext *restriction
|
||||||
mergedClass->equivalentAttributes = NIL;
|
mergedClass->equivalentAttributes = NIL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build a PlannerInfo lookup map for quick access.
|
* Match each Var to its RelationRestriction by comparing varno to
|
||||||
* Map varno → RelationRestriction for fast lookups.
|
* the restriction's index field (which is the RTE index).
|
||||||
*/
|
*/
|
||||||
ListCell *varCell = NULL;
|
ListCell *varCell = NULL;
|
||||||
foreach(varCell, group->varsInTheseECs)
|
foreach(varCell, group->varsInTheseECs)
|
||||||
{
|
{
|
||||||
Var *var = (Var *) lfirst(varCell);
|
Var *var = (Var *) lfirst(varCell);
|
||||||
ListCell *relResCell = NULL;
|
ListCell *relResCell = NULL;
|
||||||
|
bool foundMatch = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find the appropriate RelationRestriction for this Var.
|
* Find the RelationRestriction that corresponds to this Var.
|
||||||
* We need the correct PlannerInfo context to process the Var.
|
* The index field contains the RTE index (varno) of the relation.
|
||||||
*/
|
*/
|
||||||
foreach(relResCell, restrictionContext->relationRestrictionList)
|
foreach(relResCell, restrictionContext->relationRestrictionList)
|
||||||
{
|
{
|
||||||
RelationRestriction *relRestriction =
|
RelationRestriction *relRestriction =
|
||||||
(RelationRestriction *) lfirst(relResCell);
|
(RelationRestriction *) lfirst(relResCell);
|
||||||
PlannerInfo *root = relRestriction->plannerInfo;
|
|
||||||
|
|
||||||
/* Check if this Var belongs to this planner's range table */
|
/* Direct match: varno equals the restriction's index */
|
||||||
if (var->varno < root->simple_rel_array_size &&
|
if (var->varno == relRestriction->index)
|
||||||
root->simple_rte_array[var->varno] != NULL)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Process this Var through AddToAttributeEquivalenceClass.
|
* Process this Var through AddToAttributeEquivalenceClass.
|
||||||
* This handles subqueries, UNION ALL, LATERAL joins, etc.
|
* This handles subqueries, UNION ALL, LATERAL joins, etc.
|
||||||
*/
|
*/
|
||||||
AddToAttributeEquivalenceClass(mergedClass, root, var);
|
AddToAttributeEquivalenceClass(mergedClass,
|
||||||
break; /* Found the right planner, move to next Var */
|
relRestriction->plannerInfo, var);
|
||||||
|
foundMatch = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we didn't find a matching restriction, this Var might be from
|
||||||
|
* a context not tracked in our restriction list (e.g., subquery).
|
||||||
|
* We skip it as we only care about Vars from distributed tables.
|
||||||
|
*/
|
||||||
|
if (!foundMatch)
|
||||||
|
{
|
||||||
|
elog(DEBUG2, "Skipping Var with varno=%d in RLS merge - "
|
||||||
|
"no matching RelationRestriction found", var->varno);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only emit if we successfully merged attributes from multiple sources */
|
/* Only emit if we successfully merged attributes from multiple sources */
|
||||||
|
|
@ -2704,10 +2724,6 @@ FilterRelationRestrictionContext(RelationRestrictionContext *relationRestriction
|
||||||
RelationRestrictionContext *filteredRestrictionContext =
|
RelationRestrictionContext *filteredRestrictionContext =
|
||||||
palloc0(sizeof(RelationRestrictionContext));
|
palloc0(sizeof(RelationRestrictionContext));
|
||||||
|
|
||||||
/* Preserve RLS flag from the original context */
|
|
||||||
filteredRestrictionContext->hasRowSecurity =
|
|
||||||
relationRestrictionContext->hasRowSecurity;
|
|
||||||
|
|
||||||
ListCell *relationRestrictionCell = NULL;
|
ListCell *relationRestrictionCell = NULL;
|
||||||
|
|
||||||
foreach(relationRestrictionCell, relationRestrictionContext->relationRestrictionList)
|
foreach(relationRestrictionCell, relationRestrictionContext->relationRestrictionList)
|
||||||
|
|
|
||||||
|
|
@ -48,13 +48,6 @@ typedef enum RouterPlanType
|
||||||
typedef struct RelationRestrictionContext
|
typedef struct RelationRestrictionContext
|
||||||
{
|
{
|
||||||
bool allReferenceTables;
|
bool allReferenceTables;
|
||||||
|
|
||||||
/*
|
|
||||||
* Set to true when any table in the query
|
|
||||||
* has Row Level Security policies active.
|
|
||||||
*/
|
|
||||||
bool hasRowSecurity;
|
|
||||||
|
|
||||||
List *relationRestrictionList;
|
List *relationRestrictionList;
|
||||||
} RelationRestrictionContext;
|
} RelationRestrictionContext;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ SET search_path TO rls_join_test;
|
||||||
-- Create and distribute tables
|
-- Create and distribute tables
|
||||||
CREATE TABLE table_a (tenant_id uuid, id int);
|
CREATE TABLE table_a (tenant_id uuid, id int);
|
||||||
CREATE TABLE table_b (tenant_id uuid, id int);
|
CREATE TABLE table_b (tenant_id uuid, id int);
|
||||||
|
CREATE TABLE table_c (tenant_id uuid, id int);
|
||||||
|
CREATE TABLE table_d (tenant_id uuid, id int);
|
||||||
SELECT create_distributed_table('table_a', 'tenant_id');
|
SELECT create_distributed_table('table_a', 'tenant_id');
|
||||||
create_distributed_table
|
create_distributed_table
|
||||||
---------------------------------------------------------------------
|
---------------------------------------------------------------------
|
||||||
|
|
@ -33,6 +35,18 @@ SELECT create_distributed_table('table_b', 'tenant_id', colocate_with => 'table_
|
||||||
|
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT create_distributed_table('table_c', 'tenant_id', colocate_with => 'table_a');
|
||||||
|
create_distributed_table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT create_distributed_table('table_d', 'tenant_id', colocate_with => 'table_a');
|
||||||
|
create_distributed_table
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
-- Grant privileges on tables
|
-- Grant privileges on tables
|
||||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA rls_join_test TO app_user;
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA rls_join_test TO app_user;
|
||||||
-- Insert test data
|
-- Insert test data
|
||||||
|
|
@ -42,9 +56,18 @@ INSERT INTO table_a VALUES
|
||||||
INSERT INTO table_b VALUES
|
INSERT INTO table_b VALUES
|
||||||
('0194d116-5dd5-74af-be74-7f5e8468eeb7', 10),
|
('0194d116-5dd5-74af-be74-7f5e8468eeb7', 10),
|
||||||
('0194d116-5dd5-74af-be74-7f5e8468eeb8', 20);
|
('0194d116-5dd5-74af-be74-7f5e8468eeb8', 20);
|
||||||
-- Enable RLS and create policy
|
INSERT INTO table_c VALUES
|
||||||
|
('0194d116-5dd5-74af-be74-7f5e8468eeb7', 100),
|
||||||
|
('0194d116-5dd5-74af-be74-7f5e8468eeb8', 200);
|
||||||
|
INSERT INTO table_d VALUES
|
||||||
|
('0194d116-5dd5-74af-be74-7f5e8468eeb7', 1000),
|
||||||
|
('0194d116-5dd5-74af-be74-7f5e8468eeb8', 2000);
|
||||||
|
-- Enable RLS and create policies on multiple tables
|
||||||
ALTER TABLE table_a ENABLE ROW LEVEL SECURITY;
|
ALTER TABLE table_a ENABLE ROW LEVEL SECURITY;
|
||||||
CREATE POLICY tenant_isolation_0 ON table_a TO app_user
|
CREATE POLICY tenant_isolation_a ON table_a TO app_user
|
||||||
|
USING (tenant_id = current_setting('session.current_tenant_id')::UUID);
|
||||||
|
ALTER TABLE table_c ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY tenant_isolation_c ON table_c TO app_user
|
||||||
USING (tenant_id = current_setting('session.current_tenant_id')::UUID);
|
USING (tenant_id = current_setting('session.current_tenant_id')::UUID);
|
||||||
-- Test scenario that previously failed
|
-- Test scenario that previously failed
|
||||||
-- Switch to app_user and execute the query with RLS
|
-- Switch to app_user and execute the query with RLS
|
||||||
|
|
@ -59,22 +82,211 @@ BEGIN
|
||||||
EXECUTE 'SET LOCAL session.current_tenant_id = ' || quote_literal(current_setting('application_name', true));
|
EXECUTE 'SET LOCAL session.current_tenant_id = ' || quote_literal(current_setting('application_name', true));
|
||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
-- This query should work with RLS enabled
|
-- Simple 2-way join (original test case)
|
||||||
SELECT c.id, t.id
|
SELECT a.id, b.id
|
||||||
FROM table_a AS c
|
FROM table_a AS a
|
||||||
LEFT OUTER JOIN table_b AS t ON c.tenant_id = t.tenant_id
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
ORDER BY c.id, t.id;
|
ORDER BY a.id, b.id;
|
||||||
id | id
|
id | id
|
||||||
---------------------------------------------------------------------
|
---------------------------------------------------------------------
|
||||||
1 | 10
|
1 | 10
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id, b.id
|
||||||
|
FROM table_a AS a
|
||||||
|
RIGHT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
ORDER BY a.id, b.id;
|
||||||
|
id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10
|
||||||
|
| 20
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
-- 3-way join with RLS on multiple tables
|
||||||
|
SELECT a.id, b.id, c.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id, b.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
JOIN table_d AS d ON b.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, d.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 1000
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id, b.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON b.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, d.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 1000
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id, b.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
RIGHT JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
RIGHT OUTER JOIN table_d AS d ON b.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, d.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 1000
|
||||||
|
| 20 | 2000
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT a.id, b.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
RIGHT JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON b.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, d.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 1000
|
||||||
|
| 20 | 2000
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT a.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_c AS c ON a.tenant_id = c.tenant_id
|
||||||
|
JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, c.id, d.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 100 | 1000
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_c AS c ON a.tenant_id = c.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, c.id, d.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 100 | 1000
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- 4-way join with different join types
|
||||||
|
SELECT a.id, b.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
INNER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
INNER JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id, d.id;
|
||||||
|
id | id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 100 | 1000
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
INNER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
RIGHT JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id, d.id;
|
||||||
|
id | id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 100 | 1000
|
||||||
|
| | | 2000
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id, d.id;
|
||||||
|
id | id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 100 | 1000
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
RIGHT OUTER JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id, d.id;
|
||||||
|
id | id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 100 | 1000
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- IN subquery that can be transformed to semi-join
|
||||||
|
SELECT a.id
|
||||||
|
FROM table_a a
|
||||||
|
WHERE a.tenant_id IN (
|
||||||
|
SELECT b.tenant_id
|
||||||
|
FROM table_b b
|
||||||
|
JOIN table_c c USING (tenant_id)
|
||||||
|
)
|
||||||
|
ORDER BY a.id;
|
||||||
|
id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id
|
||||||
|
FROM table_a a
|
||||||
|
WHERE a.tenant_id IN (
|
||||||
|
SELECT b.tenant_id
|
||||||
|
FROM table_b b
|
||||||
|
LEFT OUTER JOIN table_c c USING (tenant_id)
|
||||||
|
)
|
||||||
|
ORDER BY a.id;
|
||||||
|
id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- Another multi-way join variation
|
||||||
|
SELECT a.id, b.id, c.id
|
||||||
|
FROM table_a AS a
|
||||||
|
INNER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
INNER JOIN table_c AS c ON a.tenant_id = c.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_c AS c ON a.tenant_id = c.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id;
|
||||||
|
id | id | id
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | 10 | 100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
-- Switch back to superuser for cleanup
|
-- Switch back to superuser for cleanup
|
||||||
RESET ROLE;
|
RESET ROLE;
|
||||||
-- Cleanup: Drop schema and all objects
|
-- Cleanup: Drop schema and all objects
|
||||||
DROP SCHEMA rls_join_test CASCADE;
|
DROP SCHEMA rls_join_test CASCADE;
|
||||||
NOTICE: drop cascades to 2 other objects
|
NOTICE: drop cascades to 4 other objects
|
||||||
DETAIL: drop cascades to table table_a
|
DETAIL: drop cascades to table table_a
|
||||||
drop cascades to table table_b
|
drop cascades to table table_b
|
||||||
|
drop cascades to table table_c
|
||||||
|
drop cascades to table table_d
|
||||||
DROP USER IF EXISTS app_user;
|
DROP USER IF EXISTS app_user;
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,13 @@ SET search_path TO rls_join_test;
|
||||||
-- Create and distribute tables
|
-- Create and distribute tables
|
||||||
CREATE TABLE table_a (tenant_id uuid, id int);
|
CREATE TABLE table_a (tenant_id uuid, id int);
|
||||||
CREATE TABLE table_b (tenant_id uuid, id int);
|
CREATE TABLE table_b (tenant_id uuid, id int);
|
||||||
|
CREATE TABLE table_c (tenant_id uuid, id int);
|
||||||
|
CREATE TABLE table_d (tenant_id uuid, id int);
|
||||||
|
|
||||||
SELECT create_distributed_table('table_a', 'tenant_id');
|
SELECT create_distributed_table('table_a', 'tenant_id');
|
||||||
SELECT create_distributed_table('table_b', 'tenant_id', colocate_with => 'table_a');
|
SELECT create_distributed_table('table_b', 'tenant_id', colocate_with => 'table_a');
|
||||||
|
SELECT create_distributed_table('table_c', 'tenant_id', colocate_with => 'table_a');
|
||||||
|
SELECT create_distributed_table('table_d', 'tenant_id', colocate_with => 'table_a');
|
||||||
|
|
||||||
-- Grant privileges on tables
|
-- Grant privileges on tables
|
||||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA rls_join_test TO app_user;
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA rls_join_test TO app_user;
|
||||||
|
|
@ -42,9 +46,21 @@ INSERT INTO table_b VALUES
|
||||||
('0194d116-5dd5-74af-be74-7f5e8468eeb7', 10),
|
('0194d116-5dd5-74af-be74-7f5e8468eeb7', 10),
|
||||||
('0194d116-5dd5-74af-be74-7f5e8468eeb8', 20);
|
('0194d116-5dd5-74af-be74-7f5e8468eeb8', 20);
|
||||||
|
|
||||||
-- Enable RLS and create policy
|
INSERT INTO table_c VALUES
|
||||||
|
('0194d116-5dd5-74af-be74-7f5e8468eeb7', 100),
|
||||||
|
('0194d116-5dd5-74af-be74-7f5e8468eeb8', 200);
|
||||||
|
|
||||||
|
INSERT INTO table_d VALUES
|
||||||
|
('0194d116-5dd5-74af-be74-7f5e8468eeb7', 1000),
|
||||||
|
('0194d116-5dd5-74af-be74-7f5e8468eeb8', 2000);
|
||||||
|
|
||||||
|
-- Enable RLS and create policies on multiple tables
|
||||||
ALTER TABLE table_a ENABLE ROW LEVEL SECURITY;
|
ALTER TABLE table_a ENABLE ROW LEVEL SECURITY;
|
||||||
CREATE POLICY tenant_isolation_0 ON table_a TO app_user
|
CREATE POLICY tenant_isolation_a ON table_a TO app_user
|
||||||
|
USING (tenant_id = current_setting('session.current_tenant_id')::UUID);
|
||||||
|
|
||||||
|
ALTER TABLE table_c ENABLE ROW LEVEL SECURITY;
|
||||||
|
CREATE POLICY tenant_isolation_c ON table_c TO app_user
|
||||||
USING (tenant_id = current_setting('session.current_tenant_id')::UUID);
|
USING (tenant_id = current_setting('session.current_tenant_id')::UUID);
|
||||||
|
|
||||||
-- Test scenario that previously failed
|
-- Test scenario that previously failed
|
||||||
|
|
@ -63,12 +79,126 @@ BEGIN
|
||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
-- This query should work with RLS enabled
|
-- Simple 2-way join (original test case)
|
||||||
|
SELECT a.id, b.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
ORDER BY a.id, b.id;
|
||||||
|
|
||||||
SELECT c.id, t.id
|
SELECT a.id, b.id
|
||||||
FROM table_a AS c
|
FROM table_a AS a
|
||||||
LEFT OUTER JOIN table_b AS t ON c.tenant_id = t.tenant_id
|
RIGHT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
ORDER BY c.id, t.id;
|
ORDER BY a.id, b.id;
|
||||||
|
|
||||||
|
-- 3-way join with RLS on multiple tables
|
||||||
|
SELECT a.id, b.id, c.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id;
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id;
|
||||||
|
|
||||||
|
SELECT a.id, b.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
JOIN table_d AS d ON b.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, d.id;
|
||||||
|
|
||||||
|
SELECT a.id, b.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON b.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, d.id;
|
||||||
|
|
||||||
|
SELECT a.id, b.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
RIGHT JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
RIGHT OUTER JOIN table_d AS d ON b.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, d.id;
|
||||||
|
|
||||||
|
SELECT a.id, b.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
RIGHT JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON b.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, d.id;
|
||||||
|
|
||||||
|
SELECT a.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_c AS c ON a.tenant_id = c.tenant_id
|
||||||
|
JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, c.id, d.id;
|
||||||
|
|
||||||
|
SELECT a.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_c AS c ON a.tenant_id = c.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, c.id, d.id;
|
||||||
|
|
||||||
|
-- 4-way join with different join types
|
||||||
|
SELECT a.id, b.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
INNER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
INNER JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id, d.id;
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
INNER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
RIGHT JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id, d.id;
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id, d.id;
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id, d.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
RIGHT OUTER JOIN table_c AS c ON b.tenant_id = c.tenant_id
|
||||||
|
LEFT OUTER JOIN table_d AS d ON c.tenant_id = d.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id, d.id;
|
||||||
|
|
||||||
|
-- IN subquery that can be transformed to semi-join
|
||||||
|
SELECT a.id
|
||||||
|
FROM table_a a
|
||||||
|
WHERE a.tenant_id IN (
|
||||||
|
SELECT b.tenant_id
|
||||||
|
FROM table_b b
|
||||||
|
JOIN table_c c USING (tenant_id)
|
||||||
|
)
|
||||||
|
ORDER BY a.id;
|
||||||
|
|
||||||
|
SELECT a.id
|
||||||
|
FROM table_a a
|
||||||
|
WHERE a.tenant_id IN (
|
||||||
|
SELECT b.tenant_id
|
||||||
|
FROM table_b b
|
||||||
|
LEFT OUTER JOIN table_c c USING (tenant_id)
|
||||||
|
)
|
||||||
|
ORDER BY a.id;
|
||||||
|
|
||||||
|
-- Another multi-way join variation
|
||||||
|
SELECT a.id, b.id, c.id
|
||||||
|
FROM table_a AS a
|
||||||
|
INNER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
INNER JOIN table_c AS c ON a.tenant_id = c.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id;
|
||||||
|
|
||||||
|
SELECT a.id, b.id, c.id
|
||||||
|
FROM table_a AS a
|
||||||
|
LEFT OUTER JOIN table_b AS b ON a.tenant_id = b.tenant_id
|
||||||
|
LEFT OUTER JOIN table_c AS c ON a.tenant_id = c.tenant_id
|
||||||
|
ORDER BY a.id, b.id, c.id;
|
||||||
|
|
||||||
ROLLBACK;
|
ROLLBACK;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue