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

typeorm / typeorm / 16405830134

21 Jul 2025 12:22AM UTC coverage: 76.442% (+0.002%) from 76.44%
16405830134

Pull #11576

github

web-flow
Merge ccddce2d3 into d57fe3bd8
Pull Request #11576: fix(sqlserver): queries returning multiple record sets

9324 of 12885 branches covered (72.36%)

Branch coverage included in aggregate %.

55 of 78 new or added lines in 25 files covered. (70.51%)

2073 existing lines in 24 files now uncovered.

19016 of 24189 relevant lines covered (78.61%)

119538.23 hits per line

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

90.71
/src/query-builder/SelectQueryBuilder.ts
1
import { RawSqlResultsToEntityTransformer } from "./transformer/RawSqlResultsToEntityTransformer"
24✔
2
import { ObjectLiteral } from "../common/ObjectLiteral"
3
import { PessimisticLockTransactionRequiredError } from "../error/PessimisticLockTransactionRequiredError"
24✔
4
import { NoVersionOrUpdateDateColumnError } from "../error/NoVersionOrUpdateDateColumnError"
24✔
5
import { OptimisticLockVersionMismatchError } from "../error/OptimisticLockVersionMismatchError"
24✔
6
import { OptimisticLockCanNotBeUsedError } from "../error/OptimisticLockCanNotBeUsedError"
24✔
7
import { JoinAttribute } from "./JoinAttribute"
24✔
8
import { RelationIdAttribute } from "./relation-id/RelationIdAttribute"
24✔
9
import { RelationCountAttribute } from "./relation-count/RelationCountAttribute"
24✔
10
import { RelationIdLoader } from "./relation-id/RelationIdLoader"
24✔
11
import { RelationIdLoader as QueryStrategyRelationIdLoader } from "./RelationIdLoader"
24✔
12
import { RelationIdMetadataToAttributeTransformer } from "./relation-id/RelationIdMetadataToAttributeTransformer"
24✔
13
import { RelationCountLoader } from "./relation-count/RelationCountLoader"
24✔
14
import { RelationCountMetadataToAttributeTransformer } from "./relation-count/RelationCountMetadataToAttributeTransformer"
24✔
15
import { QueryBuilder } from "./QueryBuilder"
24✔
16
import { ReadStream } from "../platform/PlatformTools"
17
import { LockNotSupportedOnGivenDriverError } from "../error/LockNotSupportedOnGivenDriverError"
24✔
18
import { MysqlDriver } from "../driver/mysql/MysqlDriver"
19
import { SelectQuery } from "./SelectQuery"
20
import { EntityMetadata } from "../metadata/EntityMetadata"
21
import { ColumnMetadata } from "../metadata/ColumnMetadata"
22
import { OrderByCondition } from "../find-options/OrderByCondition"
23
import { QueryExpressionMap } from "./QueryExpressionMap"
24
import { EntityTarget } from "../common/EntityTarget"
25
import { QueryRunner } from "../query-runner/QueryRunner"
26
import { WhereExpressionBuilder } from "./WhereExpressionBuilder"
27
import { Brackets } from "./Brackets"
28
import { QueryResultCacheOptions } from "../cache/QueryResultCacheOptions"
29
import { OffsetWithoutLimitNotSupportedError } from "../error/OffsetWithoutLimitNotSupportedError"
24✔
30
import { SelectQueryBuilderOption } from "./SelectQueryBuilderOption"
31
import { ObjectUtils } from "../util/ObjectUtils"
24✔
32
import { DriverUtils } from "../driver/DriverUtils"
24✔
33
import { EntityNotFoundError } from "../error/EntityNotFoundError"
24✔
34
import { TypeORMError } from "../error"
24✔
35
import { FindManyOptions } from "../find-options/FindManyOptions"
36
import { FindOptionsSelect } from "../find-options/FindOptionsSelect"
37
import { RelationMetadata } from "../metadata/RelationMetadata"
38
import { FindOptionsOrder } from "../find-options/FindOptionsOrder"
39
import { FindOptionsWhere } from "../find-options/FindOptionsWhere"
40
import { FindOptionsUtils } from "../find-options/FindOptionsUtils"
24✔
41
import { FindOptionsRelations } from "../find-options/FindOptionsRelations"
42
import { OrmUtils } from "../util/OrmUtils"
24✔
43
import { EntityPropertyNotFoundError } from "../error/EntityPropertyNotFoundError"
24✔
44
import { AuroraMysqlDriver } from "../driver/aurora-mysql/AuroraMysqlDriver"
45
import { InstanceChecker } from "../util/InstanceChecker"
24✔
46
import { FindOperator } from "../find-options/FindOperator"
24✔
47
import { ApplyValueTransformers } from "../util/ApplyValueTransformers"
24✔
48
import { SqlServerDriver } from "../driver/sqlserver/SqlServerDriver"
49

50
/**
51
 * Allows to build complex sql queries in a fashion way and execute those queries.
52
 */
53
export class SelectQueryBuilder<Entity extends ObjectLiteral>
24✔
54
    extends QueryBuilder<Entity>
55
    implements WhereExpressionBuilder
56
{
57
    readonly "@instanceof" = Symbol.for("SelectQueryBuilder")
729,005✔
58

59
    protected findOptions: FindManyOptions = {}
729,005✔
60
    protected selects: string[] = []
729,005✔
61
    protected joins: {
729,005✔
62
        type: "inner" | "left"
63
        alias: string
64
        parentAlias: string
65
        relationMetadata: RelationMetadata
66
        select: boolean
67
        selection: FindOptionsSelect<any> | undefined
68
    }[] = []
69
    protected conditions: string = ""
729,005✔
70
    protected orderBys: {
729,005✔
71
        alias: string
72
        direction: "ASC" | "DESC"
73
        nulls?: "NULLS FIRST" | "NULLS LAST"
74
    }[] = []
75
    protected relationMetadatas: RelationMetadata[] = []
729,005✔
76

77
    // -------------------------------------------------------------------------
78
    // Public Implemented Methods
79
    // -------------------------------------------------------------------------
80

81
    /**
82
     * Gets generated SQL query without parameters being replaced.
83
     */
84
    getQuery(): string {
85
        let sql = this.createComment()
462,280✔
86
        sql += this.createCteExpression()
462,280✔
87
        sql += this.createSelectExpression()
462,280✔
88
        sql += this.createJoinExpression()
462,280✔
89
        sql += this.createWhereExpression()
462,280✔
90
        sql += this.createGroupByExpression()
462,280✔
91
        sql += this.createHavingExpression()
462,280✔
92
        sql += this.createOrderByExpression()
462,280✔
93
        sql += this.createLimitOffsetExpression()
462,280✔
94
        sql += this.createLockExpression()
462,274✔
95
        sql = sql.trim()
462,182✔
96
        if (this.expressionMap.subQuery) sql = "(" + sql + ")"
462,182✔
97
        return this.replacePropertyNamesForTheWholeQuery(sql)
462,182✔
98
    }
99

100
    // -------------------------------------------------------------------------
101
    // Public Methods
102
    // -------------------------------------------------------------------------
103

104
    setFindOptions(findOptions: FindManyOptions<Entity>) {
105
        this.findOptions = findOptions
162,243✔
106
        this.applyFindOptions()
162,243✔
107
        return this
161,950✔
108
    }
109

110
    /**
111
     * Creates a subquery - query that can be used inside other queries.
112
     */
113
    subQuery(): SelectQueryBuilder<any> {
114
        const qb = this.createQueryBuilder()
2,355✔
115
        qb.expressionMap.subQuery = true
2,355✔
116
        qb.parentQueryBuilder = this
2,355✔
117
        return qb
2,355✔
118
    }
119

120
    /**
121
     * Creates SELECT query.
122
     * Replaces all previous selections if they exist.
123
     */
124
    select(): this
125

126
    /**
127
     * Creates SELECT query.
128
     * Replaces all previous selections if they exist.
129
     */
130
    select(
131
        selection: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>,
132
        selectionAliasName?: string,
133
    ): this
134

135
    /**
136
     * Creates SELECT query and selects given data.
137
     * Replaces all previous selections if they exist.
138
     */
139
    select(selection: string, selectionAliasName?: string): this
140

141
    /**
142
     * Creates SELECT query and selects given data.
143
     * Replaces all previous selections if they exist.
144
     */
145
    select(selection: string[]): this
146

147
    /**
148
     * Creates SELECT query and selects given data.
149
     * Replaces all previous selections if they exist.
150
     */
151
    select(
152
        selection?:
153
            | string
154
            | string[]
155
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
156
        selectionAliasName?: string,
157
    ): SelectQueryBuilder<Entity> {
158
        this.expressionMap.queryType = "select"
452,670✔
159
        if (Array.isArray(selection)) {
452,670✔
160
            this.expressionMap.selects = selection.map((selection) => ({
8,326✔
161
                selection: selection,
162
            }))
163
        } else if (typeof selection === "function") {
444,939!
UNCOV
164
            const subQueryBuilder = selection(this.subQuery())
×
165
            this.setParameters(subQueryBuilder.getParameters())
×
166
            this.expressionMap.selects.push({
×
167
                selection: subQueryBuilder.getQuery(),
168
                aliasName: selectionAliasName,
169
            })
170
        } else if (selection) {
444,939✔
171
            this.expressionMap.selects = [
442,795✔
172
                { selection: selection, aliasName: selectionAliasName },
173
            ]
174
        }
175

176
        return this
452,670✔
177
    }
178

179
    /**
180
     * Adds new selection to the SELECT query.
181
     */
182
    addSelect(
183
        selection: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>,
184
        selectionAliasName?: string,
185
    ): this
186

187
    /**
188
     * Adds new selection to the SELECT query.
189
     */
190
    addSelect(selection: string, selectionAliasName?: string): this
191

192
    /**
193
     * Adds new selection to the SELECT query.
194
     */
195
    addSelect(selection: string[]): this
196

197
    /**
198
     * Adds new selection to the SELECT query.
199
     */
200
    addSelect(
201
        selection:
202
            | string
203
            | string[]
204
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
205
        selectionAliasName?: string,
206
    ): this {
207
        if (!selection) return this
2,093,955✔
208

209
        if (Array.isArray(selection)) {
2,087,900✔
210
            this.expressionMap.selects = this.expressionMap.selects.concat(
7,279✔
211
                selection.map((selection) => ({ selection: selection })),
9,879✔
212
            )
213
        } else if (typeof selection === "function") {
2,080,621✔
214
            const subQueryBuilder = selection(this.subQuery())
24✔
215
            this.setParameters(subQueryBuilder.getParameters())
24✔
216
            this.expressionMap.selects.push({
24✔
217
                selection: subQueryBuilder.getQuery(),
218
                aliasName: selectionAliasName,
219
            })
220
        } else if (selection) {
2,080,597✔
221
            this.expressionMap.selects.push({
2,080,597✔
222
                selection: selection,
223
                aliasName: selectionAliasName,
224
            })
225
        }
226

227
        return this
2,087,900✔
228
    }
229

230
    /**
231
     * Set max execution time.
232
     * @param milliseconds
233
     */
234
    maxExecutionTime(milliseconds: number): this {
UNCOV
235
        this.expressionMap.maxExecutionTime = milliseconds
×
236
        return this
×
237
    }
238

239
    /**
240
     * Sets whether the selection is DISTINCT.
241
     */
242
    distinct(distinct: boolean = true): this {
27✔
243
        this.expressionMap.selectDistinct = distinct
27✔
244
        return this
27✔
245
    }
246

247
    /**
248
     * Sets the distinct on clause for Postgres.
249
     */
250
    distinctOn(distinctOn: string[]): this {
251
        this.expressionMap.selectDistinctOn = distinctOn
12✔
252
        return this
12✔
253
    }
254

255
    fromDummy(): SelectQueryBuilder<any> {
256
        return this.from(
212✔
257
            this.connection.driver.dummyTableName ??
388✔
258
                "(SELECT 1 AS dummy_column)",
259
            "dummy_table",
260
        )
261
    }
262

263
    /**
264
     * Specifies FROM which entity's table select/update/delete will be executed.
265
     * Also sets a main string alias of the selection data.
266
     * Removes all previously set from-s.
267
     */
268
    from<T extends ObjectLiteral>(
269
        entityTarget: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>,
270
        aliasName: string,
271
    ): SelectQueryBuilder<T>
272

273
    /**
274
     * Specifies FROM which entity's table select/update/delete will be executed.
275
     * Also sets a main string alias of the selection data.
276
     * Removes all previously set from-s.
277
     */
278
    from<T extends ObjectLiteral>(
279
        entityTarget: EntityTarget<T>,
280
        aliasName: string,
281
    ): SelectQueryBuilder<T>
282

283
    /**
284
     * Specifies FROM which entity's table select/update/delete will be executed.
285
     * Also sets a main string alias of the selection data.
286
     * Removes all previously set from-s.
287
     */
288
    from<T extends ObjectLiteral>(
289
        entityTarget:
290
            | EntityTarget<T>
291
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
292
        aliasName: string,
293
    ): SelectQueryBuilder<T> {
294
        const mainAlias = this.createFromAlias(entityTarget, aliasName)
457,595✔
295
        this.expressionMap.setMainAlias(mainAlias)
457,595✔
296
        return this as any as SelectQueryBuilder<T>
457,595✔
297
    }
298

299
    /**
300
     * Specifies FROM which entity's table select/update/delete will be executed.
301
     * Also sets a main string alias of the selection data.
302
     */
303
    addFrom<T extends ObjectLiteral>(
304
        entityTarget: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>,
305
        aliasName: string,
306
    ): SelectQueryBuilder<T>
307

308
    /**
309
     * Specifies FROM which entity's table select/update/delete will be executed.
310
     * Also sets a main string alias of the selection data.
311
     */
312
    addFrom<T extends ObjectLiteral>(
313
        entityTarget: EntityTarget<T>,
314
        aliasName: string,
315
    ): SelectQueryBuilder<T>
316

317
    /**
318
     * Specifies FROM which entity's table select/update/delete will be executed.
319
     * Also sets a main string alias of the selection data.
320
     */
321
    addFrom<T extends ObjectLiteral>(
322
        entityTarget:
323
            | EntityTarget<T>
324
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
325
        aliasName: string,
326
    ): SelectQueryBuilder<T> {
327
        const alias = this.createFromAlias(entityTarget, aliasName)
38✔
328
        if (!this.expressionMap.mainAlias)
38!
UNCOV
329
            this.expressionMap.setMainAlias(alias)
×
330

331
        return this as any as SelectQueryBuilder<T>
38✔
332
    }
333

334
    /**
335
     * INNER JOINs (without selection) given subquery.
336
     * You also need to specify an alias of the joined data.
337
     * Optionally, you can add condition and parameters used in condition.
338
     */
339
    innerJoin(
340
        subQueryFactory: (
341
            qb: SelectQueryBuilder<any>,
342
        ) => SelectQueryBuilder<any>,
343
        alias: string,
344
        condition?: string,
345
        parameters?: ObjectLiteral,
346
    ): this
347

348
    /**
349
     * INNER JOINs (without selection) entity's property.
350
     * Given entity property should be a relation.
351
     * You also need to specify an alias of the joined data.
352
     * Optionally, you can add condition and parameters used in condition.
353
     */
354
    innerJoin(
355
        property: string,
356
        alias: string,
357
        condition?: string,
358
        parameters?: ObjectLiteral,
359
    ): this
360

361
    /**
362
     * INNER JOINs (without selection) given entity's table.
363
     * You also need to specify an alias of the joined data.
364
     * Optionally, you can add condition and parameters used in condition.
365
     */
366
    innerJoin(
367
        entity: Function | string,
368
        alias: string,
369
        condition?: string,
370
        parameters?: ObjectLiteral,
371
    ): this
372

373
    /**
374
     * INNER JOINs (without selection) given table.
375
     * You also need to specify an alias of the joined data.
376
     * Optionally, you can add condition and parameters used in condition.
377
     */
378
    innerJoin(
379
        tableName: string,
380
        alias: string,
381
        condition?: string,
382
        parameters?: ObjectLiteral,
383
    ): this
384

385
    /**
386
     * INNER JOINs (without selection).
387
     * You also need to specify an alias of the joined data.
388
     * Optionally, you can add condition and parameters used in condition.
389
     */
390
    innerJoin(
391
        entityOrProperty:
392
            | Function
393
            | string
394
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
395
        alias: string,
396
        condition?: string,
397
        parameters?: ObjectLiteral,
398
    ): this {
399
        this.join("INNER", entityOrProperty, alias, condition, parameters)
10,094✔
400
        return this
10,094✔
401
    }
402

403
    /**
404
     * LEFT JOINs (without selection) given subquery.
405
     * You also need to specify an alias of the joined data.
406
     * Optionally, you can add condition and parameters used in condition.
407
     */
408
    leftJoin(
409
        subQueryFactory: (
410
            qb: SelectQueryBuilder<any>,
411
        ) => SelectQueryBuilder<any>,
412
        alias: string,
413
        condition?: string,
414
        parameters?: ObjectLiteral,
415
    ): this
416

417
    /**
418
     * LEFT JOINs (without selection) entity's property.
419
     * Given entity property should be a relation.
420
     * You also need to specify an alias of the joined data.
421
     * Optionally, you can add condition and parameters used in condition.
422
     */
423
    leftJoin(
424
        property: string,
425
        alias: string,
426
        condition?: string,
427
        parameters?: ObjectLiteral,
428
    ): this
429

430
    /**
431
     * LEFT JOINs (without selection) entity's table.
432
     * You also need to specify an alias of the joined data.
433
     * Optionally, you can add condition and parameters used in condition.
434
     */
435
    leftJoin(
436
        entity: Function | string,
437
        alias: string,
438
        condition?: string,
439
        parameters?: ObjectLiteral,
440
    ): this
441

442
    /**
443
     * LEFT JOINs (without selection) given table.
444
     * You also need to specify an alias of the joined data.
445
     * Optionally, you can add condition and parameters used in condition.
446
     */
447
    leftJoin(
448
        tableName: string,
449
        alias: string,
450
        condition?: string,
451
        parameters?: ObjectLiteral,
452
    ): this
453

454
    /**
455
     * LEFT JOINs (without selection).
456
     * You also need to specify an alias of the joined data.
457
     * Optionally, you can add condition and parameters used in condition.
458
     */
459
    leftJoin(
460
        entityOrProperty:
461
            | Function
462
            | string
463
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
464
        alias: string,
465
        condition?: string,
466
        parameters?: ObjectLiteral,
467
    ): this {
468
        this.join("LEFT", entityOrProperty, alias, condition, parameters)
2,058,181✔
469
        return this
2,058,181✔
470
    }
471

472
    /**
473
     * INNER JOINs given subquery and adds all selection properties to SELECT..
474
     * You also need to specify an alias of the joined data.
475
     * Optionally, you can add condition and parameters used in condition.
476
     */
477
    innerJoinAndSelect(
478
        subQueryFactory: (
479
            qb: SelectQueryBuilder<any>,
480
        ) => SelectQueryBuilder<any>,
481
        alias: string,
482
        condition?: string,
483
        parameters?: ObjectLiteral,
484
    ): this
485

486
    /**
487
     * INNER JOINs entity's property and adds all selection properties to SELECT.
488
     * Given entity property should be a relation.
489
     * You also need to specify an alias of the joined data.
490
     * Optionally, you can add condition and parameters used in condition.
491
     */
492
    innerJoinAndSelect(
493
        property: string,
494
        alias: string,
495
        condition?: string,
496
        parameters?: ObjectLiteral,
497
    ): this
498

499
    /**
500
     * INNER JOINs entity and adds all selection properties to SELECT.
501
     * You also need to specify an alias of the joined data.
502
     * Optionally, you can add condition and parameters used in condition.
503
     */
504
    innerJoinAndSelect(
505
        entity: Function | string,
506
        alias: string,
507
        condition?: string,
508
        parameters?: ObjectLiteral,
509
    ): this
510

511
    /**
512
     * INNER JOINs table and adds all selection properties to SELECT.
513
     * You also need to specify an alias of the joined data.
514
     * Optionally, you can add condition and parameters used in condition.
515
     */
516
    innerJoinAndSelect(
517
        tableName: string,
518
        alias: string,
519
        condition?: string,
520
        parameters?: ObjectLiteral,
521
    ): this
522

523
    /**
524
     * INNER JOINs and adds all selection properties to SELECT.
525
     * You also need to specify an alias of the joined data.
526
     * Optionally, you can add condition and parameters used in condition.
527
     */
528
    innerJoinAndSelect(
529
        entityOrProperty:
530
            | Function
531
            | string
532
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
533
        alias: string,
534
        condition?: string,
535
        parameters?: ObjectLiteral,
536
    ): this {
537
        this.addSelect(alias)
636✔
538
        this.innerJoin(entityOrProperty, alias, condition, parameters)
636✔
539
        return this
636✔
540
    }
541

542
    /**
543
     * LEFT JOINs given subquery and adds all selection properties to SELECT..
544
     * You also need to specify an alias of the joined data.
545
     * Optionally, you can add condition and parameters used in condition.
546
     */
547
    leftJoinAndSelect(
548
        subQueryFactory: (
549
            qb: SelectQueryBuilder<any>,
550
        ) => SelectQueryBuilder<any>,
551
        alias: string,
552
        condition?: string,
553
        parameters?: ObjectLiteral,
554
    ): this
555

556
    /**
557
     * LEFT JOINs entity's property and adds all selection properties to SELECT.
558
     * Given entity property should be a relation.
559
     * You also need to specify an alias of the joined data.
560
     * Optionally, you can add condition and parameters used in condition.
561
     */
562
    leftJoinAndSelect(
563
        property: string,
564
        alias: string,
565
        condition?: string,
566
        parameters?: ObjectLiteral,
567
    ): this
568

569
    /**
570
     * LEFT JOINs entity and adds all selection properties to SELECT.
571
     * You also need to specify an alias of the joined data.
572
     * Optionally, you can add condition and parameters used in condition.
573
     */
574
    leftJoinAndSelect(
575
        entity: Function | string,
576
        alias: string,
577
        condition?: string,
578
        parameters?: ObjectLiteral,
579
    ): this
580

581
    /**
582
     * LEFT JOINs table and adds all selection properties to SELECT.
583
     * You also need to specify an alias of the joined data.
584
     * Optionally, you can add condition and parameters used in condition.
585
     */
586
    leftJoinAndSelect(
587
        tableName: string,
588
        alias: string,
589
        condition?: string,
590
        parameters?: ObjectLiteral,
591
    ): this
592

593
    /**
594
     * LEFT JOINs and adds all selection properties to SELECT.
595
     * You also need to specify an alias of the joined data.
596
     * Optionally, you can add condition and parameters used in condition.
597
     */
598
    leftJoinAndSelect(
599
        entityOrProperty:
600
            | Function
601
            | string
602
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
603
        alias: string,
604
        condition?: string,
605
        parameters?: ObjectLiteral,
606
    ): this {
607
        this.addSelect(alias)
2,054,427✔
608
        this.leftJoin(entityOrProperty, alias, condition, parameters)
2,054,427✔
609
        return this
2,054,427✔
610
    }
611

612
    /**
613
     * INNER JOINs given subquery, SELECTs the data returned by a join and MAPs all that data to some entity's property.
614
     * This is extremely useful when you want to select some data and map it to some virtual property.
615
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
616
     * Given entity property should be a relation.
617
     * You also need to specify an alias of the joined data.
618
     * Optionally, you can add condition and parameters used in condition.
619
     */
620
    innerJoinAndMapMany(
621
        mapToProperty: string,
622
        subQueryFactory: (
623
            qb: SelectQueryBuilder<any>,
624
        ) => SelectQueryBuilder<any>,
625
        alias: string,
626
        condition?: string,
627
        parameters?: ObjectLiteral,
628
    ): this
629

630
    /**
631
     * INNER JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
632
     * This is extremely useful when you want to select some data and map it to some virtual property.
633
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
634
     * Given entity property should be a relation.
635
     * You also need to specify an alias of the joined data.
636
     * Optionally, you can add condition and parameters used in condition.
637
     */
638
    innerJoinAndMapMany(
639
        mapToProperty: string,
640
        property: string,
641
        alias: string,
642
        condition?: string,
643
        parameters?: ObjectLiteral,
644
    ): this
645

646
    /**
647
     * INNER JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
648
     * This is extremely useful when you want to select some data and map it to some virtual property.
649
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
650
     * You also need to specify an alias of the joined data.
651
     * Optionally, you can add condition and parameters used in condition.
652
     */
653
    innerJoinAndMapMany(
654
        mapToProperty: string,
655
        entity: Function | string,
656
        alias: string,
657
        condition?: string,
658
        parameters?: ObjectLiteral,
659
    ): this
660

661
    /**
662
     * INNER JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
663
     * This is extremely useful when you want to select some data and map it to some virtual property.
664
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
665
     * You also need to specify an alias of the joined data.
666
     * Optionally, you can add condition and parameters used in condition.
667
     */
668
    innerJoinAndMapMany(
669
        mapToProperty: string,
670
        tableName: string,
671
        alias: string,
672
        condition?: string,
673
        parameters?: ObjectLiteral,
674
    ): this
675

676
    /**
677
     * INNER JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
678
     * This is extremely useful when you want to select some data and map it to some virtual property.
679
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
680
     * You also need to specify an alias of the joined data.
681
     * Optionally, you can add condition and parameters used in condition.
682
     */
683
    innerJoinAndMapMany(
684
        mapToProperty: string,
685
        entityOrProperty:
686
            | Function
687
            | string
688
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
689
        alias: string,
690
        condition?: string,
691
        parameters?: ObjectLiteral,
692
    ): this {
693
        this.addSelect(alias)
240✔
694
        this.join(
240✔
695
            "INNER",
696
            entityOrProperty,
697
            alias,
698
            condition,
699
            parameters,
700
            mapToProperty,
701
            true,
702
        )
703
        return this
240✔
704
    }
705

706
    /**
707
     * INNER JOINs given subquery, SELECTs the data returned by a join and MAPs all that data to some entity's property.
708
     * This is extremely useful when you want to select some data and map it to some virtual property.
709
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
710
     * Given entity property should be a relation.
711
     * You also need to specify an alias of the joined data.
712
     * Optionally, you can add condition and parameters used in condition.
713
     */
714
    innerJoinAndMapOne(
715
        mapToProperty: string,
716
        subQueryFactory: (
717
            qb: SelectQueryBuilder<any>,
718
        ) => SelectQueryBuilder<any>,
719
        alias: string,
720
        condition?: string,
721
        parameters?: ObjectLiteral,
722
        mapAsEntity?: Function | string,
723
    ): this
724

725
    /**
726
     * INNER JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
727
     * This is extremely useful when you want to select some data and map it to some virtual property.
728
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
729
     * Given entity property should be a relation.
730
     * You also need to specify an alias of the joined data.
731
     * Optionally, you can add condition and parameters used in condition.
732
     */
733
    innerJoinAndMapOne(
734
        mapToProperty: string,
735
        property: string,
736
        alias: string,
737
        condition?: string,
738
        parameters?: ObjectLiteral,
739
    ): this
740

741
    /**
742
     * INNER JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
743
     * This is extremely useful when you want to select some data and map it to some virtual property.
744
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
745
     * You also need to specify an alias of the joined data.
746
     * Optionally, you can add condition and parameters used in condition.
747
     */
748
    innerJoinAndMapOne(
749
        mapToProperty: string,
750
        entity: Function | string,
751
        alias: string,
752
        condition?: string,
753
        parameters?: ObjectLiteral,
754
    ): this
755

756
    /**
757
     * INNER JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
758
     * This is extremely useful when you want to select some data and map it to some virtual property.
759
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
760
     * You also need to specify an alias of the joined data.
761
     * Optionally, you can add condition and parameters used in condition.
762
     */
763
    innerJoinAndMapOne(
764
        mapToProperty: string,
765
        tableName: string,
766
        alias: string,
767
        condition?: string,
768
        parameters?: ObjectLiteral,
769
    ): this
770

771
    /**
772
     * INNER JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
773
     * This is extremely useful when you want to select some data and map it to some virtual property.
774
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
775
     * You also need to specify an alias of the joined data.
776
     * Optionally, you can add condition and parameters used in condition.
777
     */
778
    innerJoinAndMapOne(
779
        mapToProperty: string,
780
        entityOrProperty:
781
            | Function
782
            | string
783
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
784
        alias: string,
785
        condition?: string,
786
        parameters?: ObjectLiteral,
787
        mapAsEntity?: Function | string,
788
    ): this {
789
        this.addSelect(alias)
168✔
790
        this.join(
168✔
791
            "INNER",
792
            entityOrProperty,
793
            alias,
794
            condition,
795
            parameters,
796
            mapToProperty,
797
            false,
798
            mapAsEntity,
799
        )
800
        return this
168✔
801
    }
802

803
    /**
804
     * LEFT JOINs given subquery, SELECTs the data returned by a join and MAPs all that data to some entity's property.
805
     * This is extremely useful when you want to select some data and map it to some virtual property.
806
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
807
     * Given entity property should be a relation.
808
     * You also need to specify an alias of the joined data.
809
     * Optionally, you can add condition and parameters used in condition.
810
     */
811
    leftJoinAndMapMany(
812
        mapToProperty: string,
813
        subQueryFactory: (
814
            qb: SelectQueryBuilder<any>,
815
        ) => SelectQueryBuilder<any>,
816
        alias: string,
817
        condition?: string,
818
        parameters?: ObjectLiteral,
819
    ): this
820

821
    /**
822
     * LEFT JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
823
     * This is extremely useful when you want to select some data and map it to some virtual property.
824
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
825
     * Given entity property should be a relation.
826
     * You also need to specify an alias of the joined data.
827
     * Optionally, you can add condition and parameters used in condition.
828
     */
829
    leftJoinAndMapMany(
830
        mapToProperty: string,
831
        property: string,
832
        alias: string,
833
        condition?: string,
834
        parameters?: ObjectLiteral,
835
    ): this
836

837
    /**
838
     * LEFT JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
839
     * This is extremely useful when you want to select some data and map it to some virtual property.
840
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
841
     * You also need to specify an alias of the joined data.
842
     * Optionally, you can add condition and parameters used in condition.
843
     */
844
    leftJoinAndMapMany(
845
        mapToProperty: string,
846
        entity: Function | string,
847
        alias: string,
848
        condition?: string,
849
        parameters?: ObjectLiteral,
850
    ): this
851

852
    /**
853
     * LEFT JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
854
     * This is extremely useful when you want to select some data and map it to some virtual property.
855
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
856
     * You also need to specify an alias of the joined data.
857
     * Optionally, you can add condition and parameters used in condition.
858
     */
859
    leftJoinAndMapMany(
860
        mapToProperty: string,
861
        tableName: string,
862
        alias: string,
863
        condition?: string,
864
        parameters?: ObjectLiteral,
865
    ): this
866

867
    /**
868
     * LEFT JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
869
     * This is extremely useful when you want to select some data and map it to some virtual property.
870
     * It will assume that there are multiple rows of selecting data, and mapped result will be an array.
871
     * You also need to specify an alias of the joined data.
872
     * Optionally, you can add condition and parameters used in condition.
873
     */
874
    leftJoinAndMapMany(
875
        mapToProperty: string,
876
        entityOrProperty:
877
            | Function
878
            | string
879
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
880
        alias: string,
881
        condition?: string,
882
        parameters?: ObjectLiteral,
883
    ): this {
884
        this.addSelect(alias)
362✔
885
        this.join(
362✔
886
            "LEFT",
887
            entityOrProperty,
888
            alias,
889
            condition,
890
            parameters,
891
            mapToProperty,
892
            true,
893
        )
894
        return this
362✔
895
    }
896

897
    /**
898
     * LEFT JOINs given subquery, SELECTs the data returned by a join and MAPs all that data to some entity's property.
899
     * This is extremely useful when you want to select some data and map it to some virtual property.
900
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
901
     * Given entity property should be a relation.
902
     * You also need to specify an alias of the joined data.
903
     * Optionally, you can add condition and parameters used in condition.
904
     */
905
    leftJoinAndMapOne(
906
        mapToProperty: string,
907
        subQueryFactory: (
908
            qb: SelectQueryBuilder<any>,
909
        ) => SelectQueryBuilder<any>,
910
        alias: string,
911
        condition?: string,
912
        parameters?: ObjectLiteral,
913
        mapAsEntity?: Function | string,
914
    ): this
915

916
    /**
917
     * LEFT JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
918
     * This is extremely useful when you want to select some data and map it to some virtual property.
919
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
920
     * Given entity property should be a relation.
921
     * You also need to specify an alias of the joined data.
922
     * Optionally, you can add condition and parameters used in condition.
923
     */
924
    leftJoinAndMapOne(
925
        mapToProperty: string,
926
        property: string,
927
        alias: string,
928
        condition?: string,
929
        parameters?: ObjectLiteral,
930
    ): this
931

932
    /**
933
     * LEFT JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
934
     * This is extremely useful when you want to select some data and map it to some virtual property.
935
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
936
     * You also need to specify an alias of the joined data.
937
     * Optionally, you can add condition and parameters used in condition.
938
     */
939
    leftJoinAndMapOne(
940
        mapToProperty: string,
941
        entity: Function | string,
942
        alias: string,
943
        condition?: string,
944
        parameters?: ObjectLiteral,
945
    ): this
946

947
    /**
948
     * LEFT JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
949
     * This is extremely useful when you want to select some data and map it to some virtual property.
950
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
951
     * You also need to specify an alias of the joined data.
952
     * Optionally, you can add condition and parameters used in condition.
953
     */
954
    leftJoinAndMapOne(
955
        mapToProperty: string,
956
        tableName: string,
957
        alias: string,
958
        condition?: string,
959
        parameters?: ObjectLiteral,
960
    ): this
961

962
    /**
963
     * LEFT JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
964
     * This is extremely useful when you want to select some data and map it to some virtual property.
965
     * It will assume that there is a single row of selecting data, and mapped result will be a single selected value.
966
     * You also need to specify an alias of the joined data.
967
     * Optionally, you can add condition and parameters used in condition.
968
     */
969
    leftJoinAndMapOne(
970
        mapToProperty: string,
971
        entityOrProperty:
972
            | Function
973
            | string
974
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
975
        alias: string,
976
        condition?: string,
977
        parameters?: ObjectLiteral,
978
        mapAsEntity?: Function | string,
979
    ): this {
980
        this.addSelect(alias)
242✔
981
        this.join(
242✔
982
            "LEFT",
983
            entityOrProperty,
984
            alias,
985
            condition,
986
            parameters,
987
            mapToProperty,
988
            false,
989
            mapAsEntity,
990
        )
991
        return this
242✔
992
    }
993

994
    /**
995
     */
996
    // selectAndMap(mapToProperty: string, property: string, aliasName: string, qbFactory: ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>)): this;
997

998
    /**
999
     */
1000
    // selectAndMap(mapToProperty: string, entity: Function|string, aliasName: string, qbFactory: ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>)): this;
1001

1002
    /**
1003
     */
1004
    // selectAndMap(mapToProperty: string, tableName: string, aliasName: string, qbFactory: ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>)): this;
1005

1006
    /**
1007
     */
1008
    // selectAndMap(mapToProperty: string, entityOrProperty: Function|string, aliasName: string, qbFactory: ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>)): this {
1009
    //     const select = new SelectAttribute(this.expressionMap);
1010
    //     select.mapToProperty = mapToProperty;
1011
    //     select.entityOrProperty = entityOrProperty;
1012
    //     select.aliasName = aliasName;
1013
    //     select.qbFactory = qbFactory;
1014
    //     return this;
1015
    // }
1016

1017
    /**
1018
     * LEFT JOINs relation id and maps it into some entity's property.
1019
     * Optionally, you can add condition and parameters used in condition.
1020
     */
1021
    loadRelationIdAndMap(
1022
        mapToProperty: string,
1023
        relationName: string,
1024
        options?: { disableMixedMap?: boolean },
1025
    ): this
1026

1027
    /**
1028
     * LEFT JOINs relation id and maps it into some entity's property.
1029
     * Optionally, you can add condition and parameters used in condition.
1030
     */
1031
    loadRelationIdAndMap(
1032
        mapToProperty: string,
1033
        relationName: string,
1034
        alias: string,
1035
        queryBuilderFactory: (
1036
            qb: SelectQueryBuilder<any>,
1037
        ) => SelectQueryBuilder<any>,
1038
    ): this
1039

1040
    /**
1041
     * LEFT JOINs relation id and maps it into some entity's property.
1042
     * Optionally, you can add condition and parameters used in condition.
1043
     */
1044
    loadRelationIdAndMap(
1045
        mapToProperty: string,
1046
        relationName: string,
1047
        aliasNameOrOptions?: string | { disableMixedMap?: boolean },
1048
        queryBuilderFactory?: (
1049
            qb: SelectQueryBuilder<any>,
1050
        ) => SelectQueryBuilder<any>,
1051
    ): this {
1052
        const relationIdAttribute = new RelationIdAttribute(this.expressionMap)
28,358✔
1053
        relationIdAttribute.mapToProperty = mapToProperty
28,358✔
1054
        relationIdAttribute.relationName = relationName
28,358✔
1055
        if (typeof aliasNameOrOptions === "string")
28,358✔
1056
            relationIdAttribute.alias = aliasNameOrOptions
456✔
1057
        if (
28,358✔
1058
            typeof aliasNameOrOptions === "object" &&
53,692✔
1059
            (aliasNameOrOptions as any).disableMixedMap
1060
        )
1061
            relationIdAttribute.disableMixedMap = true
25,334✔
1062

1063
        relationIdAttribute.queryBuilderFactory = queryBuilderFactory
28,358✔
1064
        this.expressionMap.relationIdAttributes.push(relationIdAttribute)
28,358✔
1065

1066
        if (relationIdAttribute.relation.junctionEntityMetadata) {
28,358✔
1067
            this.expressionMap.createAlias({
14,514✔
1068
                type: "other",
1069
                name: relationIdAttribute.junctionAlias,
1070
                metadata: relationIdAttribute.relation.junctionEntityMetadata,
1071
            })
1072
        }
1073
        return this
28,358✔
1074
    }
1075

1076
    /**
1077
     * Counts number of entities of entity's relation and maps the value into some entity's property.
1078
     * Optionally, you can add condition and parameters used in condition.
1079
     */
1080
    loadRelationCountAndMap(
1081
        mapToProperty: string,
1082
        relationName: string,
1083
        aliasName?: string,
1084
        queryBuilderFactory?: (
1085
            qb: SelectQueryBuilder<any>,
1086
        ) => SelectQueryBuilder<any>,
1087
    ): this {
1088
        const relationCountAttribute = new RelationCountAttribute(
1,296✔
1089
            this.expressionMap,
1090
        )
1091
        relationCountAttribute.mapToProperty = mapToProperty
1,296✔
1092
        relationCountAttribute.relationName = relationName
1,296✔
1093
        relationCountAttribute.alias = aliasName
1,296✔
1094
        relationCountAttribute.queryBuilderFactory = queryBuilderFactory
1,296✔
1095
        this.expressionMap.relationCountAttributes.push(relationCountAttribute)
1,296✔
1096

1097
        this.expressionMap.createAlias({
1,296✔
1098
            type: "other",
1099
            name: relationCountAttribute.junctionAlias,
1100
        })
1101
        if (relationCountAttribute.relation.junctionEntityMetadata) {
1,296✔
1102
            this.expressionMap.createAlias({
1,056✔
1103
                type: "other",
1104
                name: relationCountAttribute.junctionAlias,
1105
                metadata:
1106
                    relationCountAttribute.relation.junctionEntityMetadata,
1107
            })
1108
        }
1109
        return this
1,296✔
1110
    }
1111

1112
    /**
1113
     * Loads all relation ids for all relations of the selected entity.
1114
     * All relation ids will be mapped to relation property themself.
1115
     * If array of strings is given then loads only relation ids of the given properties.
1116
     */
1117
    loadAllRelationIds(options?: {
1118
        relations?: string[]
1119
        disableMixedMap?: boolean
1120
    }): this {
1121
        // todo: add skip relations
1122
        this.expressionMap.mainAlias!.metadata.relations.forEach((relation) => {
100,720✔
1123
            if (
55,326✔
1124
                options !== undefined &&
165,930✔
1125
                options.relations !== undefined &&
1126
                options.relations.indexOf(relation.propertyPath) === -1
1127
            )
1128
                return
29,968✔
1129

1130
            this.loadRelationIdAndMap(
25,358✔
1131
                this.expressionMap.mainAlias!.name +
1132
                    "." +
1133
                    relation.propertyPath,
1134
                this.expressionMap.mainAlias!.name +
1135
                    "." +
1136
                    relation.propertyPath,
1137
                options,
1138
            )
1139
        })
1140
        return this
100,720✔
1141
    }
1142

1143
    /**
1144
     * Sets WHERE condition in the query builder.
1145
     * If you had previously WHERE expression defined,
1146
     * calling this function will override previously set WHERE conditions.
1147
     * Additionally you can add parameters used in where expression.
1148
     */
1149
    where(
1150
        where:
1151
            | Brackets
1152
            | string
1153
            | ((qb: this) => string)
1154
            | ObjectLiteral
1155
            | ObjectLiteral[],
1156
        parameters?: ObjectLiteral,
1157
    ): this {
1158
        this.expressionMap.wheres = [] // don't move this block below since computeWhereParameter can add where expressions
391,187✔
1159
        const condition = this.getWhereCondition(where)
391,187✔
1160
        if (condition) {
391,175✔
1161
            this.expressionMap.wheres = [
391,175✔
1162
                { type: "simple", condition: condition },
1163
            ]
1164
        }
1165
        if (parameters) this.setParameters(parameters)
391,175✔
1166
        return this
391,151✔
1167
    }
1168

1169
    /**
1170
     * Adds new AND WHERE condition in the query builder.
1171
     * Additionally you can add parameters used in where expression.
1172
     */
1173
    andWhere(
1174
        where:
1175
            | string
1176
            | Brackets
1177
            | ((qb: this) => string)
1178
            | ObjectLiteral
1179
            | ObjectLiteral[],
1180
        parameters?: ObjectLiteral,
1181
    ): this {
1182
        this.expressionMap.wheres.push({
62,414✔
1183
            type: "and",
1184
            condition: this.getWhereCondition(where),
1185
        })
1186
        if (parameters) this.setParameters(parameters)
62,414✔
1187
        return this
62,414✔
1188
    }
1189

1190
    /**
1191
     * Adds new OR WHERE condition in the query builder.
1192
     * Additionally you can add parameters used in where expression.
1193
     */
1194
    orWhere(
1195
        where:
1196
            | Brackets
1197
            | string
1198
            | ((qb: this) => string)
1199
            | ObjectLiteral
1200
            | ObjectLiteral[],
1201
        parameters?: ObjectLiteral,
1202
    ): this {
1203
        this.expressionMap.wheres.push({
16,725✔
1204
            type: "or",
1205
            condition: this.getWhereCondition(where),
1206
        })
1207
        if (parameters) this.setParameters(parameters)
16,725✔
1208
        return this
16,725✔
1209
    }
1210

1211
    /**
1212
     * Sets a new where EXISTS clause
1213
     */
1214
    whereExists(subQuery: SelectQueryBuilder<any>): this {
1215
        return this.where(...this.getExistsCondition(subQuery))
168✔
1216
    }
1217

1218
    /**
1219
     * Adds a new AND where EXISTS clause
1220
     */
1221
    andWhereExists(subQuery: SelectQueryBuilder<any>): this {
UNCOV
1222
        return this.andWhere(...this.getExistsCondition(subQuery))
×
1223
    }
1224

1225
    /**
1226
     * Adds a new OR where EXISTS clause
1227
     */
1228
    orWhereExists(subQuery: SelectQueryBuilder<any>): this {
UNCOV
1229
        return this.orWhere(...this.getExistsCondition(subQuery))
×
1230
    }
1231

1232
    /**
1233
     * Adds new AND WHERE with conditions for the given ids.
1234
     *
1235
     * Ids are mixed.
1236
     * It means if you have single primary key you can pass a simple id values, for example [1, 2, 3].
1237
     * If you have multiple primary keys you need to pass object with property names and values specified,
1238
     * for example [{ firstId: 1, secondId: 2 }, { firstId: 2, secondId: 3 }, ...]
1239
     */
1240
    whereInIds(ids: any | any[]): this {
1241
        return this.where(this.getWhereInIdsCondition(ids))
104,681✔
1242
    }
1243

1244
    /**
1245
     * Adds new AND WHERE with conditions for the given ids.
1246
     *
1247
     * Ids are mixed.
1248
     * It means if you have single primary key you can pass a simple id values, for example [1, 2, 3].
1249
     * If you have multiple primary keys you need to pass object with property names and values specified,
1250
     * for example [{ firstId: 1, secondId: 2 }, { firstId: 2, secondId: 3 }, ...]
1251
     */
1252
    andWhereInIds(ids: any | any[]): this {
1253
        return this.andWhere(this.getWhereInIdsCondition(ids))
172✔
1254
    }
1255

1256
    /**
1257
     * Adds new OR WHERE with conditions for the given ids.
1258
     *
1259
     * Ids are mixed.
1260
     * It means if you have single primary key you can pass a simple id values, for example [1, 2, 3].
1261
     * If you have multiple primary keys you need to pass object with property names and values specified,
1262
     * for example [{ firstId: 1, secondId: 2 }, { firstId: 2, secondId: 3 }, ...]
1263
     */
1264
    orWhereInIds(ids: any | any[]): this {
1265
        return this.orWhere(this.getWhereInIdsCondition(ids))
48✔
1266
    }
1267

1268
    /**
1269
     * Sets HAVING condition in the query builder.
1270
     * If you had previously HAVING expression defined,
1271
     * calling this function will override previously set HAVING conditions.
1272
     * Additionally you can add parameters used in where expression.
1273
     */
1274
    having(having: string, parameters?: ObjectLiteral): this {
1275
        this.expressionMap.havings.push({ type: "simple", condition: having })
2✔
1276
        if (parameters) this.setParameters(parameters)
2!
1277
        return this
2✔
1278
    }
1279

1280
    /**
1281
     * Adds new AND HAVING condition in the query builder.
1282
     * Additionally you can add parameters used in where expression.
1283
     */
1284
    andHaving(having: string, parameters?: ObjectLiteral): this {
UNCOV
1285
        this.expressionMap.havings.push({ type: "and", condition: having })
×
1286
        if (parameters) this.setParameters(parameters)
×
1287
        return this
×
1288
    }
1289

1290
    /**
1291
     * Adds new OR HAVING condition in the query builder.
1292
     * Additionally you can add parameters used in where expression.
1293
     */
1294
    orHaving(having: string, parameters?: ObjectLiteral): this {
1295
        this.expressionMap.havings.push({ type: "or", condition: having })
2✔
1296
        if (parameters) this.setParameters(parameters)
2!
1297
        return this
2✔
1298
    }
1299

1300
    /**
1301
     * Sets GROUP BY condition in the query builder.
1302
     * If you had previously GROUP BY expression defined,
1303
     * calling this function will override previously set GROUP BY conditions.
1304
     */
1305
    groupBy(): this
1306

1307
    /**
1308
     * Sets GROUP BY condition in the query builder.
1309
     * If you had previously GROUP BY expression defined,
1310
     * calling this function will override previously set GROUP BY conditions.
1311
     */
1312
    groupBy(groupBy: string): this
1313

1314
    /**
1315
     * Sets GROUP BY condition in the query builder.
1316
     * If you had previously GROUP BY expression defined,
1317
     * calling this function will override previously set GROUP BY conditions.
1318
     */
1319
    groupBy(groupBy?: string): this {
1320
        if (groupBy) {
1,081✔
1321
            this.expressionMap.groupBys = [groupBy]
87✔
1322
        } else {
1323
            this.expressionMap.groupBys = []
994✔
1324
        }
1325
        return this
1,081✔
1326
    }
1327

1328
    /**
1329
     * Adds GROUP BY condition in the query builder.
1330
     */
1331
    addGroupBy(groupBy: string): this {
1332
        this.expressionMap.groupBys.push(groupBy)
2,688✔
1333
        return this
2,688✔
1334
    }
1335

1336
    /**
1337
     * Enables time travelling for the current query (only supported by cockroach currently)
1338
     */
1339
    timeTravelQuery(timeTravelFn?: string | boolean): this {
1340
        if (this.connection.driver.options.type === "cockroachdb") {
12,611✔
1341
            if (timeTravelFn === undefined) {
531✔
1342
                this.expressionMap.timeTravel = "follower_read_timestamp()"
1✔
1343
            } else {
1344
                this.expressionMap.timeTravel = timeTravelFn
530✔
1345
            }
1346
        }
1347

1348
        return this
12,611✔
1349
    }
1350

1351
    /**
1352
     * Sets ORDER BY condition in the query builder.
1353
     * If you had previously ORDER BY expression defined,
1354
     * calling this function will override previously set ORDER BY conditions.
1355
     *
1356
     * Calling order by without order set will remove all previously set order bys.
1357
     */
1358
    orderBy(): this
1359

1360
    /**
1361
     * Sets ORDER BY condition in the query builder.
1362
     * If you had previously ORDER BY expression defined,
1363
     * calling this function will override previously set ORDER BY conditions.
1364
     */
1365
    orderBy(
1366
        sort: string,
1367
        order?: "ASC" | "DESC",
1368
        nulls?: "NULLS FIRST" | "NULLS LAST",
1369
    ): this
1370

1371
    /**
1372
     * Sets ORDER BY condition in the query builder.
1373
     * If you had previously ORDER BY expression defined,
1374
     * calling this function will override previously set ORDER BY conditions.
1375
     */
1376
    orderBy(order: OrderByCondition): this
1377

1378
    /**
1379
     * Sets ORDER BY condition in the query builder.
1380
     * If you had previously ORDER BY expression defined,
1381
     * calling this function will override previously set ORDER BY conditions.
1382
     */
1383
    orderBy(
1384
        sort?: string | OrderByCondition,
1385
        order: "ASC" | "DESC" = "ASC",
18,988✔
1386
        nulls?: "NULLS FIRST" | "NULLS LAST",
1387
    ): this {
1388
        if (order !== undefined && order !== "ASC" && order !== "DESC")
19,920✔
1389
            throw new TypeORMError(
24✔
1390
                `SelectQueryBuilder.addOrderBy "order" can accept only "ASC" and "DESC" values.`,
1391
            )
1392
        if (
19,896✔
1393
            nulls !== undefined &&
19,944✔
1394
            nulls !== "NULLS FIRST" &&
1395
            nulls !== "NULLS LAST"
1396
        )
1397
            throw new TypeORMError(
24✔
1398
                `SelectQueryBuilder.addOrderBy "nulls" can accept only "NULLS FIRST" and "NULLS LAST" values.`,
1399
            )
1400

1401
        if (sort) {
19,872✔
1402
            if (typeof sort === "object") {
12,575✔
1403
                this.expressionMap.orderBys = sort as OrderByCondition
6,332✔
1404
            } else {
1405
                if (nulls) {
6,243!
UNCOV
1406
                    this.expressionMap.orderBys = {
×
1407
                        [sort as string]: { order, nulls },
1408
                    }
1409
                } else {
1410
                    this.expressionMap.orderBys = { [sort as string]: order }
6,243✔
1411
                }
1412
            }
1413
        } else {
1414
            this.expressionMap.orderBys = {}
7,297✔
1415
        }
1416
        return this
19,872✔
1417
    }
1418

1419
    /**
1420
     * Adds ORDER BY condition in the query builder.
1421
     */
1422
    addOrderBy(
1423
        sort: string,
1424
        order: "ASC" | "DESC" = "ASC",
12,631✔
1425
        nulls?: "NULLS FIRST" | "NULLS LAST",
1426
    ): this {
1427
        if (order !== undefined && order !== "ASC" && order !== "DESC")
15,197!
UNCOV
1428
            throw new TypeORMError(
×
1429
                `SelectQueryBuilder.addOrderBy "order" can accept only "ASC" and "DESC" values.`,
1430
            )
1431
        if (
15,197!
1432
            nulls !== undefined &&
15,277✔
1433
            nulls !== "NULLS FIRST" &&
1434
            nulls !== "NULLS LAST"
1435
        )
UNCOV
1436
            throw new TypeORMError(
×
1437
                `SelectQueryBuilder.addOrderBy "nulls" can accept only "NULLS FIRST" and "NULLS LAST" values.`,
1438
            )
1439

1440
        if (nulls) {
15,197✔
1441
            this.expressionMap.orderBys[sort] = { order, nulls }
52✔
1442
        } else {
1443
            this.expressionMap.orderBys[sort] = order
15,145✔
1444
        }
1445
        return this
15,197✔
1446
    }
1447

1448
    /**
1449
     * Sets LIMIT - maximum number of rows to be selected.
1450
     * NOTE that it may not work as you expect if you are using joins.
1451
     * If you want to implement pagination, and you are having join in your query,
1452
     * then use the take method instead.
1453
     */
1454
    limit(limit?: number): this {
1455
        this.expressionMap.limit = this.normalizeNumber(limit)
7,773✔
1456
        if (
7,773!
1457
            this.expressionMap.limit !== undefined &&
14,528✔
1458
            isNaN(this.expressionMap.limit)
1459
        )
UNCOV
1460
            throw new TypeORMError(
×
1461
                `Provided "limit" value is not a number. Please provide a numeric value.`,
1462
            )
1463

1464
        return this
7,773✔
1465
    }
1466

1467
    /**
1468
     * Sets OFFSET - selection offset.
1469
     * NOTE that it may not work as you expect if you are using joins.
1470
     * If you want to implement pagination, and you are having join in your query,
1471
     * then use the skip method instead.
1472
     */
1473
    offset(offset?: number): this {
1474
        this.expressionMap.offset = this.normalizeNumber(offset)
7,503✔
1475
        if (
7,503!
1476
            this.expressionMap.offset !== undefined &&
7,787✔
1477
            isNaN(this.expressionMap.offset)
1478
        )
UNCOV
1479
            throw new TypeORMError(
×
1480
                `Provided "offset" value is not a number. Please provide a numeric value.`,
1481
            )
1482

1483
        return this
7,503✔
1484
    }
1485

1486
    /**
1487
     * Sets maximal number of entities to take.
1488
     */
1489
    take(take?: number): this {
1490
        this.expressionMap.take = this.normalizeNumber(take)
15,557✔
1491
        if (
15,557✔
1492
            this.expressionMap.take !== undefined &&
30,120✔
1493
            isNaN(this.expressionMap.take)
1494
        )
1495
            throw new TypeORMError(
48✔
1496
                `Provided "take" value is not a number. Please provide a numeric value.`,
1497
            )
1498

1499
        return this
15,509✔
1500
    }
1501

1502
    /**
1503
     * Sets number of entities to skip.
1504
     */
1505
    skip(skip?: number): this {
1506
        this.expressionMap.skip = this.normalizeNumber(skip)
1,733✔
1507
        if (
1,733✔
1508
            this.expressionMap.skip !== undefined &&
2,472✔
1509
            isNaN(this.expressionMap.skip)
1510
        )
1511
            throw new TypeORMError(
48✔
1512
                `Provided "skip" value is not a number. Please provide a numeric value.`,
1513
            )
1514

1515
        return this
1,685✔
1516
    }
1517

1518
    /**
1519
     * Set certain index to be used by the query.
1520
     *
1521
     * @param index Name of index to be used.
1522
     */
1523
    useIndex(index: string): this {
1524
        this.expressionMap.useIndex = index
4✔
1525

1526
        return this
4✔
1527
    }
1528

1529
    /**
1530
     * Sets locking mode.
1531
     */
1532
    setLock(lockMode: "optimistic", lockVersion: number | Date): this
1533

1534
    /**
1535
     * Sets locking mode.
1536
     */
1537
    setLock(
1538
        lockMode:
1539
            | "pessimistic_read"
1540
            | "pessimistic_write"
1541
            | "dirty_read"
1542
            /*
1543
                "pessimistic_partial_write" and "pessimistic_write_or_fail" are deprecated and
1544
                will be removed in a future version.
1545

1546
                Use setOnLocked instead.
1547
             */
1548
            | "pessimistic_partial_write"
1549
            | "pessimistic_write_or_fail"
1550
            | "for_no_key_update"
1551
            | "for_key_share",
1552
        lockVersion?: undefined,
1553
        lockTables?: string[],
1554
    ): this
1555

1556
    /**
1557
     * Sets locking mode.
1558
     */
1559
    setLock(
1560
        lockMode:
1561
            | "optimistic"
1562
            | "pessimistic_read"
1563
            | "pessimistic_write"
1564
            | "dirty_read"
1565
            /*
1566
                "pessimistic_partial_write" and "pessimistic_write_or_fail" are deprecated and
1567
                will be removed in a future version.
1568

1569
                Use setOnLocked instead.
1570
             */
1571
            | "pessimistic_partial_write"
1572
            | "pessimistic_write_or_fail"
1573
            | "for_no_key_update"
1574
            | "for_key_share",
1575
        lockVersion?: number | Date,
1576
        lockTables?: string[],
1577
    ): this {
1578
        this.expressionMap.lockMode = lockMode
820✔
1579
        this.expressionMap.lockVersion = lockVersion
820✔
1580
        this.expressionMap.lockTables = lockTables
820✔
1581
        return this
820✔
1582
    }
1583

1584
    /**
1585
     * Sets lock handling by adding NO WAIT or SKIP LOCKED.
1586
     */
1587
    setOnLocked(onLocked: "nowait" | "skip_locked"): this {
1588
        this.expressionMap.onLocked = onLocked
44✔
1589
        return this
44✔
1590
    }
1591

1592
    /**
1593
     * Disables the global condition of "non-deleted" for the entity with delete date columns.
1594
     */
1595
    withDeleted(): this {
1596
        this.expressionMap.withDeleted = true
101,573✔
1597
        return this
101,573✔
1598
    }
1599

1600
    /**
1601
     * Gets first raw result returned by execution of generated query builder sql.
1602
     */
1603
    async getRawOne<T = any>(): Promise<T | undefined> {
1604
        return (await this.getRawMany())[0]
6,222✔
1605
    }
1606

1607
    /**
1608
     * Gets all raw results returned by execution of generated query builder sql.
1609
     */
1610
    async getRawMany<T = any>(): Promise<T[]> {
1611
        if (this.expressionMap.lockMode === "optimistic")
22,731✔
1612
            throw new OptimisticLockCanNotBeUsedError()
48✔
1613

1614
        this.expressionMap.queryEntity = false
22,683✔
1615
        const queryRunner = this.obtainQueryRunner()
22,683✔
1616
        let transactionStartedByUs: boolean = false
22,683✔
1617
        try {
22,683✔
1618
            // start transaction if it was enabled
1619
            if (
22,683!
1620
                this.expressionMap.useTransaction === true &&
22,683!
1621
                queryRunner.isTransactionActive === false
1622
            ) {
UNCOV
1623
                await queryRunner.startTransaction()
×
1624
                transactionStartedByUs = true
×
1625
            }
1626

1627
            const results = await this.loadRawResults(queryRunner)
22,683✔
1628

1629
            // close transaction if we started it
1630
            if (transactionStartedByUs) {
22,672!
NEW
1631
                await queryRunner.commitTransaction()
×
1632
            }
1633

1634
            return results
22,672✔
1635
        } catch (error) {
1636
            // rollback transaction if we started it
1637
            if (transactionStartedByUs) {
11!
NEW
1638
                try {
×
NEW
1639
                    await queryRunner.rollbackTransaction()
×
1640
                } catch (rollbackError) {}
1641
            }
1642
            throw error
11✔
1643
        } finally {
1644
            if (queryRunner !== this.queryRunner) {
22,683✔
1645
                // means we created our own query runner
1646
                await queryRunner.release()
705✔
1647
            }
1648
        }
1649
    }
1650

1651
    /**
1652
     * Executes sql generated by query builder and returns object with raw results and entities created from them.
1653
     */
1654
    async getRawAndEntities<T = any>(): Promise<{
1655
        entities: Entity[]
1656
        raw: T[]
1657
    }> {
1658
        const queryRunner = this.obtainQueryRunner()
146,491✔
1659
        let transactionStartedByUs: boolean = false
146,491✔
1660
        try {
146,491✔
1661
            // start transaction if it was enabled
1662
            if (
146,491✔
1663
                this.expressionMap.useTransaction === true &&
146,515✔
1664
                queryRunner.isTransactionActive === false
1665
            ) {
1666
                await queryRunner.startTransaction()
24✔
1667
                transactionStartedByUs = true
24✔
1668
            }
1669

1670
            this.expressionMap.queryEntity = true
146,491✔
1671
            const results = await this.executeEntitiesAndRawResults(queryRunner)
146,491✔
1672

1673
            // close transaction if we started it
1674
            if (transactionStartedByUs) {
146,281✔
1675
                await queryRunner.commitTransaction()
24✔
1676
            }
1677

1678
            return results
146,281✔
1679
        } catch (error) {
1680
            // rollback transaction if we started it
1681
            if (transactionStartedByUs) {
210!
UNCOV
1682
                try {
×
UNCOV
1683
                    await queryRunner.rollbackTransaction()
×
1684
                } catch (rollbackError) {}
1685
            }
1686
            throw error
210✔
1687
        } finally {
1688
            if (queryRunner !== this.queryRunner)
146,491✔
1689
                // means we created our own query runner
1690
                await queryRunner.release()
38,126✔
1691
        }
1692
    }
1693

1694
    /**
1695
     * Gets single entity returned by execution of generated query builder sql.
1696
     */
1697
    async getOne(): Promise<Entity | null> {
1698
        const results = await this.getRawAndEntities()
19,961✔
1699
        const result = results.entities[0] as any
19,755✔
1700

1701
        if (
19,755✔
1702
            result &&
38,392✔
1703
            this.expressionMap.lockMode === "optimistic" &&
1704
            this.expressionMap.lockVersion
1705
        ) {
1706
            const metadata = this.expressionMap.mainAlias!.metadata
136✔
1707

1708
            if (this.expressionMap.lockVersion instanceof Date) {
136✔
1709
                const actualVersion =
1710
                    metadata.updateDateColumn!.getEntityValue(result) // what if columns arent set?
66✔
1711
                if (
66✔
1712
                    actualVersion.getTime() !==
1713
                    this.expressionMap.lockVersion.getTime()
1714
                )
1715
                    throw new OptimisticLockVersionMismatchError(
22✔
1716
                        metadata.name,
1717
                        this.expressionMap.lockVersion,
1718
                        actualVersion,
1719
                    )
1720
            } else {
1721
                const actualVersion =
1722
                    metadata.versionColumn!.getEntityValue(result) // what if columns arent set?
70✔
1723
                if (actualVersion !== this.expressionMap.lockVersion)
70✔
1724
                    throw new OptimisticLockVersionMismatchError(
24✔
1725
                        metadata.name,
1726
                        this.expressionMap.lockVersion,
1727
                        actualVersion,
1728
                    )
1729
            }
1730
        }
1731

1732
        if (result === undefined) {
19,709✔
1733
            return null
1,254✔
1734
        }
1735
        return result
18,455✔
1736
    }
1737

1738
    /**
1739
     * Gets the first entity returned by execution of generated query builder sql or rejects the returned promise on error.
1740
     */
1741
    async getOneOrFail(): Promise<Entity> {
1742
        const entity = await this.getOne()
14✔
1743

1744
        if (!entity) {
14✔
1745
            throw new EntityNotFoundError(
3✔
1746
                this.expressionMap.mainAlias!.target,
1747
                this.expressionMap.parameters,
1748
            )
1749
        }
1750

1751
        return entity
11✔
1752
    }
1753

1754
    /**
1755
     * Gets entities returned by execution of generated query builder sql.
1756
     */
1757
    async getMany(): Promise<Entity[]> {
1758
        if (this.expressionMap.lockMode === "optimistic")
124,306✔
1759
            throw new OptimisticLockCanNotBeUsedError()
48✔
1760

1761
        const results = await this.getRawAndEntities()
124,258✔
1762
        return results.entities
124,254✔
1763
    }
1764

1765
    /**
1766
     * Gets count - number of entities selected by sql generated by this query builder.
1767
     * Count excludes all limitations set by offset, limit, skip, and take.
1768
     */
1769
    async getCount(): Promise<number> {
1770
        if (this.expressionMap.lockMode === "optimistic")
726✔
1771
            throw new OptimisticLockCanNotBeUsedError()
24✔
1772

1773
        const queryRunner = this.obtainQueryRunner()
702✔
1774
        let transactionStartedByUs: boolean = false
702✔
1775
        try {
702✔
1776
            // start transaction if it was enabled
1777
            if (
702!
1778
                this.expressionMap.useTransaction === true &&
702!
1779
                queryRunner.isTransactionActive === false
1780
            ) {
UNCOV
1781
                await queryRunner.startTransaction()
×
UNCOV
1782
                transactionStartedByUs = true
×
1783
            }
1784

1785
            this.expressionMap.queryEntity = false
702✔
1786
            const results = await this.executeCountQuery(queryRunner)
702✔
1787

1788
            // close transaction if we started it
1789
            if (transactionStartedByUs) {
702!
UNCOV
1790
                await queryRunner.commitTransaction()
×
1791
            }
1792

1793
            return results
702✔
1794
        } catch (error) {
1795
            // rollback transaction if we started it
UNCOV
1796
            if (transactionStartedByUs) {
×
1797
                try {
×
UNCOV
1798
                    await queryRunner.rollbackTransaction()
×
1799
                } catch (rollbackError) {}
1800
            }
UNCOV
1801
            throw error
×
1802
        } finally {
1803
            if (queryRunner !== this.queryRunner)
702✔
1804
                // means we created our own query runner
1805
                await queryRunner.release()
702✔
1806
        }
1807
    }
1808

1809
    /**
1810
     * Gets exists
1811
     * Returns whether any rows exists matching current query.
1812
     */
1813
    async getExists(): Promise<boolean> {
1814
        if (this.expressionMap.lockMode === "optimistic")
168!
UNCOV
1815
            throw new OptimisticLockCanNotBeUsedError()
×
1816

1817
        const queryRunner = this.obtainQueryRunner()
168✔
1818
        let transactionStartedByUs: boolean = false
168✔
1819
        try {
168✔
1820
            // start transaction if it was enabled
1821
            if (
168!
1822
                this.expressionMap.useTransaction === true &&
168!
1823
                queryRunner.isTransactionActive === false
1824
            ) {
UNCOV
1825
                await queryRunner.startTransaction()
×
UNCOV
1826
                transactionStartedByUs = true
×
1827
            }
1828

1829
            this.expressionMap.queryEntity = false
168✔
1830
            const results = await this.executeExistsQuery(queryRunner)
168✔
1831

1832
            // close transaction if we started it
1833
            if (transactionStartedByUs) {
168!
UNCOV
1834
                await queryRunner.commitTransaction()
×
1835
            }
1836

1837
            return results
168✔
1838
        } catch (error) {
1839
            // rollback transaction if we started it
UNCOV
1840
            if (transactionStartedByUs) {
×
1841
                try {
×
UNCOV
1842
                    await queryRunner.rollbackTransaction()
×
1843
                } catch (rollbackError) {}
1844
            }
UNCOV
1845
            throw error
×
1846
        } finally {
1847
            if (queryRunner !== this.queryRunner)
168✔
1848
                // means we created our own query runner
1849
                await queryRunner.release()
168✔
1850
        }
1851
    }
1852

1853
    /**
1854
     * Executes built SQL query and returns entities and overall entities count (without limitation).
1855
     * This method is useful to build pagination.
1856
     */
1857
    async getManyAndCount(): Promise<[Entity[], number]> {
1858
        if (this.expressionMap.lockMode === "optimistic")
724✔
1859
            throw new OptimisticLockCanNotBeUsedError()
24✔
1860

1861
        const queryRunner = this.obtainQueryRunner()
700✔
1862
        let transactionStartedByUs: boolean = false
700✔
1863
        try {
700✔
1864
            // start transaction if it was enabled
1865
            if (
700!
1866
                this.expressionMap.useTransaction === true &&
700!
1867
                queryRunner.isTransactionActive === false
1868
            ) {
UNCOV
1869
                await queryRunner.startTransaction()
×
UNCOV
1870
                transactionStartedByUs = true
×
1871
            }
1872

1873
            this.expressionMap.queryEntity = true
700✔
1874
            const entitiesAndRaw = await this.executeEntitiesAndRawResults(
700✔
1875
                queryRunner,
1876
            )
1877
            this.expressionMap.queryEntity = false
694✔
1878

1879
            let count: number | undefined = this.lazyCount(entitiesAndRaw)
694✔
1880
            if (count === undefined) {
694✔
1881
                const cacheId = this.expressionMap.cacheId
124✔
1882
                // Creates a new cacheId for the count query, or it will retrieve the above query results
1883
                // and count will return 0.
1884
                if (cacheId) {
124!
UNCOV
1885
                    this.expressionMap.cacheId = `${cacheId}-count`
×
1886
                }
1887
                count = await this.executeCountQuery(queryRunner)
124✔
1888
            }
1889
            const results: [Entity[], number] = [entitiesAndRaw.entities, count]
694✔
1890

1891
            // close transaction if we started it
1892
            if (transactionStartedByUs) {
694!
UNCOV
1893
                await queryRunner.commitTransaction()
×
1894
            }
1895

1896
            return results
694✔
1897
        } catch (error) {
1898
            // rollback transaction if we started it
1899
            if (transactionStartedByUs) {
6!
1900
                try {
×
UNCOV
1901
                    await queryRunner.rollbackTransaction()
×
1902
                } catch (rollbackError) {}
1903
            }
1904
            throw error
6✔
1905
        } finally {
1906
            if (queryRunner !== this.queryRunner)
700✔
1907
                // means we created our own query runner
1908
                await queryRunner.release()
700✔
1909
        }
1910
    }
1911

1912
    private lazyCount(entitiesAndRaw: {
1913
        entities: Entity[]
1914
        raw: any[]
1915
    }): number | undefined {
1916
        const hasLimit =
1917
            this.expressionMap.limit !== undefined &&
694✔
1918
            this.expressionMap.limit !== null
1919
        if (this.expressionMap.joinAttributes.length > 0 && hasLimit) {
694✔
1920
            return undefined
48✔
1921
        }
1922

1923
        const hasTake =
1924
            this.expressionMap.take !== undefined &&
646✔
1925
            this.expressionMap.take !== null
1926

1927
        // limit overrides take when no join is defined
1928
        const maxResults = hasLimit
646✔
1929
            ? this.expressionMap.limit
1930
            : hasTake
598✔
1931
            ? this.expressionMap.take
1932
            : undefined
1933

1934
        if (
646✔
1935
            maxResults !== undefined &&
866✔
1936
            entitiesAndRaw.entities.length === maxResults
1937
        ) {
1938
            // stop here when the result set contains the max number of rows; we need to execute a full count
1939
            return undefined
76✔
1940
        }
1941

1942
        const hasSkip =
1943
            this.expressionMap.skip !== undefined &&
570✔
1944
            this.expressionMap.skip !== null &&
1945
            this.expressionMap.skip > 0
1946
        const hasOffset =
1947
            this.expressionMap.offset !== undefined &&
570✔
1948
            this.expressionMap.offset !== null &&
1949
            this.expressionMap.offset > 0
1950

1951
        // offset overrides skip when no join is defined
1952
        const previousResults: number = hasOffset
570✔
1953
            ? this.expressionMap.offset!
1954
            : hasSkip
546✔
1955
            ? this.expressionMap.skip!
1956
            : 0
1957

1958
        return entitiesAndRaw.entities.length + previousResults
570✔
1959
    }
1960

1961
    /**
1962
     * Executes built SQL query and returns raw data stream.
1963
     */
1964
    async stream(): Promise<ReadStream> {
1965
        this.expressionMap.queryEntity = false
9✔
1966
        const [sql, parameters] = this.getQueryAndParameters()
9✔
1967
        const queryRunner = this.obtainQueryRunner()
9✔
1968
        let transactionStartedByUs: boolean = false
9✔
1969
        try {
9✔
1970
            // start transaction if it was enabled
1971
            if (
9!
1972
                this.expressionMap.useTransaction === true &&
9!
1973
                queryRunner.isTransactionActive === false
1974
            ) {
UNCOV
1975
                await queryRunner.startTransaction()
×
UNCOV
1976
                transactionStartedByUs = true
×
1977
            }
1978

1979
            const releaseFn = () => {
9✔
1980
                if (queryRunner !== this.queryRunner)
9✔
1981
                    // means we created our own query runner
1982
                    return queryRunner.release()
9✔
1983
                return
×
1984
            }
1985
            const results = queryRunner.stream(
9✔
1986
                sql,
1987
                parameters,
1988
                releaseFn,
1989
                releaseFn,
1990
            )
1991

1992
            // close transaction if we started it
1993
            if (transactionStartedByUs) {
9!
UNCOV
1994
                await queryRunner.commitTransaction()
×
1995
            }
1996

1997
            return results
9✔
1998
        } catch (error) {
1999
            // rollback transaction if we started it
UNCOV
2000
            if (transactionStartedByUs) {
×
2001
                try {
×
UNCOV
2002
                    await queryRunner.rollbackTransaction()
×
2003
                } catch (rollbackError) {}
2004
            }
UNCOV
2005
            throw error
×
2006
        }
2007
    }
2008

2009
    /**
2010
     * Enables or disables query result caching.
2011
     */
2012
    cache(enabled: boolean): this
2013

2014
    /**
2015
     * Enables query result caching and sets in milliseconds in which cache will expire.
2016
     * If not set then global caching time will be used.
2017
     */
2018
    cache(milliseconds: number): this
2019

2020
    /**
2021
     * Enables query result caching and sets cache id and milliseconds in which cache will expire.
2022
     */
2023
    cache(id: any, milliseconds?: number): this
2024

2025
    /**
2026
     * Enables or disables query result caching.
2027
     */
2028
    cache(
2029
        enabledOrMillisecondsOrId: boolean | number | string,
2030
        maybeMilliseconds?: number,
2031
    ): this {
2032
        if (typeof enabledOrMillisecondsOrId === "boolean") {
7,871✔
2033
            this.expressionMap.cache = enabledOrMillisecondsOrId
1,160✔
2034
        } else if (typeof enabledOrMillisecondsOrId === "number") {
6,711✔
2035
            this.expressionMap.cache = true
96✔
2036
            this.expressionMap.cacheDuration = enabledOrMillisecondsOrId
96✔
2037
        } else if (
6,615✔
2038
            typeof enabledOrMillisecondsOrId === "string" ||
12,846✔
2039
            typeof enabledOrMillisecondsOrId === "number"
2040
        ) {
2041
            this.expressionMap.cache = true
384✔
2042
            this.expressionMap.cacheId = enabledOrMillisecondsOrId
384✔
2043
        }
2044

2045
        if (maybeMilliseconds) {
7,871✔
2046
            this.expressionMap.cacheDuration = maybeMilliseconds
384✔
2047
        }
2048

2049
        return this
7,871✔
2050
    }
2051

2052
    /**
2053
     * Sets extra options that can be used to configure how query builder works.
2054
     */
2055
    setOption(option: SelectQueryBuilderOption): this {
2056
        this.expressionMap.options.push(option)
8,081✔
2057
        return this
8,081✔
2058
    }
2059

2060
    // -------------------------------------------------------------------------
2061
    // Protected Methods
2062
    // -------------------------------------------------------------------------
2063

2064
    protected join(
2065
        direction: "INNER" | "LEFT",
2066
        entityOrProperty:
2067
            | Function
2068
            | string
2069
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
2070
        aliasName: string,
2071
        condition?: string,
2072
        parameters?: ObjectLiteral,
2073
        mapToProperty?: string,
2074
        isMappingMany?: boolean,
2075
        mapAsEntity?: Function | string,
2076
    ): void {
2077
        if (parameters) {
2,069,287!
UNCOV
2078
            this.setParameters(parameters)
×
2079
        }
2080

2081
        const joinAttribute = new JoinAttribute(
2,069,287✔
2082
            this.connection,
2083
            this.expressionMap,
2084
        )
2085
        joinAttribute.direction = direction
2,069,287✔
2086
        joinAttribute.mapAsEntity = mapAsEntity
2,069,287✔
2087
        joinAttribute.mapToProperty = mapToProperty
2,069,287✔
2088
        joinAttribute.isMappingMany = isMappingMany
2,069,287✔
2089
        joinAttribute.entityOrProperty = entityOrProperty // relationName
2,069,287✔
2090
        joinAttribute.condition = condition // joinInverseSideCondition
2,069,287✔
2091
        // joinAttribute.junctionAlias = joinAttribute.relation.isOwning ? parentAlias + "_" + destinationTableAlias : destinationTableAlias + "_" + parentAlias;
2092
        this.expressionMap.joinAttributes.push(joinAttribute)
2,069,287✔
2093

2094
        const joinAttributeMetadata = joinAttribute.metadata
2,069,287✔
2095
        if (joinAttributeMetadata) {
2,069,287✔
2096
            if (
2,069,202✔
2097
                joinAttributeMetadata.deleteDateColumn &&
2,069,374✔
2098
                !this.expressionMap.withDeleted
2099
            ) {
2100
                const conditionDeleteColumn = `${aliasName}.${joinAttributeMetadata.deleteDateColumn.propertyName} IS NULL`
124✔
2101
                joinAttribute.condition = joinAttribute.condition
124✔
2102
                    ? ` ${joinAttribute.condition} AND ${conditionDeleteColumn}`
2103
                    : `${conditionDeleteColumn}`
2104
            }
2105
            // todo: find and set metadata right there?
2106
            joinAttribute.alias = this.expressionMap.createAlias({
2,069,202✔
2107
                type: "join",
2108
                name: aliasName,
2109
                metadata: joinAttributeMetadata,
2110
            })
2111
            if (
2,069,202✔
2112
                joinAttribute.relation &&
4,127,334✔
2113
                joinAttribute.relation.junctionEntityMetadata
2114
            ) {
2115
                this.expressionMap.createAlias({
1,685,602✔
2116
                    type: "join",
2117
                    name: joinAttribute.junctionAlias,
2118
                    metadata: joinAttribute.relation.junctionEntityMetadata,
2119
                })
2120
            }
2121
        } else {
2122
            let subQuery: string = ""
85✔
2123
            if (typeof entityOrProperty === "function") {
85✔
2124
                const subQueryBuilder: SelectQueryBuilder<any> = (
2125
                    entityOrProperty as any
37✔
2126
                )((this as any as SelectQueryBuilder<any>).subQuery())
2127
                this.setParameters(subQueryBuilder.getParameters())
37✔
2128
                subQuery = subQueryBuilder.getQuery()
37✔
2129
            } else {
2130
                subQuery = entityOrProperty
48✔
2131
            }
2132
            const isSubQuery =
2133
                typeof entityOrProperty === "function" ||
85✔
2134
                (entityOrProperty.substr(0, 1) === "(" &&
2135
                    entityOrProperty.substr(-1) === ")")
2136
            joinAttribute.alias = this.expressionMap.createAlias({
85✔
2137
                type: "join",
2138
                name: aliasName,
2139
                tablePath:
2140
                    isSubQuery === false
85✔
2141
                        ? (entityOrProperty as string)
2142
                        : undefined,
2143
                subQuery: isSubQuery === true ? subQuery : undefined,
85✔
2144
            })
2145
        }
2146
    }
2147

2148
    /**
2149
     * Creates "SELECT FROM" part of SQL query.
2150
     */
2151
    protected createSelectExpression() {
2152
        if (!this.expressionMap.mainAlias)
462,280!
UNCOV
2153
            throw new TypeORMError(
×
2154
                "Cannot build query because main alias is not set (call qb#from method)",
2155
            )
2156

2157
        // todo throw exception if selects or from is missing
2158

2159
        const allSelects: SelectQuery[] = []
462,280✔
2160
        const excludedSelects: SelectQuery[] = []
462,280✔
2161

2162
        if (this.expressionMap.mainAlias.hasMetadata) {
462,280✔
2163
            const metadata = this.expressionMap.mainAlias.metadata
453,419✔
2164
            allSelects.push(
453,419✔
2165
                ...this.buildEscapedEntityColumnSelects(
2166
                    this.expressionMap.mainAlias.name,
2167
                    metadata,
2168
                ),
2169
            )
2170
            excludedSelects.push(
453,419✔
2171
                ...this.findEntityColumnSelects(
2172
                    this.expressionMap.mainAlias.name,
2173
                    metadata,
2174
                ),
2175
            )
2176
        }
2177

2178
        // add selects from joins
2179
        this.expressionMap.joinAttributes.forEach((join) => {
462,280✔
2180
            if (join.metadata) {
2,077,052✔
2181
                allSelects.push(
2,076,967✔
2182
                    ...this.buildEscapedEntityColumnSelects(
2183
                        join.alias.name!,
2184
                        join.metadata,
2185
                    ),
2186
                )
2187
                excludedSelects.push(
2,076,967✔
2188
                    ...this.findEntityColumnSelects(
2189
                        join.alias.name!,
2190
                        join.metadata,
2191
                    ),
2192
                )
2193
            } else {
2194
                const hasMainAlias = this.expressionMap.selects.some(
85✔
2195
                    (select) => select.selection === join.alias.name,
102✔
2196
                )
2197
                if (hasMainAlias) {
85✔
2198
                    allSelects.push({
13✔
2199
                        selection: this.escape(join.alias.name!) + ".*",
2200
                    })
2201
                    const excludedSelect = this.expressionMap.selects.find(
13✔
2202
                        (select) => select.selection === join.alias.name,
26✔
2203
                    )
2204
                    excludedSelects.push(excludedSelect!)
13✔
2205
                }
2206
            }
2207
        })
2208

2209
        // add all other selects
2210
        this.expressionMap.selects
462,280✔
2211
            .filter((select) => excludedSelects.indexOf(select) === -1)
2,552,123✔
2212
            .forEach((select) =>
2213
                allSelects.push({
12,387✔
2214
                    selection: this.replacePropertyNames(select.selection),
2215
                    aliasName: select.aliasName,
2216
                }),
2217
            )
2218

2219
        // if still selection is empty, then simply set it to all (*)
2220
        if (allSelects.length === 0) allSelects.push({ selection: "*" })
462,280✔
2221

2222
        // Use certain index
2223
        let useIndex: string = ""
462,280✔
2224
        if (this.expressionMap.useIndex) {
462,280!
UNCOV
2225
            if (DriverUtils.isMySQLFamily(this.connection.driver)) {
×
UNCOV
2226
                useIndex = ` USE INDEX (${this.expressionMap.useIndex})`
×
2227
            }
2228
        }
2229

2230
        // create a selection query
2231
        const froms = this.expressionMap.aliases
462,280✔
2232
            .filter(
2233
                (alias) =>
2234
                    alias.type === "from" &&
4,243,928✔
2235
                    (alias.tablePath || alias.subQuery),
2236
            )
2237
            .map((alias) => {
2238
                if (alias.subQuery)
462,380✔
2239
                    return alias.subQuery + " " + this.escape(alias.name)
6,703✔
2240

2241
                return (
455,677✔
2242
                    this.getTableName(alias.tablePath!) +
2243
                    " " +
2244
                    this.escape(alias.name)
2245
                )
2246
            })
2247

2248
        const select = this.createSelectDistinctExpression()
462,280✔
2249
        const selection = allSelects
462,280✔
2250
            .map(
2251
                (select) =>
2252
                    select.selection +
8,517,188✔
2253
                    (select.aliasName
8,517,188✔
2254
                        ? " AS " + this.escape(select.aliasName)
2255
                        : ""),
2256
            )
2257
            .join(", ")
2258

2259
        return (
462,280✔
2260
            select +
2261
            selection +
2262
            " FROM " +
2263
            froms.join(", ") +
2264
            this.createTableLockExpression() +
2265
            useIndex
2266
        )
2267
    }
2268

2269
    /**
2270
     * Creates select | select distinct part of SQL query.
2271
     */
2272
    protected createSelectDistinctExpression(): string {
2273
        const { selectDistinct, selectDistinctOn, maxExecutionTime } =
2274
            this.expressionMap
462,280✔
2275
        const { driver } = this.connection
462,280✔
2276

2277
        let select = "SELECT "
462,280✔
2278

2279
        if (maxExecutionTime > 0) {
462,280!
UNCOV
2280
            if (DriverUtils.isMySQLFamily(driver)) {
×
UNCOV
2281
                select += `/*+ MAX_EXECUTION_TIME(${this.expressionMap.maxExecutionTime}) */ `
×
2282
            }
2283
        }
2284

2285
        if (
462,280✔
2286
            DriverUtils.isPostgresFamily(driver) &&
591,547✔
2287
            selectDistinctOn.length > 0
2288
        ) {
2289
            const selectDistinctOnMap = selectDistinctOn
12✔
2290
                .map((on) => this.replacePropertyNames(on))
20✔
2291
                .join(", ")
2292

2293
            select = `SELECT DISTINCT ON (${selectDistinctOnMap}) `
12✔
2294
        } else if (selectDistinct) {
462,268✔
2295
            select = "SELECT DISTINCT "
51✔
2296
        }
2297

2298
        return select
462,280✔
2299
    }
2300

2301
    /**
2302
     * Creates "JOIN" part of SQL query.
2303
     */
2304
    protected createJoinExpression(): string {
2305
        // examples:
2306
        // select from owning side
2307
        // qb.select("post")
2308
        //     .leftJoinAndSelect("post.category", "category");
2309
        // select from non-owning side
2310
        // qb.select("category")
2311
        //     .leftJoinAndSelect("category.post", "post");
2312

2313
        const joins = this.expressionMap.joinAttributes.map((joinAttr) => {
462,280✔
2314
            const relation = joinAttr.relation
2,077,052✔
2315
            const destinationTableName = joinAttr.tablePath
2,077,052✔
2316
            const destinationTableAlias = joinAttr.alias.name
2,077,052✔
2317
            let appendedCondition = joinAttr.condition
2,077,052✔
2318
                ? " AND (" + joinAttr.condition + ")"
2319
                : ""
2320
            const parentAlias = joinAttr.parentAlias
2,077,052✔
2321

2322
            // if join was build without relation (e.g. without "post.category") then it means that we have direct
2323
            // table to join, without junction table involved. This means we simply join direct table.
2324
            if (!parentAlias || !relation) {
2,077,052✔
2325
                const destinationJoin = joinAttr.alias.subQuery
11,155✔
2326
                    ? joinAttr.alias.subQuery
2327
                    : this.getTableName(destinationTableName)
2328
                return (
11,155✔
2329
                    " " +
2330
                    joinAttr.direction +
2331
                    " JOIN " +
2332
                    destinationJoin +
2333
                    " " +
2334
                    this.escape(destinationTableAlias) +
2335
                    this.createTableLockExpression() +
2336
                    (joinAttr.condition
11,155✔
2337
                        ? " ON " + this.replacePropertyNames(joinAttr.condition)
2338
                        : "")
2339
                )
2340
            }
2341

2342
            // if real entity relation is involved
2343
            if (relation.isManyToOne || relation.isOneToOneOwner) {
2,065,897✔
2344
                // JOIN `category` `category` ON `category`.`id` = `post`.`categoryId`
2345
                const condition = relation.joinColumns
13,031✔
2346
                    .map((joinColumn) => {
2347
                        return (
13,975✔
2348
                            destinationTableAlias +
2349
                            "." +
2350
                            joinColumn.referencedColumn!.propertyPath +
2351
                            "=" +
2352
                            parentAlias +
2353
                            "." +
2354
                            relation.propertyPath +
2355
                            "." +
2356
                            joinColumn.referencedColumn!.propertyPath
2357
                        )
2358
                    })
2359
                    .join(" AND ")
2360

2361
                return (
13,031✔
2362
                    " " +
2363
                    joinAttr.direction +
2364
                    " JOIN " +
2365
                    this.getTableName(destinationTableName) +
2366
                    " " +
2367
                    this.escape(destinationTableAlias) +
2368
                    this.createTableLockExpression() +
2369
                    " ON " +
2370
                    this.replacePropertyNames(condition + appendedCondition)
2371
                )
2372
            } else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
2,052,866✔
2373
                // JOIN `post` `post` ON `post`.`categoryId` = `category`.`id`
2374
                const condition = relation
365,236✔
2375
                    .inverseRelation!.joinColumns.map((joinColumn) => {
2376
                        if (
366,052✔
2377
                            relation.inverseEntityMetadata.tableType ===
366,148✔
2378
                                "entity-child" &&
2379
                            relation.inverseEntityMetadata.discriminatorColumn
2380
                        ) {
2381
                            appendedCondition +=
96✔
2382
                                " AND " +
2383
                                destinationTableAlias +
2384
                                "." +
2385
                                relation.inverseEntityMetadata
2386
                                    .discriminatorColumn.databaseName +
2387
                                "='" +
2388
                                relation.inverseEntityMetadata
2389
                                    .discriminatorValue +
2390
                                "'"
2391
                        }
2392

2393
                        return (
366,052✔
2394
                            destinationTableAlias +
2395
                            "." +
2396
                            relation.inverseRelation!.propertyPath +
2397
                            "." +
2398
                            joinColumn.referencedColumn!.propertyPath +
2399
                            "=" +
2400
                            parentAlias +
2401
                            "." +
2402
                            joinColumn.referencedColumn!.propertyPath
2403
                        )
2404
                    })
2405
                    .join(" AND ")
2406

2407
                if (!condition)
365,236!
UNCOV
2408
                    throw new TypeORMError(
×
2409
                        `Relation ${relation.entityMetadata.name}.${relation.propertyName} does not have join columns.`,
2410
                    )
2411

2412
                return (
365,236✔
2413
                    " " +
2414
                    joinAttr.direction +
2415
                    " JOIN " +
2416
                    this.getTableName(destinationTableName) +
2417
                    " " +
2418
                    this.escape(destinationTableAlias) +
2419
                    this.createTableLockExpression() +
2420
                    " ON " +
2421
                    this.replacePropertyNames(condition + appendedCondition)
2422
                )
2423
            } else {
2424
                // means many-to-many
2425
                const junctionTableName =
2426
                    relation.junctionEntityMetadata!.tablePath
1,687,630✔
2427

2428
                const junctionAlias = joinAttr.junctionAlias
1,687,630✔
2429
                let junctionCondition = "",
1,687,630✔
2430
                    destinationCondition = ""
1,687,630✔
2431

2432
                if (relation.isOwning) {
1,687,630✔
2433
                    junctionCondition = relation.joinColumns
1,686,812✔
2434
                        .map((joinColumn) => {
2435
                            // `post_category`.`postId` = `post`.`id`
2436
                            return (
1,687,388✔
2437
                                junctionAlias +
2438
                                "." +
2439
                                joinColumn.propertyPath +
2440
                                "=" +
2441
                                parentAlias +
2442
                                "." +
2443
                                joinColumn.referencedColumn!.propertyPath
2444
                            )
2445
                        })
2446
                        .join(" AND ")
2447

2448
                    destinationCondition = relation.inverseJoinColumns
1,686,812✔
2449
                        .map((joinColumn) => {
2450
                            // `category`.`id` = `post_category`.`categoryId`
2451
                            return (
1,687,412✔
2452
                                destinationTableAlias +
2453
                                "." +
2454
                                joinColumn.referencedColumn!.propertyPath +
2455
                                "=" +
2456
                                junctionAlias +
2457
                                "." +
2458
                                joinColumn.propertyPath
2459
                            )
2460
                        })
2461
                        .join(" AND ")
2462
                } else {
2463
                    junctionCondition = relation
818✔
2464
                        .inverseRelation!.inverseJoinColumns.map(
2465
                            (joinColumn) => {
2466
                                // `post_category`.`categoryId` = `category`.`id`
2467
                                return (
1,346✔
2468
                                    junctionAlias +
2469
                                    "." +
2470
                                    joinColumn.propertyPath +
2471
                                    "=" +
2472
                                    parentAlias +
2473
                                    "." +
2474
                                    joinColumn.referencedColumn!.propertyPath
2475
                                )
2476
                            },
2477
                        )
2478
                        .join(" AND ")
2479

2480
                    destinationCondition = relation
818✔
2481
                        .inverseRelation!.joinColumns.map((joinColumn) => {
2482
                            // `post`.`id` = `post_category`.`postId`
2483
                            return (
1,298✔
2484
                                destinationTableAlias +
2485
                                "." +
2486
                                joinColumn.referencedColumn!.propertyPath +
2487
                                "=" +
2488
                                junctionAlias +
2489
                                "." +
2490
                                joinColumn.propertyPath
2491
                            )
2492
                        })
2493
                        .join(" AND ")
2494
                }
2495

2496
                return (
1,687,630✔
2497
                    " " +
2498
                    joinAttr.direction +
2499
                    " JOIN " +
2500
                    this.getTableName(junctionTableName) +
2501
                    " " +
2502
                    this.escape(junctionAlias) +
2503
                    this.createTableLockExpression() +
2504
                    " ON " +
2505
                    this.replacePropertyNames(junctionCondition) +
2506
                    " " +
2507
                    joinAttr.direction +
2508
                    " JOIN " +
2509
                    this.getTableName(destinationTableName) +
2510
                    " " +
2511
                    this.escape(destinationTableAlias) +
2512
                    this.createTableLockExpression() +
2513
                    " ON " +
2514
                    this.replacePropertyNames(
2515
                        destinationCondition + appendedCondition,
2516
                    )
2517
                )
2518
            }
2519
        })
2520

2521
        return joins.join(" ")
462,280✔
2522
    }
2523

2524
    /**
2525
     * Creates "GROUP BY" part of SQL query.
2526
     */
2527
    protected createGroupByExpression() {
2528
        if (!this.expressionMap.groupBys || !this.expressionMap.groupBys.length)
462,280✔
2529
            return ""
459,505✔
2530
        return (
2,775✔
2531
            " GROUP BY " +
2532
            this.replacePropertyNames(this.expressionMap.groupBys.join(", "))
2533
        )
2534
    }
2535

2536
    /**
2537
     * Creates "ORDER BY" part of SQL query.
2538
     */
2539
    protected createOrderByExpression() {
2540
        const orderBys = this.expressionMap.allOrderBys
462,280✔
2541
        if (Object.keys(orderBys).length === 0) return ""
462,280✔
2542

2543
        return (
20,760✔
2544
            " ORDER BY " +
2545
            Object.keys(orderBys)
2546
                .map((columnName) => {
2547
                    const orderValue =
2548
                        typeof orderBys[columnName] === "string"
28,170✔
2549
                            ? orderBys[columnName]
2550
                            : (orderBys[columnName] as any).order +
2551
                              " " +
2552
                              (orderBys[columnName] as any).nulls
2553
                    const selection = this.expressionMap.selects.find(
28,170✔
2554
                        (s) => s.selection === columnName,
44,917✔
2555
                    )
2556
                    if (
28,170✔
2557
                        selection &&
40,196✔
2558
                        !selection.aliasName &&
2559
                        columnName.indexOf(".") !== -1
2560
                    ) {
2561
                        const criteriaParts = columnName.split(".")
516✔
2562
                        const aliasName = criteriaParts[0]
516✔
2563
                        const propertyPath = criteriaParts.slice(1).join(".")
516✔
2564
                        const alias = this.expressionMap.aliases.find(
516✔
2565
                            (alias) => alias.name === aliasName,
804✔
2566
                        )
2567
                        if (alias) {
516✔
2568
                            const column =
2569
                                alias.metadata.findColumnWithPropertyPath(
292✔
2570
                                    propertyPath,
2571
                                )
2572
                            if (column) {
292✔
2573
                                const orderAlias = DriverUtils.buildAlias(
292✔
2574
                                    this.connection.driver,
2575
                                    undefined,
2576
                                    aliasName,
2577
                                    column.databaseName,
2578
                                )
2579
                                return (
292✔
2580
                                    this.escape(orderAlias) + " " + orderValue
2581
                                )
2582
                            }
2583
                        }
2584
                    }
2585

2586
                    return (
27,878✔
2587
                        this.replacePropertyNames(columnName) + " " + orderValue
2588
                    )
2589
                })
2590
                .join(", ")
2591
        )
2592
    }
2593

2594
    /**
2595
     * Creates "LIMIT" and "OFFSET" parts of SQL query.
2596
     */
2597
    protected createLimitOffsetExpression(): string {
2598
        // in the case if nothing is joined in the query builder we don't need to make two requests to get paginated results
2599
        // we can use regular limit / offset, that's why we add offset and limit construction here based on skip and take values
2600
        let offset: number | undefined = this.expressionMap.offset,
462,280✔
2601
            limit: number | undefined = this.expressionMap.limit
462,280✔
2602
        if (
462,280✔
2603
            offset === undefined &&
1,379,777✔
2604
            limit === undefined &&
2605
            this.expressionMap.joinAttributes.length === 0
2606
        ) {
2607
            offset = this.expressionMap.skip
144,809✔
2608
            limit = this.expressionMap.take
144,809✔
2609
        }
2610

2611
        // Helper functions to check if values are set (including 0)
2612
        const hasLimit = limit !== undefined && limit !== null
462,280✔
2613
        const hasOffset = offset !== undefined && offset !== null
462,280✔
2614

2615
        if (this.connection.driver.options.type === "mssql") {
462,280✔
2616
            // Due to a limitation in SQL Server's parser implementation it does not support using
2617
            // OFFSET or FETCH NEXT without an ORDER BY clause being provided. In cases where the
2618
            // user does not request one we insert a dummy ORDER BY that does nothing and should
2619
            // have no effect on the query planner or on the order of the results returned.
2620
            // https://dba.stackexchange.com/a/193799
2621
            let prefix = ""
34,850✔
2622
            if (
34,850✔
2623
                (hasLimit || hasOffset) &&
69,702✔
2624
                Object.keys(this.expressionMap.allOrderBys).length <= 0
2625
            ) {
2626
                prefix = " ORDER BY (SELECT NULL)"
578✔
2627
            }
2628

2629
            if (hasLimit && hasOffset)
34,850✔
2630
                return (
66✔
2631
                    prefix +
2632
                    " OFFSET " +
2633
                    offset +
2634
                    " ROWS FETCH NEXT " +
2635
                    limit +
2636
                    " ROWS ONLY"
2637
                )
2638
            if (hasLimit)
34,784✔
2639
                return (
1,104✔
2640
                    prefix + " OFFSET 0 ROWS FETCH NEXT " + limit + " ROWS ONLY"
2641
                )
2642
            if (hasOffset) return prefix + " OFFSET " + offset + " ROWS"
33,680✔
2643
        } else if (
427,430✔
2644
            DriverUtils.isMySQLFamily(this.connection.driver) ||
1,461,348✔
2645
            this.connection.driver.options.type === "aurora-mysql" ||
2646
            this.connection.driver.options.type === "sap" ||
2647
            this.connection.driver.options.type === "spanner"
2648
        ) {
2649
            if (hasLimit && hasOffset)
106,072✔
2650
                return " LIMIT " + limit + " OFFSET " + offset
202✔
2651
            if (hasLimit) return " LIMIT " + limit
105,870✔
2652
            if (hasOffset) throw new OffsetWithoutLimitNotSupportedError()
102,470✔
2653
        } else if (DriverUtils.isSQLiteFamily(this.connection.driver)) {
321,358✔
2654
            if (hasLimit && hasOffset)
158,097✔
2655
                return " LIMIT " + limit + " OFFSET " + offset
312✔
2656
            if (hasLimit) return " LIMIT " + limit
157,785✔
2657
            if (hasOffset) return " LIMIT -1 OFFSET " + offset
152,724✔
2658
        } else if (this.connection.driver.options.type === "oracle") {
163,261✔
2659
            if (hasLimit && hasOffset)
33,994✔
2660
                return (
66✔
2661
                    " OFFSET " +
2662
                    offset +
2663
                    " ROWS FETCH NEXT " +
2664
                    limit +
2665
                    " ROWS ONLY"
2666
                )
2667
            if (hasLimit) return " FETCH NEXT " + limit + " ROWS ONLY"
33,928✔
2668
            if (hasOffset) return " OFFSET " + offset + " ROWS"
32,842✔
2669
        } else {
2670
            if (hasLimit && hasOffset)
129,267✔
2671
                return " LIMIT " + limit + " OFFSET " + offset
179✔
2672
            if (hasLimit) return " LIMIT " + limit
129,088✔
2673
            if (hasOffset) return " OFFSET " + offset
125,675✔
2674
        }
2675

2676
        return ""
447,367✔
2677
    }
2678

2679
    /**
2680
     * Creates "LOCK" part of SELECT Query after table Clause
2681
     * ex.
2682
     *  SELECT 1
2683
     *  FROM USER U WITH (NOLOCK)
2684
     *  JOIN ORDER O WITH (NOLOCK)
2685
     *      ON U.ID=O.OrderID
2686
     */
2687
    private createTableLockExpression(): string {
2688
        if (this.connection.driver.options.type === "mssql") {
4,226,962✔
2689
            switch (this.expressionMap.lockMode) {
318,556✔
2690
                case "pessimistic_read":
2691
                    return " WITH (HOLDLOCK, ROWLOCK)"
10✔
2692
                case "pessimistic_write":
2693
                    return " WITH (UPDLOCK, ROWLOCK)"
14✔
2694
                case "dirty_read":
2695
                    return " WITH (NOLOCK)"
22✔
2696
            }
2697
        }
2698

2699
        return ""
4,226,916✔
2700
    }
2701

2702
    /**
2703
     * Creates "LOCK" part of SQL query.
2704
     */
2705
    protected createLockExpression(): string {
2706
        const driver = this.connection.driver
462,274✔
2707

2708
        let lockTablesClause = ""
462,274✔
2709

2710
        if (this.expressionMap.lockTables) {
462,274✔
2711
            if (
89!
2712
                !(
2713
                    DriverUtils.isPostgresFamily(driver) ||
89!
2714
                    driver.options.type === "cockroachdb"
2715
                )
2716
            ) {
UNCOV
2717
                throw new TypeORMError(
×
2718
                    "Lock tables not supported in selected driver",
2719
                )
2720
            }
2721
            if (this.expressionMap.lockTables.length < 1) {
89✔
2722
                throw new TypeORMError("lockTables cannot be an empty array")
10✔
2723
            }
2724
            lockTablesClause = " OF " + this.expressionMap.lockTables.join(", ")
79✔
2725
        }
2726

2727
        let onLockExpression = ""
462,264✔
2728
        if (this.expressionMap.onLocked === "nowait") {
462,264✔
2729
            onLockExpression = " NOWAIT"
12✔
2730
        } else if (this.expressionMap.onLocked === "skip_locked") {
462,252✔
2731
            onLockExpression = " SKIP LOCKED"
32✔
2732
        }
2733
        switch (this.expressionMap.lockMode) {
462,264✔
2734
            case "pessimistic_read":
2735
                if (
98✔
2736
                    driver.options.type === "mysql" ||
186✔
2737
                    driver.options.type === "aurora-mysql"
2738
                ) {
2739
                    if (
10!
2740
                        DriverUtils.isReleaseVersionOrGreater(driver, "8.0.0")
2741
                    ) {
UNCOV
2742
                        return (
×
2743
                            " FOR SHARE" + lockTablesClause + onLockExpression
2744
                        )
2745
                    } else {
2746
                        return " LOCK IN SHARE MODE"
10✔
2747
                    }
2748
                } else if (driver.options.type === "mariadb") {
88✔
2749
                    return " LOCK IN SHARE MODE"
8✔
2750
                } else if (DriverUtils.isPostgresFamily(driver)) {
80✔
2751
                    return " FOR SHARE" + lockTablesClause + onLockExpression
40✔
2752
                } else if (driver.options.type === "oracle") {
40✔
2753
                    return " FOR UPDATE"
8✔
2754
                } else if (driver.options.type === "mssql") {
32✔
2755
                    return ""
10✔
2756
                } else {
2757
                    throw new LockNotSupportedOnGivenDriverError()
22✔
2758
                }
2759
            case "pessimistic_write":
2760
                if (
137✔
2761
                    DriverUtils.isMySQLFamily(driver) ||
375✔
2762
                    driver.options.type === "aurora-mysql" ||
2763
                    driver.options.type === "oracle"
2764
                ) {
2765
                    return " FOR UPDATE" + onLockExpression
26✔
2766
                } else if (
111✔
2767
                    DriverUtils.isPostgresFamily(driver) ||
145✔
2768
                    driver.options.type === "cockroachdb"
2769
                ) {
2770
                    return " FOR UPDATE" + lockTablesClause + onLockExpression
77✔
2771
                } else if (driver.options.type === "mssql") {
34✔
2772
                    return ""
12✔
2773
                } else {
2774
                    throw new LockNotSupportedOnGivenDriverError()
22✔
2775
                }
2776
            case "pessimistic_partial_write":
2777
                if (DriverUtils.isPostgresFamily(driver)) {
28✔
2778
                    return " FOR UPDATE" + lockTablesClause + " SKIP LOCKED"
20✔
2779
                } else if (DriverUtils.isMySQLFamily(driver)) {
8!
2780
                    return " FOR UPDATE SKIP LOCKED"
8✔
2781
                } else {
UNCOV
2782
                    throw new LockNotSupportedOnGivenDriverError()
×
2783
                }
2784
            case "pessimistic_write_or_fail":
2785
                if (
32✔
2786
                    DriverUtils.isPostgresFamily(driver) ||
42✔
2787
                    driver.options.type === "cockroachdb"
2788
                ) {
2789
                    return " FOR UPDATE" + lockTablesClause + " NOWAIT"
22✔
2790
                } else if (DriverUtils.isMySQLFamily(driver)) {
10!
2791
                    return " FOR UPDATE NOWAIT"
10✔
2792
                } else {
UNCOV
2793
                    throw new LockNotSupportedOnGivenDriverError()
×
2794
                }
2795
            case "for_no_key_update":
2796
                if (
41✔
2797
                    DriverUtils.isPostgresFamily(driver) ||
60✔
2798
                    driver.options.type === "cockroachdb"
2799
                ) {
2800
                    return (
22✔
2801
                        " FOR NO KEY UPDATE" +
2802
                        lockTablesClause +
2803
                        onLockExpression
2804
                    )
2805
                } else {
2806
                    throw new LockNotSupportedOnGivenDriverError()
19✔
2807
                }
2808
            case "for_key_share":
2809
                if (DriverUtils.isPostgresFamily(driver)) {
39✔
2810
                    return (
20✔
2811
                        " FOR KEY SHARE" + lockTablesClause + onLockExpression
2812
                    )
2813
                } else {
2814
                    throw new LockNotSupportedOnGivenDriverError()
19✔
2815
                }
2816
            default:
2817
                return ""
461,889✔
2818
        }
2819
    }
2820

2821
    /**
2822
     * Creates "HAVING" part of SQL query.
2823
     */
2824
    protected createHavingExpression() {
2825
        if (!this.expressionMap.havings || !this.expressionMap.havings.length)
462,280✔
2826
            return ""
462,278✔
2827
        const conditions = this.expressionMap.havings
2✔
2828
            .map((having, index) => {
2829
                switch (having.type) {
4!
2830
                    case "and":
UNCOV
2831
                        return (
×
2832
                            (index > 0 ? "AND " : "") +
×
2833
                            this.replacePropertyNames(having.condition)
2834
                        )
2835
                    case "or":
2836
                        return (
2✔
2837
                            (index > 0 ? "OR " : "") +
2!
2838
                            this.replacePropertyNames(having.condition)
2839
                        )
2840
                    default:
2841
                        return this.replacePropertyNames(having.condition)
2✔
2842
                }
2843
            })
2844
            .join(" ")
2845

2846
        if (!conditions.length) return ""
2!
2847
        return " HAVING " + conditions
2✔
2848
    }
2849

2850
    protected buildEscapedEntityColumnSelects(
2851
        aliasName: string,
2852
        metadata: EntityMetadata,
2853
    ): SelectQuery[] {
2854
        const hasMainAlias = this.expressionMap.selects.some(
2,530,386✔
2855
            (select) => select.selection === aliasName,
11,127,358✔
2856
        )
2857

2858
        const columns: ColumnMetadata[] = []
2,530,386✔
2859
        if (hasMainAlias) {
2,530,386✔
2860
            columns.push(
2,491,521✔
2861
                ...metadata.columns.filter(
2862
                    (column) => column.isSelect === true,
8,454,478✔
2863
                ),
2864
            )
2865
        }
2866
        columns.push(
2,530,386✔
2867
            ...metadata.columns.filter((column) => {
2868
                return this.expressionMap.selects.some(
8,595,088✔
2869
                    (select) =>
2870
                        select.selection ===
71,827,611✔
2871
                        aliasName + "." + column.propertyPath,
2872
                )
2873
            }),
2874
        )
2875

2876
        // if user used partial selection and did not select some primary columns which are required to be selected
2877
        // we select those primary columns and mark them as "virtual". Later virtual column values will be removed from final entity
2878
        // to make entity contain exactly what user selected
2879
        if (columns.length === 0)
2,530,386✔
2880
            // however not in the case when nothing (even partial) was selected from this target (for example joins without selection)
2881
            return []
12,643✔
2882

2883
        const nonSelectedPrimaryColumns = this.expressionMap.queryEntity
2,517,743✔
2884
            ? metadata.primaryColumns.filter(
2885
                  (primaryColumn) => columns.indexOf(primaryColumn) === -1,
170,885✔
2886
              )
2887
            : []
2888
        const allColumns = [...columns, ...nonSelectedPrimaryColumns]
2,517,743✔
2889
        const finalSelects: SelectQuery[] = []
2,517,743✔
2890

2891
        const escapedAliasName = this.escape(aliasName)
2,517,743✔
2892
        allColumns.forEach((column) => {
2,517,743✔
2893
            let selectionPath =
2894
                escapedAliasName + "." + this.escape(column.databaseName)
8,502,656✔
2895

2896
            if (column.isVirtualProperty && column.query) {
8,502,656✔
2897
                selectionPath = `(${column.query(escapedAliasName)})`
360✔
2898
            }
2899

2900
            if (
8,502,656✔
2901
                this.connection.driver.spatialTypes.indexOf(column.type) !== -1
2902
            ) {
2903
                if (
236✔
2904
                    DriverUtils.isMySQLFamily(this.connection.driver) ||
432✔
2905
                    this.connection.driver.options.type === "aurora-mysql"
2906
                ) {
2907
                    const useLegacy = (
2908
                        this.connection.driver as
40✔
2909
                            | MysqlDriver
2910
                            | AuroraMysqlDriver
2911
                    ).options.legacySpatialSupport
2912
                    const asText = useLegacy ? "AsText" : "ST_AsText"
40✔
2913
                    selectionPath = `${asText}(${selectionPath})`
40✔
2914
                }
2915

2916
                if (DriverUtils.isPostgresFamily(this.connection.driver))
236✔
2917
                    if (column.precision) {
168!
2918
                        // cast to JSON to trigger parsing in the driver
UNCOV
2919
                        selectionPath = `ST_AsGeoJSON(${selectionPath}, ${column.precision})::json`
×
2920
                    } else {
2921
                        selectionPath = `ST_AsGeoJSON(${selectionPath})::json`
168✔
2922
                    }
2923
                if (this.connection.driver.options.type === "mssql")
236✔
2924
                    selectionPath = `${selectionPath}.ToString()`
28✔
2925
            }
2926

2927
            const selections = this.expressionMap.selects.filter(
8,502,656✔
2928
                (select) =>
2929
                    select.selection === aliasName + "." + column.propertyPath,
71,702,037✔
2930
            )
2931
            if (selections.length) {
8,502,656✔
2932
                selections.forEach((selection) => {
48,226✔
2933
                    finalSelects.push({
48,226✔
2934
                        selection: selectionPath,
2935
                        aliasName: selection.aliasName
48,226✔
2936
                            ? selection.aliasName
2937
                            : DriverUtils.buildAlias(
2938
                                  this.connection.driver,
2939
                                  undefined,
2940
                                  aliasName,
2941
                                  column.databaseName,
2942
                              ),
2943
                        // todo: need to keep in mind that custom selection.aliasName breaks hydrator. fix it later!
2944
                        virtual: selection.virtual,
2945
                    })
2946
                })
2947
            } else {
2948
                finalSelects.push({
8,454,430✔
2949
                    selection: selectionPath,
2950
                    aliasName: DriverUtils.buildAlias(
2951
                        this.connection.driver,
2952
                        undefined,
2953
                        aliasName,
2954
                        column.databaseName,
2955
                    ),
2956
                    // todo: need to keep in mind that custom selection.aliasName breaks hydrator. fix it later!
2957
                    virtual: hasMainAlias,
2958
                })
2959
            }
2960
        })
2961
        return finalSelects
2,517,743✔
2962
    }
2963

2964
    protected findEntityColumnSelects(
2965
        aliasName: string,
2966
        metadata: EntityMetadata,
2967
    ): SelectQuery[] {
2968
        const mainSelect = this.expressionMap.selects.find(
2,530,386✔
2969
            (select) => select.selection === aliasName,
11,127,358✔
2970
        )
2971
        if (mainSelect) return [mainSelect]
2,530,386✔
2972

2973
        return this.expressionMap.selects.filter((select) => {
38,865✔
2974
            return metadata.columns.some(
78,185✔
2975
                (column) =>
2976
                    select.selection === aliasName + "." + column.propertyPath,
209,678✔
2977
            )
2978
        })
2979
    }
2980

2981
    private computeCountExpression() {
2982
        const mainAlias = this.expressionMap.mainAlias!.name // todo: will this work with "fromTableName"?
826✔
2983
        const metadata = this.expressionMap.mainAlias!.metadata
826✔
2984

2985
        const primaryColumns = metadata.primaryColumns
826✔
2986
        const distinctAlias = this.escape(mainAlias)
826✔
2987

2988
        // If we aren't doing anything that will create a join, we can use a simpler `COUNT` instead
2989
        // so we prevent poor query patterns in the most likely cases
2990
        if (
826✔
2991
            this.expressionMap.joinAttributes.length === 0 &&
2,286✔
2992
            this.expressionMap.relationIdAttributes.length === 0 &&
2993
            this.expressionMap.relationCountAttributes.length === 0
2994
        ) {
2995
            return "COUNT(1)"
730✔
2996
        }
2997

2998
        // For everything else, we'll need to do some hackery to get the correct count values.
2999

3000
        if (
96✔
3001
            this.connection.driver.options.type === "cockroachdb" ||
188✔
3002
            DriverUtils.isPostgresFamily(this.connection.driver)
3003
        ) {
3004
            // Postgres and CockroachDB can pass multiple parameters to the `DISTINCT` function
3005
            // https://www.postgresql.org/docs/9.5/sql-select.html#SQL-DISTINCT
3006
            return (
20✔
3007
                "COUNT(DISTINCT(" +
3008
                primaryColumns
3009
                    .map(
3010
                        (c) =>
3011
                            `${distinctAlias}.${this.escape(c.databaseName)}`,
35✔
3012
                    )
3013
                    .join(", ") +
3014
                "))"
3015
            )
3016
        }
3017

3018
        if (DriverUtils.isMySQLFamily(this.connection.driver)) {
76✔
3019
            // MySQL & MariaDB can pass multiple parameters to the `DISTINCT` language construct
3020
            // https://mariadb.com/kb/en/count-distinct/
3021
            return (
16✔
3022
                "COUNT(DISTINCT " +
3023
                primaryColumns
3024
                    .map(
3025
                        (c) =>
3026
                            `${distinctAlias}.${this.escape(c.databaseName)}`,
28✔
3027
                    )
3028
                    .join(", ") +
3029
                ")"
3030
            )
3031
        }
3032

3033
        if (this.connection.driver.options.type === "mssql") {
60✔
3034
            // SQL Server has gotta be different from everyone else.  They don't support
3035
            // distinct counting multiple columns & they don't have the same operator
3036
            // characteristic for concatenating, so we gotta use the `CONCAT` function.
3037
            // However, If it's exactly 1 column we can omit the `CONCAT` for better performance.
3038

3039
            const columnsExpression = primaryColumns
8✔
3040
                .map(
3041
                    (primaryColumn) =>
3042
                        `${distinctAlias}.${this.escape(
14✔
3043
                            primaryColumn.databaseName,
3044
                        )}`,
3045
                )
3046
                .join(", '|;|', ")
3047

3048
            if (primaryColumns.length === 1) {
8✔
3049
                return `COUNT(DISTINCT(${columnsExpression}))`
4✔
3050
            }
3051

3052
            return `COUNT(DISTINCT(CONCAT(${columnsExpression})))`
4✔
3053
        }
3054

3055
        if (this.connection.driver.options.type === "spanner") {
52!
3056
            // spanner also has gotta be different from everyone else.
3057
            // they do not support concatenation of different column types without casting them to string
3058

UNCOV
3059
            if (primaryColumns.length === 1) {
×
UNCOV
3060
                return `COUNT(DISTINCT(${distinctAlias}.${this.escape(
×
3061
                    primaryColumns[0].databaseName,
3062
                )}))`
3063
            }
3064

UNCOV
3065
            const columnsExpression = primaryColumns
×
3066
                .map(
3067
                    (primaryColumn) =>
UNCOV
3068
                        `CAST(${distinctAlias}.${this.escape(
×
3069
                            primaryColumn.databaseName,
3070
                        )} AS STRING)`,
3071
                )
3072
                .join(", '|;|', ")
UNCOV
3073
            return `COUNT(DISTINCT(CONCAT(${columnsExpression})))`
×
3074
        }
3075

3076
        // If all else fails, fall back to a `COUNT` and `DISTINCT` across all the primary columns concatenated.
3077
        // Per the SQL spec, this is the canonical string concatenation mechanism which is most
3078
        // likely to work across servers implementing the SQL standard.
3079

3080
        // Please note, if there is only one primary column that the concatenation does not occur in this
3081
        // query and the query is a standard `COUNT DISTINCT` in that case.
3082

3083
        return (
52✔
3084
            `COUNT(DISTINCT(` +
3085
            primaryColumns
3086
                .map((c) => `${distinctAlias}.${this.escape(c.databaseName)}`)
91✔
3087
                .join(" || '|;|' || ") +
3088
            "))"
3089
        )
3090
    }
3091

3092
    protected async executeCountQuery(
3093
        queryRunner: QueryRunner,
3094
    ): Promise<number> {
3095
        const countSql = this.computeCountExpression()
826✔
3096

3097
        const results = await this.clone()
826✔
3098
            .orderBy()
3099
            .groupBy()
3100
            .offset(undefined)
3101
            .limit(undefined)
3102
            .skip(undefined)
3103
            .take(undefined)
3104
            .select(countSql, "cnt")
3105
            .setOption("disable-global-order")
3106
            .loadRawResults(queryRunner)
3107

3108
        if (!results || !results[0] || !results[0]["cnt"]) return 0
826✔
3109

3110
        return parseInt(results[0]["cnt"])
796✔
3111
    }
3112

3113
    protected async executeExistsQuery(
3114
        queryRunner: QueryRunner,
3115
    ): Promise<boolean> {
3116
        const results = await this.connection
168✔
3117
            .createQueryBuilder()
3118
            .fromDummy()
3119
            .select("1", "row_exists")
3120
            .whereExists(this)
3121
            .limit(1)
3122
            .loadRawResults(queryRunner)
3123

3124
        return results.length > 0
168✔
3125
    }
3126

3127
    protected applyFindOptions() {
3128
        // todo: convert relations: string[] to object map to simplify code
3129
        // todo: same with selects
3130

3131
        if (this.expressionMap.mainAlias!.metadata) {
162,243✔
3132
            if (this.findOptions.relationLoadStrategy) {
162,243✔
3133
                this.expressionMap.relationLoadStrategy =
24✔
3134
                    this.findOptions.relationLoadStrategy
3135
            }
3136

3137
            if (this.findOptions.comment) {
162,243✔
3138
                this.comment(this.findOptions.comment)
24✔
3139
            }
3140

3141
            if (this.findOptions.withDeleted) {
162,243✔
3142
                this.withDeleted()
101,012✔
3143
            }
3144

3145
            if (this.findOptions.select) {
162,243✔
3146
                const select = Array.isArray(this.findOptions.select)
619!
3147
                    ? OrmUtils.propertyPathsToTruthyObject(
3148
                          this.findOptions.select as string[],
3149
                      )
3150
                    : this.findOptions.select
3151

3152
                this.buildSelect(
619✔
3153
                    select,
3154
                    this.expressionMap.mainAlias!.metadata,
3155
                    this.expressionMap.mainAlias!.name,
3156
                )
3157
            }
3158

3159
            if (this.selects.length) {
162,219✔
3160
                this.select(this.selects)
547✔
3161
            }
3162

3163
            this.selects = []
162,219✔
3164
            if (this.findOptions.relations) {
162,219✔
3165
                const relations = Array.isArray(this.findOptions.relations)
46,227✔
3166
                    ? OrmUtils.propertyPathsToTruthyObject(
3167
                          this.findOptions.relations,
3168
                      )
3169
                    : this.findOptions.relations
3170

3171
                this.buildRelations(
46,227✔
3172
                    relations,
3173
                    typeof this.findOptions.select === "object"
46,227✔
3174
                        ? (this.findOptions.select as FindOptionsSelect<any>)
3175
                        : undefined,
3176
                    this.expressionMap.mainAlias!.metadata,
3177
                    this.expressionMap.mainAlias!.name,
3178
                )
3179
                if (
46,083✔
3180
                    this.findOptions.loadEagerRelations !== false &&
92,166✔
3181
                    this.expressionMap.relationLoadStrategy === "join"
3182
                ) {
3183
                    this.buildEagerRelations(
46,059✔
3184
                        relations,
3185
                        typeof this.findOptions.select === "object"
46,059✔
3186
                            ? (this.findOptions
3187
                                  .select as FindOptionsSelect<any>)
3188
                            : undefined,
3189
                        this.expressionMap.mainAlias!.metadata,
3190
                        this.expressionMap.mainAlias!.name,
3191
                    )
3192
                }
3193
            }
3194
            if (this.selects.length) {
162,075✔
3195
                this.addSelect(this.selects)
192✔
3196
            }
3197

3198
            if (this.findOptions.where) {
162,075✔
3199
                this.conditions = this.buildWhere(
56,650✔
3200
                    this.findOptions.where,
3201
                    this.expressionMap.mainAlias!.metadata,
3202
                    this.expressionMap.mainAlias!.name,
3203
                )
3204

3205
                if (this.conditions.length)
56,602✔
3206
                    this.andWhere(
56,085✔
3207
                        this.conditions.substr(0, 1) !== "("
56,085!
3208
                            ? "(" + this.conditions + ")"
3209
                            : this.conditions,
3210
                    ) // temporary and where and braces
3211
            }
3212

3213
            if (this.findOptions.order) {
162,027✔
3214
                this.buildOrder(
2,234✔
3215
                    this.findOptions.order,
3216
                    this.expressionMap.mainAlias!.metadata,
3217
                    this.expressionMap.mainAlias!.name,
3218
                )
3219
            }
3220

3221
            // apply joins
3222
            if (this.joins.length) {
162,003✔
3223
                this.joins.forEach((join) => {
46,539✔
3224
                    if (join.select && !join.selection) {
368,283✔
3225
                        // if (join.selection) {
3226
                        //
3227
                        // } else {
3228
                        if (join.type === "inner") {
367,367!
UNCOV
3229
                            this.innerJoinAndSelect(
×
3230
                                `${join.parentAlias}.${join.relationMetadata.propertyPath}`,
3231
                                join.alias,
3232
                            )
3233
                        } else {
3234
                            this.leftJoinAndSelect(
367,367✔
3235
                                `${join.parentAlias}.${join.relationMetadata.propertyPath}`,
3236
                                join.alias,
3237
                            )
3238
                        }
3239
                        // }
3240
                    } else {
3241
                        if (join.type === "inner") {
916!
UNCOV
3242
                            this.innerJoin(
×
3243
                                `${join.parentAlias}.${join.relationMetadata.propertyPath}`,
3244
                                join.alias,
3245
                            )
3246
                        } else {
3247
                            this.leftJoin(
916✔
3248
                                `${join.parentAlias}.${join.relationMetadata.propertyPath}`,
3249
                                join.alias,
3250
                            )
3251
                        }
3252
                    }
3253

3254
                    // if (join.select) {
3255
                    //     if (this.findOptions.loadEagerRelations !== false) {
3256
                    //         FindOptionsUtils.joinEagerRelations(
3257
                    //             this,
3258
                    //             join.alias,
3259
                    //             join.relationMetadata.inverseEntityMetadata
3260
                    //         );
3261
                    //     }
3262
                    // }
3263
                })
3264
            }
3265

3266
            // if (this.conditions.length) {
3267
            //     this.where(this.conditions.join(" AND "));
3268
            // }
3269

3270
            // apply offset
3271
            if (this.findOptions.skip !== undefined) {
162,003✔
3272
                // if (this.findOptions.options && this.findOptions.options.pagination === false) {
3273
                //     this.offset(this.findOptions.skip);
3274
                // } else {
3275
                this.skip(this.findOptions.skip)
148✔
3276
                // }
3277
            }
3278

3279
            // apply limit
3280
            if (this.findOptions.take !== undefined) {
161,979✔
3281
                // if (this.findOptions.options && this.findOptions.options.pagination === false) {
3282
                //     this.limit(this.findOptions.take);
3283
                // } else {
3284
                this.take(this.findOptions.take)
13,782✔
3285
                // }
3286
            }
3287

3288
            // apply caching options
3289
            if (typeof this.findOptions.cache === "number") {
161,955✔
3290
                this.cache(this.findOptions.cache)
48✔
3291
            } else if (typeof this.findOptions.cache === "boolean") {
161,907✔
3292
                this.cache(this.findOptions.cache)
72✔
3293
            } else if (typeof this.findOptions.cache === "object") {
161,835✔
3294
                this.cache(
120✔
3295
                    this.findOptions.cache.id,
3296
                    this.findOptions.cache.milliseconds,
3297
                )
3298
            }
3299

3300
            if (this.findOptions.join) {
161,955✔
3301
                if (this.findOptions.join.leftJoin)
557!
UNCOV
3302
                    Object.keys(this.findOptions.join.leftJoin).forEach(
×
3303
                        (key) => {
UNCOV
3304
                            this.leftJoin(
×
3305
                                this.findOptions.join!.leftJoin![key],
3306
                                key,
3307
                            )
3308
                        },
3309
                    )
3310

3311
                if (this.findOptions.join.innerJoin)
557!
UNCOV
3312
                    Object.keys(this.findOptions.join.innerJoin).forEach(
×
3313
                        (key) => {
UNCOV
3314
                            this.innerJoin(
×
3315
                                this.findOptions.join!.innerJoin![key],
3316
                                key,
3317
                            )
3318
                        },
3319
                    )
3320

3321
                if (this.findOptions.join.leftJoinAndSelect)
557✔
3322
                    Object.keys(
336✔
3323
                        this.findOptions.join.leftJoinAndSelect,
3324
                    ).forEach((key) => {
3325
                        this.leftJoinAndSelect(
504✔
3326
                            this.findOptions.join!.leftJoinAndSelect![key],
3327
                            key,
3328
                        )
3329
                    })
3330

3331
                if (this.findOptions.join.innerJoinAndSelect)
557✔
3332
                    Object.keys(
221✔
3333
                        this.findOptions.join.innerJoinAndSelect,
3334
                    ).forEach((key) => {
3335
                        this.innerJoinAndSelect(
226✔
3336
                            this.findOptions.join!.innerJoinAndSelect![key],
3337
                            key,
3338
                        )
3339
                    })
3340
            }
3341

3342
            if (this.findOptions.lock) {
161,955✔
3343
                if (this.findOptions.lock.mode === "optimistic") {
374✔
3344
                    this.setLock(
208✔
3345
                        this.findOptions.lock.mode,
3346
                        this.findOptions.lock.version,
3347
                    )
3348
                } else if (
166✔
3349
                    this.findOptions.lock.mode === "pessimistic_read" ||
371✔
3350
                    this.findOptions.lock.mode === "pessimistic_write" ||
3351
                    this.findOptions.lock.mode === "dirty_read" ||
3352
                    this.findOptions.lock.mode ===
3353
                        "pessimistic_partial_write" ||
3354
                    this.findOptions.lock.mode ===
3355
                        "pessimistic_write_or_fail" ||
3356
                    this.findOptions.lock.mode === "for_no_key_update" ||
3357
                    this.findOptions.lock.mode === "for_key_share"
3358
                ) {
3359
                    const tableNames = this.findOptions.lock.tables
166✔
3360
                        ? this.findOptions.lock.tables.map((table) => {
3361
                              const tableAlias =
3362
                                  this.expressionMap.aliases.find((alias) => {
39✔
3363
                                      return (
59✔
3364
                                          alias.metadata
3365
                                              .tableNameWithoutPrefix === table
3366
                                      )
3367
                                  })
3368
                              if (!tableAlias) {
39✔
3369
                                  throw new TypeORMError(
5✔
3370
                                      `"${table}" is not part of this query`,
3371
                                  )
3372
                              }
3373
                              return this.escape(tableAlias.name)
34✔
3374
                          })
3375
                        : undefined
3376
                    this.setLock(
161✔
3377
                        this.findOptions.lock.mode,
3378
                        undefined,
3379
                        tableNames,
3380
                    )
3381

3382
                    if (this.findOptions.lock.onLocked) {
161✔
3383
                        this.setOnLocked(this.findOptions.lock.onLocked)
10✔
3384
                    }
3385
                }
3386
            }
3387

3388
            if (this.findOptions.loadRelationIds === true) {
161,950✔
3389
                this.loadAllRelationIds()
24✔
3390
            } else if (typeof this.findOptions.loadRelationIds === "object") {
161,926✔
3391
                this.loadAllRelationIds(this.findOptions.loadRelationIds as any)
100,696✔
3392
            }
3393

3394
            if (this.findOptions.loadEagerRelations !== false) {
161,950✔
3395
                FindOptionsUtils.joinEagerRelations(
61,206✔
3396
                    this,
3397
                    this.expressionMap.mainAlias!.name,
3398
                    this.expressionMap.mainAlias!.metadata,
3399
                )
3400
            }
3401

3402
            if (this.findOptions.transaction === true) {
161,950✔
3403
                this.expressionMap.useTransaction = true
24✔
3404
            }
3405

3406
            // if (this.orderBys.length) {
3407
            //     this.orderBys.forEach(orderBy => {
3408
            //         this.addOrderBy(orderBy.alias, orderBy.direction, orderBy.nulls);
3409
            //     });
3410
            // }
3411

3412
            // todo
3413
            // if (this.options.options && this.options.options.eagerRelations) {
3414
            //     this.queryBuilder
3415
            // }
3416

3417
            // todo
3418
            // if (this.findOptions.options && this.findOptions.listeners === false) {
3419
            //     this.callListeners(false);
3420
            // }
3421
        }
3422
    }
3423

3424
    public concatRelationMetadata(relationMetadata: RelationMetadata) {
3425
        this.relationMetadatas.push(relationMetadata)
16✔
3426
    }
3427

3428
    /**
3429
     * Executes sql generated by query builder and returns object with raw results and entities created from them.
3430
     */
3431
    protected async executeEntitiesAndRawResults(
3432
        queryRunner: QueryRunner,
3433
    ): Promise<{ entities: Entity[]; raw: any[] }> {
3434
        if (!this.expressionMap.mainAlias)
147,191!
UNCOV
3435
            throw new TypeORMError(
×
3436
                `Alias is not set. Use "from" method to set an alias.`,
3437
            )
3438

3439
        if (
147,191✔
3440
            (this.expressionMap.lockMode === "pessimistic_read" ||
882,297✔
3441
                this.expressionMap.lockMode === "pessimistic_write" ||
3442
                this.expressionMap.lockMode === "pessimistic_partial_write" ||
3443
                this.expressionMap.lockMode === "pessimistic_write_or_fail" ||
3444
                this.expressionMap.lockMode === "for_no_key_update" ||
3445
                this.expressionMap.lockMode === "for_key_share") &&
3446
            !queryRunner.isTransactionActive
3447
        )
3448
            throw new PessimisticLockTransactionRequiredError()
71✔
3449

3450
        if (this.expressionMap.lockMode === "optimistic") {
147,120✔
3451
            const metadata = this.expressionMap.mainAlias.metadata
208✔
3452
            if (!metadata.versionColumn && !metadata.updateDateColumn)
208✔
3453
                throw new NoVersionOrUpdateDateColumnError(metadata.name)
24✔
3454
        }
3455

3456
        const relationIdLoader = new RelationIdLoader(
147,096✔
3457
            this.connection,
3458
            queryRunner,
3459
            this.expressionMap.relationIdAttributes,
3460
        )
3461
        const relationCountLoader = new RelationCountLoader(
147,096✔
3462
            this.connection,
3463
            queryRunner,
3464
            this.expressionMap.relationCountAttributes,
3465
        )
3466
        const relationIdMetadataTransformer =
3467
            new RelationIdMetadataToAttributeTransformer(this.expressionMap)
147,096✔
3468
        relationIdMetadataTransformer.transform()
147,096✔
3469
        const relationCountMetadataTransformer =
3470
            new RelationCountMetadataToAttributeTransformer(this.expressionMap)
147,096✔
3471
        relationCountMetadataTransformer.transform()
147,096✔
3472

3473
        let rawResults: any[] = [],
147,096✔
3474
            entities: any[] = []
147,096✔
3475

3476
        // for pagination enabled (e.g. skip and take) its much more complicated - its a special process
3477
        // where we make two queries to find the data we need
3478
        // first query find ids in skip and take range
3479
        // and second query loads the actual data in given ids range
3480
        if (
147,096✔
3481
            (this.expressionMap.skip || this.expressionMap.take) &&
308,036✔
3482
            this.expressionMap.joinAttributes.length > 0
3483
        ) {
3484
            // we are skipping order by here because its not working in subqueries anyway
3485
            // to make order by working we need to apply it on a distinct query
3486
            const [selects, orderBys] =
3487
                this.createOrderByCombinedWithSelectExpression("distinctAlias")
6,303✔
3488
            const metadata = this.expressionMap.mainAlias.metadata
6,303✔
3489
            const mainAliasName = this.expressionMap.mainAlias.name
6,303✔
3490

3491
            const querySelects = metadata.primaryColumns.map(
6,303✔
3492
                (primaryColumn) => {
3493
                    const distinctAlias = this.escape("distinctAlias")
6,379✔
3494
                    const columnAlias = this.escape(
6,379✔
3495
                        DriverUtils.buildAlias(
3496
                            this.connection.driver,
3497
                            undefined,
3498
                            mainAliasName,
3499
                            primaryColumn.databaseName,
3500
                        ),
3501
                    )
3502
                    if (!orderBys[columnAlias])
6,379✔
3503
                        // make sure we aren't overriding user-defined order in inverse direction
3504
                        orderBys[columnAlias] = "ASC"
6,379✔
3505

3506
                    const alias = DriverUtils.buildAlias(
6,379✔
3507
                        this.connection.driver,
3508
                        undefined,
3509
                        "ids_" + mainAliasName,
3510
                        primaryColumn.databaseName,
3511
                    )
3512

3513
                    return `${distinctAlias}.${columnAlias} AS ${this.escape(
6,379✔
3514
                        alias,
3515
                    )}`
3516
                },
3517
            )
3518

3519
            const originalQuery = this.clone()
6,303✔
3520

3521
            // preserve original timeTravel value since we set it to "false" in subquery
3522
            const originalQueryTimeTravel =
3523
                originalQuery.expressionMap.timeTravel
6,303✔
3524

3525
            rawResults = await new SelectQueryBuilder(
6,303✔
3526
                this.connection,
3527
                queryRunner,
3528
            )
3529
                .select(`DISTINCT ${querySelects.join(", ")}`)
3530
                .addSelect(selects)
3531
                .from(
3532
                    `(${originalQuery
3533
                        .orderBy()
3534
                        .timeTravelQuery(false) // set it to "false" since time travel clause must appear at the very end and applies to the entire SELECT clause.
3535
                        .getQuery()})`,
3536
                    "distinctAlias",
3537
                )
3538
                .timeTravelQuery(originalQueryTimeTravel)
3539
                .offset(this.expressionMap.skip)
3540
                .limit(this.expressionMap.take)
3541
                .orderBy(orderBys)
3542
                .cache(
3543
                    this.expressionMap.cache && this.expressionMap.cacheId
12,678✔
3544
                        ? `${this.expressionMap.cacheId}-pagination`
3545
                        : this.expressionMap.cache,
3546
                    this.expressionMap.cacheDuration,
3547
                )
3548
                .setParameters(this.getParameters())
3549
                .setNativeParameters(this.expressionMap.nativeParameters)
3550
                .getRawMany()
3551

3552
            if (rawResults.length > 0) {
6,292✔
3553
                let condition = ""
6,182✔
3554
                const parameters: ObjectLiteral = {}
6,182✔
3555
                if (metadata.hasMultiplePrimaryKeys) {
6,182✔
3556
                    condition = rawResults
76✔
3557
                        .map((result, index) => {
3558
                            return metadata.primaryColumns
388✔
3559
                                .map((primaryColumn) => {
3560
                                    const paramKey = `orm_distinct_ids_${index}_${primaryColumn.databaseName}`
776✔
3561
                                    const paramKeyResult =
3562
                                        DriverUtils.buildAlias(
776✔
3563
                                            this.connection.driver,
3564
                                            undefined,
3565
                                            "ids_" + mainAliasName,
3566
                                            primaryColumn.databaseName,
3567
                                        )
3568
                                    parameters[paramKey] =
776✔
3569
                                        result[paramKeyResult]
3570
                                    return `${mainAliasName}.${primaryColumn.propertyPath}=:${paramKey}`
776✔
3571
                                })
3572
                                .join(" AND ")
3573
                        })
3574
                        .join(" OR ")
3575
                } else {
3576
                    const alias = DriverUtils.buildAlias(
6,106✔
3577
                        this.connection.driver,
3578
                        undefined,
3579
                        "ids_" + mainAliasName,
3580
                        metadata.primaryColumns[0].databaseName,
3581
                    )
3582

3583
                    const ids = rawResults.map((result) => result[alias])
12,654✔
3584
                    const areAllNumbers = ids.every(
6,106✔
3585
                        (id: any) => typeof id === "number",
12,628✔
3586
                    )
3587
                    if (areAllNumbers) {
6,106✔
3588
                        // fixes #190. if all numbers then its safe to perform query without parameter
3589
                        condition = `${mainAliasName}.${
5,779✔
3590
                            metadata.primaryColumns[0].propertyPath
3591
                        } IN (${ids.join(", ")})`
3592
                    } else {
3593
                        parameters["orm_distinct_ids"] = ids
327✔
3594
                        condition =
327✔
3595
                            mainAliasName +
3596
                            "." +
3597
                            metadata.primaryColumns[0].propertyPath +
3598
                            " IN (:...orm_distinct_ids)"
3599
                    }
3600
                }
3601
                rawResults = await this.clone()
6,182✔
3602
                    .mergeExpressionMap({
3603
                        extraAppendedAndWhereCondition: condition,
3604
                    })
3605
                    .setParameters(parameters)
3606
                    .loadRawResults(queryRunner)
3607
            }
3608
        } else {
3609
            rawResults = await this.loadRawResults(queryRunner)
140,793✔
3610
        }
3611

3612
        if (rawResults.length > 0) {
146,979✔
3613
            // transform raw results into entities
3614
            const rawRelationIdResults = await relationIdLoader.load(rawResults)
50,643✔
3615
            const rawRelationCountResults = await relationCountLoader.load(
50,643✔
3616
                rawResults,
3617
            )
3618
            const transformer = new RawSqlResultsToEntityTransformer(
50,643✔
3619
                this.expressionMap,
3620
                this.connection.driver,
3621
                rawRelationIdResults,
3622
                rawRelationCountResults,
3623
                this.queryRunner,
3624
            )
3625
            entities = transformer.transform(
50,643✔
3626
                rawResults,
3627
                this.expressionMap.mainAlias!,
3628
            )
3629

3630
            // broadcast all "after load" events
3631
            if (
50,639✔
3632
                this.expressionMap.callListeners === true &&
101,278✔
3633
                this.expressionMap.mainAlias.hasMetadata
3634
            ) {
3635
                await queryRunner.broadcaster.broadcast(
50,639✔
3636
                    "Load",
3637
                    this.expressionMap.mainAlias.metadata,
3638
                    entities,
3639
                )
3640
            }
3641
        }
3642

3643
        if (this.expressionMap.relationLoadStrategy === "query") {
146,975✔
3644
            const queryStrategyRelationIdLoader =
3645
                new QueryStrategyRelationIdLoader(this.connection, queryRunner)
50✔
3646

3647
            await Promise.all(
50✔
3648
                this.relationMetadatas.map(async (relation) => {
3649
                    const relationTarget = relation.inverseEntityMetadata.target
16✔
3650
                    const relationAlias =
3651
                        relation.inverseEntityMetadata.targetName
16✔
3652

3653
                    const select = Array.isArray(this.findOptions.select)
16!
3654
                        ? OrmUtils.propertyPathsToTruthyObject(
3655
                              this.findOptions.select as string[],
3656
                          )
3657
                        : this.findOptions.select
3658
                    const relations = Array.isArray(this.findOptions.relations)
16!
3659
                        ? OrmUtils.propertyPathsToTruthyObject(
3660
                              this.findOptions.relations,
3661
                          )
3662
                        : this.findOptions.relations
3663

3664
                    const queryBuilder = this.createQueryBuilder(queryRunner)
16✔
3665
                        .select(relationAlias)
3666
                        .from(relationTarget, relationAlias)
3667
                        .setFindOptions({
3668
                            select: select
16!
3669
                                ? OrmUtils.deepValue(
3670
                                      select,
3671
                                      relation.propertyPath,
3672
                                  )
3673
                                : undefined,
3674
                            order: this.findOptions.order
16!
3675
                                ? OrmUtils.deepValue(
3676
                                      this.findOptions.order,
3677
                                      relation.propertyPath,
3678
                                  )
3679
                                : undefined,
3680
                            relations: relations
16✔
3681
                                ? OrmUtils.deepValue(
3682
                                      relations,
3683
                                      relation.propertyPath,
3684
                                  )
3685
                                : undefined,
3686
                            withDeleted: this.findOptions.withDeleted,
3687
                            relationLoadStrategy:
3688
                                this.findOptions.relationLoadStrategy,
3689
                        })
3690
                    if (entities.length > 0) {
16✔
3691
                        const relatedEntityGroups: any[] =
3692
                            await queryStrategyRelationIdLoader.loadManyToManyRelationIdsAndGroup(
16✔
3693
                                relation,
3694
                                entities,
3695
                                undefined,
3696
                                queryBuilder,
3697
                            )
3698
                        entities.forEach((entity) => {
16✔
3699
                            const relatedEntityGroup = relatedEntityGroups.find(
20✔
3700
                                (group) => group.entity === entity,
24✔
3701
                            )
3702
                            if (relatedEntityGroup) {
20✔
3703
                                const value =
3704
                                    relatedEntityGroup.related === undefined
20!
3705
                                        ? null
3706
                                        : relatedEntityGroup.related
3707
                                relation.setEntityValue(entity, value)
20✔
3708
                            }
3709
                        })
3710
                    }
3711
                }),
3712
            )
3713
        }
3714

3715
        return {
146,975✔
3716
            raw: rawResults,
3717
            entities: entities,
3718
        }
3719
    }
3720

3721
    protected createOrderByCombinedWithSelectExpression(
3722
        parentAlias: string,
3723
    ): [string, OrderByCondition] {
3724
        // if table has a default order then apply it
3725
        const orderBys = this.expressionMap.allOrderBys
6,303✔
3726
        const selectString = Object.keys(orderBys)
6,303✔
3727
            .map((orderCriteria) => {
3728
                if (orderCriteria.indexOf(".") !== -1) {
276✔
3729
                    const criteriaParts = orderCriteria.split(".")
264✔
3730
                    const aliasName = criteriaParts[0]
264✔
3731
                    const propertyPath = criteriaParts.slice(1).join(".")
264✔
3732
                    const alias = this.expressionMap.findAliasByName(aliasName)
264✔
3733
                    const column =
3734
                        alias.metadata.findColumnWithPropertyPath(propertyPath)
264✔
3735
                    return (
264✔
3736
                        this.escape(parentAlias) +
3737
                        "." +
3738
                        this.escape(
3739
                            DriverUtils.buildAlias(
3740
                                this.connection.driver,
3741
                                undefined,
3742
                                aliasName,
3743
                                column!.databaseName,
3744
                            ),
3745
                        )
3746
                    )
3747
                } else {
3748
                    if (
12✔
3749
                        this.expressionMap.selects.find(
3750
                            (select) =>
3751
                                select.selection === orderCriteria ||
24✔
3752
                                select.aliasName === orderCriteria,
3753
                        )
3754
                    )
3755
                        return (
8✔
3756
                            this.escape(parentAlias) +
3757
                            "." +
3758
                            this.escape(orderCriteria)
3759
                        )
3760

3761
                    return ""
4✔
3762
                }
3763
            })
3764
            .join(", ")
3765

3766
        const orderByObject: OrderByCondition = {}
6,303✔
3767
        Object.keys(orderBys).forEach((orderCriteria) => {
6,303✔
3768
            if (orderCriteria.indexOf(".") !== -1) {
276✔
3769
                const criteriaParts = orderCriteria.split(".")
264✔
3770
                const aliasName = criteriaParts[0]
264✔
3771
                const propertyPath = criteriaParts.slice(1).join(".")
264✔
3772
                const alias = this.expressionMap.findAliasByName(aliasName)
264✔
3773
                const column =
3774
                    alias.metadata.findColumnWithPropertyPath(propertyPath)
264✔
3775
                orderByObject[
264✔
3776
                    this.escape(parentAlias) +
3777
                        "." +
3778
                        this.escape(
3779
                            DriverUtils.buildAlias(
3780
                                this.connection.driver,
3781
                                undefined,
3782
                                aliasName,
3783
                                column!.databaseName,
3784
                            ),
3785
                        )
3786
                ] = orderBys[orderCriteria]
3787
            } else {
3788
                if (
12✔
3789
                    this.expressionMap.selects.find(
3790
                        (select) =>
3791
                            select.selection === orderCriteria ||
24✔
3792
                            select.aliasName === orderCriteria,
3793
                    )
3794
                ) {
3795
                    orderByObject[
8✔
3796
                        this.escape(parentAlias) +
3797
                            "." +
3798
                            this.escape(orderCriteria)
3799
                    ] = orderBys[orderCriteria]
3800
                } else {
3801
                    orderByObject[orderCriteria] = orderBys[orderCriteria]
4✔
3802
                }
3803
            }
3804
        })
3805

3806
        return [selectString, orderByObject]
6,303✔
3807
    }
3808

3809
    /**
3810
     * Loads raw results from the database.
3811
     */
3812
    protected async loadRawResults(queryRunner: QueryRunner) {
3813
        const [sql, parameters] = this.getQueryAndParameters()
170,652✔
3814
        const queryId =
3815
            sql +
170,554✔
3816
            " -- PARAMETERS: " +
3817
            JSON.stringify(parameters, (_, value) =>
3818
                typeof value === "bigint" ? value.toString() : value,
350,393!
3819
            )
3820
        const cacheOptions =
3821
            typeof this.connection.options.cache === "object"
170,554✔
3822
                ? this.connection.options.cache
3823
                : {}
3824
        let savedQueryResultCacheOptions: QueryResultCacheOptions | undefined =
3825
            undefined
170,554✔
3826
        const isCachingEnabled =
3827
            // Caching is enabled globally and isn't disabled locally.
3828
            (cacheOptions.alwaysEnabled &&
170,554✔
3829
                this.expressionMap.cache !== false) ||
3830
            // ...or it's enabled locally explicitly.
3831
            this.expressionMap.cache === true
3832
        let cacheError = false
170,554✔
3833
        if (this.connection.queryResultCache && isCachingEnabled) {
170,554✔
3834
            try {
1,000✔
3835
                savedQueryResultCacheOptions =
1,000✔
3836
                    await this.connection.queryResultCache.getFromCache(
3837
                        {
3838
                            identifier: this.expressionMap.cacheId,
3839
                            query: queryId,
3840
                            duration:
3841
                                this.expressionMap.cacheDuration ||
2,136✔
3842
                                cacheOptions.duration ||
3843
                                1000,
3844
                        },
3845
                        queryRunner,
3846
                    )
3847
                if (
1,000✔
3848
                    savedQueryResultCacheOptions &&
1,608✔
3849
                    !this.connection.queryResultCache.isExpired(
3850
                        savedQueryResultCacheOptions,
3851
                    )
3852
                ) {
3853
                    return JSON.parse(savedQueryResultCacheOptions.result)
392✔
3854
                }
3855
            } catch (error) {
UNCOV
3856
                if (!cacheOptions.ignoreErrors) {
×
UNCOV
3857
                    throw error
×
3858
                }
UNCOV
3859
                cacheError = true
×
3860
            }
3861
        }
3862

3863
        const results = await queryRunner.query(sql, parameters, true)
170,162✔
3864

3865
        if (
170,143✔
3866
            !cacheError &&
342,230✔
3867
            this.connection.queryResultCache &&
3868
            isCachingEnabled
3869
        ) {
3870
            try {
608✔
3871
                await this.connection.queryResultCache.storeInCache(
608✔
3872
                    {
3873
                        identifier: this.expressionMap.cacheId,
3874
                        query: queryId,
3875
                        time: Date.now(),
3876
                        duration:
3877
                            this.expressionMap.cacheDuration ||
1,344✔
3878
                            cacheOptions.duration ||
3879
                            1000,
3880
                        result: JSON.stringify(results.records),
3881
                    },
3882
                    savedQueryResultCacheOptions,
3883
                    queryRunner,
3884
                )
3885
            } catch (error) {
UNCOV
3886
                if (!cacheOptions.ignoreErrors) {
×
UNCOV
3887
                    throw error
×
3888
                }
3889
            }
3890
        }
3891

3892
        return results.records
170,143✔
3893
    }
3894

3895
    /**
3896
     * Merges into expression map given expression map properties.
3897
     */
3898
    protected mergeExpressionMap(
3899
        expressionMap: Partial<QueryExpressionMap>,
3900
    ): this {
3901
        ObjectUtils.assign(this.expressionMap, expressionMap)
6,182✔
3902
        return this
6,182✔
3903
    }
3904

3905
    /**
3906
     * Normalizes a give number - converts to int if possible.
3907
     */
3908
    protected normalizeNumber(num: any) {
3909
        if (typeof num === "number" || num === undefined || num === null)
32,566✔
3910
            return num
32,470✔
3911

3912
        return Number(num)
96✔
3913
    }
3914

3915
    /**
3916
     * Creates a query builder used to execute sql queries inside this query builder.
3917
     */
3918
    protected obtainQueryRunner() {
3919
        return (
170,789✔
3920
            this.queryRunner ||
211,235✔
3921
            this.connection.createQueryRunner(
3922
                this.connection.defaultReplicationModeForReads(),
3923
            )
3924
        )
3925
    }
3926

3927
    protected buildSelect(
3928
        select: FindOptionsSelect<any>,
3929
        metadata: EntityMetadata,
3930
        alias: string,
3931
        embedPrefix?: string,
3932
    ) {
3933
        for (const key in select) {
945✔
3934
            if (select[key] === undefined || select[key] === false) continue
1,708✔
3935

3936
            const propertyPath = embedPrefix ? embedPrefix + "." + key : key
1,636✔
3937
            const column =
3938
                metadata.findColumnWithPropertyPathStrict(propertyPath)
1,636✔
3939
            const embed = metadata.findEmbeddedWithPropertyPath(propertyPath)
1,636✔
3940
            const relation = metadata.findRelationWithPropertyPath(propertyPath)
1,636✔
3941

3942
            if (!embed && !column && !relation)
1,636✔
3943
                throw new EntityPropertyNotFoundError(propertyPath, metadata)
24✔
3944

3945
            if (column) {
1,612✔
3946
                this.selects.push(alias + "." + propertyPath)
1,238✔
3947
                // this.addSelect(alias + "." + propertyPath);
3948
            } else if (embed) {
374✔
3949
                this.buildSelect(
62✔
3950
                    select[key] as FindOptionsSelect<any>,
3951
                    metadata,
3952
                    alias,
3953
                    propertyPath,
3954
                )
3955

3956
                // } else if (relation) {
3957
                //     const joinAlias = alias + "_" + relation.propertyName;
3958
                //     const existJoin = this.joins.find(join => join.alias === joinAlias);
3959
                //     if (!existJoin) {
3960
                //         this.joins.push({
3961
                //             type: "left",
3962
                //             select: false,
3963
                //             alias: joinAlias,
3964
                //             parentAlias: alias,
3965
                //             relationMetadata: relation
3966
                //         });
3967
                //     }
3968
                //     this.buildOrder(select[key] as FindOptionsOrder<any>, relation.inverseEntityMetadata, joinAlias);
3969
            }
3970
        }
3971
    }
3972

3973
    protected buildRelations(
3974
        relations: FindOptionsRelations<any>,
3975
        selection: FindOptionsSelect<any> | undefined,
3976
        metadata: EntityMetadata,
3977
        alias: string,
3978
        embedPrefix?: string,
3979
    ) {
3980
        if (!relations) return
47,171!
3981

3982
        Object.keys(relations).forEach((relationName) => {
47,171✔
3983
            const relationValue = (relations as any)[relationName]
367,761✔
3984
            const propertyPath = embedPrefix
367,761✔
3985
                ? embedPrefix + "." + relationName
3986
                : relationName
3987
            const embed = metadata.findEmbeddedWithPropertyPath(propertyPath)
367,761✔
3988
            const relation = metadata.findRelationWithPropertyPath(propertyPath)
367,761✔
3989
            if (!embed && !relation)
367,761✔
3990
                throw new EntityPropertyNotFoundError(propertyPath, metadata)
144✔
3991

3992
            if (embed) {
367,617✔
3993
                this.buildRelations(
120✔
3994
                    relationValue,
3995
                    typeof selection === "object"
120!
3996
                        ? OrmUtils.deepValue(selection, embed.propertyPath)
3997
                        : undefined,
3998
                    metadata,
3999
                    alias,
4000
                    propertyPath,
4001
                )
4002
            } else if (relation) {
367,497✔
4003
                let joinAlias = alias + "_" + propertyPath.replace(".", "_")
367,497✔
4004
                joinAlias = DriverUtils.buildAlias(
367,497✔
4005
                    this.connection.driver,
4006
                    { joiner: "__" },
4007
                    alias,
4008
                    joinAlias,
4009
                )
4010
                if (
367,497✔
4011
                    relationValue === true ||
368,321✔
4012
                    typeof relationValue === "object"
4013
                ) {
4014
                    if (this.expressionMap.relationLoadStrategy === "query") {
367,497✔
4015
                        this.concatRelationMetadata(relation)
12✔
4016
                    } else {
4017
                        // join
4018
                        this.joins.push({
367,485✔
4019
                            type: "left",
4020
                            select: true,
4021
                            selection:
4022
                                selection &&
735,354✔
4023
                                typeof selection[relationName] === "object"
4024
                                    ? (selection[
4025
                                          relationName
4026
                                      ] as FindOptionsSelect<any>)
4027
                                    : undefined,
4028
                            alias: joinAlias,
4029
                            parentAlias: alias,
4030
                            relationMetadata: relation,
4031
                        })
4032

4033
                        if (
367,485✔
4034
                            selection &&
367,869✔
4035
                            typeof selection[relationName] === "object"
4036
                        ) {
4037
                            this.buildSelect(
264✔
4038
                                selection[
4039
                                    relationName
4040
                                ] as FindOptionsSelect<any>,
4041
                                relation.inverseEntityMetadata,
4042
                                joinAlias,
4043
                            )
4044
                        }
4045
                    }
4046
                }
4047

4048
                if (
367,497✔
4049
                    typeof relationValue === "object" &&
368,321✔
4050
                    this.expressionMap.relationLoadStrategy === "join"
4051
                ) {
4052
                    this.buildRelations(
824✔
4053
                        relationValue,
4054
                        typeof selection === "object"
824✔
4055
                            ? OrmUtils.deepValue(
4056
                                  selection,
4057
                                  relation.propertyPath,
4058
                              )
4059
                            : undefined,
4060
                        relation.inverseEntityMetadata,
4061
                        joinAlias,
4062
                        undefined,
4063
                    )
4064
                }
4065
            }
4066
        })
4067
    }
4068

4069
    protected buildEagerRelations(
4070
        relations: FindOptionsRelations<any>,
4071
        selection: FindOptionsSelect<any> | undefined,
4072
        metadata: EntityMetadata,
4073
        alias: string,
4074
        embedPrefix?: string,
4075
    ) {
4076
        if (!relations) return
46,931!
4077

4078
        Object.keys(relations).forEach((relationName) => {
46,931✔
4079
            const relationValue = (relations as any)[relationName]
367,485✔
4080
            const propertyPath = embedPrefix
367,485✔
4081
                ? embedPrefix + "." + relationName
4082
                : relationName
4083
            const embed = metadata.findEmbeddedWithPropertyPath(propertyPath)
367,485✔
4084
            const relation = metadata.findRelationWithPropertyPath(propertyPath)
367,485✔
4085
            if (!embed && !relation)
367,485!
UNCOV
4086
                throw new EntityPropertyNotFoundError(propertyPath, metadata)
×
4087

4088
            if (embed) {
367,485✔
4089
                this.buildEagerRelations(
96✔
4090
                    relationValue,
4091
                    typeof selection === "object"
96!
4092
                        ? OrmUtils.deepValue(selection, embed.propertyPath)
4093
                        : undefined,
4094
                    metadata,
4095
                    alias,
4096
                    propertyPath,
4097
                )
4098
            } else if (relation) {
367,389✔
4099
                let joinAlias = alias + "_" + propertyPath.replace(".", "_")
367,389✔
4100
                joinAlias = DriverUtils.buildAlias(
367,389✔
4101
                    this.connection.driver,
4102
                    { joiner: "__" },
4103
                    alias,
4104
                    joinAlias,
4105
                )
4106

4107
                if (
367,389✔
4108
                    relationValue === true ||
368,165✔
4109
                    typeof relationValue === "object"
4110
                ) {
4111
                    relation.inverseEntityMetadata.eagerRelations.forEach(
367,389✔
4112
                        (eagerRelation) => {
4113
                            let eagerRelationJoinAlias =
4114
                                joinAlias +
362✔
4115
                                "_" +
4116
                                eagerRelation.propertyPath.replace(".", "_")
4117
                            eagerRelationJoinAlias = DriverUtils.buildAlias(
362✔
4118
                                this.connection.driver,
4119
                                { joiner: "__" },
4120
                                joinAlias,
4121
                                eagerRelationJoinAlias,
4122
                            )
4123

4124
                            const existJoin = this.joins.find(
362✔
4125
                                (join) => join.alias === eagerRelationJoinAlias,
1,802✔
4126
                            )
4127
                            if (!existJoin) {
362✔
4128
                                this.joins.push({
242✔
4129
                                    type: "left",
4130
                                    select: true,
4131
                                    alias: eagerRelationJoinAlias,
4132
                                    parentAlias: joinAlias,
4133
                                    selection: undefined,
4134
                                    relationMetadata: eagerRelation,
4135
                                })
4136
                            }
4137

4138
                            if (
362!
4139
                                selection &&
362!
4140
                                typeof selection[relationName] === "object"
4141
                            ) {
UNCOV
4142
                                this.buildSelect(
×
4143
                                    selection[
4144
                                        relationName
4145
                                    ] as FindOptionsSelect<any>,
4146
                                    relation.inverseEntityMetadata,
4147
                                    joinAlias,
4148
                                )
4149
                            }
4150
                        },
4151
                    )
4152
                }
4153

4154
                if (typeof relationValue === "object") {
367,389✔
4155
                    this.buildEagerRelations(
776✔
4156
                        relationValue,
4157
                        typeof selection === "object"
776✔
4158
                            ? OrmUtils.deepValue(
4159
                                  selection,
4160
                                  relation.propertyPath,
4161
                              )
4162
                            : undefined,
4163
                        relation.inverseEntityMetadata,
4164
                        joinAlias,
4165
                        undefined,
4166
                    )
4167
                }
4168
            }
4169
        })
4170
    }
4171

4172
    protected buildOrder(
4173
        order: FindOptionsOrder<any>,
4174
        metadata: EntityMetadata,
4175
        alias: string,
4176
        embedPrefix?: string,
4177
    ) {
4178
        for (const key in order) {
2,786✔
4179
            if (order[key] === undefined) continue
3,098!
4180

4181
            const propertyPath = embedPrefix ? embedPrefix + "." + key : key
3,098✔
4182
            const column =
4183
                metadata.findColumnWithPropertyPathStrict(propertyPath)
3,098✔
4184
            const embed = metadata.findEmbeddedWithPropertyPath(propertyPath)
3,098✔
4185
            const relation = metadata.findRelationWithPropertyPath(propertyPath)
3,098✔
4186

4187
            if (!embed && !column && !relation)
3,098✔
4188
                throw new EntityPropertyNotFoundError(propertyPath, metadata)
24✔
4189

4190
            if (column) {
3,074✔
4191
                let direction =
4192
                    typeof order[key] === "object"
2,522✔
4193
                        ? (order[key] as any).direction
4194
                        : order[key]
4195
                direction =
2,522✔
4196
                    direction === "DESC" ||
9,556✔
4197
                    direction === "desc" ||
4198
                    direction === -1
4199
                        ? "DESC"
4200
                        : "ASC"
4201
                let nulls =
4202
                    typeof order[key] === "object"
2,522✔
4203
                        ? (order[key] as any).nulls
4204
                        : undefined
4205
                nulls =
2,522✔
4206
                    nulls?.toLowerCase() === "first"
2,522✔
4207
                        ? "NULLS FIRST"
4208
                        : nulls?.toLowerCase() === "last"
2,502✔
4209
                        ? "NULLS LAST"
4210
                        : undefined
4211

4212
                const aliasPath = `${alias}.${propertyPath}`
2,522✔
4213
                // const selection = this.expressionMap.selects.find(
4214
                //     (s) => s.selection === aliasPath,
4215
                // )
4216
                // if (selection) {
4217
                //     // this is not building correctly now???
4218
                //     aliasPath = this.escape(
4219
                //         DriverUtils.buildAlias(
4220
                //             this.connection.driver,
4221
                //             undefined,
4222
                //             alias,
4223
                //             column.databaseName,
4224
                //         ),
4225
                //     )
4226
                //     // selection.aliasName = aliasPath
4227
                // } else {
4228
                //     if (column.isVirtualProperty && column.query) {
4229
                //         aliasPath = `(${column.query(alias)})`
4230
                //     }
4231
                // }
4232

4233
                // console.log("add sort", selection, aliasPath, direction, nulls)
4234
                this.addOrderBy(aliasPath, direction, nulls)
2,522✔
4235
                // this.orderBys.push({ alias: alias + "." + propertyPath, direction, nulls });
4236
            } else if (embed) {
552✔
4237
                this.buildOrder(
72✔
4238
                    order[key] as FindOptionsOrder<any>,
4239
                    metadata,
4240
                    alias,
4241
                    propertyPath,
4242
                )
4243
            } else if (relation) {
480✔
4244
                let joinAlias = alias + "_" + propertyPath.replace(".", "_")
480✔
4245
                joinAlias = DriverUtils.buildAlias(
480✔
4246
                    this.connection.driver,
4247
                    { joiner: "__" },
4248
                    alias,
4249
                    joinAlias,
4250
                )
4251
                // console.log("joinAlias", joinAlias, joinAlias.length, this.connection.driver.maxAliasLength)
4252
                // todo: use expressionMap.joinAttributes, and create a new one using
4253
                //  const joinAttribute = new JoinAttribute(this.connection, this.expressionMap);
4254

4255
                const existJoin = this.joins.find(
480✔
4256
                    (join) => join.alias === joinAlias,
576✔
4257
                )
4258
                if (!existJoin) {
480✔
4259
                    this.joins.push({
168✔
4260
                        type: "left",
4261
                        select: false,
4262
                        alias: joinAlias,
4263
                        parentAlias: alias,
4264
                        selection: undefined,
4265
                        relationMetadata: relation,
4266
                    })
4267
                }
4268
                this.buildOrder(
480✔
4269
                    order[key] as FindOptionsOrder<any>,
4270
                    relation.inverseEntityMetadata,
4271
                    joinAlias,
4272
                )
4273
            }
4274
        }
4275
    }
4276

4277
    protected buildWhere(
4278
        where: FindOptionsWhere<any>[] | FindOptionsWhere<any>,
4279
        metadata: EntityMetadata,
4280
        alias: string,
4281
        embedPrefix?: string,
4282
    ) {
4283
        let condition: string = ""
58,118✔
4284
        // let parameterIndex = Object.keys(this.expressionMap.nativeParameters).length;
4285
        if (Array.isArray(where)) {
58,118✔
4286
            if (where.length) {
172✔
4287
                condition = where
168✔
4288
                    .map((whereItem) => {
4289
                        return this.buildWhere(
336✔
4290
                            whereItem,
4291
                            metadata,
4292
                            alias,
4293
                            embedPrefix,
4294
                        )
4295
                    })
4296
                    .filter((condition) => !!condition)
336✔
4297
                    .map((condition) => "(" + condition + ")")
336✔
4298
                    .join(" OR ")
4299
            }
4300
        } else {
4301
            const andConditions: string[] = []
57,946✔
4302
            for (const key in where) {
57,946✔
4303
                if (where[key] === undefined || where[key] === null) continue
58,873✔
4304

4305
                const propertyPath = embedPrefix ? embedPrefix + "." + key : key
58,800✔
4306
                const column =
4307
                    metadata.findColumnWithPropertyPathStrict(propertyPath)
58,800✔
4308
                const embed =
4309
                    metadata.findEmbeddedWithPropertyPath(propertyPath)
58,800✔
4310
                const relation =
4311
                    metadata.findRelationWithPropertyPath(propertyPath)
58,800✔
4312

4313
                if (!embed && !column && !relation)
58,800✔
4314
                    throw new EntityPropertyNotFoundError(
48✔
4315
                        propertyPath,
4316
                        metadata,
4317
                    )
4318

4319
                if (column) {
58,752✔
4320
                    let aliasPath = `${alias}.${propertyPath}`
57,068✔
4321
                    if (column.isVirtualProperty && column.query) {
57,068✔
4322
                        aliasPath = `(${column.query(this.escape(alias))})`
96✔
4323
                    }
4324
                    // const parameterName = alias + "_" + propertyPath.split(".").join("_") + "_" + parameterIndex;
4325

4326
                    // todo: we need to handle other operators as well?
4327
                    let parameterValue = where[key]
57,068✔
4328
                    if (InstanceChecker.isEqualOperator(where[key])) {
57,068✔
4329
                        parameterValue = where[key].value
48✔
4330
                    }
4331

4332
                    if (column.transformer) {
57,068✔
4333
                        if (parameterValue instanceof FindOperator) {
110✔
4334
                            parameterValue.transformValue(column.transformer)
40✔
4335
                        } else {
4336
                            parameterValue = ApplyValueTransformers.transformTo(
70✔
4337
                                column.transformer,
4338
                                parameterValue,
4339
                            )
4340
                        }
4341
                    }
4342

4343
                    // MSSQL requires parameters to carry extra type information
4344
                    if (this.connection.driver.options.type === "mssql") {
57,068✔
4345
                        parameterValue = (
1,356✔
4346
                            this.connection.driver as SqlServerDriver
4347
                        ).parametrizeValues(column, parameterValue)
4348
                    }
4349

4350
                    // if (parameterValue === null) {
4351
                    //     andConditions.push(`${aliasPath} IS NULL`);
4352
                    //
4353
                    // } else if (parameterValue instanceof FindOperator) {
4354
                    //     // let parameters: any[] = [];
4355
                    //     // if (parameterValue.useParameter) {
4356
                    //     //     const realParameterValues: any[] = parameterValue.multipleParameters ? parameterValue.value : [parameterValue.value];
4357
                    //     //     realParameterValues.forEach((realParameterValue, realParameterValueIndex) => {
4358
                    //     //
4359
                    //     //         // don't create parameters for number to prevent max number of variables issues as much as possible
4360
                    //     //         if (typeof realParameterValue === "number") {
4361
                    //     //             parameters.push(realParameterValue);
4362
                    //     //
4363
                    //     //         } else {
4364
                    //     //             this.expressionMap.nativeParameters[parameterName + realParameterValueIndex] = realParameterValue;
4365
                    //     //             parameterIndex++;
4366
                    //     //             parameters.push(this.connection.driver.createParameter(parameterName + realParameterValueIndex, parameterIndex - 1));
4367
                    //     //         }
4368
                    //     //     });
4369
                    //     // }
4370
                    //     andConditions.push(
4371
                    //         this.createWhereConditionExpression(this.getWherePredicateCondition(aliasPath, parameterValue))
4372
                    //         // parameterValue.toSql(this.connection, aliasPath, parameters));
4373
                    //     )
4374
                    //
4375
                    // } else {
4376
                    //     this.expressionMap.nativeParameters[parameterName] = parameterValue;
4377
                    //     parameterIndex++;
4378
                    //     const parameter = this.connection.driver.createParameter(parameterName, parameterIndex - 1);
4379
                    //     andConditions.push(`${aliasPath} = ${parameter}`);
4380
                    // }
4381

4382
                    andConditions.push(
57,068✔
4383
                        this.createWhereConditionExpression(
4384
                            this.getWherePredicateCondition(
4385
                                aliasPath,
4386
                                parameterValue,
4387
                            ),
4388
                        ),
4389
                        // parameterValue.toSql(this.connection, aliasPath, parameters));
4390
                    )
4391

4392
                    // this.conditions.push(`${alias}.${propertyPath} = :${paramName}`);
4393
                    // this.expressionMap.parameters[paramName] = where[key]; // todo: handle functions and other edge cases
4394
                } else if (embed) {
1,684✔
4395
                    const condition = this.buildWhere(
480✔
4396
                        where[key],
4397
                        metadata,
4398
                        alias,
4399
                        propertyPath,
4400
                    )
4401
                    if (condition) andConditions.push(condition)
480✔
4402
                } else if (relation) {
1,204✔
4403
                    // if all properties of where are undefined we don't need to join anything
4404
                    // this can happen when user defines map with conditional queries inside
4405
                    if (typeof where[key] === "object") {
1,204✔
4406
                        const allAllUndefined = Object.keys(where[key]).every(
1,180✔
4407
                            (k) => where[key][k] === undefined,
1,204✔
4408
                        )
4409
                        if (allAllUndefined) {
1,180✔
4410
                            continue
24✔
4411
                        }
4412
                    }
4413

4414
                    if (InstanceChecker.isFindOperator(where[key])) {
1,180✔
4415
                        if (
528✔
4416
                            where[key].type === "moreThan" ||
1,488✔
4417
                            where[key].type === "lessThan" ||
4418
                            where[key].type === "moreThanOrEqual" ||
4419
                            where[key].type === "lessThanOrEqual"
4420
                        ) {
4421
                            let sqlOperator = ""
336✔
4422
                            if (where[key].type === "moreThan") {
336✔
4423
                                sqlOperator = ">"
144✔
4424
                            } else if (where[key].type === "lessThan") {
192✔
4425
                                sqlOperator = "<"
24✔
4426
                            } else if (where[key].type === "moreThanOrEqual") {
168✔
4427
                                sqlOperator = ">="
144✔
4428
                            } else if (where[key].type === "lessThanOrEqual") {
24✔
4429
                                sqlOperator = "<="
24✔
4430
                            }
4431
                            // basically relation count functionality
4432
                            const qb: QueryBuilder<any> = this.subQuery()
336✔
4433
                            if (relation.isManyToManyOwner) {
336✔
4434
                                qb.select("COUNT(*)")
144✔
4435
                                    .from(
4436
                                        relation.joinTableName,
4437
                                        relation.joinTableName,
4438
                                    )
4439
                                    .where(
4440
                                        relation.joinColumns
4441
                                            .map((column) => {
4442
                                                return `${
144✔
4443
                                                    relation.joinTableName
4444
                                                }.${
4445
                                                    column.propertyName
4446
                                                } = ${alias}.${
4447
                                                    column.referencedColumn!
4448
                                                        .propertyName
4449
                                                }`
4450
                                            })
4451
                                            .join(" AND "),
4452
                                    )
4453
                            } else if (relation.isManyToManyNotOwner) {
192✔
4454
                                qb.select("COUNT(*)")
96✔
4455
                                    .from(
4456
                                        relation.inverseRelation!.joinTableName,
4457
                                        relation.inverseRelation!.joinTableName,
4458
                                    )
4459
                                    .where(
4460
                                        relation
4461
                                            .inverseRelation!.inverseJoinColumns.map(
4462
                                                (column) => {
4463
                                                    return `${
96✔
4464
                                                        relation.inverseRelation!
4465
                                                            .joinTableName
4466
                                                    }.${
4467
                                                        column.propertyName
4468
                                                    } = ${alias}.${
4469
                                                        column.referencedColumn!
4470
                                                            .propertyName
4471
                                                    }`
4472
                                                },
4473
                                            )
4474
                                            .join(" AND "),
4475
                                    )
4476
                            } else if (relation.isOneToMany) {
96!
4477
                                qb.select("COUNT(*)")
96✔
4478
                                    .from(
4479
                                        relation.inverseEntityMetadata.target,
4480
                                        relation.inverseEntityMetadata
4481
                                            .tableName,
4482
                                    )
4483
                                    .where(
4484
                                        relation
4485
                                            .inverseRelation!.joinColumns.map(
4486
                                                (column) => {
4487
                                                    return `${
96✔
4488
                                                        relation
4489
                                                            .inverseEntityMetadata
4490
                                                            .tableName
4491
                                                    }.${
4492
                                                        column.propertyName
4493
                                                    } = ${alias}.${
4494
                                                        column.referencedColumn!
4495
                                                            .propertyName
4496
                                                    }`
4497
                                                },
4498
                                            )
4499
                                            .join(" AND "),
4500
                                    )
4501
                            } else {
UNCOV
4502
                                throw new Error(
×
4503
                                    `This relation isn't supported by given find operator`,
4504
                                )
4505
                            }
4506
                            // this
4507
                            //     .addSelect(qb.getSql(), relation.propertyAliasName + "_cnt")
4508
                            //     .andWhere(this.escape(relation.propertyAliasName + "_cnt") + " " + sqlOperator + " " + parseInt(where[key].value));
4509
                            this.andWhere(
336✔
4510
                                qb.getSql() +
4511
                                    " " +
4512
                                    sqlOperator +
4513
                                    " " +
4514
                                    parseInt(where[key].value),
4515
                            )
4516
                        } else {
4517
                            if (
192!
4518
                                relation.isManyToOne ||
384✔
4519
                                (relation.isOneToOne &&
4520
                                    relation.isOneToOneOwner)
4521
                            ) {
4522
                                const aliasPath = `${alias}.${propertyPath}`
192✔
4523

4524
                                andConditions.push(
192✔
4525
                                    this.createWhereConditionExpression(
4526
                                        this.getWherePredicateCondition(
4527
                                            aliasPath,
4528
                                            where[key],
4529
                                        ),
4530
                                    ),
4531
                                )
4532
                            } else {
UNCOV
4533
                                throw new Error(
×
4534
                                    `This relation isn't supported by given find operator`,
4535
                                )
4536
                            }
4537
                        }
4538
                    } else {
4539
                        // const joinAlias = alias + "_" + relation.propertyName;
4540
                        let joinAlias =
4541
                            alias +
652✔
4542
                            "_" +
4543
                            relation.propertyPath.replace(".", "_")
4544
                        joinAlias = DriverUtils.buildAlias(
652✔
4545
                            this.connection.driver,
4546
                            { joiner: "__" },
4547
                            alias,
4548
                            joinAlias,
4549
                        )
4550

4551
                        const existJoin = this.joins.find(
652✔
4552
                            (join) => join.alias === joinAlias,
364✔
4553
                        )
4554
                        if (!existJoin) {
652✔
4555
                            this.joins.push({
484✔
4556
                                type: "left",
4557
                                select: false,
4558
                                selection: undefined,
4559
                                alias: joinAlias,
4560
                                parentAlias: alias,
4561
                                relationMetadata: relation,
4562
                            })
4563
                        }
4564

4565
                        const condition = this.buildWhere(
652✔
4566
                            where[key],
4567
                            relation.inverseEntityMetadata,
4568
                            joinAlias,
4569
                        )
4570
                        if (condition) {
652✔
4571
                            andConditions.push(condition)
580✔
4572
                            // parameterIndex = Object.keys(this.expressionMap.nativeParameters).length;
4573
                        }
4574
                    }
4575
                }
4576
            }
4577
            condition = andConditions.length
57,898✔
4578
                ? "(" + andConditions.join(") AND (") + ")"
4579
                : andConditions.join(" AND ")
4580
        }
4581
        return condition.length ? "(" + condition + ")" : condition
58,070✔
4582
    }
4583
}
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