@@ -3,6 +3,7 @@ import { Column, DriverValueDecoder, getTableName, SQL } from 'drizzle-orm';
33import { entityKind , is } from 'drizzle-orm/entity' ;
44import type { Logger } from 'drizzle-orm/logger' ;
55import { fillPlaceholders , type Query } from 'drizzle-orm/sql/sql' ;
6+ import { SQLiteColumn } from 'drizzle-orm/sqlite-core' ;
67import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builders/select.types' ;
78import {
89 type PreparedQueryConfig as PreparedQueryConfigBase ,
@@ -98,6 +99,11 @@ export class PowerSyncSQLitePreparedQuery<
9899 }
99100}
100101
102+ /**
103+ * Maps a flat array of database row values to a result object based on the provided column definitions.
104+ * It reconstructs the hierarchical structure of the result by following the specified paths for each field.
105+ * It also handles nullification of nested objects when joined tables are nullable.
106+ */
101107export function mapResultRow < TResult > (
102108 columns : SelectedFieldsOrdered ,
103109 row : unknown [ ] ,
@@ -107,14 +113,7 @@ export function mapResultRow<TResult>(
107113 const nullifyMap : Record < string , string | false > = { } ;
108114
109115 const result = columns . reduce < Record < string , any > > ( ( result , { path, field } , columnIndex ) => {
110- let decoder : DriverValueDecoder < unknown , unknown > ;
111- if ( is ( field , Column ) ) {
112- decoder = field ;
113- } else if ( is ( field , SQL ) ) {
114- decoder = ( field as any ) . decoder ;
115- } else {
116- decoder = ( field . sql as any ) . decoder ;
117- }
116+ const decoder = getDecoder ( field ) ;
118117 let node = result ;
119118 for ( const [ pathChunkIndex , pathChunk ] of path . entries ( ) ) {
120119 if ( pathChunkIndex < path . length - 1 ) {
@@ -126,30 +125,64 @@ export function mapResultRow<TResult>(
126125 const rawValue = row [ columnIndex ] ! ;
127126 const value = ( node [ pathChunk ] = rawValue === null ? null : decoder . mapFromDriverValue ( rawValue ) ) ;
128127
129- if ( joinsNotNullableMap && is ( field , Column ) && path . length === 2 ) {
130- const objectName = path [ 0 ] ! ;
131- if ( ! ( objectName in nullifyMap ) ) {
132- nullifyMap [ objectName ] = value === null ? getTableName ( field . table ) : false ;
133- } else if (
134- typeof nullifyMap [ objectName ] === 'string' &&
135- nullifyMap [ objectName ] !== getTableName ( field . table )
136- ) {
137- nullifyMap [ objectName ] = false ;
138- }
139- }
128+ updateNullifyMap ( nullifyMap , field , path , value , joinsNotNullableMap ) ;
140129 }
141130 }
142131 return result ;
143132 } , { } ) ;
144133
145- // Nullify all nested objects from nullifyMap that are nullable
146- if ( joinsNotNullableMap && Object . keys ( nullifyMap ) . length > 0 ) {
147- for ( const [ objectName , tableName ] of Object . entries ( nullifyMap ) ) {
148- if ( typeof tableName === 'string' && ! joinsNotNullableMap [ tableName ] ) {
149- result [ objectName ] = null ;
150- }
151- }
152- }
134+ applyNullifyMap ( result , nullifyMap , joinsNotNullableMap ) ;
153135
154136 return result as TResult ;
155137}
138+
139+ /**
140+ * Determines the appropriate decoder for a given field.
141+ */
142+ function getDecoder ( field : SQLiteColumn | SQL < unknown > | SQL . Aliased ) : DriverValueDecoder < unknown , unknown > {
143+ if ( is ( field , Column ) ) {
144+ return field ;
145+ } else if ( is ( field , SQL ) ) {
146+ return ( field as any ) . decoder ;
147+ } else {
148+ return ( field . sql as any ) . decoder ;
149+ }
150+ }
151+
152+ function updateNullifyMap (
153+ nullifyMap : Record < string , string | false > ,
154+ field : any ,
155+ path : string [ ] ,
156+ value : any ,
157+ joinsNotNullableMap : Record < string , boolean > | undefined
158+ ) : void {
159+ if ( ! joinsNotNullableMap || ! is ( field , Column ) || path . length !== 2 ) {
160+ return ;
161+ }
162+
163+ const objectName = path [ 0 ] ! ;
164+ if ( ! ( objectName in nullifyMap ) ) {
165+ nullifyMap [ objectName ] = value === null ? getTableName ( field . table ) : false ;
166+ } else if ( typeof nullifyMap [ objectName ] === 'string' && nullifyMap [ objectName ] !== getTableName ( field . table ) ) {
167+ nullifyMap [ objectName ] = false ;
168+ }
169+ }
170+
171+ /**
172+ * Nullify all nested objects from nullifyMap that are nullable
173+ */
174+ function applyNullifyMap (
175+ result : Record < string , any > ,
176+ nullifyMap : Record < string , string | false > ,
177+ joinsNotNullableMap : Record < string , boolean > | undefined
178+ ) : void {
179+ if ( ! joinsNotNullableMap || Object . keys ( nullifyMap ) . length === 0 ) {
180+ return ;
181+ }
182+
183+ for ( const [ objectName , tableName ] of Object . entries ( nullifyMap ) ) {
184+ if ( typeof tableName === 'string' && ! joinsNotNullableMap [ tableName ] ) {
185+ result [ objectName ] = null ;
186+ }
187+ }
188+ }
0 commit comments