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

typeorm / typeorm / 19549987525

20 Nov 2025 08:11PM UTC coverage: 80.769% (+4.3%) from 76.433%
19549987525

push

github

web-flow
ci: run tests on commits to master and next (#11783)

Co-authored-by: Oleg "OSA413" Sokolov <OSA413@users.noreply.github.com>

26500 of 32174 branches covered (82.36%)

Branch coverage included in aggregate %.

91252 of 113615 relevant lines covered (80.32%)

88980.79 hits per line

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

70.96
/src/query-builder/RelationIdLoader.ts
1
import { RelationMetadata } from "../metadata/RelationMetadata"
26✔
2
import { ColumnMetadata } from "../metadata/ColumnMetadata"
26✔
3
import { DataSource } from "../data-source/DataSource"
26✔
4
import { ObjectLiteral } from "../common/ObjectLiteral"
26✔
5
import { SelectQueryBuilder } from "./SelectQueryBuilder"
26✔
6
import { DriverUtils } from "../driver/DriverUtils"
26✔
7
import { QueryRunner } from "../query-runner/QueryRunner"
26✔
8

26✔
9
/**
26✔
10
 * Loads relation ids for the given entities.
26✔
11
 */
26✔
12
export class RelationIdLoader {
26✔
13
    // -------------------------------------------------------------------------
26✔
14
    // Constructor
26✔
15
    // -------------------------------------------------------------------------
26✔
16

26✔
17
    constructor(
26✔
18
        private connection: DataSource,
14,661✔
19
        protected queryRunner?: QueryRunner | undefined,
14,661✔
20
    ) {}
14,661✔
21

26✔
22
    // -------------------------------------------------------------------------
26✔
23
    // Public Methods
26✔
24
    // -------------------------------------------------------------------------
26✔
25

26✔
26
    /**
26✔
27
     * Loads relation ids of the given entity or entities.
26✔
28
     */
26✔
29
    load(
26✔
30
        relation: RelationMetadata,
20✔
31
        entityOrEntities: ObjectLiteral | ObjectLiteral[],
20✔
32
        relatedEntityOrRelatedEntities?: ObjectLiteral | ObjectLiteral[],
20✔
33
    ): Promise<any[]> {
20✔
34
        const entities = Array.isArray(entityOrEntities)
20✔
35
            ? entityOrEntities
20✔
36
            : [entityOrEntities]
20!
37
        const relatedEntities = Array.isArray(relatedEntityOrRelatedEntities)
20✔
38
            ? relatedEntityOrRelatedEntities
20✔
39
            : relatedEntityOrRelatedEntities
20!
40
            ? [relatedEntityOrRelatedEntities]
×
41
            : undefined
×
42

20✔
43
        // load relation ids depend of relation type
20✔
44
        if (relation.isManyToMany) {
20✔
45
            return this.loadForManyToMany(relation, entities, relatedEntities)
8✔
46
        } else if (relation.isManyToOne || relation.isOneToOneOwner) {
20✔
47
            return this.loadForManyToOneAndOneToOneOwner(
4✔
48
                relation,
4✔
49
                entities,
4✔
50
                relatedEntities,
4✔
51
            )
4✔
52
        } else {
8✔
53
            // if (relation.isOneToMany || relation.isOneToOneNotOwner) {
8✔
54
            return this.loadForOneToManyAndOneToOneNotOwner(
8✔
55
                relation,
8✔
56
                entities,
8✔
57
                relatedEntities,
8✔
58
            )
8✔
59
        }
8✔
60
    }
20✔
61

26✔
62
    /**
26✔
63
     * Loads relation ids of the given entities and groups them into the object with parent and children.
26✔
64
     *
26✔
65
     * todo: extract this method?
26✔
66
     */
26✔
67
    async loadManyToManyRelationIdsAndGroup<
26✔
68
        E1 extends ObjectLiteral,
20✔
69
        E2 extends ObjectLiteral,
20✔
70
    >(
20✔
71
        relation: RelationMetadata,
20✔
72
        entitiesOrEntities: E1 | E1[],
20✔
73
        relatedEntityOrEntities?: E2 | E2[],
20✔
74
        queryBuilder?: SelectQueryBuilder<any>,
20✔
75
    ): Promise<{ entity: E1; related?: E2 | E2[] }[]> {
20✔
76
        // console.log("relation:", relation.propertyName);
20✔
77
        // console.log("entitiesOrEntities", entitiesOrEntities);
20✔
78
        const isMany = relation.isManyToMany || relation.isOneToMany
20✔
79
        const entities: E1[] = Array.isArray(entitiesOrEntities)
20✔
80
            ? entitiesOrEntities
20✔
81
            : [entitiesOrEntities]
20!
82

20✔
83
        if (!relatedEntityOrEntities) {
20✔
84
            relatedEntityOrEntities = await this.connection.relationLoader.load(
20✔
85
                relation,
20✔
86
                entitiesOrEntities,
20✔
87
                this.queryRunner,
20✔
88
                queryBuilder,
20✔
89
            )
20✔
90
            if (!relatedEntityOrEntities.length)
20✔
91
                return entities.map((entity) => ({
20!
92
                    entity: entity,
×
93
                    related: isMany ? [] : undefined,
×
94
                }))
×
95
        }
20✔
96
        // const relationIds = await this.load(relation, relatedEntityOrEntities!, entitiesOrEntities);
20✔
97
        const relationIds = await this.load(
20✔
98
            relation,
20✔
99
            entitiesOrEntities,
20✔
100
            relatedEntityOrEntities,
20✔
101
        )
20✔
102
        // console.log("entities", entities);
20✔
103
        // console.log("relatedEntityOrEntities", relatedEntityOrEntities);
20✔
104
        // console.log("relationIds", relationIds);
20✔
105

20✔
106
        const relatedEntities: E2[] = Array.isArray(relatedEntityOrEntities)
20✔
107
            ? relatedEntityOrEntities
20✔
108
            : [relatedEntityOrEntities!]
20!
109

20✔
110
        let columns: ColumnMetadata[] = [],
20✔
111
            inverseColumns: ColumnMetadata[] = []
20✔
112
        if (relation.isManyToManyOwner) {
20!
113
            columns = relation.junctionEntityMetadata!.inverseColumns.map(
8✔
114
                (column) => column.referencedColumn!,
8✔
115
            )
8✔
116
            inverseColumns = relation.junctionEntityMetadata!.ownerColumns.map(
8✔
117
                (column) => column.referencedColumn!,
8✔
118
            )
8✔
119
        } else if (relation.isManyToManyNotOwner) {
20!
120
            columns = relation.junctionEntityMetadata!.ownerColumns.map(
×
121
                (column) => column.referencedColumn!,
×
122
            )
×
123
            inverseColumns =
×
124
                relation.junctionEntityMetadata!.inverseColumns.map(
×
125
                    (column) => column.referencedColumn!,
×
126
                )
×
127
        } else if (relation.isManyToOne || relation.isOneToOneOwner) {
12!
128
            columns = relation.joinColumns.map(
4✔
129
                (column) => column.referencedColumn!,
4✔
130
            )
4✔
131
            inverseColumns = relation.entityMetadata.primaryColumns
4✔
132
        } else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
8!
133
            columns = relation.inverseRelation!.entityMetadata.primaryColumns
8✔
134
            inverseColumns = relation.inverseRelation!.joinColumns.map(
8✔
135
                (column) => column.referencedColumn!,
8✔
136
            )
8✔
137
        } else {
8!
138
        }
×
139

20✔
140
        return entities.map((entity) => {
20✔
141
            const group: { entity: E1; related?: E2 | E2[] } = {
28✔
142
                entity: entity,
28✔
143
                related: isMany ? [] : undefined,
28!
144
            }
28✔
145

28✔
146
            const entityRelationIds = relationIds.filter((relationId) => {
28✔
147
                return inverseColumns.every((column) => {
56✔
148
                    return column.compareEntityValue(
56✔
149
                        entity,
56✔
150
                        relationId[
56✔
151
                            column.entityMetadata.name +
56✔
152
                                "_" +
56✔
153
                                column.propertyAliasName
56✔
154
                        ],
56✔
155
                    )
56✔
156
                })
56✔
157
            })
28✔
158
            if (!entityRelationIds.length) return group
28!
159

24✔
160
            relatedEntities.forEach((relatedEntity) => {
24✔
161
                entityRelationIds.forEach((relationId) => {
36✔
162
                    const relatedEntityMatched = columns.every((column) => {
68✔
163
                        return column.compareEntityValue(
68✔
164
                            relatedEntity,
68✔
165
                            relationId[
68✔
166
                                DriverUtils.buildAlias(
68✔
167
                                    this.connection.driver,
68✔
168
                                    undefined,
68✔
169
                                    column.entityMetadata.name +
68✔
170
                                        "_" +
68✔
171
                                        relation.propertyPath.replace(
68✔
172
                                            ".",
68✔
173
                                            "_",
68✔
174
                                        ) +
68✔
175
                                        "_" +
68✔
176
                                        column.propertyPath.replace(".", "_"),
68✔
177
                                )
68✔
178
                            ],
68✔
179
                        )
68✔
180
                    })
68✔
181
                    if (relatedEntityMatched) {
68✔
182
                        if (isMany) {
36✔
183
                            ;(group.related as E2[]).push(relatedEntity)
28✔
184
                        } else {
36!
185
                            group.related = relatedEntity
8✔
186
                        }
8✔
187
                    }
36✔
188
                })
36✔
189
            })
24✔
190
            return group
24✔
191
        })
20✔
192
    }
20✔
193

26✔
194
    /**
26✔
195
     * Loads relation ids of the given entities and maps them into the given entity property.
26✔
196
     async loadManyToManyRelationIdsAndMap(
26✔
197
     relation: RelationMetadata,
26✔
198
     entityOrEntities: ObjectLiteral|ObjectLiteral[],
26✔
199
     mapToEntityOrEntities: ObjectLiteral|ObjectLiteral[],
26✔
200
     propertyName: string
26✔
201
     ): Promise<void> {
26✔
202
        const relationIds = await this.loadManyToManyRelationIds(relation, entityOrEntities, mapToEntityOrEntities);
26✔
203
        const mapToEntities = mapToEntityOrEntities instanceof Array ? mapToEntityOrEntities : [mapToEntityOrEntities];
26✔
204
        const junctionMetadata = relation.junctionEntityMetadata!;
26✔
205
        const mainAlias = junctionMetadata.name;
26✔
206
        const columns = relation.isOwning ? junctionMetadata.inverseColumns : junctionMetadata.ownerColumns;
26✔
207
        const inverseColumns = relation.isOwning ? junctionMetadata.ownerColumns : junctionMetadata.inverseColumns;
26✔
208
        mapToEntities.forEach(mapToEntity => {
26✔
209
            mapToEntity[propertyName] = [];
26✔
210
            relationIds.forEach(relationId => {
26✔
211
                const match = inverseColumns.every(column => {
26✔
212
                    return column.referencedColumn!.getEntityValue(mapToEntity) === relationId[mainAlias + "_" + column.propertyName];
26✔
213
                });
26✔
214
                if (match) {
26✔
215
                    if (columns.length === 1) {
26✔
216
                        mapToEntity[propertyName].push(relationId[mainAlias + "_" + columns[0].propertyName]);
26✔
217
                    } else {
26✔
218
                        const value = {};
26✔
219
                        columns.forEach(column => {
26✔
220
                            column.referencedColumn!.setEntityValue(value, relationId[mainAlias + "_" + column.propertyName]);
26✔
221
                        });
26✔
222
                        mapToEntity[propertyName].push(value);
26✔
223
                    }
26✔
224
                }
26✔
225
            });
26✔
226
        });
26✔
227
    }*/
26✔
228

26✔
229
    // -------------------------------------------------------------------------
26✔
230
    // Protected Methods
26✔
231
    // -------------------------------------------------------------------------
26✔
232

26✔
233
    /**
26✔
234
     * Loads relation ids for the many-to-many relation.
26✔
235
     */
26✔
236
    protected loadForManyToMany(
26✔
237
        relation: RelationMetadata,
8✔
238
        entities: ObjectLiteral[],
8✔
239
        relatedEntities?: ObjectLiteral[],
8✔
240
    ) {
8✔
241
        const junctionMetadata = relation.junctionEntityMetadata!
8✔
242
        const mainAlias = junctionMetadata.name
8✔
243
        const columns = relation.isOwning
8✔
244
            ? junctionMetadata.ownerColumns
8✔
245
            : junctionMetadata.inverseColumns
8!
246
        const inverseColumns = relation.isOwning
8✔
247
            ? junctionMetadata.inverseColumns
8✔
248
            : junctionMetadata.ownerColumns
8!
249
        const qb = this.connection.createQueryBuilder(this.queryRunner)
8✔
250

8✔
251
        // select all columns from junction table
8✔
252
        columns.forEach((column) => {
8✔
253
            const columnName = DriverUtils.buildAlias(
8✔
254
                this.connection.driver,
8✔
255
                undefined,
8✔
256
                column.referencedColumn!.entityMetadata.name +
8✔
257
                    "_" +
8✔
258
                    column.referencedColumn!.propertyPath.replace(".", "_"),
8✔
259
            )
8✔
260
            qb.addSelect(mainAlias + "." + column.propertyPath, columnName)
8✔
261
        })
8✔
262
        inverseColumns.forEach((column) => {
8✔
263
            const columnName = DriverUtils.buildAlias(
8✔
264
                this.connection.driver,
8✔
265
                undefined,
8✔
266
                column.referencedColumn!.entityMetadata.name +
8✔
267
                    "_" +
8✔
268
                    relation.propertyPath.replace(".", "_") +
8✔
269
                    "_" +
8✔
270
                    column.referencedColumn!.propertyPath.replace(".", "_"),
8✔
271
            )
8✔
272
            qb.addSelect(mainAlias + "." + column.propertyPath, columnName)
8✔
273
        })
8✔
274

8✔
275
        // add conditions for the given entities
8✔
276
        let condition1 = ""
8✔
277
        if (columns.length === 1) {
8✔
278
            const values = entities.map((entity) =>
8✔
279
                columns[0].referencedColumn!.getEntityValue(entity),
8✔
280
            )
8✔
281
            const areAllNumbers = values.every(
8✔
282
                (value) => typeof value === "number",
8✔
283
            )
8✔
284

8✔
285
            if (areAllNumbers) {
8✔
286
                condition1 = `${mainAlias}.${
8✔
287
                    columns[0].propertyPath
8✔
288
                } IN (${values.join(", ")})`
8✔
289
            } else {
8!
290
                qb.setParameter("values1", values)
×
291
                condition1 =
×
292
                    mainAlias +
×
293
                    "." +
×
294
                    columns[0].propertyPath +
×
295
                    " IN (:...values1)" // todo: use ANY for postgres
×
296
            }
×
297
        } else {
8!
298
            condition1 =
×
299
                "(" +
×
300
                entities
×
301
                    .map((entity, entityIndex) => {
×
302
                        return columns
×
303
                            .map((column) => {
×
304
                                const paramName =
×
305
                                    "entity1_" +
×
306
                                    entityIndex +
×
307
                                    "_" +
×
308
                                    column.propertyName
×
309
                                qb.setParameter(
×
310
                                    paramName,
×
311
                                    column.referencedColumn!.getEntityValue(
×
312
                                        entity,
×
313
                                    ),
×
314
                                )
×
315
                                return (
×
316
                                    mainAlias +
×
317
                                    "." +
×
318
                                    column.propertyPath +
×
319
                                    " = :" +
×
320
                                    paramName
×
321
                                )
×
322
                            })
×
323
                            .join(" AND ")
×
324
                    })
×
325
                    .map((condition) => "(" + condition + ")")
×
326
                    .join(" OR ") +
×
327
                ")"
×
328
        }
×
329

8✔
330
        // add conditions for the given inverse entities
8✔
331
        let condition2 = ""
8✔
332
        if (relatedEntities) {
8✔
333
            if (inverseColumns.length === 1) {
8✔
334
                const values = relatedEntities.map((entity) =>
8✔
335
                    inverseColumns[0].referencedColumn!.getEntityValue(entity),
8✔
336
                )
8✔
337
                const areAllNumbers = values.every(
8✔
338
                    (value) => typeof value === "number",
8✔
339
                )
8✔
340

8✔
341
                if (areAllNumbers) {
8✔
342
                    condition2 = `${mainAlias}.${
8✔
343
                        inverseColumns[0].propertyPath
8✔
344
                    } IN (${values.join(", ")})`
8✔
345
                } else {
8!
346
                    qb.setParameter("values2", values)
×
347
                    condition2 =
×
348
                        mainAlias +
×
349
                        "." +
×
350
                        inverseColumns[0].propertyPath +
×
351
                        " IN (:...values2)" // todo: use ANY for postgres
×
352
                }
×
353
            } else {
8!
354
                condition2 =
×
355
                    "(" +
×
356
                    relatedEntities
×
357
                        .map((entity, entityIndex) => {
×
358
                            return inverseColumns
×
359
                                .map((column) => {
×
360
                                    const paramName =
×
361
                                        "entity2_" +
×
362
                                        entityIndex +
×
363
                                        "_" +
×
364
                                        column.propertyName
×
365
                                    qb.setParameter(
×
366
                                        paramName,
×
367
                                        column.referencedColumn!.getEntityValue(
×
368
                                            entity,
×
369
                                        ),
×
370
                                    )
×
371
                                    return (
×
372
                                        mainAlias +
×
373
                                        "." +
×
374
                                        column.propertyPath +
×
375
                                        " = :" +
×
376
                                        paramName
×
377
                                    )
×
378
                                })
×
379
                                .join(" AND ")
×
380
                        })
×
381
                        .map((condition) => "(" + condition + ")")
×
382
                        .join(" OR ") +
×
383
                    ")"
×
384
            }
×
385
        }
8✔
386

8✔
387
        // qb.from(junctionMetadata.target, mainAlias)
8✔
388
        //     .where(condition1 + (condition2 ? " AND " + condition2 : ""));
8✔
389
        //
8✔
390
        // // execute query
8✔
391
        // const { values1, values2 } = qb.getParameters();
8✔
392
        // console.log(`I can do it`, { values1, values2 });
8✔
393
        // if (inverseColumns.length === 1 &&
8✔
394
        //     columns.length === 1 &&
8✔
395
        //     this.connection.driver instanceof SqliteDriver &&
8✔
396
        //     (values1.length + values2.length) > 500 &&
8✔
397
        //     values1.length === values2.length) {
8✔
398
        //     console.log(`I can do it`);
8✔
399
        //     return qb.getRawMany();
8✔
400
        //
8✔
401
        // } else {
8✔
402
        //     return qb.getRawMany();
8✔
403
        // }
8✔
404

8✔
405
        // execute query
8✔
406
        const condition = [condition1, condition2]
8✔
407
            .filter((v) => v.length > 0)
8✔
408
            .join(" AND ")
8✔
409
        return qb
8✔
410
            .from(junctionMetadata.target, mainAlias)
8✔
411
            .where(condition)
8✔
412
            .getRawMany()
8✔
413
    }
8✔
414

26✔
415
    /**
26✔
416
     * Loads relation ids for the many-to-one and one-to-one owner relations.
26✔
417
     */
26✔
418
    protected loadForManyToOneAndOneToOneOwner(
26✔
419
        relation: RelationMetadata,
4✔
420
        entities: ObjectLiteral[],
4✔
421
        relatedEntities?: ObjectLiteral[],
4✔
422
    ) {
4✔
423
        const mainAlias = relation.entityMetadata.targetName
4✔
424

4✔
425
        // console.log("entitiesx", entities);
4✔
426
        // console.log("relatedEntitiesx", relatedEntities);
4✔
427
        const hasAllJoinColumnsInEntity = relation.joinColumns.every(
4✔
428
            (joinColumn) => {
4✔
429
                return !!relation.entityMetadata.nonVirtualColumns.find(
4✔
430
                    (column) => column === joinColumn,
4✔
431
                )
4✔
432
            },
4✔
433
        )
4✔
434
        if (relatedEntities && hasAllJoinColumnsInEntity) {
4✔
435
            const relationIdMaps: ObjectLiteral[] = []
4✔
436
            entities.forEach((entity) => {
4✔
437
                const relationIdMap: ObjectLiteral = {}
8✔
438
                relation.entityMetadata.primaryColumns.forEach(
8✔
439
                    (primaryColumn) => {
8✔
440
                        const key =
8✔
441
                            primaryColumn.entityMetadata.name +
8✔
442
                            "_" +
8✔
443
                            primaryColumn.propertyPath.replace(".", "_")
8✔
444
                        relationIdMap[key] =
8✔
445
                            primaryColumn.getEntityValue(entity)
8✔
446
                    },
8✔
447
                )
8✔
448

8✔
449
                relatedEntities.forEach((relatedEntity) => {
8✔
450
                    relation.joinColumns.forEach((joinColumn) => {
8✔
451
                        const entityColumnValue =
8✔
452
                            joinColumn.getEntityValue(entity)
8✔
453
                        const relatedEntityColumnValue =
8✔
454
                            joinColumn.referencedColumn!.getEntityValue(
8✔
455
                                relatedEntity,
8✔
456
                            )
8✔
457
                        if (
8✔
458
                            entityColumnValue === undefined ||
8!
459
                            relatedEntityColumnValue === undefined
×
460
                        )
8✔
461
                            return
8✔
462

×
463
                        if (entityColumnValue === relatedEntityColumnValue) {
×
464
                            const key =
×
465
                                joinColumn.referencedColumn!.entityMetadata
×
466
                                    .name +
×
467
                                "_" +
×
468
                                relation.propertyPath.replace(".", "_") +
×
469
                                "_" +
×
470
                                joinColumn.referencedColumn!.propertyPath.replace(
×
471
                                    ".",
×
472
                                    "_",
×
473
                                )
×
474
                            relationIdMap[key] = relatedEntityColumnValue
×
475
                        }
×
476
                    })
8✔
477
                })
8✔
478
                if (
8✔
479
                    Object.keys(relationIdMap).length ===
8✔
480
                    relation.entityMetadata.primaryColumns.length +
8✔
481
                        relation.joinColumns.length
8✔
482
                ) {
8!
483
                    relationIdMaps.push(relationIdMap)
×
484
                }
×
485
            })
4✔
486
            // console.log("relationIdMap", relationIdMaps);
4✔
487
            // console.log("entities.length", entities.length);
4✔
488
            if (relationIdMaps.length === entities.length)
4✔
489
                return Promise.resolve(relationIdMaps)
4!
490
        }
4✔
491

4✔
492
        // select all columns we need
4✔
493
        const qb = this.connection.createQueryBuilder(this.queryRunner)
4✔
494
        relation.entityMetadata.primaryColumns.forEach((primaryColumn) => {
4✔
495
            const columnName = DriverUtils.buildAlias(
4✔
496
                this.connection.driver,
4✔
497
                undefined,
4✔
498
                primaryColumn.entityMetadata.name +
4✔
499
                    "_" +
4✔
500
                    primaryColumn.propertyPath.replace(".", "_"),
4✔
501
            )
4✔
502
            qb.addSelect(
4✔
503
                mainAlias + "." + primaryColumn.propertyPath,
4✔
504
                columnName,
4✔
505
            )
4✔
506
        })
4✔
507
        relation.joinColumns.forEach((column) => {
4✔
508
            const columnName = DriverUtils.buildAlias(
4✔
509
                this.connection.driver,
4✔
510
                undefined,
4✔
511
                column.referencedColumn!.entityMetadata.name +
4✔
512
                    "_" +
4✔
513
                    relation.propertyPath.replace(".", "_") +
4✔
514
                    "_" +
4✔
515
                    column.referencedColumn!.propertyPath.replace(".", "_"),
4✔
516
            )
4✔
517
            qb.addSelect(mainAlias + "." + column.propertyPath, columnName)
4✔
518
        })
4✔
519

4✔
520
        // add condition for entities
4✔
521
        let condition: string = ""
4✔
522
        if (relation.entityMetadata.primaryColumns.length === 1) {
4✔
523
            const values = entities.map((entity) =>
4✔
524
                relation.entityMetadata.primaryColumns[0].getEntityValue(
8✔
525
                    entity,
8✔
526
                ),
4✔
527
            )
4✔
528
            const areAllNumbers = values.every(
4✔
529
                (value) => typeof value === "number",
4✔
530
            )
4✔
531

4✔
532
            if (areAllNumbers) {
4✔
533
                condition = `${mainAlias}.${
4✔
534
                    relation.entityMetadata.primaryColumns[0].propertyPath
4✔
535
                } IN (${values.join(", ")})`
4✔
536
            } else {
4!
537
                qb.setParameter("values", values)
×
538
                condition =
×
539
                    mainAlias +
×
540
                    "." +
×
541
                    relation.entityMetadata.primaryColumns[0].propertyPath +
×
542
                    " IN (:...values)" // todo: use ANY for postgres
×
543
            }
×
544
        } else {
4!
545
            condition = entities
×
546
                .map((entity, entityIndex) => {
×
547
                    return relation.entityMetadata.primaryColumns
×
548
                        .map((column, columnIndex) => {
×
549
                            const paramName =
×
550
                                "entity" + entityIndex + "_" + columnIndex
×
551
                            qb.setParameter(
×
552
                                paramName,
×
553
                                column.getEntityValue(entity),
×
554
                            )
×
555
                            return (
×
556
                                mainAlias +
×
557
                                "." +
×
558
                                column.propertyPath +
×
559
                                " = :" +
×
560
                                paramName
×
561
                            )
×
562
                        })
×
563
                        .join(" AND ")
×
564
                })
×
565
                .map((condition) => "(" + condition + ")")
×
566
                .join(" OR ")
×
567
        }
×
568

4✔
569
        // execute query
4✔
570
        return qb
4✔
571
            .from(relation.entityMetadata.target, mainAlias)
4✔
572
            .where(condition)
4✔
573
            .getRawMany()
4✔
574
    }
4✔
575

26✔
576
    /**
26✔
577
     * Loads relation ids for the one-to-many and one-to-one not owner relations.
26✔
578
     */
26✔
579
    protected loadForOneToManyAndOneToOneNotOwner(
26✔
580
        relation: RelationMetadata,
8✔
581
        entities: ObjectLiteral[],
8✔
582
        relatedEntities?: ObjectLiteral[],
8✔
583
    ) {
8✔
584
        const originalRelation = relation
8✔
585
        relation = relation.inverseRelation!
8✔
586

8✔
587
        if (
8✔
588
            relation.entityMetadata.primaryColumns.length ===
8✔
589
            relation.joinColumns.length
8✔
590
        ) {
8✔
591
            const sameReferencedColumns =
8✔
592
                relation.entityMetadata.primaryColumns.every((column) => {
8✔
593
                    return relation.joinColumns.indexOf(column) !== -1
8✔
594
                })
8✔
595
            if (sameReferencedColumns) {
8!
596
                return Promise.resolve(
×
597
                    entities.map((entity) => {
×
598
                        const result: ObjectLiteral = {}
×
599
                        relation.joinColumns.forEach(function (joinColumn) {
×
600
                            const value =
×
601
                                joinColumn.referencedColumn!.getEntityValue(
×
602
                                    entity,
×
603
                                )
×
604
                            const joinColumnName =
×
605
                                joinColumn.referencedColumn!.entityMetadata
×
606
                                    .name +
×
607
                                "_" +
×
608
                                joinColumn.referencedColumn!.propertyPath.replace(
×
609
                                    ".",
×
610
                                    "_",
×
611
                                )
×
612
                            const primaryColumnName =
×
613
                                joinColumn.entityMetadata.name +
×
614
                                "_" +
×
615
                                originalRelation.propertyPath.replace(
×
616
                                    ".",
×
617
                                    "_",
×
618
                                ) +
×
619
                                "_" +
×
620
                                joinColumn.propertyPath.replace(".", "_")
×
621
                            result[joinColumnName] = value
×
622
                            result[primaryColumnName] = value
×
623
                        })
×
624
                        return result
×
625
                    }),
×
626
                )
×
627
            }
×
628
        }
8✔
629

8✔
630
        const mainAlias = relation.entityMetadata.targetName
8✔
631

8✔
632
        // select all columns we need
8✔
633
        const qb = this.connection.createQueryBuilder(this.queryRunner)
8✔
634
        relation.entityMetadata.primaryColumns.forEach((primaryColumn) => {
8✔
635
            const columnName = DriverUtils.buildAlias(
8✔
636
                this.connection.driver,
8✔
637
                undefined,
8✔
638
                primaryColumn.entityMetadata.name +
8✔
639
                    "_" +
8✔
640
                    originalRelation.propertyPath.replace(".", "_") +
8✔
641
                    "_" +
8✔
642
                    primaryColumn.propertyPath.replace(".", "_"),
8✔
643
            )
8✔
644
            qb.addSelect(
8✔
645
                mainAlias + "." + primaryColumn.propertyPath,
8✔
646
                columnName,
8✔
647
            )
8✔
648
        })
8✔
649
        relation.joinColumns.forEach((column) => {
8✔
650
            const columnName = DriverUtils.buildAlias(
8✔
651
                this.connection.driver,
8✔
652
                undefined,
8✔
653
                column.referencedColumn!.entityMetadata.name +
8✔
654
                    "_" +
8✔
655
                    column.referencedColumn!.propertyPath.replace(".", "_"),
8✔
656
            )
8✔
657
            qb.addSelect(mainAlias + "." + column.propertyPath, columnName)
8✔
658
        })
8✔
659

8✔
660
        // add condition for entities
8✔
661
        let condition: string = ""
8✔
662
        if (relation.joinColumns.length === 1) {
8✔
663
            const values = entities.map((entity) =>
8✔
664
                relation.joinColumns[0].referencedColumn!.getEntityValue(
12✔
665
                    entity,
12✔
666
                ),
8✔
667
            )
8✔
668
            const areAllNumbers = values.every(
8✔
669
                (value) => typeof value === "number",
8✔
670
            )
8✔
671

8✔
672
            if (areAllNumbers) {
8✔
673
                condition = `${mainAlias}.${
8✔
674
                    relation.joinColumns[0].propertyPath
8✔
675
                } IN (${values.join(", ")})`
8✔
676
            } else {
8!
677
                qb.setParameter("values", values)
×
678
                condition =
×
679
                    mainAlias +
×
680
                    "." +
×
681
                    relation.joinColumns[0].propertyPath +
×
682
                    " IN (:...values)" // todo: use ANY for postgres
×
683
            }
×
684
        } else {
8!
685
            condition = entities
×
686
                .map((entity, entityIndex) => {
×
687
                    return relation.joinColumns
×
688
                        .map((joinColumn, joinColumnIndex) => {
×
689
                            const paramName =
×
690
                                "entity" + entityIndex + "_" + joinColumnIndex
×
691
                            qb.setParameter(
×
692
                                paramName,
×
693
                                joinColumn.referencedColumn!.getEntityValue(
×
694
                                    entity,
×
695
                                ),
×
696
                            )
×
697
                            return (
×
698
                                mainAlias +
×
699
                                "." +
×
700
                                joinColumn.propertyPath +
×
701
                                " = :" +
×
702
                                paramName
×
703
                            )
×
704
                        })
×
705
                        .join(" AND ")
×
706
                })
×
707
                .map((condition) => "(" + condition + ")")
×
708
                .join(" OR ")
×
709
        }
×
710

8✔
711
        // execute query
8✔
712
        return qb
8✔
713
            .from(relation.entityMetadata.target, mainAlias)
8✔
714
            .where(condition)
8✔
715
            .getRawMany()
8✔
716
    }
8✔
717
}
26✔
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

© 2026 Coveralls, Inc