Skip to content

fix(orm): keep nested left-join object when a later column is set - #6152

Open
adsqx wants to merge 1 commit into
drizzle-team:mainfrom
adsqx:fix/nested-partial-select-left-join-null
Open

fix(orm): keep nested left-join object when a later column is set#6152
adsqx wants to merge 1 commit into
drizzle-team:mainfrom
adsqx:fix/nested-partial-select-left-join-null

Conversation

@adsqx

@adsqx adsqx commented Aug 20, 2026

Copy link
Copy Markdown

/claim #1603
Fixes #1603

Fix nested partial select returning null on left join when the first column is null

Closes #1603

Problem

With a nested partial select over a leftJoin, the whole nested object is mapped to null whenever the first selected column of that object happens to be null — even though the joined row exists and its other columns have values.

.select({
  name: orgTable.name,
  branding: {
    logo: orgBrandingTable.logo,                       // NULL in DB
    panelBackground: orgBrandingTable.panelBackground, // '#1a8cff'
  },
})
.from(orgTable)
.leftJoin(orgBrandingTable, eq(orgTable.id, orgBrandingTable.orgId))

Returns branding: null. Swapping the two keys inside branding returns { panelBackground: '#1a8cff', logo: null }. The generated SQL is correct in both cases — the result is order-dependent purely because of the row mapper.

Cause

In mapResultRow (drizzle-orm/src/utils.ts), nested objects are tracked in nullifyMap, where a table name means "every field seen so far for this object came from that table and was null" and false means "do not nullify".

The first column of a nested object seeds the entry:

nullifyMap[objectName] = value === null ? getTableName(field.table) : false;

The else if for the following columns only cleared the entry when a column came from a different table. A later non-null value from the same table left the table name in place, so after the reduce the object was wiped for any nullable join.

Fix

One condition, keeping the existing string | false encoding: also clear the entry when a later column of the same table is not null.

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

A missing left-join row still nullifies the nested object, because in that case every column is null, so the entry is never cleared and the existing joinsNotNullableMap check at the end applies unchanged.

This is the same code change as #6018; this PR adds the cases its test does not cover.

Tests

New drizzle-orm/tests/map-result-row.test.ts calls mapResultRow directly (no database) and covers:

  • the reported case — first nested column null, a later one not null → object kept,
  • the inverse order — first not null, later null → object kept (regression),
  • order-independence of the two above,
  • missing left-join row (all nested columns null) → nested object is null,
  • all nested columns null on a not nullable join → object kept,
  • a nested object mixing columns from two tables → never nullified,
  • several nested objects in one selection → only the all-null one is nullified,
  • no joinsNotNullableMap → nothing is nullified.

Three of them fail on main and pass with the fix; the rest guard the behaviour that must not change.

mapResultRow is shared by every driver, so the fix applies to all dialects.

… null

`mapResultRow` tracks nested left-join objects in `nullifyMap`, where a
table name marks the object as a candidate for nullification. Only a
column from a different table cleared that marker, so a later non-null
value from the same table left it in place and the whole nested object
was wiped — making the result depend on the order of the nested keys.

Also clear the marker when a later column of the same table is not null.
A missing left-join row still nullifies the object, since all of its
columns are null and the marker is never cleared.

Closes drizzle-team#1603

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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