@@ -180,47 +180,102 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
180180 return ;
181181 }
182182
183- for ( const kind of [ 'select' , 'include' ] as const ) {
184- if ( args [ kind ] && typeof args [ kind ] === 'object' ) {
185- for ( const [ field , value ] of Object . entries < any > ( args [ kind ] ) ) {
186- const fieldInfo = resolveField ( this . options . modelMeta , model , field ) ;
187- if ( ! fieldInfo ) {
188- continue ;
189- }
183+ // there're two cases where we need to inject polymorphic base hierarchy for fields
184+ // defined in base models
185+ // 1. base fields mentioned in select/include clause
186+ // { select: { fieldFromBase: true } } => { select: { delegate_aux_[Base]: { fieldFromBase: true } } }
187+ // 2. base fields mentioned in _count select/include clause
188+ // { select: { _count: { select: { fieldFromBase: true } } } } => { select: { delegate_aux_[Base]: { select: { _count: { select: { fieldFromBase: true } } } } } }
189+ //
190+ // Note that although structurally similar, we need to correctly deal with different injection location of the "delegate_aux" hierarchy
191+
192+ // selectors for the above two cases
193+ const selectors = [
194+ // regular select: { select: { field: true } }
195+ ( payload : any ) => ( { data : payload . select , kind : 'select' as const , isCount : false } ) ,
196+ // regular include: { include: { field: true } }
197+ ( payload : any ) => ( { data : payload . include , kind : 'include' as const , isCount : false } ) ,
198+ // select _count: { select: { _count: { select: { field: true } } } }
199+ ( payload : any ) => ( {
200+ data : payload . select ?. _count ?. select ,
201+ kind : 'select' as const ,
202+ isCount : true ,
203+ } ) ,
204+ // include _count: { include: { _count: { select: { field: true } } } }
205+ ( payload : any ) => ( {
206+ data : payload . include ?. _count ?. select ,
207+ kind : 'include' as const ,
208+ isCount : true ,
209+ } ) ,
210+ ] ;
211+
212+ for ( const selector of selectors ) {
213+ const { data, kind, isCount } = selector ( args ) ;
214+ if ( ! data || typeof data !== 'object' ) {
215+ continue ;
216+ }
190217
191- if ( this . isDelegateOrDescendantOfDelegate ( fieldInfo ?. type ) && value ) {
192- // delegate model, recursively inject hierarchy
193- if ( args [ kind ] [ field ] ) {
194- if ( args [ kind ] [ field ] === true ) {
195- // make sure the payload is an object
196- args [ kind ] [ field ] = { } ;
197- }
198- await this . injectSelectIncludeHierarchy ( fieldInfo . type , args [ kind ] [ field ] ) ;
218+ for ( const [ field , value ] of Object . entries < any > ( data ) ) {
219+ const fieldInfo = resolveField ( this . options . modelMeta , model , field ) ;
220+ if ( ! fieldInfo ) {
221+ continue ;
222+ }
223+
224+ if ( this . isDelegateOrDescendantOfDelegate ( fieldInfo ?. type ) && value ) {
225+ // delegate model, recursively inject hierarchy
226+ if ( data [ field ] ) {
227+ if ( data [ field ] === true ) {
228+ // make sure the payload is an object
229+ data [ field ] = { } ;
199230 }
231+ await this . injectSelectIncludeHierarchy ( fieldInfo . type , data [ field ] ) ;
200232 }
233+ }
201234
202- // refetch the field select/include value because it may have been
203- // updated during injection
204- const fieldValue = args [ kind ] [ field ] ;
235+ // refetch the field select/include value because it may have been
236+ // updated during injection
237+ const fieldValue = data [ field ] ;
205238
206- if ( fieldValue !== undefined ) {
207- if ( fieldValue . orderBy ) {
208- // `orderBy` may contain fields from base types
209- enumerate ( fieldValue . orderBy ) . forEach ( ( item ) =>
210- this . injectWhereHierarchy ( fieldInfo . type , item )
211- ) ;
212- }
239+ if ( fieldValue !== undefined ) {
240+ if ( fieldValue . orderBy ) {
241+ // `orderBy` may contain fields from base types
242+ enumerate ( fieldValue . orderBy ) . forEach ( ( item ) =>
243+ this . injectWhereHierarchy ( fieldInfo . type , item )
244+ ) ;
245+ }
213246
214- if ( this . injectBaseFieldSelect ( model , field , fieldValue , args , kind ) ) {
215- delete args [ kind ] [ field ] ;
216- } else if ( fieldInfo . isDataModel ) {
217- let nextValue = fieldValue ;
218- if ( nextValue === true ) {
219- // make sure the payload is an object
220- args [ kind ] [ field ] = nextValue = { } ;
247+ let injected = false ;
248+ if ( ! isCount ) {
249+ // regular select/include injection
250+ injected = await this . injectBaseFieldSelect ( model , field , fieldValue , args , kind ) ;
251+ if ( injected ) {
252+ // if injected, remove the field from the original payload
253+ delete data [ field ] ;
254+ }
255+ } else {
256+ // _count select/include injection, inject into an empty payload and then merge to the proper location
257+ const injectTarget = { [ kind ] : { } } ;
258+ injected = await this . injectBaseFieldSelect ( model , field , fieldValue , injectTarget , kind , true ) ;
259+ if ( injected ) {
260+ // if injected, remove the field from the original payload
261+ delete data [ field ] ;
262+ if ( Object . keys ( data ) . length === 0 ) {
263+ // if the original "_count" payload becomes empty, remove it
264+ delete args [ kind ] [ '_count' ] ;
221265 }
222- await this . injectSelectIncludeHierarchy ( fieldInfo . type , nextValue ) ;
266+ // finally merge the injection into the original payload
267+ const merged = deepmerge ( args [ kind ] , injectTarget [ kind ] ) ;
268+ args [ kind ] = merged ;
269+ }
270+ }
271+
272+ if ( ! injected && fieldInfo . isDataModel ) {
273+ let nextValue = fieldValue ;
274+ if ( nextValue === true ) {
275+ // make sure the payload is an object
276+ data [ field ] = nextValue = { } ;
223277 }
278+ await this . injectSelectIncludeHierarchy ( fieldInfo . type , nextValue ) ;
224279 }
225280 }
226281 }
@@ -272,7 +327,8 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
272327 field : string ,
273328 value : any ,
274329 selectInclude : any ,
275- context : 'select' | 'include'
330+ context : 'select' | 'include' ,
331+ forCount = false // if the injection is for a "{ _count: { select: { field: true } } }" payload
276332 ) {
277333 const fieldInfo = resolveField ( this . options . modelMeta , model , field ) ;
278334 if ( ! fieldInfo ?. inheritedFrom ) {
@@ -286,24 +342,35 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
286342 const baseRelationName = this . makeAuxRelationName ( base ) ;
287343
288344 // prepare base layer select/include
289- // let selectOrInclude = 'select';
290345 let thisLayer : any ;
291346 if ( target . include ) {
292- // selectOrInclude = 'include';
293347 thisLayer = target . include ;
294348 } else if ( target . select ) {
295- // selectOrInclude = 'select';
296349 thisLayer = target . select ;
297350 } else {
298- // selectInclude = 'include';
299351 thisLayer = target . select = { } ;
300352 }
301353
302354 if ( base . name === fieldInfo . inheritedFrom ) {
303355 if ( ! thisLayer [ baseRelationName ] ) {
304356 thisLayer [ baseRelationName ] = { [ context ] : { } } ;
305357 }
306- thisLayer [ baseRelationName ] [ context ] [ field ] = value ;
358+ if ( forCount ) {
359+ // { _count: { select: { field: true } } } => { delegate_aux_[Base]: { select: { _count: { select: { field: true } } } } }
360+ if (
361+ ! thisLayer [ baseRelationName ] [ context ] [ '_count' ] ||
362+ typeof thisLayer [ baseRelationName ] [ context ] !== 'object'
363+ ) {
364+ thisLayer [ baseRelationName ] [ context ] [ '_count' ] = { } ;
365+ }
366+ thisLayer [ baseRelationName ] [ context ] [ '_count' ] = deepmerge (
367+ thisLayer [ baseRelationName ] [ context ] [ '_count' ] ,
368+ { select : { [ field ] : value } }
369+ ) ;
370+ } else {
371+ // { select: { field: true } } => { delegate_aux_[Base]: { select: { field: true } } }
372+ thisLayer [ baseRelationName ] [ context ] [ field ] = value ;
373+ }
307374 break ;
308375 } else {
309376 if ( ! thisLayer [ baseRelationName ] ) {
0 commit comments