Skip to content

fix(orm): keep nested partial-select object when leftJoin first column is null#5797

Open
barobaonguyen wants to merge 1 commit into
drizzle-team:mainfrom
barobaonguyen:bounty/1603-drizzle-orm-1603
Open

fix(orm): keep nested partial-select object when leftJoin first column is null#5797
barobaonguyen wants to merge 1 commit into
drizzle-team:mainfrom
barobaonguyen:bounty/1603-drizzle-orm-1603

Conversation

@barobaonguyen
Copy link
Copy Markdown

Closes #1603.

Root cause

mapResultRow (drizzle-orm/src/utils.ts) builds a per-row nullifyMap that decides which nested-object paths should be replaced with null when their left-joined table didn't match. The flag for a path is set on the first column it sees being null, and only cleared when a column from a different table later appears under the same path.

When the second (and later) columns under that path come from the same nullable left-joined table, the existing branch isn't taken — even if those columns are non-null. So a select like:

db.select({
  name: org.name,
  branding: {
    logo: brand.logo,             // null in the joined row
    panelBackground: brand.panel, // "#1a8cff"
  },
}).from(org).leftJoin(brand, ...)

returns branding: null instead of branding: { logo: null, panelBackground: "#1a8cff" }. Swapping the column order in the select hides the bug, which matches every report on the issue.

Fix

Also clear the flag when a non-null value arrives for the same path, not only on a table mismatch. Two-line condition change in mapResultRow:

} else if (
    typeof nullifyMap[objectName] === 'string'
    && (nullifyMap[objectName] !== tableName || value !== null)
) {
    nullifyMap[objectName] = false;
}

Tests

New drizzle-orm/tests/mapResultRow.test.ts (pnpm test → 7 new, 573 total passing):

  • repro from the issue (first nested col null, later non-null → object preserved)
  • column-order control (last col null, earlier non-null → object preserved)
  • all-null on nullable join → object nullified
  • all-null on inner join → object preserved (join guarantees non-null)
  • mixed-table nested keys → never nullified
  • two independent nested objects with different null patterns
  • non-Column field with no joinsNotNullableMap → unchanged behaviour

Prior art

Equivalent one-line variants land in #1825 (2024), #5436, #5750, #5765, #5785, #5793. None of those PRs ship a regression-test file; the new test covers the surrounding invariants so the bug stays fixed under future refactors of nullifyMap.

…n is null

mapResultRow marks a nested-object path for nullification on the first
column it sees being null. The flag was only cleared when a column from
a different table appeared under the same path, so non-null values from
the same nullable left-joined table never reset it.

Reset the flag whenever a non-null value arrives for the same nested
path. Add regression tests covering the issue repro, column-order
control, all-null nullable/inner joins, mixed-table keys, and two
independent nested objects with different null patterns.

Closes drizzle-team#1603
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.

[BUG]: Nested Partial Select returns null on left join if first column value is null

1 participant