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

typeorm / typeorm / 16405830134

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

Pull #11576

github

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

9324 of 12885 branches covered (72.36%)

Branch coverage included in aggregate %.

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

2073 existing lines in 24 files now uncovered.

19016 of 24189 relevant lines covered (78.61%)

119538.23 hits per line

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

87.85
/src/query-builder/InsertQueryBuilder.ts
1
import { v4 as uuidv4 } from "uuid"
24✔
2
import { EntityTarget } from "../common/EntityTarget"
3
import { ObjectLiteral } from "../common/ObjectLiteral"
4
import { AuroraMysqlDriver } from "../driver/aurora-mysql/AuroraMysqlDriver"
5
import { DriverUtils } from "../driver/DriverUtils"
24✔
6
import { MysqlDriver } from "../driver/mysql/MysqlDriver"
7
import { SqlServerDriver } from "../driver/sqlserver/SqlServerDriver"
8
import { TypeORMError } from "../error"
24✔
9
import { InsertValuesMissingError } from "../error/InsertValuesMissingError"
24✔
10
import { ReturningStatementNotSupportedError } from "../error/ReturningStatementNotSupportedError"
24✔
11
import { ColumnMetadata } from "../metadata/ColumnMetadata"
12
import { BroadcasterResult } from "../subscriber/BroadcasterResult"
24✔
13
import { InstanceChecker } from "../util/InstanceChecker"
24✔
14
import { ObjectUtils } from "../util/ObjectUtils"
24✔
15
import { InsertOrUpdateOptions } from "./InsertOrUpdateOptions"
16
import { QueryBuilder } from "./QueryBuilder"
24✔
17
import { QueryDeepPartialEntity } from "./QueryPartialEntity"
18
import { InsertResult } from "./result/InsertResult"
24✔
19
import { ReturningResultsEntityUpdator } from "./ReturningResultsEntityUpdator"
24✔
20
import { WhereClause } from "./WhereClause"
21

22
/**
23
 * Allows to build complex sql queries in a fashion way and execute those queries.
24
 */
25
export class InsertQueryBuilder<
24✔
26
    Entity extends ObjectLiteral,
27
> extends QueryBuilder<Entity> {
28
    readonly "@instanceof" = Symbol.for("InsertQueryBuilder")
205,617✔
29

30
    // -------------------------------------------------------------------------
31
    // Public Implemented Methods
32
    // -------------------------------------------------------------------------
33

34
    /**
35
     * Gets generated SQL query without parameters being replaced.
36
     */
37
    getQuery(): string {
38
        let sql = this.createComment()
205,574✔
39
        sql += this.createCteExpression()
205,574✔
40
        sql += this.createInsertExpression()
205,574✔
41
        return this.replacePropertyNamesForTheWholeQuery(sql.trim())
205,565✔
42
    }
43

44
    /**
45
     * Executes sql generated by query builder and returns raw database results.
46
     */
47
    async execute(): Promise<InsertResult> {
48
        // console.time(".value sets");
49
        const valueSets: ObjectLiteral[] = this.getValueSets()
204,557✔
50
        // console.timeEnd(".value sets");
51

52
        // If user passed empty array of entities then we don't need to do
53
        // anything.
54
        //
55
        // Fixes GitHub issues #3111 and #5734. If we were to let this through
56
        // we would run into problems downstream, like subscribers getting
57
        // invoked with the empty array where they expect an entity, and SQL
58
        // queries with an empty VALUES clause.
59
        if (valueSets.length === 0) return new InsertResult()
204,533✔
60

61
        // console.time("QueryBuilder.execute");
62
        // console.time(".database stuff");
63
        const queryRunner = this.obtainQueryRunner()
204,485✔
64
        let transactionStartedByUs: boolean = false
204,485✔
65

66
        try {
204,485✔
67
            // start transaction if it was enabled
68
            if (
204,485✔
69
                this.expressionMap.useTransaction === true &&
204,509✔
70
                queryRunner.isTransactionActive === false
71
            ) {
72
                await queryRunner.startTransaction()
24✔
73
                transactionStartedByUs = true
24✔
74
            }
75

76
            // console.timeEnd(".database stuff");
77

78
            // call before insertion methods in listeners and subscribers
79
            if (
204,485✔
80
                this.expressionMap.callListeners === true &&
206,806✔
81
                this.expressionMap.mainAlias!.hasMetadata
82
            ) {
83
                const broadcastResult = new BroadcasterResult()
1,785✔
84
                valueSets.forEach((valueSet) => {
1,785✔
85
                    queryRunner.broadcaster.broadcastBeforeInsertEvent(
202,540✔
86
                        broadcastResult,
87
                        this.expressionMap.mainAlias!.metadata,
88
                        valueSet,
89
                    )
90
                })
91
                await broadcastResult.wait()
1,785✔
92
            }
93

94
            let declareSql: string | null = null
204,485✔
95
            let selectOutputSql: string | null = null
204,485✔
96

97
            // if update entity mode is enabled we may need extra columns for the returning statement
98
            // console.time(".prepare returning statement");
99
            const returningResultsEntityUpdator =
100
                new ReturningResultsEntityUpdator(
204,485✔
101
                    queryRunner,
102
                    this.expressionMap,
103
                )
104

105
            const returningColumns: ColumnMetadata[] = []
204,485✔
106

107
            if (
204,485✔
108
                Array.isArray(this.expressionMap.returning) &&
204,489✔
109
                this.expressionMap.mainAlias!.hasMetadata
110
            ) {
111
                for (const columnPath of this.expressionMap.returning) {
4✔
112
                    returningColumns.push(
6✔
113
                        ...this.expressionMap.mainAlias!.metadata.findColumnsWithPropertyPath(
114
                            columnPath,
115
                        ),
116
                    )
117
                }
118
            }
119

120
            if (
204,485✔
121
                this.expressionMap.updateEntity === true &&
405,688✔
122
                this.expressionMap.mainAlias!.hasMetadata
123
            ) {
124
                if (
200,667✔
125
                    !(
126
                        valueSets.length > 1 &&
214,164✔
127
                        this.connection.driver.options.type === "oracle"
128
                    )
129
                ) {
130
                    this.expressionMap.extraReturningColumns =
200,645✔
131
                        this.expressionMap.mainAlias!.metadata.getInsertionReturningColumns()
132
                }
133

134
                returningColumns.push(
200,667✔
135
                    ...this.expressionMap.extraReturningColumns.filter(
136
                        (c) => !returningColumns.includes(c),
102,234✔
137
                    ),
138
                )
139
            }
140

141
            if (
204,485✔
142
                returningColumns.length > 0 &&
290,208✔
143
                this.connection.driver.options.type === "mssql"
144
            ) {
145
                declareSql = (
5,514✔
146
                    this.connection.driver as SqlServerDriver
147
                ).buildTableVariableDeclaration(
148
                    "@OutputTable",
149
                    returningColumns,
150
                )
151
                selectOutputSql = `SELECT * FROM @OutputTable`
5,514✔
152
            }
153
            // console.timeEnd(".prepare returning statement");
154

155
            // execute query
156
            // console.time(".getting query and parameters");
157
            const [insertSql, parameters] = this.getQueryAndParameters()
204,485✔
158
            // console.timeEnd(".getting query and parameters");
159

160
            // console.time(".query execution by database");
161
            const statements = [declareSql, insertSql, selectOutputSql]
204,476✔
162
            const sql = statements.filter((s) => s != null).join(";\n\n")
613,428✔
163

164
            const queryResult = await queryRunner.query(sql, parameters, true)
204,476✔
165

166
            const insertResult = InsertResult.from(queryResult)
204,400✔
167

168
            // console.timeEnd(".query execution by database");
169

170
            // load returning results and set them to the entity if entity updation is enabled
171
            if (
204,400✔
172
                this.expressionMap.updateEntity === true &&
405,518✔
173
                this.expressionMap.mainAlias!.hasMetadata
174
            ) {
175
                // console.time(".updating entity");
176
                await returningResultsEntityUpdator.insert(
200,586✔
177
                    insertResult,
178
                    valueSets,
179
                )
180
                // console.timeEnd(".updating entity");
181
            }
182

183
            // call after insertion methods in listeners and subscribers
184
            if (
204,400✔
185
                this.expressionMap.callListeners === true &&
206,700✔
186
                this.expressionMap.mainAlias!.hasMetadata
187
            ) {
188
                const broadcastResult = new BroadcasterResult()
1,768✔
189
                valueSets.forEach((valueSet) => {
1,768✔
190
                    queryRunner.broadcaster.broadcastAfterInsertEvent(
202,521✔
191
                        broadcastResult,
192
                        this.expressionMap.mainAlias!.metadata,
193
                        valueSet,
194
                    )
195
                })
196
                await broadcastResult.wait()
1,768✔
197
            }
198

199
            // close transaction if we started it
200
            // console.time(".commit");
201
            if (transactionStartedByUs) {
204,400✔
202
                await queryRunner.commitTransaction()
24✔
203
            }
204
            // console.timeEnd(".commit");
205

206
            return insertResult
204,400✔
207
        } catch (error) {
208
            // rollback transaction if we started it
209
            if (transactionStartedByUs) {
85!
UNCOV
210
                try {
×
UNCOV
211
                    await queryRunner.rollbackTransaction()
×
212
                } catch (rollbackError) {}
213
            }
214
            throw error
85✔
215
        } finally {
216
            // console.time(".releasing connection");
217
            if (queryRunner !== this.queryRunner) {
204,485✔
218
                // means we created our own query runner
219
                await queryRunner.release()
1,789✔
220
            }
221
            // console.timeEnd(".releasing connection");
222
            // console.timeEnd("QueryBuilder.execute");
223
        }
224
    }
225

226
    // -------------------------------------------------------------------------
227
    // Public Methods
228
    // -------------------------------------------------------------------------
229

230
    /**
231
     * Specifies INTO which entity's table insertion will be executed.
232
     */
233
    into<T extends ObjectLiteral>(
234
        entityTarget: EntityTarget<T>,
235
        columns?: string[],
236
    ): InsertQueryBuilder<T> {
237
        entityTarget = InstanceChecker.isEntitySchema(entityTarget)
205,472!
238
            ? entityTarget.options.name
239
            : entityTarget
240
        const mainAlias = this.createFromAlias(entityTarget)
205,472✔
241
        this.expressionMap.setMainAlias(mainAlias)
205,472✔
242
        this.expressionMap.insertColumns = columns || []
205,472✔
243
        return this as any as InsertQueryBuilder<T>
205,472✔
244
    }
245

246
    /**
247
     * Values needs to be inserted into table.
248
     */
249
    values(
250
        values:
251
            | QueryDeepPartialEntity<Entity>
252
            | QueryDeepPartialEntity<Entity>[],
253
    ): this {
254
        this.expressionMap.valuesSet = values
205,593✔
255
        return this
205,593✔
256
    }
257

258
    /**
259
     * Optional returning/output clause.
260
     * This will return given column values.
261
     */
262
    output(columns: string[]): this
263

264
    /**
265
     * Optional returning/output clause.
266
     * Returning is a SQL string containing returning statement.
267
     */
268
    output(output: string): this
269

270
    /**
271
     * Optional returning/output clause.
272
     */
273
    output(output: string | string[]): this
274

275
    /**
276
     * Optional returning/output clause.
277
     */
278
    output(output: string | string[]): this {
UNCOV
279
        return this.returning(output)
×
280
    }
281

282
    /**
283
     * Optional returning/output clause.
284
     * This will return given column values.
285
     */
286
    returning(columns: string[]): this
287

288
    /**
289
     * Optional returning/output clause.
290
     * Returning is a SQL string containing returning statement.
291
     */
292
    returning(returning: string): this
293

294
    /**
295
     * Optional returning/output clause.
296
     */
297
    returning(returning: string | string[]): this
298

299
    /**
300
     * Optional returning/output clause.
301
     */
302
    returning(returning: string | string[]): this {
303
        // not all databases support returning/output cause
304
        if (!this.connection.driver.isReturningSqlSupported("insert")) {
25!
UNCOV
305
            throw new ReturningStatementNotSupportedError()
×
306
        }
307

308
        this.expressionMap.returning = returning
25✔
309
        return this
25✔
310
    }
311

312
    /**
313
     * Indicates if entity must be updated after insertion operations.
314
     * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
315
     * Enabled by default.
316
     */
317
    updateEntity(enabled: boolean): this {
318
        this.expressionMap.updateEntity = enabled
202,236✔
319
        return this
202,236✔
320
    }
321

322
    /**
323
     * Adds additional ON CONFLICT statement supported in postgres and cockroach.
324
     *
325
     * @deprecated Use `orIgnore` or `orUpdate`
326
     */
327
    onConflict(statement: string): this {
328
        this.expressionMap.onConflict = statement
30✔
329
        return this
30✔
330
    }
331

332
    /**
333
     * Adds additional ignore statement supported in databases.
334
     */
335
    orIgnore(statement: string | boolean = true): this {
21✔
336
        this.expressionMap.onIgnore = !!statement
38✔
337
        return this
38✔
338
    }
339

340
    /**
341
     * @deprecated
342
     *
343
     * `.orUpdate({ columns: [ "is_updated" ] }).setParameter("is_updated", value)`
344
     *
345
     * is now `.orUpdate(["is_updated"])`
346
     *
347
     * `.orUpdate({ conflict_target: ['date'], overwrite: ['title'] })`
348
     *
349
     * is now `.orUpdate(['title'], ['date'])`
350
     *
351
     */
352
    orUpdate(statement?: {
353
        columns?: string[]
354
        overwrite?: string[]
355
        conflict_target?: string | string[]
356
    }): this
357

358
    orUpdate(
359
        overwrite: string[],
360
        conflictTarget?: string | string[],
361
        orUpdateOptions?: InsertOrUpdateOptions,
362
    ): this
363

364
    /**
365
     * Adds additional update statement supported in databases.
366
     */
367
    orUpdate(
368
        statementOrOverwrite?:
369
            | {
370
                  columns?: string[]
371
                  overwrite?: string[]
372
                  conflict_target?: string | string[]
373
              }
374
            | string[],
375
        conflictTarget?: string | string[],
376
        orUpdateOptions?: InsertOrUpdateOptions,
377
    ): this {
378
        const { where, parameters } = orUpdateOptions?.overwriteCondition ?? {}
594✔
379
        let wheres: WhereClause[] | undefined
380
        if (where) {
594✔
381
            const condition = this.getWhereCondition(where)
17✔
382
            if (Array.isArray(condition) ? condition.length !== 0 : condition)
17!
383
                wheres = [{ type: "simple", condition: condition }]
17✔
384
        }
385
        if (parameters) this.setParameters(parameters)
594!
386

387
        if (!Array.isArray(statementOrOverwrite)) {
594!
UNCOV
388
            this.expressionMap.onUpdate = {
×
389
                conflict: statementOrOverwrite?.conflict_target,
390
                columns: statementOrOverwrite?.columns,
391
                overwrite: statementOrOverwrite?.overwrite,
392
                skipUpdateIfNoValuesChanged:
393
                    orUpdateOptions?.skipUpdateIfNoValuesChanged,
394
                upsertType: orUpdateOptions?.upsertType,
395
                overwriteCondition: wheres,
396
            }
UNCOV
397
            return this
×
398
        }
399

400
        this.expressionMap.onUpdate = {
594✔
401
            overwrite: statementOrOverwrite,
402
            conflict: conflictTarget,
403
            skipUpdateIfNoValuesChanged:
404
                orUpdateOptions?.skipUpdateIfNoValuesChanged,
405
            indexPredicate: orUpdateOptions?.indexPredicate,
406
            upsertType: orUpdateOptions?.upsertType,
407
            overwriteCondition: wheres,
408
        }
409
        return this
594✔
410
    }
411

412
    // -------------------------------------------------------------------------
413
    // Protected Methods
414
    // -------------------------------------------------------------------------
415

416
    /**
417
     * Creates INSERT express used to perform insert query.
418
     */
419
    protected createInsertExpression() {
420
        if (this.expressionMap.onUpdate || this.expressionMap.onIgnore) {
205,574✔
421
            if (
647✔
422
                (this.expressionMap.onUpdate?.upsertType ?? "merge-into") ===
1,698✔
423
                    "merge-into" &&
424
                this.connection.driver.supportedUpsertTypes.includes(
425
                    "merge-into",
426
                )
427
            )
428
                return this.createMergeExpression()
138✔
429
        }
430
        const tableName = this.getTableName(this.getMainTableName())
205,436✔
431
        const tableOrAliasName =
432
            this.alias !== this.getMainTableName()
205,436✔
433
                ? this.escape(this.alias)
434
                : tableName
435
        const valuesExpression = this.createValuesExpression() // its important to get values before returning expression because oracle rely on native parameters and ordering of them is important
205,436✔
436
        const returningExpression =
437
            this.connection.driver.options.type === "oracle" &&
205,436✔
438
            this.getValueSets().length > 1
439
                ? null
440
                : this.createReturningExpression("insert") // oracle doesnt support returning with multi-row insert
441
        const columnsExpression = this.createColumnNamesExpression()
205,436✔
442
        let query = "INSERT "
205,436✔
443

444
        if (this.expressionMap.onUpdate?.upsertType === "primary-key") {
205,436!
UNCOV
445
            query = "UPSERT "
×
446
        }
447

448
        if (
205,436✔
449
            DriverUtils.isMySQLFamily(this.connection.driver) ||
378,350✔
450
            this.connection.driver.options.type === "aurora-mysql"
451
        ) {
452
            query += `${this.expressionMap.onIgnore ? " IGNORE " : ""}`
32,522✔
453
        }
454

455
        query += `INTO ${tableName}`
205,436✔
456

457
        if (
205,436✔
458
            this.alias !== this.getMainTableName() &&
205,495✔
459
            DriverUtils.isPostgresFamily(this.connection.driver)
460
        ) {
461
            query += ` AS "${this.alias}"`
34✔
462
        }
463

464
        // add columns expression
465
        if (columnsExpression) {
205,436✔
466
            query += `(${columnsExpression})`
205,330✔
467
        } else {
468
            if (
106!
469
                !valuesExpression &&
264✔
470
                (DriverUtils.isMySQLFamily(this.connection.driver) ||
471
                    this.connection.driver.options.type === "aurora-mysql")
472
            )
473
                // special syntax for mysql DEFAULT VALUES insertion
UNCOV
474
                query += "()"
×
475
        }
476

477
        // add OUTPUT expression
478
        if (
205,436✔
479
            returningExpression &&
238,357✔
480
            this.connection.driver.options.type === "mssql"
481
        ) {
482
            query += ` OUTPUT ${returningExpression}`
5,492✔
483
        }
484

485
        // add VALUES expression
486
        if (valuesExpression) {
205,436✔
487
            if (
205,357✔
488
                (this.connection.driver.options.type === "oracle" ||
429,470✔
489
                    this.connection.driver.options.type === "sap") &&
490
                this.getValueSets().length > 1
491
            ) {
492
                query += ` ${valuesExpression}`
18✔
493
            } else {
494
                query += ` VALUES ${valuesExpression}`
205,339✔
495
            }
496
        } else {
497
            if (
79!
498
                DriverUtils.isMySQLFamily(this.connection.driver) ||
158✔
499
                this.connection.driver.options.type === "aurora-mysql"
500
            ) {
501
                // special syntax for mysql DEFAULT VALUES insertion
UNCOV
502
                query += " VALUES ()"
×
503
            } else {
504
                query += ` DEFAULT VALUES`
79✔
505
            }
506
        }
507
        if (this.expressionMap.onUpdate?.upsertType !== "primary-key") {
205,436✔
508
            if (
205,436✔
509
                this.connection.driver.supportedUpsertTypes.includes(
510
                    "on-conflict-do-update",
511
                )
512
            ) {
513
                if (this.expressionMap.onIgnore) {
120,292✔
514
                    query += " ON CONFLICT DO NOTHING "
24✔
515
                } else if (this.expressionMap.onConflict) {
120,268✔
516
                    query += ` ON CONFLICT ${this.expressionMap.onConflict} `
30✔
517
                } else if (this.expressionMap.onUpdate) {
120,238✔
518
                    const {
519
                        overwrite,
520
                        columns,
521
                        conflict,
522
                        skipUpdateIfNoValuesChanged,
523
                        indexPredicate,
524
                    } = this.expressionMap.onUpdate
389✔
525

526
                    let conflictTarget = "ON CONFLICT"
389✔
527

528
                    if (Array.isArray(conflict)) {
389✔
529
                        conflictTarget += ` ( ${conflict
381✔
530
                            .map((column) => this.escape(column))
404✔
531
                            .join(", ")} )`
532
                        if (
381✔
533
                            indexPredicate &&
407✔
534
                            !DriverUtils.isPostgresFamily(
535
                                this.connection.driver,
536
                            )
537
                        ) {
538
                            throw new TypeORMError(
9✔
539
                                `indexPredicate option is not supported by the current database driver`,
540
                            )
541
                        }
542
                        if (
372✔
543
                            indexPredicate &&
389✔
544
                            DriverUtils.isPostgresFamily(this.connection.driver)
545
                        ) {
546
                            conflictTarget += ` WHERE ( ${indexPredicate} )`
17✔
547
                        }
548
                    } else if (conflict) {
8✔
549
                        conflictTarget += ` ON CONSTRAINT ${this.escape(
8✔
550
                            conflict,
551
                        )}`
552
                    }
553

554
                    const updatePart: string[] = []
380✔
555

556
                    if (Array.isArray(overwrite)) {
380!
557
                        updatePart.push(
380✔
558
                            ...overwrite.map(
559
                                (column) =>
560
                                    `${this.escape(
761✔
561
                                        column,
562
                                    )} = EXCLUDED.${this.escape(column)}`,
563
                            ),
564
                        )
UNCOV
565
                    } else if (columns) {
×
UNCOV
566
                        updatePart.push(
×
567
                            ...columns.map(
568
                                (column) =>
UNCOV
569
                                    `${this.escape(column)} = :${column}`,
×
570
                            ),
571
                        )
572
                    }
573

574
                    if (updatePart.length > 0) {
380✔
575
                        query += ` ${conflictTarget} DO UPDATE SET `
380✔
576

577
                        updatePart.push(
380✔
578
                            ...this.expressionMap
579
                                .mainAlias!.metadata.columns.filter(
580
                                    (column) =>
581
                                        column.isUpdateDate &&
1,587✔
582
                                        !overwrite?.includes(
583
                                            column.databaseName,
584
                                        ) &&
585
                                        !(
586
                                            (this.connection.driver.options
232!
587
                                                .type === "oracle" &&
588
                                                this.getValueSets().length >
589
                                                    1) ||
590
                                            DriverUtils.isSQLiteFamily(
591
                                                this.connection.driver,
592
                                            ) ||
593
                                            this.connection.driver.options
594
                                                .type === "sap" ||
595
                                            this.connection.driver.options
596
                                                .type === "spanner"
597
                                        ),
598
                                )
599
                                .map(
600
                                    (column) =>
601
                                        `${this.escape(
40✔
602
                                            column.databaseName,
603
                                        )} = DEFAULT`,
604
                                ),
605
                        )
606

607
                        query += updatePart.join(", ")
380✔
608
                    }
609

610
                    if (
380✔
611
                        Array.isArray(overwrite) &&
760✔
612
                        skipUpdateIfNoValuesChanged
613
                    ) {
614
                        this.expressionMap.onUpdate.overwriteCondition ??= []
57✔
615
                        const wheres = overwrite.map<WhereClause>((column) => ({
98✔
616
                            type: "or",
617
                            condition: `${tableOrAliasName}.${this.escape(
618
                                column,
619
                            )} IS DISTINCT FROM EXCLUDED.${this.escape(
620
                                column,
621
                            )}`,
622
                        }))
623
                        this.expressionMap.onUpdate.overwriteCondition.push({
57✔
624
                            type: "and",
625
                            condition: wheres,
626
                        })
627
                    }
628
                    if (
380✔
629
                        DriverUtils.isPostgresFamily(this.connection.driver) &&
644✔
630
                        this.expressionMap.onUpdate.overwriteCondition &&
631
                        this.expressionMap.onUpdate.overwriteCondition.length >
632
                            0
633
                    ) {
634
                        query += ` WHERE ${this.createUpsertConditionExpression()}`
67✔
635
                    }
636
                }
637
            } else if (
85,144✔
638
                this.connection.driver.supportedUpsertTypes.includes(
639
                    "on-duplicate-key-update",
640
                )
641
            ) {
642
                if (this.expressionMap.onUpdate) {
32,522✔
643
                    const { overwrite, columns } = this.expressionMap.onUpdate
88✔
644

645
                    if (Array.isArray(overwrite)) {
88!
646
                        query += " ON DUPLICATE KEY UPDATE "
88✔
647
                        query += overwrite
88✔
648
                            .map(
649
                                (column) =>
650
                                    `${this.escape(
184✔
651
                                        column,
652
                                    )} = VALUES(${this.escape(column)})`,
653
                            )
654
                            .join(", ")
655
                        query += " "
88✔
UNCOV
656
                    } else if (Array.isArray(columns)) {
×
UNCOV
657
                        query += " ON DUPLICATE KEY UPDATE "
×
658
                        query += columns
×
659
                            .map(
660
                                (column) =>
UNCOV
661
                                    `${this.escape(column)} = :${column}`,
×
662
                            )
663
                            .join(", ")
UNCOV
664
                        query += " "
×
665
                    }
666
                }
667
            } else {
668
                if (this.expressionMap.onUpdate) {
52,622!
UNCOV
669
                    throw new TypeORMError(
×
670
                        `onUpdate is not supported by the current database driver`,
671
                    )
672
                }
673
            }
674
        }
675

676
        // add RETURNING expression
677
        if (
205,427✔
678
            returningExpression &&
277,390✔
679
            (DriverUtils.isPostgresFamily(this.connection.driver) ||
680
                this.connection.driver.options.type === "oracle" ||
681
                this.connection.driver.options.type === "cockroachdb" ||
682
                DriverUtils.isMySQLFamily(this.connection.driver))
683
        ) {
684
            query += ` RETURNING ${returningExpression}`
27,429✔
685
        }
686

687
        if (
205,427!
688
            returningExpression &&
238,348✔
689
            this.connection.driver.options.type === "spanner"
690
        ) {
UNCOV
691
            query += ` THEN RETURN ${returningExpression}`
×
692
        }
693

694
        // Inserting a specific value for an auto-increment primary key in mssql requires enabling IDENTITY_INSERT
695
        // IDENTITY_INSERT can only be enabled for tables where there is an IDENTITY column and only if there is a value to be inserted (i.e. supplying DEFAULT is prohibited if IDENTITY_INSERT is enabled)
696
        if (
205,427✔
697
            this.connection.driver.options.type === "mssql" &&
235,457✔
698
            this.expressionMap.mainAlias!.hasMetadata &&
699
            this.expressionMap
700
                .mainAlias!.metadata.columns.filter((column) =>
701
                    this.expressionMap.insertColumns.length > 0
49,602!
702
                        ? this.expressionMap.insertColumns.indexOf(
703
                              column.propertyPath,
704
                          ) !== -1
705
                        : column.isInsert,
706
                )
707
                .some((column) =>
708
                    this.isOverridingAutoIncrementBehavior(column),
49,382✔
709
                )
710
        ) {
711
            query = `SET IDENTITY_INSERT ${tableName} ON; ${query}; SET IDENTITY_INSERT ${tableName} OFF`
88✔
712
        }
713

714
        return query
205,427✔
715
    }
716

717
    /**
718
     * Gets list of columns where values must be inserted to.
719
     */
720
    protected getInsertedColumns(): ColumnMetadata[] {
721
        if (!this.expressionMap.mainAlias!.hasMetadata) return []
411,424✔
722

723
        return this.expressionMap.mainAlias!.metadata.columns.filter(
408,368✔
724
            (column) => {
725
                // if user specified list of columns he wants to insert to, then we filter only them
726
                if (this.expressionMap.insertColumns.length)
1,295,364!
UNCOV
727
                    return (
×
728
                        this.expressionMap.insertColumns.indexOf(
729
                            column.propertyPath,
730
                        ) !== -1
731
                    )
732

733
                // skip columns the user doesn't want included by default
734
                if (!column.isInsert) {
1,295,364✔
735
                    return false
576✔
736
                }
737

738
                // if user did not specified such list then return all columns except auto-increment one
739
                // for Oracle we return auto-increment column as well because Oracle does not support DEFAULT VALUES expression
740
                if (
1,294,788✔
741
                    column.isGenerated &&
2,119,882✔
742
                    column.generationStrategy === "increment" &&
743
                    !(this.connection.driver.options.type === "spanner") &&
744
                    !(this.connection.driver.options.type === "oracle") &&
745
                    !DriverUtils.isSQLiteFamily(this.connection.driver) &&
746
                    !DriverUtils.isMySQLFamily(this.connection.driver) &&
747
                    !(this.connection.driver.options.type === "aurora-mysql") &&
748
                    !(
749
                        this.connection.driver.options.type === "mssql" &&
62,224✔
750
                        this.isOverridingAutoIncrementBehavior(column)
751
                    )
752
                )
753
                    return false
51,792✔
754

755
                return true
1,242,996✔
756
            },
757
        )
758
    }
759

760
    /**
761
     * Creates a columns string where values must be inserted to for INSERT INTO expression.
762
     */
763
    protected createColumnNamesExpression(): string {
764
        const columns = this.getInsertedColumns()
205,574✔
765
        if (columns.length > 0)
205,574✔
766
            return columns
203,944✔
767
                .map((column) => this.escape(column.databaseName))
620,928✔
768
                .join(", ")
769

770
        // in the case if there are no insert columns specified and table without metadata used
771
        // we get columns from the inserted value map, in the case if only one inserted map is specified
772
        if (
1,630✔
773
            !this.expressionMap.mainAlias!.hasMetadata &&
3,158✔
774
            !this.expressionMap.insertColumns.length
775
        ) {
776
            const valueSets = this.getValueSets()
1,524✔
777
            if (valueSets.length === 1)
1,524✔
778
                return Object.keys(valueSets[0])
1,520✔
779
                    .map((columnName) => this.escape(columnName))
8,184✔
780
                    .join(", ")
781
        }
782

783
        // get a table name and all column database names
784
        return this.expressionMap.insertColumns
110✔
785
            .map((columnName) => this.escape(columnName))
20✔
786
            .join(", ")
787
    }
788

789
    /**
790
     * Creates list of values needs to be inserted in the VALUES expression.
791
     */
792
    protected createValuesExpression(): string {
793
        const valueSets = this.getValueSets()
205,436✔
794
        const columns = this.getInsertedColumns()
205,436✔
795

796
        // if column metadatas are given then apply all necessary operations with values
797
        if (columns.length > 0) {
205,436✔
798
            let expression = ""
203,806✔
799
            valueSets.forEach((valueSet, valueSetIndex) => {
203,806✔
800
                columns.forEach((column, columnIndex) => {
650,425✔
801
                    if (columnIndex === 0) {
1,803,321✔
802
                        if (
650,425✔
803
                            this.connection.driver.options.type === "oracle" &&
669,083✔
804
                            valueSets.length > 1
805
                        ) {
806
                            expression += " SELECT "
34✔
807
                        } else if (
650,391✔
808
                            this.connection.driver.options.type === "sap" &&
669,107✔
809
                            valueSets.length > 1
810
                        ) {
811
                            expression += " SELECT "
12✔
812
                        } else {
813
                            expression += "("
650,379✔
814
                        }
815
                    }
816

817
                    expression += this.createColumnValueExpression(
1,803,321✔
818
                        valueSets,
819
                        valueSetIndex,
820
                        column,
821
                    )
822

823
                    if (columnIndex === columns.length - 1) {
1,803,321✔
824
                        if (valueSetIndex === valueSets.length - 1) {
650,425✔
825
                            if (
203,806✔
826
                                ["oracle", "sap"].includes(
241,152✔
827
                                    this.connection.driver.options.type,
828
                                ) &&
829
                                valueSets.length > 1
830
                            ) {
831
                                expression +=
18✔
832
                                    " FROM " +
833
                                    this.connection.driver.dummyTableName
834
                            } else {
835
                                expression += ")"
203,788✔
836
                            }
837
                        } else {
838
                            if (
446,619✔
839
                                ["oracle", "sap"].includes(
446,647✔
840
                                    this.connection.driver.options.type,
841
                                ) &&
842
                                valueSets.length > 1
843
                            ) {
844
                                expression +=
28✔
845
                                    " FROM " +
846
                                    this.connection.driver.dummyTableName +
847
                                    " UNION ALL "
848
                            } else {
849
                                expression += "), "
446,591✔
850
                            }
851
                        }
852
                    } else {
853
                        expression += ", "
1,152,896✔
854
                    }
855
                })
856
            })
857
            if (expression === "()") return ""
203,806!
858

859
            return expression
203,806✔
860
        } else {
861
            // for tables without metadata
862
            // get values needs to be inserted
863
            let expression = ""
1,630✔
864

865
            valueSets.forEach((valueSet, insertionIndex) => {
1,630✔
866
                const columns = Object.keys(valueSet)
41,634✔
867
                columns.forEach((columnName, columnIndex) => {
41,634✔
868
                    if (columnIndex === 0) {
208,219✔
869
                        expression += "("
41,555✔
870
                    }
871

872
                    const value = valueSet[columnName]
208,219✔
873

874
                    // support for SQL expressions in queries
875
                    if (typeof value === "function") {
208,219!
UNCOV
876
                        expression += value()
×
877

878
                        // if value for this column was not provided then insert default value
879
                    } else if (value === undefined) {
208,219✔
880
                        if (
1,581✔
881
                            (this.connection.driver.options.type === "oracle" &&
5,598✔
882
                                valueSets.length > 1) ||
883
                            DriverUtils.isSQLiteFamily(
884
                                this.connection.driver,
885
                            ) ||
886
                            this.connection.driver.options.type === "sap" ||
887
                            this.connection.driver.options.type === "spanner"
888
                        ) {
889
                            expression += "NULL"
507✔
890
                        } else {
891
                            expression += "DEFAULT"
1,074✔
892
                        }
893
                    } else if (
206,638!
894
                        value === null &&
206,638!
895
                        this.connection.driver.options.type === "spanner"
896
                    ) {
897
                        // just any other regular value
898
                    } else {
899
                        expression += this.createParameter(value)
206,638✔
900
                    }
901

902
                    if (columnIndex === Object.keys(valueSet).length - 1) {
208,219✔
903
                        if (insertionIndex === valueSets.length - 1) {
41,555✔
904
                            expression += ")"
1,551✔
905
                        } else {
906
                            expression += "), "
40,004✔
907
                        }
908
                    } else {
909
                        expression += ", "
166,664✔
910
                    }
911
                })
912
            })
913
            if (expression === "()") return ""
1,630!
914
            return expression
1,630✔
915
        }
916
    }
917

918
    /**
919
     * Gets array of values need to be inserted into the target table.
920
     */
921
    protected getValueSets(): ObjectLiteral[] {
922
        if (Array.isArray(this.expressionMap.valuesSet))
483,343✔
923
            return this.expressionMap.valuesSet
320,044✔
924

925
        if (ObjectUtils.isObject(this.expressionMap.valuesSet))
163,299✔
926
            return [this.expressionMap.valuesSet]
163,275✔
927

928
        throw new InsertValuesMissingError()
24✔
929
    }
930

931
    /**
932
     * Checks if column is an auto-generated primary key, but the current insertion specifies a value for it.
933
     *
934
     * @param column
935
     */
936
    protected isOverridingAutoIncrementBehavior(
937
        column: ColumnMetadata,
938
    ): boolean {
939
        return (
59,638✔
940
            column.isPrimary &&
119,180✔
941
            column.isGenerated &&
942
            column.generationStrategy === "increment" &&
943
            this.getValueSets().some(
944
                (valueSet) =>
945
                    column.getEntityValue(valueSet) !== undefined &&
20,958✔
946
                    column.getEntityValue(valueSet) !== null,
947
            )
948
        )
949
    }
950

951
    /**
952
     * Creates MERGE express used to perform insert query.
953
     */
954
    protected createMergeExpression() {
955
        if (!this.connection.driver.supportedUpsertTypes.includes("merge-into"))
138!
UNCOV
956
            throw new TypeORMError(
×
957
                `Upsert type "merge-into" is not supported by current database driver`,
958
            )
959

960
        if (
138!
961
            this.expressionMap.onUpdate?.upsertType &&
252✔
962
            this.expressionMap.onUpdate.upsertType !== "merge-into"
963
        ) {
UNCOV
964
            throw new TypeORMError(
×
965
                `Upsert type "${this.expressionMap.onUpdate.upsertType}" is not supported by current database driver`,
966
            )
967
        }
968
        // const mainAlias = this.expressionMap.mainAlias!
969
        const tableName = this.getTableName(this.getMainTableName())
138✔
970
        const tableAlias = this.escape(this.alias)
138✔
971
        const columns = this.getInsertedColumns()
138✔
972
        const columnsExpression = this.createColumnNamesExpression()
138✔
973

974
        let query = `MERGE INTO ${tableName} ${this.escape(this.alias)}`
138✔
975

976
        const mergeSourceAlias = this.escape("mergeIntoSource")
138✔
977

978
        const mergeSourceExpression =
979
            this.createMergeIntoSourceExpression(mergeSourceAlias)
138✔
980

981
        query += ` ${mergeSourceExpression}`
138✔
982

983
        // build on condition
984
        if (this.expressionMap.onIgnore) {
138✔
985
            const primaryKey = columns.find((column) => column.isPrimary)
6✔
986
            if (primaryKey) {
6!
987
                query += ` ON (${tableAlias}.${this.escape(
6✔
988
                    primaryKey.databaseName,
989
                )} = ${mergeSourceAlias}.${this.escape(
990
                    primaryKey.databaseName,
991
                )})`
992
            } else {
UNCOV
993
                query += `ON (${this.expressionMap
×
994
                    .mainAlias!.metadata.uniques.map((unique) => {
995
                        return `(${unique.columns
×
996
                            .map((column) => {
997
                                return `${tableAlias}.${this.escape(
×
998
                                    column.databaseName,
999
                                )} = ${mergeSourceAlias}.${this.escape(
1000
                                    column.databaseName,
1001
                                )}`
1002
                            })
1003
                            .join(" AND ")})`
1004
                    })
1005
                    .join(" OR ")})`
1006
            }
1007
        } else if (this.expressionMap.onUpdate) {
132✔
1008
            const { conflict, indexPredicate } = this.expressionMap.onUpdate
132✔
1009

1010
            if (indexPredicate) {
132!
UNCOV
1011
                throw new TypeORMError(
×
1012
                    `indexPredicate option is not supported by upsert type "merge-into"`,
1013
                )
1014
            }
1015

1016
            if (Array.isArray(conflict)) {
132!
1017
                query += ` ON (${conflict
132✔
1018
                    .map(
1019
                        (column) =>
1020
                            `${tableAlias}.${this.escape(
132✔
1021
                                column,
1022
                            )} = ${mergeSourceAlias}.${this.escape(column)}`,
1023
                    )
1024
                    .join(" AND ")})`
UNCOV
1025
            } else if (conflict) {
×
UNCOV
1026
                query += ` ON (${tableAlias}.${this.escape(
×
1027
                    conflict,
1028
                )} = ${mergeSourceAlias}.${this.escape(conflict)})`
1029
            } else {
UNCOV
1030
                query += `ON (${this.expressionMap
×
1031
                    .mainAlias!.metadata.uniques.map((unique) => {
1032
                        return `(${unique.columns
×
1033
                            .map((column) => {
1034
                                return `${tableAlias}.${this.escape(
×
1035
                                    column.databaseName,
1036
                                )} = ${mergeSourceAlias}.${this.escape(
1037
                                    column.databaseName,
1038
                                )}`
1039
                            })
1040
                            .join(" AND ")})`
1041
                    })
1042
                    .join(" OR ")})`
1043
            }
1044
        }
1045

1046
        if (this.expressionMap.onUpdate) {
138✔
1047
            const {
1048
                overwrite,
1049
                columns,
1050
                conflict,
1051
                skipUpdateIfNoValuesChanged,
1052
            } = this.expressionMap.onUpdate
132✔
1053
            let updateExpression = ""
132✔
1054

1055
            if (Array.isArray(overwrite)) {
132✔
1056
                updateExpression += (overwrite || columns)
132!
1057
                    ?.filter((column) => !conflict?.includes(column))
276✔
1058
                    .map(
1059
                        (column) =>
1060
                            `${tableAlias}.${this.escape(
162✔
1061
                                column,
1062
                            )} = ${mergeSourceAlias}.${this.escape(column)}`,
1063
                    )
1064
                    .join(", ")
1065
            }
1066

1067
            if (Array.isArray(overwrite) && skipUpdateIfNoValuesChanged) {
132✔
1068
                this.expressionMap.onUpdate.overwriteCondition ??= []
6✔
1069
                const wheres = overwrite.map<WhereClause>((column) => ({
6✔
1070
                    type: "or",
1071
                    condition: {
1072
                        operator: "notEqual",
1073
                        parameters: [
1074
                            `${tableAlias}.${this.escape(column)}`,
1075
                            `${mergeSourceAlias}.${this.escape(column)}`,
1076
                        ],
1077
                    },
1078
                }))
1079
                this.expressionMap.onUpdate.overwriteCondition.push({
6✔
1080
                    type: "and",
1081
                    condition: wheres,
1082
                })
1083
            }
1084
            const mergeCondition = this.createUpsertConditionExpression()
132✔
1085
            if (updateExpression.trim()) {
132✔
1086
                if (
126✔
1087
                    (this.connection.driver.options.type === "mssql" ||
294✔
1088
                        this.connection.driver.options.type === "sap") &&
1089
                    mergeCondition != ""
1090
                ) {
1091
                    query += ` WHEN MATCHED AND ${mergeCondition} THEN UPDATE SET ${updateExpression}`
8✔
1092
                } else {
1093
                    query += ` WHEN MATCHED THEN UPDATE SET ${updateExpression}`
118✔
1094
                    if (mergeCondition != "") {
118✔
1095
                        query += ` WHERE ${mergeCondition}`
4✔
1096
                    }
1097
                }
1098
            }
1099
        }
1100

1101
        const valuesExpression =
1102
            this.createMergeIntoInsertValuesExpression(mergeSourceAlias)
138✔
1103
        const returningExpression =
1104
            this.connection.driver.options.type === "mssql"
138✔
1105
                ? this.createReturningExpression("insert")
1106
                : null
1107

1108
        query += " WHEN NOT MATCHED THEN INSERT"
138✔
1109

1110
        // add columns expression
1111
        if (columnsExpression) {
138✔
1112
            query += `(${columnsExpression})`
138✔
1113
        }
1114

1115
        // add VALUES expression
1116
        if (valuesExpression) {
138✔
1117
            query += ` VALUES ${valuesExpression}`
138✔
1118
        }
1119

1120
        // add OUTPUT expression
1121
        if (
138✔
1122
            returningExpression &&
162✔
1123
            this.connection.driver.options.type === "mssql"
1124
        ) {
1125
            query += ` OUTPUT ${returningExpression}`
24✔
1126
        }
1127
        if (this.connection.driver.options.type === "mssql") {
138✔
1128
            query += `;`
46✔
1129
        }
1130
        return query
138✔
1131
    }
1132

1133
    /**
1134
     * Creates list of values needs to be inserted in the VALUES expression.
1135
     */
1136
    protected createMergeIntoSourceExpression(
1137
        mergeSourceAlias: string,
1138
    ): string {
1139
        const valueSets = this.getValueSets()
138✔
1140
        const columns = this.getInsertedColumns()
138✔
1141

1142
        let expression = "USING ("
138✔
1143
        // if column metadatas are given then apply all necessary operations with values
1144
        if (columns.length > 0) {
138!
1145
            if (this.connection.driver.options.type === "mssql") {
138✔
1146
                expression += "VALUES "
46✔
1147
            }
1148
            valueSets.forEach((valueSet, valueSetIndex) => {
138✔
1149
                columns.forEach((column, columnIndex) => {
198✔
1150
                    if (columnIndex === 0) {
798✔
1151
                        if (this.connection.driver.options.type === "mssql") {
198✔
1152
                            expression += "("
66✔
1153
                        } else {
1154
                            expression += "SELECT "
132✔
1155
                        }
1156
                    }
1157

1158
                    const value = column.getEntityValue(valueSet)
798✔
1159

1160
                    if (
798✔
1161
                        value === undefined &&
1,122✔
1162
                        !(
1163
                            column.isGenerated &&
408✔
1164
                            column.generationStrategy === "uuid" &&
1165
                            !this.connection.driver.isUUIDGenerationSupported()
1166
                        )
1167
                    ) {
1168
                        if (
300✔
1169
                            column.default !== undefined &&
360✔
1170
                            column.default !== null
1171
                        ) {
1172
                            // try to use default defined in the column
1173
                            expression +=
60✔
1174
                                this.connection.driver.normalizeDefault(column)
1175
                        } else {
1176
                            expression += "NULL" // otherwise simply use NULL and pray if column is nullable
240✔
1177
                        }
1178
                    } else if (value === null) {
498!
UNCOV
1179
                        expression += "NULL"
×
1180
                    } else {
1181
                        expression += this.createColumnValueExpression(
498✔
1182
                            valueSets,
1183
                            valueSetIndex,
1184
                            column,
1185
                        )
1186
                    }
1187

1188
                    if (this.connection.driver.options.type !== "mssql")
798✔
1189
                        expression += ` AS ${this.escape(column.databaseName)}`
536✔
1190

1191
                    if (columnIndex === columns.length - 1) {
798✔
1192
                        if (valueSetIndex === valueSets.length - 1) {
198✔
1193
                            if (
138✔
1194
                                ["oracle", "sap"].includes(
1195
                                    this.connection.driver.options.type,
1196
                                )
1197
                            ) {
1198
                                expression +=
92✔
1199
                                    " FROM " +
1200
                                    this.connection.driver.dummyTableName
1201
                            } else if (
46✔
1202
                                this.connection.driver.options.type === "mssql"
1203
                            ) {
1204
                                expression += ")"
46✔
1205
                            }
1206
                        } else {
1207
                            if (
60✔
1208
                                ["oracle", "sap"].includes(
100✔
1209
                                    this.connection.driver.options.type,
1210
                                ) &&
1211
                                valueSets.length > 1
1212
                            ) {
1213
                                expression +=
40✔
1214
                                    " FROM " +
1215
                                    this.connection.driver.dummyTableName +
1216
                                    " UNION ALL "
1217
                            } else if (
20!
1218
                                this.connection.driver.options.type === "mssql"
1219
                            ) {
1220
                                expression += "), "
20✔
1221
                            } else {
UNCOV
1222
                                expression += " UNION ALL "
×
1223
                            }
1224
                        }
1225
                    } else {
1226
                        expression += ", "
600✔
1227
                    }
1228
                })
1229
            })
1230
        } else {
1231
            // for tables without metadata
UNCOV
1232
            throw new TypeORMError(
×
1233
                'Upsert type "merge-into" is not supported without metadata tables',
1234
            )
1235
        }
1236
        expression += `) ${mergeSourceAlias}`
138✔
1237
        if (this.connection.driver.options.type === "mssql")
138✔
1238
            expression += ` (${columns
46✔
1239
                .map((column) => this.escape(column.databaseName))
186✔
1240
                .join(", ")})`
1241
        return expression
138✔
1242
    }
1243

1244
    /**
1245
     * Creates list of values needs to be inserted in the VALUES expression.
1246
     */
1247
    protected createMergeIntoInsertValuesExpression(
1248
        mergeSourceAlias: string,
1249
    ): string {
1250
        const columns = this.getInsertedColumns()
138✔
1251

1252
        let expression = ""
138✔
1253
        // if column metadatas are given then apply all necessary operations with values
1254
        if (columns.length > 0) {
138!
1255
            columns.forEach((column, columnIndex) => {
138✔
1256
                if (columnIndex === 0) {
570✔
1257
                    expression += "("
138✔
1258
                }
1259

1260
                if (
570✔
1261
                    (column.isGenerated &&
1,220✔
1262
                        column.generationStrategy === "uuid" &&
1263
                        this.connection.driver.isUUIDGenerationSupported()) ||
1264
                    (column.isGenerated && column.generationStrategy !== "uuid")
1265
                ) {
1266
                    expression += `DEFAULT`
20✔
1267
                } else {
1268
                    expression += `${mergeSourceAlias}.${this.escape(
550✔
1269
                        column.databaseName,
1270
                    )}`
1271
                }
1272

1273
                if (columnIndex === columns.length - 1) {
570✔
1274
                    expression += ")"
138✔
1275
                } else {
1276
                    expression += ", "
432✔
1277
                }
1278
            })
1279
        } else {
1280
            // for tables without metadata
UNCOV
1281
            throw new TypeORMError(
×
1282
                'Upsert type "merge-into" is not supported without metadata tables',
1283
            )
1284
        }
1285
        if (expression === "()") return ""
138!
1286
        return expression
138✔
1287
    }
1288

1289
    /**
1290
     * Create upsert search condition expression.
1291
     */
1292
    protected createUpsertConditionExpression() {
1293
        if (!this.expressionMap.onUpdate.overwriteCondition) return ""
199✔
1294
        const conditionsArray = []
79✔
1295

1296
        const whereExpression = this.createWhereClausesExpression(
79✔
1297
            this.expressionMap.onUpdate.overwriteCondition,
1298
        )
1299

1300
        if (whereExpression.length > 0 && whereExpression !== "1=1") {
79✔
1301
            conditionsArray.push(whereExpression)
79✔
1302
        }
1303

1304
        if (this.expressionMap.mainAlias!.hasMetadata) {
79✔
1305
            const metadata = this.expressionMap.mainAlias!.metadata
79✔
1306
            // Adds the global condition of "non-deleted" for the entity with delete date columns in select query.
1307
            if (
79!
1308
                this.expressionMap.queryType === "select" &&
79!
1309
                !this.expressionMap.withDeleted &&
1310
                metadata.deleteDateColumn
1311
            ) {
UNCOV
1312
                const column = this.expressionMap.aliasNamePrefixingEnabled
×
1313
                    ? this.expressionMap.mainAlias!.name +
1314
                      "." +
1315
                      metadata.deleteDateColumn.propertyName
1316
                    : metadata.deleteDateColumn.propertyName
1317

UNCOV
1318
                const condition = `${column} IS NULL`
×
UNCOV
1319
                conditionsArray.push(condition)
×
1320
            }
1321

1322
            if (metadata.discriminatorColumn && metadata.parentEntityMetadata) {
79!
UNCOV
1323
                const column = this.expressionMap.aliasNamePrefixingEnabled
×
1324
                    ? this.expressionMap.mainAlias!.name +
1325
                      "." +
1326
                      metadata.discriminatorColumn.databaseName
1327
                    : metadata.discriminatorColumn.databaseName
1328

UNCOV
1329
                const condition = `${column} IN (:...discriminatorColumnValues)`
×
UNCOV
1330
                conditionsArray.push(condition)
×
1331
            }
1332
        }
1333

1334
        if (this.expressionMap.extraAppendedAndWhereCondition) {
79!
UNCOV
1335
            const condition = this.expressionMap.extraAppendedAndWhereCondition
×
UNCOV
1336
            conditionsArray.push(condition)
×
1337
        }
1338

1339
        let condition = ""
79✔
1340

1341
        if (!conditionsArray.length) {
79!
UNCOV
1342
            condition += ""
×
1343
        } else if (conditionsArray.length === 1) {
79!
1344
            condition += `${conditionsArray[0]}`
79✔
1345
        } else {
UNCOV
1346
            condition += `( ${conditionsArray.join(" ) AND ( ")} )`
×
1347
        }
1348

1349
        return condition
79✔
1350
    }
1351

1352
    protected createColumnValueExpression(
1353
        valueSets: ObjectLiteral[],
1354
        valueSetIndex: number,
1355
        column: ColumnMetadata,
1356
    ): string {
1357
        const valueSet = valueSets[valueSetIndex]
1,803,819✔
1358
        let expression = ""
1,803,819✔
1359

1360
        // extract real value from the entity
1361
        let value = column.getEntityValue(valueSet)
1,803,819✔
1362

1363
        // if column is relational and value is an object then get real referenced column value from this object
1364
        // for example column value is { question: { id: 1 } }, value will be equal to { id: 1 }
1365
        // and we extract "1" from this object
1366
        /*if (column.referencedColumn && value instanceof Object && !(typeof value === "function")) { // todo: check if we still need it since getEntityValue already has similar code
1367
            value = column.referencedColumn.getEntityValue(value);
1368
        }*/
1369

1370
        if (!(typeof value === "function")) {
1,803,819✔
1371
            // make sure our value is normalized by a driver
1372
            value = this.connection.driver.preparePersistentValue(value, column)
1,803,693✔
1373
        }
1374

1375
        // newly inserted entities always have a version equal to 1 (first version)
1376
        // also, user-specified version must be empty
1377
        if (column.isVersion && value === undefined) {
1,803,819✔
1378
            expression += "1"
542✔
1379

1380
            // } else if (column.isNestedSetLeft) {
1381
            //     const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
1382
            //     const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
1383
            //     const subQuery = `(SELECT c.max + 1 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
1384
            //     expression += subQuery;
1385
            //
1386
            // } else if (column.isNestedSetRight) {
1387
            //     const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
1388
            //     const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
1389
            //     const subQuery = `(SELECT c.max + 2 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
1390
            //     expression += subQuery;
1391
        } else if (column.isDiscriminator) {
1,803,277✔
1392
            expression += this.createParameter(
1,343✔
1393
                this.expressionMap.mainAlias!.metadata.discriminatorValue,
1394
            )
1395
            // return "1";
1396

1397
            // for create and update dates we insert current date
1398
            // no, we don't do it because this constant is already in "default" value of the column
1399
            // with extended timestamp functionality, like CURRENT_TIMESTAMP(6) for example
1400
            // } else if (column.isCreateDate || column.isUpdateDate) {
1401
            //     return "CURRENT_TIMESTAMP";
1402

1403
            // if column is generated uuid and database does not support its generation and custom generated value was not provided by a user - we generate a new uuid value for insertion
1404
        } else if (
1,801,934✔
1405
            column.isGenerated &&
2,067,283✔
1406
            column.generationStrategy === "uuid" &&
1407
            !this.connection.driver.isUUIDGenerationSupported() &&
1408
            value === undefined
1409
        ) {
1410
            value = uuidv4()
1,146✔
1411
            expression += this.createParameter(value)
1,146✔
1412

1413
            if (!(valueSetIndex in this.expressionMap.locallyGenerated)) {
1,146✔
1414
                this.expressionMap.locallyGenerated[valueSetIndex] = {}
1,112✔
1415
            }
1416
            column.setEntityValue(
1,146✔
1417
                this.expressionMap.locallyGenerated[valueSetIndex],
1418
                value,
1419
            )
1420

1421
            // if value for this column was not provided then insert default value
1422
        } else if (value === undefined) {
1,800,788✔
1423
            if (
312,192✔
1424
                (this.connection.driver.options.type === "oracle" &&
1,148,202✔
1425
                    valueSets.length > 1) ||
1426
                DriverUtils.isSQLiteFamily(this.connection.driver) ||
1427
                this.connection.driver.options.type === "sap" ||
1428
                this.connection.driver.options.type === "spanner"
1429
            ) {
1430
                // unfortunately sqlite does not support DEFAULT expression in INSERT queries
1431
                if (column.default !== undefined && column.default !== null) {
57,221✔
1432
                    // try to use default defined in the column
1433
                    expression +=
4,062✔
1434
                        this.connection.driver.normalizeDefault(column)
1435
                } else if (
53,159!
1436
                    this.connection.driver.options.type === "spanner" &&
53,159!
1437
                    column.isGenerated &&
1438
                    column.generationStrategy === "uuid"
1439
                ) {
UNCOV
1440
                    expression += "GENERATE_UUID()" // Produces a random universally unique identifier (UUID) as a STRING value.
×
1441
                } else {
1442
                    expression += "NULL" // otherwise simply use NULL and pray if column is nullable
53,159✔
1443
                }
1444
            } else {
1445
                expression += "DEFAULT"
254,971✔
1446
            }
1447
        } else if (
1,488,596✔
1448
            value === null &&
1,490,588✔
1449
            (this.connection.driver.options.type === "spanner" ||
1450
                this.connection.driver.options.type === "oracle")
1451
        ) {
1452
            expression += "NULL"
78✔
1453

1454
            // support for SQL expressions in queries
1455
        } else if (typeof value === "function") {
1,488,518✔
1456
            expression += value()
132✔
1457

1458
            // just any other regular value
1459
        } else {
1460
            if (this.connection.driver.options.type === "mssql")
1,488,386✔
1461
                value = (
48,364✔
1462
                    this.connection.driver as SqlServerDriver
1463
                ).parametrizeValue(column, value)
1464

1465
            // we need to store array values in a special class to make sure parameter replacement will work correctly
1466
            // if (value instanceof Array)
1467
            //     value = new ArrayParameter(value);
1468

1469
            const paramName = this.createParameter(value)
1,488,386✔
1470

1471
            if (
1,488,386✔
1472
                (DriverUtils.isMySQLFamily(this.connection.driver) ||
2,976,772✔
1473
                    this.connection.driver.options.type === "aurora-mysql") &&
1474
                this.connection.driver.spatialTypes.includes(column.type)
1475
            ) {
1476
                const useLegacy = (
1477
                    this.connection.driver as MysqlDriver | AuroraMysqlDriver
32✔
1478
                ).options.legacySpatialSupport
1479
                const geomFromText = useLegacy
32✔
1480
                    ? "GeomFromText"
1481
                    : "ST_GeomFromText"
1482
                if (column.srid != null) {
32✔
1483
                    expression += `${geomFromText}(${paramName}, ${column.srid})`
16✔
1484
                } else {
1485
                    expression += `${geomFromText}(${paramName})`
16✔
1486
                }
1487
            } else if (
1,488,354✔
1488
                DriverUtils.isPostgresFamily(this.connection.driver) &&
2,258,558✔
1489
                this.connection.driver.spatialTypes.includes(column.type)
1490
            ) {
1491
                if (column.srid != null) {
44!
UNCOV
1492
                    expression += `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`
×
1493
                } else {
1494
                    expression += `ST_GeomFromGeoJSON(${paramName})::${column.type}`
44✔
1495
                }
1496
            } else if (
1,488,310✔
1497
                this.connection.driver.options.type === "mssql" &&
1,536,674✔
1498
                this.connection.driver.spatialTypes.includes(column.type)
1499
            ) {
1500
                expression +=
14✔
1501
                    column.type +
1502
                    "::STGeomFromText(" +
1503
                    paramName +
1504
                    ", " +
1505
                    (column.srid || "0") +
24✔
1506
                    ")"
1507
            } else {
1508
                expression += paramName
1,488,296✔
1509
            }
1510
        }
1511
        return expression
1,803,819✔
1512
    }
1513
}
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