• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

typeorm / typeorm / 15219332477

23 May 2025 09:13PM UTC coverage: 17.216% (-59.1%) from 76.346%
15219332477

Pull #11332

github

naorpeled
cr comments - move if block
Pull Request #11332: feat: add new undefined and null behavior flags

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

0 of 31 new or added lines in 3 files covered. (0.0%)

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

1.16
/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts
1
import { Driver } from "../../driver/Driver"
2
import { RelationIdLoadResult } from "../relation-id/RelationIdLoadResult"
3
import { ObjectLiteral } from "../../common/ObjectLiteral"
4
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
5
import { Alias } from "../Alias"
6
import { RelationCountLoadResult } from "../relation-count/RelationCountLoadResult"
7
import { RelationMetadata } from "../../metadata/RelationMetadata"
8
import { OrmUtils } from "../../util/OrmUtils"
1✔
9
import { QueryExpressionMap } from "../QueryExpressionMap"
10
import { EntityMetadata } from "../../metadata/EntityMetadata"
11
import { QueryRunner } from "../.."
12
import { DriverUtils } from "../../driver/DriverUtils"
1✔
13
import { ObjectUtils } from "../../util/ObjectUtils"
1✔
14

15
/**
16
 * Transforms raw sql results returned from the database into entity object.
17
 * Entity is constructed based on its entity metadata.
18
 */
19
export class RawSqlResultsToEntityTransformer {
1✔
20
    /**
21
     * Contains a hashmap for every rawRelationIdResults given.
22
     * In the hashmap you will find the idMaps of a result under the hash of this.hashEntityIds for the result.
23
     */
24
    private relationIdMaps: Array<{ [idHash: string]: any[] }>
25

26
    private pojo: boolean
27
    private selections: Set<string>
28
    private aliasCache: Map<string, Map<string, string>>
29
    private columnsCache: Map<
30
        string,
31
        Map<EntityMetadata, [string, ColumnMetadata][]>
32
    >
33

34
    // -------------------------------------------------------------------------
35
    // Constructor
36
    // -------------------------------------------------------------------------
37

38
    constructor(
UNCOV
39
        protected expressionMap: QueryExpressionMap,
×
UNCOV
40
        protected driver: Driver,
×
UNCOV
41
        protected rawRelationIdResults: RelationIdLoadResult[],
×
UNCOV
42
        protected rawRelationCountResults: RelationCountLoadResult[],
×
UNCOV
43
        protected queryRunner?: QueryRunner,
×
44
    ) {
UNCOV
45
        this.pojo = this.expressionMap.options.includes("create-pojo")
×
UNCOV
46
        this.selections = new Set(
×
UNCOV
47
            this.expressionMap.selects.map((s) => s.selection),
×
48
        )
UNCOV
49
        this.aliasCache = new Map()
×
UNCOV
50
        this.columnsCache = new Map()
×
51
    }
52

53
    // -------------------------------------------------------------------------
54
    // Public Methods
55
    // -------------------------------------------------------------------------
56

57
    /**
58
     * Since db returns a duplicated rows of the data where accuracies of the same object can be duplicated
59
     * we need to group our result and we must have some unique id (primary key in our case)
60
     */
61
    transform(rawResults: any[], alias: Alias): any[] {
UNCOV
62
        const group = this.group(rawResults, alias)
×
UNCOV
63
        const entities: any[] = []
×
UNCOV
64
        for (const results of group.values()) {
×
UNCOV
65
            const entity = this.transformRawResultsGroup(results, alias)
×
UNCOV
66
            if (entity !== undefined) entities.push(entity)
×
67
        }
UNCOV
68
        return entities
×
69
    }
70

71
    // -------------------------------------------------------------------------
72
    // Protected Methods
73
    // -------------------------------------------------------------------------
74

75
    /**
76
     * Build an alias from a name and column name.
77
     */
78
    protected buildAlias(aliasName: string, columnName: string) {
UNCOV
79
        let aliases = this.aliasCache.get(aliasName)
×
UNCOV
80
        if (!aliases) {
×
UNCOV
81
            aliases = new Map()
×
UNCOV
82
            this.aliasCache.set(aliasName, aliases)
×
83
        }
UNCOV
84
        let columnAlias = aliases.get(columnName)
×
UNCOV
85
        if (!columnAlias) {
×
UNCOV
86
            columnAlias = DriverUtils.buildAlias(
×
87
                this.driver,
88
                undefined,
89
                aliasName,
90
                columnName,
91
            )
UNCOV
92
            aliases.set(columnName, columnAlias)
×
93
        }
UNCOV
94
        return columnAlias
×
95
    }
96

97
    /**
98
     * Groups given raw results by ids of given alias.
99
     */
100
    protected group(rawResults: any[], alias: Alias): Map<string, any[]> {
UNCOV
101
        const map = new Map()
×
UNCOV
102
        const keys: string[] = []
×
UNCOV
103
        if (alias.metadata.tableType === "view") {
×
UNCOV
104
            keys.push(
×
105
                ...alias.metadata.columns.map((column) =>
UNCOV
106
                    this.buildAlias(alias.name, column.databaseName),
×
107
                ),
108
            )
109
        } else {
UNCOV
110
            keys.push(
×
111
                ...alias.metadata.primaryColumns.map((column) =>
UNCOV
112
                    this.buildAlias(alias.name, column.databaseName),
×
113
                ),
114
            )
115
        }
UNCOV
116
        for (const rawResult of rawResults) {
×
UNCOV
117
            const id = keys
×
118
                .map((key) => {
UNCOV
119
                    const keyValue = rawResult[key]
×
120

UNCOV
121
                    if (Buffer.isBuffer(keyValue)) {
×
UNCOV
122
                        return keyValue.toString("hex")
×
123
                    }
124

UNCOV
125
                    if (ObjectUtils.isObject(keyValue)) {
×
UNCOV
126
                        return JSON.stringify(keyValue)
×
127
                    }
128

UNCOV
129
                    return keyValue
×
130
                })
131
                .join("_") // todo: check partial
132

UNCOV
133
            const items = map.get(id)
×
UNCOV
134
            if (!items) {
×
UNCOV
135
                map.set(id, [rawResult])
×
136
            } else {
UNCOV
137
                items.push(rawResult)
×
138
            }
139
        }
UNCOV
140
        return map
×
141
    }
142

143
    /**
144
     * Transforms set of data results into single entity.
145
     */
146
    protected transformRawResultsGroup(
147
        rawResults: any[],
148
        alias: Alias,
149
    ): ObjectLiteral | undefined {
150
        // let hasColumns = false; // , hasEmbeddedColumns = false, hasParentColumns = false, hasParentEmbeddedColumns = false;
UNCOV
151
        let metadata = alias.metadata
×
152

UNCOV
153
        if (metadata.discriminatorColumn) {
×
UNCOV
154
            const discriminatorValues = rawResults.map(
×
155
                (result) =>
UNCOV
156
                    result[
×
157
                        this.buildAlias(
158
                            alias.name,
159
                            alias.metadata.discriminatorColumn!.databaseName,
160
                        )
161
                    ],
162
            )
UNCOV
163
            const discriminatorMetadata = metadata.childEntityMetadatas.find(
×
164
                (childEntityMetadata) => {
UNCOV
165
                    return (
×
166
                        typeof discriminatorValues.find(
167
                            (value) =>
UNCOV
168
                                value ===
×
169
                                childEntityMetadata.discriminatorValue,
170
                        ) !== "undefined"
171
                    )
172
                },
173
            )
UNCOV
174
            if (discriminatorMetadata) metadata = discriminatorMetadata
×
175
        }
UNCOV
176
        const entity: any = metadata.create(this.queryRunner, {
×
177
            fromDeserializer: true,
178
            pojo: this.pojo,
179
        })
180

181
        // get value from columns selections and put them into newly created entity
UNCOV
182
        const hasColumns = this.transformColumns(
×
183
            rawResults,
184
            alias,
185
            entity,
186
            metadata,
187
        )
UNCOV
188
        const hasRelations = this.transformJoins(
×
189
            rawResults,
190
            entity,
191
            alias,
192
            metadata,
193
        )
UNCOV
194
        const hasRelationIds = this.transformRelationIds(
×
195
            rawResults,
196
            alias,
197
            entity,
198
            metadata,
199
        )
UNCOV
200
        const hasRelationCounts = this.transformRelationCounts(
×
201
            rawResults,
202
            alias,
203
            entity,
204
        )
205

206
        // if we have at least one selected column then return this entity
207
        // since entity must have at least primary columns to be really selected and transformed into entity
UNCOV
208
        if (hasColumns) return entity
×
209

210
        // if we don't have any selected column we should not return entity,
211
        // except for the case when entity only contain a primary column as a relation to another entity
212
        // in this case its absolutely possible our entity to not have any columns except a single relation
UNCOV
213
        const hasOnlyVirtualPrimaryColumns = metadata.primaryColumns.every(
×
UNCOV
214
            (column) => column.isVirtual === true,
×
215
        ) // todo: create metadata.hasOnlyVirtualPrimaryColumns
UNCOV
216
        if (
×
217
            hasOnlyVirtualPrimaryColumns &&
×
218
            (hasRelations || hasRelationIds || hasRelationCounts)
219
        )
220
            return entity
×
221

UNCOV
222
        return undefined
×
223
    }
224

225
    // get value from columns selections and put them into object
226
    protected transformColumns(
227
        rawResults: any[],
228
        alias: Alias,
229
        entity: ObjectLiteral,
230
        metadata: EntityMetadata,
231
    ): boolean {
UNCOV
232
        let hasData = false
×
UNCOV
233
        const result = rawResults[0]
×
UNCOV
234
        for (const [key, column] of this.getColumnsToProcess(
×
235
            alias.name,
236
            metadata,
237
        )) {
UNCOV
238
            const value = result[key]
×
239

UNCOV
240
            if (value === undefined) continue
×
241
            // we don't mark it as has data because if we will have all nulls in our object - we don't need such object
UNCOV
242
            else if (value !== null && !column.isVirtualProperty) hasData = true
×
243

UNCOV
244
            column.setEntityValue(
×
245
                entity,
246
                this.driver.prepareHydratedValue(value, column),
247
            )
248
        }
UNCOV
249
        return hasData
×
250
    }
251

252
    /**
253
     * Transforms joined entities in the given raw results by a given alias and stores to the given (parent) entity
254
     */
255
    protected transformJoins(
256
        rawResults: any[],
257
        entity: ObjectLiteral,
258
        alias: Alias,
259
        metadata: EntityMetadata,
260
    ) {
UNCOV
261
        let hasData = false
×
262

263
        // let discriminatorValue: string = "";
264
        // if (metadata.discriminatorColumn)
265
        //     discriminatorValue = rawResults[0][this.buildAlias(alias.name, alias.metadata.discriminatorColumn!.databaseName)];
266

UNCOV
267
        for (const join of this.expressionMap.joinAttributes) {
×
268
            // todo: we have problem here - when inner joins are used without selects it still create empty array
269

270
            // skip joins without metadata
UNCOV
271
            if (!join.metadata) continue
×
272

273
            // if simple left or inner join was performed without selection then we don't need to do anything
UNCOV
274
            if (!join.isSelected) continue
×
275

276
            // this check need to avoid setting properties than not belong to entity when single table inheritance used. (todo: check if we still need it)
277
            // const metadata = metadata.childEntityMetadatas.find(childEntityMetadata => discriminatorValue === childEntityMetadata.discriminatorValue);
UNCOV
278
            if (
×
279
                join.relation &&
×
280
                !metadata.relations.find(
UNCOV
281
                    (relation) => relation === join.relation,
×
282
                )
283
            )
UNCOV
284
                continue
×
285

286
            // some checks to make sure this join is for current alias
UNCOV
287
            if (join.mapToProperty) {
×
UNCOV
288
                if (join.mapToPropertyParentAlias !== alias.name) continue
×
289
            } else {
UNCOV
290
                if (
×
291
                    !join.relation ||
×
292
                    join.parentAlias !== alias.name ||
293
                    join.relationPropertyPath !== join.relation!.propertyPath
294
                )
UNCOV
295
                    continue
×
296
            }
297

298
            // transform joined data into entities
UNCOV
299
            let result: any = this.transform(rawResults, join.alias)
×
UNCOV
300
            result = !join.isMany ? result[0] : result
×
UNCOV
301
            result = !join.isMany && result === undefined ? null : result // this is needed to make relations to return null when its joined but nothing was found in the database
×
302
            // if nothing was joined then simply continue
UNCOV
303
            if (result === undefined) continue
×
304

305
            // if join was mapped to some property then save result to that property
UNCOV
306
            if (join.mapToPropertyPropertyName) {
×
UNCOV
307
                entity[join.mapToPropertyPropertyName] = result // todo: fix embeds
×
308
            } else {
309
                // otherwise set to relation
UNCOV
310
                join.relation!.setEntityValue(entity, result)
×
311
            }
312

UNCOV
313
            hasData = true
×
314
        }
UNCOV
315
        return hasData
×
316
    }
317

318
    protected transformRelationIds(
319
        rawSqlResults: any[],
320
        alias: Alias,
321
        entity: ObjectLiteral,
322
        metadata: EntityMetadata,
323
    ): boolean {
UNCOV
324
        let hasData = false
×
UNCOV
325
        for (const [
×
326
            index,
327
            rawRelationIdResult,
328
        ] of this.rawRelationIdResults.entries()) {
UNCOV
329
            if (
×
330
                rawRelationIdResult.relationIdAttribute.parentAlias !==
331
                alias.name
332
            )
UNCOV
333
                continue
×
334

UNCOV
335
            const relation = rawRelationIdResult.relationIdAttribute.relation
×
UNCOV
336
            const valueMap = this.createValueMapFromJoinColumns(
×
337
                relation,
338
                rawRelationIdResult.relationIdAttribute.parentAlias,
339
                rawSqlResults,
340
            )
UNCOV
341
            if (valueMap === undefined || valueMap === null) {
×
342
                continue
×
343
            }
344

345
            // prepare common data for this call
UNCOV
346
            this.prepareDataForTransformRelationIds()
×
347

348
            // Extract idMaps from prepared data by hash
UNCOV
349
            const hash = this.hashEntityIds(relation, valueMap)
×
UNCOV
350
            const idMaps = this.relationIdMaps[index][hash] || []
×
351

352
            // Map data to properties
353
            const properties =
UNCOV
354
                rawRelationIdResult.relationIdAttribute.mapToPropertyPropertyPath.split(
×
355
                    ".",
356
                )
UNCOV
357
            const mapToProperty = (
×
358
                properties: string[],
359
                map: ObjectLiteral,
360
                value: any,
361
            ): any => {
UNCOV
362
                const property = properties.shift()
×
UNCOV
363
                if (property && properties.length === 0) {
×
UNCOV
364
                    map[property] = value
×
UNCOV
365
                    return map
×
366
                }
UNCOV
367
                if (property && properties.length > 0) {
×
UNCOV
368
                    mapToProperty(properties, map[property], value)
×
369
                } else {
370
                    return map
×
371
                }
372
            }
UNCOV
373
            if (relation.isOneToOne || relation.isManyToOne) {
×
UNCOV
374
                if (idMaps[0] !== undefined) {
×
UNCOV
375
                    mapToProperty(properties, entity, idMaps[0])
×
UNCOV
376
                    hasData = true
×
377
                }
378
            } else {
UNCOV
379
                mapToProperty(properties, entity, idMaps)
×
UNCOV
380
                hasData = hasData || idMaps.length > 0
×
381
            }
382
        }
383

UNCOV
384
        return hasData
×
385
    }
386

387
    protected transformRelationCounts(
388
        rawSqlResults: any[],
389
        alias: Alias,
390
        entity: ObjectLiteral,
391
    ): boolean {
UNCOV
392
        let hasData = false
×
UNCOV
393
        for (const rawRelationCountResult of this.rawRelationCountResults) {
×
UNCOV
394
            if (
×
395
                rawRelationCountResult.relationCountAttribute.parentAlias !==
396
                alias.name
397
            )
UNCOV
398
                continue
×
399
            const relation =
UNCOV
400
                rawRelationCountResult.relationCountAttribute.relation
×
401
            let referenceColumnName: string
402

UNCOV
403
            if (relation.isOneToMany) {
×
UNCOV
404
                referenceColumnName =
×
405
                    relation.inverseRelation!.joinColumns[0].referencedColumn!
406
                        .databaseName // todo: fix joinColumns[0]
407
            } else {
UNCOV
408
                referenceColumnName = relation.isOwning
×
409
                    ? relation.joinColumns[0].referencedColumn!.databaseName
410
                    : relation.inverseRelation!.joinColumns[0].referencedColumn!
411
                          .databaseName
412
            }
413

414
            const referenceColumnValue =
UNCOV
415
                rawSqlResults[0][
×
416
                    this.buildAlias(alias.name, referenceColumnName)
417
                ] // we use zero index since its grouped data // todo: selection with alias for entity columns wont work
UNCOV
418
            if (
×
419
                referenceColumnValue !== undefined &&
×
420
                referenceColumnValue !== null
421
            ) {
UNCOV
422
                entity[
×
423
                    rawRelationCountResult.relationCountAttribute.mapToPropertyPropertyName
424
                ] = 0
UNCOV
425
                for (const result of rawRelationCountResult.results) {
×
UNCOV
426
                    if (result["parentId"] !== referenceColumnValue) continue
×
UNCOV
427
                    entity[
×
428
                        rawRelationCountResult.relationCountAttribute.mapToPropertyPropertyName
429
                    ] = parseInt(result["cnt"])
UNCOV
430
                    hasData = true
×
431
                }
432
            }
433
        }
434

UNCOV
435
        return hasData
×
436
    }
437

438
    private getColumnsToProcess(aliasName: string, metadata: EntityMetadata) {
UNCOV
439
        let metadatas = this.columnsCache.get(aliasName)
×
UNCOV
440
        if (!metadatas) {
×
UNCOV
441
            metadatas = new Map()
×
UNCOV
442
            this.columnsCache.set(aliasName, metadatas)
×
443
        }
UNCOV
444
        let columns = metadatas.get(metadata)
×
UNCOV
445
        if (!columns) {
×
UNCOV
446
            columns = metadata.columns
×
447
                .filter(
448
                    (column) =>
UNCOV
449
                        !column.isVirtual &&
×
450
                        // if user does not selected the whole entity or he used partial selection and does not select this particular column
451
                        // then we don't add this column and its value into the entity
452
                        (this.selections.has(aliasName) ||
453
                            this.selections.has(
454
                                `${aliasName}.${column.propertyPath}`,
455
                            )) &&
456
                        // if table inheritance is used make sure this column is not child's column
457
                        !metadata.childEntityMetadatas.some(
458
                            (childMetadata) =>
UNCOV
459
                                childMetadata.target === column.target,
×
460
                        ),
461
                )
UNCOV
462
                .map((column) => [
×
463
                    this.buildAlias(aliasName, column.databaseName),
464
                    column,
465
                ])
UNCOV
466
            metadatas.set(metadata, columns)
×
467
        }
UNCOV
468
        return columns
×
469
    }
470

471
    private createValueMapFromJoinColumns(
472
        relation: RelationMetadata,
473
        parentAlias: string,
474
        rawSqlResults: any[],
475
    ): ObjectLiteral {
476
        let columns: ColumnMetadata[]
UNCOV
477
        if (relation.isManyToOne || relation.isOneToOneOwner) {
×
UNCOV
478
            columns = relation.entityMetadata.primaryColumns.map(
×
UNCOV
479
                (joinColumn) => joinColumn,
×
480
            )
UNCOV
481
        } else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
×
UNCOV
482
            columns = relation.inverseRelation!.joinColumns.map(
×
UNCOV
483
                (joinColumn) => joinColumn,
×
484
            )
485
        } else {
UNCOV
486
            if (relation.isOwning) {
×
UNCOV
487
                columns = relation.joinColumns.map((joinColumn) => joinColumn)
×
488
            } else {
UNCOV
489
                columns = relation.inverseRelation!.inverseJoinColumns.map(
×
UNCOV
490
                    (joinColumn) => joinColumn,
×
491
                )
492
            }
493
        }
UNCOV
494
        return columns.reduce((valueMap, column) => {
×
UNCOV
495
            for (const rawSqlResult of rawSqlResults) {
×
UNCOV
496
                if (relation.isManyToOne || relation.isOneToOneOwner) {
×
UNCOV
497
                    valueMap[column.databaseName] =
×
498
                        this.driver.prepareHydratedValue(
499
                            rawSqlResult[
500
                                this.buildAlias(
501
                                    parentAlias,
502
                                    column.databaseName,
503
                                )
504
                            ],
505
                            column,
506
                        )
507
                } else {
UNCOV
508
                    valueMap[column.databaseName] =
×
509
                        this.driver.prepareHydratedValue(
510
                            rawSqlResult[
511
                                this.buildAlias(
512
                                    parentAlias,
513
                                    column.referencedColumn!.databaseName,
514
                                )
515
                            ],
516
                            column.referencedColumn!,
517
                        )
518
                }
519
            }
UNCOV
520
            return valueMap
×
521
        }, {} as ObjectLiteral)
522
    }
523

524
    private extractEntityPrimaryIds(
525
        relation: RelationMetadata,
526
        relationIdRawResult: any,
527
    ) {
528
        let columns: ColumnMetadata[]
UNCOV
529
        if (relation.isManyToOne || relation.isOneToOneOwner) {
×
UNCOV
530
            columns = relation.entityMetadata.primaryColumns.map(
×
UNCOV
531
                (joinColumn) => joinColumn,
×
532
            )
UNCOV
533
        } else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
×
UNCOV
534
            columns = relation.inverseRelation!.joinColumns.map(
×
UNCOV
535
                (joinColumn) => joinColumn,
×
536
            )
537
        } else {
UNCOV
538
            if (relation.isOwning) {
×
UNCOV
539
                columns = relation.joinColumns.map((joinColumn) => joinColumn)
×
540
            } else {
UNCOV
541
                columns = relation.inverseRelation!.inverseJoinColumns.map(
×
UNCOV
542
                    (joinColumn) => joinColumn,
×
543
                )
544
            }
545
        }
UNCOV
546
        return columns.reduce((data, column) => {
×
UNCOV
547
            data[column.databaseName] = relationIdRawResult[column.databaseName]
×
UNCOV
548
            return data
×
549
        }, {} as ObjectLiteral)
550
    }
551

552
    /*private removeVirtualColumns(entity: ObjectLiteral, alias: Alias) {
553
        const virtualColumns = this.expressionMap.selects
554
            .filter(select => select.virtual)
555
            .map(select => select.selection.replace(alias.name + ".", ""));
556

557
        virtualColumns.forEach(virtualColumn => delete entity[virtualColumn]);
558
    }*/
559

560
    /** Prepare data to run #transformRelationIds, as a lot of result independent data is needed in every call */
561
    private prepareDataForTransformRelationIds() {
562
        // Return early if the relationIdMaps were already calculated
UNCOV
563
        if (this.relationIdMaps) {
×
UNCOV
564
            return
×
565
        }
566

567
        // Ensure this prepare function is only called once
UNCOV
568
        this.relationIdMaps = this.rawRelationIdResults.map(
×
569
            (rawRelationIdResult) => {
570
                const relation =
UNCOV
571
                    rawRelationIdResult.relationIdAttribute.relation
×
572

573
                // Calculate column metadata
574
                let columns: ColumnMetadata[]
UNCOV
575
                if (relation.isManyToOne || relation.isOneToOneOwner) {
×
UNCOV
576
                    columns = relation.joinColumns
×
UNCOV
577
                } else if (
×
578
                    relation.isOneToMany ||
×
579
                    relation.isOneToOneNotOwner
580
                ) {
UNCOV
581
                    columns = relation.inverseEntityMetadata.primaryColumns
×
582
                } else {
583
                    // ManyToMany
UNCOV
584
                    if (relation.isOwning) {
×
UNCOV
585
                        columns = relation.inverseJoinColumns
×
586
                    } else {
UNCOV
587
                        columns = relation.inverseRelation!.joinColumns
×
588
                    }
589
                }
590

591
                // Calculate the idMaps for the rawRelationIdResult
UNCOV
592
                return rawRelationIdResult.results.reduce((agg, result) => {
×
UNCOV
593
                    let idMap = columns.reduce((idMap, column) => {
×
UNCOV
594
                        let value = result[column.databaseName]
×
UNCOV
595
                        if (
×
596
                            relation.isOneToMany ||
×
597
                            relation.isOneToOneNotOwner
598
                        ) {
UNCOV
599
                            if (
×
600
                                column.isVirtual &&
×
601
                                column.referencedColumn &&
602
                                column.referencedColumn.propertyName !==
603
                                    column.propertyName
604
                            ) {
605
                                // if column is a relation
606
                                value =
×
607
                                    column.referencedColumn.createValueMap(
608
                                        value,
609
                                    )
610
                            }
611

UNCOV
612
                            return OrmUtils.mergeDeep(
×
613
                                idMap,
614
                                column.createValueMap(value),
615
                            )
616
                        }
UNCOV
617
                        if (
×
618
                            !column.isPrimary &&
×
619
                            column.referencedColumn!.referencedColumn
620
                        ) {
621
                            // if column is a relation
622
                            value =
×
623
                                column.referencedColumn!.referencedColumn!.createValueMap(
624
                                    value,
625
                                )
626
                        }
627

UNCOV
628
                        return OrmUtils.mergeDeep(
×
629
                            idMap,
630
                            column.referencedColumn!.createValueMap(value),
631
                        )
632
                    }, {} as ObjectLiteral)
633

UNCOV
634
                    if (
×
635
                        columns.length === 1 &&
×
636
                        !rawRelationIdResult.relationIdAttribute.disableMixedMap
637
                    ) {
UNCOV
638
                        if (
×
639
                            relation.isOneToMany ||
×
640
                            relation.isOneToOneNotOwner
641
                        ) {
UNCOV
642
                            idMap = columns[0].getEntityValue(idMap)
×
643
                        } else {
UNCOV
644
                            idMap =
×
645
                                columns[0].referencedColumn!.getEntityValue(
646
                                    idMap,
647
                                )
648
                        }
649
                    }
650

651
                    // If an idMap is found, set it in the aggregator under the correct hash
UNCOV
652
                    if (idMap !== undefined) {
×
UNCOV
653
                        const hash = this.hashEntityIds(relation, result)
×
654

UNCOV
655
                        if (agg[hash]) {
×
UNCOV
656
                            agg[hash].push(idMap)
×
657
                        } else {
UNCOV
658
                            agg[hash] = [idMap]
×
659
                        }
660
                    }
661

UNCOV
662
                    return agg
×
663
                }, {})
664
            },
665
        )
666
    }
667

668
    /**
669
     * Use a simple JSON.stringify to create a simple hash of the primary ids of an entity.
670
     * As this.extractEntityPrimaryIds always creates the primary id object in the same order, if the same relation is
671
     * given, a simple JSON.stringify should be enough to get a unique hash per entity!
672
     */
673
    private hashEntityIds(relation: RelationMetadata, data: ObjectLiteral) {
UNCOV
674
        const entityPrimaryIds = this.extractEntityPrimaryIds(relation, data)
×
UNCOV
675
        return JSON.stringify(entityPrimaryIds)
×
676
    }
677
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc