Skip to content

Preserve declared joins on saved query-backed models - #508

Merged
ZmeiGorynych merged 6 commits into
MotleyAI:mainfrom
mattfaltyn:fix/query-backed-declared-joins
Oct 22, 2026
Merged

ZmeiGorynych merged 6 commits into
MotleyAI:mainfrom
mattfaltyn:fix/query-backed-declared-joins

Conversation

@mattfaltyn

@mattfaltyn mattfaltyn commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes #507.

A query over a saved customer-total model could return 150 instead of 120 for a gold-customer filter. The model's declared join was saved correctly, then discarded when its result became a relation; the filter consequently became an uncorrelated existence check.

Preserve the saved relation's own joins through source, extension and joined-target materialization. Ordinary stages remain flat and do not inherit backing-source joins. An extension join repeating a stored join of its base (ignoring description, meta and pair order) is that stored edge for every model kind, in both the runtime overlay and the schema-drift stage graph, while differing or distinct named edges retain their meaning. No dependency, persisted-format or public request-schema change.

SQLite/DuckDB regressions cover filtered totals, aggregate attribution, multi-hop and joined-target traversal, incoming reverse edges, extensions, repeated stored joins on table and saved models, legal target names and stage isolation. The DEV-1918 strict expected failure now passes. The models guide and OpenSpec change describe the corrected boundary.

Validation

  • Red/green: the issue reproduction returns 150 before the fix and 120 afterward on SQLite and DuckDB.
  • Local Python 3.14 full unit run: 31,377 passed, 99 skipped, 7 xfailed, 3 failed. All three failed nodes passed isolated retries after correcting the Poetry PATH and investigating two timing assertions. The persistent timing case also failed on unchanged upstream; no test assertions were relaxed.
  • Documented local integration selection: 656 passed, 36 skipped, 1 xfailed.
  • Ruff, basedpyright (0 errors), architecture cross-walk, strict OpenSpec validation and the pinned pre-commit hook pass. Independent code/data review found no outstanding defects.
  • Review follow-up (extension-join reuse for every model kind, helper consolidation): full unit run 31,385 passed, 104 skipped, 7 xfailed; ruff, basedpyright, la-arch-check, LikeC4 validation and strict OpenSpec validation pass.
  • CI hardening: the DuckDB httpfs notebook now skips, rather than fails, when the CDN reports the remote file changed mid-read (DuckDB's ETag-mismatch error).

Credential-dependent BigQuery/Snowflake jobs are intentionally skipped for fork PRs. Docker dialect and Metabase suites are outside the documented always-on selection and their path-gated workflows are unaffected.

Codex assisted with reproduction, implementation and independent review.

@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

This review ran on the open-source allowance, not this organization's plan, because the pull request author doesn't have an assigned seat. Waiting won't change this — ask an organization admin to assign them a seat, or add seats in Billing if every seat is already assigned, then retry.

Next included review available in 17 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 57e6a8b and 535efd3.

📒 Files selected for processing: 8
  • openspec/changes/archive/2026-10-22-fix-query-backed-declared-joins/.openspec.yaml
  • openspec/changes/archive/2026-10-22-fix-query-backed-declared-joins/design.md
  • openspec/changes/archive/2026-10-22-fix-query-backed-declared-joins/proposal.md
  • openspec/changes/archive/2026-10-22-fix-query-backed-declared-joins/specs/models/join-traversal/spec.md
  • openspec/changes/archive/2026-10-22-fix-query-backed-declared-joins/specs/queries/query-backed-inline/spec.md
  • openspec/changes/archive/2026-10-22-fix-query-backed-declared-joins/tasks.md
  • openspec/specs/models/join-traversal/spec.md
  • openspec/specs/queries/query-backed-inline/spec.md

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between e6be7f7 and 57e6a8b.

📒 Files selected for processing: 2
  • tests/integration/test_notebooks.py
  • tests/test_notebook_network_guard.py

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.

📝 Walkthrough

Saved query-backed models retain their declared joins when consumed. Query planning binds saved relations with those joins, while ordinary query stages do not inherit joins from backing models. Equivalent extension joins are applied once.

Changes: Query-backed declared joins

Layer / File(s) Summary
Retain joins during model materialization
slayer/ir/source_bundle.py, slayer/engine/bundle_builder.py, slayer/engine/plan.py
Query-backed models retain their declared joins. Stage-model construction accepts and validates saved joins. Query-backed sibling sources use ModelScope; ordinary siblings continue to use StageSchema.
Deduplicate equivalent extension joins
slayer/core/join_edges.py, slayer/ir/source_bundle.py, slayer/engine/schema_drift.py
same_edge compares join targets, names, types, cardinalities, and join-pair sets. Runtime overlays and schema-drift graphs omit extension joins that match stored edges.
Verify saved-join behavior and boundaries
tests/test_query_backed_declared_joins.py, tests/test_query_backed_declared_join_boundaries.py, tests/test_dev1902_join_key_spelling.py, tests/test_dev2073_join_edge_identity.py, tests/test_schema_drift_typed.py, openspec/changes/fix-query-backed-declared-joins/*, docs/concepts/models.md
SQLite and DuckDB tests cover filtering, traversal, extensions, ordinary-stage isolation, and stage-named targets. The prior expected-failure test is now expected to pass. OpenSpec requirements and model documentation describe the behavior.

Notebook network error classification

Layer / File(s) Summary
Classify remote file changes
tests/integration/test_notebooks.py, tests/test_notebook_network_guard.py
DuckDB transient-error signatures and network-guard cases include remote-file-changed and ETag-mismatch messages.

Priority: ➖ Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

Suggested reviewers: zmeigorynych

Merge Risk: ⚪ Minimal · up to 57e6a

No actionable merge-blocking risk was identified in the reviewed changes.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Out of Scope Changes check Warning Most changes support #507, including production join planning, regression tests, schema-drift handling, and related documentation. However, tests/integration/test_notebooks.py adds `remote file has … Remove the notebook network-classification changes from this pull request or move them to a separate pull request. Keep only changes that implement or validate issue #507.
Docstring Coverage Warning Docstring coverage is 32.35% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 34 functions across 11 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 3 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely identifies the main change: preserving declared joins on saved query-backed models.
Linked Issues check Passed Issue #507 requires saved query-backed joins to survive materialization and remain usable for filtering and traversal, while ordinary stages stay flat. The change preserves joins in `_split_query_back…
Full details: Linked Issues check

Explanation

Issue #507 requires saved query-backed joins to survive materialization and remain usable for filtering and traversal, while ordinary stages stay flat. The change preserves joins in _split_query_backed, carries saved joins through source and sibling construction, and uses same_edge for repeated extension joins. The added SQLite and DuckDB tests cover filtered totals, aggregates, traversal, incoming edges, extensions, stage isolation, and legal stage targets. No unmet coding requirement is shown.

Full details: Out of Scope Changes check

Explanation

Most changes support #507, including production join planning, regression tests, schema-drift handling, and related documentation. However, tests/integration/test_notebooks.py adds remote file has changed to DuckDB transient network signatures, and tests/test_notebook_network_guard.py adds ETag and hostless network cases. These changes concern notebook network-error classification and have no demonstrated connection to saved query-backed joins.

Resolution

Remove the notebook network-classification changes from this pull request or move them to a separate pull request. Keep only changes that implement or validate issue #507.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@mattfaltyn
mattfaltyn marked this pull request as ready for review October 21, 2026 22:05
Move the exact-repeat extension-join dedupe from the query-backed-only planner branch into apply_extension_overlay, keyed on a semantic edge identity (target, name, join type, cardinality, pair set), and mirror it in the schema-drift stage graph. Attach saved joins after the stage-model rename with an explicit key check, route the saved-joins lookup through one bundle helper, and shorten the models-guide addition to one sentence.
Replace design decision 5's saved-relation-only workaround with the one cross-model rule, and add the models/join-traversal delta for it.
DuckDB reports an ETag change across CDN edges as 'this likely means the remote file has changed'; classify it as a transient outage of the named remote host so the notebook skips instead of failing.
@sonarqubecloud

Copy link
Copy Markdown

@ZmeiGorynych
ZmeiGorynych merged commit 54beea3 into MotleyAI:main Oct 22, 2026
7 checks passed
@ZmeiGorynych

Copy link
Copy Markdown
Member

@mattfaltyn Thanks for the PR!
Released as 1.4.1

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Saved query-backed joins are lost, causing incorrect filtered totals

2 participants