@@ -124,7 +124,90 @@ export function groupInversedOrMappedKeysByEntity<T extends AnyEntity<T>>(
124124 return entitiesMap ;
125125}
126126
127+ function allKeysArePK < K extends object > (
128+ keys : Array < EntityKey < K > > | undefined ,
129+ primaryKeys : Array < EntityKey < K > > ,
130+ ) : boolean {
131+ if ( keys == null ) {
132+ return false ;
133+ }
134+ if ( keys . length !== primaryKeys . length ) {
135+ return false ;
136+ }
137+ for ( const key of keys ) {
138+ if ( ! primaryKeys . includes ( key ) ) {
139+ return false ;
140+ }
141+ }
142+ return true ;
143+ }
144+
145+ // {id: 5, name: "a"} returns false because contains additional fields
146+ // Returns true for all PK formats including {id: 1} or {owner: 1, recipient: 2}
147+ function isPK < K extends object > ( filter : FilterQueryDataloader < K > , meta : EntityMetadata < K > ) : boolean {
148+ if ( meta == null ) {
149+ return false ;
150+ }
151+ if ( meta . compositePK ) {
152+ // COMPOSITE
153+ if ( Array . isArray ( filter ) ) {
154+ // PK or PK[] or object[]
155+ // [1, 2]
156+ // [[1, 2], [3, 4]]
157+ // [{owner: 1, recipient: 2}, {owner: 3, recipient: 4}]
158+ // [{owner: 1, recipient: 2, sex: 0}, {owner: 3, recipient: 4, sex: 1}]
159+ if ( Utils . isPrimaryKey ( filter , meta . compositePK ) ) {
160+ // PK
161+ return true ;
162+ }
163+ if ( Utils . isPrimaryKey ( filter [ 0 ] , meta . compositePK ) ) {
164+ // PK[]
165+ return true ;
166+ }
167+ const keys = typeof filter [ 0 ] === "object" ? ( Object . keys ( filter [ 0 ] ) as Array < EntityKey < K > > ) : undefined ;
168+ if ( allKeysArePK ( keys , meta . primaryKeys ) ) {
169+ // object is PK or PK[]
170+ return true ;
171+ }
172+ } else {
173+ // object
174+ // {owner: 1, recipient: 2, sex: 0}
175+ const keys = typeof filter === "object" ? ( Object . keys ( filter ) as Array < EntityKey < K > > ) : undefined ;
176+ if ( allKeysArePK ( keys , meta . primaryKeys ) ) {
177+ // object is PK
178+ return true ;
179+ }
180+ }
181+ } else {
182+ // NOT COMPOSITE
183+ if ( Array . isArray ( filter ) ) {
184+ // PK[]
185+ // [1, 2]
186+ // [{id: 1}, {id: 2}] NOT POSSIBLE FOR NON COMPOSITE
187+ if ( Utils . isPrimaryKey ( filter [ 0 ] ) ) {
188+ return true ;
189+ }
190+ } else {
191+ // PK or object
192+ // 1
193+ // {id: [1, 2], sex: 0} or {id: 1, sex: 0}
194+ if ( Utils . isPrimaryKey ( filter ) ) {
195+ // PK
196+ return true ;
197+ }
198+ const keys =
199+ typeof filter === "object" ? ( Object . keys ( filter ) as [ EntityKey < K > , ...Array < EntityKey < K > > ] ) : undefined ;
200+ if ( keys ?. length === 1 && keys [ 0 ] === meta . primaryKeys [ 0 ] ) {
201+ // object is PK
202+ return true ;
203+ }
204+ }
205+ }
206+ return false ;
207+ }
208+
127209// Call this fn only if keyProp.targetMeta != null otherwise you will get false positives
210+ // Returns only PKs in short-hand format like 1 or [1, 1] not {id: 1} or {owner: 1, recipient: 2}
128211function getPKs < K extends object > (
129212 filter : FilterQueryDataloader < K > ,
130213 meta : EntityMetadata < K > ,
@@ -180,20 +263,20 @@ COMPOSITE PK:
180263const asc = ( a : string , b : string ) : number => a . localeCompare ( b ) ;
181264const notNull = ( el : string | undefined ) : boolean => el != null ;
182265
183- function getNewFiltersAndMapKeys < K extends object > (
266+ function getNewFiltersMapKeysAndMandatoryPopulate < K extends object > (
184267 cur : FilterQueryDataloader < K > ,
185268 meta : EntityMetadata < K > ,
186269 entityName : string ,
187- ) : Array < [ FilterQueryDataloader < K > , string ] > ;
188- function getNewFiltersAndMapKeys < K extends object > (
270+ ) : Array < [ FilterQueryDataloader < K > , string , Set < string > ? ] > ;
271+ function getNewFiltersMapKeysAndMandatoryPopulate < K extends object > (
189272 cur : FilterQueryDataloader < K > ,
190273 meta : EntityMetadata < K > ,
191- ) : [ FilterQueryDataloader < K > , string ] ;
192- function getNewFiltersAndMapKeys < K extends object > (
274+ ) : [ FilterQueryDataloader < K > , string , string ? ] ;
275+ function getNewFiltersMapKeysAndMandatoryPopulate < K extends object > (
193276 cur : FilterQueryDataloader < K > ,
194277 meta : EntityMetadata < K > ,
195278 entityName ?: string ,
196- ) : [ FilterQueryDataloader < K > , string ] | Array < [ FilterQueryDataloader < K > , string ] > {
279+ ) : [ FilterQueryDataloader < K > , string , string ? ] | Array < [ FilterQueryDataloader < K > , string , Set < string > ? ] > {
197280 const PKs = getPKs ( cur , meta ) ;
198281 if ( PKs != null ) {
199282 const res : [ FilterQueryDataloader < K > , string ] = [
@@ -206,6 +289,8 @@ function getNewFiltersAndMapKeys<K extends object>(
206289 } else {
207290 const newFilter : any = { } ;
208291 const keys : string [ ] = [ ] ;
292+ const populateSet = new Set < string > ( ) ;
293+ let computedPopulate : string | undefined ;
209294 if ( Array . isArray ( cur ) ) {
210295 // COMPOSITE PKs like [{owner: 1, recipient: 2}, {recipient: 4, owner: 3}]
211296 for ( const key of meta . primaryKeys ) {
@@ -223,27 +308,42 @@ function getNewFiltersAndMapKeys<K extends object>(
223308 // Using $or at the top level means that we can treat it as two separate queries and filter results from either of them
224309 if ( key === "$or" && entityName != null ) {
225310 return ( value as Array < FilterQueryDataloader < K > > )
226- . map ( ( el ) => getNewFiltersAndMapKeys ( el , meta , entityName ) )
311+ . map ( ( el ) => getNewFiltersMapKeysAndMandatoryPopulate ( el , meta , entityName ) )
227312 . flat ( ) ;
228313 }
229314 const keyProp = meta . properties [ key as EntityKey < K > ] ;
230315 if ( keyProp == null ) {
231316 throw new Error ( `Cannot find properties for ${ key } ` ) ;
232317 }
233318 if ( keyProp . targetMeta == null ) {
319+ // Our current key might lead to a scalar (thus we don't need to populate anything)
320+ // or to an explicited PK like {id: 1, name: 'a'}
234321 newFilter [ key ] = Array . isArray ( value ) ? value : [ value ] ;
235322 keys . push ( key ) ;
236323 } else {
237- const [ subFilter , subKey ] = getNewFiltersAndMapKeys ( value , keyProp . targetMeta ) ;
324+ // Our current key points to either a Reference or a Collection
325+ const [ subFilter , subKey , furtherPop ] = getNewFiltersMapKeysAndMandatoryPopulate ( value , keyProp . targetMeta ) ;
238326 newFilter [ key ] = subFilter ;
239327 keys . push ( `${ key } :${ subKey } ` ) ;
328+ // We need to populate all Collections and all the References
329+ // where we further match non-PKs properties
330+ if ( keyProp . ref !== true || ! isPK ( value , keyProp . targetMeta ) ) {
331+ computedPopulate = furtherPop == null ? `${ key } ` : `${ key } .${ furtherPop } ` ;
332+ if ( entityName != null ) {
333+ populateSet . add ( computedPopulate ) ;
334+ } else {
335+ // We return computedPopulate as the third element of the array
336+ }
337+ }
240338 }
241339 }
242340 const res : [ FilterQueryDataloader < K > , string ] = [
243341 newFilter ,
244342 [ entityName , `{${ keys . sort ( asc ) . join ( "," ) } }` ] . filter ( notNull ) . join ( "|" ) ,
245343 ] ;
246- return entityName == null ? res : [ res ] ;
344+ return entityName == null
345+ ? [ ...res , computedPopulate ]
346+ : [ [ ...res , populateSet . size === 0 ? undefined : populateSet ] ] ;
247347 }
248348 }
249349}
@@ -259,7 +359,7 @@ function updateQueryFilter<K extends object, P extends string = never>(
259359 newQueryMap ?: boolean ,
260360) : void {
261361 if ( options ?. populate != null && accOptions != null && accOptions . populate !== true ) {
262- if ( Array . isArray ( options . populate ) && options . populate [ 0 ] === "*" ) {
362+ if ( Array . isArray ( options . populate ) && options . populate . includes ( "*" ) ) {
263363 accOptions . populate = true ;
264364 } else if ( Array . isArray ( options . populate ) ) {
265365 if ( accOptions . populate == null ) {
@@ -276,14 +376,54 @@ function updateQueryFilter<K extends object, P extends string = never>(
276376 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
277377 const curValue = ( cur as Record < string , any [ ] > ) [ key ] ! ;
278378 if ( Array . isArray ( value ) ) {
279- value . push ( ...curValue . reduce < any [ ] > ( ( acc , cur ) => acc . concat ( cur ) , [ ] ) ) ;
379+ // value.push(...curValue.reduce<any[]>((acc, cur) => acc.concat(cur), []));
380+ value . push ( ...structuredClone ( curValue ) ) ;
280381 } else {
281382 updateQueryFilter ( [ value ] , curValue ) ;
282383 }
283384 }
284385 }
285386}
286387
388+ // The least amount of populate necessary to map the dataloader results to their original queries
389+ export function getMandatoryPopulate < K extends object > (
390+ cur : FilterQueryDataloader < K > ,
391+ meta : EntityMetadata < K > ,
392+ ) : string | undefined ;
393+ export function getMandatoryPopulate < K extends object > (
394+ cur : FilterQueryDataloader < K > ,
395+ meta : EntityMetadata < K > ,
396+ populate : Set < any > ,
397+ ) : void ;
398+ export function getMandatoryPopulate < K extends object > (
399+ cur : FilterQueryDataloader < K > ,
400+ meta : EntityMetadata < K > ,
401+ populate ?: Set < any > ,
402+ ) : any {
403+ for ( const [ key , value ] of Object . entries ( cur ) ) {
404+ const keyProp = meta . properties [ key as EntityKey < K > ] ;
405+ if ( keyProp == null ) {
406+ throw new Error ( `Cannot find properties for ${ key } ` ) ;
407+ }
408+ // If our current key leads to scalar we don't need to populate anything
409+ if ( keyProp . targetMeta != null ) {
410+ // Our current key points to either a Reference or a Collection
411+ // We need to populate all Collections
412+ // We also need to populate References whenever we have to further match non-PKs properties
413+ const PKs = getPKs ( value , keyProp . targetMeta ) ;
414+ if ( keyProp . ref !== true || PKs == null ) {
415+ const furtherPopulate = getMandatoryPopulate ( value , keyProp . targetMeta ) ;
416+ const computedPopulate = furtherPopulate == null ? `${ key } ` : `${ key } .${ furtherPopulate } ` ;
417+ if ( populate != null ) {
418+ populate . add ( computedPopulate ) ;
419+ } else {
420+ return computedPopulate ;
421+ }
422+ }
423+ }
424+ }
425+ }
426+
287427export interface DataloaderFind < K extends object , Hint extends string = never , Fields extends string = never > {
288428 entityName : string ;
289429 meta : EntityMetadata < K > ;
@@ -299,13 +439,13 @@ export function groupFindQueriesByOpts(
299439 const queriesMap = new Map < string , [ FilterQueryDataloader < any > , { populate ?: true | Set < any > } ?] > ( ) ;
300440 for ( const dataloaderFind of dataloaderFinds ) {
301441 const { entityName, meta, filter, options } = dataloaderFind ;
302- const filtersAndKeys = getNewFiltersAndMapKeys ( filter , meta , entityName ) ;
442+ const filtersAndKeys = getNewFiltersMapKeysAndMandatoryPopulate ( filter , meta , entityName ) ;
303443 dataloaderFind . filtersAndKeys = [ ] ;
304- filtersAndKeys . forEach ( ( [ newFilter , key ] ) => {
444+ filtersAndKeys . forEach ( ( [ newFilter , key , mandatoryPopulate ] ) => {
305445 dataloaderFind . filtersAndKeys ?. push ( { key, newFilter } ) ;
306446 let queryMap = queriesMap . get ( key ) ;
307447 if ( queryMap == null ) {
308- queryMap = [ structuredClone ( newFilter ) , { } ] ;
448+ queryMap = [ structuredClone ( newFilter ) , { ... ( mandatoryPopulate != null && { populate : mandatoryPopulate } ) } ] ;
309449 updateQueryFilter ( queryMap , newFilter , options , true ) ;
310450 queriesMap . set ( key , queryMap ) ;
311451 } else {
@@ -382,6 +522,9 @@ export function optsMapToQueries<Entity extends object>(
382522 populate : options . populate === true ? [ "*" ] : Array . from ( options . populate ) ,
383523 } ) ,
384524 } satisfies Pick < FindOptions < any , any > , "populate" > ;
525+ console . log ( "entityName" , entityName ) ;
526+ console . log ( "filter" , filter ) ;
527+ console . log ( "findOptions" , findOptions ) ;
385528 const entities = await em . find ( entityName , filter , findOptions ) ;
386529 return [ key , entities ] ;
387530 } ) ;
0 commit comments