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 processing
muusama/7969
Muhammad Usama 2025-12-01 17:17:09 +03:00
parent c1bf17d96b
commit 467e74a727
6 changed files with 394 additions and 57 deletions

View File

@ -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;
} }

View File

@ -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)

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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;