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

typeorm / typeorm / 15219332477

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

Pull #11332

github

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

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

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

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

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

2.16
/src/query-builder/InsertQueryBuilder.ts
1
import { v4 as uuidv4 } from "uuid"
1✔
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"
1✔
6
import { MysqlDriver } from "../driver/mysql/MysqlDriver"
7
import { SqlServerDriver } from "../driver/sqlserver/SqlServerDriver"
8
import { TypeORMError } from "../error"
1✔
9
import { InsertValuesMissingError } from "../error/InsertValuesMissingError"
1✔
10
import { ReturningStatementNotSupportedError } from "../error/ReturningStatementNotSupportedError"
1✔
11
import { ColumnMetadata } from "../metadata/ColumnMetadata"
12
import { BroadcasterResult } from "../subscriber/BroadcasterResult"
1✔
13
import { InstanceChecker } from "../util/InstanceChecker"
1✔
14
import { ObjectUtils } from "../util/ObjectUtils"
1✔
15
import { InsertOrUpdateOptions } from "./InsertOrUpdateOptions"
16
import { QueryBuilder } from "./QueryBuilder"
1✔
17
import { QueryDeepPartialEntity } from "./QueryPartialEntity"
18
import { InsertResult } from "./result/InsertResult"
1✔
19
import { ReturningResultsEntityUpdator } from "./ReturningResultsEntityUpdator"
1✔
20

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

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

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

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

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

60
        // console.time("QueryBuilder.execute");
61
        // console.time(".database stuff");
UNCOV
62
        const queryRunner = this.obtainQueryRunner()
×
UNCOV
63
        let transactionStartedByUs: boolean = false
×
64

UNCOV
65
        try {
×
66
            // start transaction if it was enabled
UNCOV
67
            if (
×
68
                this.expressionMap.useTransaction === true &&
×
69
                queryRunner.isTransactionActive === false
70
            ) {
UNCOV
71
                await queryRunner.startTransaction()
×
UNCOV
72
                transactionStartedByUs = true
×
73
            }
74

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

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

UNCOV
93
            let declareSql: string | null = null
×
UNCOV
94
            let selectOutputSql: string | null = null
×
95

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

UNCOV
104
            const returningColumns: ColumnMetadata[] = []
×
105

UNCOV
106
            if (
×
107
                Array.isArray(this.expressionMap.returning) &&
×
108
                this.expressionMap.mainAlias!.hasMetadata
109
            ) {
UNCOV
110
                for (const columnPath of this.expressionMap.returning) {
×
UNCOV
111
                    returningColumns.push(
×
112
                        ...this.expressionMap.mainAlias!.metadata.findColumnsWithPropertyPath(
113
                            columnPath,
114
                        ),
115
                    )
116
                }
117
            }
118

UNCOV
119
            if (
×
120
                this.expressionMap.updateEntity === true &&
×
121
                this.expressionMap.mainAlias!.hasMetadata
122
            ) {
UNCOV
123
                if (
×
124
                    !(
125
                        valueSets.length > 1 &&
×
126
                        this.connection.driver.options.type === "oracle"
127
                    )
128
                ) {
UNCOV
129
                    this.expressionMap.extraReturningColumns =
×
130
                        this.expressionMap.mainAlias!.metadata.getInsertionReturningColumns()
131
                }
132

UNCOV
133
                returningColumns.push(
×
134
                    ...this.expressionMap.extraReturningColumns.filter(
UNCOV
135
                        (c) => !returningColumns.includes(c),
×
136
                    ),
137
                )
138
            }
139

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

154
            // execute query
155
            // console.time(".getting query and parameters");
UNCOV
156
            const [insertSql, parameters] = this.getQueryAndParameters()
×
157
            // console.timeEnd(".getting query and parameters");
158

159
            // console.time(".query execution by database");
UNCOV
160
            const statements = [declareSql, insertSql, selectOutputSql]
×
UNCOV
161
            const sql = statements.filter((s) => s != null).join(";\n\n")
×
162

UNCOV
163
            const queryResult = await queryRunner.query(sql, parameters, true)
×
164

UNCOV
165
            const insertResult = InsertResult.from(queryResult)
×
166

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

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

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

198
            // close transaction if we started it
199
            // console.time(".commit");
UNCOV
200
            if (transactionStartedByUs) {
×
UNCOV
201
                await queryRunner.commitTransaction()
×
202
            }
203
            // console.timeEnd(".commit");
204

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

225
    // -------------------------------------------------------------------------
226
    // Public Methods
227
    // -------------------------------------------------------------------------
228

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

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

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

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

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

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

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

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

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

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

UNCOV
307
        this.expressionMap.returning = returning
×
UNCOV
308
        return this
×
309
    }
310

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

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

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

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

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

363
    /**
364
     * Adds additional update statement supported in databases.
365
     */
366
    orUpdate(
367
        statementOrOverwrite?:
368
            | {
369
                  columns?: string[]
370
                  overwrite?: string[]
371
                  conflict_target?: string | string[]
372
              }
373
            | string[],
374
        conflictTarget?: string | string[],
375
        orUpdateOptions?: InsertOrUpdateOptions,
376
    ): this {
UNCOV
377
        if (!Array.isArray(statementOrOverwrite)) {
×
378
            this.expressionMap.onUpdate = {
×
379
                conflict: statementOrOverwrite?.conflict_target,
380
                columns: statementOrOverwrite?.columns,
381
                overwrite: statementOrOverwrite?.overwrite,
382
                skipUpdateIfNoValuesChanged:
383
                    orUpdateOptions?.skipUpdateIfNoValuesChanged,
384
                upsertType: orUpdateOptions?.upsertType,
385
            }
386
            return this
×
387
        }
388

UNCOV
389
        this.expressionMap.onUpdate = {
×
390
            overwrite: statementOrOverwrite,
391
            conflict: conflictTarget,
392
            skipUpdateIfNoValuesChanged:
393
                orUpdateOptions?.skipUpdateIfNoValuesChanged,
394
            indexPredicate: orUpdateOptions?.indexPredicate,
395
            upsertType: orUpdateOptions?.upsertType,
396
        }
UNCOV
397
        return this
×
398
    }
399

400
    // -------------------------------------------------------------------------
401
    // Protected Methods
402
    // -------------------------------------------------------------------------
403

404
    /**
405
     * Creates INSERT express used to perform insert query.
406
     */
407
    protected createInsertExpression() {
UNCOV
408
        const tableName = this.getTableName(this.getMainTableName())
×
UNCOV
409
        const valuesExpression = this.createValuesExpression() // its important to get values before returning expression because oracle rely on native parameters and ordering of them is important
×
410
        const returningExpression =
UNCOV
411
            this.connection.driver.options.type === "oracle" &&
×
412
            this.getValueSets().length > 1
413
                ? null
414
                : this.createReturningExpression("insert") // oracle doesnt support returning with multi-row insert
UNCOV
415
        const columnsExpression = this.createColumnNamesExpression()
×
UNCOV
416
        let query = "INSERT "
×
417

UNCOV
418
        if (this.expressionMap.onUpdate?.upsertType === "primary-key") {
×
419
            query = "UPSERT "
×
420
        }
421

UNCOV
422
        if (
×
423
            DriverUtils.isMySQLFamily(this.connection.driver) ||
×
424
            this.connection.driver.options.type === "aurora-mysql"
425
        ) {
UNCOV
426
            query += `${this.expressionMap.onIgnore ? " IGNORE " : ""}`
×
427
        }
428

UNCOV
429
        query += `INTO ${tableName}`
×
430

UNCOV
431
        if (
×
432
            this.alias !== this.getMainTableName() &&
×
433
            DriverUtils.isPostgresFamily(this.connection.driver)
434
        ) {
UNCOV
435
            query += ` AS "${this.alias}"`
×
436
        }
437

438
        // add columns expression
UNCOV
439
        if (columnsExpression) {
×
UNCOV
440
            query += `(${columnsExpression})`
×
441
        } else {
UNCOV
442
            if (
×
443
                !valuesExpression &&
×
444
                (DriverUtils.isMySQLFamily(this.connection.driver) ||
445
                    this.connection.driver.options.type === "aurora-mysql")
446
            )
447
                // special syntax for mysql DEFAULT VALUES insertion
448
                query += "()"
×
449
        }
450

451
        // add OUTPUT expression
UNCOV
452
        if (
×
453
            returningExpression &&
×
454
            this.connection.driver.options.type === "mssql"
455
        ) {
UNCOV
456
            query += ` OUTPUT ${returningExpression}`
×
457
        }
458

459
        // add VALUES expression
UNCOV
460
        if (valuesExpression) {
×
UNCOV
461
            if (
×
462
                (this.connection.driver.options.type === "oracle" ||
×
463
                    this.connection.driver.options.type === "sap") &&
464
                this.getValueSets().length > 1
465
            ) {
UNCOV
466
                query += ` ${valuesExpression}`
×
467
            } else {
UNCOV
468
                query += ` VALUES ${valuesExpression}`
×
469
            }
470
        } else {
UNCOV
471
            if (
×
472
                DriverUtils.isMySQLFamily(this.connection.driver) ||
×
473
                this.connection.driver.options.type === "aurora-mysql"
474
            ) {
475
                // special syntax for mysql DEFAULT VALUES insertion
476
                query += " VALUES ()"
×
477
            } else {
UNCOV
478
                query += ` DEFAULT VALUES`
×
479
            }
480
        }
UNCOV
481
        if (this.expressionMap.onUpdate?.upsertType !== "primary-key") {
×
UNCOV
482
            if (
×
483
                this.connection.driver.supportedUpsertTypes.includes(
484
                    "on-conflict-do-update",
485
                )
486
            ) {
UNCOV
487
                if (this.expressionMap.onIgnore) {
×
UNCOV
488
                    query += " ON CONFLICT DO NOTHING "
×
UNCOV
489
                } else if (this.expressionMap.onConflict) {
×
UNCOV
490
                    query += ` ON CONFLICT ${this.expressionMap.onConflict} `
×
UNCOV
491
                } else if (this.expressionMap.onUpdate) {
×
492
                    const {
493
                        overwrite,
494
                        columns,
495
                        conflict,
496
                        skipUpdateIfNoValuesChanged,
497
                        indexPredicate,
UNCOV
498
                    } = this.expressionMap.onUpdate
×
499

UNCOV
500
                    let conflictTarget = "ON CONFLICT"
×
501

UNCOV
502
                    if (Array.isArray(conflict)) {
×
UNCOV
503
                        conflictTarget += ` ( ${conflict
×
UNCOV
504
                            .map((column) => this.escape(column))
×
505
                            .join(", ")} )`
UNCOV
506
                        if (
×
507
                            indexPredicate &&
×
508
                            !DriverUtils.isPostgresFamily(
509
                                this.connection.driver,
510
                            )
511
                        ) {
UNCOV
512
                            throw new TypeORMError(
×
513
                                `indexPredicate option is not supported by the current database driver`,
514
                            )
515
                        }
UNCOV
516
                        if (
×
517
                            indexPredicate &&
×
518
                            DriverUtils.isPostgresFamily(this.connection.driver)
519
                        ) {
UNCOV
520
                            conflictTarget += ` WHERE ( ${indexPredicate} )`
×
521
                        }
UNCOV
522
                    } else if (conflict) {
×
UNCOV
523
                        conflictTarget += ` ON CONSTRAINT ${this.escape(
×
524
                            conflict,
525
                        )}`
526
                    }
527

UNCOV
528
                    const updatePart: string[] = []
×
529

UNCOV
530
                    if (Array.isArray(overwrite)) {
×
UNCOV
531
                        updatePart.push(
×
532
                            ...overwrite.map(
533
                                (column) =>
UNCOV
534
                                    `${this.escape(
×
535
                                        column,
536
                                    )} = EXCLUDED.${this.escape(column)}`,
537
                            ),
538
                        )
539
                    } else if (columns) {
×
540
                        updatePart.push(
×
541
                            ...columns.map(
542
                                (column) =>
543
                                    `${this.escape(column)} = :${column}`,
×
544
                            ),
545
                        )
546
                    }
547

UNCOV
548
                    if (updatePart.length > 0) {
×
UNCOV
549
                        query += ` ${conflictTarget} DO UPDATE SET `
×
550

UNCOV
551
                        updatePart.push(
×
552
                            ...this.expressionMap
553
                                .mainAlias!.metadata.columns.filter(
554
                                    (column) =>
UNCOV
555
                                        column.isUpdateDate &&
×
556
                                        !overwrite?.includes(
557
                                            column.databaseName,
558
                                        ) &&
559
                                        !(
560
                                            (this.connection.driver.options
×
561
                                                .type === "oracle" &&
562
                                                this.getValueSets().length >
563
                                                    1) ||
564
                                            DriverUtils.isSQLiteFamily(
565
                                                this.connection.driver,
566
                                            ) ||
567
                                            this.connection.driver.options
568
                                                .type === "sap" ||
569
                                            this.connection.driver.options
570
                                                .type === "spanner"
571
                                        ),
572
                                )
573
                                .map(
574
                                    (column) =>
UNCOV
575
                                        `${this.escape(
×
576
                                            column.databaseName,
577
                                        )} = DEFAULT`,
578
                                ),
579
                        )
580

UNCOV
581
                        query += updatePart.join(", ")
×
582
                    }
583

UNCOV
584
                    if (
×
585
                        Array.isArray(overwrite) &&
×
586
                        skipUpdateIfNoValuesChanged &&
587
                        DriverUtils.isPostgresFamily(this.connection.driver)
588
                    ) {
UNCOV
589
                        query += ` WHERE (`
×
UNCOV
590
                        query += overwrite
×
591
                            .map(
592
                                (column) =>
UNCOV
593
                                    `${this.escape(this.alias)}.${this.escape(
×
594
                                        column,
595
                                    )} IS DISTINCT FROM EXCLUDED.${this.escape(
596
                                        column,
597
                                    )}`,
598
                            )
599
                            .join(" OR ")
UNCOV
600
                        query += ") "
×
601
                    }
602
                }
UNCOV
603
            } else if (
×
604
                this.connection.driver.supportedUpsertTypes.includes(
605
                    "on-duplicate-key-update",
606
                )
607
            ) {
UNCOV
608
                if (this.expressionMap.onUpdate) {
×
UNCOV
609
                    const { overwrite, columns } = this.expressionMap.onUpdate
×
610

UNCOV
611
                    if (Array.isArray(overwrite)) {
×
UNCOV
612
                        query += " ON DUPLICATE KEY UPDATE "
×
UNCOV
613
                        query += overwrite
×
614
                            .map(
615
                                (column) =>
UNCOV
616
                                    `${this.escape(
×
617
                                        column,
618
                                    )} = VALUES(${this.escape(column)})`,
619
                            )
620
                            .join(", ")
UNCOV
621
                        query += " "
×
622
                    } else if (Array.isArray(columns)) {
×
623
                        query += " ON DUPLICATE KEY UPDATE "
×
624
                        query += columns
×
625
                            .map(
626
                                (column) =>
627
                                    `${this.escape(column)} = :${column}`,
×
628
                            )
629
                            .join(", ")
630
                        query += " "
×
631
                    }
632
                }
633
            } else {
UNCOV
634
                if (this.expressionMap.onUpdate) {
×
UNCOV
635
                    throw new TypeORMError(
×
636
                        `onUpdate is not supported by the current database driver`,
637
                    )
638
                }
639
            }
640
        }
641

642
        // add RETURNING expression
UNCOV
643
        if (
×
644
            returningExpression &&
×
645
            (DriverUtils.isPostgresFamily(this.connection.driver) ||
646
                this.connection.driver.options.type === "oracle" ||
647
                this.connection.driver.options.type === "cockroachdb" ||
648
                DriverUtils.isMySQLFamily(this.connection.driver))
649
        ) {
UNCOV
650
            query += ` RETURNING ${returningExpression}`
×
651
        }
652

UNCOV
653
        if (
×
654
            returningExpression &&
×
655
            this.connection.driver.options.type === "spanner"
656
        ) {
657
            query += ` THEN RETURN ${returningExpression}`
×
658
        }
659

660
        // Inserting a specific value for an auto-increment primary key in mssql requires enabling IDENTITY_INSERT
661
        // 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)
UNCOV
662
        if (
×
663
            this.connection.driver.options.type === "mssql" &&
×
664
            this.expressionMap.mainAlias!.hasMetadata &&
665
            this.expressionMap
666
                .mainAlias!.metadata.columns.filter((column) =>
UNCOV
667
                    this.expressionMap.insertColumns.length > 0
×
668
                        ? this.expressionMap.insertColumns.indexOf(
669
                              column.propertyPath,
670
                          ) !== -1
671
                        : column.isInsert,
672
                )
673
                .some((column) =>
UNCOV
674
                    this.isOverridingAutoIncrementBehavior(column),
×
675
                )
676
        ) {
UNCOV
677
            query = `SET IDENTITY_INSERT ${tableName} ON; ${query}; SET IDENTITY_INSERT ${tableName} OFF`
×
678
        }
679

UNCOV
680
        return query
×
681
    }
682

683
    /**
684
     * Gets list of columns where values must be inserted to.
685
     */
686
    protected getInsertedColumns(): ColumnMetadata[] {
UNCOV
687
        if (!this.expressionMap.mainAlias!.hasMetadata) return []
×
688

UNCOV
689
        return this.expressionMap.mainAlias!.metadata.columns.filter(
×
690
            (column) => {
691
                // if user specified list of columns he wants to insert to, then we filter only them
UNCOV
692
                if (this.expressionMap.insertColumns.length)
×
693
                    return (
×
694
                        this.expressionMap.insertColumns.indexOf(
695
                            column.propertyPath,
696
                        ) !== -1
697
                    )
698

699
                // skip columns the user doesn't want included by default
UNCOV
700
                if (!column.isInsert) {
×
UNCOV
701
                    return false
×
702
                }
703

704
                // if user did not specified such list then return all columns except auto-increment one
705
                // for Oracle we return auto-increment column as well because Oracle does not support DEFAULT VALUES expression
UNCOV
706
                if (
×
707
                    column.isGenerated &&
×
708
                    column.generationStrategy === "increment" &&
709
                    !(this.connection.driver.options.type === "spanner") &&
710
                    !(this.connection.driver.options.type === "oracle") &&
711
                    !DriverUtils.isSQLiteFamily(this.connection.driver) &&
712
                    !DriverUtils.isMySQLFamily(this.connection.driver) &&
713
                    !(this.connection.driver.options.type === "aurora-mysql") &&
714
                    !(
715
                        this.connection.driver.options.type === "mssql" &&
×
716
                        this.isOverridingAutoIncrementBehavior(column)
717
                    )
718
                )
UNCOV
719
                    return false
×
720

UNCOV
721
                return true
×
722
            },
723
        )
724
    }
725

726
    /**
727
     * Creates a columns string where values must be inserted to for INSERT INTO expression.
728
     */
729
    protected createColumnNamesExpression(): string {
UNCOV
730
        const columns = this.getInsertedColumns()
×
UNCOV
731
        if (columns.length > 0)
×
UNCOV
732
            return columns
×
UNCOV
733
                .map((column) => this.escape(column.databaseName))
×
734
                .join(", ")
735

736
        // in the case if there are no insert columns specified and table without metadata used
737
        // we get columns from the inserted value map, in the case if only one inserted map is specified
UNCOV
738
        if (
×
739
            !this.expressionMap.mainAlias!.hasMetadata &&
×
740
            !this.expressionMap.insertColumns.length
741
        ) {
UNCOV
742
            const valueSets = this.getValueSets()
×
UNCOV
743
            if (valueSets.length === 1)
×
UNCOV
744
                return Object.keys(valueSets[0])
×
UNCOV
745
                    .map((columnName) => this.escape(columnName))
×
746
                    .join(", ")
747
        }
748

749
        // get a table name and all column database names
UNCOV
750
        return this.expressionMap.insertColumns
×
UNCOV
751
            .map((columnName) => this.escape(columnName))
×
752
            .join(", ")
753
    }
754

755
    /**
756
     * Creates list of values needs to be inserted in the VALUES expression.
757
     */
758
    protected createValuesExpression(): string {
UNCOV
759
        const valueSets = this.getValueSets()
×
UNCOV
760
        const columns = this.getInsertedColumns()
×
761

762
        // if column metadatas are given then apply all necessary operations with values
UNCOV
763
        if (columns.length > 0) {
×
UNCOV
764
            let expression = ""
×
UNCOV
765
            valueSets.forEach((valueSet, valueSetIndex) => {
×
UNCOV
766
                columns.forEach((column, columnIndex) => {
×
UNCOV
767
                    if (columnIndex === 0) {
×
UNCOV
768
                        if (
×
769
                            this.connection.driver.options.type === "oracle" &&
×
770
                            valueSets.length > 1
771
                        ) {
UNCOV
772
                            expression += " SELECT "
×
UNCOV
773
                        } else if (
×
774
                            this.connection.driver.options.type === "sap" &&
×
775
                            valueSets.length > 1
776
                        ) {
UNCOV
777
                            expression += " SELECT "
×
778
                        } else {
UNCOV
779
                            expression += "("
×
780
                        }
781
                    }
782

783
                    // extract real value from the entity
UNCOV
784
                    let value = column.getEntityValue(valueSet)
×
785

786
                    // if column is relational and value is an object then get real referenced column value from this object
787
                    // for example column value is { question: { id: 1 } }, value will be equal to { id: 1 }
788
                    // and we extract "1" from this object
789
                    /*if (column.referencedColumn && value instanceof Object && !(typeof value === "function")) { // todo: check if we still need it since getEntityValue already has similar code
790
                        value = column.referencedColumn.getEntityValue(value);
791
                    }*/
792

UNCOV
793
                    if (!(typeof value === "function")) {
×
794
                        // make sure our value is normalized by a driver
UNCOV
795
                        value = this.connection.driver.preparePersistentValue(
×
796
                            value,
797
                            column,
798
                        )
799
                    }
800

801
                    // newly inserted entities always have a version equal to 1 (first version)
802
                    // also, user-specified version must be empty
UNCOV
803
                    if (column.isVersion && value === undefined) {
×
UNCOV
804
                        expression += "1"
×
805

806
                        // } else if (column.isNestedSetLeft) {
807
                        //     const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
808
                        //     const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
809
                        //     const subQuery = `(SELECT c.max + 1 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
810
                        //     expression += subQuery;
811
                        //
812
                        // } else if (column.isNestedSetRight) {
813
                        //     const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
814
                        //     const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
815
                        //     const subQuery = `(SELECT c.max + 2 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
816
                        //     expression += subQuery;
UNCOV
817
                    } else if (column.isDiscriminator) {
×
UNCOV
818
                        expression += this.createParameter(
×
819
                            this.expressionMap.mainAlias!.metadata
820
                                .discriminatorValue,
821
                        )
822
                        // return "1";
823

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

830
                        // 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
UNCOV
831
                    } else if (
×
832
                        column.isGenerated &&
×
833
                        column.generationStrategy === "uuid" &&
834
                        !this.connection.driver.isUUIDGenerationSupported() &&
835
                        value === undefined
836
                    ) {
UNCOV
837
                        value = uuidv4()
×
UNCOV
838
                        expression += this.createParameter(value)
×
839

UNCOV
840
                        if (
×
841
                            !(
842
                                valueSetIndex in
843
                                this.expressionMap.locallyGenerated
844
                            )
845
                        ) {
UNCOV
846
                            this.expressionMap.locallyGenerated[valueSetIndex] =
×
847
                                {}
848
                        }
UNCOV
849
                        column.setEntityValue(
×
850
                            this.expressionMap.locallyGenerated[valueSetIndex],
851
                            value,
852
                        )
853

854
                        // if value for this column was not provided then insert default value
UNCOV
855
                    } else if (value === undefined) {
×
UNCOV
856
                        if (
×
857
                            (this.connection.driver.options.type === "oracle" &&
×
858
                                valueSets.length > 1) ||
859
                            DriverUtils.isSQLiteFamily(
860
                                this.connection.driver,
861
                            ) ||
862
                            this.connection.driver.options.type === "sap" ||
863
                            this.connection.driver.options.type === "spanner"
864
                        ) {
865
                            // unfortunately sqlite does not support DEFAULT expression in INSERT queries
UNCOV
866
                            if (
×
867
                                column.default !== undefined &&
×
868
                                column.default !== null
869
                            ) {
870
                                // try to use default defined in the column
UNCOV
871
                                expression +=
×
872
                                    this.connection.driver.normalizeDefault(
873
                                        column,
874
                                    )
UNCOV
875
                            } else if (
×
876
                                this.connection.driver.options.type ===
×
877
                                    "spanner" &&
878
                                column.isGenerated &&
879
                                column.generationStrategy === "uuid"
880
                            ) {
881
                                expression += "GENERATE_UUID()" // Produces a random universally unique identifier (UUID) as a STRING value.
×
882
                            } else {
UNCOV
883
                                expression += "NULL" // otherwise simply use NULL and pray if column is nullable
×
884
                            }
885
                        } else {
UNCOV
886
                            expression += "DEFAULT"
×
887
                        }
UNCOV
888
                    } else if (
×
889
                        value === null &&
×
890
                        (this.connection.driver.options.type === "spanner" ||
891
                            this.connection.driver.options.type === "oracle")
892
                    ) {
UNCOV
893
                        expression += "NULL"
×
894

895
                        // support for SQL expressions in queries
UNCOV
896
                    } else if (typeof value === "function") {
×
UNCOV
897
                        expression += value()
×
898

899
                        // just any other regular value
900
                    } else {
UNCOV
901
                        if (this.connection.driver.options.type === "mssql")
×
UNCOV
902
                            value = (
×
903
                                this.connection.driver as SqlServerDriver
904
                            ).parametrizeValue(column, value)
905

906
                        // we need to store array values in a special class to make sure parameter replacement will work correctly
907
                        // if (value instanceof Array)
908
                        //     value = new ArrayParameter(value);
909

UNCOV
910
                        const paramName = this.createParameter(value)
×
911

UNCOV
912
                        if (
×
913
                            (DriverUtils.isMySQLFamily(
×
914
                                this.connection.driver,
915
                            ) ||
916
                                this.connection.driver.options.type ===
917
                                    "aurora-mysql") &&
918
                            this.connection.driver.spatialTypes.indexOf(
919
                                column.type,
920
                            ) !== -1
921
                        ) {
922
                            const useLegacy = (
UNCOV
923
                                this.connection.driver as
×
924
                                    | MysqlDriver
925
                                    | AuroraMysqlDriver
926
                            ).options.legacySpatialSupport
UNCOV
927
                            const geomFromText = useLegacy
×
928
                                ? "GeomFromText"
929
                                : "ST_GeomFromText"
UNCOV
930
                            if (column.srid != null) {
×
UNCOV
931
                                expression += `${geomFromText}(${paramName}, ${column.srid})`
×
932
                            } else {
UNCOV
933
                                expression += `${geomFromText}(${paramName})`
×
934
                            }
UNCOV
935
                        } else if (
×
936
                            DriverUtils.isPostgresFamily(
×
937
                                this.connection.driver,
938
                            ) &&
939
                            this.connection.driver.spatialTypes.indexOf(
940
                                column.type,
941
                            ) !== -1
942
                        ) {
UNCOV
943
                            if (column.srid != null) {
×
944
                                expression += `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`
×
945
                            } else {
UNCOV
946
                                expression += `ST_GeomFromGeoJSON(${paramName})::${column.type}`
×
947
                            }
UNCOV
948
                        } else if (
×
949
                            this.connection.driver.options.type === "mssql" &&
×
950
                            this.connection.driver.spatialTypes.indexOf(
951
                                column.type,
952
                            ) !== -1
953
                        ) {
UNCOV
954
                            expression +=
×
955
                                column.type +
956
                                "::STGeomFromText(" +
957
                                paramName +
958
                                ", " +
959
                                (column.srid || "0") +
×
960
                                ")"
961
                        } else {
UNCOV
962
                            expression += paramName
×
963
                        }
964
                    }
965

UNCOV
966
                    if (columnIndex === columns.length - 1) {
×
UNCOV
967
                        if (valueSetIndex === valueSets.length - 1) {
×
UNCOV
968
                            if (
×
969
                                this.connection.driver.options.type ===
×
970
                                    "oracle" &&
971
                                valueSets.length > 1
972
                            ) {
UNCOV
973
                                expression += " FROM DUAL "
×
UNCOV
974
                            } else if (
×
975
                                this.connection.driver.options.type === "sap" &&
×
976
                                valueSets.length > 1
977
                            ) {
UNCOV
978
                                expression += " FROM dummy "
×
979
                            } else {
UNCOV
980
                                expression += ")"
×
981
                            }
982
                        } else {
UNCOV
983
                            if (
×
984
                                this.connection.driver.options.type ===
×
985
                                    "oracle" &&
986
                                valueSets.length > 1
987
                            ) {
UNCOV
988
                                expression += " FROM DUAL UNION ALL "
×
UNCOV
989
                            } else if (
×
990
                                this.connection.driver.options.type === "sap" &&
×
991
                                valueSets.length > 1
992
                            ) {
UNCOV
993
                                expression += " FROM dummy UNION ALL "
×
994
                            } else {
UNCOV
995
                                expression += "), "
×
996
                            }
997
                        }
998
                    } else {
UNCOV
999
                        expression += ", "
×
1000
                    }
1001
                })
1002
            })
UNCOV
1003
            if (expression === "()") return ""
×
1004

UNCOV
1005
            return expression
×
1006
        } else {
1007
            // for tables without metadata
1008
            // get values needs to be inserted
UNCOV
1009
            let expression = ""
×
1010

UNCOV
1011
            valueSets.forEach((valueSet, insertionIndex) => {
×
UNCOV
1012
                const columns = Object.keys(valueSet)
×
UNCOV
1013
                columns.forEach((columnName, columnIndex) => {
×
UNCOV
1014
                    if (columnIndex === 0) {
×
UNCOV
1015
                        expression += "("
×
1016
                    }
1017

UNCOV
1018
                    const value = valueSet[columnName]
×
1019

1020
                    // support for SQL expressions in queries
UNCOV
1021
                    if (typeof value === "function") {
×
1022
                        expression += value()
×
1023

1024
                        // if value for this column was not provided then insert default value
UNCOV
1025
                    } else if (value === undefined) {
×
UNCOV
1026
                        if (
×
1027
                            (this.connection.driver.options.type === "oracle" &&
×
1028
                                valueSets.length > 1) ||
1029
                            DriverUtils.isSQLiteFamily(
1030
                                this.connection.driver,
1031
                            ) ||
1032
                            this.connection.driver.options.type === "sap" ||
1033
                            this.connection.driver.options.type === "spanner"
1034
                        ) {
UNCOV
1035
                            expression += "NULL"
×
1036
                        } else {
UNCOV
1037
                            expression += "DEFAULT"
×
1038
                        }
UNCOV
1039
                    } else if (
×
1040
                        value === null &&
×
1041
                        this.connection.driver.options.type === "spanner"
1042
                    ) {
1043
                        // just any other regular value
1044
                    } else {
UNCOV
1045
                        expression += this.createParameter(value)
×
1046
                    }
1047

UNCOV
1048
                    if (columnIndex === Object.keys(valueSet).length - 1) {
×
UNCOV
1049
                        if (insertionIndex === valueSets.length - 1) {
×
UNCOV
1050
                            expression += ")"
×
1051
                        } else {
UNCOV
1052
                            expression += "), "
×
1053
                        }
1054
                    } else {
UNCOV
1055
                        expression += ", "
×
1056
                    }
1057
                })
1058
            })
UNCOV
1059
            if (expression === "()") return ""
×
UNCOV
1060
            return expression
×
1061
        }
1062
    }
1063

1064
    /**
1065
     * Gets array of values need to be inserted into the target table.
1066
     */
1067
    protected getValueSets(): ObjectLiteral[] {
UNCOV
1068
        if (Array.isArray(this.expressionMap.valuesSet))
×
UNCOV
1069
            return this.expressionMap.valuesSet
×
1070

UNCOV
1071
        if (ObjectUtils.isObject(this.expressionMap.valuesSet))
×
UNCOV
1072
            return [this.expressionMap.valuesSet]
×
1073

UNCOV
1074
        throw new InsertValuesMissingError()
×
1075
    }
1076

1077
    /**
1078
     * Checks if column is an auto-generated primary key, but the current insertion specifies a value for it.
1079
     *
1080
     * @param column
1081
     */
1082
    protected isOverridingAutoIncrementBehavior(
1083
        column: ColumnMetadata,
1084
    ): boolean {
UNCOV
1085
        return (
×
1086
            column.isPrimary &&
×
1087
            column.isGenerated &&
1088
            column.generationStrategy === "increment" &&
1089
            this.getValueSets().some(
1090
                (valueSet) =>
UNCOV
1091
                    column.getEntityValue(valueSet) !== undefined &&
×
1092
                    column.getEntityValue(valueSet) !== null,
1093
            )
1094
        )
1095
    }
1096
}
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