• 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.51
/src/query-builder/QueryBuilder.ts
1
import { ObjectLiteral } from "../common/ObjectLiteral"
2
import { QueryRunner } from "../query-runner/QueryRunner"
3
import { DataSource } from "../data-source/DataSource"
4
import { QueryBuilderCteOptions } from "./QueryBuilderCte"
5
import { QueryExpressionMap } from "./QueryExpressionMap"
1✔
6
import { SelectQueryBuilder } from "./SelectQueryBuilder"
7
import { UpdateQueryBuilder } from "./UpdateQueryBuilder"
8
import { DeleteQueryBuilder } from "./DeleteQueryBuilder"
9
import { SoftDeleteQueryBuilder } from "./SoftDeleteQueryBuilder"
10
import { InsertQueryBuilder } from "./InsertQueryBuilder"
11
import { RelationQueryBuilder } from "./RelationQueryBuilder"
12
import { EntityTarget } from "../common/EntityTarget"
13
import { Alias } from "./Alias"
14
import { Brackets } from "./Brackets"
1✔
15
import { QueryDeepPartialEntity } from "./QueryPartialEntity"
16
import { EntityMetadata } from "../metadata/EntityMetadata"
17
import { ColumnMetadata } from "../metadata/ColumnMetadata"
18
import { FindOperator } from "../find-options/FindOperator"
1✔
19
import { In } from "../find-options/operator/In"
1✔
20
import { TypeORMError } from "../error"
1✔
21
import { WhereClause, WhereClauseCondition } from "./WhereClause"
22
import { NotBrackets } from "./NotBrackets"
23
import { EntityPropertyNotFoundError } from "../error/EntityPropertyNotFoundError"
1✔
24
import { ReturningType } from "../driver/Driver"
25
import { OracleDriver } from "../driver/oracle/OracleDriver"
26
import { InstanceChecker } from "../util/InstanceChecker"
1✔
27
import { escapeRegExp } from "../util/escapeRegExp"
1✔
28

29
// todo: completely cover query builder with tests
30
// todo: entityOrProperty can be target name. implement proper behaviour if it is.
31
// todo: check in persistment if id exist on object and throw exception (can be in partial selection?)
32
// todo: fix problem with long aliases eg getMaxIdentifierLength
33
// todo: fix replacing in .select("COUNT(post.id) AS cnt") statement
34
// todo: implement joinAlways in relations and relationId
35
// todo: finish partial selection
36
// todo: sugar methods like: .addCount and .selectCount, selectCountAndMap, selectSum, selectSumAndMap, ...
37
// todo: implement @Select decorator
38
// todo: add select and map functions
39

40
// todo: implement relation/entity loading and setting them into properties within a separate query
41
// .loadAndMap("post.categories", "post.categories", qb => ...)
42
// .loadAndMap("post.categories", Category, qb => ...)
43

44
/**
45
 * Allows to build complex sql queries in a fashion way and execute those queries.
46
 */
47
export abstract class QueryBuilder<Entity extends ObjectLiteral> {
1✔
UNCOV
48
    readonly "@instanceof" = Symbol.for("QueryBuilder")
×
49

50
    // -------------------------------------------------------------------------
51
    // Public Properties
52
    // -------------------------------------------------------------------------
53

54
    /**
55
     * Connection on which QueryBuilder was created.
56
     */
57
    readonly connection: DataSource
58

59
    /**
60
     * Contains all properties of the QueryBuilder that needs to be build a final query.
61
     */
62
    readonly expressionMap: QueryExpressionMap
63

64
    // -------------------------------------------------------------------------
65
    // Protected Properties
66
    // -------------------------------------------------------------------------
67

68
    /**
69
     * Query runner used to execute query builder query.
70
     */
71
    protected queryRunner?: QueryRunner
72

73
    /**
74
     * If QueryBuilder was created in a subquery mode then its parent QueryBuilder (who created subquery) will be stored here.
75
     */
76
    protected parentQueryBuilder: QueryBuilder<any>
77

78
    /**
79
     * Memo to help keep place of current parameter index for `createParameter`
80
     */
UNCOV
81
    private parameterIndex = 0
×
82

83
    /**
84
     * Contains all registered query builder classes.
85
     */
86
    private static queryBuilderRegistry: Record<string, any> = {}
1✔
87

88
    // -------------------------------------------------------------------------
89
    // Constructor
90
    // -------------------------------------------------------------------------
91

92
    /**
93
     * QueryBuilder can be initialized from given Connection and QueryRunner objects or from given other QueryBuilder.
94
     */
95
    constructor(queryBuilder: QueryBuilder<any>)
96

97
    /**
98
     * QueryBuilder can be initialized from given Connection and QueryRunner objects or from given other QueryBuilder.
99
     */
100
    constructor(connection: DataSource, queryRunner?: QueryRunner)
101

102
    /**
103
     * QueryBuilder can be initialized from given Connection and QueryRunner objects or from given other QueryBuilder.
104
     */
105
    constructor(
106
        connectionOrQueryBuilder: DataSource | QueryBuilder<any>,
107
        queryRunner?: QueryRunner,
108
    ) {
UNCOV
109
        if (InstanceChecker.isDataSource(connectionOrQueryBuilder)) {
×
UNCOV
110
            this.connection = connectionOrQueryBuilder
×
UNCOV
111
            this.queryRunner = queryRunner
×
UNCOV
112
            this.expressionMap = new QueryExpressionMap(this.connection)
×
113
        } else {
UNCOV
114
            this.connection = connectionOrQueryBuilder.connection
×
UNCOV
115
            this.queryRunner = connectionOrQueryBuilder.queryRunner
×
UNCOV
116
            this.expressionMap = connectionOrQueryBuilder.expressionMap.clone()
×
117
        }
118
    }
119

120
    static registerQueryBuilderClass(name: string, factory: any) {
121
        QueryBuilder.queryBuilderRegistry[name] = factory
258✔
122
    }
123

124
    // -------------------------------------------------------------------------
125
    // Abstract Methods
126
    // -------------------------------------------------------------------------
127

128
    /**
129
     * Gets generated SQL query without parameters being replaced.
130
     */
131
    abstract getQuery(): string
132

133
    // -------------------------------------------------------------------------
134
    // Accessors
135
    // -------------------------------------------------------------------------
136

137
    /**
138
     * Gets the main alias string used in this query builder.
139
     */
140
    get alias(): string {
UNCOV
141
        if (!this.expressionMap.mainAlias)
×
142
            throw new TypeORMError(`Main alias is not set`) // todo: better exception
×
143

UNCOV
144
        return this.expressionMap.mainAlias.name
×
145
    }
146

147
    // -------------------------------------------------------------------------
148
    // Public Methods
149
    // -------------------------------------------------------------------------
150

151
    /**
152
     * Creates SELECT query.
153
     * Replaces all previous selections if they exist.
154
     */
155
    select(): SelectQueryBuilder<Entity>
156

157
    /**
158
     * Creates SELECT query and selects given data.
159
     * Replaces all previous selections if they exist.
160
     */
161
    select(
162
        selection: string,
163
        selectionAliasName?: string,
164
    ): SelectQueryBuilder<Entity>
165

166
    /**
167
     * Creates SELECT query and selects given data.
168
     * Replaces all previous selections if they exist.
169
     */
170
    select(selection: string[]): SelectQueryBuilder<Entity>
171

172
    /**
173
     * Creates SELECT query and selects given data.
174
     * Replaces all previous selections if they exist.
175
     */
176
    select(
177
        selection?: string | string[],
178
        selectionAliasName?: string,
179
    ): SelectQueryBuilder<Entity> {
UNCOV
180
        this.expressionMap.queryType = "select"
×
UNCOV
181
        if (Array.isArray(selection)) {
×
182
            this.expressionMap.selects = selection.map((selection) => ({
×
183
                selection: selection,
184
            }))
UNCOV
185
        } else if (selection) {
×
UNCOV
186
            this.expressionMap.selects = [
×
187
                { selection: selection, aliasName: selectionAliasName },
188
            ]
189
        }
190

UNCOV
191
        if (InstanceChecker.isSelectQueryBuilder(this)) return this as any
×
192

UNCOV
193
        return QueryBuilder.queryBuilderRegistry["SelectQueryBuilder"](this)
×
194
    }
195

196
    /**
197
     * Creates INSERT query.
198
     */
199
    insert(): InsertQueryBuilder<Entity> {
UNCOV
200
        this.expressionMap.queryType = "insert"
×
201

UNCOV
202
        if (InstanceChecker.isInsertQueryBuilder(this)) return this as any
×
203

UNCOV
204
        return QueryBuilder.queryBuilderRegistry["InsertQueryBuilder"](this)
×
205
    }
206

207
    /**
208
     * Creates UPDATE query and applies given update values.
209
     */
210
    update(): UpdateQueryBuilder<Entity>
211

212
    /**
213
     * Creates UPDATE query and applies given update values.
214
     */
215
    update(
216
        updateSet: QueryDeepPartialEntity<Entity>,
217
    ): UpdateQueryBuilder<Entity>
218

219
    /**
220
     * Creates UPDATE query for the given entity and applies given update values.
221
     */
222
    update<Entity extends ObjectLiteral>(
223
        entity: EntityTarget<Entity>,
224
        updateSet?: QueryDeepPartialEntity<Entity>,
225
    ): UpdateQueryBuilder<Entity>
226

227
    /**
228
     * Creates UPDATE query for the given table name and applies given update values.
229
     */
230
    update(
231
        tableName: string,
232
        updateSet?: QueryDeepPartialEntity<Entity>,
233
    ): UpdateQueryBuilder<Entity>
234

235
    /**
236
     * Creates UPDATE query and applies given update values.
237
     */
238
    update(
239
        entityOrTableNameUpdateSet?: EntityTarget<any> | ObjectLiteral,
240
        maybeUpdateSet?: ObjectLiteral,
241
    ): UpdateQueryBuilder<any> {
UNCOV
242
        const updateSet = maybeUpdateSet
×
243
            ? maybeUpdateSet
244
            : (entityOrTableNameUpdateSet as ObjectLiteral | undefined)
UNCOV
245
        entityOrTableNameUpdateSet = InstanceChecker.isEntitySchema(
×
246
            entityOrTableNameUpdateSet,
247
        )
248
            ? entityOrTableNameUpdateSet.options.name
249
            : entityOrTableNameUpdateSet
250

UNCOV
251
        if (
×
252
            typeof entityOrTableNameUpdateSet === "function" ||
×
253
            typeof entityOrTableNameUpdateSet === "string"
254
        ) {
UNCOV
255
            const mainAlias = this.createFromAlias(entityOrTableNameUpdateSet)
×
UNCOV
256
            this.expressionMap.setMainAlias(mainAlias)
×
257
        }
258

UNCOV
259
        this.expressionMap.queryType = "update"
×
UNCOV
260
        this.expressionMap.valuesSet = updateSet
×
261

UNCOV
262
        if (InstanceChecker.isUpdateQueryBuilder(this)) return this as any
×
263

UNCOV
264
        return QueryBuilder.queryBuilderRegistry["UpdateQueryBuilder"](this)
×
265
    }
266

267
    /**
268
     * Creates DELETE query.
269
     */
270
    delete(): DeleteQueryBuilder<Entity> {
UNCOV
271
        this.expressionMap.queryType = "delete"
×
272

UNCOV
273
        if (InstanceChecker.isDeleteQueryBuilder(this)) return this as any
×
274

UNCOV
275
        return QueryBuilder.queryBuilderRegistry["DeleteQueryBuilder"](this)
×
276
    }
277

278
    softDelete(): SoftDeleteQueryBuilder<any> {
UNCOV
279
        this.expressionMap.queryType = "soft-delete"
×
280

UNCOV
281
        if (InstanceChecker.isSoftDeleteQueryBuilder(this)) return this as any
×
282

UNCOV
283
        return QueryBuilder.queryBuilderRegistry["SoftDeleteQueryBuilder"](this)
×
284
    }
285

286
    restore(): SoftDeleteQueryBuilder<any> {
UNCOV
287
        this.expressionMap.queryType = "restore"
×
288

UNCOV
289
        if (InstanceChecker.isSoftDeleteQueryBuilder(this)) return this as any
×
290

UNCOV
291
        return QueryBuilder.queryBuilderRegistry["SoftDeleteQueryBuilder"](this)
×
292
    }
293

294
    /**
295
     * Sets entity's relation with which this query builder gonna work.
296
     */
297
    relation(propertyPath: string): RelationQueryBuilder<Entity>
298

299
    /**
300
     * Sets entity's relation with which this query builder gonna work.
301
     */
302
    relation<T extends ObjectLiteral>(
303
        entityTarget: EntityTarget<T>,
304
        propertyPath: string,
305
    ): RelationQueryBuilder<T>
306

307
    /**
308
     * Sets entity's relation with which this query builder gonna work.
309
     */
310
    relation(
311
        entityTargetOrPropertyPath: Function | string,
312
        maybePropertyPath?: string,
313
    ): RelationQueryBuilder<Entity> {
314
        const entityTarget =
UNCOV
315
            arguments.length === 2 ? entityTargetOrPropertyPath : undefined
×
316
        const propertyPath =
UNCOV
317
            arguments.length === 2
×
318
                ? (maybePropertyPath as string)
319
                : (entityTargetOrPropertyPath as string)
320

UNCOV
321
        this.expressionMap.queryType = "relation"
×
UNCOV
322
        this.expressionMap.relationPropertyPath = propertyPath
×
323

UNCOV
324
        if (entityTarget) {
×
UNCOV
325
            const mainAlias = this.createFromAlias(entityTarget)
×
UNCOV
326
            this.expressionMap.setMainAlias(mainAlias)
×
327
        }
328

UNCOV
329
        if (InstanceChecker.isRelationQueryBuilder(this)) return this as any
×
330

UNCOV
331
        return QueryBuilder.queryBuilderRegistry["RelationQueryBuilder"](this)
×
332
    }
333

334
    /**
335
     * Checks if given relation exists in the entity.
336
     * Returns true if relation exists, false otherwise.
337
     *
338
     * todo: move this method to manager? or create a shortcut?
339
     */
340
    hasRelation<T>(target: EntityTarget<T>, relation: string): boolean
341

342
    /**
343
     * Checks if given relations exist in the entity.
344
     * Returns true if relation exists, false otherwise.
345
     *
346
     * todo: move this method to manager? or create a shortcut?
347
     */
348
    hasRelation<T>(target: EntityTarget<T>, relation: string[]): boolean
349

350
    /**
351
     * Checks if given relation or relations exist in the entity.
352
     * Returns true if relation exists, false otherwise.
353
     *
354
     * todo: move this method to manager? or create a shortcut?
355
     */
356
    hasRelation<T>(
357
        target: EntityTarget<T>,
358
        relation: string | string[],
359
    ): boolean {
360
        const entityMetadata = this.connection.getMetadata(target)
×
361
        const relations = Array.isArray(relation) ? relation : [relation]
×
362
        return relations.every((relation) => {
×
363
            return !!entityMetadata.findRelationWithPropertyPath(relation)
×
364
        })
365
    }
366

367
    /**
368
     * Check the existence of a parameter for this query builder.
369
     */
370
    hasParameter(key: string): boolean {
UNCOV
371
        return (
×
372
            this.parentQueryBuilder?.hasParameter(key) ||
×
373
            key in this.expressionMap.parameters
374
        )
375
    }
376

377
    /**
378
     * Sets parameter name and its value.
379
     *
380
     * The key for this parameter may contain numbers, letters, underscores, or periods.
381
     */
382
    setParameter(key: string, value: any): this {
UNCOV
383
        if (typeof value === "function") {
×
UNCOV
384
            throw new TypeORMError(
×
385
                `Function parameter isn't supported in the parameters. Please check "${key}" parameter.`,
386
            )
387
        }
388

UNCOV
389
        if (!key.match(/^([A-Za-z0-9_.]+)$/)) {
×
UNCOV
390
            throw new TypeORMError(
×
391
                "QueryBuilder parameter keys may only contain numbers, letters, underscores, or periods.",
392
            )
393
        }
394

UNCOV
395
        if (this.parentQueryBuilder) {
×
UNCOV
396
            this.parentQueryBuilder.setParameter(key, value)
×
397
        }
398

UNCOV
399
        this.expressionMap.parameters[key] = value
×
UNCOV
400
        return this
×
401
    }
402

403
    /**
404
     * Adds all parameters from the given object.
405
     */
406
    setParameters(parameters: ObjectLiteral): this {
UNCOV
407
        for (const [key, value] of Object.entries(parameters)) {
×
UNCOV
408
            this.setParameter(key, value)
×
409
        }
410

UNCOV
411
        return this
×
412
    }
413

414
    protected createParameter(value: any): string {
415
        let parameterName
416

UNCOV
417
        do {
×
UNCOV
418
            parameterName = `orm_param_${this.parameterIndex++}`
×
419
        } while (this.hasParameter(parameterName))
420

UNCOV
421
        this.setParameter(parameterName, value)
×
422

UNCOV
423
        return `:${parameterName}`
×
424
    }
425

426
    /**
427
     * Adds native parameters from the given object.
428
     *
429
     * @deprecated Use `setParameters` instead
430
     */
431
    setNativeParameters(parameters: ObjectLiteral): this {
432
        // set parent query builder parameters as well in sub-query mode
UNCOV
433
        if (this.parentQueryBuilder) {
×
434
            this.parentQueryBuilder.setNativeParameters(parameters)
×
435
        }
436

UNCOV
437
        Object.keys(parameters).forEach((key) => {
×
438
            this.expressionMap.nativeParameters[key] = parameters[key]
×
439
        })
UNCOV
440
        return this
×
441
    }
442

443
    /**
444
     * Gets all parameters.
445
     */
446
    getParameters(): ObjectLiteral {
UNCOV
447
        const parameters: ObjectLiteral = Object.assign(
×
448
            {},
449
            this.expressionMap.parameters,
450
        )
451

452
        // add discriminator column parameter if it exist
UNCOV
453
        if (
×
454
            this.expressionMap.mainAlias &&
×
455
            this.expressionMap.mainAlias.hasMetadata
456
        ) {
UNCOV
457
            const metadata = this.expressionMap.mainAlias!.metadata
×
UNCOV
458
            if (metadata.discriminatorColumn && metadata.parentEntityMetadata) {
×
UNCOV
459
                const values = metadata.childEntityMetadatas
×
460
                    .filter(
UNCOV
461
                        (childMetadata) => childMetadata.discriminatorColumn,
×
462
                    )
UNCOV
463
                    .map((childMetadata) => childMetadata.discriminatorValue)
×
UNCOV
464
                values.push(metadata.discriminatorValue)
×
UNCOV
465
                parameters["discriminatorColumnValues"] = values
×
466
            }
467
        }
468

UNCOV
469
        return parameters
×
470
    }
471

472
    /**
473
     * Prints sql to stdout using console.log.
474
     */
475
    printSql(): this {
476
        // TODO rename to logSql()
477
        const [query, parameters] = this.getQueryAndParameters()
×
478
        this.connection.logger.logQuery(query, parameters)
×
479
        return this
×
480
    }
481

482
    /**
483
     * Gets generated sql that will be executed.
484
     * Parameters in the query are escaped for the currently used driver.
485
     */
486
    getSql(): string {
UNCOV
487
        return this.getQueryAndParameters()[0]
×
488
    }
489

490
    /**
491
     * Gets query to be executed with all parameters used in it.
492
     */
493
    getQueryAndParameters(): [string, any[]] {
494
        // this execution order is important because getQuery method generates this.expressionMap.nativeParameters values
UNCOV
495
        const query = this.getQuery()
×
UNCOV
496
        const parameters = this.getParameters()
×
UNCOV
497
        return this.connection.driver.escapeQueryWithParameters(
×
498
            query,
499
            parameters,
500
            this.expressionMap.nativeParameters,
501
        )
502
    }
503

504
    /**
505
     * Executes sql generated by query builder and returns raw database results.
506
     */
507
    async execute(): Promise<any> {
UNCOV
508
        const [sql, parameters] = this.getQueryAndParameters()
×
UNCOV
509
        const queryRunner = this.obtainQueryRunner()
×
UNCOV
510
        try {
×
UNCOV
511
            return await queryRunner.query(sql, parameters) // await is needed here because we are using finally
×
512
        } finally {
UNCOV
513
            if (queryRunner !== this.queryRunner) {
×
514
                // means we created our own query runner
UNCOV
515
                await queryRunner.release()
×
516
            }
517
        }
518
    }
519

520
    /**
521
     * Creates a completely new query builder.
522
     * Uses same query runner as current QueryBuilder.
523
     */
524
    createQueryBuilder(queryRunner?: QueryRunner): this {
UNCOV
525
        return new (this.constructor as any)(
×
526
            this.connection,
527
            queryRunner ?? this.queryRunner,
×
528
        )
529
    }
530

531
    /**
532
     * Clones query builder as it is.
533
     * Note: it uses new query runner, if you want query builder that uses exactly same query runner,
534
     * you can create query builder using its constructor, for example new SelectQueryBuilder(queryBuilder)
535
     * where queryBuilder is cloned QueryBuilder.
536
     */
537
    clone(): this {
UNCOV
538
        return new (this.constructor as any)(this)
×
539
    }
540

541
    /**
542
     * Includes a Query comment in the query builder.  This is helpful for debugging purposes,
543
     * such as finding a specific query in the database server's logs, or for categorization using
544
     * an APM product.
545
     */
546
    comment(comment: string): this {
UNCOV
547
        this.expressionMap.comment = comment
×
UNCOV
548
        return this
×
549
    }
550

551
    /**
552
     * Disables escaping.
553
     */
554
    disableEscaping(): this {
UNCOV
555
        this.expressionMap.disableEscaping = false
×
UNCOV
556
        return this
×
557
    }
558

559
    /**
560
     * Escapes table name, column name or alias name using current database's escaping character.
561
     */
562
    escape(name: string): string {
UNCOV
563
        if (!this.expressionMap.disableEscaping) return name
×
UNCOV
564
        return this.connection.driver.escape(name)
×
565
    }
566

567
    /**
568
     * Sets or overrides query builder's QueryRunner.
569
     */
570
    setQueryRunner(queryRunner: QueryRunner): this {
UNCOV
571
        this.queryRunner = queryRunner
×
UNCOV
572
        return this
×
573
    }
574

575
    /**
576
     * Indicates if listeners and subscribers must be called before and after query execution.
577
     * Enabled by default.
578
     */
579
    callListeners(enabled: boolean): this {
UNCOV
580
        this.expressionMap.callListeners = enabled
×
UNCOV
581
        return this
×
582
    }
583

584
    /**
585
     * If set to true the query will be wrapped into a transaction.
586
     */
587
    useTransaction(enabled: boolean): this {
UNCOV
588
        this.expressionMap.useTransaction = enabled
×
UNCOV
589
        return this
×
590
    }
591

592
    /**
593
     * Adds CTE to query
594
     */
595
    addCommonTableExpression(
596
        queryBuilder: QueryBuilder<any> | string,
597
        alias: string,
598
        options?: QueryBuilderCteOptions,
599
    ): this {
UNCOV
600
        this.expressionMap.commonTableExpressions.push({
×
601
            queryBuilder,
602
            alias,
603
            options: options || {},
×
604
        })
UNCOV
605
        return this
×
606
    }
607

608
    // -------------------------------------------------------------------------
609
    // Protected Methods
610
    // -------------------------------------------------------------------------
611

612
    /**
613
     * Gets escaped table name with schema name if SqlServer driver used with custom
614
     * schema name, otherwise returns escaped table name.
615
     */
616
    protected getTableName(tablePath: string): string {
UNCOV
617
        return tablePath
×
618
            .split(".")
619
            .map((i) => {
620
                // this condition need because in SQL Server driver when custom database name was specified and schema name was not, we got `dbName..tableName` string, and doesn't need to escape middle empty string
UNCOV
621
                if (i === "") return i
×
UNCOV
622
                return this.escape(i)
×
623
            })
624
            .join(".")
625
    }
626

627
    /**
628
     * Gets name of the table where insert should be performed.
629
     */
630
    protected getMainTableName(): string {
UNCOV
631
        if (!this.expressionMap.mainAlias)
×
632
            throw new TypeORMError(
×
633
                `Entity where values should be inserted is not specified. Call "qb.into(entity)" method to specify it.`,
634
            )
635

UNCOV
636
        if (this.expressionMap.mainAlias.hasMetadata)
×
UNCOV
637
            return this.expressionMap.mainAlias.metadata.tablePath
×
638

UNCOV
639
        return this.expressionMap.mainAlias.tablePath!
×
640
    }
641

642
    /**
643
     * Specifies FROM which entity's table select/update/delete will be executed.
644
     * Also sets a main string alias of the selection data.
645
     */
646
    protected createFromAlias(
647
        entityTarget:
648
            | EntityTarget<any>
649
            | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>),
650
        aliasName?: string,
651
    ): Alias {
652
        // if table has a metadata then find it to properly escape its properties
653
        // const metadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === tableName);
UNCOV
654
        if (this.connection.hasMetadata(entityTarget)) {
×
UNCOV
655
            const metadata = this.connection.getMetadata(entityTarget)
×
656

UNCOV
657
            return this.expressionMap.createAlias({
×
658
                type: "from",
659
                name: aliasName,
660
                metadata: this.connection.getMetadata(entityTarget),
661
                tablePath: metadata.tablePath,
662
            })
663
        } else {
UNCOV
664
            if (typeof entityTarget === "string") {
×
665
                const isSubquery =
UNCOV
666
                    entityTarget.substr(0, 1) === "(" &&
×
667
                    entityTarget.substr(-1) === ")"
668

UNCOV
669
                return this.expressionMap.createAlias({
×
670
                    type: "from",
671
                    name: aliasName,
672
                    tablePath: !isSubquery
×
673
                        ? (entityTarget as string)
674
                        : undefined,
675
                    subQuery: isSubquery ? entityTarget : undefined,
×
676
                })
677
            }
678

679
            const subQueryBuilder: SelectQueryBuilder<any> = (
UNCOV
680
                entityTarget as any
×
681
            )((this as any as SelectQueryBuilder<any>).subQuery())
UNCOV
682
            this.setParameters(subQueryBuilder.getParameters())
×
UNCOV
683
            const subquery = subQueryBuilder.getQuery()
×
684

UNCOV
685
            return this.expressionMap.createAlias({
×
686
                type: "from",
687
                name: aliasName,
688
                subQuery: subquery,
689
            })
690
        }
691
    }
692

693
    /**
694
     * @deprecated this way of replace property names is too slow.
695
     *  Instead, we'll replace property names at the end - once query is build.
696
     */
697
    protected replacePropertyNames(statement: string) {
UNCOV
698
        return statement
×
699
    }
700

701
    /**
702
     * Replaces all entity's propertyName to name in the given SQL string.
703
     */
704
    protected replacePropertyNamesForTheWholeQuery(statement: string) {
UNCOV
705
        const replacements: { [key: string]: { [key: string]: string } } = {}
×
706

UNCOV
707
        for (const alias of this.expressionMap.aliases) {
×
UNCOV
708
            if (!alias.hasMetadata) continue
×
709
            const replaceAliasNamePrefix =
UNCOV
710
                this.expressionMap.aliasNamePrefixingEnabled && alias.name
×
711
                    ? `${alias.name}.`
712
                    : ""
713

UNCOV
714
            if (!replacements[replaceAliasNamePrefix]) {
×
UNCOV
715
                replacements[replaceAliasNamePrefix] = {}
×
716
            }
717

718
            // Insert & overwrite the replacements from least to most relevant in our replacements object.
719
            // To do this we iterate and overwrite in the order of relevance.
720
            // Least to Most Relevant:
721
            // * Relation Property Path to first join column key
722
            // * Relation Property Path + Column Path
723
            // * Column Database Name
724
            // * Column Property Name
725
            // * Column Property Path
726

UNCOV
727
            for (const relation of alias.metadata.relations) {
×
UNCOV
728
                if (relation.joinColumns.length > 0)
×
UNCOV
729
                    replacements[replaceAliasNamePrefix][
×
730
                        relation.propertyPath
731
                    ] = relation.joinColumns[0].databaseName
732
            }
733

UNCOV
734
            for (const relation of alias.metadata.relations) {
×
UNCOV
735
                const allColumns = [
×
736
                    ...relation.joinColumns,
737
                    ...relation.inverseJoinColumns,
738
                ]
UNCOV
739
                for (const joinColumn of allColumns) {
×
UNCOV
740
                    const propertyKey = `${relation.propertyPath}.${
×
741
                        joinColumn.referencedColumn!.propertyPath
742
                    }`
UNCOV
743
                    replacements[replaceAliasNamePrefix][propertyKey] =
×
744
                        joinColumn.databaseName
745
                }
746
            }
747

UNCOV
748
            for (const column of alias.metadata.columns) {
×
UNCOV
749
                replacements[replaceAliasNamePrefix][column.databaseName] =
×
750
                    column.databaseName
751
            }
752

UNCOV
753
            for (const column of alias.metadata.columns) {
×
UNCOV
754
                replacements[replaceAliasNamePrefix][column.propertyName] =
×
755
                    column.databaseName
756
            }
757

UNCOV
758
            for (const column of alias.metadata.columns) {
×
UNCOV
759
                replacements[replaceAliasNamePrefix][column.propertyPath] =
×
760
                    column.databaseName
761
            }
762
        }
763

UNCOV
764
        const replacementKeys = Object.keys(replacements)
×
UNCOV
765
        const replaceAliasNamePrefixes = replacementKeys
×
UNCOV
766
            .map((key) => escapeRegExp(key))
×
767
            .join("|")
768

UNCOV
769
        if (replacementKeys.length > 0) {
×
UNCOV
770
            statement = statement.replace(
×
771
                new RegExp(
772
                    // Avoid a lookbehind here since it's not well supported
773
                    `([ =(]|^.{0})` + // any of ' =(' or start of line
774
                        // followed by our prefix, e.g. 'tablename.' or ''
775
                        `${
776
                            replaceAliasNamePrefixes
×
777
                                ? "(" + replaceAliasNamePrefixes + ")"
778
                                : ""
779
                        }([^ =(),]+)` + // a possible property name: sequence of anything but ' =(),'
780
                        // terminated by ' =),' or end of line
781
                        `(?=[ =),]|.{0}$)`,
782
                    "gm",
783
                ),
784
                (...matches) => {
785
                    let match: string, pre: string, p: string
UNCOV
786
                    if (replaceAliasNamePrefixes) {
×
UNCOV
787
                        match = matches[0]
×
UNCOV
788
                        pre = matches[1]
×
UNCOV
789
                        p = matches[3]
×
790

UNCOV
791
                        if (replacements[matches[2]][p]) {
×
UNCOV
792
                            return `${pre}${this.escape(
×
793
                                matches[2].substring(0, matches[2].length - 1),
794
                            )}.${this.escape(replacements[matches[2]][p])}`
795
                        }
796
                    } else {
UNCOV
797
                        match = matches[0]
×
UNCOV
798
                        pre = matches[1]
×
UNCOV
799
                        p = matches[2]
×
800

UNCOV
801
                        if (replacements[""][p]) {
×
UNCOV
802
                            return `${pre}${this.escape(replacements[""][p])}`
×
803
                        }
804
                    }
UNCOV
805
                    return match
×
806
                },
807
            )
808
        }
809

UNCOV
810
        return statement
×
811
    }
812

813
    protected createComment(): string {
UNCOV
814
        if (!this.expressionMap.comment) {
×
UNCOV
815
            return ""
×
816
        }
817

818
        // ANSI SQL 2003 support C style comments - comments that start with `/*` and end with `*/`
819
        // In some dialects query nesting is available - but not all.  Because of this, we'll need
820
        // to scrub "ending" characters from the SQL but otherwise we can leave everything else
821
        // as-is and it should be valid.
822

UNCOV
823
        return `/* ${this.expressionMap.comment.replace(/\*\//g, "")} */ `
×
824
    }
825

826
    /**
827
     * Time travel queries for CockroachDB
828
     */
829
    protected createTimeTravelQuery(): string {
UNCOV
830
        if (
×
831
            this.expressionMap.queryType === "select" &&
×
832
            this.expressionMap.timeTravel
833
        ) {
UNCOV
834
            return ` AS OF SYSTEM TIME ${this.expressionMap.timeTravel}`
×
835
        }
836

UNCOV
837
        return ""
×
838
    }
839

840
    /**
841
     * Creates "WHERE" expression.
842
     */
843
    protected createWhereExpression() {
UNCOV
844
        const conditionsArray = []
×
845

UNCOV
846
        const whereExpression = this.createWhereClausesExpression(
×
847
            this.expressionMap.wheres,
848
        )
849

UNCOV
850
        if (whereExpression.length > 0 && whereExpression !== "1=1") {
×
UNCOV
851
            conditionsArray.push(this.replacePropertyNames(whereExpression))
×
852
        }
853

UNCOV
854
        if (this.expressionMap.mainAlias!.hasMetadata) {
×
UNCOV
855
            const metadata = this.expressionMap.mainAlias!.metadata
×
856
            // Adds the global condition of "non-deleted" for the entity with delete date columns in select query.
UNCOV
857
            if (
×
858
                this.expressionMap.queryType === "select" &&
×
859
                !this.expressionMap.withDeleted &&
860
                metadata.deleteDateColumn
861
            ) {
UNCOV
862
                const column = this.expressionMap.aliasNamePrefixingEnabled
×
863
                    ? this.expressionMap.mainAlias!.name +
864
                      "." +
865
                      metadata.deleteDateColumn.propertyName
866
                    : metadata.deleteDateColumn.propertyName
867

UNCOV
868
                const condition = `${this.replacePropertyNames(column)} IS NULL`
×
UNCOV
869
                conditionsArray.push(condition)
×
870
            }
871

UNCOV
872
            if (metadata.discriminatorColumn && metadata.parentEntityMetadata) {
×
UNCOV
873
                const column = this.expressionMap.aliasNamePrefixingEnabled
×
874
                    ? this.expressionMap.mainAlias!.name +
875
                      "." +
876
                      metadata.discriminatorColumn.databaseName
877
                    : metadata.discriminatorColumn.databaseName
878

UNCOV
879
                const condition = `${this.replacePropertyNames(
×
880
                    column,
881
                )} IN (:...discriminatorColumnValues)`
UNCOV
882
                conditionsArray.push(condition)
×
883
            }
884
        }
885

UNCOV
886
        if (this.expressionMap.extraAppendedAndWhereCondition) {
×
UNCOV
887
            const condition = this.replacePropertyNames(
×
888
                this.expressionMap.extraAppendedAndWhereCondition,
889
            )
UNCOV
890
            conditionsArray.push(condition)
×
891
        }
892

UNCOV
893
        let condition = ""
×
894

895
        // time travel
UNCOV
896
        condition += this.createTimeTravelQuery()
×
897

UNCOV
898
        if (!conditionsArray.length) {
×
UNCOV
899
            condition += ""
×
UNCOV
900
        } else if (conditionsArray.length === 1) {
×
UNCOV
901
            condition += ` WHERE ${conditionsArray[0]}`
×
902
        } else {
UNCOV
903
            condition += ` WHERE ( ${conditionsArray.join(" ) AND ( ")} )`
×
904
        }
905

UNCOV
906
        return condition
×
907
    }
908

909
    /**
910
     * Creates "RETURNING" / "OUTPUT" expression.
911
     */
912
    protected createReturningExpression(returningType: ReturningType): string {
UNCOV
913
        const columns = this.getReturningColumns()
×
UNCOV
914
        const driver = this.connection.driver
×
915

916
        // also add columns we must auto-return to perform entity updation
917
        // if user gave his own returning
UNCOV
918
        if (
×
919
            typeof this.expressionMap.returning !== "string" &&
×
920
            this.expressionMap.extraReturningColumns.length > 0 &&
921
            driver.isReturningSqlSupported(returningType)
922
        ) {
UNCOV
923
            columns.push(
×
924
                ...this.expressionMap.extraReturningColumns.filter((column) => {
UNCOV
925
                    return columns.indexOf(column) === -1
×
926
                }),
927
            )
928
        }
929

UNCOV
930
        if (columns.length) {
×
UNCOV
931
            let columnsExpression = columns
×
932
                .map((column) => {
UNCOV
933
                    const name = this.escape(column.databaseName)
×
UNCOV
934
                    if (driver.options.type === "mssql") {
×
UNCOV
935
                        if (
×
936
                            this.expressionMap.queryType === "insert" ||
×
937
                            this.expressionMap.queryType === "update" ||
938
                            this.expressionMap.queryType === "soft-delete" ||
939
                            this.expressionMap.queryType === "restore"
940
                        ) {
UNCOV
941
                            return "INSERTED." + name
×
942
                        } else {
943
                            return (
×
944
                                this.escape(this.getMainTableName()) +
945
                                "." +
946
                                name
947
                            )
948
                        }
949
                    } else {
UNCOV
950
                        return name
×
951
                    }
952
                })
953
                .join(", ")
954

UNCOV
955
            if (driver.options.type === "oracle") {
×
UNCOV
956
                columnsExpression +=
×
957
                    " INTO " +
958
                    columns
959
                        .map((column) => {
UNCOV
960
                            return this.createParameter({
×
961
                                type: (
962
                                    driver as OracleDriver
963
                                ).columnTypeToNativeParameter(column.type),
964
                                dir: (driver as OracleDriver).oracle.BIND_OUT,
965
                            })
966
                        })
967
                        .join(", ")
968
            }
969

UNCOV
970
            if (driver.options.type === "mssql") {
×
UNCOV
971
                if (
×
972
                    this.expressionMap.queryType === "insert" ||
×
973
                    this.expressionMap.queryType === "update"
974
                ) {
UNCOV
975
                    columnsExpression += " INTO @OutputTable"
×
976
                }
977
            }
978

UNCOV
979
            return columnsExpression
×
UNCOV
980
        } else if (typeof this.expressionMap.returning === "string") {
×
UNCOV
981
            return this.expressionMap.returning
×
982
        }
983

UNCOV
984
        return ""
×
985
    }
986

987
    /**
988
     * If returning / output cause is set to array of column names,
989
     * then this method will return all column metadatas of those column names.
990
     */
991
    protected getReturningColumns(): ColumnMetadata[] {
UNCOV
992
        const columns: ColumnMetadata[] = []
×
UNCOV
993
        if (Array.isArray(this.expressionMap.returning)) {
×
UNCOV
994
            ;(this.expressionMap.returning as string[]).forEach(
×
995
                (columnName) => {
UNCOV
996
                    if (this.expressionMap.mainAlias!.hasMetadata) {
×
UNCOV
997
                        columns.push(
×
998
                            ...this.expressionMap.mainAlias!.metadata.findColumnsWithPropertyPath(
999
                                columnName,
1000
                            ),
1001
                        )
1002
                    }
1003
                },
1004
            )
1005
        }
UNCOV
1006
        return columns
×
1007
    }
1008

1009
    protected createWhereClausesExpression(clauses: WhereClause[]): string {
UNCOV
1010
        return clauses
×
1011
            .map((clause, index) => {
UNCOV
1012
                const expression = this.createWhereConditionExpression(
×
1013
                    clause.condition,
1014
                )
1015

UNCOV
1016
                switch (clause.type) {
×
1017
                    case "and":
UNCOV
1018
                        return (
×
1019
                            (index > 0 ? "AND " : "") +
×
1020
                            `${
1021
                                this.connection.options.isolateWhereStatements
×
1022
                                    ? "("
1023
                                    : ""
1024
                            }${expression}${
1025
                                this.connection.options.isolateWhereStatements
×
1026
                                    ? ")"
1027
                                    : ""
1028
                            }`
1029
                        )
1030
                    case "or":
UNCOV
1031
                        return (
×
1032
                            (index > 0 ? "OR " : "") +
×
1033
                            `${
1034
                                this.connection.options.isolateWhereStatements
×
1035
                                    ? "("
1036
                                    : ""
1037
                            }${expression}${
1038
                                this.connection.options.isolateWhereStatements
×
1039
                                    ? ")"
1040
                                    : ""
1041
                            }`
1042
                        )
1043
                }
1044

UNCOV
1045
                return expression
×
1046
            })
1047
            .join(" ")
1048
            .trim()
1049
    }
1050

1051
    /**
1052
     * Computes given where argument - transforms to a where string all forms it can take.
1053
     */
1054
    protected createWhereConditionExpression(
1055
        condition: WhereClauseCondition,
1056
        alwaysWrap: boolean = false,
×
1057
    ): string {
UNCOV
1058
        if (typeof condition === "string") return condition
×
1059

UNCOV
1060
        if (Array.isArray(condition)) {
×
UNCOV
1061
            if (condition.length === 0) {
×
1062
                return "1=1"
×
1063
            }
1064

1065
            // In the future we should probably remove this entire condition
1066
            // but for now to prevent any breaking changes it exists.
UNCOV
1067
            if (condition.length === 1 && !alwaysWrap) {
×
UNCOV
1068
                return this.createWhereClausesExpression(condition)
×
1069
            }
1070

UNCOV
1071
            return "(" + this.createWhereClausesExpression(condition) + ")"
×
1072
        }
1073

UNCOV
1074
        const { driver } = this.connection
×
1075

UNCOV
1076
        switch (condition.operator) {
×
1077
            case "lessThan":
UNCOV
1078
                return `${condition.parameters[0]} < ${condition.parameters[1]}`
×
1079
            case "lessThanOrEqual":
UNCOV
1080
                return `${condition.parameters[0]} <= ${condition.parameters[1]}`
×
1081
            case "arrayContains":
UNCOV
1082
                return `${condition.parameters[0]} @> ${condition.parameters[1]}`
×
1083
            case "jsonContains":
UNCOV
1084
                return `${condition.parameters[0]} ::jsonb @> ${condition.parameters[1]}`
×
1085
            case "arrayContainedBy":
UNCOV
1086
                return `${condition.parameters[0]} <@ ${condition.parameters[1]}`
×
1087
            case "arrayOverlap":
UNCOV
1088
                return `${condition.parameters[0]} && ${condition.parameters[1]}`
×
1089
            case "moreThan":
UNCOV
1090
                return `${condition.parameters[0]} > ${condition.parameters[1]}`
×
1091
            case "moreThanOrEqual":
UNCOV
1092
                return `${condition.parameters[0]} >= ${condition.parameters[1]}`
×
1093
            case "notEqual":
UNCOV
1094
                return `${condition.parameters[0]} != ${condition.parameters[1]}`
×
1095
            case "equal":
UNCOV
1096
                return `${condition.parameters[0]} = ${condition.parameters[1]}`
×
1097
            case "ilike":
UNCOV
1098
                if (
×
1099
                    driver.options.type === "postgres" ||
×
1100
                    driver.options.type === "cockroachdb"
1101
                ) {
UNCOV
1102
                    return `${condition.parameters[0]} ILIKE ${condition.parameters[1]}`
×
1103
                }
1104

UNCOV
1105
                return `UPPER(${condition.parameters[0]}) LIKE UPPER(${condition.parameters[1]})`
×
1106
            case "like":
UNCOV
1107
                return `${condition.parameters[0]} LIKE ${condition.parameters[1]}`
×
1108
            case "between":
UNCOV
1109
                return `${condition.parameters[0]} BETWEEN ${condition.parameters[1]} AND ${condition.parameters[2]}`
×
1110
            case "in":
UNCOV
1111
                if (condition.parameters.length <= 1) {
×
UNCOV
1112
                    return "0=1"
×
1113
                }
UNCOV
1114
                return `${condition.parameters[0]} IN (${condition.parameters
×
1115
                    .slice(1)
1116
                    .join(", ")})`
1117
            case "any":
UNCOV
1118
                if (driver.options.type === "cockroachdb") {
×
1119
                    return `${condition.parameters[0]}::STRING = ANY(${condition.parameters[1]}::STRING[])`
×
1120
                }
1121

UNCOV
1122
                return `${condition.parameters[0]} = ANY(${condition.parameters[1]})`
×
1123
            case "isNull":
UNCOV
1124
                return `${condition.parameters[0]} IS NULL`
×
1125

1126
            case "not":
UNCOV
1127
                return `NOT(${this.createWhereConditionExpression(
×
1128
                    condition.condition,
1129
                )})`
1130
            case "brackets":
UNCOV
1131
                return `${this.createWhereConditionExpression(
×
1132
                    condition.condition,
1133
                    true,
1134
                )}`
1135
            case "and":
UNCOV
1136
                return "(" + condition.parameters.join(" AND ") + ")"
×
1137
            case "or":
UNCOV
1138
                return "(" + condition.parameters.join(" OR ") + ")"
×
1139
        }
1140

1141
        throw new TypeError(
×
1142
            `Unsupported FindOperator ${FindOperator.constructor.name}`,
1143
        )
1144
    }
1145

1146
    protected createCteExpression(): string {
UNCOV
1147
        if (!this.hasCommonTableExpressions()) {
×
UNCOV
1148
            return ""
×
1149
        }
1150
        const databaseRequireRecusiveHint =
UNCOV
1151
            this.connection.driver.cteCapabilities.requiresRecursiveHint
×
1152

UNCOV
1153
        const cteStrings = this.expressionMap.commonTableExpressions.map(
×
1154
            (cte) => {
1155
                let cteBodyExpression =
UNCOV
1156
                    typeof cte.queryBuilder === "string" ? cte.queryBuilder : ""
×
UNCOV
1157
                if (typeof cte.queryBuilder !== "string") {
×
UNCOV
1158
                    if (cte.queryBuilder.hasCommonTableExpressions()) {
×
1159
                        throw new TypeORMError(
×
1160
                            `Nested CTEs aren't supported (CTE: ${cte.alias})`,
1161
                        )
1162
                    }
UNCOV
1163
                    cteBodyExpression = cte.queryBuilder.getQuery()
×
UNCOV
1164
                    if (
×
1165
                        !this.connection.driver.cteCapabilities.writable &&
×
1166
                        !InstanceChecker.isSelectQueryBuilder(cte.queryBuilder)
1167
                    ) {
1168
                        throw new TypeORMError(
×
1169
                            `Only select queries are supported in CTEs in ${this.connection.options.type} (CTE: ${cte.alias})`,
1170
                        )
1171
                    }
UNCOV
1172
                    this.setParameters(cte.queryBuilder.getParameters())
×
1173
                }
UNCOV
1174
                let cteHeader = this.escape(cte.alias)
×
UNCOV
1175
                if (cte.options.columnNames) {
×
UNCOV
1176
                    const escapedColumnNames = cte.options.columnNames.map(
×
UNCOV
1177
                        (column) => this.escape(column),
×
1178
                    )
UNCOV
1179
                    if (
×
1180
                        InstanceChecker.isSelectQueryBuilder(cte.queryBuilder)
1181
                    ) {
UNCOV
1182
                        if (
×
1183
                            cte.queryBuilder.expressionMap.selects.length &&
×
1184
                            cte.options.columnNames.length !==
1185
                                cte.queryBuilder.expressionMap.selects.length
1186
                        ) {
1187
                            throw new TypeORMError(
×
1188
                                `cte.options.columnNames length (${cte.options.columnNames.length}) doesn't match subquery select list length ${cte.queryBuilder.expressionMap.selects.length} (CTE: ${cte.alias})`,
1189
                            )
1190
                        }
1191
                    }
UNCOV
1192
                    cteHeader += `(${escapedColumnNames.join(", ")})`
×
1193
                }
1194
                const recursiveClause =
UNCOV
1195
                    cte.options.recursive && databaseRequireRecusiveHint
×
1196
                        ? "RECURSIVE"
1197
                        : ""
UNCOV
1198
                let materializeClause = ""
×
UNCOV
1199
                if (
×
1200
                    this.connection.driver.cteCapabilities.materializedHint &&
×
1201
                    cte.options.materialized !== undefined
1202
                ) {
UNCOV
1203
                    materializeClause = cte.options.materialized
×
1204
                        ? "MATERIALIZED"
1205
                        : "NOT MATERIALIZED"
1206
                }
1207

UNCOV
1208
                return [
×
1209
                    recursiveClause,
1210
                    cteHeader,
1211
                    "AS",
1212
                    materializeClause,
1213
                    `(${cteBodyExpression})`,
1214
                ]
1215
                    .filter(Boolean)
1216
                    .join(" ")
1217
            },
1218
        )
1219

UNCOV
1220
        return "WITH " + cteStrings.join(", ") + " "
×
1221
    }
1222

1223
    /**
1224
     * Creates "WHERE" condition for an in-ids condition.
1225
     */
1226
    protected getWhereInIdsCondition(
1227
        ids: any | any[],
1228
    ): ObjectLiteral | Brackets {
UNCOV
1229
        const metadata = this.expressionMap.mainAlias!.metadata
×
UNCOV
1230
        const normalized = (Array.isArray(ids) ? ids : [ids]).map((id) =>
×
UNCOV
1231
            metadata.ensureEntityIdMap(id),
×
1232
        )
1233

1234
        // using in(...ids) for single primary key entities
UNCOV
1235
        if (!metadata.hasMultiplePrimaryKeys) {
×
UNCOV
1236
            const primaryColumn = metadata.primaryColumns[0]
×
1237

1238
            // getEntityValue will try to transform `In`, it is a bug
1239
            // todo: remove this transformer check after #2390 is fixed
1240
            // This also fails for embedded & relation, so until that is fixed skip it.
UNCOV
1241
            if (
×
1242
                !primaryColumn.transformer &&
×
1243
                !primaryColumn.relationMetadata &&
1244
                !primaryColumn.embeddedMetadata
1245
            ) {
UNCOV
1246
                return {
×
1247
                    [primaryColumn.propertyName]: In(
1248
                        normalized.map((id) =>
UNCOV
1249
                            primaryColumn.getEntityValue(id, false),
×
1250
                        ),
1251
                    ),
1252
                }
1253
            }
1254
        }
1255

UNCOV
1256
        return new Brackets((qb) => {
×
UNCOV
1257
            for (const data of normalized) {
×
UNCOV
1258
                qb.orWhere(new Brackets((qb) => qb.where(data)))
×
1259
            }
1260
        })
1261
    }
1262

1263
    protected getExistsCondition(subQuery: any): [string, any[]] {
UNCOV
1264
        const query = subQuery
×
1265
            .clone()
1266
            .orderBy()
1267
            .groupBy()
1268
            .offset(undefined)
1269
            .limit(undefined)
1270
            .skip(undefined)
1271
            .take(undefined)
1272
            .select("1")
1273
            .setOption("disable-global-order")
1274

UNCOV
1275
        return [`EXISTS (${query.getQuery()})`, query.getParameters()]
×
1276
    }
1277

1278
    private findColumnsForPropertyPath(
1279
        propertyPath: string,
1280
    ): [Alias, string[], ColumnMetadata[]] {
1281
        // Make a helper to iterate the entity & relations?
1282
        // Use that to set the correct alias?  Or the other way around?
1283

1284
        // Start with the main alias with our property paths
UNCOV
1285
        let alias = this.expressionMap.mainAlias
×
UNCOV
1286
        const root: string[] = []
×
UNCOV
1287
        const propertyPathParts = propertyPath.split(".")
×
1288

UNCOV
1289
        while (propertyPathParts.length > 1) {
×
UNCOV
1290
            const part = propertyPathParts[0]
×
1291

UNCOV
1292
            if (!alias?.hasMetadata) {
×
1293
                // If there's no metadata, we're wasting our time
1294
                // and can't actually look any of this up.
1295
                break
×
1296
            }
1297

UNCOV
1298
            if (alias.metadata.hasEmbeddedWithPropertyPath(part)) {
×
1299
                // If this is an embedded then we should combine the two as part of our lookup.
1300
                // Instead of just breaking, we keep going with this in case there's an embedded/relation
1301
                // inside an embedded.
UNCOV
1302
                propertyPathParts.unshift(
×
1303
                    `${propertyPathParts.shift()}.${propertyPathParts.shift()}`,
1304
                )
UNCOV
1305
                continue
×
1306
            }
1307

UNCOV
1308
            if (alias.metadata.hasRelationWithPropertyPath(part)) {
×
1309
                // If this is a relation then we should find the aliases
1310
                // that match the relation & then continue further down
1311
                // the property path
UNCOV
1312
                const joinAttr = this.expressionMap.joinAttributes.find(
×
UNCOV
1313
                    (joinAttr) => joinAttr.relationPropertyPath === part,
×
1314
                )
1315

UNCOV
1316
                if (!joinAttr?.alias) {
×
1317
                    const fullRelationPath =
1318
                        root.length > 0 ? `${root.join(".")}.${part}` : part
×
1319
                    throw new Error(
×
1320
                        `Cannot find alias for relation at ${fullRelationPath}`,
1321
                    )
1322
                }
1323

UNCOV
1324
                alias = joinAttr.alias
×
UNCOV
1325
                root.push(...part.split("."))
×
UNCOV
1326
                propertyPathParts.shift()
×
UNCOV
1327
                continue
×
1328
            }
1329

1330
            break
×
1331
        }
1332

UNCOV
1333
        if (!alias) {
×
1334
            throw new Error(`Cannot find alias for property ${propertyPath}`)
×
1335
        }
1336

1337
        // Remaining parts are combined back and used to find the actual property path
UNCOV
1338
        const aliasPropertyPath = propertyPathParts.join(".")
×
1339

1340
        const columns =
UNCOV
1341
            alias.metadata.findColumnsWithPropertyPath(aliasPropertyPath)
×
1342

UNCOV
1343
        if (!columns.length) {
×
UNCOV
1344
            throw new EntityPropertyNotFoundError(propertyPath, alias.metadata)
×
1345
        }
1346

UNCOV
1347
        return [alias, root, columns]
×
1348
    }
1349

1350
    /**
1351
     * Creates a property paths for a given ObjectLiteral.
1352
     */
1353
    protected createPropertyPath(
1354
        metadata: EntityMetadata,
1355
        entity: ObjectLiteral,
1356
        prefix: string = "",
×
1357
    ) {
UNCOV
1358
        const paths: string[] = []
×
1359

UNCOV
1360
        for (const key of Object.keys(entity)) {
×
UNCOV
1361
            const path = prefix ? `${prefix}.${key}` : key
×
1362

1363
            // There's times where we don't actually want to traverse deeper.
1364
            // If the value is a `FindOperator`, or null, or not an object, then we don't, for example.
UNCOV
1365
            if (
×
1366
                entity[key] === null ||
×
1367
                typeof entity[key] !== "object" ||
1368
                InstanceChecker.isFindOperator(entity[key])
1369
            ) {
UNCOV
1370
                paths.push(path)
×
UNCOV
1371
                continue
×
1372
            }
1373

UNCOV
1374
            if (metadata.hasEmbeddedWithPropertyPath(path)) {
×
UNCOV
1375
                const subPaths = this.createPropertyPath(
×
1376
                    metadata,
1377
                    entity[key],
1378
                    path,
1379
                )
UNCOV
1380
                paths.push(...subPaths)
×
UNCOV
1381
                continue
×
1382
            }
1383

UNCOV
1384
            if (metadata.hasRelationWithPropertyPath(path)) {
×
UNCOV
1385
                const relation = metadata.findRelationWithPropertyPath(path)!
×
1386

1387
                // There's also cases where we don't want to return back all of the properties.
1388
                // These handles the situation where someone passes the model & we don't need to make
1389
                // a HUGE `where` to uniquely look up the entity.
1390

1391
                // In the case of a *-to-one, there's only ever one possible entity on the other side
1392
                // so if the join columns are all defined we can return just the relation itself
1393
                // because it will fetch only the join columns and do the lookup.
UNCOV
1394
                if (
×
1395
                    relation.relationType === "one-to-one" ||
×
1396
                    relation.relationType === "many-to-one"
1397
                ) {
UNCOV
1398
                    const joinColumns = relation.joinColumns
×
UNCOV
1399
                        .map((j) => j.referencedColumn)
×
UNCOV
1400
                        .filter((j): j is ColumnMetadata => !!j)
×
1401

1402
                    const hasAllJoinColumns =
UNCOV
1403
                        joinColumns.length > 0 &&
×
1404
                        joinColumns.every((column) =>
UNCOV
1405
                            column.getEntityValue(entity[key], false),
×
1406
                        )
1407

UNCOV
1408
                    if (hasAllJoinColumns) {
×
UNCOV
1409
                        paths.push(path)
×
UNCOV
1410
                        continue
×
1411
                    }
1412
                }
1413

UNCOV
1414
                if (
×
1415
                    relation.relationType === "one-to-many" ||
×
1416
                    relation.relationType === "many-to-many"
1417
                ) {
UNCOV
1418
                    throw new Error(
×
1419
                        `Cannot query across ${relation.relationType} for property ${path}`,
1420
                    )
1421
                }
1422

1423
                // For any other case, if the `entity[key]` contains all of the primary keys we can do a
1424
                // lookup via these.  We don't need to look up via any other values 'cause these are
1425
                // the unique primary keys.
1426
                // This handles the situation where someone passes the model & we don't need to make
1427
                // a HUGE where.
1428
                const primaryColumns =
UNCOV
1429
                    relation.inverseEntityMetadata.primaryColumns
×
1430
                const hasAllPrimaryKeys =
UNCOV
1431
                    primaryColumns.length > 0 &&
×
1432
                    primaryColumns.every((column) =>
UNCOV
1433
                        column.getEntityValue(entity[key], false),
×
1434
                    )
1435

UNCOV
1436
                if (hasAllPrimaryKeys) {
×
1437
                    const subPaths = primaryColumns.map(
×
1438
                        (column) => `${path}.${column.propertyPath}`,
×
1439
                    )
1440
                    paths.push(...subPaths)
×
1441
                    continue
×
1442
                }
1443

1444
                // If nothing else, just return every property that's being passed to us.
UNCOV
1445
                const subPaths = this.createPropertyPath(
×
1446
                    relation.inverseEntityMetadata,
1447
                    entity[key],
UNCOV
1448
                ).map((p) => `${path}.${p}`)
×
UNCOV
1449
                paths.push(...subPaths)
×
UNCOV
1450
                continue
×
1451
            }
1452

UNCOV
1453
            paths.push(path)
×
1454
        }
1455

UNCOV
1456
        return paths
×
1457
    }
1458

1459
    protected *getPredicates(where: ObjectLiteral) {
UNCOV
1460
        if (this.expressionMap.mainAlias!.hasMetadata) {
×
UNCOV
1461
            const propertyPaths = this.createPropertyPath(
×
1462
                this.expressionMap.mainAlias!.metadata,
1463
                where,
1464
            )
1465

UNCOV
1466
            for (const propertyPath of propertyPaths) {
×
1467
                const [alias, aliasPropertyPath, columns] =
UNCOV
1468
                    this.findColumnsForPropertyPath(propertyPath)
×
1469

UNCOV
1470
                for (const column of columns) {
×
UNCOV
1471
                    let containedWhere = where
×
1472

UNCOV
1473
                    for (const part of aliasPropertyPath) {
×
UNCOV
1474
                        if (!containedWhere || !(part in containedWhere)) {
×
1475
                            containedWhere = {}
×
1476
                            break
×
1477
                        }
1478

UNCOV
1479
                        containedWhere = containedWhere[part]
×
1480
                    }
1481

1482
                    // Use the correct alias & the property path from the column
UNCOV
1483
                    const aliasPath = this.expressionMap
×
1484
                        .aliasNamePrefixingEnabled
1485
                        ? `${alias.name}.${column.propertyPath}`
1486
                        : column.propertyPath
1487

UNCOV
1488
                    const parameterValue = column.getEntityValue(
×
1489
                        containedWhere,
1490
                        true,
1491
                    )
1492

UNCOV
1493
                    yield [aliasPath, parameterValue]
×
1494
                }
1495
            }
1496
        } else {
1497
            for (const key of Object.keys(where)) {
×
1498
                const parameterValue = where[key]
×
1499
                const aliasPath = this.expressionMap.aliasNamePrefixingEnabled
×
1500
                    ? `${this.alias}.${key}`
1501
                    : key
1502

1503
                yield [aliasPath, parameterValue]
×
1504
            }
1505
        }
1506
    }
1507

1508
    protected getWherePredicateCondition(
1509
        aliasPath: string,
1510
        parameterValue: any,
1511
    ): WhereClauseCondition {
UNCOV
1512
        if (InstanceChecker.isFindOperator(parameterValue)) {
×
UNCOV
1513
            const parameters: any[] = []
×
UNCOV
1514
            if (parameterValue.useParameter) {
×
UNCOV
1515
                if (parameterValue.objectLiteralParameters) {
×
UNCOV
1516
                    this.setParameters(parameterValue.objectLiteralParameters)
×
UNCOV
1517
                } else if (parameterValue.multipleParameters) {
×
UNCOV
1518
                    for (const v of parameterValue.value) {
×
UNCOV
1519
                        parameters.push(this.createParameter(v))
×
1520
                    }
1521
                } else {
UNCOV
1522
                    parameters.push(this.createParameter(parameterValue.value))
×
1523
                }
1524
            }
1525

UNCOV
1526
            if (parameterValue.type === "raw") {
×
UNCOV
1527
                if (parameterValue.getSql) {
×
UNCOV
1528
                    return parameterValue.getSql(aliasPath)
×
1529
                } else {
UNCOV
1530
                    return {
×
1531
                        operator: "equal",
1532
                        parameters: [aliasPath, parameterValue.value],
1533
                    }
1534
                }
UNCOV
1535
            } else if (parameterValue.type === "not") {
×
UNCOV
1536
                if (parameterValue.child) {
×
UNCOV
1537
                    return {
×
1538
                        operator: parameterValue.type,
1539
                        condition: this.getWherePredicateCondition(
1540
                            aliasPath,
1541
                            parameterValue.child,
1542
                        ),
1543
                    }
1544
                } else {
UNCOV
1545
                    return {
×
1546
                        operator: "notEqual",
1547
                        parameters: [aliasPath, ...parameters],
1548
                    }
1549
                }
UNCOV
1550
            } else if (parameterValue.type === "and") {
×
UNCOV
1551
                const values: FindOperator<any>[] = parameterValue.value
×
1552

UNCOV
1553
                return {
×
1554
                    operator: parameterValue.type,
1555
                    parameters: values.map((operator) =>
UNCOV
1556
                        this.createWhereConditionExpression(
×
1557
                            this.getWherePredicateCondition(
1558
                                aliasPath,
1559
                                operator,
1560
                            ),
1561
                        ),
1562
                    ),
1563
                }
UNCOV
1564
            } else if (parameterValue.type === "or") {
×
UNCOV
1565
                const values: FindOperator<any>[] = parameterValue.value
×
1566

UNCOV
1567
                return {
×
1568
                    operator: parameterValue.type,
1569
                    parameters: values.map((operator) =>
UNCOV
1570
                        this.createWhereConditionExpression(
×
1571
                            this.getWherePredicateCondition(
1572
                                aliasPath,
1573
                                operator,
1574
                            ),
1575
                        ),
1576
                    ),
1577
                }
1578
            } else {
UNCOV
1579
                return {
×
1580
                    operator: parameterValue.type,
1581
                    parameters: [aliasPath, ...parameters],
1582
                }
1583
            }
NEW
1584
        } else if (parameterValue === null) {
×
NEW
1585
            if (this.expressionMap.treatJsNullAsSqlNull) {
×
NEW
1586
                return {
×
1587
                    operator: "isNull",
1588
                    parameters: [aliasPath],
1589
                }
1590
            }
NEW
1591
        } else if (parameterValue === undefined) {
×
NEW
1592
            if (this.expressionMap.throwOnUndefinedInFind) {
×
NEW
1593
                throw new TypeORMError(
×
1594
                    `Undefined value encountered in property '${aliasPath}' of the find operation. ` +
1595
                        `Set 'throwOnUndefinedInFind' to false in connection options to skip properties with undefined values.`,
1596
                )
1597
            }
1598
        }
1599

NEW
1600
        return {
×
1601
            operator: "equal",
1602
            parameters: [aliasPath, this.createParameter(parameterValue)],
1603
        }
1604
    }
1605

1606
    protected getWhereCondition(
1607
        where:
1608
            | string
1609
            | ((qb: this) => string)
1610
            | Brackets
1611
            | NotBrackets
1612
            | ObjectLiteral
1613
            | ObjectLiteral[],
1614
    ): WhereClauseCondition {
UNCOV
1615
        if (typeof where === "string") {
×
UNCOV
1616
            return where
×
1617
        }
1618

UNCOV
1619
        if (InstanceChecker.isBrackets(where)) {
×
UNCOV
1620
            const whereQueryBuilder = this.createQueryBuilder()
×
1621

UNCOV
1622
            whereQueryBuilder.parentQueryBuilder = this
×
1623

UNCOV
1624
            whereQueryBuilder.expressionMap.mainAlias =
×
1625
                this.expressionMap.mainAlias
UNCOV
1626
            whereQueryBuilder.expressionMap.aliasNamePrefixingEnabled =
×
1627
                this.expressionMap.aliasNamePrefixingEnabled
UNCOV
1628
            whereQueryBuilder.expressionMap.parameters =
×
1629
                this.expressionMap.parameters
UNCOV
1630
            whereQueryBuilder.expressionMap.nativeParameters =
×
1631
                this.expressionMap.nativeParameters
1632

UNCOV
1633
            whereQueryBuilder.expressionMap.wheres = []
×
1634

UNCOV
1635
            where.whereFactory(whereQueryBuilder as any)
×
1636

UNCOV
1637
            return {
×
1638
                operator: InstanceChecker.isNotBrackets(where)
×
1639
                    ? "not"
1640
                    : "brackets",
1641
                condition: whereQueryBuilder.expressionMap.wheres,
1642
            }
1643
        }
1644

UNCOV
1645
        if (typeof where === "function") {
×
UNCOV
1646
            return where(this)
×
1647
        }
1648

UNCOV
1649
        const wheres: ObjectLiteral[] = Array.isArray(where) ? where : [where]
×
UNCOV
1650
        const clauses: WhereClause[] = []
×
1651

UNCOV
1652
        for (const where of wheres) {
×
UNCOV
1653
            const conditions: WhereClauseCondition = []
×
1654

1655
            // Filter the conditions and set up the parameter values
UNCOV
1656
            for (const [aliasPath, parameterValue] of this.getPredicates(
×
1657
                where,
1658
            )) {
UNCOV
1659
                conditions.push({
×
1660
                    type: "and",
1661
                    condition: this.getWherePredicateCondition(
1662
                        aliasPath,
1663
                        parameterValue,
1664
                    ),
1665
                })
1666
            }
1667

UNCOV
1668
            clauses.push({ type: "or", condition: conditions })
×
1669
        }
1670

UNCOV
1671
        if (clauses.length === 1) {
×
UNCOV
1672
            return clauses[0].condition
×
1673
        }
1674

UNCOV
1675
        return clauses
×
1676
    }
1677

1678
    /**
1679
     * Creates a query builder used to execute sql queries inside this query builder.
1680
     */
1681
    protected obtainQueryRunner() {
UNCOV
1682
        return this.queryRunner || this.connection.createQueryRunner()
×
1683
    }
1684

1685
    protected hasCommonTableExpressions(): boolean {
UNCOV
1686
        return this.expressionMap.commonTableExpressions.length > 0
×
1687
    }
1688
}
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