-
Notifications
You must be signed in to change notification settings - Fork 739
Description
Steps to reproduce:
-- create tables and distribute them
CREATE TABLE table_a (tenant_id uuid, id int);
CREATE TABLE table_b (tenant_id uuid, id int);
SELECT create_distributed_table('table_a', 'tenant_id');
SELECT create_distributed_table('table_b', 'tenant_id', colocate_with => 'table_a');
-- create a new user and grant all stuff
CREATE USER app_user;
GRANT ALL PRIVILEGES ON DATABASE postgres TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT CREATE ON SCHEMA public TO app_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO app_user;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO app_user;
-- enable RLS and create a policy
ALTER TABLE table_a ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_0 ON table_a TO app_user
USING (tenant_id = current_setting('session.current_tenant_id')::UUID);
-- scenario that fails
SET ROLE app_user;
SET citus.propagate_set_commands = local;
SET application_name = '0194d116-5dd5-74af-be74-7f5e8468eeb7';
BEGIN;
DO $$ DECLARE BEGIN
EXECUTE 'SET LOCAL session.current_tenant_id = ' || quote_literal(current_setting('application_name', true));
END; $$;
SELECT c.id, t.id
FROM table_a AS c
LEFT OUTER JOIN table_b AS t ON c.tenant_id = t.tenant_id;
ERROR: complex joins are only supported
when all distributed tables are co-located and joined on their distribution columns
ROLLBACK;The error is misleading because the distributed tables are actually co-located and joined on their distribution columns.
After some investigation, I realized that because of the RLS, the joinRestrictInfoList changes.
https://github.com/citusdata/citus/blob/main/src/backend/distributed/planner/relation_restriction_equivalence.c#L1142-L1150
foreach(restrictionInfoList, joinRestriction - > joinRestrictInfoList)
{
RestrictInfo * rinfo = (RestrictInfo * ) lfirst(restrictionInfoList);
Expr * restrictionClause = rinfo - > clause;
if (!IsA(restrictionClause, OpExpr))
{
continue;
}
}Without the RLS, this list contains 1 element of type OpExpr which corresponds to the join between the distribution columns of the tables. With the RLS, this list contains 1 element of type Const, which corresponds to the policy condition. However, the relation restriction list still contains the distributed tables, and then we reach that misleading error because the attribute equivalences are generated wrongly.
https://github.com/citusdata/citus/blob/main/src/backend/distributed/planner/relation_restriction_equivalence.c#L532-L535
// this list is not generated correctly with the RLS
List * attributeEquivalenceList = GenerateAllAttributeEquivalences(restrictionContext);
return RestrictionEquivalenceForPartitionKeysViaEquivalences(restrictionContext, attributeEquivalenceList);Another interesting observation by the user who reported this issue, is that when the policy is replaced by the following policy with hardcoded tenant_id, we have no issues:
CREATE POLICY tenant_isolation_1 ON table_a TO app_user
USING (tenant_id = '0194d116-5dd5-74af-be74-7f5e8468eeb7');So, the current_setting function seems to be the culprit for the planner changes.
Reported on Slack https://citus-public.slack.com/archives/C0XRHT1KJ/p1743090187382189