• 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

2.41
/src/query-builder/relation-id/RelationIdLoader.ts
1
import { RelationIdAttribute } from "./RelationIdAttribute"
2
import { DataSource } from "../../data-source/DataSource"
3
import { RelationIdLoadResult } from "./RelationIdLoadResult"
4
import { ObjectLiteral } from "../../common/ObjectLiteral"
5
import { QueryRunner } from "../../query-runner/QueryRunner"
6
import { DriverUtils } from "../../driver/DriverUtils"
1✔
7
import { TypeORMError } from "../../error/TypeORMError"
1✔
8
import { OrmUtils } from "../../util/OrmUtils"
1✔
9

10
export class RelationIdLoader {
1✔
11
    // -------------------------------------------------------------------------
12
    // Constructor
13
    // -------------------------------------------------------------------------
14

15
    constructor(
UNCOV
16
        protected connection: DataSource,
×
UNCOV
17
        protected queryRunner: QueryRunner | undefined,
×
UNCOV
18
        protected relationIdAttributes: RelationIdAttribute[],
×
19
    ) {}
20

21
    // -------------------------------------------------------------------------
22
    // Public Methods
23
    // -------------------------------------------------------------------------
24

25
    async load(rawEntities: any[]): Promise<RelationIdLoadResult[]> {
UNCOV
26
        const promises = this.relationIdAttributes.map(
×
27
            async (relationIdAttr) => {
UNCOV
28
                if (
×
29
                    relationIdAttr.relation.isManyToOne ||
×
30
                    relationIdAttr.relation.isOneToOneOwner
31
                ) {
32
                    // example: Post and Tag
33
                    // loadRelationIdAndMap("post.tagId", "post.tag")
34
                    // we expect it to load id of tag
35

UNCOV
36
                    if (relationIdAttr.queryBuilderFactory)
×
37
                        throw new TypeORMError(
×
38
                            "Additional condition can not be used with ManyToOne or OneToOne owner relations.",
39
                        )
40

UNCOV
41
                    const duplicates: { [duplicateKey: string]: boolean } = {}
×
UNCOV
42
                    const results = rawEntities
×
43
                        .map((rawEntity) => {
UNCOV
44
                            const result: ObjectLiteral = {}
×
UNCOV
45
                            const duplicateParts: Array<string> = []
×
UNCOV
46
                            relationIdAttr.relation.joinColumns.forEach(
×
47
                                (joinColumn) => {
UNCOV
48
                                    result[joinColumn.databaseName] =
×
49
                                        this.connection.driver.prepareHydratedValue(
50
                                            rawEntity[
51
                                                DriverUtils.buildAlias(
52
                                                    this.connection.driver,
53
                                                    undefined,
54
                                                    relationIdAttr.parentAlias,
55
                                                    joinColumn.databaseName,
56
                                                )
57
                                            ],
58
                                            joinColumn.referencedColumn!,
59
                                        )
UNCOV
60
                                    const duplicatePart = `${
×
61
                                        joinColumn.databaseName
62
                                    }:${result[joinColumn.databaseName]}`
UNCOV
63
                                    if (
×
64
                                        duplicateParts.indexOf(
65
                                            duplicatePart,
66
                                        ) === -1
67
                                    ) {
UNCOV
68
                                        duplicateParts.push(duplicatePart)
×
69
                                    }
70
                                },
71
                            )
72

UNCOV
73
                            relationIdAttr.relation.entityMetadata.primaryColumns.forEach(
×
74
                                (primaryColumn) => {
UNCOV
75
                                    result[primaryColumn.databaseName] =
×
76
                                        this.connection.driver.prepareHydratedValue(
77
                                            rawEntity[
78
                                                DriverUtils.buildAlias(
79
                                                    this.connection.driver,
80
                                                    undefined,
81
                                                    relationIdAttr.parentAlias,
82
                                                    primaryColumn.databaseName,
83
                                                )
84
                                            ],
85
                                            primaryColumn,
86
                                        )
UNCOV
87
                                    const duplicatePart = `${
×
88
                                        primaryColumn.databaseName
89
                                    }:${result[primaryColumn.databaseName]}`
UNCOV
90
                                    if (
×
91
                                        duplicateParts.indexOf(
92
                                            duplicatePart,
93
                                        ) === -1
94
                                    ) {
UNCOV
95
                                        duplicateParts.push(duplicatePart)
×
96
                                    }
97
                                },
98
                            )
99

UNCOV
100
                            duplicateParts.sort()
×
UNCOV
101
                            const duplicate = duplicateParts.join("::")
×
UNCOV
102
                            if (duplicates[duplicate]) {
×
UNCOV
103
                                return null
×
104
                            }
UNCOV
105
                            duplicates[duplicate] = true
×
UNCOV
106
                            return result
×
107
                        })
UNCOV
108
                        .filter((v) => v)
×
109

UNCOV
110
                    return {
×
111
                        relationIdAttribute: relationIdAttr,
112
                        results: results,
113
                    }
UNCOV
114
                } else if (
×
115
                    relationIdAttr.relation.isOneToMany ||
×
116
                    relationIdAttr.relation.isOneToOneNotOwner
117
                ) {
118
                    // example: Post and Category
119
                    // loadRelationIdAndMap("post.categoryIds", "post.categories")
120
                    // we expect it to load array of category ids
121

UNCOV
122
                    const relation = relationIdAttr.relation // "post.categories"
×
UNCOV
123
                    const joinColumns = relation.isOwning
×
124
                        ? relation.joinColumns
125
                        : relation.inverseRelation!.joinColumns
UNCOV
126
                    const table = relation.inverseEntityMetadata.target // category
×
UNCOV
127
                    const tableName = relation.inverseEntityMetadata.tableName // category
×
UNCOV
128
                    const tableAlias = relationIdAttr.alias || tableName // if condition (custom query builder factory) is set then relationIdAttr.alias defined
×
129

UNCOV
130
                    const duplicates: { [duplicateKey: string]: boolean } = {}
×
UNCOV
131
                    const parameters: ObjectLiteral = {}
×
UNCOV
132
                    const condition = rawEntities
×
133
                        .map((rawEntity, index) => {
UNCOV
134
                            const duplicateParts: Array<string> = []
×
UNCOV
135
                            const parameterParts: ObjectLiteral = {}
×
UNCOV
136
                            const queryPart = joinColumns
×
137
                                .map((joinColumn) => {
138
                                    const parameterName =
UNCOV
139
                                        joinColumn.databaseName + index
×
140
                                    const parameterValue =
UNCOV
141
                                        rawEntity[
×
142
                                            DriverUtils.buildAlias(
143
                                                this.connection.driver,
144
                                                undefined,
145
                                                relationIdAttr.parentAlias,
146
                                                joinColumn.referencedColumn!
147
                                                    .databaseName,
148
                                            )
149
                                        ]
UNCOV
150
                                    const duplicatePart = `${tableAlias}:${joinColumn.propertyPath}:${parameterValue}`
×
UNCOV
151
                                    if (
×
152
                                        duplicateParts.indexOf(
153
                                            duplicatePart,
154
                                        ) !== -1
155
                                    ) {
156
                                        return ""
×
157
                                    }
UNCOV
158
                                    duplicateParts.push(duplicatePart)
×
UNCOV
159
                                    parameterParts[parameterName] =
×
160
                                        parameterValue
UNCOV
161
                                    return (
×
162
                                        tableAlias +
163
                                        "." +
164
                                        joinColumn.propertyPath +
165
                                        " = :" +
166
                                        parameterName
167
                                    )
168
                                })
UNCOV
169
                                .filter((v) => v)
×
170
                                .join(" AND ")
UNCOV
171
                            duplicateParts.sort()
×
UNCOV
172
                            const duplicate = duplicateParts.join("::")
×
UNCOV
173
                            if (duplicates[duplicate]) {
×
UNCOV
174
                                return ""
×
175
                            }
UNCOV
176
                            duplicates[duplicate] = true
×
UNCOV
177
                            Object.assign(parameters, parameterParts)
×
UNCOV
178
                            return queryPart
×
179
                        })
UNCOV
180
                        .filter((v) => v)
×
UNCOV
181
                        .map((condition) => "(" + condition + ")")
×
182
                        .join(" OR ")
183

184
                    // ensure we won't perform redundant queries for joined data which was not found in selection
185
                    // example: if post.category was not found in db then no need to execute query for category.imageIds
UNCOV
186
                    if (!condition)
×
187
                        return {
×
188
                            relationIdAttribute: relationIdAttr,
189
                            results: [],
190
                        }
191

192
                    // generate query:
193
                    // SELECT category.id, category.postId FROM category category ON category.postId = :postId
UNCOV
194
                    const qb = this.connection.createQueryBuilder(
×
195
                        this.queryRunner,
196
                    )
197

UNCOV
198
                    const columns = OrmUtils.uniq(
×
199
                        [
200
                            ...joinColumns,
201
                            ...relation.inverseRelation!.entityMetadata
202
                                .primaryColumns,
203
                        ],
UNCOV
204
                        (column) => column.propertyPath,
×
205
                    )
206

UNCOV
207
                    columns.forEach((joinColumn) => {
×
UNCOV
208
                        qb.addSelect(
×
209
                            tableAlias + "." + joinColumn.propertyPath,
210
                            joinColumn.databaseName,
211
                        )
212
                    })
213

UNCOV
214
                    qb.from(table, tableAlias)
×
215
                        .where("(" + condition + ")") // need brackets because if we have additional condition and no brackets, it looks like (a = 1) OR (a = 2) AND b = 1, that is incorrect
216
                        .setParameters(parameters)
217

218
                    // apply condition (custom query builder factory)
UNCOV
219
                    if (relationIdAttr.queryBuilderFactory)
×
UNCOV
220
                        relationIdAttr.queryBuilderFactory(qb)
×
221

UNCOV
222
                    const results = await qb.getRawMany()
×
UNCOV
223
                    results.forEach((result) => {
×
UNCOV
224
                        joinColumns.forEach((column) => {
×
UNCOV
225
                            result[column.databaseName] =
×
226
                                this.connection.driver.prepareHydratedValue(
227
                                    result[column.databaseName],
228
                                    column.referencedColumn!,
229
                                )
230
                        })
UNCOV
231
                        relation.inverseRelation!.entityMetadata.primaryColumns.forEach(
×
232
                            (column) => {
UNCOV
233
                                result[column.databaseName] =
×
234
                                    this.connection.driver.prepareHydratedValue(
235
                                        result[column.databaseName],
236
                                        column,
237
                                    )
238
                            },
239
                        )
240
                    })
241

UNCOV
242
                    return {
×
243
                        relationIdAttribute: relationIdAttr,
244
                        results,
245
                    }
246
                } else {
247
                    // many-to-many
248
                    // example: Post and Category
249
                    // owner side: loadRelationIdAndMap("post.categoryIds", "post.categories")
250
                    // inverse side: loadRelationIdAndMap("category.postIds", "category.posts")
251
                    // we expect it to load array of post ids
252

UNCOV
253
                    const relation = relationIdAttr.relation
×
UNCOV
254
                    const joinColumns = relation.isOwning
×
255
                        ? relation.joinColumns
256
                        : relation.inverseRelation!.inverseJoinColumns
UNCOV
257
                    const inverseJoinColumns = relation.isOwning
×
258
                        ? relation.inverseJoinColumns
259
                        : relation.inverseRelation!.joinColumns
UNCOV
260
                    const junctionAlias = relationIdAttr.junctionAlias
×
261
                    const inverseSideTableName =
UNCOV
262
                        relationIdAttr.joinInverseSideMetadata.tableName
×
263
                    const inverseSideTableAlias =
UNCOV
264
                        relationIdAttr.alias || inverseSideTableName
×
UNCOV
265
                    const junctionTableName = relation.isOwning
×
266
                        ? relation.junctionEntityMetadata!.tableName
267
                        : relation.inverseRelation!.junctionEntityMetadata!
268
                              .tableName
269

UNCOV
270
                    const mappedColumns = rawEntities.map((rawEntity) => {
×
UNCOV
271
                        return joinColumns.reduce((map, joinColumn) => {
×
UNCOV
272
                            map[joinColumn.propertyPath] =
×
273
                                rawEntity[
274
                                    DriverUtils.buildAlias(
275
                                        this.connection.driver,
276
                                        undefined,
277
                                        relationIdAttr.parentAlias,
278
                                        joinColumn.referencedColumn!
279
                                            .databaseName,
280
                                    )
281
                                ]
UNCOV
282
                            return map
×
283
                        }, {} as ObjectLiteral)
284
                    })
285

286
                    // ensure we won't perform redundant queries for joined data which was not found in selection
287
                    // example: if post.category was not found in db then no need to execute query for category.imageIds
UNCOV
288
                    if (mappedColumns.length === 0)
×
289
                        return {
×
290
                            relationIdAttribute: relationIdAttr,
291
                            results: [],
292
                        }
293

UNCOV
294
                    const parameters: ObjectLiteral = {}
×
UNCOV
295
                    const duplicates: { [duplicateKey: string]: boolean } = {}
×
UNCOV
296
                    const joinColumnConditions = mappedColumns
×
297
                        .map((mappedColumn, index) => {
UNCOV
298
                            const duplicateParts: Array<string> = []
×
UNCOV
299
                            const parameterParts: ObjectLiteral = {}
×
UNCOV
300
                            const queryPart = Object.keys(mappedColumn)
×
301
                                .map((key) => {
UNCOV
302
                                    const parameterName = key + index
×
UNCOV
303
                                    const parameterValue = mappedColumn[key]
×
UNCOV
304
                                    const duplicatePart = `${junctionAlias}:${key}:${parameterValue}`
×
UNCOV
305
                                    if (
×
306
                                        duplicateParts.indexOf(
307
                                            duplicatePart,
308
                                        ) !== -1
309
                                    ) {
310
                                        return ""
×
311
                                    }
UNCOV
312
                                    duplicateParts.push(duplicatePart)
×
UNCOV
313
                                    parameterParts[parameterName] =
×
314
                                        parameterValue
UNCOV
315
                                    return (
×
316
                                        junctionAlias +
317
                                        "." +
318
                                        key +
319
                                        " = :" +
320
                                        parameterName
321
                                    )
322
                                })
UNCOV
323
                                .filter((s) => s)
×
324
                                .join(" AND ")
UNCOV
325
                            duplicateParts.sort()
×
UNCOV
326
                            const duplicate = duplicateParts.join("::")
×
UNCOV
327
                            if (duplicates[duplicate]) {
×
UNCOV
328
                                return ""
×
329
                            }
UNCOV
330
                            duplicates[duplicate] = true
×
UNCOV
331
                            Object.assign(parameters, parameterParts)
×
UNCOV
332
                            return queryPart
×
333
                        })
UNCOV
334
                        .filter((s) => s)
×
335

UNCOV
336
                    const inverseJoinColumnCondition = inverseJoinColumns
×
337
                        .map((joinColumn) => {
UNCOV
338
                            return (
×
339
                                junctionAlias +
340
                                "." +
341
                                joinColumn.propertyPath +
342
                                " = " +
343
                                inverseSideTableAlias +
344
                                "." +
345
                                joinColumn.referencedColumn!.propertyPath
346
                            )
347
                        })
348
                        .join(" AND ")
349

UNCOV
350
                    const condition = joinColumnConditions
×
351
                        .map((condition) => {
UNCOV
352
                            return (
×
353
                                "(" +
354
                                condition +
355
                                " AND " +
356
                                inverseJoinColumnCondition +
357
                                ")"
358
                            )
359
                        })
360
                        .join(" OR ")
361

UNCOV
362
                    const qb = this.connection.createQueryBuilder(
×
363
                        this.queryRunner,
364
                    )
365

UNCOV
366
                    inverseJoinColumns.forEach((joinColumn) => {
×
UNCOV
367
                        qb.addSelect(
×
368
                            junctionAlias + "." + joinColumn.propertyPath,
369
                            joinColumn.databaseName,
370
                        ).addOrderBy(
371
                            junctionAlias + "." + joinColumn.propertyPath,
372
                        )
373
                    })
374

UNCOV
375
                    joinColumns.forEach((joinColumn) => {
×
UNCOV
376
                        qb.addSelect(
×
377
                            junctionAlias + "." + joinColumn.propertyPath,
378
                            joinColumn.databaseName,
379
                        ).addOrderBy(
380
                            junctionAlias + "." + joinColumn.propertyPath,
381
                        )
382
                    })
383

UNCOV
384
                    qb.from(inverseSideTableName, inverseSideTableAlias)
×
385
                        .innerJoin(junctionTableName, junctionAlias, condition)
386
                        .setParameters(parameters)
387

388
                    // apply condition (custom query builder factory)
UNCOV
389
                    if (relationIdAttr.queryBuilderFactory)
×
UNCOV
390
                        relationIdAttr.queryBuilderFactory(qb)
×
391

UNCOV
392
                    const results = await qb.getRawMany()
×
UNCOV
393
                    results.forEach((result) => {
×
UNCOV
394
                        ;[...joinColumns, ...inverseJoinColumns].forEach(
×
395
                            (column) => {
UNCOV
396
                                result[column.databaseName] =
×
397
                                    this.connection.driver.prepareHydratedValue(
398
                                        result[column.databaseName],
399
                                        column.referencedColumn!,
400
                                    )
401
                            },
402
                        )
403
                    })
404

UNCOV
405
                    return {
×
406
                        relationIdAttribute: relationIdAttr,
407
                        results,
408
                    }
409
                }
410
            },
411
        )
412

UNCOV
413
        return Promise.all(promises)
×
414
    }
415
}
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