fix(core): keep RLAC cycle-detection state per analyzer invocation - #2619
fix(core): keep RLAC cycle-detection state per analyzer invocation#2619ttw225 wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Walkthrough
ChangesRLAC cycle detection
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Session
participant ModelAnalyzeRule
participant RLACSubqueries
participant Optimizer
Session->>ModelAnalyzeRule: create logical plan
ModelAnalyzeRule->>ModelAnalyzeRule: create invocation cycle stack
ModelAnalyzeRule->>RLACSubqueries: rewrite RLAC subqueries with stack
RLACSubqueries->>ModelAnalyzeRule: analyze nested model plan
ModelAnalyzeRule->>Optimizer: return analyzed plan
Optimizer-->>Session: optimized plan
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Why
ModelAnalyzeRulekept its RLAC cycle-detection stack (building_models) as an instance field, cleared at the top of everyanalyze(). A derivedSessionContextholds one rule instance for its whole lifetime, so concurrentoptimize()calls on the same context share — and corrupt — that stack: an 8-thread stress run of a valid acyclic query reliably fails within ~2 iterations with a spurious "Detected a cycle in row level access control conditions" error, and a lateclear()can equally erase the state that would catch a real cycle.Today the wren-core-py binding masks this with a per-context call lock; removing that lock (#2504, step 3) is only safe once this state is per-invocation.
What
analyze()allocates the cycle stack per invocation and threads it through the model-rewrite path (analyze_model→analyze_table_scan/analyze_subquery_alias_model→build_model_plan_node→analyze_rlac_subqueries→analyze_subquery_plan). RLAC subquery recursion passes the caller's stack, so transitive cycles (A → B → A) are still detected.analyze_scopenever touches cycle state and is unchanged.ModelStackGuardborrows the stack (&ModelStack) instead of sharing ownership; cleanup-on-drop behavior is unchanged.analyze_table_scandrops two parameters that every caller filled with clones of the rule's own fields.Detection logic, error message, and
ModelAnalyzeRule::new's signature are unchanged; single-query behavior is identical.Test Plan
analyzer_concurrencyregression: one shared LocalRuntime derived context, 8 Barrier-started threads × 50 iterations planning an acyclic RLAC chain throughSessionState::optimize(the Analyzer only runs there;transform_sql_with_ctxwould build fresh rule instances per call and cannot observe the race). Fails 3/3 on the unfixed rule, passes 5/5 with the fix, ~0.1s.test_rlac_subquery_cycle_detected,test_rlac_self_reference_is_cycle) still pass — real cycles are still rejected.clippy --all-targets --all-features -D warnings,cargo fmt --check, sqllogictest suite.Part of #2504 (step 2 of the plan in the issue comments).
Summary by CodeRabbit
Bug Fixes
Tests