Skip to content

fix(wren-core): resolve relationship handles by the model they point at - #2661

Open
wilyan09007 wants to merge 1 commit into
Canner:mainfrom
wilyan09007:fix/issue-2658
Open

fix(wren-core): resolve relationship handles by the model they point at#2661
wilyan09007 wants to merge 1 commit into
Canner:mainfrom
wilyan09007:fix/issue-2658

Conversation

@wilyan09007

@wilyan09007 wilyan09007 commented Aug 11, 2026

Copy link
Copy Markdown

Summary

A calculated column or cube dimension whose expression traverses a relationship fails to plan
when the relationship handle is not named after the model it points at. Given a handle
customer on orders pointing at the model customers:

- name: customer
  type: customers
  relationship: orders_customers

an expression of customer.name fails:

column 'name' not found in 'customer'
possible column customers.name

The only workaround is to rename the handle to customers, which forces every relationship
alias to repeat the name of its target model. docs/core/reference/mdl.md documents the
singular handle as valid, so this is a gap between the documented model and the engine.

Refs #2658. That issue reports two problems and this addresses one of them; see the last
section, which is why it does not carry a closing keyword.

Root cause

create_wren_calculated_field_expr (core/wren-core/core/src/mdl/utils.rs) plans the
expression against a schema assembled from the lineage's required_fields_map. The lineage
resolves a relationship path correctly: it follows Column::relationship through to the
related model, so every qualifier in that schema is a model name.

The expression itself was rewritten separately, by trimming any compound identifier to its
last two identifiers. That drops the leading path but keeps the handle name as the
qualifier. The expression therefore asks for customer.name while the schema offers
customers.name.

The two names coincide whenever a handle is named after the model it points at, which is the
case in every fixture in the repo (core/wren-core/core/tests/data/mdl.json declares handle
customer of type customer, and core/wren-core-py/tests/test_modeling_core.py declares
handle orders of type orders), so no existing test exercised a differing name.

Fix

resolve_relationship_path walks the identifier path through the MDL, resolving each handle
with get_column_reference and advancing to the related model the way the lineage does, then
qualifies the rewritten identifier with the model the path lands on. An identifier that
traverses no relationship is returned untouched, and the previous trimming remains as the
fallback for any path the walk cannot resolve, so nothing that planned before changes shape.

Tests

  • test_create_wren_expr_handle_name_differs_from_model (mdl/utils.rs) calls
    create_wren_calculated_field_expr against a manifest whose handle customer points at the
    model customers, and asserts the built expression is customers.name.
  • test_calculated_column_with_aliased_relationship_handle (mdl/mod.rs) puts
    SELECT order_id, customer_name FROM orders through transform_sql_with_ctx and snapshots
    the generated SQL, covering the join end to end.

Both tests fail without the change and pass with it. cargo test --lib is 151 passed / 0
failed; cargo fmt --all -- --check and cargo clippy --all-targets --all-features -- -D warnings are clean.

Not addressed: relationship traversal in user SQL

#2658 also reports that SELECT orders.customer.name FROM orders fails, and proposes that
naming the handle after the model fixes it. That is a separate gap, and this PR does not
touch it.

Relationship traversal in a user query fails regardless of the handle name, because
relationship columns are filtered out of the schema a model registers with DataFusion
(Model::get_physical_columns keeps only columns where relationship.is_none()), so the path
has nothing to resolve against. Checked against wren-core 0.7.3 using this repo's own test
manifest, where the handle orders already matches the model orders:

SELECT customer.orders.o_orderkey FROM my_catalog.my_schema.customer

Schema error: No field named customer.orders.o_orderkey.
Valid fields are my_catalog.my_schema.customer.c_custkey, my_catalog.my_schema.customer.c_name.

The same query with a handle renamed to match its model fails identically, so renaming is not
a workaround there. docs/core/reference/mdl.md currently states that orders.customer.first_name
is valid SQL, so either the engine or that page needs a follow-up. Glad to open a separate
issue for it if that is the preference.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed calculated columns that reference relationships using aliases different from the related model’s name.
    • Relationship paths are now resolved correctly, producing the expected joins and projected query results.
    • Added regression coverage to help prevent similar query-planning issues.

A relationship column is a handle whose name is local to the model that
declares it, so `customer` on `orders` may point at the model `customers`.

`create_wren_calculated_field_expr` trimmed a relationship path down to its
last two identifiers, which left the handle name as the qualifier. It then
planned the expression against a schema built from the lineage's required
fields, and that schema is qualified by model name. The two agree only when
the handle happens to be named after its model, so a calculated column or a
cube dimension that used any other alias failed to plan:

    column 'name' not found in 'customer'
    possible column customers.name

Walk the path through the MDL instead, resolving each handle to its related
model the way the lineage does, and qualify the rewritten identifier with
that model. Paths that traverse no relationship are left as they were.
@github-actions github-actions Bot added rust Pull requests that update rust code core labels Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Calculated-field rewriting now resolves relationship handles to their destination model names before planning. New regression tests verify the generated join and projected SQL when a local relationship name differs from the target model name.

Changes

Relationship path resolution

Layer / File(s) Summary
Calculated-field path rewriting
core/wren-core/core/src/mdl/utils.rs
Compound identifiers now traverse relationship handles and use the destination model as the qualifier. Unresolved paths retain the existing fallback behavior.
Aliased relationship regression coverage
core/wren-core/core/src/mdl/utils.rs, core/wren-core/core/src/mdl/mod.rs
Integration tests verify that customer resolves to customers and produces the expected join and projected SQL.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Suggested reviewers: goldmedal

Poem

A rabbit traced the customer trail,
From handle name to model rail.
The join now finds the proper door,
And calculated fields shine more.
Tests hop gladly through the queue.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the fix for resolving relationship handles by their target model.
Description check ✅ Passed The description covers the summary, observed failure, root cause, fix, tests, and scope; only the duplicate check section is not explicit.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/wren-core/core/src/mdl/utils.rs`:
- Around line 245-249: Restrict the model-prefix skip in the path-resolution
loop to the first component only: skip path[0] when it matches base_model, while
resolving every subsequent identifier as a relationship handle. Update the logic
around the relation/path traversal in the relevant utility function and add a
multi-hop regression test covering a repeated model name such as
orders.customer.customers.name.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 55d8d627-a30d-4275-9bd5-ffdef3c0f4be

📥 Commits

Reviewing files that changed from the base of the PR and between ec85b1e and 97334f6.

📒 Files selected for processing (2)
  • core/wren-core/core/src/mdl/mod.rs
  • core/wren-core/core/src/mdl/utils.rs

Comment on lines +245 to +249
for ident in path {
// the path may be written starting from the model owning the calculated field
if ident.value == relation {
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restrict the model-prefix skip to the first path component.

Lines 247-249 skip an identifier whenever it equals the current destination model. Only the optional initial base-model qualifier has this meaning.

For example, if orders.customer resolves to customers, and customers has a relationship handle also named customers, orders.customer.customers.name returns customers.name instead of resolving the second relationship. Skip only path[0] when it equals base_model, then resolve every later identifier as a relationship handle. Add a multi-hop regression test for this case.

Proposed fix
-    for ident in path {
-        // the path may be written starting from the model owning the calculated field
-        if ident.value == relation {
+    for (index, ident) in path.iter().enumerate() {
+        // Only the first component can be the explicit base-model qualifier.
+        if index == 0 && ident.value == base_model {
             continue;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for ident in path {
// the path may be written starting from the model owning the calculated field
if ident.value == relation {
continue;
}
for (index, ident) in path.iter().enumerate() {
// Only the first component can be the explicit base-model qualifier.
if index == 0 && ident.value == base_model {
continue;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren-core/core/src/mdl/utils.rs` around lines 245 - 249, Restrict the
model-prefix skip in the path-resolution loop to the first component only: skip
path[0] when it matches base_model, while resolving every subsequent identifier
as a relationship handle. Update the logic around the relation/path traversal in
the relevant utility function and add a multi-hop regression test covering a
repeated model name such as orders.customer.customers.name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant