Summary
A multi_stage: true measure (e.g. type: rank) whose order_by.sql template references another measure by name crashes Cube at schema-compile time when queried through a view that doesn't expose the referenced member under its bare name (either not at all, or only under an alias).
The view shouldn't need to expose the referenced member at all — it's a cube-internal implementation detail of the rank, not something the consumer asks for. But Cube's resolver evaluates the rank's template in the view's context rather than the cube's, fails to find the bare name, and trips an unguarded property access.
Cube version 1.6.45 (also reproduces on master). Tesseract enabled. Backend-agnostic — the crash is in JS schema compilation, before Tesseract takes over for SQL generation.
Repro
Minimal — the view exposes only the rank and the time dim. num_parcels is not included at all:
cubes:
- name: orders
sql: "SELECT * FROM orders"
dimensions:
- name: id
sql: id
type: number
primary_key: true
- name: created_at
sql: created_at
type: time
measures:
- name: num_parcels
type: sum
sql: parcels
- name: volume_by_day_rank
multi_stage: true
type: rank
reduce_by:
- created_at
order_by:
- sql: "{num_parcels}"
dir: desc
views:
- name: orders_view
cubes:
- join_path: orders
includes:
- created_at
- volume_by_day_rank
Query:
{
"measures": ["orders_view.volume_by_day_rank"],
"timeDimensions": [{ "dimension": "orders_view.created_at", "granularity": "day" }]
}
Adding { name: num_parcels, alias: shipment_count } to the view's includes (intentional aliased exposure for consumers) crashes identically — the resolver looks up the bare num_parcels, finds only shipment_count on the view, returns undefined just the same.
Stack trace
TypeError: Cannot read properties of undefined (reading '_objectWithResolvedProperties')
at packages/cubejs-schema-compiler/dist/src/adapter/BaseQuery.js:2838
at packages/cubejs-schema-compiler/dist/src/compiler/CubeSymbols.js:1147
Equivalent positions in source:
packages/cubejs-schema-compiler/src/adapter/BaseQuery.js:3593 — unguarded resolvedSymbol._objectWithResolvedProperties access
packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts:1308 — lookup returns undefined
Root cause
packages/cubejs-schema-compiler/src/adapter/BaseQuery.js:3349:
const orderBySql = (symbol.orderBy || [])
.map(o => ({ sql: this.evaluateSql(cubeName, o.sql), dir: o.dir }));
cubeName is the consumer's queried cube — i.e., the view orders_view, not the underlying cube orders where the rank was defined. The rank's order_by template {num_parcels} is evaluated against the view's symbol table, which doesn't carry num_parcels unless the view explicitly includes it bare.
packages/cubejs-schema-compiler/src/adapter/BaseQuery.js:3593 then accesses ._objectWithResolvedProperties on undefined → TypeError.
Semantically the rank's order_by should resolve in the cube's context (where the rank was authored), not the consumer's view context. {num_parcels} is the rank's implementation detail, not a member the view consumer needs to know about.
The Rust planner already does this correctly: rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_symbol.rs:544-550 compiles the rank's order_by at schema-compile time in path.cube_name() — i.e., the owning cube. The crash fires earlier in JS schema validation before Tesseract takes over.
Current workaround
Include the referenced member bare in every view that exposes any measure whose templates reference it:
- num_parcels # workaround — forces resolver to find it
- { name: num_parcels, alias: shipment_count } # what the consumer actually wants
Brittle, easy to forget, and a leaky abstraction — view authors shouldn't need to know which internal references each cube measure's templates touch.
Suggested fix direction
Two-part patch in packages/cubejs-schema-compiler/:
-
Substantive: Change BaseQuery.js:3349 (and the equivalent for any other measure-internal template — filters, case, etc., if the same gap applies) to evaluate the template in the measure's owning cube context instead of the consumer's queried cubeName. Mirrors the Rust planner's behaviour.
-
Defensive guard: Replace the unguarded resolvedSymbol._objectWithResolvedProperties at BaseQuery.js:3593 with a clear UserError when resolvedSymbol is undefined, so any future case hitting the same gap surfaces a diagnostic instead of a generic TypeError.
A regression test in packages/cubejs-schema-compiler/test/integration/ exercising the repro above (view exposes only the rank, not the referenced measure) should be sufficient coverage. Happy to put up a PR.
Related
#10854 — multi_stage reduce_by against type: time granularity-suffixed projections (fix in #10855). Both are Tesseract-era resolution gaps but live in different code paths.
Summary
A
multi_stage: truemeasure (e.g.type: rank) whoseorder_by.sqltemplate references another measure by name crashes Cube at schema-compile time when queried through a view that doesn't expose the referenced member under its bare name (either not at all, or only under an alias).The view shouldn't need to expose the referenced member at all — it's a cube-internal implementation detail of the rank, not something the consumer asks for. But Cube's resolver evaluates the rank's template in the view's context rather than the cube's, fails to find the bare name, and trips an unguarded property access.
Cube version 1.6.45 (also reproduces on master). Tesseract enabled. Backend-agnostic — the crash is in JS schema compilation, before Tesseract takes over for SQL generation.
Repro
Minimal — the view exposes only the rank and the time dim.
num_parcelsis not included at all:Query:
{ "measures": ["orders_view.volume_by_day_rank"], "timeDimensions": [{ "dimension": "orders_view.created_at", "granularity": "day" }] }Adding
{ name: num_parcels, alias: shipment_count }to the view's includes (intentional aliased exposure for consumers) crashes identically — the resolver looks up the barenum_parcels, finds onlyshipment_counton the view, returns undefined just the same.Stack trace
Equivalent positions in source:
packages/cubejs-schema-compiler/src/adapter/BaseQuery.js:3593— unguardedresolvedSymbol._objectWithResolvedPropertiesaccesspackages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts:1308— lookup returns undefinedRoot cause
packages/cubejs-schema-compiler/src/adapter/BaseQuery.js:3349:cubeNameis the consumer's queried cube — i.e., the vieworders_view, not the underlying cubeorderswhere the rank was defined. The rank'sorder_bytemplate{num_parcels}is evaluated against the view's symbol table, which doesn't carrynum_parcelsunless the view explicitly includes it bare.packages/cubejs-schema-compiler/src/adapter/BaseQuery.js:3593then accesses._objectWithResolvedPropertieson undefined →TypeError.Semantically the rank's
order_byshould resolve in the cube's context (where the rank was authored), not the consumer's view context.{num_parcels}is the rank's implementation detail, not a member the view consumer needs to know about.The Rust planner already does this correctly:
rust/cube/cubesqlplanner/cubesqlplanner/src/planner/symbols/measure_symbol.rs:544-550compiles the rank'sorder_byat schema-compile time inpath.cube_name()— i.e., the owning cube. The crash fires earlier in JS schema validation before Tesseract takes over.Current workaround
Include the referenced member bare in every view that exposes any measure whose templates reference it:
Brittle, easy to forget, and a leaky abstraction — view authors shouldn't need to know which internal references each cube measure's templates touch.
Suggested fix direction
Two-part patch in
packages/cubejs-schema-compiler/:Substantive: Change
BaseQuery.js:3349(and the equivalent for any other measure-internal template —filters,case, etc., if the same gap applies) to evaluate the template in the measure's owning cube context instead of the consumer's queriedcubeName. Mirrors the Rust planner's behaviour.Defensive guard: Replace the unguarded
resolvedSymbol._objectWithResolvedPropertiesatBaseQuery.js:3593with a clearUserErrorwhenresolvedSymbolis undefined, so any future case hitting the same gap surfaces a diagnostic instead of a genericTypeError.A regression test in
packages/cubejs-schema-compiler/test/integration/exercising the repro above (view exposes only the rank, not the referenced measure) should be sufficient coverage. Happy to put up a PR.Related
#10854 —
multi_stage reduce_byagainsttype: timegranularity-suffixed projections (fix in #10855). Both are Tesseract-era resolution gaps but live in different code paths.