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

typeorm / typeorm / 15460340712

05 Jun 2025 06:45AM UTC coverage: 76.379% (+0.06%) from 76.315%
15460340712

push

github

web-flow
feat: add upsert support for Oracle, SQLServer and SAP HANA (#10974)

9285 of 12864 branches covered (72.18%)

Branch coverage included in aggregate %.

178 of 205 new or added lines in 4 files covered. (86.83%)

1 existing line in 1 file now uncovered.

18996 of 24163 relevant lines covered (78.62%)

196784.51 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"
40✔
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"
40✔
6
import { MysqlDriver } from "../driver/mysql/MysqlDriver"
7
import { SqlServerDriver } from "../driver/sqlserver/SqlServerDriver"
8
import { TypeORMError } from "../error"
40✔
9
import { InsertValuesMissingError } from "../error/InsertValuesMissingError"
40✔
10
import { ReturningStatementNotSupportedError } from "../error/ReturningStatementNotSupportedError"
40✔
11
import { ColumnMetadata } from "../metadata/ColumnMetadata"
12
import { BroadcasterResult } from "../subscriber/BroadcasterResult"
40✔
13
import { InstanceChecker } from "../util/InstanceChecker"
40✔
14
import { ObjectUtils } from "../util/ObjectUtils"
40✔
15
import { InsertOrUpdateOptions } from "./InsertOrUpdateOptions"
16
import { QueryBuilder } from "./QueryBuilder"
40✔
17
import { QueryDeepPartialEntity } from "./QueryPartialEntity"
18
import { InsertResult } from "./result/InsertResult"
40✔
19
import { ReturningResultsEntityUpdator } from "./ReturningResultsEntityUpdator"
40✔
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<
40✔
26
    Entity extends ObjectLiteral,
27
> extends QueryBuilder<Entity> {
28
    readonly "@instanceof" = Symbol.for("InsertQueryBuilder")
328,520✔
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()
328,445✔
39
        sql += this.createCteExpression()
328,445✔
40
        sql += this.createInsertExpression()
328,445✔
41
        return this.replacePropertyNamesForTheWholeQuery(sql.trim())
328,433✔
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()
326,655✔
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()
326,615✔
60

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

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

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

78
            // call before insertion methods in listeners and subscribers
79
            if (
326,535✔
80
                this.expressionMap.callListeners === true &&
330,489✔
81
                this.expressionMap.mainAlias!.hasMetadata
82
            ) {
83
                const broadcastResult = new BroadcasterResult()
2,963✔
84
                valueSets.forEach((valueSet) => {
2,963✔
85
                    queryRunner.broadcaster.broadcastBeforeInsertEvent(
304,190✔
86
                        broadcastResult,
87
                        this.expressionMap.mainAlias!.metadata,
88
                        valueSet,
89
                    )
90
                })
91
                await broadcastResult.wait()
2,963✔
92
            }
93

94
            let declareSql: string | null = null
326,535✔
95
            let selectOutputSql: string | null = null
326,535✔
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(
326,535✔
101
                    queryRunner,
102
                    this.expressionMap,
103
                )
104

105
            const returningColumns: ColumnMetadata[] = []
326,535✔
106

107
            if (
326,535✔
108
                Array.isArray(this.expressionMap.returning) &&
326,547✔
109
                this.expressionMap.mainAlias!.hasMetadata
110
            ) {
111
                for (const columnPath of this.expressionMap.returning) {
12✔
112
                    returningColumns.push(
15✔
113
                        ...this.expressionMap.mainAlias!.metadata.findColumnsWithPropertyPath(
114
                            columnPath,
115
                        ),
116
                    )
117
                }
118
            }
119

120
            if (
326,535✔
121
                this.expressionMap.updateEntity === true &&
647,451✔
122
                this.expressionMap.mainAlias!.hasMetadata
123
            ) {
124
                if (
319,925✔
125
                    !(
126
                        valueSets.length > 1 &&
344,463✔
127
                        this.connection.driver.options.type === "oracle"
128
                    )
129
                ) {
130
                    this.expressionMap.extraReturningColumns =
319,903✔
131
                        this.expressionMap.mainAlias!.metadata.getInsertionReturningColumns()
132
                }
133

134
                returningColumns.push(
319,925✔
135
                    ...this.expressionMap.extraReturningColumns.filter(
136
                        (c) => !returningColumns.includes(c),
159,887✔
137
                    ),
138
                )
139
            }
140

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

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

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

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

166
            const insertResult = InsertResult.from(queryResult)
326,397✔
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 (
326,397✔
172
                this.expressionMap.updateEntity === true &&
647,175✔
173
                this.expressionMap.mainAlias!.hasMetadata
174
            ) {
175
                // console.time(".updating entity");
176
                await returningResultsEntityUpdator.insert(
319,793✔
177
                    insertResult,
178
                    valueSets,
179
                )
180
                // console.timeEnd(".updating entity");
181
            }
182

183
            // call after insertion methods in listeners and subscribers
184
            if (
326,397✔
185
                this.expressionMap.callListeners === true &&
330,316✔
186
                this.expressionMap.mainAlias!.hasMetadata
187
            ) {
188
                const broadcastResult = new BroadcasterResult()
2,934✔
189
                valueSets.forEach((valueSet) => {
2,934✔
190
                    queryRunner.broadcaster.broadcastAfterInsertEvent(
304,159✔
191
                        broadcastResult,
192
                        this.expressionMap.mainAlias!.metadata,
193
                        valueSet,
194
                    )
195
                })
196
                await broadcastResult.wait()
2,934✔
197
            }
198

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

206
            return insertResult
326,397✔
207
        } catch (error) {
208
            // rollback transaction if we started it
209
            if (transactionStartedByUs) {
138!
210
                try {
×
211
                    await queryRunner.rollbackTransaction()
×
212
                } catch (rollbackError) {}
213
            }
214
            throw error
138✔
215
        } finally {
216
            // console.time(".releasing connection");
217
            if (queryRunner !== this.queryRunner) {
326,535✔
218
                // means we created our own query runner
219
                await queryRunner.release()
2,969✔
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)
328,287!
238
            ? entityTarget.options.name
239
            : entityTarget
240
        const mainAlias = this.createFromAlias(entityTarget)
328,287✔
241
        this.expressionMap.setMainAlias(mainAlias)
328,287✔
242
        this.expressionMap.insertColumns = columns || []
328,287✔
243
        return this as any as InsertQueryBuilder<T>
328,287✔
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
328,480✔
255
        return this
328,480✔
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 {
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")) {
51!
305
            throw new ReturningStatementNotSupportedError()
×
306
        }
307

308
        this.expressionMap.returning = returning
51✔
309
        return this
51✔
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
322,701✔
319
        return this
322,701✔
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
52✔
329
        return this
52✔
330
    }
331

332
    /**
333
     * Adds additional ignore statement supported in databases.
334
     */
335
    orIgnore(statement: string | boolean = true): this {
33✔
336
        this.expressionMap.onIgnore = !!statement
63✔
337
        return this
63✔
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 ?? {}
968✔
379
        let wheres: WhereClause[] | undefined
380
        if (where) {
968✔
381
            const condition = this.getWhereCondition(where)
35✔
382
            if (Array.isArray(condition) ? condition.length !== 0 : condition)
35!
383
                wheres = [{ type: "simple", condition: condition }]
35✔
384
        }
385
        if (parameters) this.setParameters(parameters)
968!
386

387
        if (!Array.isArray(statementOrOverwrite)) {
968!
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
            }
397
            return this
×
398
        }
399

400
        this.expressionMap.onUpdate = {
968✔
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
968✔
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) {
328,445✔
421
            if (
1,049✔
422
                (this.expressionMap.onUpdate?.upsertType ?? "merge-into") ===
2,803✔
423
                    "merge-into" &&
424
                this.connection.driver.supportedUpsertTypes.includes(
425
                    "merge-into",
426
                )
427
            )
428
                return this.createMergeExpression()
299✔
429
        }
430
        const tableName = this.getTableName(this.getMainTableName())
328,146✔
431
        const tableOrAliasName =
432
            this.alias !== this.getMainTableName()
328,146✔
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
328,146✔
436
        const returningExpression =
437
            this.connection.driver.options.type === "oracle" &&
328,146✔
438
            this.getValueSets().length > 1
439
                ? null
440
                : this.createReturningExpression("insert") // oracle doesnt support returning with multi-row insert
441
        const columnsExpression = this.createColumnNamesExpression()
328,146✔
442
        let query = "INSERT "
328,146✔
443

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

448
        if (
328,146✔
449
            DriverUtils.isMySQLFamily(this.connection.driver) ||
608,151✔
450
            this.connection.driver.options.type === "aurora-mysql"
451
        ) {
452
            query += `${this.expressionMap.onIgnore ? " IGNORE " : ""}`
48,141✔
453
        }
454

455
        query += `INTO ${tableName}`
328,146✔
456

457
        if (
328,146✔
458
            this.alias !== this.getMainTableName() &&
328,228✔
459
            DriverUtils.isPostgresFamily(this.connection.driver)
460
        ) {
461
            query += ` AS "${this.alias}"`
42✔
462
        }
463

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

477
        // add OUTPUT expression
478
        if (
328,146✔
479
            returningExpression &&
392,518✔
480
            this.connection.driver.options.type === "mssql"
481
        ) {
482
            query += ` OUTPUT ${returningExpression}`
23,985✔
483
        }
484

485
        // add VALUES expression
486
        if (valuesExpression) {
328,146✔
487
            if (
327,984✔
488
                (this.connection.driver.options.type === "oracle" ||
674,464✔
489
                    this.connection.driver.options.type === "sap") &&
490
                this.getValueSets().length > 1
491
            ) {
492
                query += ` ${valuesExpression}`
18✔
493
            } else {
494
                query += ` VALUES ${valuesExpression}`
327,966✔
495
            }
496
        } else {
497
            if (
162!
498
                DriverUtils.isMySQLFamily(this.connection.driver) ||
324✔
499
                this.connection.driver.options.type === "aurora-mysql"
500
            ) {
501
                // special syntax for mysql DEFAULT VALUES insertion
502
                query += " VALUES ()"
×
503
            } else {
504
                query += ` DEFAULT VALUES`
162✔
505
            }
506
        }
507
        if (this.expressionMap.onUpdate?.upsertType !== "primary-key") {
328,146✔
508
            if (
328,146✔
509
                this.connection.driver.supportedUpsertTypes.includes(
510
                    "on-conflict-do-update",
511
                )
512
            ) {
513
                if (this.expressionMap.onIgnore) {
175,852✔
514
                    query += " ON CONFLICT DO NOTHING "
38✔
515
                } else if (this.expressionMap.onConflict) {
175,814✔
516
                    query += ` ON CONFLICT ${this.expressionMap.onConflict} `
52✔
517
                } else if (this.expressionMap.onUpdate) {
175,762✔
518
                    const {
519
                        overwrite,
520
                        columns,
521
                        conflict,
522
                        skipUpdateIfNoValuesChanged,
523
                        indexPredicate,
524
                    } = this.expressionMap.onUpdate
568✔
525

526
                    let conflictTarget = "ON CONFLICT"
568✔
527

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

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

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

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

577
                        updatePart.push(
556✔
578
                            ...this.expressionMap
579
                                .mainAlias!.metadata.columns.filter(
580
                                    (column) =>
581
                                        column.isUpdateDate &&
2,358✔
582
                                        !overwrite?.includes(
583
                                            column.databaseName,
584
                                        ) &&
585
                                        !(
586
                                            (this.connection.driver.options
360!
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(
66✔
602
                                            column.databaseName,
603
                                        )} = DEFAULT`,
604
                                ),
605
                        )
606

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

610
                    if (
556✔
611
                        Array.isArray(overwrite) &&
1,112✔
612
                        skipUpdateIfNoValuesChanged
613
                    ) {
614
                        this.expressionMap.onUpdate.overwriteCondition ??= []
66✔
615
                        const wheres = overwrite.map<WhereClause>((column) => ({
120✔
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({
66✔
624
                            type: "and",
625
                            condition: wheres,
626
                        })
627
                    }
628
                    if (
556✔
629
                        DriverUtils.isPostgresFamily(this.connection.driver) &&
952✔
630
                        this.expressionMap.onUpdate.overwriteCondition &&
631
                        this.expressionMap.onUpdate.overwriteCondition.length >
632
                            0
633
                    ) {
634
                        query += ` WHERE ${this.createUpsertConditionExpression()}`
84✔
635
                    }
636
                }
637
            } else if (
152,294✔
638
                this.connection.driver.supportedUpsertTypes.includes(
639
                    "on-duplicate-key-update",
640
                )
641
            ) {
642
                if (this.expressionMap.onUpdate) {
48,141✔
643
                    const { overwrite, columns } = this.expressionMap.onUpdate
132✔
644

645
                    if (Array.isArray(overwrite)) {
132!
646
                        query += " ON DUPLICATE KEY UPDATE "
132✔
647
                        query += overwrite
132✔
648
                            .map(
649
                                (column) =>
650
                                    `${this.escape(
276✔
651
                                        column,
652
                                    )} = VALUES(${this.escape(column)})`,
653
                            )
654
                            .join(", ")
655
                        query += " "
132✔
656
                    } else if (Array.isArray(columns)) {
×
657
                        query += " ON DUPLICATE KEY UPDATE "
×
658
                        query += columns
×
659
                            .map(
660
                                (column) =>
661
                                    `${this.escape(column)} = :${column}`,
×
662
                            )
663
                            .join(", ")
664
                        query += " "
×
665
                    }
666
                }
667
            } else {
668
                if (this.expressionMap.onUpdate) {
104,153!
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 (
328,134✔
678
            returningExpression &&
493,880✔
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}`
40,387✔
685
        }
686

687
        if (
328,134!
688
            returningExpression &&
392,506✔
689
            this.connection.driver.options.type === "spanner"
690
        ) {
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 (
328,134✔
697
            this.connection.driver.options.type === "mssql" &&
461,784✔
698
            this.expressionMap.mainAlias!.hasMetadata &&
699
            this.expressionMap
700
                .mainAlias!.metadata.columns.filter((column) =>
701
                    this.expressionMap.insertColumns.length > 0
221,310!
702
                        ? this.expressionMap.insertColumns.indexOf(
703
                              column.propertyPath,
704
                          ) !== -1
705
                        : column.isInsert,
706
                )
707
                .some((column) =>
708
                    this.isOverridingAutoIncrementBehavior(column),
220,320✔
709
                )
710
        ) {
711
            query = `SET IDENTITY_INSERT ${tableName} ON; ${query}; SET IDENTITY_INSERT ${tableName} OFF`
396✔
712
        }
713

714
        return query
328,134✔
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 []
657,488✔
722

723
        return this.expressionMap.mainAlias!.metadata.columns.filter(
652,010✔
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)
2,095,322!
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) {
2,095,322✔
735
                    return false
960✔
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 (
2,094,362✔
741
                    column.isGenerated &&
3,440,772✔
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" &&
150,686✔
750
                        this.isOverridingAutoIncrementBehavior(column)
751
                    )
752
                )
753
                    return false
105,200✔
754

755
                return true
1,989,162✔
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()
328,445✔
765
        if (columns.length > 0)
328,445✔
766
            return columns
325,509✔
767
                .map((column) => this.escape(column.databaseName))
993,360✔
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 (
2,936✔
773
            !this.expressionMap.mainAlias!.hasMetadata &&
5,675✔
774
            !this.expressionMap.insertColumns.length
775
        ) {
776
            const valueSets = this.getValueSets()
2,733✔
777
            if (valueSets.length === 1)
2,733✔
778
                return Object.keys(valueSets[0])
2,727✔
779
                    .map((columnName) => this.escape(columnName))
14,762✔
780
                    .join(", ")
781
        }
782

783
        // get a table name and all column database names
784
        return this.expressionMap.insertColumns
209✔
785
            .map((columnName) => this.escape(columnName))
30✔
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()
328,146✔
794
        const columns = this.getInsertedColumns()
328,146✔
795

796
        // if column metadatas are given then apply all necessary operations with values
797
        if (columns.length > 0) {
328,146✔
798
            let expression = ""
325,210✔
799
            valueSets.forEach((valueSet, valueSetIndex) => {
325,210✔
800
                columns.forEach((column, columnIndex) => {
1,003,470✔
801
                    if (columnIndex === 0) {
2,780,314✔
802
                        if (
1,003,470✔
803
                            this.connection.driver.options.type === "oracle" &&
1,021,862✔
804
                            valueSets.length > 1
805
                        ) {
806
                            expression += " SELECT "
34✔
807
                        } else if (
1,003,436✔
808
                            this.connection.driver.options.type === "sap" &&
1,021,886✔
809
                            valueSets.length > 1
810
                        ) {
811
                            expression += " SELECT "
12✔
812
                        } else {
813
                            expression += "("
1,003,424✔
814
                        }
815
                    }
816

817
                    expression += this.createColumnValueExpression(
2,780,314✔
818
                        valueSets,
819
                        valueSetIndex,
820
                        column,
821
                    )
822

823
                    if (columnIndex === columns.length - 1) {
2,780,314✔
824
                        if (valueSetIndex === valueSets.length - 1) {
1,003,470✔
825
                            if (
325,210✔
826
                                ["oracle", "sap"].includes(
362,024✔
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 += ")"
325,192✔
836
                            }
837
                        } else {
838
                            if (
678,260✔
839
                                ["oracle", "sap"].includes(
678,288✔
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 += "), "
678,232✔
850
                            }
851
                        }
852
                    } else {
853
                        expression += ", "
1,776,844✔
854
                    }
855
                })
856
            })
857
            if (expression === "()") return ""
325,210!
858

859
            return expression
325,210✔
860
        } else {
861
            // for tables without metadata
862
            // get values needs to be inserted
863
            let expression = ""
2,936✔
864

865
            valueSets.forEach((valueSet, insertionIndex) => {
2,936✔
866
                const columns = Object.keys(valueSet)
62,942✔
867
                columns.forEach((columnName, columnIndex) => {
62,942✔
868
                    if (columnIndex === 0) {
314,815✔
869
                        expression += "("
62,780✔
870
                    }
871

872
                    const value = valueSet[columnName]
314,815✔
873

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

878
                        // if value for this column was not provided then insert default value
879
                    } else if (value === undefined) {
314,815✔
880
                        if (
2,319✔
881
                            (this.connection.driver.options.type === "oracle" &&
8,216✔
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"
676✔
890
                        } else {
891
                            expression += "DEFAULT"
1,643✔
892
                        }
893
                    } else if (
312,496!
894
                        value === null &&
312,496!
895
                        this.connection.driver.options.type === "spanner"
896
                    ) {
897
                        // just any other regular value
898
                    } else {
899
                        expression += this.createParameter(value)
312,496✔
900
                    }
901

902
                    if (columnIndex === Object.keys(valueSet).length - 1) {
314,815✔
903
                        if (insertionIndex === valueSets.length - 1) {
62,780✔
904
                            expression += ")"
2,774✔
905
                        } else {
906
                            expression += "), "
60,006✔
907
                        }
908
                    } else {
909
                        expression += ", "
252,035✔
910
                    }
911
                })
912
            })
913
            if (expression === "()") return ""
2,936!
914
            return expression
2,936✔
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))
780,314✔
923
            return this.expressionMap.valuesSet
583,891✔
924

925
        if (ObjectUtils.isObject(this.expressionMap.valuesSet))
196,423✔
926
            return [this.expressionMap.valuesSet]
196,383✔
927

928
        throw new InsertValuesMissingError()
40✔
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 (
265,014✔
940
            column.isPrimary &&
526,356✔
941
            column.isGenerated &&
942
            column.generationStrategy === "increment" &&
943
            this.getValueSets().some(
944
                (valueSet) =>
945
                    column.getEntityValue(valueSet) !== undefined &&
90,963✔
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"))
299!
NEW
956
            throw new TypeORMError(
×
957
                `Upsert type "merge-into" is not supported by current database driver`,
958
            )
959

960
        if (
299!
961
            this.expressionMap.onUpdate?.upsertType &&
546✔
962
            this.expressionMap.onUpdate.upsertType !== "merge-into"
963
        ) {
NEW
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())
299✔
970
        const tableAlias = this.escape(this.alias)
299✔
971
        const columns = this.getInsertedColumns()
299✔
972
        const columnsExpression = this.createColumnNamesExpression()
299✔
973

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

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

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

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

983
        // build on condition
984
        if (this.expressionMap.onIgnore) {
299✔
985
            const primaryKey = columns.find((column) => column.isPrimary)
13✔
986
            if (primaryKey) {
13!
987
                query += ` ON (${tableAlias}.${this.escape(
13✔
988
                    primaryKey.databaseName,
989
                )} = ${mergeSourceAlias}.${this.escape(
990
                    primaryKey.databaseName,
991
                )})`
992
            } else {
NEW
993
                query += `ON (${this.expressionMap
×
994
                    .mainAlias!.metadata.uniques.map((unique) => {
NEW
995
                        return `(${unique.columns
×
996
                            .map((column) => {
NEW
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) {
286✔
1008
            const { conflict, indexPredicate } = this.expressionMap.onUpdate
286✔
1009

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

1016
            if (Array.isArray(conflict)) {
286!
1017
                query += ` ON (${conflict
286✔
1018
                    .map(
1019
                        (column) =>
1020
                            `${tableAlias}.${this.escape(
286✔
1021
                                column,
1022
                            )} = ${mergeSourceAlias}.${this.escape(column)}`,
1023
                    )
1024
                    .join(" AND ")})`
NEW
1025
            } else if (conflict) {
×
NEW
1026
                query += ` ON (${tableAlias}.${this.escape(
×
1027
                    conflict,
1028
                )} = ${mergeSourceAlias}.${this.escape(conflict)})`
1029
            } else {
NEW
1030
                query += `ON (${this.expressionMap
×
1031
                    .mainAlias!.metadata.uniques.map((unique) => {
NEW
1032
                        return `(${unique.columns
×
1033
                            .map((column) => {
NEW
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) {
299✔
1047
            const {
1048
                overwrite,
1049
                columns,
1050
                conflict,
1051
                skipUpdateIfNoValuesChanged,
1052
            } = this.expressionMap.onUpdate
286✔
1053
            let updateExpression = ""
286✔
1054

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

1067
            if (Array.isArray(overwrite) && skipUpdateIfNoValuesChanged) {
286✔
1068
                this.expressionMap.onUpdate.overwriteCondition ??= []
13✔
1069
                const wheres = overwrite.map<WhereClause>((column) => ({
13✔
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({
13✔
1080
                    type: "and",
1081
                    condition: wheres,
1082
                })
1083
            }
1084
            const mergeCondition = this.createUpsertConditionExpression()
286✔
1085
            if (updateExpression.trim()) {
286✔
1086
                if (
273✔
1087
                    (this.connection.driver.options.type === "mssql" ||
588✔
1088
                        this.connection.driver.options.type === "sap") &&
1089
                    mergeCondition != ""
1090
                ) {
1091
                    query += ` WHEN MATCHED AND ${mergeCondition} THEN UPDATE SET ${updateExpression}`
22✔
1092
                } else {
1093
                    query += ` WHEN MATCHED THEN UPDATE SET ${updateExpression}`
251✔
1094
                    if (mergeCondition != "") {
251✔
1095
                        query += ` WHERE ${mergeCondition}`
4✔
1096
                    }
1097
                }
1098
            }
1099
        }
1100

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

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

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

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

1120
        // add OUTPUT expression
1121
        if (
299✔
1122
            returningExpression &&
407✔
1123
            this.connection.driver.options.type === "mssql"
1124
        ) {
1125
            query += ` OUTPUT ${returningExpression}`
108✔
1126
        }
1127
        if (this.connection.driver.options.type === "mssql") {
299✔
1128
            query += `;`
207✔
1129
        }
1130
        return query
299✔
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()
299✔
1140
        const columns = this.getInsertedColumns()
299✔
1141

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

1158
                    const value = column.getEntityValue(valueSet)
1,715✔
1159

1160
                    if (
1,715✔
1161
                        value === undefined &&
2,403✔
1162
                        !(
1163
                            column.isGenerated &&
856✔
1164
                            column.generationStrategy === "uuid" &&
1165
                            !this.connection.driver.isUUIDGenerationSupported()
1166
                        )
1167
                    ) {
1168
                        if (
664✔
1169
                            column.default !== undefined &&
794✔
1170
                            column.default !== null
1171
                        ) {
1172
                            // try to use default defined in the column
1173
                            expression +=
130✔
1174
                                this.connection.driver.normalizeDefault(column)
1175
                        } else {
1176
                            expression += "NULL" // otherwise simply use NULL and pray if column is nullable
534✔
1177
                        }
1178
                    } else if (value === null) {
1,051!
NEW
1179
                        expression += "NULL"
×
1180
                    } else {
1181
                        expression += this.createColumnValueExpression(
1,051✔
1182
                            valueSets,
1183
                            valueSetIndex,
1184
                            column,
1185
                        )
1186
                    }
1187

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

1191
                    if (columnIndex === columns.length - 1) {
1,715✔
1192
                        if (valueSetIndex === valueSets.length - 1) {
429✔
1193
                            if (
299✔
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 (
207✔
1202
                                this.connection.driver.options.type === "mssql"
1203
                            ) {
1204
                                expression += ")"
207✔
1205
                            }
1206
                        } else {
1207
                            if (
130✔
1208
                                ["oracle", "sap"].includes(
170✔
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 (
90!
1218
                                this.connection.driver.options.type === "mssql"
1219
                            ) {
1220
                                expression += "), "
90✔
1221
                            } else {
NEW
1222
                                expression += " UNION ALL "
×
1223
                            }
1224
                        }
1225
                    } else {
1226
                        expression += ", "
1,286✔
1227
                    }
1228
                })
1229
            })
1230
        } else {
1231
            // for tables without metadata
NEW
1232
            throw new TypeORMError(
×
1233
                'Upsert type "merge-into" is not supported without metadata tables',
1234
            )
1235
        }
1236
        expression += `) ${mergeSourceAlias}`
299✔
1237
        if (this.connection.driver.options.type === "mssql")
299✔
1238
            expression += ` (${columns
207✔
1239
                .map((column) => this.escape(column.databaseName))
837✔
1240
                .join(", ")})`
1241
        return expression
299✔
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()
299✔
1251

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

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

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

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

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

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

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

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

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

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

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

1339
        let condition = ""
110✔
1340

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

1349
        return condition
110✔
1350
    }
1351

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

1360
        // extract real value from the entity
1361
        let value = column.getEntityValue(valueSet)
2,781,365✔
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")) {
2,781,365✔
1371
            // make sure our value is normalized by a driver
1372
            value = this.connection.driver.preparePersistentValue(value, column)
2,781,158✔
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) {
2,781,365✔
1378
            expression += "1"
883✔
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) {
2,780,482✔
1392
            expression += this.createParameter(
2,239✔
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 (
2,778,243✔
1405
            column.isGenerated &&
3,165,312✔
1406
            column.generationStrategy === "uuid" &&
1407
            !this.connection.driver.isUUIDGenerationSupported() &&
1408
            value === undefined
1409
        ) {
1410
            value = uuidv4()
1,499✔
1411
            expression += this.createParameter(value)
1,499✔
1412

1413
            if (!(valueSetIndex in this.expressionMap.locallyGenerated)) {
1,499✔
1414
                this.expressionMap.locallyGenerated[valueSetIndex] = {}
1,456✔
1415
            }
1416
            column.setEntityValue(
1,499✔
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) {
2,776,744✔
1423
            if (
466,357✔
1424
                (this.connection.driver.options.type === "oracle" &&
1,731,828✔
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) {
73,626✔
1432
                    // try to use default defined in the column
1433
                    expression +=
5,184✔
1434
                        this.connection.driver.normalizeDefault(column)
1435
                } else if (
68,442!
1436
                    this.connection.driver.options.type === "spanner" &&
68,442!
1437
                    column.isGenerated &&
1438
                    column.generationStrategy === "uuid"
1439
                ) {
NEW
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
68,442✔
1443
                }
1444
            } else {
1445
                expression += "DEFAULT"
392,731✔
1446
            }
1447
        } else if (
2,310,387✔
1448
            value === null &&
2,313,671✔
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") {
2,310,309✔
1456
            expression += value()
213✔
1457

1458
            // just any other regular value
1459
        } else {
1460
            if (this.connection.driver.options.type === "mssql")
2,310,096✔
1461
                value = (
215,487✔
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)
2,310,096✔
1470

1471
            if (
2,310,096✔
1472
                (DriverUtils.isMySQLFamily(this.connection.driver) ||
4,620,192✔
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
48✔
1478
                ).options.legacySpatialSupport
1479
                const geomFromText = useLegacy
48✔
1480
                    ? "GeomFromText"
1481
                    : "ST_GeomFromText"
1482
                if (column.srid != null) {
48✔
1483
                    expression += `${geomFromText}(${paramName}, ${column.srid})`
24✔
1484
                } else {
1485
                    expression += `${geomFromText}(${paramName})`
24✔
1486
                }
1487
            } else if (
2,310,048✔
1488
                DriverUtils.isPostgresFamily(this.connection.driver) &&
3,498,711✔
1489
                this.connection.driver.spatialTypes.includes(column.type)
1490
            ) {
1491
                if (column.srid != null) {
96!
NEW
1492
                    expression += `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`
×
1493
                } else {
1494
                    expression += `ST_GeomFromGeoJSON(${paramName})::${column.type}`
96✔
1495
                }
1496
            } else if (
2,309,952✔
1497
                this.connection.driver.options.type === "mssql" &&
2,525,439✔
1498
                this.connection.driver.spatialTypes.includes(column.type)
1499
            ) {
1500
                expression +=
63✔
1501
                    column.type +
1502
                    "::STGeomFromText(" +
1503
                    paramName +
1504
                    ", " +
1505
                    (column.srid || "0") +
108✔
1506
                    ")"
1507
            } else {
1508
                expression += paramName
2,309,889✔
1509
            }
1510
        }
1511
        return expression
2,781,365✔
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

© 2026 Coveralls, Inc