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

typeorm / typeorm / 19549987525

20 Nov 2025 08:11PM UTC coverage: 80.769% (+4.3%) from 76.433%
19549987525

push

github

web-flow
ci: run tests on commits to master and next (#11783)

Co-authored-by: Oleg "OSA413" Sokolov <OSA413@users.noreply.github.com>

26500 of 32174 branches covered (82.36%)

Branch coverage included in aggregate %.

91252 of 113615 relevant lines covered (80.32%)

88980.79 hits per line

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

81.53
/src/query-builder/UpdateQueryBuilder.ts
1
import { ColumnMetadata } from "../metadata/ColumnMetadata"
26✔
2
import { QueryBuilder } from "./QueryBuilder"
26✔
3
import { ObjectLiteral } from "../common/ObjectLiteral"
26✔
4
import { DataSource } from "../data-source/DataSource"
26✔
5
import { QueryRunner } from "../query-runner/QueryRunner"
26✔
6
import { WhereExpressionBuilder } from "./WhereExpressionBuilder"
26✔
7
import { Brackets } from "./Brackets"
26✔
8
import { UpdateResult } from "./result/UpdateResult"
26✔
9
import { ReturningStatementNotSupportedError } from "../error/ReturningStatementNotSupportedError"
26✔
10
import { ReturningResultsEntityUpdator } from "./ReturningResultsEntityUpdator"
26✔
11
import { MysqlDriver } from "../driver/mysql/MysqlDriver"
26✔
12
import { OrderByCondition } from "../find-options/OrderByCondition"
26✔
13
import { LimitOnUpdateNotSupportedError } from "../error/LimitOnUpdateNotSupportedError"
26✔
14
import { UpdateValuesMissingError } from "../error/UpdateValuesMissingError"
26✔
15
import { QueryDeepPartialEntity } from "./QueryPartialEntity"
26✔
16
import { AuroraMysqlDriver } from "../driver/aurora-mysql/AuroraMysqlDriver"
26✔
17
import { TypeORMError } from "../error"
26✔
18
import { EntityPropertyNotFoundError } from "../error/EntityPropertyNotFoundError"
26✔
19
import { SqlServerDriver } from "../driver/sqlserver/SqlServerDriver"
26✔
20
import { DriverUtils } from "../driver/DriverUtils"
26✔
21

26✔
22
/**
26✔
23
 * Allows to build complex sql queries in a fashion way and execute those queries.
26✔
24
 */
26✔
25
export class UpdateQueryBuilder<Entity extends ObjectLiteral>
26✔
26
    extends QueryBuilder<Entity>
26✔
27
    implements WhereExpressionBuilder
26✔
28
{
26✔
29
    readonly "@instanceof" = Symbol.for("UpdateQueryBuilder")
26✔
30

26✔
31
    // -------------------------------------------------------------------------
26✔
32
    // Constructor
26✔
33
    // -------------------------------------------------------------------------
26✔
34

26✔
35
    constructor(
26✔
36
        connectionOrQueryBuilder: DataSource | QueryBuilder<any>,
19,832✔
37
        queryRunner?: QueryRunner,
19,832✔
38
    ) {
19,832✔
39
        super(connectionOrQueryBuilder as any, queryRunner)
19,832✔
40
        this.expressionMap.aliasNamePrefixingEnabled = false
19,832✔
41
    }
19,832✔
42

26✔
43
    // -------------------------------------------------------------------------
26✔
44
    // Public Implemented Methods
26✔
45
    // -------------------------------------------------------------------------
26✔
46

26✔
47
    /**
26✔
48
     * Gets generated SQL query without parameters being replaced.
26✔
49
     */
26✔
50
    getQuery(): string {
26✔
51
        let sql = this.createComment()
18,426✔
52
        sql += this.createCteExpression()
18,426✔
53
        sql += this.createUpdateExpression()
18,426✔
54
        sql += this.createOrderByExpression()
18,426✔
55
        sql += this.createLimitExpression()
18,426✔
56
        return this.replacePropertyNamesForTheWholeQuery(sql.trim())
18,426✔
57
    }
18,426✔
58

26✔
59
    /**
26✔
60
     * Executes sql generated by query builder and returns raw database results.
26✔
61
     */
26✔
62
    async execute(): Promise<UpdateResult> {
26✔
63
        const queryRunner = this.obtainQueryRunner()
18,312✔
64
        let transactionStartedByUs: boolean = false
18,312✔
65

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

18,312✔
76
            // call before updation methods in listeners and subscribers
18,312✔
77
            if (
18,312✔
78
                this.expressionMap.callListeners === true &&
18,312✔
79
                this.expressionMap.mainAlias!.hasMetadata
7,926✔
80
            ) {
18,312✔
81
                await queryRunner.broadcaster.broadcast(
7,674✔
82
                    "BeforeUpdate",
7,674✔
83
                    this.expressionMap.mainAlias!.metadata,
7,674✔
84
                    this.expressionMap.valuesSet,
7,674✔
85
                )
7,674✔
86
            }
7,674✔
87

18,312✔
88
            let declareSql: string | null = null
18,312✔
89
            let selectOutputSql: string | null = null
18,312✔
90

18,312✔
91
            // if update entity mode is enabled we may need extra columns for the returning statement
18,312✔
92
            const returningResultsEntityUpdator =
18,312✔
93
                new ReturningResultsEntityUpdator(
18,312✔
94
                    queryRunner,
18,312✔
95
                    this.expressionMap,
18,312✔
96
                )
18,312✔
97

18,312✔
98
            const returningColumns: ColumnMetadata[] = []
18,312✔
99

18,312✔
100
            if (
18,312✔
101
                Array.isArray(this.expressionMap.returning) &&
18,312✔
102
                this.expressionMap.mainAlias!.hasMetadata
2✔
103
            ) {
18,312✔
104
                for (const columnPath of this.expressionMap.returning) {
2✔
105
                    returningColumns.push(
4✔
106
                        ...this.expressionMap.mainAlias!.metadata.findColumnsWithPropertyPath(
4✔
107
                            columnPath,
4✔
108
                        ),
4✔
109
                    )
4✔
110
                }
4✔
111
            }
2✔
112

18,312✔
113
            if (
18,312✔
114
                this.expressionMap.updateEntity === true &&
18,312✔
115
                this.expressionMap.mainAlias!.hasMetadata &&
18,312✔
116
                this.expressionMap.whereEntities.length > 0
18,032✔
117
            ) {
18,312✔
118
                this.expressionMap.extraReturningColumns =
2,946✔
119
                    returningResultsEntityUpdator.getUpdationReturningColumns()
2,946✔
120

2,946✔
121
                returningColumns.push(
2,946✔
122
                    ...this.expressionMap.extraReturningColumns.filter(
2,946✔
123
                        (c) => !returningColumns.includes(c),
2,946✔
124
                    ),
2,946✔
125
                )
2,946✔
126
            }
2,946✔
127

18,312✔
128
            if (
18,312✔
129
                returningColumns.length > 0 &&
18,312✔
130
                this.connection.driver.options.type === "mssql"
324✔
131
            ) {
18,312!
132
                declareSql = (
28✔
133
                    this.connection.driver as SqlServerDriver
28✔
134
                ).buildTableVariableDeclaration(
28✔
135
                    "@OutputTable",
28✔
136
                    returningColumns,
28✔
137
                )
28✔
138
                selectOutputSql = `SELECT * FROM @OutputTable`
28✔
139
            }
28✔
140

18,312✔
141
            // execute update query
18,312✔
142
            const [updateSql, parameters] = this.getQueryAndParameters()
18,312✔
143

18,312✔
144
            const statements = [declareSql, updateSql, selectOutputSql]
18,312✔
145
            const queryResult = await queryRunner.query(
18,312✔
146
                statements.filter((sql) => sql != null).join(";\n\n"),
18,312✔
147
                parameters,
18,312✔
148
                true,
18,312✔
149
            )
18,312✔
150
            const updateResult = UpdateResult.from(queryResult)
18,116✔
151

18,116✔
152
            // if we are updating entities and entity updation is enabled we must update some of entity columns (like version, update date, etc.)
18,116✔
153
            if (
18,116✔
154
                this.expressionMap.updateEntity === true &&
18,116✔
155
                this.expressionMap.mainAlias!.hasMetadata &&
18,312✔
156
                this.expressionMap.whereEntities.length > 0
17,836✔
157
            ) {
18,312✔
158
                await returningResultsEntityUpdator.update(
2,946✔
159
                    updateResult,
2,946✔
160
                    this.expressionMap.whereEntities,
2,946✔
161
                )
2,946✔
162
            }
2,946✔
163

18,116✔
164
            // call after updation methods in listeners and subscribers
18,116✔
165
            if (
18,116✔
166
                this.expressionMap.callListeners === true &&
18,116✔
167
                this.expressionMap.mainAlias!.hasMetadata
7,738✔
168
            ) {
18,312✔
169
                await queryRunner.broadcaster.broadcast(
7,486✔
170
                    "AfterUpdate",
7,486✔
171
                    this.expressionMap.mainAlias!.metadata,
7,486✔
172
                    this.expressionMap.valuesSet,
7,486✔
173
                )
7,486✔
174
            }
7,486✔
175

18,116✔
176
            // close transaction if we started it
18,116✔
177
            if (transactionStartedByUs) await queryRunner.commitTransaction()
18,312!
178

18,116✔
179
            return updateResult
18,116✔
180
        } catch (error) {
18,312✔
181
            // rollback transaction if we started it
196✔
182
            if (transactionStartedByUs) {
196!
183
                try {
×
184
                    await queryRunner.rollbackTransaction()
×
185
                } catch (rollbackError) {}
×
186
            }
×
187
            throw error
196✔
188
        } finally {
18,312!
189
            if (queryRunner !== this.queryRunner) {
18,312✔
190
                // means we created our own query runner
2,138✔
191
                await queryRunner.release()
2,138✔
192
            }
2,138✔
193
        }
18,312✔
194
    }
18,312✔
195

26✔
196
    // -------------------------------------------------------------------------
26✔
197
    // Public Methods
26✔
198
    // -------------------------------------------------------------------------
26✔
199

26✔
200
    /**
26✔
201
     * Values needs to be updated.
26✔
202
     */
26✔
203
    set(values: QueryDeepPartialEntity<Entity>): this {
26✔
204
        this.expressionMap.valuesSet = values
18,476✔
205
        return this
18,476✔
206
    }
18,476✔
207

26✔
208
    /**
26✔
209
     * Sets WHERE condition in the query builder.
26✔
210
     * If you had previously WHERE expression defined,
26✔
211
     * calling this function will override previously set WHERE conditions.
26✔
212
     * Additionally you can add parameters used in where expression.
26✔
213
     */
26✔
214
    where(
26✔
215
        where:
16,121✔
216
            | string
16,121✔
217
            | ((qb: this) => string)
16,121✔
218
            | Brackets
16,121✔
219
            | ObjectLiteral
16,121✔
220
            | ObjectLiteral[],
16,121✔
221
        parameters?: ObjectLiteral,
16,121✔
222
    ): this {
16,121✔
223
        this.expressionMap.wheres = [] // don't move this block below since computeWhereParameter can add where expressions
16,121✔
224
        const condition = this.getWhereCondition(where)
16,121✔
225
        if (condition)
16,121✔
226
            this.expressionMap.wheres = [
16,121✔
227
                { type: "simple", condition: condition },
15,953✔
228
            ]
15,953✔
229
        if (parameters) this.setParameters(parameters)
16,121✔
230
        return this
15,953✔
231
    }
15,953✔
232

26✔
233
    /**
26✔
234
     * Adds new AND WHERE condition in the query builder.
26✔
235
     * Additionally you can add parameters used in where expression.
26✔
236
     */
26✔
237
    andWhere(
26✔
238
        where:
72✔
239
            | string
72✔
240
            | ((qb: this) => string)
72✔
241
            | Brackets
72✔
242
            | ObjectLiteral
72✔
243
            | ObjectLiteral[],
72✔
244
        parameters?: ObjectLiteral,
72✔
245
    ): this {
72✔
246
        this.expressionMap.wheres.push({
72✔
247
            type: "and",
72✔
248
            condition: this.getWhereCondition(where),
72✔
249
        })
72✔
250
        if (parameters) this.setParameters(parameters)
72✔
251
        return this
72✔
252
    }
72✔
253

26✔
254
    /**
26✔
255
     * Adds new OR WHERE condition in the query builder.
26✔
256
     * Additionally you can add parameters used in where expression.
26✔
257
     */
26✔
258
    orWhere(
26✔
259
        where:
3,596✔
260
            | string
3,596✔
261
            | ((qb: this) => string)
3,596✔
262
            | Brackets
3,596✔
263
            | ObjectLiteral
3,596✔
264
            | ObjectLiteral[],
3,596✔
265
        parameters?: ObjectLiteral,
3,596✔
266
    ): this {
3,596✔
267
        this.expressionMap.wheres.push({
3,596✔
268
            type: "or",
3,596✔
269
            condition: this.getWhereCondition(where),
3,596✔
270
        })
3,596✔
271
        if (parameters) this.setParameters(parameters)
3,596!
272
        return this
3,596✔
273
    }
3,596✔
274

26✔
275
    /**
26✔
276
     * Sets WHERE condition in the query builder with a condition for the given ids.
26✔
277
     * If you had previously WHERE expression defined,
26✔
278
     * calling this function will override previously set WHERE conditions.
26✔
279
     */
26✔
280
    whereInIds(ids: any | any[]): this {
26✔
281
        return this.where(this.getWhereInIdsCondition(ids))
872✔
282
    }
872✔
283

26✔
284
    /**
26✔
285
     * Adds new AND WHERE with conditions for the given ids.
26✔
286
     */
26✔
287
    andWhereInIds(ids: any | any[]): this {
26✔
288
        return this.andWhere(this.getWhereInIdsCondition(ids))
×
289
    }
×
290

26✔
291
    /**
26✔
292
     * Adds new OR WHERE with conditions for the given ids.
26✔
293
     */
26✔
294
    orWhereInIds(ids: any | any[]): this {
26✔
295
        return this.orWhere(this.getWhereInIdsCondition(ids))
2,974✔
296
    }
2,974✔
297
    /**
26✔
298
     * Optional returning/output clause.
26✔
299
     * This will return given column values.
26✔
300
     */
26✔
301
    output(columns: string[]): this
26✔
302

26✔
303
    /**
26✔
304
     * Optional returning/output clause.
26✔
305
     * Returning is a SQL string containing returning statement.
26✔
306
     */
26✔
307
    output(output: string): this
26✔
308

26✔
309
    /**
26✔
310
     * Optional returning/output clause.
26✔
311
     */
26✔
312
    output(output: string | string[]): this
26✔
313

26✔
314
    /**
26✔
315
     * Optional returning/output clause.
26✔
316
     */
26✔
317
    output(output: string | string[]): this {
26✔
318
        return this.returning(output)
×
319
    }
×
320

26✔
321
    /**
26✔
322
     * Optional returning/output clause.
26✔
323
     * This will return given column values.
26✔
324
     */
26✔
325
    returning(columns: string[]): this
26✔
326

26✔
327
    /**
26✔
328
     * Optional returning/output clause.
26✔
329
     * Returning is a SQL string containing returning statement.
26✔
330
     */
26✔
331
    returning(returning: string): this
26✔
332

26✔
333
    /**
26✔
334
     * Optional returning/output clause.
26✔
335
     */
26✔
336
    returning(returning: string | string[]): this
26✔
337

26✔
338
    /**
26✔
339
     * Optional returning/output clause.
26✔
340
     */
26✔
341
    returning(returning: string | string[]): this {
26✔
342
        // not all databases support returning/output cause
8✔
343
        if (!this.connection.driver.isReturningSqlSupported("update")) {
8!
344
            throw new ReturningStatementNotSupportedError()
×
345
        }
×
346

8✔
347
        this.expressionMap.returning = returning
8✔
348
        return this
8✔
349
    }
8✔
350

26✔
351
    /**
26✔
352
     * Sets ORDER BY condition in the query builder.
26✔
353
     * If you had previously ORDER BY expression defined,
26✔
354
     * calling this function will override previously set ORDER BY conditions.
26✔
355
     *
26✔
356
     * Calling order by without order set will remove all previously set order bys.
26✔
357
     */
26✔
358
    orderBy(): this
26✔
359

26✔
360
    /**
26✔
361
     * Sets ORDER BY condition in the query builder.
26✔
362
     * If you had previously ORDER BY expression defined,
26✔
363
     * calling this function will override previously set ORDER BY conditions.
26✔
364
     */
26✔
365
    orderBy(
26✔
366
        sort: string,
26✔
367
        order?: "ASC" | "DESC",
26✔
368
        nulls?: "NULLS FIRST" | "NULLS LAST",
26✔
369
    ): this
26✔
370

26✔
371
    /**
26✔
372
     * Sets ORDER BY condition in the query builder.
26✔
373
     * If you had previously ORDER BY expression defined,
26✔
374
     * calling this function will override previously set ORDER BY conditions.
26✔
375
     */
26✔
376
    orderBy(order: OrderByCondition): this
26✔
377

26✔
378
    /**
26✔
379
     * Sets ORDER BY condition in the query builder.
26✔
380
     * If you had previously ORDER BY expression defined,
26✔
381
     * calling this function will override previously set ORDER BY conditions.
26✔
382
     */
26✔
383
    orderBy(
26✔
384
        sort?: string | OrderByCondition,
×
385
        order: "ASC" | "DESC" = "ASC",
×
386
        nulls?: "NULLS FIRST" | "NULLS LAST",
×
387
    ): this {
×
388
        if (sort) {
×
389
            if (typeof sort === "object") {
×
390
                this.expressionMap.orderBys = sort as OrderByCondition
×
391
            } else {
×
392
                if (nulls) {
×
393
                    this.expressionMap.orderBys = {
×
394
                        [sort as string]: { order, nulls },
×
395
                    }
×
396
                } else {
×
397
                    this.expressionMap.orderBys = { [sort as string]: order }
×
398
                }
×
399
            }
×
400
        } else {
×
401
            this.expressionMap.orderBys = {}
×
402
        }
×
403
        return this
×
404
    }
×
405

26✔
406
    /**
26✔
407
     * Adds ORDER BY condition in the query builder.
26✔
408
     */
26✔
409
    addOrderBy(
26✔
410
        sort: string,
×
411
        order: "ASC" | "DESC" = "ASC",
×
412
        nulls?: "NULLS FIRST" | "NULLS LAST",
×
413
    ): this {
×
414
        if (nulls) {
×
415
            this.expressionMap.orderBys[sort] = { order, nulls }
×
416
        } else {
×
417
            this.expressionMap.orderBys[sort] = order
×
418
        }
×
419
        return this
×
420
    }
×
421

26✔
422
    /**
26✔
423
     * Sets LIMIT - maximum number of rows to be selected.
26✔
424
     */
26✔
425
    limit(limit?: number): this {
26✔
426
        this.expressionMap.limit = limit
28✔
427
        return this
28✔
428
    }
28✔
429

26✔
430
    /**
26✔
431
     * Indicates if entity must be updated after update operation.
26✔
432
     * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
26✔
433
     * Enabled by default.
26✔
434
     */
26✔
435
    whereEntity(entity: Entity | Entity[]): this {
26✔
436
        if (!this.expressionMap.mainAlias!.hasMetadata)
2,974✔
437
            throw new TypeORMError(
2,974!
438
                `.whereEntity method can only be used on queries which update real entity table.`,
×
439
            )
×
440

2,974✔
441
        this.expressionMap.wheres = []
2,974✔
442
        const entities: Entity[] = Array.isArray(entity) ? entity : [entity]
2,974!
443
        entities.forEach((entity) => {
2,974✔
444
            const entityIdMap =
2,974✔
445
                this.expressionMap.mainAlias!.metadata.getEntityIdMap(entity)
2,974✔
446
            if (!entityIdMap)
2,974✔
447
                throw new TypeORMError(
2,974!
448
                    `Provided entity does not have ids set, cannot perform operation.`,
×
449
                )
×
450

2,974✔
451
            this.orWhereInIds(entityIdMap)
2,974✔
452
        })
2,974✔
453

2,974✔
454
        this.expressionMap.whereEntities = entities
2,974✔
455
        return this
2,974✔
456
    }
2,974✔
457

26✔
458
    /**
26✔
459
     * Indicates if entity must be updated after update operation.
26✔
460
     * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
26✔
461
     * Enabled by default.
26✔
462
     */
26✔
463
    updateEntity(enabled: boolean): this {
26✔
464
        this.expressionMap.updateEntity = enabled
10,444✔
465
        return this
10,444✔
466
    }
10,444✔
467

26✔
468
    // -------------------------------------------------------------------------
26✔
469
    // Protected Methods
26✔
470
    // -------------------------------------------------------------------------
26✔
471

26✔
472
    /**
26✔
473
     * Creates UPDATE express used to perform insert query.
26✔
474
     */
26✔
475
    protected createUpdateExpression() {
26✔
476
        const valuesSet = this.getValueSet()
18,426✔
477
        const metadata = this.expressionMap.mainAlias!.hasMetadata
18,426✔
478
            ? this.expressionMap.mainAlias!.metadata
18,426✔
479
            : undefined
18,426✔
480

18,426✔
481
        // it doesn't make sense to update undefined properties, so just skip them
18,426✔
482
        const valuesSetNormalized: ObjectLiteral = {}
18,426✔
483
        for (const key in valuesSet) {
18,426✔
484
            if (valuesSet[key] !== undefined) {
20,246✔
485
                valuesSetNormalized[key] = valuesSet[key]
20,060✔
486
            }
20,060✔
487
        }
20,246✔
488

18,370✔
489
        // prepare columns and values to be updated
18,370✔
490
        const updateColumnAndValues: string[] = []
18,370✔
491
        const updatedColumns: ColumnMetadata[] = []
18,370✔
492
        if (metadata) {
18,426✔
493
            this.createPropertyPath(metadata, valuesSetNormalized).forEach(
18,094✔
494
                (propertyPath) => {
18,094✔
495
                    // todo: make this and other query builder to work with properly with tables without metadata
19,554✔
496
                    const columns =
19,554✔
497
                        metadata.findColumnsWithPropertyPath(propertyPath)
19,554✔
498

19,554✔
499
                    if (columns.length <= 0) {
19,554✔
500
                        throw new EntityPropertyNotFoundError(
28✔
501
                            propertyPath,
28✔
502
                            metadata,
28✔
503
                        )
28✔
504
                    }
28✔
505

19,526✔
506
                    columns.forEach((column) => {
19,526✔
507
                        if (
21,888✔
508
                            !column.isUpdate ||
21,888✔
509
                            updatedColumns.includes(column)
21,772✔
510
                        ) {
21,888✔
511
                            return
144✔
512
                        }
144✔
513

21,744✔
514
                        updatedColumns.push(column)
21,744✔
515

21,744✔
516
                        //
21,744✔
517
                        let value = column.getEntityValue(valuesSetNormalized)
21,744✔
518
                        if (
21,744✔
519
                            column.referencedColumn &&
21,744✔
520
                            typeof value === "object" &&
21,888✔
521
                            !(value instanceof Date) &&
21,888✔
522
                            value !== null &&
21,888!
523
                            !Buffer.isBuffer(value)
4✔
524
                        ) {
21,888!
525
                            value =
×
526
                                column.referencedColumn.getEntityValue(value)
×
527
                        } else if (!(typeof value === "function")) {
21,888✔
528
                            value =
21,042✔
529
                                this.connection.driver.preparePersistentValue(
21,042✔
530
                                    value,
21,042✔
531
                                    column,
21,042✔
532
                                )
21,042✔
533
                        }
21,042✔
534

21,744✔
535
                        // todo: duplication zone
21,744✔
536
                        if (typeof value === "function") {
21,888✔
537
                            // support for SQL expressions in update query
702✔
538
                            updateColumnAndValues.push(
702✔
539
                                this.escape(column.databaseName) +
702✔
540
                                    " = " +
702✔
541
                                    value(),
702✔
542
                            )
702✔
543
                        } else if (
21,888✔
544
                            (this.connection.driver.options.type === "sap" ||
21,042!
545
                                this.connection.driver.options.type ===
19,658✔
546
                                    "spanner") &&
21,042!
547
                            value === null
1,384✔
548
                        ) {
21,042!
549
                            updateColumnAndValues.push(
68✔
550
                                this.escape(column.databaseName) + " = NULL",
68✔
551
                            )
68✔
552
                        } else {
21,042✔
553
                            if (
20,974✔
554
                                this.connection.driver.options.type === "mssql"
20,974✔
555
                            ) {
20,974!
556
                                value = (
1,650✔
557
                                    this.connection.driver as SqlServerDriver
1,650✔
558
                                ).parametrizeValue(column, value)
1,650✔
559
                            }
1,650✔
560

20,974✔
561
                            const paramName = this.createParameter(value)
20,974✔
562

20,974✔
563
                            let expression = null
20,974✔
564
                            if (
20,974✔
565
                                (DriverUtils.isMySQLFamily(
20,974✔
566
                                    this.connection.driver,
20,974✔
567
                                ) ||
20,974!
568
                                    this.connection.driver.options.type ===
14,814✔
569
                                        "aurora-mysql") &&
20,974!
570
                                this.connection.driver.spatialTypes.indexOf(
6,160✔
571
                                    column.type,
6,160✔
572
                                ) !== -1
6,160✔
573
                            ) {
20,974!
574
                                const useLegacy = (
×
575
                                    this.connection.driver as
×
576
                                        | MysqlDriver
×
577
                                        | AuroraMysqlDriver
×
578
                                ).options.legacySpatialSupport
×
579
                                const geomFromText = useLegacy
×
580
                                    ? "GeomFromText"
×
581
                                    : "ST_GeomFromText"
×
582
                                if (column.srid != null) {
×
583
                                    expression = `${geomFromText}(${paramName}, ${column.srid})`
×
584
                                } else {
×
585
                                    expression = `${geomFromText}(${paramName})`
×
586
                                }
×
587
                            } else if (
20,974✔
588
                                DriverUtils.isPostgresFamily(
20,974✔
589
                                    this.connection.driver,
20,974✔
590
                                ) &&
20,974!
591
                                this.connection.driver.spatialTypes.indexOf(
4,218✔
592
                                    column.type,
4,218✔
593
                                ) !== -1
4,218✔
594
                            ) {
20,974!
595
                                if (column.srid != null) {
10!
596
                                    expression = `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`
×
597
                                } else {
10✔
598
                                    expression = `ST_GeomFromGeoJSON(${paramName})::${column.type}`
10✔
599
                                }
10✔
600
                            } else if (
20,974✔
601
                                this.connection.driver.options.type ===
20,964✔
602
                                    "mssql" &&
20,964!
603
                                this.connection.driver.spatialTypes.indexOf(
1,650✔
604
                                    column.type,
1,650✔
605
                                ) !== -1
1,650✔
606
                            ) {
20,964!
607
                                expression =
4✔
608
                                    column.type +
4✔
609
                                    "::STGeomFromText(" +
4✔
610
                                    paramName +
4✔
611
                                    ", " +
4✔
612
                                    (column.srid || "0") +
4✔
613
                                    ")"
4✔
614
                            } else {
20,964✔
615
                                expression = paramName
20,960✔
616
                            }
20,960✔
617
                            updateColumnAndValues.push(
20,974✔
618
                                this.escape(column.databaseName) +
20,974✔
619
                                    " = " +
20,974✔
620
                                    expression,
20,974✔
621
                            )
20,974✔
622
                        }
20,974✔
623
                    })
19,526✔
624
                },
18,094✔
625
            )
18,094✔
626

18,094✔
627
            // Don't allow calling update only with columns that are `update: false`
18,094✔
628
            if (
18,094✔
629
                updateColumnAndValues.length > 0 ||
18,094✔
630
                Object.keys(valuesSetNormalized).length === 0
56✔
631
            ) {
18,094✔
632
                if (
18,038✔
633
                    metadata.versionColumn &&
18,038✔
634
                    updatedColumns.indexOf(metadata.versionColumn) === -1
170✔
635
                )
18,038✔
636
                    updateColumnAndValues.push(
18,038✔
637
                        this.escape(metadata.versionColumn.databaseName) +
142✔
638
                            " = " +
142✔
639
                            this.escape(metadata.versionColumn.databaseName) +
142✔
640
                            " + 1",
142✔
641
                    )
142✔
642
                if (
18,038✔
643
                    metadata.updateDateColumn &&
18,038✔
644
                    updatedColumns.indexOf(metadata.updateDateColumn) === -1
300✔
645
                )
18,038✔
646
                    updateColumnAndValues.push(
18,038✔
647
                        this.escape(metadata.updateDateColumn.databaseName) +
268✔
648
                            " = CURRENT_TIMESTAMP",
268✔
649
                    ) // todo: fix issue with CURRENT_TIMESTAMP(6) being used, can "DEFAULT" be used?!
18,038✔
650
            }
18,038✔
651
        } else {
18,426✔
652
            Object.keys(valuesSetNormalized).map((key) => {
276✔
653
                const value = valuesSetNormalized[key]
1,102✔
654

1,102✔
655
                // todo: duplication zone
1,102✔
656
                if (typeof value === "function") {
1,102!
657
                    // support for SQL expressions in update query
×
658
                    updateColumnAndValues.push(
×
659
                        this.escape(key) + " = " + value(),
×
660
                    )
×
661
                } else if (
1,102✔
662
                    (this.connection.driver.options.type === "sap" ||
1,102!
663
                        this.connection.driver.options.type === "spanner") &&
1,102!
664
                    value === null
76✔
665
                ) {
1,102!
666
                    updateColumnAndValues.push(this.escape(key) + " = NULL")
×
667
                } else {
1,102✔
668
                    // we need to store array values in a special class to make sure parameter replacement will work correctly
1,102✔
669
                    // if (value instanceof Array)
1,102✔
670
                    //     value = new ArrayParameter(value);
1,102✔
671

1,102✔
672
                    const paramName = this.createParameter(value)
1,102✔
673
                    updateColumnAndValues.push(
1,102✔
674
                        this.escape(key) + " = " + paramName,
1,102✔
675
                    )
1,102✔
676
                }
1,102✔
677
            })
276✔
678
        }
276✔
679

18,342✔
680
        if (updateColumnAndValues.length <= 0) {
18,426✔
681
            throw new UpdateValuesMissingError()
28✔
682
        }
28✔
683

18,314✔
684
        // get a table name and all column database names
18,314✔
685
        const whereExpression = this.createWhereExpression()
18,314✔
686
        const returningExpression = this.createReturningExpression("update")
18,314✔
687

18,314✔
688
        if (returningExpression === "") {
18,350✔
689
            return `UPDATE ${this.getTableName(
18,193✔
690
                this.getMainTableName(),
18,193✔
691
            )} SET ${updateColumnAndValues.join(", ")}${whereExpression}` // todo: how do we replace aliases in where to nothing?
18,193✔
692
        }
18,193✔
693
        if (this.connection.driver.options.type === "mssql") {
4,975!
694
            return `UPDATE ${this.getTableName(
32✔
695
                this.getMainTableName(),
32✔
696
            )} SET ${updateColumnAndValues.join(
32✔
697
                ", ",
32✔
698
            )} OUTPUT ${returningExpression}${whereExpression}`
32✔
699
        }
32✔
700
        if (this.connection.driver.options.type === "spanner") {
4,943!
701
            return `UPDATE ${this.getTableName(
×
702
                this.getMainTableName(),
×
703
            )} SET ${updateColumnAndValues.join(
×
704
                ", ",
×
705
            )}${whereExpression} THEN RETURN ${returningExpression}`
×
706
        }
×
707

89✔
708
        return `UPDATE ${this.getTableName(
89✔
709
            this.getMainTableName(),
89✔
710
        )} SET ${updateColumnAndValues.join(
89✔
711
            ", ",
89✔
712
        )}${whereExpression} RETURNING ${returningExpression}`
89✔
713
    }
89✔
714

26✔
715
    /**
26✔
716
     * Creates "ORDER BY" part of SQL query.
26✔
717
     */
26✔
718
    protected createOrderByExpression() {
26✔
719
        const orderBys = this.expressionMap.orderBys
18,314✔
720
        if (Object.keys(orderBys).length > 0)
18,314✔
721
            return (
18,314!
722
                " ORDER BY " +
×
723
                Object.keys(orderBys)
×
724
                    .map((columnName) => {
×
725
                        if (typeof orderBys[columnName] === "string") {
×
726
                            return (
×
727
                                this.replacePropertyNames(columnName) +
×
728
                                " " +
×
729
                                orderBys[columnName]
×
730
                            )
×
731
                        } else {
×
732
                            return (
×
733
                                this.replacePropertyNames(columnName) +
×
734
                                " " +
×
735
                                (orderBys[columnName] as any).order +
×
736
                                " " +
×
737
                                (orderBys[columnName] as any).nulls
×
738
                            )
×
739
                        }
×
740
                    })
×
741
                    .join(", ")
×
742
            )
×
743

18,314✔
744
        return ""
18,314✔
745
    }
18,314✔
746

26✔
747
    /**
26✔
748
     * Creates "LIMIT" parts of SQL query.
26✔
749
     */
26✔
750
    protected createLimitExpression(): string {
26✔
751
        const limit: number | undefined = this.expressionMap.limit
18,314✔
752

18,314✔
753
        if (limit) {
18,314✔
754
            if (
28✔
755
                DriverUtils.isMySQLFamily(this.connection.driver) ||
28!
756
                this.connection.driver.options.type === "aurora-mysql"
20✔
757
            ) {
28!
758
                return " LIMIT " + limit
8✔
759
            } else {
28!
760
                throw new LimitOnUpdateNotSupportedError()
20✔
761
            }
20✔
762
        }
28✔
763

18,286✔
764
        return ""
18,286✔
765
    }
18,286✔
766

26✔
767
    /**
26✔
768
     * Gets array of values need to be inserted into the target table.
26✔
769
     */
26✔
770
    protected getValueSet(): ObjectLiteral {
26✔
771
        if (typeof this.expressionMap.valuesSet === "object")
18,426✔
772
            return this.expressionMap.valuesSet
18,426✔
773

56✔
774
        throw new UpdateValuesMissingError()
56✔
775
    }
56✔
776
}
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc