fix(tesseract): mask.sql with cross-cube refs through prefixed views#10727
fix(tesseract): mask.sql with cross-cube refs through prefixed views#10727paveltiunov merged 7 commits intomasterfrom
Conversation
…bers
When a cube member with mask.sql is re-exposed through a view (especially
with prefix: true), mask.sql references such as {CUBE.city}, {CUBE}.city
or {users}.city used to fail inside Tesseract with errors like
'Cannot resolve: city', 'Can't find join path to join …' or
'Member references foreign cubes: users'.
The legacy BaseQuery path already compiles mask.sql against the owning
cube by consulting the symbol's aliasMember. Tesseract was compiling
mask.sql against the view's cube name, producing mismatched dependency
resolution.
Propagate aliasMember through DimensionDefinitionStatic /
MeasureDefinitionStatic and use the owning cube name when compiling
mask.sql in the Rust dimension/measure symbol factories.
Also exclude mask.sql from iter_sql_calls so the generic
validate_regular_member_cube_refs check doesn't flag mask references
as foreign cubes.
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
…ymbols
SECURITY_CONTEXT array filters (e.g. {groups.filter("…")}) used to
render 'col IN ()' when the array was empty, which is invalid SQL in
most dialects.
Treat an empty array as 'matches nothing' and emit '1 = 0' (or invoke
the callback with an empty list) for both the legacy BaseQuery filter
helper and the Tesseract security_context_filter_fn.
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
…ed views
Add unit tests (BaseQuery and Tesseract paths) and E2E smoke tests
covering:
* mask.sql authored with {cube.member}, {CUBE.member}, {CUBE}.column
and {cube}.column references on a cube member re-exposed through a
prefixed view.
* {groups.filter(...)} with an empty groups array: must not emit
invalid 'IN ()' SQL.
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
|
Claude finished @paveltiunov's task in 1m 48s —— View job Code Review: fix(tesseract): mask.sql with cross-cube refs through prefixed views
Overall AssessmentThis is a well-structured fix that addresses three distinct but related issues in the Tesseract planner's handling of Code Quality & CorrectnessThe core fix is sound. The // Legacy
const sqlCubeName = symbol.aliasMember ? symbol.aliasMember.split('.')[0] : cubeName;// Tesseract (new)
let mask_sql_cube_name = definition
.static_data()
.alias_member
.as_deref()
.and_then(|alias| alias.split('.').next())
.map(|s| s.to_string())
.unwrap_or_else(|| path.cube_name().clone());Both extract the cube name from the first segment of Excluding The empty-array filter fix ( Potential Issues1. Duplicated logic between dimension and measure symbol factories The 2. The code uses 3. Comment verbosity in The 6-line comments in both SecurityNo security concerns. The empty-array filter change is actually a security improvement — it converts a potential SQL syntax error ( TestingThe test coverage is thorough:
One test gap to consider: there's no explicit test for a non-view dimension/measure where SummaryClean, well-tested fix that correctly aligns Tesseract's |
|
Claude finished @paveltiunov's task in 1m 38s —— View job Code Review: fix(tesseract): mask.sql with cross-cube refs through prefixed views
Overall AssessmentThis is a well-structured, correctly-scoped fix for three related issues in how Tesseract handles Code Quality & CorrectnessCore fix — The // Tesseract (new)
let mask_sql_cube_name = definition
.static_data()
.alias_member
.as_deref()
.and_then(|alias| alias.split('.').next())
.map(|s| s.to_string())
.unwrap_or_else(|| path.cube_name().clone());// Legacy (BaseQuery.js:3462)
const sqlCubeName = symbol.aliasMember ? symbol.aliasMember.split('.')[0] : cubeName;Both correctly extract the cube name from Excluding This is the correct decision. Since
Empty-array filter fix ( Both implementations handle the empty-array edge case identically:
This converts a SQL syntax error ( Potential Observations1. Duplicated The 7-line extraction block is identical between 2. Comment length in The 6-line block comments in both 3. Both SecurityNo security concerns. The empty-array filter fix is actually a security improvement — it eliminates a potential SQL syntax error path ( PerformanceNo performance concerns. The TestingThe test coverage is thorough and well-structured:
One minor gap: there's no explicit unit test for the SummaryClean, well-tested fix that correctly aligns Tesseract's |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #10727 +/- ##
===========================================
+ Coverage 58.02% 78.80% +20.78%
===========================================
Files 215 466 +251
Lines 16689 91990 +75301
Branches 3358 3360 +2
===========================================
+ Hits 9683 72492 +62809
- Misses 6514 19007 +12493
+ Partials 492 491 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
The previous fixture names produced Postgres aliases that exceeded the 63-char identifier limit, so the SQL engine's rewrite truncated them and then failed with 'Member name not found for alias'. Rename the cube / view / masked dimensions to short forms (view_mask_base, view_mask_test, pid_full, etc.) and limit the access_policy.member_masking scope to just the masked dimensions so non-masked members (id, product_id, count) still resolve to their real SQL instead of the default NULL mask. The smoke tests now also select explicit columns instead of SELECT *. Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
security_context_filter_fn had three near-identical blocks calling the user-provided column callback and converting the result to a string. Extract them into a single helper method. Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Drop the aliasMember plumbing through DimensionDefinitionStatic / MeasureDefinitionStatic. Tesseract already computes is_sql_direct_ref for the dimension / measure sql and has SqlCall::resolve_direct_reference to fetch the referenced MemberSymbol. Use that existing machinery to find the owning cube for mask.sql, keeping the behavior identical while avoiding duplicating the owning-cube info across the native bridge. The semantics match the legacy BaseQuery path: JS sets aliasMember only when the view member's sql is a pure one-to-one reference to another cube member, which is exactly the condition SqlCall::is_direct_reference captures. Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Summary
When a cube member with
mask.sqlis re-exposed through a view (especially withprefix: true), mask references such as{CUBE.city},{CUBE}.city,{users}.citywere failing in Tesseract with errors like:Error in 'CUBE.city': Cannot resolve: cityCan't find join path to join 'users', 'users_secure_view'Member 'users_secure_view.users_city_sensitive_masked' references foreign cubes: users. Please split and move this definition to corresponding cubes.This is the setup that was reported — a cube
userswithmask.sqlon a dimension, exposed throughusers_secure_viewwithprefix: true:The legacy
BaseQuerypath already handled this:memberMaskSqlpicksaliasMember.split('.')[0]as the cube context formask.sql. Tesseract was using the symbol's current cube name (the view's name), which broke cross-cube/CUBEreferences inside masks.Additionally,
{groups.filter("…")}withgroups: []was emitting invalidcol IN ()SQL.What's changed
fix(tesseract): compilemask.sqlagainst the owning cube for view membersaliasMemberthroughDimensionDefinitionStatic/MeasureDefinitionStaticto Tesseract.DimensionSymbolFactory::buildandMeasureSymbolFactory::build, compilemask.sqlagainstaliasMember's cube name when set. This mirrors the existing legacyBaseQuerybehavior.mask.sqlfromiter_sql_calls()so the genericvalidate_regular_member_cube_refscheck does not flag legitimate cross-cube mask references on view members as foreign cubes.fix(schema-compiler): empty array filter no longer emitsIN ()SECURITY_CONTEXTarray filters (e.g.{groups.filter("…")}) with an empty array now render as1 = 0(or invoke the user callback with[]) in both the legacy helper and the Tesseractsecurity_context_filter_fn.test(rbac): unit and E2E coverageyaml-schema.test.tsexercise the four reference styles ({users.city},{CUBE.city},{CUBE}.city,{users}.city) on a prefixed view under both the legacyBaseQueryand the Tesseract (useNativeSqlPlanner: true) paths, plus the empty-groups case.smoke-rbac.test.ts) add SQL API + REST API + Tesseract coverage that a view-exposed member with such mask references actually executes end-to-end.Reproduction
Before the fix (Tesseract only):
After the fix:
Files changed
rust/cubesqlplanner/cubesqlplanner/src/cube_bridge/dimension_definition.rs,measure_definition.rs— exposealiasMember.rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs,measure_symbol.rs— usealiasMembercube formask.sqlcompile; exclude fromiter_sql_calls.rust/cubesqlplanner/cubesqlplanner/src/cube_bridge/member_sql.rs— empty-array filter short-circuit.packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts— empty-array filter short-circuit incontextSymbolsProxyFrom.