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

typeorm / typeorm / 15219332477

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

Pull #11332

github

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

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

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

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

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

1.55
/src/query-builder/SelectQueryBuilder.ts
1
import { RawSqlResultsToEntityTransformer } from "./transformer/RawSqlResultsToEntityTransformer"
1✔
2
import { ObjectLiteral } from "../common/ObjectLiteral"
3
import { PessimisticLockTransactionRequiredError } from "../error/PessimisticLockTransactionRequiredError"
1✔
4
import { NoVersionOrUpdateDateColumnError } from "../error/NoVersionOrUpdateDateColumnError"
1✔
5
import { OptimisticLockVersionMismatchError } from "../error/OptimisticLockVersionMismatchError"
1✔
6
import { OptimisticLockCanNotBeUsedError } from "../error/OptimisticLockCanNotBeUsedError"
1✔
7
import { JoinAttribute } from "./JoinAttribute"
1✔
8
import { RelationIdAttribute } from "./relation-id/RelationIdAttribute"
1✔
9
import { RelationCountAttribute } from "./relation-count/RelationCountAttribute"
1✔
10
import { RelationIdLoader } from "./relation-id/RelationIdLoader"
1✔
11
import { RelationIdLoader as QueryStrategyRelationIdLoader } from "./RelationIdLoader"
1✔
12
import { RelationIdMetadataToAttributeTransformer } from "./relation-id/RelationIdMetadataToAttributeTransformer"
1✔
13
import { RelationCountLoader } from "./relation-count/RelationCountLoader"
1✔
14
import { RelationCountMetadataToAttributeTransformer } from "./relation-count/RelationCountMetadataToAttributeTransformer"
1✔
15
import { QueryBuilder } from "./QueryBuilder"
1✔
16
import { ReadStream } from "../platform/PlatformTools"
17
import { LockNotSupportedOnGivenDriverError } from "../error/LockNotSupportedOnGivenDriverError"
1✔
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"
1✔
30
import { SelectQueryBuilderOption } from "./SelectQueryBuilderOption"
31
import { ObjectUtils } from "../util/ObjectUtils"
1✔
32
import { DriverUtils } from "../driver/DriverUtils"
1✔
33
import { EntityNotFoundError } from "../error/EntityNotFoundError"
1✔
34
import { TypeORMError } from "../error"
1✔
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"
1✔
41
import { FindOptionsRelations } from "../find-options/FindOptionsRelations"
42
import { OrmUtils } from "../util/OrmUtils"
1✔
43
import { EntityPropertyNotFoundError } from "../error/EntityPropertyNotFoundError"
1✔
44
import { AuroraMysqlDriver } from "../driver/aurora-mysql/AuroraMysqlDriver"
45
import { InstanceChecker } from "../util/InstanceChecker"
1✔
46
import { FindOperator } from "../find-options/FindOperator"
1✔
47
import { ApplyValueTransformers } from "../util/ApplyValueTransformers"
1✔
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>
1✔
54
    extends QueryBuilder<Entity>
55
    implements WhereExpressionBuilder
56
{
UNCOV
57
    readonly "@instanceof" = Symbol.for("SelectQueryBuilder")
×
58

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

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

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

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

104
    setFindOptions(findOptions: FindManyOptions<Entity>) {
UNCOV
105
        this.findOptions = findOptions
×
UNCOV
106
        this.applyFindOptions()
×
UNCOV
107
        return this
×
108
    }
109

110
    /**
111
     * Creates a subquery - query that can be used inside other queries.
112
     */
113
    subQuery(): SelectQueryBuilder<any> {
UNCOV
114
        const qb = this.createQueryBuilder()
×
UNCOV
115
        qb.expressionMap.subQuery = true
×
UNCOV
116
        qb.parentQueryBuilder = this
×
UNCOV
117
        return qb
×
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> {
UNCOV
158
        this.expressionMap.queryType = "select"
×
UNCOV
159
        if (Array.isArray(selection)) {
×
UNCOV
160
            this.expressionMap.selects = selection.map((selection) => ({
×
161
                selection: selection,
162
            }))
UNCOV
163
        } else if (typeof selection === "function") {
×
164
            const subQueryBuilder = selection(this.subQuery())
×
165
            this.setParameters(subQueryBuilder.getParameters())
×
166
            this.expressionMap.selects.push({
×
167
                selection: subQueryBuilder.getQuery(),
168
                aliasName: selectionAliasName,
169
            })
UNCOV
170
        } else if (selection) {
×
UNCOV
171
            this.expressionMap.selects = [
×
172
                { selection: selection, aliasName: selectionAliasName },
173
            ]
174
        }
175

UNCOV
176
        return this
×
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 {
UNCOV
207
        if (!selection) return this
×
208

UNCOV
209
        if (Array.isArray(selection)) {
×
UNCOV
210
            this.expressionMap.selects = this.expressionMap.selects.concat(
×
UNCOV
211
                selection.map((selection) => ({ selection: selection })),
×
212
            )
UNCOV
213
        } else if (typeof selection === "function") {
×
UNCOV
214
            const subQueryBuilder = selection(this.subQuery())
×
UNCOV
215
            this.setParameters(subQueryBuilder.getParameters())
×
UNCOV
216
            this.expressionMap.selects.push({
×
217
                selection: subQueryBuilder.getQuery(),
218
                aliasName: selectionAliasName,
219
            })
UNCOV
220
        } else if (selection) {
×
UNCOV
221
            this.expressionMap.selects.push({
×
222
                selection: selection,
223
                aliasName: selectionAliasName,
224
            })
225
        }
226

UNCOV
227
        return this
×
228
    }
229

230
    /**
231
     * Set max execution time.
232
     * @param milliseconds
233
     */
234
    maxExecutionTime(milliseconds: number): this {
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 {
×
UNCOV
243
        this.expressionMap.selectDistinct = distinct
×
UNCOV
244
        return this
×
245
    }
246

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

255
    fromDummy(): SelectQueryBuilder<any> {
UNCOV
256
        return this.from(
×
257
            this.connection.driver.dummyTableName ??
×
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> {
UNCOV
294
        const mainAlias = this.createFromAlias(entityTarget, aliasName)
×
UNCOV
295
        this.expressionMap.setMainAlias(mainAlias)
×
UNCOV
296
        return this as any as SelectQueryBuilder<T>
×
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> {
UNCOV
327
        const alias = this.createFromAlias(entityTarget, aliasName)
×
UNCOV
328
        if (!this.expressionMap.mainAlias)
×
329
            this.expressionMap.setMainAlias(alias)
×
330

UNCOV
331
        return this as any as SelectQueryBuilder<T>
×
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 {
UNCOV
399
        this.join("INNER", entityOrProperty, alias, condition, parameters)
×
UNCOV
400
        return this
×
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 {
UNCOV
468
        this.join("LEFT", entityOrProperty, alias, condition, parameters)
×
UNCOV
469
        return this
×
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 {
UNCOV
537
        this.addSelect(alias)
×
UNCOV
538
        this.innerJoin(entityOrProperty, alias, condition, parameters)
×
UNCOV
539
        return this
×
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 {
UNCOV
607
        this.addSelect(alias)
×
UNCOV
608
        this.leftJoin(entityOrProperty, alias, condition, parameters)
×
UNCOV
609
        return this
×
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 {
UNCOV
693
        this.addSelect(alias)
×
UNCOV
694
        this.join(
×
695
            "INNER",
696
            entityOrProperty,
697
            alias,
698
            condition,
699
            parameters,
700
            mapToProperty,
701
            true,
702
        )
UNCOV
703
        return this
×
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 {
UNCOV
789
        this.addSelect(alias)
×
UNCOV
790
        this.join(
×
791
            "INNER",
792
            entityOrProperty,
793
            alias,
794
            condition,
795
            parameters,
796
            mapToProperty,
797
            false,
798
            mapAsEntity,
799
        )
UNCOV
800
        return this
×
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 {
UNCOV
884
        this.addSelect(alias)
×
UNCOV
885
        this.join(
×
886
            "LEFT",
887
            entityOrProperty,
888
            alias,
889
            condition,
890
            parameters,
891
            mapToProperty,
892
            true,
893
        )
UNCOV
894
        return this
×
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 {
UNCOV
980
        this.addSelect(alias)
×
UNCOV
981
        this.join(
×
982
            "LEFT",
983
            entityOrProperty,
984
            alias,
985
            condition,
986
            parameters,
987
            mapToProperty,
988
            false,
989
            mapAsEntity,
990
        )
UNCOV
991
        return this
×
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 {
UNCOV
1052
        const relationIdAttribute = new RelationIdAttribute(this.expressionMap)
×
UNCOV
1053
        relationIdAttribute.mapToProperty = mapToProperty
×
UNCOV
1054
        relationIdAttribute.relationName = relationName
×
UNCOV
1055
        if (typeof aliasNameOrOptions === "string")
×
UNCOV
1056
            relationIdAttribute.alias = aliasNameOrOptions
×
UNCOV
1057
        if (
×
1058
            typeof aliasNameOrOptions === "object" &&
×
1059
            (aliasNameOrOptions as any).disableMixedMap
1060
        )
UNCOV
1061
            relationIdAttribute.disableMixedMap = true
×
1062

UNCOV
1063
        relationIdAttribute.queryBuilderFactory = queryBuilderFactory
×
UNCOV
1064
        this.expressionMap.relationIdAttributes.push(relationIdAttribute)
×
1065

UNCOV
1066
        if (relationIdAttribute.relation.junctionEntityMetadata) {
×
UNCOV
1067
            this.expressionMap.createAlias({
×
1068
                type: "other",
1069
                name: relationIdAttribute.junctionAlias,
1070
                metadata: relationIdAttribute.relation.junctionEntityMetadata,
1071
            })
1072
        }
UNCOV
1073
        return this
×
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 {
UNCOV
1088
        const relationCountAttribute = new RelationCountAttribute(
×
1089
            this.expressionMap,
1090
        )
UNCOV
1091
        relationCountAttribute.mapToProperty = mapToProperty
×
UNCOV
1092
        relationCountAttribute.relationName = relationName
×
UNCOV
1093
        relationCountAttribute.alias = aliasName
×
UNCOV
1094
        relationCountAttribute.queryBuilderFactory = queryBuilderFactory
×
UNCOV
1095
        this.expressionMap.relationCountAttributes.push(relationCountAttribute)
×
1096

UNCOV
1097
        this.expressionMap.createAlias({
×
1098
            type: "other",
1099
            name: relationCountAttribute.junctionAlias,
1100
        })
UNCOV
1101
        if (relationCountAttribute.relation.junctionEntityMetadata) {
×
UNCOV
1102
            this.expressionMap.createAlias({
×
1103
                type: "other",
1104
                name: relationCountAttribute.junctionAlias,
1105
                metadata:
1106
                    relationCountAttribute.relation.junctionEntityMetadata,
1107
            })
1108
        }
UNCOV
1109
        return this
×
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
UNCOV
1122
        this.expressionMap.mainAlias!.metadata.relations.forEach((relation) => {
×
UNCOV
1123
            if (
×
1124
                options !== undefined &&
×
1125
                options.relations !== undefined &&
1126
                options.relations.indexOf(relation.propertyPath) === -1
1127
            )
UNCOV
1128
                return
×
1129

UNCOV
1130
            this.loadRelationIdAndMap(
×
1131
                this.expressionMap.mainAlias!.name +
1132
                    "." +
1133
                    relation.propertyPath,
1134
                this.expressionMap.mainAlias!.name +
1135
                    "." +
1136
                    relation.propertyPath,
1137
                options,
1138
            )
1139
        })
UNCOV
1140
        return this
×
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 {
UNCOV
1158
        this.expressionMap.wheres = [] // don't move this block below since computeWhereParameter can add where expressions
×
UNCOV
1159
        const condition = this.getWhereCondition(where)
×
UNCOV
1160
        if (condition) {
×
UNCOV
1161
            this.expressionMap.wheres = [
×
1162
                { type: "simple", condition: condition },
1163
            ]
1164
        }
UNCOV
1165
        if (parameters) this.setParameters(parameters)
×
UNCOV
1166
        return this
×
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 {
UNCOV
1182
        this.expressionMap.wheres.push({
×
1183
            type: "and",
1184
            condition: this.getWhereCondition(where),
1185
        })
UNCOV
1186
        if (parameters) this.setParameters(parameters)
×
UNCOV
1187
        return this
×
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 {
UNCOV
1203
        this.expressionMap.wheres.push({
×
1204
            type: "or",
1205
            condition: this.getWhereCondition(where),
1206
        })
UNCOV
1207
        if (parameters) this.setParameters(parameters)
×
UNCOV
1208
        return this
×
1209
    }
1210

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

1218
    /**
1219
     * Adds a new AND where EXISTS clause
1220
     */
1221
    andWhereExists(subQuery: SelectQueryBuilder<any>): this {
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 {
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 {
UNCOV
1241
        return this.where(this.getWhereInIdsCondition(ids))
×
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 {
UNCOV
1253
        return this.andWhere(this.getWhereInIdsCondition(ids))
×
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 {
UNCOV
1265
        return this.orWhere(this.getWhereInIdsCondition(ids))
×
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 {
UNCOV
1275
        this.expressionMap.havings.push({ type: "simple", condition: having })
×
UNCOV
1276
        if (parameters) this.setParameters(parameters)
×
UNCOV
1277
        return this
×
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 {
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 {
UNCOV
1295
        this.expressionMap.havings.push({ type: "or", condition: having })
×
UNCOV
1296
        if (parameters) this.setParameters(parameters)
×
UNCOV
1297
        return this
×
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 {
UNCOV
1320
        if (groupBy) {
×
UNCOV
1321
            this.expressionMap.groupBys = [groupBy]
×
1322
        } else {
UNCOV
1323
            this.expressionMap.groupBys = []
×
1324
        }
UNCOV
1325
        return this
×
1326
    }
1327

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

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

UNCOV
1348
        return this
×
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",
×
1386
        nulls?: "NULLS FIRST" | "NULLS LAST",
1387
    ): this {
UNCOV
1388
        if (order !== undefined && order !== "ASC" && order !== "DESC")
×
UNCOV
1389
            throw new TypeORMError(
×
1390
                `SelectQueryBuilder.addOrderBy "order" can accept only "ASC" and "DESC" values.`,
1391
            )
UNCOV
1392
        if (
×
1393
            nulls !== undefined &&
×
1394
            nulls !== "NULLS FIRST" &&
1395
            nulls !== "NULLS LAST"
1396
        )
UNCOV
1397
            throw new TypeORMError(
×
1398
                `SelectQueryBuilder.addOrderBy "nulls" can accept only "NULLS FIRST" and "NULLS LAST" values.`,
1399
            )
1400

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

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

UNCOV
1440
        if (nulls) {
×
UNCOV
1441
            this.expressionMap.orderBys[sort] = { order, nulls }
×
1442
        } else {
UNCOV
1443
            this.expressionMap.orderBys[sort] = order
×
1444
        }
UNCOV
1445
        return this
×
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 {
UNCOV
1455
        this.expressionMap.limit = this.normalizeNumber(limit)
×
UNCOV
1456
        if (
×
1457
            this.expressionMap.limit !== undefined &&
×
1458
            isNaN(this.expressionMap.limit)
1459
        )
1460
            throw new TypeORMError(
×
1461
                `Provided "limit" value is not a number. Please provide a numeric value.`,
1462
            )
1463

UNCOV
1464
        return this
×
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 {
UNCOV
1474
        this.expressionMap.offset = this.normalizeNumber(offset)
×
UNCOV
1475
        if (
×
1476
            this.expressionMap.offset !== undefined &&
×
1477
            isNaN(this.expressionMap.offset)
1478
        )
1479
            throw new TypeORMError(
×
1480
                `Provided "offset" value is not a number. Please provide a numeric value.`,
1481
            )
1482

UNCOV
1483
        return this
×
1484
    }
1485

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

UNCOV
1499
        return this
×
1500
    }
1501

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

UNCOV
1515
        return this
×
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
×
1525

1526
        return this
×
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 {
UNCOV
1578
        this.expressionMap.lockMode = lockMode
×
UNCOV
1579
        this.expressionMap.lockVersion = lockVersion
×
UNCOV
1580
        this.expressionMap.lockTables = lockTables
×
UNCOV
1581
        return this
×
1582
    }
1583

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

1592
    /**
1593
     * Disables the global condition of "non-deleted" for the entity with delete date columns.
1594
     */
1595
    withDeleted(): this {
UNCOV
1596
        this.expressionMap.withDeleted = true
×
UNCOV
1597
        return this
×
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> {
UNCOV
1604
        return (await this.getRawMany())[0]
×
1605
    }
1606

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

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

UNCOV
1627
            const results = await this.loadRawResults(queryRunner)
×
1628

1629
            // close transaction if we started it
UNCOV
1630
            if (transactionStartedByUs) {
×
1631
                await queryRunner.commitTransaction()
×
1632
            }
1633

UNCOV
1634
            return results
×
1635
        } catch (error) {
1636
            // rollback transaction if we started it
UNCOV
1637
            if (transactionStartedByUs) {
×
1638
                try {
×
1639
                    await queryRunner.rollbackTransaction()
×
1640
                } catch (rollbackError) {}
1641
            }
UNCOV
1642
            throw error
×
1643
        } finally {
UNCOV
1644
            if (queryRunner !== this.queryRunner) {
×
1645
                // means we created our own query runner
UNCOV
1646
                await queryRunner.release()
×
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
    }> {
UNCOV
1658
        const queryRunner = this.obtainQueryRunner()
×
UNCOV
1659
        let transactionStartedByUs: boolean = false
×
UNCOV
1660
        try {
×
1661
            // start transaction if it was enabled
UNCOV
1662
            if (
×
1663
                this.expressionMap.useTransaction === true &&
×
1664
                queryRunner.isTransactionActive === false
1665
            ) {
UNCOV
1666
                await queryRunner.startTransaction()
×
UNCOV
1667
                transactionStartedByUs = true
×
1668
            }
1669

UNCOV
1670
            this.expressionMap.queryEntity = true
×
UNCOV
1671
            const results = await this.executeEntitiesAndRawResults(queryRunner)
×
1672

1673
            // close transaction if we started it
UNCOV
1674
            if (transactionStartedByUs) {
×
UNCOV
1675
                await queryRunner.commitTransaction()
×
1676
            }
1677

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

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

UNCOV
1701
        if (
×
1702
            result &&
×
1703
            this.expressionMap.lockMode === "optimistic" &&
1704
            this.expressionMap.lockVersion
1705
        ) {
UNCOV
1706
            const metadata = this.expressionMap.mainAlias!.metadata
×
1707

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

UNCOV
1732
        if (result === undefined) {
×
UNCOV
1733
            return null
×
1734
        }
UNCOV
1735
        return result
×
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> {
UNCOV
1742
        const entity = await this.getOne()
×
1743

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

UNCOV
1751
        return entity
×
1752
    }
1753

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

UNCOV
1761
        const results = await this.getRawAndEntities()
×
UNCOV
1762
        return results.entities
×
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> {
UNCOV
1770
        if (this.expressionMap.lockMode === "optimistic")
×
UNCOV
1771
            throw new OptimisticLockCanNotBeUsedError()
×
1772

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

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

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

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

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

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

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

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

UNCOV
1837
            return results
×
1838
        } catch (error) {
1839
            // rollback transaction if we started it
1840
            if (transactionStartedByUs) {
×
1841
                try {
×
1842
                    await queryRunner.rollbackTransaction()
×
1843
                } catch (rollbackError) {}
1844
            }
1845
            throw error
×
1846
        } finally {
UNCOV
1847
            if (queryRunner !== this.queryRunner)
×
1848
                // means we created our own query runner
UNCOV
1849
                await queryRunner.release()
×
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]> {
UNCOV
1858
        if (this.expressionMap.lockMode === "optimistic")
×
UNCOV
1859
            throw new OptimisticLockCanNotBeUsedError()
×
1860

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

UNCOV
1873
            this.expressionMap.queryEntity = true
×
UNCOV
1874
            const entitiesAndRaw = await this.executeEntitiesAndRawResults(
×
1875
                queryRunner,
1876
            )
UNCOV
1877
            this.expressionMap.queryEntity = false
×
UNCOV
1878
            const cacheId = this.expressionMap.cacheId
×
1879
            // Creates a new cacheId for the count query, or it will retreive the above query results
1880
            // and count will return 0.
UNCOV
1881
            this.expressionMap.cacheId = cacheId ? `${cacheId}-count` : cacheId
×
UNCOV
1882
            const count = await this.executeCountQuery(queryRunner)
×
UNCOV
1883
            const results: [Entity[], number] = [entitiesAndRaw.entities, count]
×
1884

1885
            // close transaction if we started it
UNCOV
1886
            if (transactionStartedByUs) {
×
1887
                await queryRunner.commitTransaction()
×
1888
            }
1889

UNCOV
1890
            return results
×
1891
        } catch (error) {
1892
            // rollback transaction if we started it
UNCOV
1893
            if (transactionStartedByUs) {
×
1894
                try {
×
1895
                    await queryRunner.rollbackTransaction()
×
1896
                } catch (rollbackError) {}
1897
            }
UNCOV
1898
            throw error
×
1899
        } finally {
UNCOV
1900
            if (queryRunner !== this.queryRunner)
×
1901
                // means we created our own query runner
UNCOV
1902
                await queryRunner.release()
×
1903
        }
1904
    }
1905

1906
    /**
1907
     * Executes built SQL query and returns raw data stream.
1908
     */
1909
    async stream(): Promise<ReadStream> {
UNCOV
1910
        this.expressionMap.queryEntity = false
×
UNCOV
1911
        const [sql, parameters] = this.getQueryAndParameters()
×
UNCOV
1912
        const queryRunner = this.obtainQueryRunner()
×
UNCOV
1913
        let transactionStartedByUs: boolean = false
×
UNCOV
1914
        try {
×
1915
            // start transaction if it was enabled
UNCOV
1916
            if (
×
1917
                this.expressionMap.useTransaction === true &&
×
1918
                queryRunner.isTransactionActive === false
1919
            ) {
1920
                await queryRunner.startTransaction()
×
1921
                transactionStartedByUs = true
×
1922
            }
1923

UNCOV
1924
            const releaseFn = () => {
×
UNCOV
1925
                if (queryRunner !== this.queryRunner)
×
1926
                    // means we created our own query runner
UNCOV
1927
                    return queryRunner.release()
×
1928
                return
×
1929
            }
UNCOV
1930
            const results = queryRunner.stream(
×
1931
                sql,
1932
                parameters,
1933
                releaseFn,
1934
                releaseFn,
1935
            )
1936

1937
            // close transaction if we started it
UNCOV
1938
            if (transactionStartedByUs) {
×
1939
                await queryRunner.commitTransaction()
×
1940
            }
1941

UNCOV
1942
            return results
×
1943
        } catch (error) {
1944
            // rollback transaction if we started it
1945
            if (transactionStartedByUs) {
×
1946
                try {
×
1947
                    await queryRunner.rollbackTransaction()
×
1948
                } catch (rollbackError) {}
1949
            }
1950
            throw error
×
1951
        }
1952
    }
1953

1954
    /**
1955
     * Enables or disables query result caching.
1956
     */
1957
    cache(enabled: boolean): this
1958

1959
    /**
1960
     * Enables query result caching and sets in milliseconds in which cache will expire.
1961
     * If not set then global caching time will be used.
1962
     */
1963
    cache(milliseconds: number): this
1964

1965
    /**
1966
     * Enables query result caching and sets cache id and milliseconds in which cache will expire.
1967
     */
1968
    cache(id: any, milliseconds?: number): this
1969

1970
    /**
1971
     * Enables or disables query result caching.
1972
     */
1973
    cache(
1974
        enabledOrMillisecondsOrId: boolean | number | string,
1975
        maybeMilliseconds?: number,
1976
    ): this {
UNCOV
1977
        if (typeof enabledOrMillisecondsOrId === "boolean") {
×
UNCOV
1978
            this.expressionMap.cache = enabledOrMillisecondsOrId
×
UNCOV
1979
        } else if (typeof enabledOrMillisecondsOrId === "number") {
×
UNCOV
1980
            this.expressionMap.cache = true
×
UNCOV
1981
            this.expressionMap.cacheDuration = enabledOrMillisecondsOrId
×
UNCOV
1982
        } else if (
×
1983
            typeof enabledOrMillisecondsOrId === "string" ||
×
1984
            typeof enabledOrMillisecondsOrId === "number"
1985
        ) {
UNCOV
1986
            this.expressionMap.cache = true
×
UNCOV
1987
            this.expressionMap.cacheId = enabledOrMillisecondsOrId
×
1988
        }
1989

UNCOV
1990
        if (maybeMilliseconds) {
×
UNCOV
1991
            this.expressionMap.cacheDuration = maybeMilliseconds
×
1992
        }
1993

UNCOV
1994
        return this
×
1995
    }
1996

1997
    /**
1998
     * Sets extra options that can be used to configure how query builder works.
1999
     */
2000
    setOption(option: SelectQueryBuilderOption): this {
UNCOV
2001
        this.expressionMap.options.push(option)
×
UNCOV
2002
        return this
×
2003
    }
2004

2005
    // -------------------------------------------------------------------------
2006
    // Protected Methods
2007
    // -------------------------------------------------------------------------
2008

2009
    protected join(
2010
        direction: "INNER" | "LEFT",
2011
        entityOrProperty:
2012
            | Function
2013
            | string
2014
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
2015
        aliasName: string,
2016
        condition?: string,
2017
        parameters?: ObjectLiteral,
2018
        mapToProperty?: string,
2019
        isMappingMany?: boolean,
2020
        mapAsEntity?: Function | string,
2021
    ): void {
UNCOV
2022
        if (parameters) {
×
2023
            this.setParameters(parameters)
×
2024
        }
2025

UNCOV
2026
        const joinAttribute = new JoinAttribute(
×
2027
            this.connection,
2028
            this.expressionMap,
2029
        )
UNCOV
2030
        joinAttribute.direction = direction
×
UNCOV
2031
        joinAttribute.mapAsEntity = mapAsEntity
×
UNCOV
2032
        joinAttribute.mapToProperty = mapToProperty
×
UNCOV
2033
        joinAttribute.isMappingMany = isMappingMany
×
UNCOV
2034
        joinAttribute.entityOrProperty = entityOrProperty // relationName
×
UNCOV
2035
        joinAttribute.condition = condition // joinInverseSideCondition
×
2036
        // joinAttribute.junctionAlias = joinAttribute.relation.isOwning ? parentAlias + "_" + destinationTableAlias : destinationTableAlias + "_" + parentAlias;
UNCOV
2037
        this.expressionMap.joinAttributes.push(joinAttribute)
×
2038

UNCOV
2039
        const joinAttributeMetadata = joinAttribute.metadata
×
UNCOV
2040
        if (joinAttributeMetadata) {
×
UNCOV
2041
            if (
×
2042
                joinAttributeMetadata.deleteDateColumn &&
×
2043
                !this.expressionMap.withDeleted
2044
            ) {
UNCOV
2045
                const conditionDeleteColumn = `${aliasName}.${joinAttributeMetadata.deleteDateColumn.propertyName} IS NULL`
×
UNCOV
2046
                joinAttribute.condition = joinAttribute.condition
×
2047
                    ? ` ${joinAttribute.condition} AND ${conditionDeleteColumn}`
2048
                    : `${conditionDeleteColumn}`
2049
            }
2050
            // todo: find and set metadata right there?
UNCOV
2051
            joinAttribute.alias = this.expressionMap.createAlias({
×
2052
                type: "join",
2053
                name: aliasName,
2054
                metadata: joinAttributeMetadata,
2055
            })
UNCOV
2056
            if (
×
2057
                joinAttribute.relation &&
×
2058
                joinAttribute.relation.junctionEntityMetadata
2059
            ) {
UNCOV
2060
                this.expressionMap.createAlias({
×
2061
                    type: "join",
2062
                    name: joinAttribute.junctionAlias,
2063
                    metadata: joinAttribute.relation.junctionEntityMetadata,
2064
                })
2065
            }
2066
        } else {
UNCOV
2067
            let subQuery: string = ""
×
UNCOV
2068
            if (typeof entityOrProperty === "function") {
×
2069
                const subQueryBuilder: SelectQueryBuilder<any> = (
UNCOV
2070
                    entityOrProperty as any
×
2071
                )((this as any as SelectQueryBuilder<any>).subQuery())
UNCOV
2072
                this.setParameters(subQueryBuilder.getParameters())
×
UNCOV
2073
                subQuery = subQueryBuilder.getQuery()
×
2074
            } else {
UNCOV
2075
                subQuery = entityOrProperty
×
2076
            }
2077
            const isSubQuery =
UNCOV
2078
                typeof entityOrProperty === "function" ||
×
2079
                (entityOrProperty.substr(0, 1) === "(" &&
2080
                    entityOrProperty.substr(-1) === ")")
UNCOV
2081
            joinAttribute.alias = this.expressionMap.createAlias({
×
2082
                type: "join",
2083
                name: aliasName,
2084
                tablePath:
2085
                    isSubQuery === false
×
2086
                        ? (entityOrProperty as string)
2087
                        : undefined,
2088
                subQuery: isSubQuery === true ? subQuery : undefined,
×
2089
            })
2090
        }
2091
    }
2092

2093
    /**
2094
     * Creates "SELECT FROM" part of SQL query.
2095
     */
2096
    protected createSelectExpression() {
UNCOV
2097
        if (!this.expressionMap.mainAlias)
×
2098
            throw new TypeORMError(
×
2099
                "Cannot build query because main alias is not set (call qb#from method)",
2100
            )
2101

2102
        // todo throw exception if selects or from is missing
2103

UNCOV
2104
        const allSelects: SelectQuery[] = []
×
UNCOV
2105
        const excludedSelects: SelectQuery[] = []
×
2106

UNCOV
2107
        if (this.expressionMap.mainAlias.hasMetadata) {
×
UNCOV
2108
            const metadata = this.expressionMap.mainAlias.metadata
×
UNCOV
2109
            allSelects.push(
×
2110
                ...this.buildEscapedEntityColumnSelects(
2111
                    this.expressionMap.mainAlias.name,
2112
                    metadata,
2113
                ),
2114
            )
UNCOV
2115
            excludedSelects.push(
×
2116
                ...this.findEntityColumnSelects(
2117
                    this.expressionMap.mainAlias.name,
2118
                    metadata,
2119
                ),
2120
            )
2121
        }
2122

2123
        // add selects from joins
UNCOV
2124
        this.expressionMap.joinAttributes.forEach((join) => {
×
UNCOV
2125
            if (join.metadata) {
×
UNCOV
2126
                allSelects.push(
×
2127
                    ...this.buildEscapedEntityColumnSelects(
2128
                        join.alias.name!,
2129
                        join.metadata,
2130
                    ),
2131
                )
UNCOV
2132
                excludedSelects.push(
×
2133
                    ...this.findEntityColumnSelects(
2134
                        join.alias.name!,
2135
                        join.metadata,
2136
                    ),
2137
                )
2138
            } else {
UNCOV
2139
                const hasMainAlias = this.expressionMap.selects.some(
×
UNCOV
2140
                    (select) => select.selection === join.alias.name,
×
2141
                )
UNCOV
2142
                if (hasMainAlias) {
×
UNCOV
2143
                    allSelects.push({
×
2144
                        selection: this.escape(join.alias.name!) + ".*",
2145
                    })
UNCOV
2146
                    const excludedSelect = this.expressionMap.selects.find(
×
UNCOV
2147
                        (select) => select.selection === join.alias.name,
×
2148
                    )
UNCOV
2149
                    excludedSelects.push(excludedSelect!)
×
2150
                }
2151
            }
2152
        })
2153

2154
        // add all other selects
UNCOV
2155
        this.expressionMap.selects
×
UNCOV
2156
            .filter((select) => excludedSelects.indexOf(select) === -1)
×
2157
            .forEach((select) =>
UNCOV
2158
                allSelects.push({
×
2159
                    selection: this.replacePropertyNames(select.selection),
2160
                    aliasName: select.aliasName,
2161
                }),
2162
            )
2163

2164
        // if still selection is empty, then simply set it to all (*)
UNCOV
2165
        if (allSelects.length === 0) allSelects.push({ selection: "*" })
×
2166

2167
        // Use certain index
UNCOV
2168
        let useIndex: string = ""
×
UNCOV
2169
        if (this.expressionMap.useIndex) {
×
2170
            if (DriverUtils.isMySQLFamily(this.connection.driver)) {
×
2171
                useIndex = ` USE INDEX (${this.expressionMap.useIndex})`
×
2172
            }
2173
        }
2174

2175
        // create a selection query
UNCOV
2176
        const froms = this.expressionMap.aliases
×
2177
            .filter(
2178
                (alias) =>
UNCOV
2179
                    alias.type === "from" &&
×
2180
                    (alias.tablePath || alias.subQuery),
2181
            )
2182
            .map((alias) => {
UNCOV
2183
                if (alias.subQuery)
×
UNCOV
2184
                    return alias.subQuery + " " + this.escape(alias.name)
×
2185

UNCOV
2186
                return (
×
2187
                    this.getTableName(alias.tablePath!) +
2188
                    " " +
2189
                    this.escape(alias.name)
2190
                )
2191
            })
2192

UNCOV
2193
        const select = this.createSelectDistinctExpression()
×
UNCOV
2194
        const selection = allSelects
×
2195
            .map(
2196
                (select) =>
UNCOV
2197
                    select.selection +
×
2198
                    (select.aliasName
×
2199
                        ? " AS " + this.escape(select.aliasName)
2200
                        : ""),
2201
            )
2202
            .join(", ")
2203

UNCOV
2204
        return (
×
2205
            select +
2206
            selection +
2207
            " FROM " +
2208
            froms.join(", ") +
2209
            this.createTableLockExpression() +
2210
            useIndex
2211
        )
2212
    }
2213

2214
    /**
2215
     * Creates select | select distinct part of SQL query.
2216
     */
2217
    protected createSelectDistinctExpression(): string {
2218
        const { selectDistinct, selectDistinctOn, maxExecutionTime } =
UNCOV
2219
            this.expressionMap
×
UNCOV
2220
        const { driver } = this.connection
×
2221

UNCOV
2222
        let select = "SELECT "
×
2223

UNCOV
2224
        if (maxExecutionTime > 0) {
×
2225
            if (DriverUtils.isMySQLFamily(driver)) {
×
2226
                select += `/*+ MAX_EXECUTION_TIME(${this.expressionMap.maxExecutionTime}) */ `
×
2227
            }
2228
        }
2229

UNCOV
2230
        if (
×
2231
            DriverUtils.isPostgresFamily(driver) &&
×
2232
            selectDistinctOn.length > 0
2233
        ) {
UNCOV
2234
            const selectDistinctOnMap = selectDistinctOn
×
UNCOV
2235
                .map((on) => this.replacePropertyNames(on))
×
2236
                .join(", ")
2237

UNCOV
2238
            select = `SELECT DISTINCT ON (${selectDistinctOnMap}) `
×
UNCOV
2239
        } else if (selectDistinct) {
×
UNCOV
2240
            select = "SELECT DISTINCT "
×
2241
        }
2242

UNCOV
2243
        return select
×
2244
    }
2245

2246
    /**
2247
     * Creates "JOIN" part of SQL query.
2248
     */
2249
    protected createJoinExpression(): string {
2250
        // examples:
2251
        // select from owning side
2252
        // qb.select("post")
2253
        //     .leftJoinAndSelect("post.category", "category");
2254
        // select from non-owning side
2255
        // qb.select("category")
2256
        //     .leftJoinAndSelect("category.post", "post");
2257

UNCOV
2258
        const joins = this.expressionMap.joinAttributes.map((joinAttr) => {
×
UNCOV
2259
            const relation = joinAttr.relation
×
UNCOV
2260
            const destinationTableName = joinAttr.tablePath
×
UNCOV
2261
            const destinationTableAlias = joinAttr.alias.name
×
UNCOV
2262
            let appendedCondition = joinAttr.condition
×
2263
                ? " AND (" + joinAttr.condition + ")"
2264
                : ""
UNCOV
2265
            const parentAlias = joinAttr.parentAlias
×
2266

2267
            // if join was build without relation (e.g. without "post.category") then it means that we have direct
2268
            // table to join, without junction table involved. This means we simply join direct table.
UNCOV
2269
            if (!parentAlias || !relation) {
×
UNCOV
2270
                const destinationJoin = joinAttr.alias.subQuery
×
2271
                    ? joinAttr.alias.subQuery
2272
                    : this.getTableName(destinationTableName)
UNCOV
2273
                return (
×
2274
                    " " +
2275
                    joinAttr.direction +
2276
                    " JOIN " +
2277
                    destinationJoin +
2278
                    " " +
2279
                    this.escape(destinationTableAlias) +
2280
                    this.createTableLockExpression() +
2281
                    (joinAttr.condition
×
2282
                        ? " ON " + this.replacePropertyNames(joinAttr.condition)
2283
                        : "")
2284
                )
2285
            }
2286

2287
            // if real entity relation is involved
UNCOV
2288
            if (relation.isManyToOne || relation.isOneToOneOwner) {
×
2289
                // JOIN `category` `category` ON `category`.`id` = `post`.`categoryId`
UNCOV
2290
                const condition = relation.joinColumns
×
2291
                    .map((joinColumn) => {
UNCOV
2292
                        return (
×
2293
                            destinationTableAlias +
2294
                            "." +
2295
                            joinColumn.referencedColumn!.propertyPath +
2296
                            "=" +
2297
                            parentAlias +
2298
                            "." +
2299
                            relation.propertyPath +
2300
                            "." +
2301
                            joinColumn.referencedColumn!.propertyPath
2302
                        )
2303
                    })
2304
                    .join(" AND ")
2305

UNCOV
2306
                return (
×
2307
                    " " +
2308
                    joinAttr.direction +
2309
                    " JOIN " +
2310
                    this.getTableName(destinationTableName) +
2311
                    " " +
2312
                    this.escape(destinationTableAlias) +
2313
                    this.createTableLockExpression() +
2314
                    " ON " +
2315
                    this.replacePropertyNames(condition + appendedCondition)
2316
                )
UNCOV
2317
            } else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
×
2318
                // JOIN `post` `post` ON `post`.`categoryId` = `category`.`id`
UNCOV
2319
                const condition = relation
×
2320
                    .inverseRelation!.joinColumns.map((joinColumn) => {
UNCOV
2321
                        if (
×
2322
                            relation.inverseEntityMetadata.tableType ===
×
2323
                                "entity-child" &&
2324
                            relation.inverseEntityMetadata.discriminatorColumn
2325
                        ) {
UNCOV
2326
                            appendedCondition +=
×
2327
                                " AND " +
2328
                                destinationTableAlias +
2329
                                "." +
2330
                                relation.inverseEntityMetadata
2331
                                    .discriminatorColumn.databaseName +
2332
                                "='" +
2333
                                relation.inverseEntityMetadata
2334
                                    .discriminatorValue +
2335
                                "'"
2336
                        }
2337

UNCOV
2338
                        return (
×
2339
                            destinationTableAlias +
2340
                            "." +
2341
                            relation.inverseRelation!.propertyPath +
2342
                            "." +
2343
                            joinColumn.referencedColumn!.propertyPath +
2344
                            "=" +
2345
                            parentAlias +
2346
                            "." +
2347
                            joinColumn.referencedColumn!.propertyPath
2348
                        )
2349
                    })
2350
                    .join(" AND ")
2351

UNCOV
2352
                if (!condition)
×
2353
                    throw new TypeORMError(
×
2354
                        `Relation ${relation.entityMetadata.name}.${relation.propertyName} does not have join columns.`,
2355
                    )
2356

UNCOV
2357
                return (
×
2358
                    " " +
2359
                    joinAttr.direction +
2360
                    " JOIN " +
2361
                    this.getTableName(destinationTableName) +
2362
                    " " +
2363
                    this.escape(destinationTableAlias) +
2364
                    this.createTableLockExpression() +
2365
                    " ON " +
2366
                    this.replacePropertyNames(condition + appendedCondition)
2367
                )
2368
            } else {
2369
                // means many-to-many
2370
                const junctionTableName =
UNCOV
2371
                    relation.junctionEntityMetadata!.tablePath
×
2372

UNCOV
2373
                const junctionAlias = joinAttr.junctionAlias
×
UNCOV
2374
                let junctionCondition = "",
×
UNCOV
2375
                    destinationCondition = ""
×
2376

UNCOV
2377
                if (relation.isOwning) {
×
UNCOV
2378
                    junctionCondition = relation.joinColumns
×
2379
                        .map((joinColumn) => {
2380
                            // `post_category`.`postId` = `post`.`id`
UNCOV
2381
                            return (
×
2382
                                junctionAlias +
2383
                                "." +
2384
                                joinColumn.propertyPath +
2385
                                "=" +
2386
                                parentAlias +
2387
                                "." +
2388
                                joinColumn.referencedColumn!.propertyPath
2389
                            )
2390
                        })
2391
                        .join(" AND ")
2392

UNCOV
2393
                    destinationCondition = relation.inverseJoinColumns
×
2394
                        .map((joinColumn) => {
2395
                            // `category`.`id` = `post_category`.`categoryId`
UNCOV
2396
                            return (
×
2397
                                destinationTableAlias +
2398
                                "." +
2399
                                joinColumn.referencedColumn!.propertyPath +
2400
                                "=" +
2401
                                junctionAlias +
2402
                                "." +
2403
                                joinColumn.propertyPath
2404
                            )
2405
                        })
2406
                        .join(" AND ")
2407
                } else {
UNCOV
2408
                    junctionCondition = relation
×
2409
                        .inverseRelation!.inverseJoinColumns.map(
2410
                            (joinColumn) => {
2411
                                // `post_category`.`categoryId` = `category`.`id`
UNCOV
2412
                                return (
×
2413
                                    junctionAlias +
2414
                                    "." +
2415
                                    joinColumn.propertyPath +
2416
                                    "=" +
2417
                                    parentAlias +
2418
                                    "." +
2419
                                    joinColumn.referencedColumn!.propertyPath
2420
                                )
2421
                            },
2422
                        )
2423
                        .join(" AND ")
2424

UNCOV
2425
                    destinationCondition = relation
×
2426
                        .inverseRelation!.joinColumns.map((joinColumn) => {
2427
                            // `post`.`id` = `post_category`.`postId`
UNCOV
2428
                            return (
×
2429
                                destinationTableAlias +
2430
                                "." +
2431
                                joinColumn.referencedColumn!.propertyPath +
2432
                                "=" +
2433
                                junctionAlias +
2434
                                "." +
2435
                                joinColumn.propertyPath
2436
                            )
2437
                        })
2438
                        .join(" AND ")
2439
                }
2440

UNCOV
2441
                return (
×
2442
                    " " +
2443
                    joinAttr.direction +
2444
                    " JOIN " +
2445
                    this.getTableName(junctionTableName) +
2446
                    " " +
2447
                    this.escape(junctionAlias) +
2448
                    this.createTableLockExpression() +
2449
                    " ON " +
2450
                    this.replacePropertyNames(junctionCondition) +
2451
                    " " +
2452
                    joinAttr.direction +
2453
                    " JOIN " +
2454
                    this.getTableName(destinationTableName) +
2455
                    " " +
2456
                    this.escape(destinationTableAlias) +
2457
                    this.createTableLockExpression() +
2458
                    " ON " +
2459
                    this.replacePropertyNames(
2460
                        destinationCondition + appendedCondition,
2461
                    )
2462
                )
2463
            }
2464
        })
2465

UNCOV
2466
        return joins.join(" ")
×
2467
    }
2468

2469
    /**
2470
     * Creates "GROUP BY" part of SQL query.
2471
     */
2472
    protected createGroupByExpression() {
UNCOV
2473
        if (!this.expressionMap.groupBys || !this.expressionMap.groupBys.length)
×
UNCOV
2474
            return ""
×
UNCOV
2475
        return (
×
2476
            " GROUP BY " +
2477
            this.replacePropertyNames(this.expressionMap.groupBys.join(", "))
2478
        )
2479
    }
2480

2481
    /**
2482
     * Creates "ORDER BY" part of SQL query.
2483
     */
2484
    protected createOrderByExpression() {
UNCOV
2485
        const orderBys = this.expressionMap.allOrderBys
×
UNCOV
2486
        if (Object.keys(orderBys).length === 0) return ""
×
2487

UNCOV
2488
        return (
×
2489
            " ORDER BY " +
2490
            Object.keys(orderBys)
2491
                .map((columnName) => {
2492
                    const orderValue =
UNCOV
2493
                        typeof orderBys[columnName] === "string"
×
2494
                            ? orderBys[columnName]
2495
                            : (orderBys[columnName] as any).order +
2496
                              " " +
2497
                              (orderBys[columnName] as any).nulls
UNCOV
2498
                    const selection = this.expressionMap.selects.find(
×
UNCOV
2499
                        (s) => s.selection === columnName,
×
2500
                    )
UNCOV
2501
                    if (
×
2502
                        selection &&
×
2503
                        !selection.aliasName &&
2504
                        columnName.indexOf(".") !== -1
2505
                    ) {
UNCOV
2506
                        const criteriaParts = columnName.split(".")
×
UNCOV
2507
                        const aliasName = criteriaParts[0]
×
UNCOV
2508
                        const propertyPath = criteriaParts.slice(1).join(".")
×
UNCOV
2509
                        const alias = this.expressionMap.aliases.find(
×
UNCOV
2510
                            (alias) => alias.name === aliasName,
×
2511
                        )
UNCOV
2512
                        if (alias) {
×
2513
                            const column =
UNCOV
2514
                                alias.metadata.findColumnWithPropertyPath(
×
2515
                                    propertyPath,
2516
                                )
UNCOV
2517
                            if (column) {
×
UNCOV
2518
                                const orderAlias = DriverUtils.buildAlias(
×
2519
                                    this.connection.driver,
2520
                                    undefined,
2521
                                    aliasName,
2522
                                    column.databaseName,
2523
                                )
UNCOV
2524
                                return (
×
2525
                                    this.escape(orderAlias) + " " + orderValue
2526
                                )
2527
                            }
2528
                        }
2529
                    }
2530

UNCOV
2531
                    return (
×
2532
                        this.replacePropertyNames(columnName) + " " + orderValue
2533
                    )
2534
                })
2535
                .join(", ")
2536
        )
2537
    }
2538

2539
    /**
2540
     * Creates "LIMIT" and "OFFSET" parts of SQL query.
2541
     */
2542
    protected createLimitOffsetExpression(): string {
2543
        // in the case if nothing is joined in the query builder we don't need to make two requests to get paginated results
2544
        // we can use regular limit / offset, that's why we add offset and limit construction here based on skip and take values
UNCOV
2545
        let offset: number | undefined = this.expressionMap.offset,
×
UNCOV
2546
            limit: number | undefined = this.expressionMap.limit
×
UNCOV
2547
        if (
×
2548
            !offset &&
×
2549
            !limit &&
2550
            this.expressionMap.joinAttributes.length === 0
2551
        ) {
UNCOV
2552
            offset = this.expressionMap.skip
×
UNCOV
2553
            limit = this.expressionMap.take
×
2554
        }
2555

UNCOV
2556
        if (this.connection.driver.options.type === "mssql") {
×
2557
            // Due to a limitation in SQL Server's parser implementation it does not support using
2558
            // OFFSET or FETCH NEXT without an ORDER BY clause being provided. In cases where the
2559
            // user does not request one we insert a dummy ORDER BY that does nothing and should
2560
            // have no effect on the query planner or on the order of the results returned.
2561
            // https://dba.stackexchange.com/a/193799
UNCOV
2562
            let prefix = ""
×
UNCOV
2563
            if (
×
2564
                (limit || offset) &&
×
2565
                Object.keys(this.expressionMap.allOrderBys).length <= 0
2566
            ) {
UNCOV
2567
                prefix = " ORDER BY (SELECT NULL)"
×
2568
            }
2569

UNCOV
2570
            if (limit && offset)
×
UNCOV
2571
                return (
×
2572
                    prefix +
2573
                    " OFFSET " +
2574
                    offset +
2575
                    " ROWS FETCH NEXT " +
2576
                    limit +
2577
                    " ROWS ONLY"
2578
                )
UNCOV
2579
            if (limit)
×
UNCOV
2580
                return (
×
2581
                    prefix + " OFFSET 0 ROWS FETCH NEXT " + limit + " ROWS ONLY"
2582
                )
UNCOV
2583
            if (offset) return prefix + " OFFSET " + offset + " ROWS"
×
UNCOV
2584
        } else if (
×
2585
            DriverUtils.isMySQLFamily(this.connection.driver) ||
×
2586
            this.connection.driver.options.type === "aurora-mysql" ||
2587
            this.connection.driver.options.type === "sap" ||
2588
            this.connection.driver.options.type === "spanner"
2589
        ) {
UNCOV
2590
            if (limit && offset) return " LIMIT " + limit + " OFFSET " + offset
×
UNCOV
2591
            if (limit) return " LIMIT " + limit
×
UNCOV
2592
            if (offset) throw new OffsetWithoutLimitNotSupportedError()
×
UNCOV
2593
        } else if (DriverUtils.isSQLiteFamily(this.connection.driver)) {
×
UNCOV
2594
            if (limit && offset) return " LIMIT " + limit + " OFFSET " + offset
×
UNCOV
2595
            if (limit) return " LIMIT " + limit
×
UNCOV
2596
            if (offset) return " LIMIT -1 OFFSET " + offset
×
UNCOV
2597
        } else if (this.connection.driver.options.type === "oracle") {
×
UNCOV
2598
            if (limit && offset)
×
UNCOV
2599
                return (
×
2600
                    " OFFSET " +
2601
                    offset +
2602
                    " ROWS FETCH NEXT " +
2603
                    limit +
2604
                    " ROWS ONLY"
2605
                )
UNCOV
2606
            if (limit) return " FETCH NEXT " + limit + " ROWS ONLY"
×
UNCOV
2607
            if (offset) return " OFFSET " + offset + " ROWS"
×
2608
        } else {
UNCOV
2609
            if (limit && offset) return " LIMIT " + limit + " OFFSET " + offset
×
UNCOV
2610
            if (limit) return " LIMIT " + limit
×
UNCOV
2611
            if (offset) return " OFFSET " + offset
×
2612
        }
2613

UNCOV
2614
        return ""
×
2615
    }
2616

2617
    /**
2618
     * Creates "LOCK" part of SELECT Query after table Clause
2619
     * ex.
2620
     *  SELECT 1
2621
     *  FROM USER U WITH (NOLOCK)
2622
     *  JOIN ORDER O WITH (NOLOCK)
2623
     *      ON U.ID=O.OrderID
2624
     */
2625
    private createTableLockExpression(): string {
UNCOV
2626
        if (this.connection.driver.options.type === "mssql") {
×
UNCOV
2627
            switch (this.expressionMap.lockMode) {
×
2628
                case "pessimistic_read":
UNCOV
2629
                    return " WITH (HOLDLOCK, ROWLOCK)"
×
2630
                case "pessimistic_write":
UNCOV
2631
                    return " WITH (UPDLOCK, ROWLOCK)"
×
2632
                case "dirty_read":
UNCOV
2633
                    return " WITH (NOLOCK)"
×
2634
            }
2635
        }
2636

UNCOV
2637
        return ""
×
2638
    }
2639

2640
    /**
2641
     * Creates "LOCK" part of SQL query.
2642
     */
2643
    protected createLockExpression(): string {
UNCOV
2644
        const driver = this.connection.driver
×
2645

UNCOV
2646
        let lockTablesClause = ""
×
2647

UNCOV
2648
        if (this.expressionMap.lockTables) {
×
UNCOV
2649
            if (
×
2650
                !(
2651
                    DriverUtils.isPostgresFamily(driver) ||
×
2652
                    driver.options.type === "cockroachdb"
2653
                )
2654
            ) {
2655
                throw new TypeORMError(
×
2656
                    "Lock tables not supported in selected driver",
2657
                )
2658
            }
UNCOV
2659
            if (this.expressionMap.lockTables.length < 1) {
×
UNCOV
2660
                throw new TypeORMError("lockTables cannot be an empty array")
×
2661
            }
UNCOV
2662
            lockTablesClause = " OF " + this.expressionMap.lockTables.join(", ")
×
2663
        }
2664

UNCOV
2665
        let onLockExpression = ""
×
UNCOV
2666
        if (this.expressionMap.onLocked === "nowait") {
×
UNCOV
2667
            onLockExpression = " NOWAIT"
×
UNCOV
2668
        } else if (this.expressionMap.onLocked === "skip_locked") {
×
UNCOV
2669
            onLockExpression = " SKIP LOCKED"
×
2670
        }
UNCOV
2671
        switch (this.expressionMap.lockMode) {
×
2672
            case "pessimistic_read":
UNCOV
2673
                if (
×
2674
                    driver.options.type === "mysql" ||
×
2675
                    driver.options.type === "aurora-mysql"
2676
                ) {
UNCOV
2677
                    if (
×
2678
                        DriverUtils.isReleaseVersionOrGreater(driver, "8.0.0")
2679
                    ) {
2680
                        return (
×
2681
                            " FOR SHARE" + lockTablesClause + onLockExpression
2682
                        )
2683
                    } else {
UNCOV
2684
                        return " LOCK IN SHARE MODE"
×
2685
                    }
UNCOV
2686
                } else if (driver.options.type === "mariadb") {
×
UNCOV
2687
                    return " LOCK IN SHARE MODE"
×
UNCOV
2688
                } else if (DriverUtils.isPostgresFamily(driver)) {
×
UNCOV
2689
                    return " FOR SHARE" + lockTablesClause + onLockExpression
×
UNCOV
2690
                } else if (driver.options.type === "oracle") {
×
UNCOV
2691
                    return " FOR UPDATE"
×
UNCOV
2692
                } else if (driver.options.type === "mssql") {
×
UNCOV
2693
                    return ""
×
2694
                } else {
UNCOV
2695
                    throw new LockNotSupportedOnGivenDriverError()
×
2696
                }
2697
            case "pessimistic_write":
UNCOV
2698
                if (
×
2699
                    DriverUtils.isMySQLFamily(driver) ||
×
2700
                    driver.options.type === "aurora-mysql" ||
2701
                    driver.options.type === "oracle"
2702
                ) {
UNCOV
2703
                    return " FOR UPDATE" + onLockExpression
×
UNCOV
2704
                } else if (
×
2705
                    DriverUtils.isPostgresFamily(driver) ||
×
2706
                    driver.options.type === "cockroachdb"
2707
                ) {
UNCOV
2708
                    return " FOR UPDATE" + lockTablesClause + onLockExpression
×
UNCOV
2709
                } else if (driver.options.type === "mssql") {
×
UNCOV
2710
                    return ""
×
2711
                } else {
UNCOV
2712
                    throw new LockNotSupportedOnGivenDriverError()
×
2713
                }
2714
            case "pessimistic_partial_write":
UNCOV
2715
                if (DriverUtils.isPostgresFamily(driver)) {
×
UNCOV
2716
                    return " FOR UPDATE" + lockTablesClause + " SKIP LOCKED"
×
UNCOV
2717
                } else if (DriverUtils.isMySQLFamily(driver)) {
×
UNCOV
2718
                    return " FOR UPDATE SKIP LOCKED"
×
2719
                } else {
2720
                    throw new LockNotSupportedOnGivenDriverError()
×
2721
                }
2722
            case "pessimistic_write_or_fail":
UNCOV
2723
                if (
×
2724
                    DriverUtils.isPostgresFamily(driver) ||
×
2725
                    driver.options.type === "cockroachdb"
2726
                ) {
UNCOV
2727
                    return " FOR UPDATE" + lockTablesClause + " NOWAIT"
×
UNCOV
2728
                } else if (DriverUtils.isMySQLFamily(driver)) {
×
UNCOV
2729
                    return " FOR UPDATE NOWAIT"
×
2730
                } else {
2731
                    throw new LockNotSupportedOnGivenDriverError()
×
2732
                }
2733
            case "for_no_key_update":
UNCOV
2734
                if (
×
2735
                    DriverUtils.isPostgresFamily(driver) ||
×
2736
                    driver.options.type === "cockroachdb"
2737
                ) {
UNCOV
2738
                    return (
×
2739
                        " FOR NO KEY UPDATE" +
2740
                        lockTablesClause +
2741
                        onLockExpression
2742
                    )
2743
                } else {
UNCOV
2744
                    throw new LockNotSupportedOnGivenDriverError()
×
2745
                }
2746
            case "for_key_share":
UNCOV
2747
                if (DriverUtils.isPostgresFamily(driver)) {
×
UNCOV
2748
                    return (
×
2749
                        " FOR KEY SHARE" + lockTablesClause + onLockExpression
2750
                    )
2751
                } else {
UNCOV
2752
                    throw new LockNotSupportedOnGivenDriverError()
×
2753
                }
2754
            default:
UNCOV
2755
                return ""
×
2756
        }
2757
    }
2758

2759
    /**
2760
     * Creates "HAVING" part of SQL query.
2761
     */
2762
    protected createHavingExpression() {
UNCOV
2763
        if (!this.expressionMap.havings || !this.expressionMap.havings.length)
×
UNCOV
2764
            return ""
×
2765
        const conditions = this.expressionMap.havings
×
2766
            .map((having, index) => {
2767
                switch (having.type) {
×
2768
                    case "and":
2769
                        return (
×
2770
                            (index > 0 ? "AND " : "") +
×
2771
                            this.replacePropertyNames(having.condition)
2772
                        )
2773
                    case "or":
2774
                        return (
×
2775
                            (index > 0 ? "OR " : "") +
×
2776
                            this.replacePropertyNames(having.condition)
2777
                        )
2778
                    default:
2779
                        return this.replacePropertyNames(having.condition)
×
2780
                }
2781
            })
2782
            .join(" ")
2783

2784
        if (!conditions.length) return ""
×
2785
        return " HAVING " + conditions
×
2786
    }
2787

2788
    protected buildEscapedEntityColumnSelects(
2789
        aliasName: string,
2790
        metadata: EntityMetadata,
2791
    ): SelectQuery[] {
UNCOV
2792
        const hasMainAlias = this.expressionMap.selects.some(
×
UNCOV
2793
            (select) => select.selection === aliasName,
×
2794
        )
2795

UNCOV
2796
        const columns: ColumnMetadata[] = []
×
UNCOV
2797
        if (hasMainAlias) {
×
UNCOV
2798
            columns.push(
×
2799
                ...metadata.columns.filter(
UNCOV
2800
                    (column) => column.isSelect === true,
×
2801
                ),
2802
            )
2803
        }
UNCOV
2804
        columns.push(
×
2805
            ...metadata.columns.filter((column) => {
UNCOV
2806
                return this.expressionMap.selects.some(
×
2807
                    (select) =>
UNCOV
2808
                        select.selection ===
×
2809
                        aliasName + "." + column.propertyPath,
2810
                )
2811
            }),
2812
        )
2813

2814
        // if user used partial selection and did not select some primary columns which are required to be selected
2815
        // we select those primary columns and mark them as "virtual". Later virtual column values will be removed from final entity
2816
        // to make entity contain exactly what user selected
UNCOV
2817
        if (columns.length === 0)
×
2818
            // however not in the case when nothing (even partial) was selected from this target (for example joins without selection)
UNCOV
2819
            return []
×
2820

UNCOV
2821
        const nonSelectedPrimaryColumns = this.expressionMap.queryEntity
×
2822
            ? metadata.primaryColumns.filter(
UNCOV
2823
                  (primaryColumn) => columns.indexOf(primaryColumn) === -1,
×
2824
              )
2825
            : []
UNCOV
2826
        const allColumns = [...columns, ...nonSelectedPrimaryColumns]
×
UNCOV
2827
        const finalSelects: SelectQuery[] = []
×
2828

UNCOV
2829
        const escapedAliasName = this.escape(aliasName)
×
UNCOV
2830
        allColumns.forEach((column) => {
×
2831
            let selectionPath =
UNCOV
2832
                escapedAliasName + "." + this.escape(column.databaseName)
×
2833

UNCOV
2834
            if (column.isVirtualProperty && column.query) {
×
UNCOV
2835
                selectionPath = `(${column.query(escapedAliasName)})`
×
2836
            }
2837

UNCOV
2838
            if (
×
2839
                this.connection.driver.spatialTypes.indexOf(column.type) !== -1
2840
            ) {
UNCOV
2841
                if (
×
2842
                    DriverUtils.isMySQLFamily(this.connection.driver) ||
×
2843
                    this.connection.driver.options.type === "aurora-mysql"
2844
                ) {
2845
                    const useLegacy = (
UNCOV
2846
                        this.connection.driver as
×
2847
                            | MysqlDriver
2848
                            | AuroraMysqlDriver
2849
                    ).options.legacySpatialSupport
UNCOV
2850
                    const asText = useLegacy ? "AsText" : "ST_AsText"
×
UNCOV
2851
                    selectionPath = `${asText}(${selectionPath})`
×
2852
                }
2853

UNCOV
2854
                if (DriverUtils.isPostgresFamily(this.connection.driver))
×
UNCOV
2855
                    if (column.precision) {
×
2856
                        // cast to JSON to trigger parsing in the driver
2857
                        selectionPath = `ST_AsGeoJSON(${selectionPath}, ${column.precision})::json`
×
2858
                    } else {
UNCOV
2859
                        selectionPath = `ST_AsGeoJSON(${selectionPath})::json`
×
2860
                    }
UNCOV
2861
                if (this.connection.driver.options.type === "mssql")
×
UNCOV
2862
                    selectionPath = `${selectionPath}.ToString()`
×
2863
            }
2864

UNCOV
2865
            const selections = this.expressionMap.selects.filter(
×
2866
                (select) =>
UNCOV
2867
                    select.selection === aliasName + "." + column.propertyPath,
×
2868
            )
UNCOV
2869
            if (selections.length) {
×
UNCOV
2870
                selections.forEach((selection) => {
×
UNCOV
2871
                    finalSelects.push({
×
2872
                        selection: selectionPath,
2873
                        aliasName: selection.aliasName
×
2874
                            ? selection.aliasName
2875
                            : DriverUtils.buildAlias(
2876
                                  this.connection.driver,
2877
                                  undefined,
2878
                                  aliasName,
2879
                                  column.databaseName,
2880
                              ),
2881
                        // todo: need to keep in mind that custom selection.aliasName breaks hydrator. fix it later!
2882
                        virtual: selection.virtual,
2883
                    })
2884
                })
2885
            } else {
UNCOV
2886
                finalSelects.push({
×
2887
                    selection: selectionPath,
2888
                    aliasName: DriverUtils.buildAlias(
2889
                        this.connection.driver,
2890
                        undefined,
2891
                        aliasName,
2892
                        column.databaseName,
2893
                    ),
2894
                    // todo: need to keep in mind that custom selection.aliasName breaks hydrator. fix it later!
2895
                    virtual: hasMainAlias,
2896
                })
2897
            }
2898
        })
UNCOV
2899
        return finalSelects
×
2900
    }
2901

2902
    protected findEntityColumnSelects(
2903
        aliasName: string,
2904
        metadata: EntityMetadata,
2905
    ): SelectQuery[] {
UNCOV
2906
        const mainSelect = this.expressionMap.selects.find(
×
UNCOV
2907
            (select) => select.selection === aliasName,
×
2908
        )
UNCOV
2909
        if (mainSelect) return [mainSelect]
×
2910

UNCOV
2911
        return this.expressionMap.selects.filter((select) => {
×
UNCOV
2912
            return metadata.columns.some(
×
2913
                (column) =>
UNCOV
2914
                    select.selection === aliasName + "." + column.propertyPath,
×
2915
            )
2916
        })
2917
    }
2918

2919
    private computeCountExpression() {
UNCOV
2920
        const mainAlias = this.expressionMap.mainAlias!.name // todo: will this work with "fromTableName"?
×
UNCOV
2921
        const metadata = this.expressionMap.mainAlias!.metadata
×
2922

UNCOV
2923
        const primaryColumns = metadata.primaryColumns
×
UNCOV
2924
        const distinctAlias = this.escape(mainAlias)
×
2925

2926
        // If we aren't doing anything that will create a join, we can use a simpler `COUNT` instead
2927
        // so we prevent poor query patterns in the most likely cases
UNCOV
2928
        if (
×
2929
            this.expressionMap.joinAttributes.length === 0 &&
×
2930
            this.expressionMap.relationIdAttributes.length === 0 &&
2931
            this.expressionMap.relationCountAttributes.length === 0
2932
        ) {
UNCOV
2933
            return "COUNT(1)"
×
2934
        }
2935

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

UNCOV
2938
        if (
×
2939
            this.connection.driver.options.type === "cockroachdb" ||
×
2940
            DriverUtils.isPostgresFamily(this.connection.driver)
2941
        ) {
2942
            // Postgres and CockroachDB can pass multiple parameters to the `DISTINCT` function
2943
            // https://www.postgresql.org/docs/9.5/sql-select.html#SQL-DISTINCT
UNCOV
2944
            return (
×
2945
                "COUNT(DISTINCT(" +
2946
                primaryColumns
2947
                    .map(
2948
                        (c) =>
UNCOV
2949
                            `${distinctAlias}.${this.escape(c.databaseName)}`,
×
2950
                    )
2951
                    .join(", ") +
2952
                "))"
2953
            )
2954
        }
2955

UNCOV
2956
        if (DriverUtils.isMySQLFamily(this.connection.driver)) {
×
2957
            // MySQL & MariaDB can pass multiple parameters to the `DISTINCT` language construct
2958
            // https://mariadb.com/kb/en/count-distinct/
UNCOV
2959
            return (
×
2960
                "COUNT(DISTINCT " +
2961
                primaryColumns
2962
                    .map(
2963
                        (c) =>
UNCOV
2964
                            `${distinctAlias}.${this.escape(c.databaseName)}`,
×
2965
                    )
2966
                    .join(", ") +
2967
                ")"
2968
            )
2969
        }
2970

UNCOV
2971
        if (this.connection.driver.options.type === "mssql") {
×
2972
            // SQL Server has gotta be different from everyone else.  They don't support
2973
            // distinct counting multiple columns & they don't have the same operator
2974
            // characteristic for concatenating, so we gotta use the `CONCAT` function.
2975
            // However, If it's exactly 1 column we can omit the `CONCAT` for better performance.
2976

UNCOV
2977
            const columnsExpression = primaryColumns
×
2978
                .map(
2979
                    (primaryColumn) =>
UNCOV
2980
                        `${distinctAlias}.${this.escape(
×
2981
                            primaryColumn.databaseName,
2982
                        )}`,
2983
                )
2984
                .join(", '|;|', ")
2985

UNCOV
2986
            if (primaryColumns.length === 1) {
×
UNCOV
2987
                return `COUNT(DISTINCT(${columnsExpression}))`
×
2988
            }
2989

UNCOV
2990
            return `COUNT(DISTINCT(CONCAT(${columnsExpression})))`
×
2991
        }
2992

UNCOV
2993
        if (this.connection.driver.options.type === "spanner") {
×
2994
            // spanner also has gotta be different from everyone else.
2995
            // they do not support concatenation of different column types without casting them to string
2996

2997
            if (primaryColumns.length === 1) {
×
2998
                return `COUNT(DISTINCT(${distinctAlias}.${this.escape(
×
2999
                    primaryColumns[0].databaseName,
3000
                )}))`
3001
            }
3002

3003
            const columnsExpression = primaryColumns
×
3004
                .map(
3005
                    (primaryColumn) =>
3006
                        `CAST(${distinctAlias}.${this.escape(
×
3007
                            primaryColumn.databaseName,
3008
                        )} AS STRING)`,
3009
                )
3010
                .join(", '|;|', ")
3011
            return `COUNT(DISTINCT(CONCAT(${columnsExpression})))`
×
3012
        }
3013

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

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

UNCOV
3021
        return (
×
3022
            `COUNT(DISTINCT(` +
3023
            primaryColumns
UNCOV
3024
                .map((c) => `${distinctAlias}.${this.escape(c.databaseName)}`)
×
3025
                .join(" || '|;|' || ") +
3026
            "))"
3027
        )
3028
    }
3029

3030
    protected async executeCountQuery(
3031
        queryRunner: QueryRunner,
3032
    ): Promise<number> {
UNCOV
3033
        const countSql = this.computeCountExpression()
×
3034

UNCOV
3035
        const results = await this.clone()
×
3036
            .orderBy()
3037
            .groupBy()
3038
            .offset(undefined)
3039
            .limit(undefined)
3040
            .skip(undefined)
3041
            .take(undefined)
3042
            .select(countSql, "cnt")
3043
            .setOption("disable-global-order")
3044
            .loadRawResults(queryRunner)
3045

UNCOV
3046
        if (!results || !results[0] || !results[0]["cnt"]) return 0
×
3047

UNCOV
3048
        return parseInt(results[0]["cnt"])
×
3049
    }
3050

3051
    protected async executeExistsQuery(
3052
        queryRunner: QueryRunner,
3053
    ): Promise<boolean> {
UNCOV
3054
        const results = await this.connection
×
3055
            .createQueryBuilder()
3056
            .fromDummy()
3057
            .select("1", "row_exists")
3058
            .whereExists(this)
3059
            .limit(1)
3060
            .loadRawResults(queryRunner)
3061

UNCOV
3062
        return results.length > 0
×
3063
    }
3064

3065
    protected applyFindOptions() {
3066
        // todo: convert relations: string[] to object map to simplify code
3067
        // todo: same with selects
3068

UNCOV
3069
        if (this.expressionMap.mainAlias!.metadata) {
×
UNCOV
3070
            if (this.findOptions.relationLoadStrategy) {
×
UNCOV
3071
                this.expressionMap.relationLoadStrategy =
×
3072
                    this.findOptions.relationLoadStrategy
3073
            }
3074

UNCOV
3075
            if (this.findOptions.comment) {
×
UNCOV
3076
                this.comment(this.findOptions.comment)
×
3077
            }
3078

UNCOV
3079
            if (this.findOptions.withDeleted) {
×
UNCOV
3080
                this.withDeleted()
×
3081
            }
3082

UNCOV
3083
            if (this.findOptions.select) {
×
UNCOV
3084
                const select = Array.isArray(this.findOptions.select)
×
3085
                    ? OrmUtils.propertyPathsToTruthyObject(
3086
                          this.findOptions.select as string[],
3087
                      )
3088
                    : this.findOptions.select
3089

UNCOV
3090
                this.buildSelect(
×
3091
                    select,
3092
                    this.expressionMap.mainAlias!.metadata,
3093
                    this.expressionMap.mainAlias!.name,
3094
                )
3095
            }
3096

UNCOV
3097
            if (this.selects.length) {
×
UNCOV
3098
                this.select(this.selects)
×
3099
            }
3100

UNCOV
3101
            this.selects = []
×
3102

UNCOV
3103
            if (this.findOptions.relations) {
×
UNCOV
3104
                const relations = Array.isArray(this.findOptions.relations)
×
3105
                    ? OrmUtils.propertyPathsToTruthyObject(
3106
                          this.findOptions.relations,
3107
                      )
3108
                    : this.findOptions.relations
3109

UNCOV
3110
                this.buildRelations(
×
3111
                    relations,
3112
                    typeof this.findOptions.select === "object"
×
3113
                        ? (this.findOptions.select as FindOptionsSelect<any>)
3114
                        : undefined,
3115
                    this.expressionMap.mainAlias!.metadata,
3116
                    this.expressionMap.mainAlias!.name,
3117
                )
UNCOV
3118
                if (
×
3119
                    this.findOptions.loadEagerRelations !== false &&
×
3120
                    this.expressionMap.relationLoadStrategy === "join"
3121
                ) {
UNCOV
3122
                    this.buildEagerRelations(
×
3123
                        relations,
3124
                        typeof this.findOptions.select === "object"
×
3125
                            ? (this.findOptions
3126
                                  .select as FindOptionsSelect<any>)
3127
                            : undefined,
3128
                        this.expressionMap.mainAlias!.metadata,
3129
                        this.expressionMap.mainAlias!.name,
3130
                    )
3131
                }
3132
            }
UNCOV
3133
            if (this.selects.length) {
×
UNCOV
3134
                this.addSelect(this.selects)
×
3135
            }
3136

UNCOV
3137
            if (this.findOptions.where) {
×
UNCOV
3138
                this.conditions = this.buildWhere(
×
3139
                    this.findOptions.where,
3140
                    this.expressionMap.mainAlias!.metadata,
3141
                    this.expressionMap.mainAlias!.name,
3142
                )
3143

UNCOV
3144
                if (this.conditions.length)
×
UNCOV
3145
                    this.andWhere(
×
3146
                        this.conditions.substr(0, 1) !== "("
×
3147
                            ? "(" + this.conditions + ")"
3148
                            : this.conditions,
3149
                    ) // temporary and where and braces
3150
            }
3151

UNCOV
3152
            if (this.findOptions.order) {
×
UNCOV
3153
                this.buildOrder(
×
3154
                    this.findOptions.order,
3155
                    this.expressionMap.mainAlias!.metadata,
3156
                    this.expressionMap.mainAlias!.name,
3157
                )
3158
            }
3159

3160
            // apply joins
UNCOV
3161
            if (this.joins.length) {
×
UNCOV
3162
                this.joins.forEach((join) => {
×
UNCOV
3163
                    if (join.select && !join.selection) {
×
3164
                        // if (join.selection) {
3165
                        //
3166
                        // } else {
UNCOV
3167
                        if (join.type === "inner") {
×
3168
                            this.innerJoinAndSelect(
×
3169
                                `${join.parentAlias}.${join.relationMetadata.propertyPath}`,
3170
                                join.alias,
3171
                            )
3172
                        } else {
UNCOV
3173
                            this.leftJoinAndSelect(
×
3174
                                `${join.parentAlias}.${join.relationMetadata.propertyPath}`,
3175
                                join.alias,
3176
                            )
3177
                        }
3178
                        // }
3179
                    } else {
UNCOV
3180
                        if (join.type === "inner") {
×
3181
                            this.innerJoin(
×
3182
                                `${join.parentAlias}.${join.relationMetadata.propertyPath}`,
3183
                                join.alias,
3184
                            )
3185
                        } else {
UNCOV
3186
                            this.leftJoin(
×
3187
                                `${join.parentAlias}.${join.relationMetadata.propertyPath}`,
3188
                                join.alias,
3189
                            )
3190
                        }
3191
                    }
3192

3193
                    // if (join.select) {
3194
                    //     if (this.findOptions.loadEagerRelations !== false) {
3195
                    //         FindOptionsUtils.joinEagerRelations(
3196
                    //             this,
3197
                    //             join.alias,
3198
                    //             join.relationMetadata.inverseEntityMetadata
3199
                    //         );
3200
                    //     }
3201
                    // }
3202
                })
3203
            }
3204

3205
            // if (this.conditions.length) {
3206
            //     this.where(this.conditions.join(" AND "));
3207
            // }
3208

3209
            // apply offset
UNCOV
3210
            if (this.findOptions.skip !== undefined) {
×
3211
                // if (this.findOptions.options && this.findOptions.options.pagination === false) {
3212
                //     this.offset(this.findOptions.skip);
3213
                // } else {
UNCOV
3214
                this.skip(this.findOptions.skip)
×
3215
                // }
3216
            }
3217

3218
            // apply limit
UNCOV
3219
            if (this.findOptions.take !== undefined) {
×
3220
                // if (this.findOptions.options && this.findOptions.options.pagination === false) {
3221
                //     this.limit(this.findOptions.take);
3222
                // } else {
UNCOV
3223
                this.take(this.findOptions.take)
×
3224
                // }
3225
            }
3226

3227
            // apply caching options
UNCOV
3228
            if (typeof this.findOptions.cache === "number") {
×
UNCOV
3229
                this.cache(this.findOptions.cache)
×
UNCOV
3230
            } else if (typeof this.findOptions.cache === "boolean") {
×
UNCOV
3231
                this.cache(this.findOptions.cache)
×
UNCOV
3232
            } else if (typeof this.findOptions.cache === "object") {
×
UNCOV
3233
                this.cache(
×
3234
                    this.findOptions.cache.id,
3235
                    this.findOptions.cache.milliseconds,
3236
                )
3237
            }
3238

UNCOV
3239
            if (this.findOptions.join) {
×
UNCOV
3240
                if (this.findOptions.join.leftJoin)
×
3241
                    Object.keys(this.findOptions.join.leftJoin).forEach(
×
3242
                        (key) => {
3243
                            this.leftJoin(
×
3244
                                this.findOptions.join!.leftJoin![key],
3245
                                key,
3246
                            )
3247
                        },
3248
                    )
3249

UNCOV
3250
                if (this.findOptions.join.innerJoin)
×
3251
                    Object.keys(this.findOptions.join.innerJoin).forEach(
×
3252
                        (key) => {
3253
                            this.innerJoin(
×
3254
                                this.findOptions.join!.innerJoin![key],
3255
                                key,
3256
                            )
3257
                        },
3258
                    )
3259

UNCOV
3260
                if (this.findOptions.join.leftJoinAndSelect)
×
UNCOV
3261
                    Object.keys(
×
3262
                        this.findOptions.join.leftJoinAndSelect,
3263
                    ).forEach((key) => {
UNCOV
3264
                        this.leftJoinAndSelect(
×
3265
                            this.findOptions.join!.leftJoinAndSelect![key],
3266
                            key,
3267
                        )
3268
                    })
3269

UNCOV
3270
                if (this.findOptions.join.innerJoinAndSelect)
×
UNCOV
3271
                    Object.keys(
×
3272
                        this.findOptions.join.innerJoinAndSelect,
3273
                    ).forEach((key) => {
UNCOV
3274
                        this.innerJoinAndSelect(
×
3275
                            this.findOptions.join!.innerJoinAndSelect![key],
3276
                            key,
3277
                        )
3278
                    })
3279
            }
3280

UNCOV
3281
            if (this.findOptions.lock) {
×
UNCOV
3282
                if (this.findOptions.lock.mode === "optimistic") {
×
UNCOV
3283
                    this.setLock(
×
3284
                        this.findOptions.lock.mode,
3285
                        this.findOptions.lock.version,
3286
                    )
UNCOV
3287
                } else if (
×
3288
                    this.findOptions.lock.mode === "pessimistic_read" ||
×
3289
                    this.findOptions.lock.mode === "pessimistic_write" ||
3290
                    this.findOptions.lock.mode === "dirty_read" ||
3291
                    this.findOptions.lock.mode ===
3292
                        "pessimistic_partial_write" ||
3293
                    this.findOptions.lock.mode ===
3294
                        "pessimistic_write_or_fail" ||
3295
                    this.findOptions.lock.mode === "for_no_key_update" ||
3296
                    this.findOptions.lock.mode === "for_key_share"
3297
                ) {
UNCOV
3298
                    const tableNames = this.findOptions.lock.tables
×
3299
                        ? this.findOptions.lock.tables.map((table) => {
3300
                              const tableAlias =
UNCOV
3301
                                  this.expressionMap.aliases.find((alias) => {
×
UNCOV
3302
                                      return (
×
3303
                                          alias.metadata
3304
                                              .tableNameWithoutPrefix === table
3305
                                      )
3306
                                  })
UNCOV
3307
                              if (!tableAlias) {
×
UNCOV
3308
                                  throw new TypeORMError(
×
3309
                                      `"${table}" is not part of this query`,
3310
                                  )
3311
                              }
UNCOV
3312
                              return this.escape(tableAlias.name)
×
3313
                          })
3314
                        : undefined
UNCOV
3315
                    this.setLock(
×
3316
                        this.findOptions.lock.mode,
3317
                        undefined,
3318
                        tableNames,
3319
                    )
3320

UNCOV
3321
                    if (this.findOptions.lock.onLocked) {
×
UNCOV
3322
                        this.setOnLocked(this.findOptions.lock.onLocked)
×
3323
                    }
3324
                }
3325
            }
3326

UNCOV
3327
            if (this.findOptions.loadRelationIds === true) {
×
UNCOV
3328
                this.loadAllRelationIds()
×
UNCOV
3329
            } else if (typeof this.findOptions.loadRelationIds === "object") {
×
UNCOV
3330
                this.loadAllRelationIds(this.findOptions.loadRelationIds as any)
×
3331
            }
3332

UNCOV
3333
            if (this.findOptions.loadEagerRelations !== false) {
×
UNCOV
3334
                FindOptionsUtils.joinEagerRelations(
×
3335
                    this,
3336
                    this.expressionMap.mainAlias!.name,
3337
                    this.expressionMap.mainAlias!.metadata,
3338
                )
3339
            }
3340

UNCOV
3341
            if (this.findOptions.transaction === true) {
×
UNCOV
3342
                this.expressionMap.useTransaction = true
×
3343
            }
3344

3345
            // if (this.orderBys.length) {
3346
            //     this.orderBys.forEach(orderBy => {
3347
            //         this.addOrderBy(orderBy.alias, orderBy.direction, orderBy.nulls);
3348
            //     });
3349
            // }
3350

3351
            // todo
3352
            // if (this.options.options && this.options.options.eagerRelations) {
3353
            //     this.queryBuilder
3354
            // }
3355

3356
            // todo
3357
            // if (this.findOptions.options && this.findOptions.listeners === false) {
3358
            //     this.callListeners(false);
3359
            // }
3360
        }
3361
    }
3362

3363
    public concatRelationMetadata(relationMetadata: RelationMetadata) {
UNCOV
3364
        this.relationMetadatas.push(relationMetadata)
×
3365
    }
3366

3367
    /**
3368
     * Executes sql generated by query builder and returns object with raw results and entities created from them.
3369
     */
3370
    protected async executeEntitiesAndRawResults(
3371
        queryRunner: QueryRunner,
3372
    ): Promise<{ entities: Entity[]; raw: any[] }> {
UNCOV
3373
        if (!this.expressionMap.mainAlias)
×
3374
            throw new TypeORMError(
×
3375
                `Alias is not set. Use "from" method to set an alias.`,
3376
            )
3377

UNCOV
3378
        if (
×
3379
            (this.expressionMap.lockMode === "pessimistic_read" ||
×
3380
                this.expressionMap.lockMode === "pessimistic_write" ||
3381
                this.expressionMap.lockMode === "pessimistic_partial_write" ||
3382
                this.expressionMap.lockMode === "pessimistic_write_or_fail" ||
3383
                this.expressionMap.lockMode === "for_no_key_update" ||
3384
                this.expressionMap.lockMode === "for_key_share") &&
3385
            !queryRunner.isTransactionActive
3386
        )
UNCOV
3387
            throw new PessimisticLockTransactionRequiredError()
×
3388

UNCOV
3389
        if (this.expressionMap.lockMode === "optimistic") {
×
UNCOV
3390
            const metadata = this.expressionMap.mainAlias.metadata
×
UNCOV
3391
            if (!metadata.versionColumn && !metadata.updateDateColumn)
×
UNCOV
3392
                throw new NoVersionOrUpdateDateColumnError(metadata.name)
×
3393
        }
3394

UNCOV
3395
        const relationIdLoader = new RelationIdLoader(
×
3396
            this.connection,
3397
            queryRunner,
3398
            this.expressionMap.relationIdAttributes,
3399
        )
UNCOV
3400
        const relationCountLoader = new RelationCountLoader(
×
3401
            this.connection,
3402
            queryRunner,
3403
            this.expressionMap.relationCountAttributes,
3404
        )
3405
        const relationIdMetadataTransformer =
UNCOV
3406
            new RelationIdMetadataToAttributeTransformer(this.expressionMap)
×
UNCOV
3407
        relationIdMetadataTransformer.transform()
×
3408
        const relationCountMetadataTransformer =
UNCOV
3409
            new RelationCountMetadataToAttributeTransformer(this.expressionMap)
×
UNCOV
3410
        relationCountMetadataTransformer.transform()
×
3411

UNCOV
3412
        let rawResults: any[] = [],
×
UNCOV
3413
            entities: any[] = []
×
3414

3415
        // for pagination enabled (e.g. skip and take) its much more complicated - its a special process
3416
        // where we make two queries to find the data we need
3417
        // first query find ids in skip and take range
3418
        // and second query loads the actual data in given ids range
UNCOV
3419
        if (
×
3420
            (this.expressionMap.skip || this.expressionMap.take) &&
×
3421
            this.expressionMap.joinAttributes.length > 0
3422
        ) {
3423
            // we are skipping order by here because its not working in subqueries anyway
3424
            // to make order by working we need to apply it on a distinct query
3425
            const [selects, orderBys] =
UNCOV
3426
                this.createOrderByCombinedWithSelectExpression("distinctAlias")
×
UNCOV
3427
            const metadata = this.expressionMap.mainAlias.metadata
×
UNCOV
3428
            const mainAliasName = this.expressionMap.mainAlias.name
×
3429

UNCOV
3430
            const querySelects = metadata.primaryColumns.map(
×
3431
                (primaryColumn) => {
UNCOV
3432
                    const distinctAlias = this.escape("distinctAlias")
×
UNCOV
3433
                    const columnAlias = this.escape(
×
3434
                        DriverUtils.buildAlias(
3435
                            this.connection.driver,
3436
                            undefined,
3437
                            mainAliasName,
3438
                            primaryColumn.databaseName,
3439
                        ),
3440
                    )
UNCOV
3441
                    if (!orderBys[columnAlias])
×
3442
                        // make sure we aren't overriding user-defined order in inverse direction
UNCOV
3443
                        orderBys[columnAlias] = "ASC"
×
3444

UNCOV
3445
                    const alias = DriverUtils.buildAlias(
×
3446
                        this.connection.driver,
3447
                        undefined,
3448
                        "ids_" + mainAliasName,
3449
                        primaryColumn.databaseName,
3450
                    )
3451

UNCOV
3452
                    return `${distinctAlias}.${columnAlias} AS ${this.escape(
×
3453
                        alias,
3454
                    )}`
3455
                },
3456
            )
3457

UNCOV
3458
            const originalQuery = this.clone()
×
3459

3460
            // preserve original timeTravel value since we set it to "false" in subquery
3461
            const originalQueryTimeTravel =
UNCOV
3462
                originalQuery.expressionMap.timeTravel
×
3463

UNCOV
3464
            rawResults = await new SelectQueryBuilder(
×
3465
                this.connection,
3466
                queryRunner,
3467
            )
3468
                .select(`DISTINCT ${querySelects.join(", ")}`)
3469
                .addSelect(selects)
3470
                .from(
3471
                    `(${originalQuery
3472
                        .orderBy()
3473
                        .timeTravelQuery(false) // set it to "false" since time travel clause must appear at the very end and applies to the entire SELECT clause.
3474
                        .getQuery()})`,
3475
                    "distinctAlias",
3476
                )
3477
                .timeTravelQuery(originalQueryTimeTravel)
3478
                .offset(this.expressionMap.skip)
3479
                .limit(this.expressionMap.take)
3480
                .orderBy(orderBys)
3481
                .cache(
3482
                    this.expressionMap.cache && this.expressionMap.cacheId
×
3483
                        ? `${this.expressionMap.cacheId}-pagination`
3484
                        : this.expressionMap.cache,
3485
                    this.expressionMap.cacheDuration,
3486
                )
3487
                .setParameters(this.getParameters())
3488
                .setNativeParameters(this.expressionMap.nativeParameters)
3489
                .getRawMany()
3490

UNCOV
3491
            if (rawResults.length > 0) {
×
UNCOV
3492
                let condition = ""
×
UNCOV
3493
                const parameters: ObjectLiteral = {}
×
UNCOV
3494
                if (metadata.hasMultiplePrimaryKeys) {
×
UNCOV
3495
                    condition = rawResults
×
3496
                        .map((result, index) => {
UNCOV
3497
                            return metadata.primaryColumns
×
3498
                                .map((primaryColumn) => {
UNCOV
3499
                                    const paramKey = `orm_distinct_ids_${index}_${primaryColumn.databaseName}`
×
3500
                                    const paramKeyResult =
UNCOV
3501
                                        DriverUtils.buildAlias(
×
3502
                                            this.connection.driver,
3503
                                            undefined,
3504
                                            "ids_" + mainAliasName,
3505
                                            primaryColumn.databaseName,
3506
                                        )
UNCOV
3507
                                    parameters[paramKey] =
×
3508
                                        result[paramKeyResult]
UNCOV
3509
                                    return `${mainAliasName}.${primaryColumn.propertyPath}=:${paramKey}`
×
3510
                                })
3511
                                .join(" AND ")
3512
                        })
3513
                        .join(" OR ")
3514
                } else {
UNCOV
3515
                    const alias = DriverUtils.buildAlias(
×
3516
                        this.connection.driver,
3517
                        undefined,
3518
                        "ids_" + mainAliasName,
3519
                        metadata.primaryColumns[0].databaseName,
3520
                    )
3521

UNCOV
3522
                    const ids = rawResults.map((result) => result[alias])
×
UNCOV
3523
                    const areAllNumbers = ids.every(
×
UNCOV
3524
                        (id: any) => typeof id === "number",
×
3525
                    )
UNCOV
3526
                    if (areAllNumbers) {
×
3527
                        // fixes #190. if all numbers then its safe to perform query without parameter
UNCOV
3528
                        condition = `${mainAliasName}.${
×
3529
                            metadata.primaryColumns[0].propertyPath
3530
                        } IN (${ids.join(", ")})`
3531
                    } else {
UNCOV
3532
                        parameters["orm_distinct_ids"] = ids
×
UNCOV
3533
                        condition =
×
3534
                            mainAliasName +
3535
                            "." +
3536
                            metadata.primaryColumns[0].propertyPath +
3537
                            " IN (:...orm_distinct_ids)"
3538
                    }
3539
                }
UNCOV
3540
                rawResults = await this.clone()
×
3541
                    .mergeExpressionMap({
3542
                        extraAppendedAndWhereCondition: condition,
3543
                    })
3544
                    .setParameters(parameters)
3545
                    .loadRawResults(queryRunner)
3546
            }
3547
        } else {
UNCOV
3548
            rawResults = await this.loadRawResults(queryRunner)
×
3549
        }
3550

UNCOV
3551
        if (rawResults.length > 0) {
×
3552
            // transform raw results into entities
UNCOV
3553
            const rawRelationIdResults = await relationIdLoader.load(rawResults)
×
UNCOV
3554
            const rawRelationCountResults = await relationCountLoader.load(
×
3555
                rawResults,
3556
            )
UNCOV
3557
            const transformer = new RawSqlResultsToEntityTransformer(
×
3558
                this.expressionMap,
3559
                this.connection.driver,
3560
                rawRelationIdResults,
3561
                rawRelationCountResults,
3562
                this.queryRunner,
3563
            )
UNCOV
3564
            entities = transformer.transform(
×
3565
                rawResults,
3566
                this.expressionMap.mainAlias!,
3567
            )
3568

3569
            // broadcast all "after load" events
UNCOV
3570
            if (
×
3571
                this.expressionMap.callListeners === true &&
×
3572
                this.expressionMap.mainAlias.hasMetadata
3573
            ) {
UNCOV
3574
                await queryRunner.broadcaster.broadcast(
×
3575
                    "Load",
3576
                    this.expressionMap.mainAlias.metadata,
3577
                    entities,
3578
                )
3579
            }
3580
        }
3581

UNCOV
3582
        if (this.expressionMap.relationLoadStrategy === "query") {
×
3583
            const queryStrategyRelationIdLoader =
UNCOV
3584
                new QueryStrategyRelationIdLoader(this.connection, queryRunner)
×
3585

UNCOV
3586
            await Promise.all(
×
3587
                this.relationMetadatas.map(async (relation) => {
UNCOV
3588
                    const relationTarget = relation.inverseEntityMetadata.target
×
3589
                    const relationAlias =
UNCOV
3590
                        relation.inverseEntityMetadata.targetName
×
3591

UNCOV
3592
                    const select = Array.isArray(this.findOptions.select)
×
3593
                        ? OrmUtils.propertyPathsToTruthyObject(
3594
                              this.findOptions.select as string[],
3595
                          )
3596
                        : this.findOptions.select
UNCOV
3597
                    const relations = Array.isArray(this.findOptions.relations)
×
3598
                        ? OrmUtils.propertyPathsToTruthyObject(
3599
                              this.findOptions.relations,
3600
                          )
3601
                        : this.findOptions.relations
3602

UNCOV
3603
                    const queryBuilder = this.createQueryBuilder(queryRunner)
×
3604
                        .select(relationAlias)
3605
                        .from(relationTarget, relationAlias)
3606
                        .setFindOptions({
3607
                            select: select
×
3608
                                ? OrmUtils.deepValue(
3609
                                      select,
3610
                                      relation.propertyPath,
3611
                                  )
3612
                                : undefined,
3613
                            order: this.findOptions.order
×
3614
                                ? OrmUtils.deepValue(
3615
                                      this.findOptions.order,
3616
                                      relation.propertyPath,
3617
                                  )
3618
                                : undefined,
3619
                            relations: relations
×
3620
                                ? OrmUtils.deepValue(
3621
                                      relations,
3622
                                      relation.propertyPath,
3623
                                  )
3624
                                : undefined,
3625
                            withDeleted: this.findOptions.withDeleted,
3626
                            relationLoadStrategy:
3627
                                this.findOptions.relationLoadStrategy,
3628
                        })
UNCOV
3629
                    if (entities.length > 0) {
×
3630
                        const relatedEntityGroups: any[] =
UNCOV
3631
                            await queryStrategyRelationIdLoader.loadManyToManyRelationIdsAndGroup(
×
3632
                                relation,
3633
                                entities,
3634
                                undefined,
3635
                                queryBuilder,
3636
                            )
UNCOV
3637
                        entities.forEach((entity) => {
×
UNCOV
3638
                            const relatedEntityGroup = relatedEntityGroups.find(
×
UNCOV
3639
                                (group) => group.entity === entity,
×
3640
                            )
UNCOV
3641
                            if (relatedEntityGroup) {
×
3642
                                const value =
UNCOV
3643
                                    relatedEntityGroup.related === undefined
×
3644
                                        ? null
3645
                                        : relatedEntityGroup.related
UNCOV
3646
                                relation.setEntityValue(entity, value)
×
3647
                            }
3648
                        })
3649
                    }
3650
                }),
3651
            )
3652
        }
3653

UNCOV
3654
        return {
×
3655
            raw: rawResults,
3656
            entities: entities,
3657
        }
3658
    }
3659

3660
    protected createOrderByCombinedWithSelectExpression(
3661
        parentAlias: string,
3662
    ): [string, OrderByCondition] {
3663
        // if table has a default order then apply it
UNCOV
3664
        const orderBys = this.expressionMap.allOrderBys
×
UNCOV
3665
        const selectString = Object.keys(orderBys)
×
3666
            .map((orderCriteria) => {
UNCOV
3667
                if (orderCriteria.indexOf(".") !== -1) {
×
UNCOV
3668
                    const criteriaParts = orderCriteria.split(".")
×
UNCOV
3669
                    const aliasName = criteriaParts[0]
×
UNCOV
3670
                    const propertyPath = criteriaParts.slice(1).join(".")
×
UNCOV
3671
                    const alias = this.expressionMap.findAliasByName(aliasName)
×
3672
                    const column =
UNCOV
3673
                        alias.metadata.findColumnWithPropertyPath(propertyPath)
×
UNCOV
3674
                    return (
×
3675
                        this.escape(parentAlias) +
3676
                        "." +
3677
                        this.escape(
3678
                            DriverUtils.buildAlias(
3679
                                this.connection.driver,
3680
                                undefined,
3681
                                aliasName,
3682
                                column!.databaseName,
3683
                            ),
3684
                        )
3685
                    )
3686
                } else {
UNCOV
3687
                    if (
×
3688
                        this.expressionMap.selects.find(
3689
                            (select) =>
UNCOV
3690
                                select.selection === orderCriteria ||
×
3691
                                select.aliasName === orderCriteria,
3692
                        )
3693
                    )
UNCOV
3694
                        return (
×
3695
                            this.escape(parentAlias) +
3696
                            "." +
3697
                            this.escape(orderCriteria)
3698
                        )
3699

UNCOV
3700
                    return ""
×
3701
                }
3702
            })
3703
            .join(", ")
3704

UNCOV
3705
        const orderByObject: OrderByCondition = {}
×
UNCOV
3706
        Object.keys(orderBys).forEach((orderCriteria) => {
×
UNCOV
3707
            if (orderCriteria.indexOf(".") !== -1) {
×
UNCOV
3708
                const criteriaParts = orderCriteria.split(".")
×
UNCOV
3709
                const aliasName = criteriaParts[0]
×
UNCOV
3710
                const propertyPath = criteriaParts.slice(1).join(".")
×
UNCOV
3711
                const alias = this.expressionMap.findAliasByName(aliasName)
×
3712
                const column =
UNCOV
3713
                    alias.metadata.findColumnWithPropertyPath(propertyPath)
×
UNCOV
3714
                orderByObject[
×
3715
                    this.escape(parentAlias) +
3716
                        "." +
3717
                        this.escape(
3718
                            DriverUtils.buildAlias(
3719
                                this.connection.driver,
3720
                                undefined,
3721
                                aliasName,
3722
                                column!.databaseName,
3723
                            ),
3724
                        )
3725
                ] = orderBys[orderCriteria]
3726
            } else {
UNCOV
3727
                if (
×
3728
                    this.expressionMap.selects.find(
3729
                        (select) =>
UNCOV
3730
                            select.selection === orderCriteria ||
×
3731
                            select.aliasName === orderCriteria,
3732
                    )
3733
                ) {
UNCOV
3734
                    orderByObject[
×
3735
                        this.escape(parentAlias) +
3736
                            "." +
3737
                            this.escape(orderCriteria)
3738
                    ] = orderBys[orderCriteria]
3739
                } else {
UNCOV
3740
                    orderByObject[orderCriteria] = orderBys[orderCriteria]
×
3741
                }
3742
            }
3743
        })
3744

UNCOV
3745
        return [selectString, orderByObject]
×
3746
    }
3747

3748
    /**
3749
     * Loads raw results from the database.
3750
     */
3751
    protected async loadRawResults(queryRunner: QueryRunner) {
UNCOV
3752
        const [sql, parameters] = this.getQueryAndParameters()
×
3753
        const queryId =
UNCOV
3754
            sql +
×
3755
            " -- PARAMETERS: " +
3756
            JSON.stringify(parameters, (_, value) =>
UNCOV
3757
                typeof value === "bigint" ? value.toString() : value,
×
3758
            )
3759
        const cacheOptions =
UNCOV
3760
            typeof this.connection.options.cache === "object"
×
3761
                ? this.connection.options.cache
3762
                : {}
3763
        let savedQueryResultCacheOptions: QueryResultCacheOptions | undefined =
UNCOV
3764
            undefined
×
3765
        const isCachingEnabled =
3766
            // Caching is enabled globally and isn't disabled locally.
UNCOV
3767
            (cacheOptions.alwaysEnabled &&
×
3768
                this.expressionMap.cache !== false) ||
3769
            // ...or it's enabled locally explicitly.
3770
            this.expressionMap.cache === true
UNCOV
3771
        let cacheError = false
×
UNCOV
3772
        if (this.connection.queryResultCache && isCachingEnabled) {
×
UNCOV
3773
            try {
×
UNCOV
3774
                savedQueryResultCacheOptions =
×
3775
                    await this.connection.queryResultCache.getFromCache(
3776
                        {
3777
                            identifier: this.expressionMap.cacheId,
3778
                            query: queryId,
3779
                            duration:
3780
                                this.expressionMap.cacheDuration ||
×
3781
                                cacheOptions.duration ||
3782
                                1000,
3783
                        },
3784
                        queryRunner,
3785
                    )
UNCOV
3786
                if (
×
3787
                    savedQueryResultCacheOptions &&
×
3788
                    !this.connection.queryResultCache.isExpired(
3789
                        savedQueryResultCacheOptions,
3790
                    )
3791
                ) {
UNCOV
3792
                    return JSON.parse(savedQueryResultCacheOptions.result)
×
3793
                }
3794
            } catch (error) {
3795
                if (!cacheOptions.ignoreErrors) {
×
3796
                    throw error
×
3797
                }
3798
                cacheError = true
×
3799
            }
3800
        }
3801

UNCOV
3802
        const results = await queryRunner.query(sql, parameters, true)
×
3803

UNCOV
3804
        if (
×
3805
            !cacheError &&
×
3806
            this.connection.queryResultCache &&
3807
            isCachingEnabled
3808
        ) {
UNCOV
3809
            try {
×
UNCOV
3810
                await this.connection.queryResultCache.storeInCache(
×
3811
                    {
3812
                        identifier: this.expressionMap.cacheId,
3813
                        query: queryId,
3814
                        time: Date.now(),
3815
                        duration:
3816
                            this.expressionMap.cacheDuration ||
×
3817
                            cacheOptions.duration ||
3818
                            1000,
3819
                        result: JSON.stringify(results.records),
3820
                    },
3821
                    savedQueryResultCacheOptions,
3822
                    queryRunner,
3823
                )
3824
            } catch (error) {
3825
                if (!cacheOptions.ignoreErrors) {
×
3826
                    throw error
×
3827
                }
3828
            }
3829
        }
3830

UNCOV
3831
        return results.records
×
3832
    }
3833

3834
    /**
3835
     * Merges into expression map given expression map properties.
3836
     */
3837
    protected mergeExpressionMap(
3838
        expressionMap: Partial<QueryExpressionMap>,
3839
    ): this {
UNCOV
3840
        ObjectUtils.assign(this.expressionMap, expressionMap)
×
UNCOV
3841
        return this
×
3842
    }
3843

3844
    /**
3845
     * Normalizes a give number - converts to int if possible.
3846
     */
3847
    protected normalizeNumber(num: any) {
UNCOV
3848
        if (typeof num === "number" || num === undefined || num === null)
×
UNCOV
3849
            return num
×
3850

UNCOV
3851
        return Number(num)
×
3852
    }
3853

3854
    /**
3855
     * Creates a query builder used to execute sql queries inside this query builder.
3856
     */
3857
    protected obtainQueryRunner() {
UNCOV
3858
        return (
×
3859
            this.queryRunner ||
×
3860
            this.connection.createQueryRunner(
3861
                this.connection.defaultReplicationModeForReads(),
3862
            )
3863
        )
3864
    }
3865

3866
    protected buildSelect(
3867
        select: FindOptionsSelect<any>,
3868
        metadata: EntityMetadata,
3869
        alias: string,
3870
        embedPrefix?: string,
3871
    ) {
UNCOV
3872
        for (const key in select) {
×
UNCOV
3873
            if (select[key] === undefined || select[key] === false) continue
×
3874

UNCOV
3875
            const propertyPath = embedPrefix ? embedPrefix + "." + key : key
×
3876
            const column =
UNCOV
3877
                metadata.findColumnWithPropertyPathStrict(propertyPath)
×
UNCOV
3878
            const embed = metadata.findEmbeddedWithPropertyPath(propertyPath)
×
UNCOV
3879
            const relation = metadata.findRelationWithPropertyPath(propertyPath)
×
3880

UNCOV
3881
            if (!embed && !column && !relation)
×
UNCOV
3882
                throw new EntityPropertyNotFoundError(propertyPath, metadata)
×
3883

UNCOV
3884
            if (column) {
×
UNCOV
3885
                this.selects.push(alias + "." + propertyPath)
×
3886
                // this.addSelect(alias + "." + propertyPath);
UNCOV
3887
            } else if (embed) {
×
UNCOV
3888
                this.buildSelect(
×
3889
                    select[key] as FindOptionsSelect<any>,
3890
                    metadata,
3891
                    alias,
3892
                    propertyPath,
3893
                )
3894

3895
                // } else if (relation) {
3896
                //     const joinAlias = alias + "_" + relation.propertyName;
3897
                //     const existJoin = this.joins.find(join => join.alias === joinAlias);
3898
                //     if (!existJoin) {
3899
                //         this.joins.push({
3900
                //             type: "left",
3901
                //             select: false,
3902
                //             alias: joinAlias,
3903
                //             parentAlias: alias,
3904
                //             relationMetadata: relation
3905
                //         });
3906
                //     }
3907
                //     this.buildOrder(select[key] as FindOptionsOrder<any>, relation.inverseEntityMetadata, joinAlias);
3908
            }
3909
        }
3910
    }
3911

3912
    protected buildRelations(
3913
        relations: FindOptionsRelations<any>,
3914
        selection: FindOptionsSelect<any> | undefined,
3915
        metadata: EntityMetadata,
3916
        alias: string,
3917
        embedPrefix?: string,
3918
    ) {
UNCOV
3919
        if (!relations) return
×
3920

UNCOV
3921
        Object.keys(relations).forEach((relationName) => {
×
UNCOV
3922
            const relationValue = (relations as any)[relationName]
×
UNCOV
3923
            const propertyPath = embedPrefix
×
3924
                ? embedPrefix + "." + relationName
3925
                : relationName
UNCOV
3926
            const embed = metadata.findEmbeddedWithPropertyPath(propertyPath)
×
UNCOV
3927
            const relation = metadata.findRelationWithPropertyPath(propertyPath)
×
UNCOV
3928
            if (!embed && !relation)
×
UNCOV
3929
                throw new EntityPropertyNotFoundError(propertyPath, metadata)
×
3930

UNCOV
3931
            if (embed) {
×
UNCOV
3932
                this.buildRelations(
×
3933
                    relationValue,
3934
                    typeof selection === "object"
×
3935
                        ? OrmUtils.deepValue(selection, embed.propertyPath)
3936
                        : undefined,
3937
                    metadata,
3938
                    alias,
3939
                    propertyPath,
3940
                )
UNCOV
3941
            } else if (relation) {
×
UNCOV
3942
                let joinAlias = alias + "_" + propertyPath.replace(".", "_")
×
UNCOV
3943
                joinAlias = DriverUtils.buildAlias(
×
3944
                    this.connection.driver,
3945
                    { joiner: "__" },
3946
                    alias,
3947
                    joinAlias,
3948
                )
UNCOV
3949
                if (
×
3950
                    relationValue === true ||
×
3951
                    typeof relationValue === "object"
3952
                ) {
UNCOV
3953
                    if (this.expressionMap.relationLoadStrategy === "query") {
×
UNCOV
3954
                        this.concatRelationMetadata(relation)
×
3955
                    } else {
3956
                        // join
UNCOV
3957
                        this.joins.push({
×
3958
                            type: "left",
3959
                            select: true,
3960
                            selection:
3961
                                selection &&
×
3962
                                typeof selection[relationName] === "object"
3963
                                    ? (selection[
3964
                                          relationName
3965
                                      ] as FindOptionsSelect<any>)
3966
                                    : undefined,
3967
                            alias: joinAlias,
3968
                            parentAlias: alias,
3969
                            relationMetadata: relation,
3970
                        })
3971

UNCOV
3972
                        if (
×
3973
                            selection &&
×
3974
                            typeof selection[relationName] === "object"
3975
                        ) {
UNCOV
3976
                            this.buildSelect(
×
3977
                                selection[
3978
                                    relationName
3979
                                ] as FindOptionsSelect<any>,
3980
                                relation.inverseEntityMetadata,
3981
                                joinAlias,
3982
                            )
3983
                        }
3984
                    }
3985
                }
3986

UNCOV
3987
                if (
×
3988
                    typeof relationValue === "object" &&
×
3989
                    this.expressionMap.relationLoadStrategy === "join"
3990
                ) {
UNCOV
3991
                    this.buildRelations(
×
3992
                        relationValue,
3993
                        typeof selection === "object"
×
3994
                            ? OrmUtils.deepValue(
3995
                                  selection,
3996
                                  relation.propertyPath,
3997
                              )
3998
                            : undefined,
3999
                        relation.inverseEntityMetadata,
4000
                        joinAlias,
4001
                        undefined,
4002
                    )
4003
                }
4004
            }
4005
        })
4006
    }
4007

4008
    protected buildEagerRelations(
4009
        relations: FindOptionsRelations<any>,
4010
        selection: FindOptionsSelect<any> | undefined,
4011
        metadata: EntityMetadata,
4012
        alias: string,
4013
        embedPrefix?: string,
4014
    ) {
UNCOV
4015
        if (!relations) return
×
4016

UNCOV
4017
        Object.keys(relations).forEach((relationName) => {
×
UNCOV
4018
            const relationValue = (relations as any)[relationName]
×
UNCOV
4019
            const propertyPath = embedPrefix
×
4020
                ? embedPrefix + "." + relationName
4021
                : relationName
UNCOV
4022
            const embed = metadata.findEmbeddedWithPropertyPath(propertyPath)
×
UNCOV
4023
            const relation = metadata.findRelationWithPropertyPath(propertyPath)
×
UNCOV
4024
            if (!embed && !relation)
×
4025
                throw new EntityPropertyNotFoundError(propertyPath, metadata)
×
4026

UNCOV
4027
            if (embed) {
×
UNCOV
4028
                this.buildEagerRelations(
×
4029
                    relationValue,
4030
                    typeof selection === "object"
×
4031
                        ? OrmUtils.deepValue(selection, embed.propertyPath)
4032
                        : undefined,
4033
                    metadata,
4034
                    alias,
4035
                    propertyPath,
4036
                )
UNCOV
4037
            } else if (relation) {
×
UNCOV
4038
                let joinAlias = alias + "_" + propertyPath.replace(".", "_")
×
UNCOV
4039
                joinAlias = DriverUtils.buildAlias(
×
4040
                    this.connection.driver,
4041
                    { joiner: "__" },
4042
                    alias,
4043
                    joinAlias,
4044
                )
4045

UNCOV
4046
                if (
×
4047
                    relationValue === true ||
×
4048
                    typeof relationValue === "object"
4049
                ) {
UNCOV
4050
                    relation.inverseEntityMetadata.eagerRelations.forEach(
×
4051
                        (eagerRelation) => {
4052
                            let eagerRelationJoinAlias =
UNCOV
4053
                                joinAlias +
×
4054
                                "_" +
4055
                                eagerRelation.propertyPath.replace(".", "_")
UNCOV
4056
                            eagerRelationJoinAlias = DriverUtils.buildAlias(
×
4057
                                this.connection.driver,
4058
                                { joiner: "__" },
4059
                                joinAlias,
4060
                                eagerRelationJoinAlias,
4061
                            )
4062

UNCOV
4063
                            const existJoin = this.joins.find(
×
UNCOV
4064
                                (join) => join.alias === eagerRelationJoinAlias,
×
4065
                            )
UNCOV
4066
                            if (!existJoin) {
×
UNCOV
4067
                                this.joins.push({
×
4068
                                    type: "left",
4069
                                    select: true,
4070
                                    alias: eagerRelationJoinAlias,
4071
                                    parentAlias: joinAlias,
4072
                                    selection: undefined,
4073
                                    relationMetadata: eagerRelation,
4074
                                })
4075
                            }
4076

UNCOV
4077
                            if (
×
4078
                                selection &&
×
4079
                                typeof selection[relationName] === "object"
4080
                            ) {
4081
                                this.buildSelect(
×
4082
                                    selection[
4083
                                        relationName
4084
                                    ] as FindOptionsSelect<any>,
4085
                                    relation.inverseEntityMetadata,
4086
                                    joinAlias,
4087
                                )
4088
                            }
4089
                        },
4090
                    )
4091
                }
4092

UNCOV
4093
                if (typeof relationValue === "object") {
×
UNCOV
4094
                    this.buildEagerRelations(
×
4095
                        relationValue,
4096
                        typeof selection === "object"
×
4097
                            ? OrmUtils.deepValue(
4098
                                  selection,
4099
                                  relation.propertyPath,
4100
                              )
4101
                            : undefined,
4102
                        relation.inverseEntityMetadata,
4103
                        joinAlias,
4104
                        undefined,
4105
                    )
4106
                }
4107
            }
4108
        })
4109
    }
4110

4111
    protected buildOrder(
4112
        order: FindOptionsOrder<any>,
4113
        metadata: EntityMetadata,
4114
        alias: string,
4115
        embedPrefix?: string,
4116
    ) {
UNCOV
4117
        for (const key in order) {
×
UNCOV
4118
            if (order[key] === undefined) continue
×
4119

UNCOV
4120
            const propertyPath = embedPrefix ? embedPrefix + "." + key : key
×
4121
            const column =
UNCOV
4122
                metadata.findColumnWithPropertyPathStrict(propertyPath)
×
UNCOV
4123
            const embed = metadata.findEmbeddedWithPropertyPath(propertyPath)
×
UNCOV
4124
            const relation = metadata.findRelationWithPropertyPath(propertyPath)
×
4125

UNCOV
4126
            if (!embed && !column && !relation)
×
UNCOV
4127
                throw new EntityPropertyNotFoundError(propertyPath, metadata)
×
4128

UNCOV
4129
            if (column) {
×
4130
                let direction =
UNCOV
4131
                    typeof order[key] === "object"
×
4132
                        ? (order[key] as any).direction
4133
                        : order[key]
UNCOV
4134
                direction =
×
4135
                    direction === "DESC" ||
×
4136
                    direction === "desc" ||
4137
                    direction === -1
4138
                        ? "DESC"
4139
                        : "ASC"
4140
                let nulls =
UNCOV
4141
                    typeof order[key] === "object"
×
4142
                        ? (order[key] as any).nulls
4143
                        : undefined
UNCOV
4144
                nulls =
×
4145
                    nulls?.toLowerCase() === "first"
×
4146
                        ? "NULLS FIRST"
4147
                        : nulls?.toLowerCase() === "last"
×
4148
                        ? "NULLS LAST"
4149
                        : undefined
4150

UNCOV
4151
                const aliasPath = `${alias}.${propertyPath}`
×
4152
                // const selection = this.expressionMap.selects.find(
4153
                //     (s) => s.selection === aliasPath,
4154
                // )
4155
                // if (selection) {
4156
                //     // this is not building correctly now???
4157
                //     aliasPath = this.escape(
4158
                //         DriverUtils.buildAlias(
4159
                //             this.connection.driver,
4160
                //             undefined,
4161
                //             alias,
4162
                //             column.databaseName,
4163
                //         ),
4164
                //     )
4165
                //     // selection.aliasName = aliasPath
4166
                // } else {
4167
                //     if (column.isVirtualProperty && column.query) {
4168
                //         aliasPath = `(${column.query(alias)})`
4169
                //     }
4170
                // }
4171

4172
                // console.log("add sort", selection, aliasPath, direction, nulls)
UNCOV
4173
                this.addOrderBy(aliasPath, direction, nulls)
×
4174
                // this.orderBys.push({ alias: alias + "." + propertyPath, direction, nulls });
UNCOV
4175
            } else if (embed) {
×
UNCOV
4176
                this.buildOrder(
×
4177
                    order[key] as FindOptionsOrder<any>,
4178
                    metadata,
4179
                    alias,
4180
                    propertyPath,
4181
                )
UNCOV
4182
            } else if (relation) {
×
UNCOV
4183
                let joinAlias = alias + "_" + propertyPath.replace(".", "_")
×
UNCOV
4184
                joinAlias = DriverUtils.buildAlias(
×
4185
                    this.connection.driver,
4186
                    { joiner: "__" },
4187
                    alias,
4188
                    joinAlias,
4189
                )
4190
                // console.log("joinAlias", joinAlias, joinAlias.length, this.connection.driver.maxAliasLength)
4191
                // todo: use expressionMap.joinAttributes, and create a new one using
4192
                //  const joinAttribute = new JoinAttribute(this.connection, this.expressionMap);
4193

UNCOV
4194
                const existJoin = this.joins.find(
×
UNCOV
4195
                    (join) => join.alias === joinAlias,
×
4196
                )
UNCOV
4197
                if (!existJoin) {
×
UNCOV
4198
                    this.joins.push({
×
4199
                        type: "left",
4200
                        select: false,
4201
                        alias: joinAlias,
4202
                        parentAlias: alias,
4203
                        selection: undefined,
4204
                        relationMetadata: relation,
4205
                    })
4206
                }
UNCOV
4207
                this.buildOrder(
×
4208
                    order[key] as FindOptionsOrder<any>,
4209
                    relation.inverseEntityMetadata,
4210
                    joinAlias,
4211
                )
4212
            }
4213
        }
4214
    }
4215

4216
    protected buildWhere(
4217
        where: FindOptionsWhere<any>[] | FindOptionsWhere<any>,
4218
        metadata: EntityMetadata,
4219
        alias: string,
4220
        embedPrefix?: string,
4221
    ) {
UNCOV
4222
        let condition: string = ""
×
4223
        // let parameterIndex = Object.keys(this.expressionMap.nativeParameters).length;
UNCOV
4224
        if (Array.isArray(where)) {
×
UNCOV
4225
            if (where.length) {
×
UNCOV
4226
                condition = where
×
4227
                    .map((whereItem) => {
UNCOV
4228
                        return this.buildWhere(
×
4229
                            whereItem,
4230
                            metadata,
4231
                            alias,
4232
                            embedPrefix,
4233
                        )
4234
                    })
UNCOV
4235
                    .filter((condition) => !!condition)
×
UNCOV
4236
                    .map((condition) => "(" + condition + ")")
×
4237
                    .join(" OR ")
4238
            }
4239
        } else {
NEW
4240
            let andConditions: string[] = []
×
NEW
4241
            for (let key in where) {
×
NEW
4242
                let parameterValue = where[key]
×
4243

UNCOV
4244
                const propertyPath = embedPrefix ? embedPrefix + "." + key : key
×
4245
                const column =
UNCOV
4246
                    metadata.findColumnWithPropertyPathStrict(propertyPath)
×
4247
                const embed =
UNCOV
4248
                    metadata.findEmbeddedWithPropertyPath(propertyPath)
×
4249
                const relation =
UNCOV
4250
                    metadata.findRelationWithPropertyPath(propertyPath)
×
4251

NEW
4252
                if (!embed && !column && !relation) {
×
UNCOV
4253
                    throw new EntityPropertyNotFoundError(
×
4254
                        propertyPath,
4255
                        metadata,
4256
                    )
4257
                }
4258

NEW
4259
                if (parameterValue === undefined) {
×
NEW
4260
                    if (this.expressionMap.throwOnUndefinedInFind) {
×
NEW
4261
                        throw new TypeORMError(
×
4262
                            `Undefined value encountered in property '${alias}.${key}' of the find operation. ` +
4263
                                `Set 'throwOnUndefinedInFind' to false in connection options to skip properties with undefined values.`,
4264
                        )
4265
                    }
NEW
4266
                    continue
×
4267
                }
4268

NEW
4269
                if (column) {
×
NEW
4270
                    if (parameterValue === null) {
×
NEW
4271
                        if (!this.expressionMap.treatJsNullAsSqlNull) {
×
NEW
4272
                            continue // Skip null values by default
×
4273
                        }
4274

4275
                        // If treatJsNullAsSqlNull is true, transform JS null to SQL NULL
UNCOV
4276
                        const propertyPath = embedPrefix
×
4277
                            ? embedPrefix + "." + key
4278
                            : key
NEW
4279
                        let aliasPath = `${alias}.${propertyPath}`
×
NEW
4280
                        if (column.isVirtualProperty && column.query) {
×
NEW
4281
                            aliasPath = `(${column.query(alias)})`
×
4282
                        }
NEW
4283
                        andConditions.push(`${aliasPath} IS NULL`)
×
NEW
4284
                        continue
×
4285
                    }
4286

NEW
4287
                    let aliasPath = `${alias}.${propertyPath}`
×
NEW
4288
                    if (column.isVirtualProperty && column.query) {
×
UNCOV
4289
                        aliasPath = `(${column.query(this.escape(alias))})`
×
4290
                    }
4291
                    // const parameterName = alias + "_" + propertyPath.split(".").join("_") + "_" + parameterIndex;
4292

4293
                    // todo: we need to handle other operators as well?
UNCOV
4294
                    if (InstanceChecker.isEqualOperator(where[key])) {
×
UNCOV
4295
                        parameterValue = where[key].value
×
4296
                    }
4297

UNCOV
4298
                    if (column.transformer) {
×
UNCOV
4299
                        if (parameterValue instanceof FindOperator) {
×
UNCOV
4300
                            parameterValue.transformValue(column.transformer)
×
4301
                        } else {
UNCOV
4302
                            parameterValue = ApplyValueTransformers.transformTo(
×
4303
                                column.transformer,
4304
                                parameterValue,
4305
                            )
4306
                        }
4307
                    }
4308

4309
                    // MSSQL requires parameters to carry extra type information
UNCOV
4310
                    if (this.connection.driver.options.type === "mssql") {
×
UNCOV
4311
                        parameterValue = (
×
4312
                            this.connection.driver as SqlServerDriver
4313
                        ).parametrizeValues(column, parameterValue)
4314
                    }
4315

UNCOV
4316
                    andConditions.push(
×
4317
                        this.createWhereConditionExpression(
4318
                            this.getWherePredicateCondition(
4319
                                aliasPath,
4320
                                parameterValue,
4321
                            ),
4322
                        ),
4323
                        // parameterValue.toSql(this.connection, aliasPath, parameters));
4324
                    )
4325

4326
                    // this.conditions.push(`${alias}.${propertyPath} = :${paramName}`);
4327
                    // this.expressionMap.parameters[paramName] = where[key]; // todo: handle functions and other edge cases
UNCOV
4328
                } else if (embed) {
×
UNCOV
4329
                    const condition = this.buildWhere(
×
4330
                        where[key],
4331
                        metadata,
4332
                        alias,
4333
                        propertyPath,
4334
                    )
UNCOV
4335
                    if (condition) andConditions.push(condition)
×
UNCOV
4336
                } else if (relation) {
×
UNCOV
4337
                    if (where[key] === null) {
×
UNCOV
4338
                        andConditions.push(`${alias}.${propertyPath} IS NULL`)
×
NEW
4339
                        continue
×
4340
                    }
4341

4342
                    // if all properties of where are undefined we don't need to join anything
4343
                    // this can happen when user defines map with conditional queries inside
UNCOV
4344
                    if (typeof where[key] === "object") {
×
UNCOV
4345
                        const allAllUndefined = Object.keys(where[key]).every(
×
UNCOV
4346
                            (k) => where[key][k] === undefined,
×
4347
                        )
UNCOV
4348
                        if (allAllUndefined) {
×
UNCOV
4349
                            continue
×
4350
                        }
4351
                    }
4352

UNCOV
4353
                    if (InstanceChecker.isFindOperator(where[key])) {
×
UNCOV
4354
                        if (
×
4355
                            where[key].type === "moreThan" ||
×
4356
                            where[key].type === "lessThan" ||
4357
                            where[key].type === "moreThanOrEqual" ||
4358
                            where[key].type === "lessThanOrEqual"
4359
                        ) {
UNCOV
4360
                            let sqlOperator = ""
×
UNCOV
4361
                            if (where[key].type === "moreThan") {
×
UNCOV
4362
                                sqlOperator = ">"
×
UNCOV
4363
                            } else if (where[key].type === "lessThan") {
×
UNCOV
4364
                                sqlOperator = "<"
×
UNCOV
4365
                            } else if (where[key].type === "moreThanOrEqual") {
×
UNCOV
4366
                                sqlOperator = ">="
×
UNCOV
4367
                            } else if (where[key].type === "lessThanOrEqual") {
×
UNCOV
4368
                                sqlOperator = "<="
×
4369
                            }
4370
                            // basically relation count functionality
UNCOV
4371
                            const qb: QueryBuilder<any> = this.subQuery()
×
UNCOV
4372
                            if (relation.isManyToManyOwner) {
×
UNCOV
4373
                                qb.select("COUNT(*)")
×
4374
                                    .from(
4375
                                        relation.joinTableName,
4376
                                        relation.joinTableName,
4377
                                    )
4378
                                    .where(
4379
                                        relation.joinColumns
4380
                                            .map((column) => {
UNCOV
4381
                                                return `${
×
4382
                                                    relation.joinTableName
4383
                                                }.${
4384
                                                    column.propertyName
4385
                                                } = ${alias}.${
4386
                                                    column.referencedColumn!
4387
                                                        .propertyName
4388
                                                }`
4389
                                            })
4390
                                            .join(" AND "),
4391
                                    )
UNCOV
4392
                            } else if (relation.isManyToManyNotOwner) {
×
UNCOV
4393
                                qb.select("COUNT(*)")
×
4394
                                    .from(
4395
                                        relation.inverseRelation!.joinTableName,
4396
                                        relation.inverseRelation!.joinTableName,
4397
                                    )
4398
                                    .where(
4399
                                        relation
4400
                                            .inverseRelation!.inverseJoinColumns.map(
4401
                                                (column) => {
UNCOV
4402
                                                    return `${
×
4403
                                                        relation.inverseRelation!
4404
                                                            .joinTableName
4405
                                                    }.${
4406
                                                        column.propertyName
4407
                                                    } = ${alias}.${
4408
                                                        column.referencedColumn!
4409
                                                            .propertyName
4410
                                                    }`
4411
                                                },
4412
                                            )
4413
                                            .join(" AND "),
4414
                                    )
UNCOV
4415
                            } else if (relation.isOneToMany) {
×
UNCOV
4416
                                qb.select("COUNT(*)")
×
4417
                                    .from(
4418
                                        relation.inverseEntityMetadata.target,
4419
                                        relation.inverseEntityMetadata
4420
                                            .tableName,
4421
                                    )
4422
                                    .where(
4423
                                        relation
4424
                                            .inverseRelation!.joinColumns.map(
4425
                                                (column) => {
UNCOV
4426
                                                    return `${
×
4427
                                                        relation
4428
                                                            .inverseEntityMetadata
4429
                                                            .tableName
4430
                                                    }.${
4431
                                                        column.propertyName
4432
                                                    } = ${alias}.${
4433
                                                        column.referencedColumn!
4434
                                                            .propertyName
4435
                                                    }`
4436
                                                },
4437
                                            )
4438
                                            .join(" AND "),
4439
                                    )
4440
                            } else {
UNCOV
4441
                                throw new Error(
×
4442
                                    `This relation isn't supported by given find operator`,
4443
                                )
4444
                            }
4445
                            // this
4446
                            //     .addSelect(qb.getSql(), relation.propertyAliasName + "_cnt")
4447
                            //     .andWhere(this.escape(relation.propertyAliasName + "_cnt") + " " + sqlOperator + " " + parseInt(where[key].value));
UNCOV
4448
                            this.andWhere(
×
4449
                                qb.getSql() +
4450
                                    " " +
4451
                                    sqlOperator +
4452
                                    " " +
4453
                                    parseInt(where[key].value),
4454
                            )
4455
                        } else {
UNCOV
4456
                            if (
×
4457
                                relation.isManyToOne ||
×
4458
                                (relation.isOneToOne &&
4459
                                    relation.isOneToOneOwner)
4460
                            ) {
UNCOV
4461
                                const aliasPath = `${alias}.${propertyPath}`
×
4462

UNCOV
4463
                                andConditions.push(
×
4464
                                    this.createWhereConditionExpression(
4465
                                        this.getWherePredicateCondition(
4466
                                            aliasPath,
4467
                                            where[key],
4468
                                        ),
4469
                                    ),
4470
                                )
4471
                            } else {
UNCOV
4472
                                throw new Error(
×
4473
                                    `This relation isn't supported by given find operator`,
4474
                                )
4475
                            }
4476
                        }
4477
                    } else {
4478
                        // const joinAlias = alias + "_" + relation.propertyName;
4479
                        let joinAlias =
UNCOV
4480
                            alias +
×
4481
                            "_" +
4482
                            relation.propertyPath.replace(".", "_")
UNCOV
4483
                        joinAlias = DriverUtils.buildAlias(
×
4484
                            this.connection.driver,
4485
                            { joiner: "__" },
4486
                            alias,
4487
                            joinAlias,
4488
                        )
4489

UNCOV
4490
                        const existJoin = this.joins.find(
×
UNCOV
4491
                            (join) => join.alias === joinAlias,
×
4492
                        )
UNCOV
4493
                        if (!existJoin) {
×
UNCOV
4494
                            this.joins.push({
×
4495
                                type: "left",
4496
                                select: false,
4497
                                selection: undefined,
4498
                                alias: joinAlias,
4499
                                parentAlias: alias,
4500
                                relationMetadata: relation,
4501
                            })
4502
                        }
4503

UNCOV
4504
                        const condition = this.buildWhere(
×
4505
                            where[key],
4506
                            relation.inverseEntityMetadata,
4507
                            joinAlias,
4508
                        )
UNCOV
4509
                        if (condition) {
×
UNCOV
4510
                            andConditions.push(condition)
×
4511
                            // parameterIndex = Object.keys(this.expressionMap.nativeParameters).length;
4512
                        }
4513
                    }
4514
                }
4515
            }
UNCOV
4516
            condition = andConditions.length
×
4517
                ? "(" + andConditions.join(") AND (") + ")"
4518
                : andConditions.join(" AND ")
4519
        }
UNCOV
4520
        return condition.length ? "(" + condition + ")" : condition
×
4521
    }
4522
}
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