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

typeorm / typeorm / 15006410012

13 May 2025 08:45PM UTC coverage: 76.309% (-0.03%) from 76.341%
15006410012

push

github

web-flow
feat(spanner): support insert returning (#11460)

* Update SpannerDriver.ts

* Update package.json

* Update package.json

* Update package.json

* Update package-lock.json

* Update package.json

* #11453

* Revert "Update package.json"

This reverts commit 20f24de10.

* Revert "Update package.json"

This reverts commit bcf6678e9.

* Update package.json

* Revert "Update package-lock.json"

This reverts commit a003e5659.

* #11460

* #11460

* FIX/Spanner Numeric type value string

* #11460 Test functional spanner

* test: update returning tests

* refactor: simplify condition

* style: fix lint/format

* test: fix returning test for spanner

---------

Co-authored-by: Lucian Mocanu <alumni@users.noreply.github.com>

9190 of 12750 branches covered (72.08%)

Branch coverage included in aggregate %.

9 of 18 new or added lines in 5 files covered. (50.0%)

3 existing lines in 3 files now uncovered.

18862 of 24011 relevant lines covered (78.56%)

197371.4 hits per line

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

91.71
/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

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

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

33
    /**
34
     * Gets generated SQL query without parameters being replaced.
35
     */
36
    getQuery(): string {
37
        let sql = this.createComment()
328,071✔
38
        sql += this.createCteExpression()
328,071✔
39
        sql += this.createInsertExpression()
328,071✔
40
        return this.replacePropertyNamesForTheWholeQuery(sql.trim())
328,046✔
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");
48
        const valueSets: ObjectLiteral[] = this.getValueSets()
326,303✔
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.
58
        if (valueSets.length === 0) return new InsertResult()
326,263✔
59

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

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

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

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

93
            let declareSql: string | null = null
326,183✔
94
            let selectOutputSql: string | null = null
326,183✔
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 =
99
                new ReturningResultsEntityUpdator(
326,183✔
100
                    queryRunner,
101
                    this.expressionMap,
102
                )
103

104
            const returningColumns: ColumnMetadata[] = []
326,183✔
105

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

119
            if (
326,183✔
120
                this.expressionMap.updateEntity === true &&
646,747✔
121
                this.expressionMap.mainAlias!.hasMetadata
122
            ) {
123
                if (
319,573✔
124
                    !(
125
                        valueSets.length > 1 &&
344,059✔
126
                        this.connection.driver.options.type === "oracle"
127
                    )
128
                ) {
129
                    this.expressionMap.extraReturningColumns =
319,559✔
130
                        this.expressionMap.mainAlias!.metadata.getInsertionReturningColumns()
131
                }
132

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

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

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

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

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

165
            const insertResult = InsertResult.from(queryResult)
326,032✔
166

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

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

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

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

205
            return insertResult
326,032✔
206
        } catch (error) {
207
            // rollback transaction if we started it
208
            if (transactionStartedByUs) {
151!
209
                try {
×
210
                    await queryRunner.rollbackTransaction()
×
211
                } catch (rollbackError) {}
212
            }
213
            throw error
151✔
214
        } finally {
215
            // console.time(".releasing connection");
216
            if (queryRunner !== this.queryRunner) {
326,183✔
217
                // means we created our own query runner
218
                await queryRunner.release()
2,643✔
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> {
236
        entityTarget = InstanceChecker.isEntitySchema(entityTarget)
327,922!
237
            ? entityTarget.options.name
238
            : entityTarget
239
        const mainAlias = this.createFromAlias(entityTarget)
327,922✔
240
        this.expressionMap.setMainAlias(mainAlias)
327,922✔
241
        this.expressionMap.insertColumns = columns || []
327,922✔
242
        return this as any as InsertQueryBuilder<T>
327,922✔
243
    }
244

245
    /**
246
     * Values needs to be inserted into table.
247
     */
248
    values(
249
        values:
250
            | QueryDeepPartialEntity<Entity>
251
            | QueryDeepPartialEntity<Entity>[],
252
    ): this {
253
        this.expressionMap.valuesSet = values
328,115✔
254
        return this
328,115✔
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
303
        if (!this.connection.driver.isReturningSqlSupported("insert")) {
51!
UNCOV
304
            throw new ReturningStatementNotSupportedError()
×
305
        }
306

307
        this.expressionMap.returning = returning
51✔
308
        return this
51✔
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 {
317
        this.expressionMap.updateEntity = enabled
322,675✔
318
        return this
322,675✔
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 {
327
        this.expressionMap.onConflict = statement
52✔
328
        return this
52✔
329
    }
330

331
    /**
332
     * Adds additional ignore statement supported in databases.
333
     */
334
    orIgnore(statement: string | boolean = true): this {
33✔
335
        this.expressionMap.onIgnore = !!statement
50✔
336
        return this
50✔
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 {
377
        if (!Array.isArray(statementOrOverwrite)) {
686!
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

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

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

404
    /**
405
     * Creates INSERT express used to perform insert query.
406
     */
407
    protected createInsertExpression() {
408
        const tableName = this.getTableName(this.getMainTableName())
328,071✔
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
328,071✔
410
        const returningExpression =
411
            this.connection.driver.options.type === "oracle" &&
328,071✔
412
            this.getValueSets().length > 1
413
                ? null
414
                : this.createReturningExpression("insert") // oracle doesnt support returning with multi-row insert
415
        const columnsExpression = this.createColumnNamesExpression()
328,071✔
416
        let query = "INSERT "
328,071✔
417

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

422
        if (
328,071✔
423
            DriverUtils.isMySQLFamily(this.connection.driver) ||
608,001✔
424
            this.connection.driver.options.type === "aurora-mysql"
425
        ) {
426
            query += `${this.expressionMap.onIgnore ? " IGNORE " : ""}`
48,141✔
427
        }
428

429
        query += `INTO ${tableName}`
328,071✔
430

431
        if (
328,071✔
432
            this.alias !== this.getMainTableName() &&
328,153✔
433
            DriverUtils.isPostgresFamily(this.connection.driver)
434
        ) {
435
            query += ` AS "${this.alias}"`
42✔
436
        }
437

438
        // add columns expression
439
        if (columnsExpression) {
328,071✔
440
            query += `(${columnsExpression})`
327,868✔
441
        } else {
442
            if (
203!
443
                !valuesExpression &&
527✔
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
452
        if (
328,071✔
453
            returningExpression &&
392,432✔
454
            this.connection.driver.options.type === "mssql"
455
        ) {
456
            query += ` OUTPUT ${returningExpression}`
23,976✔
457
        }
458

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

500
                    let conflictTarget = "ON CONFLICT"
550✔
501

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

528
                    const updatePart: string[] = []
538✔
529

530
                    if (Array.isArray(overwrite)) {
538!
531
                        updatePart.push(
538✔
532
                            ...overwrite.map(
533
                                (column) =>
534
                                    `${this.escape(
1,102✔
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

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

551
                        updatePart.push(
538✔
552
                            ...this.expressionMap
553
                                .mainAlias!.metadata.columns.filter(
554
                                    (column) =>
555
                                        column.isUpdateDate &&
2,304✔
556
                                        !overwrite?.includes(
557
                                            column.databaseName,
558
                                        ) &&
559
                                        !(
560
                                            (this.connection.driver.options
360!
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) =>
575
                                        `${this.escape(
66✔
576
                                            column.databaseName,
577
                                        )} = DEFAULT`,
578
                                ),
579
                        )
580

581
                        query += updatePart.join(", ")
538✔
582
                    }
583

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

611
                    if (Array.isArray(overwrite)) {
132!
612
                        query += " ON DUPLICATE KEY UPDATE "
132✔
613
                        query += overwrite
132✔
614
                            .map(
615
                                (column) =>
616
                                    `${this.escape(
276✔
617
                                        column,
618
                                    )} = VALUES(${this.escape(column)})`,
619
                            )
620
                            .join(", ")
621
                        query += " "
132✔
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 {
634
                if (this.expressionMap.onUpdate) {
104,114✔
635
                    throw new TypeORMError(
13✔
636
                        `onUpdate is not supported by the current database driver`,
637
                    )
638
                }
639
            }
640
        }
641

642
        // add RETURNING expression
643
        if (
328,046✔
644
            returningExpression &&
493,712✔
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
        ) {
650
            query += ` RETURNING ${returningExpression}`
40,383✔
651
        }
652

653
        if (
328,046!
654
            returningExpression &&
392,396✔
655
            this.connection.driver.options.type === "spanner"
656
        ) {
NEW
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)
662
        if (
328,046✔
663
            this.connection.driver.options.type === "mssql" &&
461,624✔
664
            this.expressionMap.mainAlias!.hasMetadata &&
665
            this.expressionMap
666
                .mainAlias!.metadata.columns.filter((column) =>
667
                    this.expressionMap.insertColumns.length > 0
221,202!
668
                        ? this.expressionMap.insertColumns.indexOf(
669
                              column.propertyPath,
670
                          ) !== -1
671
                        : column.isInsert,
672
                )
673
                .some((column) =>
674
                    this.isOverridingAutoIncrementBehavior(column),
220,212✔
675
                )
676
        ) {
677
            query = `SET IDENTITY_INSERT ${tableName} ON; ${query}; SET IDENTITY_INSERT ${tableName} OFF`
396✔
678
        }
679

680
        return query
328,046✔
681
    }
682

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

689
        return this.expressionMap.mainAlias!.metadata.columns.filter(
650,664✔
690
            (column) => {
691
                // if user specified list of columns he wants to insert to, then we filter only them
692
                if (this.expressionMap.insertColumns.length)
2,089,854!
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
700
                if (!column.isInsert) {
2,089,854✔
701
                    return false
960✔
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
706
                if (
2,088,894✔
707
                    column.isGenerated &&
3,432,938✔
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" &&
150,166✔
716
                        this.isOverridingAutoIncrementBehavior(column)
717
                    )
718
                )
719
                    return false
104,914✔
720

721
                return true
1,983,980✔
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 {
730
        const columns = this.getInsertedColumns()
328,071✔
731
        if (columns.length > 0)
328,071✔
732
            return columns
325,135✔
733
                .map((column) => this.escape(column.databaseName))
991,990✔
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
738
        if (
2,936✔
739
            !this.expressionMap.mainAlias!.hasMetadata &&
5,675✔
740
            !this.expressionMap.insertColumns.length
741
        ) {
742
            const valueSets = this.getValueSets()
2,733✔
743
            if (valueSets.length === 1)
2,733✔
744
                return Object.keys(valueSets[0])
2,727✔
745
                    .map((columnName) => this.escape(columnName))
14,762✔
746
                    .join(", ")
747
        }
748

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

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

762
        // if column metadatas are given then apply all necessary operations with values
763
        if (columns.length > 0) {
328,071✔
764
            let expression = ""
325,135✔
765
            valueSets.forEach((valueSet, valueSetIndex) => {
325,135✔
766
                columns.forEach((column, columnIndex) => {
1,003,395✔
767
                    if (columnIndex === 0) {
2,780,165✔
768
                        if (
1,003,395✔
769
                            this.connection.driver.options.type === "oracle" &&
1,021,781✔
770
                            valueSets.length > 1
771
                        ) {
772
                            expression += " SELECT "
34✔
773
                        } else if (
1,003,361✔
774
                            this.connection.driver.options.type === "sap" &&
1,021,805✔
775
                            valueSets.length > 1
776
                        ) {
777
                            expression += " SELECT "
12✔
778
                        } else {
779
                            expression += "("
1,003,349✔
780
                        }
781
                    }
782

783
                    // extract real value from the entity
784
                    let value = column.getEntityValue(valueSet)
2,780,165✔
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

793
                    if (!(typeof value === "function")) {
2,780,165✔
794
                        // make sure our value is normalized by a driver
795
                        value = this.connection.driver.preparePersistentValue(
2,779,958✔
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
803
                    if (column.isVersion && value === undefined) {
2,780,165✔
804
                        expression += "1"
883✔
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;
817
                    } else if (column.isDiscriminator) {
2,779,282✔
818
                        expression += this.createParameter(
2,239✔
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
831
                    } else if (
2,777,043✔
832
                        column.isGenerated &&
3,164,038✔
833
                        column.generationStrategy === "uuid" &&
834
                        !this.connection.driver.isUUIDGenerationSupported() &&
835
                        value === undefined
836
                    ) {
837
                        value = uuidv4()
1,475✔
838
                        expression += this.createParameter(value)
1,475✔
839

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

854
                        // if value for this column was not provided then insert default value
855
                    } else if (value === undefined) {
2,775,568✔
856
                        if (
466,420✔
857
                            (this.connection.driver.options.type === "oracle" &&
1,732,078✔
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
866
                            if (
73,636✔
867
                                column.default !== undefined &&
78,866✔
868
                                column.default !== null
869
                            ) {
870
                                // try to use default defined in the column
871
                                expression +=
5,188✔
872
                                    this.connection.driver.normalizeDefault(
873
                                        column,
874
                                    )
875
                            } else if (
68,448!
876
                                this.connection.driver.options.type ===
68,448!
877
                                    "spanner" &&
878
                                column.isGenerated &&
879
                                column.generationStrategy === "uuid"
880
                            ) {
NEW
881
                                expression += "GENERATE_UUID()" // Produces a random universally unique identifier (UUID) as a STRING value.
×
882
                            } else {
883
                                expression += "NULL" // otherwise simply use NULL and pray if column is nullable
68,448✔
884
                            }
885
                        } else {
886
                            expression += "DEFAULT"
392,784✔
887
                        }
888
                    } else if (
2,309,148✔
889
                        value === null &&
2,312,432✔
890
                        (this.connection.driver.options.type === "spanner" ||
891
                            this.connection.driver.options.type === "oracle")
892
                    ) {
893
                        expression += "NULL"
78✔
894

895
                        // support for SQL expressions in queries
896
                    } else if (typeof value === "function") {
2,309,070✔
897
                        expression += value()
213✔
898

899
                        // just any other regular value
900
                    } else {
901
                        if (this.connection.driver.options.type === "mssql")
2,308,857✔
902
                            value = (
214,704✔
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

910
                        const paramName = this.createParameter(value)
2,308,857✔
911

912
                        if (
2,308,857✔
913
                            (DriverUtils.isMySQLFamily(
4,617,714✔
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 = (
923
                                this.connection.driver as
48✔
924
                                    | MysqlDriver
925
                                    | AuroraMysqlDriver
926
                            ).options.legacySpatialSupport
927
                            const geomFromText = useLegacy
48✔
928
                                ? "GeomFromText"
929
                                : "ST_GeomFromText"
930
                            if (column.srid != null) {
48✔
931
                                expression += `${geomFromText}(${paramName}, ${column.srid})`
24✔
932
                            } else {
933
                                expression += `${geomFromText}(${paramName})`
24✔
934
                            }
935
                        } else if (
2,308,809✔
936
                            DriverUtils.isPostgresFamily(
3,497,364✔
937
                                this.connection.driver,
938
                            ) &&
939
                            this.connection.driver.spatialTypes.indexOf(
940
                                column.type,
941
                            ) !== -1
942
                        ) {
943
                            if (column.srid != null) {
96!
944
                                expression += `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`
×
945
                            } else {
946
                                expression += `ST_GeomFromGeoJSON(${paramName})::${column.type}`
96✔
947
                            }
948
                        } else if (
2,308,713✔
949
                            this.connection.driver.options.type === "mssql" &&
2,523,417✔
950
                            this.connection.driver.spatialTypes.indexOf(
951
                                column.type,
952
                            ) !== -1
953
                        ) {
954
                            expression +=
63✔
955
                                column.type +
956
                                "::STGeomFromText(" +
957
                                paramName +
958
                                ", " +
959
                                (column.srid || "0") +
108✔
960
                                ")"
961
                        } else {
962
                            expression += paramName
2,308,650✔
963
                        }
964
                    }
965

966
                    if (columnIndex === columns.length - 1) {
2,780,165✔
967
                        if (valueSetIndex === valueSets.length - 1) {
1,003,395✔
968
                            if (
325,135✔
969
                                this.connection.driver.options.type ===
343,501✔
970
                                    "oracle" &&
971
                                valueSets.length > 1
972
                            ) {
973
                                expression += " FROM DUAL "
14✔
974
                            } else if (
325,121✔
975
                                this.connection.driver.options.type === "sap" &&
343,557✔
976
                                valueSets.length > 1
977
                            ) {
978
                                expression += " FROM dummy "
4✔
979
                            } else {
980
                                expression += ")"
325,117✔
981
                            }
982
                        } else {
983
                            if (
678,260✔
984
                                this.connection.driver.options.type ===
678,280✔
985
                                    "oracle" &&
986
                                valueSets.length > 1
987
                            ) {
988
                                expression += " FROM DUAL UNION ALL "
20✔
989
                            } else if (
678,240✔
990
                                this.connection.driver.options.type === "sap" &&
678,248✔
991
                                valueSets.length > 1
992
                            ) {
993
                                expression += " FROM dummy UNION ALL "
8✔
994
                            } else {
995
                                expression += "), "
678,232✔
996
                            }
997
                        }
998
                    } else {
999
                        expression += ", "
1,776,770✔
1000
                    }
1001
                })
1002
            })
1003
            if (expression === "()") return ""
325,135!
1004

1005
            return expression
325,135✔
1006
        } else {
1007
            // for tables without metadata
1008
            // get values needs to be inserted
1009
            let expression = ""
2,936✔
1010

1011
            valueSets.forEach((valueSet, insertionIndex) => {
2,936✔
1012
                const columns = Object.keys(valueSet)
62,942✔
1013
                columns.forEach((columnName, columnIndex) => {
62,942✔
1014
                    if (columnIndex === 0) {
314,815✔
1015
                        expression += "("
62,780✔
1016
                    }
1017

1018
                    const value = valueSet[columnName]
314,815✔
1019

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

1024
                        // if value for this column was not provided then insert default value
1025
                    } else if (value === undefined) {
314,815✔
1026
                        if (
2,319✔
1027
                            (this.connection.driver.options.type === "oracle" &&
8,216✔
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
                        ) {
1035
                            expression += "NULL"
676✔
1036
                        } else {
1037
                            expression += "DEFAULT"
1,643✔
1038
                        }
1039
                    } else if (
312,496!
1040
                        value === null &&
312,496!
1041
                        this.connection.driver.options.type === "spanner"
1042
                    ) {
1043
                        // just any other regular value
1044
                    } else {
1045
                        expression += this.createParameter(value)
312,496✔
1046
                    }
1047

1048
                    if (columnIndex === Object.keys(valueSet).length - 1) {
314,815✔
1049
                        if (insertionIndex === valueSets.length - 1) {
62,780✔
1050
                            expression += ")"
2,774✔
1051
                        } else {
1052
                            expression += "), "
60,006✔
1053
                        }
1054
                    } else {
1055
                        expression += ", "
252,035✔
1056
                    }
1057
                })
1058
            })
1059
            if (expression === "()") return ""
2,936!
1060
            return expression
2,936✔
1061
        }
1062
    }
1063

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

1071
        if (ObjectUtils.isObject(this.expressionMap.valuesSet))
196,177✔
1072
            return [this.expressionMap.valuesSet]
196,137✔
1073

1074
        throw new InsertValuesMissingError()
40✔
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 {
1085
        return (
264,672✔
1086
            column.isPrimary &&
525,240✔
1087
            column.isGenerated &&
1088
            column.generationStrategy === "increment" &&
1089
            this.getValueSets().some(
1090
                (valueSet) =>
1091
                    column.getEntityValue(valueSet) !== undefined &&
90,711✔
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

© 2026 Coveralls, Inc