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

typeorm / typeorm / 15089093306

17 May 2025 09:03PM UTC coverage: 50.109% (-26.2%) from 76.346%
15089093306

Pull #11437

github

naorpeled
add comment about vector <#>
Pull Request #11437: feat(postgres): support vector data type

5836 of 12767 branches covered (45.71%)

Branch coverage included in aggregate %.

16 of 17 new or added lines in 4 files covered. (94.12%)

6283 existing lines in 64 files now uncovered.

12600 of 24025 relevant lines covered (52.45%)

28708.0 hits per line

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

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

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

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

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

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

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

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

104
            const returningColumns: ColumnMetadata[] = []
51,518✔
105

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

119
            if (
51,518✔
120
                this.expressionMap.updateEntity === true &&
102,249✔
121
                this.expressionMap.mainAlias!.hasMetadata
122
            ) {
123
                if (
50,574✔
124
                    !(
125
                        valueSets.length > 1 &&
54,014✔
126
                        this.connection.driver.options.type === "oracle"
127
                    )
128
                ) {
129
                    this.expressionMap.extraReturningColumns =
50,574✔
130
                        this.expressionMap.mainAlias!.metadata.getInsertionReturningColumns()
131
                }
132

133
                returningColumns.push(
50,574✔
134
                    ...this.expressionMap.extraReturningColumns.filter(
135
                        (c) => !returningColumns.includes(c),
27,089✔
136
                    ),
137
                )
138
            }
139

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

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

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

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

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

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

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

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

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

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

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

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

429
        query += `INTO ${tableName}`
51,684✔
430

431
        if (
51,684✔
432
            this.alias !== this.getMainTableName() &&
51,694✔
433
            DriverUtils.isPostgresFamily(this.connection.driver)
434
        ) {
435
            query += ` AS "${this.alias}"`
5✔
436
        }
437

438
        // add columns expression
439
        if (columnsExpression) {
51,684✔
440
            query += `(${columnsExpression})`
51,662✔
441
        } else {
442
            if (
22!
443
                !valuesExpression &&
54✔
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 (
51,684!
453
            returningExpression &&
54,778✔
454
            this.connection.driver.options.type === "mssql"
455
        ) {
UNCOV
456
            query += ` OUTPUT ${returningExpression}`
×
457
        }
458

459
        // add VALUES expression
460
        if (valuesExpression) {
51,684✔
461
            if (
51,668!
462
                (this.connection.driver.options.type === "oracle" ||
103,336!
463
                    this.connection.driver.options.type === "sap") &&
464
                this.getValueSets().length > 1
465
            ) {
UNCOV
466
                query += ` ${valuesExpression}`
×
467
            } else {
468
                query += ` VALUES ${valuesExpression}`
51,668✔
469
            }
470
        } else {
471
            if (
16!
472
                DriverUtils.isMySQLFamily(this.connection.driver) ||
32✔
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`
16✔
479
            }
480
        }
481
        if (this.expressionMap.onUpdate?.upsertType !== "primary-key") {
51,684✔
482
            if (
51,684!
483
                this.connection.driver.supportedUpsertTypes.includes(
484
                    "on-conflict-do-update",
485
                )
486
            ) {
487
                if (this.expressionMap.onIgnore) {
51,684✔
488
                    query += " ON CONFLICT DO NOTHING "
7✔
489
                } else if (this.expressionMap.onConflict) {
51,677✔
490
                    query += ` ON CONFLICT ${this.expressionMap.onConflict} `
9✔
491
                } else if (this.expressionMap.onUpdate) {
51,668✔
492
                    const {
493
                        overwrite,
494
                        columns,
495
                        conflict,
496
                        skipUpdateIfNoValuesChanged,
497
                        indexPredicate,
498
                    } = this.expressionMap.onUpdate
143✔
499

500
                    let conflictTarget = "ON CONFLICT"
143✔
501

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

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

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

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

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

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

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

642
        // add RETURNING expression
643
        if (
51,679✔
644
            returningExpression &&
54,773!
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,094✔
651
        }
652

653
        if (
51,679!
654
            returningExpression &&
54,773✔
655
            this.connection.driver.options.type === "spanner"
656
        ) {
657
            query += ` THEN RETURN ${returningExpression}`
×
658
        }
659

660
        // Inserting a specific value for an auto-increment primary key in mssql requires enabling IDENTITY_INSERT
661
        // IDENTITY_INSERT can only be enabled for tables where there is an IDENTITY column and only if there is a value to be inserted (i.e. supplying DEFAULT is prohibited if IDENTITY_INSERT is enabled)
662
        if (
51,679!
663
            this.connection.driver.options.type === "mssql" &&
51,679!
664
            this.expressionMap.mainAlias!.hasMetadata &&
665
            this.expressionMap
666
                .mainAlias!.metadata.columns.filter((column) =>
UNCOV
667
                    this.expressionMap.insertColumns.length > 0
×
668
                        ? this.expressionMap.insertColumns.indexOf(
669
                              column.propertyPath,
670
                          ) !== -1
671
                        : column.isInsert,
672
                )
673
                .some((column) =>
UNCOV
674
                    this.isOverridingAutoIncrementBehavior(column),
×
675
                )
676
        ) {
UNCOV
677
            query = `SET IDENTITY_INSERT ${tableName} ON; ${query}; SET IDENTITY_INSERT ${tableName} OFF`
×
678
        }
679

680
        return query
51,679✔
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 []
103,368✔
688

689
        return this.expressionMap.mainAlias!.metadata.columns.filter(
102,756✔
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)
322,068!
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) {
322,068✔
701
                    return false
144✔
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 (
321,924✔
707
                    column.isGenerated &&
517,168✔
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" &&
5,680!
716
                        this.isOverridingAutoIncrementBehavior(column)
717
                    )
718
                )
719
                    return false
5,680✔
720

721
                return true
316,244✔
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()
51,684✔
731
        if (columns.length > 0)
51,684✔
732
            return columns
51,357✔
733
                .map((column) => this.escape(column.databaseName))
158,122✔
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 (
327✔
739
            !this.expressionMap.mainAlias!.hasMetadata &&
633✔
740
            !this.expressionMap.insertColumns.length
741
        ) {
742
            const valueSets = this.getValueSets()
305✔
743
            if (valueSets.length === 1)
305✔
744
                return Object.keys(valueSets[0])
304✔
745
                    .map((columnName) => this.escape(columnName))
1,552✔
746
                    .join(", ")
747
        }
748

749
        // get a table name and all column database names
750
        return this.expressionMap.insertColumns
23✔
751
            .map((columnName) => this.escape(columnName))
5✔
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()
51,684✔
760
        const columns = this.getInsertedColumns()
51,684✔
761

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

783
                    // extract real value from the entity
784
                    let value = column.getEntityValue(valueSet)
359,709✔
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")) {
359,709✔
794
                        // make sure our value is normalized by a driver
795
                        value = this.connection.driver.preparePersistentValue(
359,689✔
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) {
359,709✔
804
                        expression += "1"
133✔
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) {
359,576✔
818
                        expression += this.createParameter(
333✔
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 (
359,243✔
832
                        column.isGenerated &&
380,544✔
833
                        column.generationStrategy === "uuid" &&
834
                        !this.connection.driver.isUUIDGenerationSupported() &&
835
                        value === undefined
836
                    ) {
837
                        value = uuidv4()
345✔
838
                        expression += this.createParameter(value)
345✔
839

840
                        if (
345✔
841
                            !(
842
                                valueSetIndex in
843
                                this.expressionMap.locallyGenerated
844
                            )
845
                        ) {
846
                            this.expressionMap.locallyGenerated[valueSetIndex] =
339✔
847
                                {}
848
                        }
849
                        column.setEntityValue(
345✔
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) {
358,898✔
856
                        if (
32,632✔
857
                            (this.connection.driver.options.type === "oracle" &&
73,064!
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 (
28,732✔
867
                                column.default !== undefined &&
30,604✔
868
                                column.default !== null
869
                            ) {
870
                                // try to use default defined in the column
871
                                expression +=
1,857✔
872
                                    this.connection.driver.normalizeDefault(
873
                                        column,
874
                                    )
875
                            } else if (
26,875!
876
                                this.connection.driver.options.type ===
26,875!
877
                                    "spanner" &&
878
                                column.isGenerated &&
879
                                column.generationStrategy === "uuid"
880
                            ) {
881
                                expression += "GENERATE_UUID()" // Produces a random universally unique identifier (UUID) as a STRING value.
×
882
                            } else {
883
                                expression += "NULL" // otherwise simply use NULL and pray if column is nullable
26,875✔
884
                            }
885
                        } else {
886
                            expression += "DEFAULT"
3,900✔
887
                        }
888
                    } else if (
326,266!
889
                        value === null &&
326,758✔
890
                        (this.connection.driver.options.type === "spanner" ||
891
                            this.connection.driver.options.type === "oracle")
892
                    ) {
UNCOV
893
                        expression += "NULL"
×
894

895
                        // support for SQL expressions in queries
896
                    } else if (typeof value === "function") {
326,266✔
897
                        expression += value()
20✔
898

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

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

910
                        const paramName = this.createParameter(value)
326,246✔
911

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

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

1005
            return expression
51,357✔
1006
        } else {
1007
            // for tables without metadata
1008
            // get values needs to be inserted
1009
            let expression = ""
327✔
1010

1011
            valueSets.forEach((valueSet, insertionIndex) => {
327✔
1012
                const columns = Object.keys(valueSet)
10,328✔
1013
                columns.forEach((columnName, columnIndex) => {
10,328✔
1014
                    if (columnIndex === 0) {
51,560✔
1015
                        expression += "("
10,312✔
1016
                    }
1017

1018
                    const value = valueSet[columnName]
51,560✔
1019

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

1024
                        // if value for this column was not provided then insert default value
1025
                    } else if (value === undefined) {
51,560✔
1026
                        if (
330✔
1027
                            (this.connection.driver.options.type === "oracle" &&
880!
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"
220✔
1036
                        } else {
1037
                            expression += "DEFAULT"
110✔
1038
                        }
1039
                    } else if (
51,230!
1040
                        value === null &&
51,230!
1041
                        this.connection.driver.options.type === "spanner"
1042
                    ) {
1043
                        // just any other regular value
1044
                    } else {
1045
                        expression += this.createParameter(value)
51,230✔
1046
                    }
1047

1048
                    if (columnIndex === Object.keys(valueSet).length - 1) {
51,560✔
1049
                        if (insertionIndex === valueSets.length - 1) {
10,312✔
1050
                            expression += ")"
311✔
1051
                        } else {
1052
                            expression += "), "
10,001✔
1053
                        }
1054
                    } else {
1055
                        expression += ", "
41,248✔
1056
                    }
1057
                })
1058
            })
1059
            if (expression === "()") return ""
327!
1060
            return expression
327✔
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))
103,525✔
1069
            return this.expressionMap.valuesSet
95,732✔
1070

1071
        if (ObjectUtils.isObject(this.expressionMap.valuesSet))
7,793✔
1072
            return [this.expressionMap.valuesSet]
7,787✔
1073

1074
        throw new InsertValuesMissingError()
6✔
1075
    }
1076

1077
    /**
1078
     * Checks if column is an auto-generated primary key, but the current insertion specifies a value for it.
1079
     *
1080
     * @param column
1081
     */
1082
    protected isOverridingAutoIncrementBehavior(
1083
        column: ColumnMetadata,
1084
    ): boolean {
UNCOV
1085
        return (
×
1086
            column.isPrimary &&
×
1087
            column.isGenerated &&
1088
            column.generationStrategy === "increment" &&
1089
            this.getValueSets().some(
1090
                (valueSet) =>
UNCOV
1091
                    column.getEntityValue(valueSet) !== undefined &&
×
1092
                    column.getEntityValue(valueSet) !== null,
1093
            )
1094
        )
1095
    }
1096
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc