fix(rls): handle same-named CTEs and quoted aliases in the SQL rewrite - #43005
fix(rls): handle same-named CTEs and quoted aliases in the SQL rewrite#43005onheap wants to merge 1 commit into
Conversation
Code Review Agent Run #562beeActionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
✅ Deploy Preview for superset-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #43005 +/- ##
=======================================
Coverage 66.59% 66.59%
=======================================
Files 2863 2863
Lines 161701 161729 +28
Branches 37254 37261 +7
=======================================
+ Hits 107686 107711 +25
+ Misses 51973 51972 -1
- Partials 2042 2046 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Three fixes on the raw-SQL row-level-security path: - Resolve CTE references through ``Scope.cte_sources`` in ``extract_tables_from_statement()`` rather than comparing bare names, so a real table read that shares a CTE's name -- a schema/catalog-qualified read, a non-recursive CTE's own name inside its body, or a forward reference to a later ``WITH`` item -- is reported and receives its RLS predicate and access check. - Rewrite the ``AS_SUBQUERY`` method to wrap the real reads enumerated by that same walk, in place, instead of matching table nodes by name. A CTE reference that shares a rule table's name is left untouched; reads are de-duplicated by identity and wrapped deepest-first. - Carry the parsed table alias through both RLS transformers instead of the quoting-stripped ``node.alias`` string, so a quoted alias keeps its quoting and a column-list alias is preserved. Add unit tests for extraction, both rewrite methods, and a filtered-set invariant; document the behavior change in UPDATING.md.
There was a problem hiding this comment.
Code Review Agent Run #c2ae3c
Actionable Suggestions - 1
-
tests/unit_tests/sql/parse_tests.py - 1
- Type mismatch in parametrize decorator · Line 3033-3033
Review Details
-
Files reviewed - 2 · Commit Range:
3e72252..3e72252- superset/sql/parse.py
- tests/unit_tests/sql/parse_tests.py
-
Files skipped - 1
- UPDATING.md - Reason: Filter setting
-
Tools
- MyPy (Static Code Analysis) - ✔︎ Successful
- Astral Ruff (Static Code Analysis) - ✔︎ Successful
- Whispers (Secret Scanner) - ✔︎ Successful
- Detect-secrets (Secret Scanner) - ✔︎ Successful
Bito Usage Guide
Commands
Type the following command in the pull request comment and save the comment.
-
/review- Manually triggers a full AI review. -
/pause- Pauses automatic reviews on this pull request. -
/resume- Resumes automatic reviews. -
/resolve- Marks all Bito-posted review comments as resolved. -
/abort- Cancels all in-progress reviews.
Refer to the documentation for additional commands.
Configuration
This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.
Documentation & Help
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "sql, read_counts", |
There was a problem hiding this comment.
The rules parameter in test_rls_subquery_transformer uses dict[Table, str] but the test passes dict[Table, list[exp.Node]] at line 3033. Multiple similar type mismatches likely exist in parameterized tests (rows 3000-3013).
Code Review Run #c2ae3c
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
SUMMARY
Superset enforces row-level security on raw SQL by rewriting the query: every table it reads is wrapped in a subquery that filters by the user's row predicate. First it lists the tables a statement reads, with
extract_tables_from_statement(). That list drives both the RLS predicate and the dataset access check. Then it rewrites each read. This PR tidies up three edge cases in that path.1. A read whose name matches a CTE is treated as the CTE. To tell a CTE reference from a real table, extraction compares the reference's bare name to the CTE names in scope. A real table read that shares a name with a CTE is therefore misclassified as a CTE reference. It is omitted from the statement’s table set, so neither an RLS predicate nor an access check is applied. This happens for a schema- or catalog-qualified read, a non-recursive CTE's own name inside its body, and a forward reference to a later
WITHitem:2. The subquery rewrite picks the nodes to wrap by name. The
AS_SUBQUERYrewrite walks the tree and wraps every table node whose name matches a RLS rule. A CTE reference with the same name as the rule's table matches too, so it also gets wrapped and the predicate is applied to it. If the CTE does not select the predicate's column, the database cannot resolve the query:3. Aliases pass through the rewrite as strings. Both RLS transformers read the alias from
node.alias. That is a string with the quoting removed, and they pass it back as the alias. So a quoted alias loses its quotes, and a column-list alias keeps only its name:BEFORE / AFTER
CTE reference vs. real table —
is_cte()resolved by bare name (before) vs. through the scope (after):Subquery rewrite of a same-named CTE (rule on
orders):Alias handling:
TESTING INSTRUCTIONS
Unit tests are added in
tests/unit_tests/sql/parse_tests.pycovering extraction (CTE-vs-table shapes), both rewrite methods (quoted/column-list aliases, a same-named CTE, correlatedLATERAL, a read inside a DML subquery), and a filtered-set invariant that checks each real read is wrapped exactly once.ADDITIONAL INFORMATION