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

typeorm / typeorm / 19549987525

20 Nov 2025 08:11PM UTC coverage: 80.769% (+4.3%) from 76.433%
19549987525

push

github

web-flow
ci: run tests on commits to master and next (#11783)

Co-authored-by: Oleg "OSA413" Sokolov <OSA413@users.noreply.github.com>

26500 of 32174 branches covered (82.36%)

Branch coverage included in aggregate %.

91252 of 113615 relevant lines covered (80.32%)

88980.79 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

26✔
88
    // -------------------------------------------------------------------------
26✔
89
    // Constructor
26✔
90
    // -------------------------------------------------------------------------
26✔
91

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

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

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

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

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

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

26✔
133
    // -------------------------------------------------------------------------
26✔
134
    // Accessors
26✔
135
    // -------------------------------------------------------------------------
26✔
136

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

483,031✔
144
        return this.expressionMap.mainAlias.name
483,031✔
145
    }
483,031✔
146

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

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

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

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

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

320✔
191
        if (InstanceChecker.isSelectQueryBuilder(this)) return this as any
320!
192

320✔
193
        return QueryBuilder.queryBuilderRegistry["SelectQueryBuilder"](this)
320✔
194
    }
320✔
195

26✔
196
    /**
26✔
197
     * Creates INSERT query.
26✔
198
     */
26✔
199
    insert(): InsertQueryBuilder<Entity> {
26✔
200
        this.expressionMap.queryType = "insert"
241,087✔
201

241,087✔
202
        if (InstanceChecker.isInsertQueryBuilder(this)) return this as any
241,087!
203

241,087✔
204
        return QueryBuilder.queryBuilderRegistry["InsertQueryBuilder"](this)
241,087✔
205
    }
241,087✔
206

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

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

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

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

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

18,588✔
251
        if (
18,588✔
252
            typeof entityOrTableNameUpdateSet === "function" ||
18,588✔
253
            typeof entityOrTableNameUpdateSet === "string"
474✔
254
        ) {
18,588✔
255
            const mainAlias = this.createFromAlias(entityOrTableNameUpdateSet)
18,390✔
256
            this.expressionMap.setMainAlias(mainAlias)
18,390✔
257
        }
18,390✔
258

18,588✔
259
        this.expressionMap.queryType = "update"
18,588✔
260
        this.expressionMap.valuesSet = updateSet
18,588✔
261

18,588✔
262
        if (InstanceChecker.isUpdateQueryBuilder(this)) return this as any
18,588!
263

18,588✔
264
        return QueryBuilder.queryBuilderRegistry["UpdateQueryBuilder"](this)
18,588✔
265
    }
18,588✔
266

26✔
267
    /**
26✔
268
     * Creates DELETE query.
26✔
269
     */
26✔
270
    delete(): DeleteQueryBuilder<Entity> {
26✔
271
        this.expressionMap.queryType = "delete"
4,529✔
272

4,529✔
273
        if (InstanceChecker.isDeleteQueryBuilder(this)) return this as any
4,529!
274

4,529✔
275
        return QueryBuilder.queryBuilderRegistry["DeleteQueryBuilder"](this)
4,529✔
276
    }
4,529✔
277

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

820✔
281
        if (InstanceChecker.isSoftDeleteQueryBuilder(this)) return this as any
820!
282

820✔
283
        return QueryBuilder.queryBuilderRegistry["SoftDeleteQueryBuilder"](this)
820✔
284
    }
820✔
285

26✔
286
    restore(): SoftDeleteQueryBuilder<any> {
26✔
287
        this.expressionMap.queryType = "restore"
336✔
288

336✔
289
        if (InstanceChecker.isSoftDeleteQueryBuilder(this)) return this as any
336!
290

336✔
291
        return QueryBuilder.queryBuilderRegistry["SoftDeleteQueryBuilder"](this)
336✔
292
    }
336✔
293

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

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

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

1,944✔
321
        this.expressionMap.queryType = "relation"
1,944✔
322
        this.expressionMap.relationPropertyPath = propertyPath
1,944✔
323

1,944✔
324
        if (entityTarget) {
1,944✔
325
            const mainAlias = this.createFromAlias(entityTarget)
1,944✔
326
            this.expressionMap.setMainAlias(mainAlias)
1,944✔
327
        }
1,944✔
328

1,944✔
329
        if (InstanceChecker.isRelationQueryBuilder(this)) return this as any
1,944!
330

1,944✔
331
        return QueryBuilder.queryBuilderRegistry["RelationQueryBuilder"](this)
1,944✔
332
    }
1,944✔
333

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

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

26✔
350
    /**
26✔
351
     * Checks if given relation or relations exist in the entity.
26✔
352
     * Returns true if relation exists, false otherwise.
26✔
353
     *
26✔
354
     * todo: move this method to manager? or create a shortcut?
26✔
355
     */
26✔
356
    hasRelation<T>(
26✔
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

26✔
367
    /**
26✔
368
     * Check the existence of a parameter for this query builder.
26✔
369
     */
26✔
370
    hasParameter(key: string): boolean {
26✔
371
        return (
2,436,210✔
372
            this.parentQueryBuilder?.hasParameter(key) ||
2,436,210✔
373
            key in this.expressionMap.parameters
2,430,138✔
374
        )
2,436,210✔
375
    }
2,436,210✔
376

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

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

2,481,094✔
395
        if (this.parentQueryBuilder) {
2,481,134✔
396
            this.parentQueryBuilder.setParameter(key, value)
77,985✔
397
        }
77,985✔
398

2,481,094✔
399
        this.expressionMap.parameters[key] = value
2,481,094✔
400
        return this
2,481,094✔
401
    }
2,481,094✔
402

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

50,279✔
411
        return this
50,279✔
412
    }
50,279✔
413

26✔
414
    protected createParameter(value: any): string {
26✔
415
        let parameterName
2,347,976✔
416

2,347,976✔
417
        do {
2,347,976✔
418
            parameterName = `orm_param_${this.parameterIndex++}`
2,352,338✔
419
        } while (this.hasParameter(parameterName))
2,347,976✔
420

2,347,976✔
421
        this.setParameter(parameterName, value)
2,347,976✔
422

2,347,976✔
423
        return `:${parameterName}`
2,347,976✔
424
    }
2,347,976✔
425

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

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

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

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

475,565✔
469
        return parameters
475,565✔
470
    }
475,565✔
471

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

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

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

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

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

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

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

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

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

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

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

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

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

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

26✔
612
    /**
26✔
613
     * Gets escaped table name with schema name if SqlServer driver used with custom
26✔
614
     * schema name, otherwise returns escaped table name.
26✔
615
     */
26✔
616
    protected getTableName(tablePath: string): string {
26✔
617
        return tablePath
5,123,643✔
618
            .split(".")
5,123,643✔
619
            .map((i) => {
5,123,643✔
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
5,127,067✔
621
                if (i === "") return i
5,127,067!
622
                return this.escape(i)
5,126,625✔
623
            })
5,123,643✔
624
            .join(".")
5,123,643✔
625
    }
5,123,643✔
626

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

746,417✔
636
        if (this.expressionMap.mainAlias.hasMetadata)
746,417✔
637
            return this.expressionMap.mainAlias.metadata.tablePath
746,417✔
638

7,092✔
639
        return this.expressionMap.mainAlias.tablePath!
7,092✔
640
    }
7,092✔
641

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

782,369✔
657
            return this.expressionMap.createAlias({
782,369✔
658
                type: "from",
782,369✔
659
                name: aliasName,
782,369✔
660
                metadata: this.connection.getMetadata(entityTarget),
782,369✔
661
                tablePath: metadata.tablePath,
782,369✔
662
            })
782,369✔
663
        } else {
796,307✔
664
            if (typeof entityTarget === "string") {
13,938✔
665
                const isSubquery =
13,854✔
666
                    entityTarget.substr(0, 1) === "(" &&
13,854✔
667
                    entityTarget.substr(-1) === ")"
7,799✔
668

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

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

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

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

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

799,055✔
707
        for (const alias of this.expressionMap.aliases) {
799,055✔
708
            if (!alias.hasMetadata) continue
5,151,611✔
709
            const replaceAliasNamePrefix =
5,136,053✔
710
                this.expressionMap.aliasNamePrefixingEnabled && alias.name
5,151,611✔
711
                    ? `${alias.name}.`
5,151,611✔
712
                    : ""
5,151,611✔
713

5,151,611✔
714
            if (!replacements[replaceAliasNamePrefix]) {
5,151,611✔
715
                replacements[replaceAliasNamePrefix] = {}
5,135,157✔
716
            }
5,135,157✔
717

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

5,136,053✔
727
            for (const relation of alias.metadata.relations) {
5,151,611✔
728
                if (relation.joinColumns.length > 0)
1,437,703✔
729
                    replacements[replaceAliasNamePrefix][
1,437,703✔
730
                        relation.propertyPath
913,779✔
731
                    ] = relation.joinColumns[0].databaseName
913,779✔
732
            }
1,437,703✔
733

5,136,053✔
734
            for (const relation of alias.metadata.relations) {
5,151,611✔
735
                const allColumns = [
1,437,703✔
736
                    ...relation.joinColumns,
1,437,703✔
737
                    ...relation.inverseJoinColumns,
1,437,703✔
738
                ]
1,437,703✔
739
                for (const joinColumn of allColumns) {
1,437,703✔
740
                    const propertyKey = `${relation.propertyPath}.${
1,341,393✔
741
                        joinColumn.referencedColumn!.propertyPath
1,341,393✔
742
                    }`
1,341,393✔
743
                    replacements[replaceAliasNamePrefix][propertyKey] =
1,341,393✔
744
                        joinColumn.databaseName
1,341,393✔
745
                }
1,341,393✔
746
            }
1,437,703✔
747

5,136,053✔
748
            for (const column of alias.metadata.columns) {
5,151,611✔
749
                replacements[replaceAliasNamePrefix][column.databaseName] =
14,215,400✔
750
                    column.databaseName
14,215,400✔
751
            }
14,215,400✔
752

5,136,053✔
753
            for (const column of alias.metadata.columns) {
5,151,611✔
754
                replacements[replaceAliasNamePrefix][column.propertyName] =
14,215,400✔
755
                    column.databaseName
14,215,400✔
756
            }
14,215,400✔
757

5,136,053✔
758
            for (const column of alias.metadata.columns) {
5,151,611✔
759
                replacements[replaceAliasNamePrefix][column.propertyPath] =
14,215,400✔
760
                    column.databaseName
14,215,400✔
761
            }
14,215,400✔
762
        }
5,136,053✔
763

799,055✔
764
        const replacementKeys = Object.keys(replacements)
799,055✔
765
        const replaceAliasNamePrefixes = replacementKeys
799,055✔
766
            .map((key) => escapeRegExp(key))
799,055✔
767
            .join("|")
799,055✔
768

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

9,260,340✔
791
                        if (replacements[matches[2]][p]) {
9,260,340✔
792
                            return `${pre}${this.escape(
9,260,329✔
793
                                matches[2].substring(0, matches[2].length - 1),
9,260,329✔
794
                            )}.${this.escape(replacements[matches[2]][p])}`
9,260,329✔
795
                        }
9,260,329✔
796
                    } else {
9,467,846✔
797
                        match = matches[0]
207,506✔
798
                        pre = matches[1]
207,506✔
799
                        p = matches[2]
207,506✔
800

207,506✔
801
                        if (replacements[""][p]) {
207,506✔
802
                            return `${pre}${this.escape(replacements[""][p])}`
28,068✔
803
                        }
28,068✔
804
                    }
207,506✔
805
                    return match
179,449✔
806
                },
785,140✔
807
            )
785,140✔
808
        }
785,140✔
809

799,055✔
810
        return statement
799,055✔
811
    }
799,055✔
812

26✔
813
    protected createComment(): string {
26✔
814
        if (!this.expressionMap.comment) {
798,442✔
815
            return ""
798,218✔
816
        }
798,218✔
817

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

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

26✔
826
    /**
26✔
827
     * Time travel queries for CockroachDB
26✔
828
     */
26✔
829
    protected createTimeTravelQuery(): string {
26✔
830
        if (
558,198✔
831
            this.expressionMap.queryType === "select" &&
558,198✔
832
            this.expressionMap.timeTravel
534,613✔
833
        ) {
558,198!
834
            return ` AS OF SYSTEM TIME ${this.expressionMap.timeTravel}`
6✔
835
        }
6✔
836

558,192✔
837
        return ""
558,192✔
838
    }
558,192✔
839

26✔
840
    /**
26✔
841
     * Creates "WHERE" expression.
26✔
842
     */
26✔
843
    protected createWhereExpression() {
26✔
844
        const conditionsArray = []
558,198✔
845

558,198✔
846
        const whereExpression = this.createWhereClausesExpression(
558,198✔
847
            this.expressionMap.wheres,
558,198✔
848
        )
558,198✔
849

558,198✔
850
        if (whereExpression.length > 0 && whereExpression !== "1=1") {
558,198✔
851
            conditionsArray.push(this.replacePropertyNames(whereExpression))
530,508✔
852
        }
530,508✔
853

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

2,025✔
868
                const condition = `${this.replacePropertyNames(column)} IS NULL`
2,025✔
869
                conditionsArray.push(condition)
2,025✔
870
            }
2,025✔
871

546,125✔
872
            if (metadata.discriminatorColumn && metadata.parentEntityMetadata) {
546,125✔
873
                const column = this.expressionMap.aliasNamePrefixingEnabled
1,476✔
874
                    ? this.expressionMap.mainAlias!.name +
1,476✔
875
                      "." +
1,280✔
876
                      metadata.discriminatorColumn.databaseName
1,280✔
877
                    : metadata.discriminatorColumn.databaseName
1,476✔
878

1,476✔
879
                const condition = `${this.replacePropertyNames(
1,476✔
880
                    column,
1,476✔
881
                )} IN (:...discriminatorColumnValues)`
1,476✔
882
                conditionsArray.push(condition)
1,476✔
883
            }
1,476✔
884
        }
546,125✔
885

558,198✔
886
        if (this.expressionMap.extraAppendedAndWhereCondition) {
558,198✔
887
            const condition = this.replacePropertyNames(
7,234✔
888
                this.expressionMap.extraAppendedAndWhereCondition,
7,234✔
889
            )
7,234✔
890
            conditionsArray.push(condition)
7,234✔
891
        }
7,234✔
892

558,198✔
893
        let condition = ""
558,198✔
894

558,198✔
895
        // time travel
558,198✔
896
        condition += this.createTimeTravelQuery()
558,198✔
897

558,198✔
898
        if (!conditionsArray.length) {
558,198✔
899
            condition += ""
26,708✔
900
        } else if (conditionsArray.length === 1) {
558,198✔
901
            condition += ` WHERE ${conditionsArray[0]}`
521,801✔
902
        } else {
531,490✔
903
            condition += ` WHERE ( ${conditionsArray.join(" ) AND ( ")} )`
9,689✔
904
        }
9,689✔
905

558,198✔
906
        return condition
558,198✔
907
    }
558,198✔
908

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

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

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

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

39,631✔
970
            if (driver.options.type === "mssql") {
39,631!
971
                if (
5,748✔
972
                    this.expressionMap.queryType === "insert" ||
5,748✔
973
                    this.expressionMap.queryType === "update"
64✔
974
                ) {
5,748✔
975
                    columnsExpression += " INTO @OutputTable"
5,712✔
976
                }
5,712✔
977
            }
5,748✔
978

39,631✔
979
            return columnsExpression
39,631✔
980
        } else if (typeof this.expressionMap.returning === "string") {
264,515!
981
            return this.expressionMap.returning
52✔
982
        }
52✔
983

224,832✔
984
        return ""
224,832✔
985
    }
224,832✔
986

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

26✔
1009
    protected createWhereClausesExpression(clauses: WhereClause[]): string {
26✔
1010
        return clauses
755,449✔
1011
            .map((clause, index) => {
755,449✔
1012
                const expression = this.createWhereConditionExpression(
759,525✔
1013
                    clause.condition,
759,525✔
1014
                )
759,525✔
1015

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

478,033✔
1045
                return expression
478,033✔
1046
            })
755,449✔
1047
            .join(" ")
755,449✔
1048
            .trim()
755,449✔
1049
    }
755,449✔
1050

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

476,909✔
1060
        if (Array.isArray(condition)) {
860,232✔
1061
            if (condition.length === 0) {
197,172!
1062
                return "1=1"
×
1063
            }
×
1064

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

59,731✔
1071
            return "(" + this.createWhereClausesExpression(condition) + ")"
59,731✔
1072
        }
59,731✔
1073

279,737✔
1074
        const { driver } = this.connection
279,737✔
1075

279,737✔
1076
        switch (condition.operator) {
279,737✔
1077
            case "lessThan":
860,232✔
1078
                return `${condition.parameters[0]} < ${condition.parameters[1]}`
204✔
1079
            case "lessThanOrEqual":
860,232✔
1080
                return `${condition.parameters[0]} <= ${condition.parameters[1]}`
56✔
1081
            case "arrayContains":
860,232!
1082
                return `${condition.parameters[0]} @> ${condition.parameters[1]}`
20✔
1083
            case "jsonContains":
860,232!
1084
                return `${condition.parameters[0]} ::jsonb @> ${condition.parameters[1]}`
20✔
1085
            case "arrayContainedBy":
860,232!
1086
                return `${condition.parameters[0]} <@ ${condition.parameters[1]}`
20✔
1087
            case "arrayOverlap":
860,232!
1088
                return `${condition.parameters[0]} && ${condition.parameters[1]}`
16✔
1089
            case "moreThan":
860,232✔
1090
                return `${condition.parameters[0]} > ${condition.parameters[1]}`
210✔
1091
            case "moreThanOrEqual":
860,232✔
1092
                return `${condition.parameters[0]} >= ${condition.parameters[1]}`
56✔
1093
            case "notEqual":
860,232✔
1094
                return `${condition.parameters[0]} != ${condition.parameters[1]}`
92✔
1095
            case "equal":
860,232✔
1096
                return `${condition.parameters[0]} = ${condition.parameters[1]}`
126,060✔
1097
            case "ilike":
860,232✔
1098
                if (
64✔
1099
                    driver.options.type === "postgres" ||
64!
1100
                    driver.options.type === "cockroachdb"
52✔
1101
                ) {
64!
1102
                    return `${condition.parameters[0]} ILIKE ${condition.parameters[1]}`
14✔
1103
                }
14✔
1104

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

8✔
1122
                return `${condition.parameters[0]} = ANY(${condition.parameters[1]})`
8✔
1123
            case "isNull":
860,232✔
1124
                return `${condition.parameters[0]} IS NULL`
358✔
1125

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

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

26✔
1146
    protected createCteExpression(): string {
26✔
1147
        if (!this.hasCommonTableExpressions()) {
799,346✔
1148
            return ""
799,209✔
1149
        }
799,209✔
1150
        const databaseRequireRecusiveHint =
137✔
1151
            this.connection.driver.cteCapabilities.requiresRecursiveHint
137✔
1152

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

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

137✔
1220
        return "WITH " + cteStrings.join(", ") + " "
137✔
1221
    }
137✔
1222

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

127,315✔
1234
        // using in(...ids) for single primary key entities
127,315✔
1235
        if (!metadata.hasMultiplePrimaryKeys) {
127,315✔
1236
            const primaryColumn = metadata.primaryColumns[0]
112,372✔
1237

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

15,589✔
1256
        return new Brackets((qb) => {
15,589✔
1257
            for (const data of normalized) {
15,589✔
1258
                qb.orWhere(new Brackets((qb) => qb.where(data)))
16,403✔
1259
            }
16,403✔
1260
        })
15,589✔
1261
    }
15,589✔
1262

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

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

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

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

179,970✔
1289
        while (propertyPathParts.length > 1) {
179,970✔
1290
            const part = propertyPathParts[0]
6,307✔
1291

6,307✔
1292
            if (!alias?.hasMetadata) {
6,307!
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

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

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

27✔
1316
                if (!joinAttr?.alias) {
27!
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

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

×
1330
            break
×
1331
        }
×
1332

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

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

179,970✔
1340
        const columns =
179,970✔
1341
            alias.metadata.findColumnsWithPropertyPath(aliasPropertyPath)
179,970✔
1342

179,970✔
1343
        if (!columns.length) {
179,970✔
1344
            throw new EntityPropertyNotFoundError(propertyPath, alias.metadata)
112✔
1345
        }
112✔
1346

179,858✔
1347
        return [alias, root, columns]
179,858✔
1348
    }
179,858✔
1349

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

182,236✔
1360
        for (const key of Object.keys(entity)) {
182,236✔
1361
            const path = prefix ? `${prefix}.${key}` : key
205,879✔
1362

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

14,975✔
1374
            if (metadata.hasEmbeddedWithPropertyPath(path)) {
205,879✔
1375
                const subPaths = this.createPropertyPath(
6,120✔
1376
                    metadata,
6,120✔
1377
                    entity[key],
6,120✔
1378
                    path,
6,120✔
1379
                )
6,120✔
1380
                paths.push(...subPaths)
6,120✔
1381
                continue
6,120✔
1382
            }
6,120✔
1383

8,855✔
1384
            if (metadata.hasRelationWithPropertyPath(path)) {
205,879✔
1385
                const relation = metadata.findRelationWithPropertyPath(path)!
8,393✔
1386

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

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

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

8,381✔
1408
                    if (hasAllJoinColumns) {
8,381✔
1409
                        paths.push(path)
8,158✔
1410
                        continue
8,158✔
1411
                    }
8,158✔
1412
                }
8,381✔
1413

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

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

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

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

462✔
1453
            paths.push(path)
462✔
1454
        }
462✔
1455

182,224✔
1456
        return paths
182,224✔
1457
    }
182,224✔
1458

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

157,799✔
1466
            for (const propertyPath of propertyPaths) {
157,799✔
1467
                const [alias, aliasPropertyPath, columns] =
179,970✔
1468
                    this.findColumnsForPropertyPath(propertyPath)
179,970✔
1469

179,970✔
1470
                for (const column of columns) {
179,970✔
1471
                    let containedWhere = where
179,858✔
1472

179,858✔
1473
                    for (const part of aliasPropertyPath) {
179,858!
1474
                        if (!containedWhere || !(part in containedWhere)) {
27!
1475
                            containedWhere = {}
×
1476
                            break
×
1477
                        }
×
1478

27✔
1479
                        containedWhere = containedWhere[part]
27✔
1480
                    }
27✔
1481

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

179,858✔
1488
                    const parameterValue = column.getEntityValue(
179,858✔
1489
                        containedWhere,
179,858✔
1490
                        true,
179,858✔
1491
                    )
179,858✔
1492

179,858✔
1493
                    yield [aliasPath, parameterValue]
179,858✔
1494
                }
179,382✔
1495
            }
179,382✔
1496
        } else {
157,799!
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
    }
157,799✔
1507

26✔
1508
    protected getWherePredicateCondition(
26✔
1509
        aliasPath: string,
240,918✔
1510
        parameterValue: any,
240,918✔
1511
    ): WhereClauseCondition {
240,918✔
1512
        if (InstanceChecker.isFindOperator(parameterValue)) {
240,918✔
1513
            const parameters: any[] = []
114,345✔
1514
            if (parameterValue.useParameter) {
114,345✔
1515
                if (parameterValue.objectLiteralParameters) {
114,025✔
1516
                    this.setParameters(parameterValue.objectLiteralParameters)
196✔
1517
                } else if (parameterValue.multipleParameters) {
114,025✔
1518
                    for (const v of parameterValue.value) {
112,721✔
1519
                        parameters.push(this.createParameter(v))
187,911✔
1520
                    }
187,911✔
1521
                } else {
113,829✔
1522
                    parameters.push(this.createParameter(parameterValue.value))
1,108✔
1523
                }
1,108✔
1524
            }
114,025✔
1525

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

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

36✔
1567
                return {
36✔
1568
                    operator: parameterValue.type,
36✔
1569
                    parameters: values.map((operator) =>
36✔
1570
                        this.createWhereConditionExpression(
72✔
1571
                            this.getWherePredicateCondition(
72✔
1572
                                aliasPath,
72✔
1573
                                operator,
72✔
1574
                            ),
72✔
1575
                        ),
36✔
1576
                    ),
36✔
1577
                }
36✔
1578
            } else {
113,518✔
1579
                return {
113,482✔
1580
                    operator: parameterValue.type,
113,482✔
1581
                    parameters: [aliasPath, ...parameters],
113,482✔
1582
                }
113,482✔
1583
            }
113,482✔
1584
        } else if (parameterValue === null) {
240,918✔
1585
            const nullBehavior =
392✔
1586
                this.connection.options.invalidWhereValuesBehavior?.null ||
392!
1587
                "ignore"
×
1588
            if (nullBehavior === "sql-null") {
392✔
1589
                return {
112✔
1590
                    operator: "isNull",
112✔
1591
                    parameters: [aliasPath],
112✔
1592
                }
112✔
1593
            } else if (nullBehavior === "throw") {
392✔
1594
                throw new TypeORMError(
280✔
1595
                    `Null value encountered in property '${aliasPath}' of a where condition. ` +
280✔
1596
                        `To match with SQL NULL, the IsNull() operator must be used. ` +
280✔
1597
                        `Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in connection options to skip or handle null values.`,
280✔
1598
                )
280✔
1599
            }
280✔
1600
        } else if (parameterValue === undefined) {
126,573✔
1601
            const undefinedBehavior =
196✔
1602
                this.connection.options.invalidWhereValuesBehavior?.undefined ||
196!
1603
                "ignore"
×
1604
            if (undefinedBehavior === "throw") {
196✔
1605
                throw new TypeORMError(
196✔
1606
                    `Undefined value encountered in property '${aliasPath}' of a where condition. ` +
196✔
1607
                        `Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in connection options to skip properties with undefined values.`,
196✔
1608
                )
196✔
1609
            }
196✔
1610
        }
196✔
1611

125,985✔
1612
        return {
125,985✔
1613
            operator: "equal",
125,985✔
1614
            parameters: [aliasPath, this.createParameter(parameterValue)],
125,985✔
1615
        }
125,985✔
1616
    }
125,985✔
1617

26✔
1618
    protected getWhereCondition(
26✔
1619
        where:
573,301✔
1620
            | string
573,301✔
1621
            | ((qb: this) => string)
573,301✔
1622
            | Brackets
573,301✔
1623
            | NotBrackets
573,301✔
1624
            | ObjectLiteral
573,301✔
1625
            | ObjectLiteral[],
573,301✔
1626
    ): WhereClauseCondition {
573,301✔
1627
        if (typeof where === "string") {
573,301✔
1628
            return where
373,979✔
1629
        }
373,979✔
1630

199,322✔
1631
        if (InstanceChecker.isBrackets(where)) {
573,301✔
1632
            const whereQueryBuilder = this.createQueryBuilder()
39,647✔
1633

39,647✔
1634
            whereQueryBuilder.parentQueryBuilder = this
39,647✔
1635

39,647✔
1636
            whereQueryBuilder.expressionMap.mainAlias =
39,647✔
1637
                this.expressionMap.mainAlias
39,647✔
1638
            whereQueryBuilder.expressionMap.aliasNamePrefixingEnabled =
39,647✔
1639
                this.expressionMap.aliasNamePrefixingEnabled
39,647✔
1640
            whereQueryBuilder.expressionMap.parameters =
39,647✔
1641
                this.expressionMap.parameters
39,647✔
1642
            whereQueryBuilder.expressionMap.nativeParameters =
39,647✔
1643
                this.expressionMap.nativeParameters
39,647✔
1644

39,647✔
1645
            whereQueryBuilder.expressionMap.wheres = []
39,647✔
1646

39,647✔
1647
            where.whereFactory(whereQueryBuilder as any)
39,647✔
1648

39,647✔
1649
            return {
39,647✔
1650
                operator: InstanceChecker.isNotBrackets(where)
39,647✔
1651
                    ? "not"
39,647!
1652
                    : "brackets",
39,647✔
1653
                condition: whereQueryBuilder.expressionMap.wheres,
39,647✔
1654
            }
39,647✔
1655
        }
39,647✔
1656

159,675✔
1657
        if (typeof where === "function") {
573,301✔
1658
            return where(this)
2,324✔
1659
        }
2,324✔
1660

157,351✔
1661
        const wheres: ObjectLiteral[] = Array.isArray(where) ? where : [where]
573,301✔
1662
        const clauses: WhereClause[] = []
573,301✔
1663

573,301✔
1664
        for (const where of wheres) {
573,301✔
1665
            const conditions: WhereClauseCondition = []
157,799✔
1666

157,799✔
1667
            // Filter the conditions and set up the parameter values
157,799✔
1668
            for (const [aliasPath, parameterValue] of this.getPredicates(
157,799✔
1669
                where,
157,799✔
1670
            )) {
157,799✔
1671
                conditions.push({
179,858✔
1672
                    type: "and",
179,858✔
1673
                    condition: this.getWherePredicateCondition(
179,858✔
1674
                        aliasPath,
179,858✔
1675
                        parameterValue,
179,858✔
1676
                    ),
179,858✔
1677
                })
179,858✔
1678
            }
179,858✔
1679

157,199✔
1680
            clauses.push({ type: "or", condition: conditions })
157,199✔
1681
        }
157,199✔
1682

156,751✔
1683
        if (clauses.length === 1) {
573,301✔
1684
            return clauses[0].condition
156,367✔
1685
        }
156,367✔
1686

384✔
1687
        return clauses
384✔
1688
    }
384✔
1689

26✔
1690
    /**
26✔
1691
     * Creates a query builder used to execute sql queries inside this query builder.
26✔
1692
     */
26✔
1693
    protected obtainQueryRunner() {
26✔
1694
        return this.queryRunner || this.connection.createQueryRunner()
262,180✔
1695
    }
262,180✔
1696

26✔
1697
    protected hasCommonTableExpressions(): boolean {
26✔
1698
        return this.expressionMap.commonTableExpressions.length > 0
799,433✔
1699
    }
799,433✔
1700
}
26✔
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

© 2026 Coveralls, Inc