Skip to content

fix: resolve materialize(findOne()) to undefined when the correlation key is null - #1707

Open
Mohith26 wants to merge 2 commits into
TanStack:mainfrom
Mohith26:fix/findone-correlation-miss-undefined
Open

fix: resolve materialize(findOne()) to undefined when the correlation key is null#1707
Mohith26 wants to merge 2 commits into
TanStack:mainfrom
Mohith26:fix/findone-correlation-miss-undefined

Conversation

@Mohith26

@Mohith26 Mohith26 commented Jul 30, 2026

Copy link
Copy Markdown

Fixes #1706, reported by @Jaime02.

materialize(q...findOne()) returned null instead of the typed undefined whenever the correlated FK column was itself null: the compiler replaces the include in select with a literal null placeholder, and Phase 1 of the includes flush only handled correlationKey != null, so null-FK parents never reached materializeIncludedValue and the raw placeholder leaked out, violating the T | undefined contract in the types.

The fix resolves null-correlation-key fields eagerly to their empty materialized value at parent-insert time (undefined for findOne, [] for arrays, "" for concat), with a skipped routing flag so conditional-select branches that do not own the field are untouched. Bare non-materialized includes with null FK intentionally keep the placeholder pending RFC #1658.

Independent of #1705 (different region of the compiler plus the live-collection routing; reproduced on plain main; no overlapping hunks). Patch changeset included. Three regression tests (the issue's three-post repro with an FK-set-but-unmatched control, array shape, live FK-update-to-null transition) fail without the fix; full suite 2486 passed, zero new failures; eslint/prettier/typecheck clean.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed materialized query results when correlation keys are null.
    • findOne() now consistently returns undefined, array queries return [], and concatenated results return an empty value.
    • Corrected updates when a correlation key changes from a valid value to null.
    • Prevented placeholder values from appearing in included results.

Mohith26 added 2 commits July 29, 2026 18:35
… key is null

A null correlation key never reaches materializeIncludedValue: the Phase 1
includes flush only handled 'correlationKey != null', so the compiler's null
select placeholder leaked into the parent result, violating the typed
'T | undefined' contract of findOne() (and 'Array<T>' for array
materializations).

Null correlation keys can never match a child row and never receive child
pipeline output, so the field is now resolved eagerly to its empty
materialized value: undefined for findOne(), [] for arrays, an empty string
for concat. Rows that do not participate in the include at all
(conditional-select guards failed, or no routing info found) are tagged
'skipped' by the compiler's routing functions so values owned by other
conditional-select branches are left untouched.

Bare (non-materialized) subquery includes are unchanged; their null-key
behavior is tracked separately by the RFC 1658 cluster.

Fixes TanStack#1706
Covers the three paths from the TanStack#1706 repro: findOne() with a matching FK,
an FK that matches no child, and a null FK, plus the array materialization
shape and a live update transitioning the FK to null.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change distinguishes skipped include routing from null correlation keys, emits empty materialized values for inline includes with null correlations, synchronizes stored parent rows, and adds regression coverage for findOne(), array materialization, and correlation updates.

Changes

Null correlation materialization

Layer / File(s) Summary
Routing metadata and guard handling
packages/db/src/query/compiler/index.ts
Include routing metadata now carries skipped?: boolean across source, direct, and compiled subquery routing paths.
Inline empty materialization
packages/db/src/query/live/collection-config-builder.ts
Inline includes with non-skipped null or undefined correlation keys receive the appropriate empty materialized value, while stored parent rows are synchronized.
Regression coverage and release note
packages/db/tests/query/includes.test.ts, .changeset/shiny-donkeys-repeat.md
Tests verify undefined, empty-array, and reactive null-correlation results; a patch changeset documents the fix.

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

Possibly related issues

  • Issue 1495 — Both changes involve flushIncludesState and nested include materialization, though they address different behaviors.

Possibly related PRs

  • TanStack/db#1705 — Both changes update compiler routing and include tests for correlated-subquery materialization behavior.

Suggested reviewers: kevin-dp, kyleamathews

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main bug fix and the affected materialize(findOne()) behavior.
Description check ✅ Passed The description covers the bug, fix, tests, changeset, and release impact, though it doesn't use the exact template headings.
Linked Issues check ✅ Passed The changes address #1706 by resolving null correlation keys to undefined for findOne, with array/concat handling and regression tests.
Out of Scope Changes check ✅ Passed The added compiler, live-query, changeset, and test updates all support the #1706 fix and don't appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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

🧹 Nitpick comments (1)
packages/db/tests/query/includes.test.ts (1)

6583-6597: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the regression fixtures type-safe.

The helper has no return annotation and the assertions use as any, so result-shape regressions are not type-checked. Use a precise helper return type and inferred/narrowed query results instead.

As per coding guidelines, avoid any and provide the most precise TypeScript return type annotations.

Also applies to: 6620-6682

🤖 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 `@packages/db/tests/query/includes.test.ts` around lines 6583 - 6597, The
createAuthorAndPostCollections helper should declare a precise return type for
both collection instances, and the related query assertions should remove as any
casts. Use inferred or properly narrowed result types so regressions in the
query result shape are caught by TypeScript.

Source: Coding guidelines

🤖 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 @.changeset/shiny-donkeys-repeat.md:
- Line 5: Update the changeset description to replace the invalid-looking
`materialize(q…findOne())` expression with valid API syntax such as
`materialize(q.from(...).findOne())`, or describe the behavior in prose while
preserving the existing null-correlation-key details.

---

Nitpick comments:
In `@packages/db/tests/query/includes.test.ts`:
- Around line 6583-6597: The createAuthorAndPostCollections helper should
declare a precise return type for both collection instances, and the related
query assertions should remove as any casts. Use inferred or properly narrowed
result types so regressions in the query result shape are caught by TypeScript.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a3f855fd-290c-4076-8ce6-410bcea0f9aa

📥 Commits

Reviewing files that changed from the base of the PR and between 67c840f and 0997bed.

📒 Files selected for processing (4)
  • .changeset/shiny-donkeys-repeat.md
  • packages/db/src/query/compiler/index.ts
  • packages/db/src/query/live/collection-config-builder.ts
  • packages/db/tests/query/includes.test.ts

'@tanstack/db': patch
---

Fix: `materialize(q…findOne())` resolved to `null` instead of `undefined` when the correlation key itself was null. A null correlation key never reached the includes materialization step, so the compiler's `null` select placeholder leaked into the result, violating the typed `T | undefined` contract. Null correlation keys now resolve to the empty materialized value (`undefined` for `findOne()`, `[]` for arrays, `` for `concat`).

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a valid-looking query expression.

materialize(q…findOne()) is unclear and reads like invalid API syntax. Use materialize(q.from(...).findOne()) or prose instead.

Proposed fix
-Fix: `materialize(q…findOne())` resolved to `null` instead of `undefined`
+Fix: `materialize(q.from(...).findOne())` resolved to `null` instead of `undefined`
📝 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
Fix: `materialize(qfindOne())` resolved to `null` instead of `undefined` when the correlation key itself was null. A null correlation key never reached the includes materialization step, so the compiler's `null` select placeholder leaked into the result, violating the typed `T | undefined` contract. Null correlation keys now resolve to the empty materialized value (`undefined` for `findOne()`, `[]` for arrays, `` for `concat`).
Fix: `materialize(q.from(...).findOne())` resolved to `null` instead of `undefined` when the correlation key itself was null. A null correlation key never reached the includes materialization step, so the compiler's `null` select placeholder leaked into the result, violating the typed `T | undefined` contract. Null correlation keys now resolve to the empty materialized value (`undefined` for `findOne()`, `[]` for arrays, `` for `concat`).
🤖 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 @.changeset/shiny-donkeys-repeat.md at line 5, Update the changeset
description to replace the invalid-looking `materialize(q…findOne())` expression
with valid API syntax such as `materialize(q.from(...).findOne())`, or describe
the behavior in prose while preserving the existing null-correlation-key
details.

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.

materialize(q…findOne()) resolves to null, not undefined, when the correlation key is itself null

1 participant