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

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

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

21
/**
22
 * Allows to build complex sql queries in a fashion way and execute those queries.
23
 */
24
export class InsertQueryBuilder<
4✔
25
    Entity extends ObjectLiteral,
26
> extends QueryBuilder<Entity> {
27
    readonly "@instanceof" = Symbol.for("InsertQueryBuilder")
35,461✔
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()
35,446✔
38
        sql += this.createCteExpression()
35,446✔
39
        sql += this.createInsertExpression()
35,446✔
40
        return this.replacePropertyNamesForTheWholeQuery(sql.trim())
35,442✔
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()
35,339✔
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()
35,335✔
59

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

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

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

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

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

104
            const returningColumns: ColumnMetadata[] = []
35,327✔
105

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

119
            if (
35,327✔
120
                this.expressionMap.updateEntity === true &&
70,162✔
121
                this.expressionMap.mainAlias!.hasMetadata
122
            ) {
123
                if (
34,741✔
124
                    !(
125
                        valueSets.length > 1 &&
36,284✔
126
                        this.connection.driver.options.type === "oracle"
127
                    )
128
                ) {
129
                    this.expressionMap.extraReturningColumns =
34,734✔
130
                        this.expressionMap.mainAlias!.metadata.getInsertionReturningColumns()
131
                }
132

133
                returningColumns.push(
34,741✔
134
                    ...this.expressionMap.extraReturningColumns.filter(
135
                        (c) => !returningColumns.includes(c),
17,768✔
136
                    ),
137
                )
138
            }
139

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

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

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

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

165
            const insertResult = InsertResult.from(queryResult)
35,312✔
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 (
35,312✔
171
                this.expressionMap.updateEntity === true &&
70,132✔
172
                this.expressionMap.mainAlias!.hasMetadata
173
            ) {
174
                // console.time(".updating entity");
175
                await returningResultsEntityUpdator.insert(
34,726✔
176
                    insertResult,
177
                    valueSets,
178
                )
179
                // console.timeEnd(".updating entity");
180
            }
181

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

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

205
            return insertResult
35,312✔
206
        } catch (error) {
207
            // rollback transaction if we started it
208
            if (transactionStartedByUs) {
15!
209
                try {
×
210
                    await queryRunner.rollbackTransaction()
×
211
                } catch (rollbackError) {}
212
            }
213
            throw error
15✔
214
        } finally {
215
            // console.time(".releasing connection");
216
            if (queryRunner !== this.queryRunner) {
35,327✔
217
                // means we created our own query runner
218
                await queryRunner.release()
228✔
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)
35,441!
237
            ? entityTarget.options.name
238
            : entityTarget
239
        const mainAlias = this.createFromAlias(entityTarget)
35,441✔
240
        this.expressionMap.setMainAlias(mainAlias)
35,441✔
241
        this.expressionMap.insertColumns = columns || []
35,441✔
242
        return this as any as InsertQueryBuilder<T>
35,441✔
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
35,457✔
254
        return this
35,457✔
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")) {
4✔
304
            throw new ReturningStatementNotSupportedError()
3✔
305
        }
306

307
        this.expressionMap.returning = returning
1✔
308
        return this
1✔
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
35,017✔
318
        return this
35,017✔
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
4✔
328
        return this
4✔
329
    }
330

331
    /**
332
     * Adds additional ignore statement supported in databases.
333
     */
334
    orIgnore(statement: string | boolean = true): this {
×
335
        this.expressionMap.onIgnore = !!statement
2✔
336
        return this
2✔
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)) {
65!
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 = {
65✔
390
            overwrite: statementOrOverwrite,
391
            conflict: conflictTarget,
392
            skipUpdateIfNoValuesChanged:
393
                orUpdateOptions?.skipUpdateIfNoValuesChanged,
394
            indexPredicate: orUpdateOptions?.indexPredicate,
395
            upsertType: orUpdateOptions?.upsertType,
396
        }
397
        return this
65✔
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())
35,446✔
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
35,446✔
410
        const returningExpression =
411
            this.connection.driver.options.type === "oracle" &&
35,446✔
412
            this.getValueSets().length > 1
413
                ? null
414
                : this.createReturningExpression("insert") // oracle doesnt support returning with multi-row insert
415
        const columnsExpression = this.createColumnNamesExpression()
35,446✔
416
        let query = "INSERT "
35,446✔
417

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

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

429
        query += `INTO ${tableName}`
35,446✔
430

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

438
        // add columns expression
439
        if (columnsExpression) {
35,446!
440
            query += `(${columnsExpression})`
35,446✔
441
        } else {
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
452
        if (
35,446!
453
            returningExpression &&
38,737✔
454
            this.connection.driver.options.type === "mssql"
455
        ) {
456
            query += ` OUTPUT ${returningExpression}`
×
457
        }
458

459
        // add VALUES expression
460
        if (valuesExpression) {
35,446!
461
            if (
35,446✔
462
                (this.connection.driver.options.type === "oracle" ||
70,892✔
463
                    this.connection.driver.options.type === "sap") &&
464
                this.getValueSets().length > 1
465
            ) {
466
                query += ` ${valuesExpression}`
7✔
467
            } else {
468
                query += ` VALUES ${valuesExpression}`
35,439✔
469
            }
470
        } else {
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 {
478
                query += ` DEFAULT VALUES`
×
479
            }
480
        }
481
        if (this.expressionMap.onUpdate?.upsertType !== "primary-key") {
35,446✔
482
            if (
35,446✔
483
                this.connection.driver.supportedUpsertTypes.includes(
484
                    "on-conflict-do-update",
485
                )
486
            ) {
487
                if (this.expressionMap.onIgnore) {
26,243✔
488
                    query += " ON CONFLICT DO NOTHING "
2✔
489
                } else if (this.expressionMap.onConflict) {
26,241✔
490
                    query += ` ON CONFLICT ${this.expressionMap.onConflict} `
4✔
491
                } else if (this.expressionMap.onUpdate) {
26,237✔
492
                    const {
493
                        overwrite,
494
                        columns,
495
                        conflict,
496
                        skipUpdateIfNoValuesChanged,
497
                        indexPredicate,
498
                    } = this.expressionMap.onUpdate
64✔
499

500
                    let conflictTarget = "ON CONFLICT"
64✔
501

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

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

530
                    if (Array.isArray(overwrite)) {
61!
531
                        updatePart.push(
61✔
532
                            ...overwrite.map(
533
                                (column) =>
534
                                    `${this.escape(
133✔
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) {
61✔
549
                        query += ` ${conflictTarget} DO UPDATE SET `
61✔
550

551
                        updatePart.push(
61✔
552
                            ...this.expressionMap
553
                                .mainAlias!.metadata.columns.filter(
554
                                    (column) =>
555
                                        column.isUpdateDate &&
261✔
556
                                        !overwrite?.includes(
557
                                            column.databaseName,
558
                                        ) &&
559
                                        !(
560
                                            (this.connection.driver.options
24!
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(
×
576
                                            column.databaseName,
577
                                        )} = DEFAULT`,
578
                                ),
579
                        )
580

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

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

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

642
        // add RETURNING expression
643
        if (
35,442✔
644
            returningExpression &&
42,022!
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}`
3,290✔
651
        }
652

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

673
        return query
35,442✔
674
    }
675

676
    /**
677
     * Gets list of columns where values must be inserted to.
678
     */
679
    protected getInsertedColumns(): ColumnMetadata[] {
680
        if (!this.expressionMap.mainAlias!.hasMetadata) return []
70,892✔
681

682
        return this.expressionMap.mainAlias!.metadata.columns.filter(
70,484✔
683
            (column) => {
684
                // if user specified list of columns he wants to insert to, then we filter only them
685
                if (this.expressionMap.insertColumns.length)
217,362!
686
                    return (
×
687
                        this.expressionMap.insertColumns.indexOf(
688
                            column.propertyPath,
689
                        ) !== -1
690
                    )
691

692
                // skip columns the user doesn't want included by default
693
                if (!column.isInsert) {
217,362✔
694
                    return false
96✔
695
                }
696

697
                // if user did not specified such list then return all columns except auto-increment one
698
                // for Oracle we return auto-increment column as well because Oracle does not support DEFAULT VALUES expression
699
                if (
217,266!
700
                    column.isGenerated &&
330,498!
701
                    column.generationStrategy === "increment" &&
702
                    !(this.connection.driver.options.type === "spanner") &&
703
                    !(this.connection.driver.options.type === "oracle") &&
704
                    !DriverUtils.isSQLiteFamily(this.connection.driver) &&
705
                    !DriverUtils.isMySQLFamily(this.connection.driver) &&
706
                    !(this.connection.driver.options.type === "aurora-mysql") &&
707
                    !(
708
                        this.connection.driver.options.type === "mssql" &&
×
709
                        this.isOverridingAutoIncrementBehavior(column)
710
                    )
711
                )
712
                    return false
×
713

714
                return true
217,266✔
715
            },
716
        )
717
    }
718

719
    /**
720
     * Creates a columns string where values must be inserted to for INSERT INTO expression.
721
     */
722
    protected createColumnNamesExpression(): string {
723
        const columns = this.getInsertedColumns()
35,446✔
724
        if (columns.length > 0)
35,446✔
725
            return columns
35,242✔
726
                .map((column) => this.escape(column.databaseName))
108,633✔
727
                .join(", ")
728

729
        // in the case if there are no insert columns specified and table without metadata used
730
        // we get columns from the inserted value map, in the case if only one inserted map is specified
731
        if (
204✔
732
            !this.expressionMap.mainAlias!.hasMetadata &&
408✔
733
            !this.expressionMap.insertColumns.length
734
        ) {
735
            const valueSets = this.getValueSets()
204✔
736
            if (valueSets.length === 1)
204✔
737
                return Object.keys(valueSets[0])
204✔
738
                    .map((columnName) => this.escape(columnName))
1,076✔
739
                    .join(", ")
740
        }
741

742
        // get a table name and all column database names
743
        return this.expressionMap.insertColumns
×
744
            .map((columnName) => this.escape(columnName))
×
745
            .join(", ")
746
    }
747

748
    /**
749
     * Creates list of values needs to be inserted in the VALUES expression.
750
     */
751
    protected createValuesExpression(): string {
752
        const valueSets = this.getValueSets()
35,446✔
753
        const columns = this.getInsertedColumns()
35,446✔
754

755
        // if column metadatas are given then apply all necessary operations with values
756
        if (columns.length > 0) {
35,446✔
757
            let expression = ""
35,242✔
758
            valueSets.forEach((valueSet, valueSetIndex) => {
35,242✔
759
                columns.forEach((column, columnIndex) => {
48,849✔
760
                    if (columnIndex === 0) {
137,605✔
761
                        if (
48,849✔
762
                            this.connection.driver.options.type === "oracle" &&
57,985✔
763
                            valueSets.length > 1
764
                        ) {
765
                            expression += " SELECT "
17✔
766
                        } else if (
48,832!
767
                            this.connection.driver.options.type === "sap" &&
48,832!
768
                            valueSets.length > 1
769
                        ) {
770
                            expression += " SELECT "
×
771
                        } else {
772
                            expression += "("
48,832✔
773
                        }
774
                    }
775

776
                    // extract real value from the entity
777
                    let value = column.getEntityValue(valueSet)
137,605✔
778

779
                    // if column is relational and value is an object then get real referenced column value from this object
780
                    // for example column value is { question: { id: 1 } }, value will be equal to { id: 1 }
781
                    // and we extract "1" from this object
782
                    /*if (column.referencedColumn && value instanceof Object && !(typeof value === "function")) { // todo: check if we still need it since getEntityValue already has similar code
783
                        value = column.referencedColumn.getEntityValue(value);
784
                    }*/
785

786
                    if (!(typeof value === "function")) {
137,605✔
787
                        // make sure our value is normalized by a driver
788
                        value = this.connection.driver.preparePersistentValue(
137,593✔
789
                            value,
790
                            column,
791
                        )
792
                    }
793

794
                    // newly inserted entities always have a version equal to 1 (first version)
795
                    // also, user-specified version must be empty
796
                    if (column.isVersion && value === undefined) {
137,605✔
797
                        expression += "1"
88✔
798

799
                        // } else if (column.isNestedSetLeft) {
800
                        //     const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
801
                        //     const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
802
                        //     const subQuery = `(SELECT c.max + 1 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
803
                        //     expression += subQuery;
804
                        //
805
                        // } else if (column.isNestedSetRight) {
806
                        //     const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
807
                        //     const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
808
                        //     const subQuery = `(SELECT c.max + 2 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
809
                        //     expression += subQuery;
810
                    } else if (column.isDiscriminator) {
137,517✔
811
                        expression += this.createParameter(
218✔
812
                            this.expressionMap.mainAlias!.metadata
813
                                .discriminatorValue,
814
                        )
815
                        // return "1";
816

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

823
                        // 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
824
                    } else if (
137,299✔
825
                        column.isGenerated &&
153,465✔
826
                        column.generationStrategy === "uuid" &&
827
                        !this.connection.driver.isUUIDGenerationSupported() &&
828
                        value === undefined
829
                    ) {
830
                        value = uuidv4()
245✔
831
                        expression += this.createParameter(value)
245✔
832

833
                        if (
245✔
834
                            !(
835
                                valueSetIndex in
836
                                this.expressionMap.locallyGenerated
837
                            )
838
                        ) {
839
                            this.expressionMap.locallyGenerated[valueSetIndex] =
239✔
840
                                {}
841
                        }
842
                        column.setEntityValue(
245✔
843
                            this.expressionMap.locallyGenerated[valueSetIndex],
844
                            value,
845
                        )
846

847
                        // if value for this column was not provided then insert default value
848
                    } else if (value === undefined) {
137,054✔
849
                        if (
22,111✔
850
                            (this.connection.driver.options.type === "oracle" &&
59,021✔
851
                                valueSets.length > 1) ||
852
                            DriverUtils.isSQLiteFamily(
853
                                this.connection.driver,
854
                            ) ||
855
                            this.connection.driver.options.type === "sap" ||
856
                            this.connection.driver.options.type === "spanner"
857
                        ) {
858
                            // unfortunately sqlite does not support DEFAULT expression in INSERT queries
859
                            if (
17,178✔
860
                                column.default !== undefined &&
18,281✔
861
                                column.default !== null
862
                            ) {
863
                                // try to use default defined in the column
864
                                expression +=
1,094✔
865
                                    this.connection.driver.normalizeDefault(
866
                                        column,
867
                                    )
868
                            } else {
869
                                expression += "NULL" // otherwise simply use NULL and pray if column is nullable
16,084✔
870
                            }
871
                        } else {
872
                            expression += "DEFAULT"
4,933✔
873
                        }
874
                    } else if (
114,943✔
875
                        value === null &&
115,259✔
876
                        (this.connection.driver.options.type === "spanner" ||
877
                            this.connection.driver.options.type === "oracle")
878
                    ) {
879
                        expression += "NULL"
39✔
880

881
                        // support for SQL expressions in queries
882
                    } else if (typeof value === "function") {
114,904✔
883
                        expression += value()
15✔
884

885
                        // just any other regular value
886
                    } else {
887
                        if (this.connection.driver.options.type === "mssql")
114,889!
888
                            value = (
×
889
                                this.connection.driver as SqlServerDriver
890
                            ).parametrizeValue(column, value)
891

892
                        // we need to store array values in a special class to make sure parameter replacement will work correctly
893
                        // if (value instanceof Array)
894
                        //     value = new ArrayParameter(value);
895

896
                        const paramName = this.createParameter(value)
114,889✔
897

898
                        if (
114,889!
899
                            (DriverUtils.isMySQLFamily(
229,778!
900
                                this.connection.driver,
901
                            ) ||
902
                                this.connection.driver.options.type ===
903
                                    "aurora-mysql") &&
904
                            this.connection.driver.spatialTypes.indexOf(
905
                                column.type,
906
                            ) !== -1
907
                        ) {
908
                            const useLegacy = (
909
                                this.connection.driver as
×
910
                                    | MysqlDriver
911
                                    | AuroraMysqlDriver
912
                            ).options.legacySpatialSupport
913
                            const geomFromText = useLegacy
×
914
                                ? "GeomFromText"
915
                                : "ST_GeomFromText"
916
                            if (column.srid != null) {
×
917
                                expression += `${geomFromText}(${paramName}, ${column.srid})`
×
918
                            } else {
919
                                expression += `${geomFromText}(${paramName})`
×
920
                            }
921
                        } else if (
114,889!
922
                            DriverUtils.isPostgresFamily(
114,889!
923
                                this.connection.driver,
924
                            ) &&
925
                            this.connection.driver.spatialTypes.indexOf(
926
                                column.type,
927
                            ) !== -1
928
                        ) {
929
                            if (column.srid != null) {
×
930
                                expression += `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`
×
931
                            } else {
932
                                expression += `ST_GeomFromGeoJSON(${paramName})::${column.type}`
×
933
                            }
934
                        } else if (
114,889!
935
                            this.connection.driver.options.type === "mssql" &&
114,889!
936
                            this.connection.driver.spatialTypes.indexOf(
937
                                column.type,
938
                            ) !== -1
939
                        ) {
940
                            expression +=
×
941
                                column.type +
942
                                "::STGeomFromText(" +
943
                                paramName +
944
                                ", " +
945
                                (column.srid || "0") +
×
946
                                ")"
947
                        } else {
948
                            expression += paramName
114,889✔
949
                        }
950
                    }
951

952
                    if (columnIndex === columns.length - 1) {
137,605✔
953
                        if (valueSetIndex === valueSets.length - 1) {
48,849✔
954
                            if (
35,242✔
955
                                this.connection.driver.options.type ===
44,368✔
956
                                    "oracle" &&
957
                                valueSets.length > 1
958
                            ) {
959
                                expression += " FROM DUAL "
7✔
960
                            } else if (
35,235!
961
                                this.connection.driver.options.type === "sap" &&
35,235!
962
                                valueSets.length > 1
963
                            ) {
964
                                expression += " FROM dummy "
×
965
                            } else {
966
                                expression += ")"
35,235✔
967
                            }
968
                        } else {
969
                            if (
13,607✔
970
                                this.connection.driver.options.type ===
13,617✔
971
                                    "oracle" &&
972
                                valueSets.length > 1
973
                            ) {
974
                                expression += " FROM DUAL UNION ALL "
10✔
975
                            } else if (
13,597!
976
                                this.connection.driver.options.type === "sap" &&
13,597!
977
                                valueSets.length > 1
978
                            ) {
979
                                expression += " FROM dummy UNION ALL "
×
980
                            } else {
981
                                expression += "), "
13,597✔
982
                            }
983
                        }
984
                    } else {
985
                        expression += ", "
88,756✔
986
                    }
987
                })
988
            })
989
            if (expression === "()") return ""
35,242!
990

991
            return expression
35,242✔
992
        } else {
993
            // for tables without metadata
994
            // get values needs to be inserted
995
            let expression = ""
204✔
996

997
            valueSets.forEach((valueSet, insertionIndex) => {
204✔
998
                const columns = Object.keys(valueSet)
204✔
999
                columns.forEach((columnName, columnIndex) => {
204✔
1000
                    if (columnIndex === 0) {
1,076✔
1001
                        expression += "("
204✔
1002
                    }
1003

1004
                    const value = valueSet[columnName]
1,076✔
1005

1006
                    // support for SQL expressions in queries
1007
                    if (typeof value === "function") {
1,076!
1008
                        expression += value()
×
1009

1010
                        // if value for this column was not provided then insert default value
1011
                    } else if (value === undefined) {
1,076✔
1012
                        if (
282✔
1013
                            (this.connection.driver.options.type === "oracle" &&
936✔
1014
                                valueSets.length > 1) ||
1015
                            DriverUtils.isSQLiteFamily(
1016
                                this.connection.driver,
1017
                            ) ||
1018
                            this.connection.driver.options.type === "sap" ||
1019
                            this.connection.driver.options.type === "spanner"
1020
                        ) {
1021
                            expression += "NULL"
158✔
1022
                        } else {
1023
                            expression += "DEFAULT"
124✔
1024
                        }
1025
                    } else if (
794!
1026
                        value === null &&
794!
1027
                        this.connection.driver.options.type === "spanner"
1028
                    ) {
1029
                        // just any other regular value
1030
                    } else {
1031
                        expression += this.createParameter(value)
794✔
1032
                    }
1033

1034
                    if (columnIndex === Object.keys(valueSet).length - 1) {
1,076✔
1035
                        if (insertionIndex === valueSets.length - 1) {
204!
1036
                            expression += ")"
204✔
1037
                        } else {
1038
                            expression += "), "
×
1039
                        }
1040
                    } else {
1041
                        expression += ", "
872✔
1042
                    }
1043
                })
1044
            })
1045
            if (expression === "()") return ""
204!
1046
            return expression
204✔
1047
        }
1048
    }
1049

1050
    /**
1051
     * Gets array of values need to be inserted into the target table.
1052
     */
1053
    protected getValueSets(): ObjectLiteral[] {
1054
        if (Array.isArray(this.expressionMap.valuesSet))
89,395✔
1055
            return this.expressionMap.valuesSet
49,058✔
1056

1057
        if (ObjectUtils.isObject(this.expressionMap.valuesSet))
40,337✔
1058
            return [this.expressionMap.valuesSet]
40,333✔
1059

1060
        throw new InsertValuesMissingError()
4✔
1061
    }
1062

1063
    /**
1064
     * Checks if column is an auto-generated primary key, but the current insertion specifies a value for it.
1065
     *
1066
     * @param column
1067
     */
1068
    protected isOverridingAutoIncrementBehavior(
1069
        column: ColumnMetadata,
1070
    ): boolean {
1071
        return (
×
1072
            column.isPrimary &&
×
1073
            column.isGenerated &&
1074
            column.generationStrategy === "increment" &&
1075
            this.getValueSets().some(
1076
                (valueSet) =>
1077
                    column.getEntityValue(valueSet) !== undefined &&
×
1078
                    column.getEntityValue(valueSet) !== null,
1079
            )
1080
        )
1081
    }
1082
}
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