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

typeorm / typeorm / 15089093306

17 May 2025 09:03PM UTC coverage: 50.109% (-26.2%) from 76.346%
15089093306

Pull #11437

github

naorpeled
add comment about vector <#>
Pull Request #11437: feat(postgres): support vector data type

5836 of 12767 branches covered (45.71%)

Branch coverage included in aggregate %.

16 of 17 new or added lines in 4 files covered. (94.12%)

6283 existing lines in 64 files now uncovered.

12600 of 24025 relevant lines covered (52.45%)

28708.0 hits per line

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

83.33
/src/query-builder/RelationLoader.ts
1
import { DataSource } from "../data-source/DataSource"
2
import { ObjectLiteral } from "../common/ObjectLiteral"
3
import { QueryRunner } from "../query-runner/QueryRunner"
4
import { RelationMetadata } from "../metadata/RelationMetadata"
5
import { FindOptionsUtils } from "../find-options/FindOptionsUtils"
6✔
6
import { SelectQueryBuilder } from "./SelectQueryBuilder"
7

8
/**
9
 * Wraps entities and creates getters/setters for their relations
10
 * to be able to lazily load relations when accessing these relations.
11
 */
12
export class RelationLoader {
6✔
13
    // -------------------------------------------------------------------------
14
    // Constructor
15
    // -------------------------------------------------------------------------
16

17
    constructor(private connection: DataSource) {}
2,978✔
18

19
    // -------------------------------------------------------------------------
20
    // Public Methods
21
    // -------------------------------------------------------------------------
22

23
    /**
24
     * Loads relation data for the given entity and its relation.
25
     */
26
    load(
27
        relation: RelationMetadata,
28
        entityOrEntities: ObjectLiteral | ObjectLiteral[],
29
        queryRunner?: QueryRunner,
30
        queryBuilder?: SelectQueryBuilder<any>,
31
    ): Promise<any[]> {
32
        // todo: check all places where it uses non array
33
        if (queryRunner && queryRunner.isReleased) queryRunner = undefined // get new one if already closed
147✔
34
        if (relation.isManyToOne || relation.isOneToOneOwner) {
147✔
35
            return this.loadManyToOneOrOneToOneOwner(
85✔
36
                relation,
37
                entityOrEntities,
38
                queryRunner,
39
                queryBuilder,
40
            )
41
        } else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
62✔
42
            return this.loadOneToManyOrOneToOneNotOwner(
30✔
43
                relation,
44
                entityOrEntities,
45
                queryRunner,
46
                queryBuilder,
47
            )
48
        } else if (relation.isManyToManyOwner) {
32✔
49
            return this.loadManyToManyOwner(
28✔
50
                relation,
51
                entityOrEntities,
52
                queryRunner,
53
                queryBuilder,
54
            )
55
        } else {
56
            // many-to-many non owner
57
            return this.loadManyToManyNotOwner(
4✔
58
                relation,
59
                entityOrEntities,
60
                queryRunner,
61
                queryBuilder,
62
            )
63
        }
64
    }
65

66
    /**
67
     * Loads data for many-to-one and one-to-one owner relations.
68
     *
69
     * (ow) post.category<=>category.post
70
     * loaded: category from post
71
     * example: SELECT category.id AS category_id, category.name AS category_name FROM category category
72
     *              INNER JOIN post Post ON Post.category=category.id WHERE Post.id=1
73
     */
74
    loadManyToOneOrOneToOneOwner(
75
        relation: RelationMetadata,
76
        entityOrEntities: ObjectLiteral | ObjectLiteral[],
77
        queryRunner?: QueryRunner,
78
        queryBuilder?: SelectQueryBuilder<any>,
79
    ): Promise<any> {
80
        const entities = Array.isArray(entityOrEntities)
85!
81
            ? entityOrEntities
82
            : [entityOrEntities]
83

84
        const joinAliasName = relation.entityMetadata.name
85✔
85
        const qb = queryBuilder
85!
86
            ? queryBuilder
87
            : this.connection
88
                  .createQueryBuilder(queryRunner)
89
                  .select(relation.propertyName) // category
90
                  .from(relation.type, relation.propertyName)
91

92
        const mainAlias = qb.expressionMap.mainAlias!.name
85✔
93
        const columns = relation.entityMetadata.primaryColumns
85✔
94
        const joinColumns = relation.isOwning
85!
95
            ? relation.joinColumns
96
            : relation.inverseRelation!.joinColumns
97
        const conditions = joinColumns
85✔
98
            .map((joinColumn) => {
99
                return `${relation.entityMetadata.name}.${
85✔
100
                    joinColumn.propertyName
101
                } = ${mainAlias}.${joinColumn.referencedColumn!.propertyName}`
102
            })
103
            .join(" AND ")
104

105
        qb.innerJoin(
85✔
106
            relation.entityMetadata.target as Function,
107
            joinAliasName,
108
            conditions,
109
        )
110

111
        if (columns.length === 1) {
85!
112
            qb.where(
85✔
113
                `${joinAliasName}.${columns[0].propertyPath} IN (:...${
114
                    joinAliasName + "_" + columns[0].propertyName
115
                })`,
116
            )
117
            qb.setParameter(
85✔
118
                joinAliasName + "_" + columns[0].propertyName,
119
                entities.map((entity) =>
120
                    columns[0].getEntityValue(entity, true),
85✔
121
                ),
122
            )
123
        } else {
124
            const condition = entities
×
125
                .map((entity, entityIndex) => {
126
                    return columns
×
127
                        .map((column, columnIndex) => {
128
                            const paramName =
129
                                joinAliasName +
×
130
                                "_entity_" +
131
                                entityIndex +
132
                                "_" +
133
                                columnIndex
134
                            qb.setParameter(
×
135
                                paramName,
136
                                column.getEntityValue(entity, true),
137
                            )
138
                            return (
×
139
                                joinAliasName +
140
                                "." +
141
                                column.propertyPath +
142
                                " = :" +
143
                                paramName
144
                            )
145
                        })
146
                        .join(" AND ")
147
                })
148
                .map((condition) => "(" + condition + ")")
×
149
                .join(" OR ")
150
            qb.where(condition)
×
151
        }
152

153
        FindOptionsUtils.joinEagerRelations(
85✔
154
            qb,
155
            qb.alias,
156
            qb.expressionMap.mainAlias!.metadata,
157
        )
158

159
        return qb.getMany()
85✔
160
        // return qb.getOne(); todo: fix all usages
161
    }
162

163
    /**
164
     * Loads data for one-to-many and one-to-one not owner relations.
165
     *
166
     * SELECT post
167
     * FROM post post
168
     * WHERE post.[joinColumn.name] = entity[joinColumn.referencedColumn]
169
     */
170
    loadOneToManyOrOneToOneNotOwner(
171
        relation: RelationMetadata,
172
        entityOrEntities: ObjectLiteral | ObjectLiteral[],
173
        queryRunner?: QueryRunner,
174
        queryBuilder?: SelectQueryBuilder<any>,
175
    ): Promise<any> {
176
        const entities = Array.isArray(entityOrEntities)
30✔
177
            ? entityOrEntities
178
            : [entityOrEntities]
179
        const columns = relation.inverseRelation!.joinColumns
30✔
180
        const qb = queryBuilder
30✔
181
            ? queryBuilder
182
            : this.connection
183
                  .createQueryBuilder(queryRunner)
184
                  .select(relation.propertyName)
185
                  .from(
186
                      relation.inverseRelation!.entityMetadata.target,
187
                      relation.propertyName,
188
                  )
189

190
        const aliasName = qb.expressionMap.mainAlias!.name
30✔
191

192
        if (columns.length === 1) {
30!
193
            qb.where(
30✔
194
                `${aliasName}.${columns[0].propertyPath} IN (:...${
195
                    aliasName + "_" + columns[0].propertyName
196
                })`,
197
            )
198
            qb.setParameter(
30✔
199
                aliasName + "_" + columns[0].propertyName,
200
                entities.map((entity) =>
201
                    columns[0].referencedColumn!.getEntityValue(entity, true),
30✔
202
                ),
203
            )
204
        } else {
205
            const condition = entities
×
206
                .map((entity, entityIndex) => {
207
                    return columns
×
208
                        .map((column, columnIndex) => {
209
                            const paramName =
210
                                aliasName +
×
211
                                "_entity_" +
212
                                entityIndex +
213
                                "_" +
214
                                columnIndex
215
                            qb.setParameter(
×
216
                                paramName,
217
                                column.referencedColumn!.getEntityValue(
218
                                    entity,
219
                                    true,
220
                                ),
221
                            )
222
                            return (
×
223
                                aliasName +
224
                                "." +
225
                                column.propertyPath +
226
                                " = :" +
227
                                paramName
228
                            )
229
                        })
230
                        .join(" AND ")
231
                })
232
                .map((condition) => "(" + condition + ")")
×
233
                .join(" OR ")
234
            qb.where(condition)
×
235
        }
236

237
        FindOptionsUtils.joinEagerRelations(
30✔
238
            qb,
239
            qb.alias,
240
            qb.expressionMap.mainAlias!.metadata,
241
        )
242

243
        return qb.getMany()
30✔
244
        // return relation.isOneToMany ? qb.getMany() : qb.getOne(); todo: fix all usages
245
    }
246

247
    /**
248
     * Loads data for many-to-many owner relations.
249
     *
250
     * SELECT category
251
     * FROM category category
252
     * INNER JOIN post_categories post_categories
253
     * ON post_categories.postId = :postId
254
     * AND post_categories.categoryId = category.id
255
     */
256
    loadManyToManyOwner(
257
        relation: RelationMetadata,
258
        entityOrEntities: ObjectLiteral | ObjectLiteral[],
259
        queryRunner?: QueryRunner,
260
        queryBuilder?: SelectQueryBuilder<any>,
261
    ): Promise<any> {
262
        const entities = Array.isArray(entityOrEntities)
28✔
263
            ? entityOrEntities
264
            : [entityOrEntities]
265
        const parameters = relation.joinColumns.reduce(
28✔
266
            (parameters, joinColumn) => {
267
                parameters[joinColumn.propertyName] = entities.map((entity) =>
28✔
268
                    joinColumn.referencedColumn!.getEntityValue(entity, true),
28✔
269
                )
270
                return parameters
28✔
271
            },
272
            {} as ObjectLiteral,
273
        )
274

275
        const qb = queryBuilder
28✔
276
            ? queryBuilder
277
            : this.connection
278
                  .createQueryBuilder(queryRunner)
279
                  .select(relation.propertyName)
280
                  .from(relation.type, relation.propertyName)
281

282
        const mainAlias = qb.expressionMap.mainAlias!.name
28✔
283
        const joinAlias = relation.junctionEntityMetadata!.tableName
28✔
284
        const joinColumnConditions = relation.joinColumns.map((joinColumn) => {
28✔
285
            return `${joinAlias}.${joinColumn.propertyName} IN (:...${joinColumn.propertyName})`
28✔
286
        })
287
        const inverseJoinColumnConditions = relation.inverseJoinColumns.map(
28✔
288
            (inverseJoinColumn) => {
289
                return `${joinAlias}.${
28✔
290
                    inverseJoinColumn.propertyName
291
                }=${mainAlias}.${
292
                    inverseJoinColumn.referencedColumn!.propertyName
293
                }`
294
            },
295
        )
296

297
        qb.innerJoin(
28✔
298
            joinAlias,
299
            joinAlias,
300
            [...joinColumnConditions, ...inverseJoinColumnConditions].join(
301
                " AND ",
302
            ),
303
        ).setParameters(parameters)
304

305
        FindOptionsUtils.joinEagerRelations(
28✔
306
            qb,
307
            qb.alias,
308
            qb.expressionMap.mainAlias!.metadata,
309
        )
310

311
        return qb.getMany()
28✔
312
    }
313

314
    /**
315
     * Loads data for many-to-many not owner relations.
316
     *
317
     * SELECT post
318
     * FROM post post
319
     * INNER JOIN post_categories post_categories
320
     * ON post_categories.postId = post.id
321
     * AND post_categories.categoryId = post_categories.categoryId
322
     */
323
    loadManyToManyNotOwner(
324
        relation: RelationMetadata,
325
        entityOrEntities: ObjectLiteral | ObjectLiteral[],
326
        queryRunner?: QueryRunner,
327
        queryBuilder?: SelectQueryBuilder<any>,
328
    ): Promise<any> {
329
        const entities = Array.isArray(entityOrEntities)
4!
330
            ? entityOrEntities
331
            : [entityOrEntities]
332

333
        const qb = queryBuilder
4!
334
            ? queryBuilder
335
            : this.connection
336
                  .createQueryBuilder(queryRunner)
337
                  .select(relation.propertyName)
338
                  .from(relation.type, relation.propertyName)
339

340
        const mainAlias = qb.expressionMap.mainAlias!.name
4✔
341
        const joinAlias = relation.junctionEntityMetadata!.tableName
4✔
342
        const joinColumnConditions = relation.inverseRelation!.joinColumns.map(
4✔
343
            (joinColumn) => {
344
                return `${joinAlias}.${
4✔
345
                    joinColumn.propertyName
346
                } = ${mainAlias}.${joinColumn.referencedColumn!.propertyName}`
347
            },
348
        )
349
        const inverseJoinColumnConditions =
350
            relation.inverseRelation!.inverseJoinColumns.map(
4✔
351
                (inverseJoinColumn) => {
352
                    return `${joinAlias}.${inverseJoinColumn.propertyName} IN (:...${inverseJoinColumn.propertyName})`
4✔
353
                },
354
            )
355
        const parameters = relation.inverseRelation!.inverseJoinColumns.reduce(
4✔
356
            (parameters, joinColumn) => {
357
                parameters[joinColumn.propertyName] = entities.map((entity) =>
4✔
358
                    joinColumn.referencedColumn!.getEntityValue(entity, true),
4✔
359
                )
360
                return parameters
4✔
361
            },
362
            {} as ObjectLiteral,
363
        )
364

365
        qb.innerJoin(
4✔
366
            joinAlias,
367
            joinAlias,
368
            [...joinColumnConditions, ...inverseJoinColumnConditions].join(
369
                " AND ",
370
            ),
371
        ).setParameters(parameters)
372

373
        FindOptionsUtils.joinEagerRelations(
4✔
374
            qb,
375
            qb.alias,
376
            qb.expressionMap.mainAlias!.metadata,
377
        )
378

379
        return qb.getMany()
4✔
380
    }
381

382
    /**
383
     * Wraps given entity and creates getters/setters for its given relation
384
     * to be able to lazily load data when accessing this relation.
385
     */
386
    enableLazyLoad(
387
        relation: RelationMetadata,
388
        entity: ObjectLiteral,
389
        queryRunner?: QueryRunner,
390
    ) {
391
        const relationLoader = this
994✔
392
        const dataIndex = "__" + relation.propertyName + "__" // in what property of the entity loaded data will be stored
994✔
393
        const promiseIndex = "__promise_" + relation.propertyName + "__" // in what property of the entity loading promise will be stored
994✔
394
        const resolveIndex = "__has_" + relation.propertyName + "__" // indicates if relation data already was loaded or not, we need this flag if loaded data is empty
994✔
395

396
        const setData = (entity: ObjectLiteral, value: any) => {
994✔
397
            entity[dataIndex] = value
120✔
398
            entity[resolveIndex] = true
120✔
399
            delete entity[promiseIndex]
120✔
400
            return value
120✔
401
        }
402
        const setPromise = (entity: ObjectLiteral, value: Promise<any>) => {
994✔
403
            delete entity[resolveIndex]
120✔
404
            delete entity[dataIndex]
120✔
405
            entity[promiseIndex] = value
120✔
406
            value.then(
120✔
407
                // ensure different value is not assigned yet
408
                (result) =>
409
                    entity[promiseIndex] === value
120!
410
                        ? setData(entity, result)
411
                        : result,
412
            )
413
            return value
120✔
414
        }
415

416
        Object.defineProperty(entity, relation.propertyName, {
994✔
417
            get: function () {
418
                if (
104✔
419
                    this[resolveIndex] === true ||
199✔
420
                    this[dataIndex] !== undefined
421
                )
422
                    // if related data already was loaded then simply return it
423
                    return Promise.resolve(this[dataIndex])
33✔
424

425
                if (this[promiseIndex])
71!
426
                    // if related data is loading then return a promise relationLoader loads it
UNCOV
427
                    return this[promiseIndex]
×
428

429
                // nothing is loaded yet, load relation data and save it in the model once they are loaded
430
                const loader = relationLoader
71✔
431
                    .load(relation, this, queryRunner)
432
                    .then((result) =>
433
                        relation.isOneToOne || relation.isManyToOne
71✔
434
                            ? result.length === 0
43✔
435
                                ? null
436
                                : result[0]
437
                            : result,
438
                    )
439
                return setPromise(this, loader)
71✔
440
            },
441
            set: function (value: any | Promise<any>) {
442
                if (value instanceof Promise) {
49!
443
                    // if set data is a promise then wait for its resolve and save in the object
444
                    setPromise(this, value)
49✔
445
                } else {
446
                    // if its direct data set (non promise, probably not safe-typed)
UNCOV
447
                    setData(this, value)
×
448
                }
449
            },
450
            configurable: true,
451
            enumerable: false,
452
        })
453
    }
454
}
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