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

typeorm / typeorm / 23390157208

21 Mar 2026 10:26PM UTC coverage: 56.678% (-16.6%) from 73.277%
23390157208

Pull #12252

github

web-flow
Merge 5b60ba41c into 7038fa166
Pull Request #12252: fix: unskip cascade soft remove test

17767 of 26580 branches covered (66.84%)

Branch coverage included in aggregate %.

64033 of 117744 relevant lines covered (54.38%)

1514.83 hits per line

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

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

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

28✔
34
    // -------------------------------------------------------------------------
28✔
35
    // Constructor
28✔
36
    // -------------------------------------------------------------------------
28✔
37

28✔
38
    constructor(
28✔
39
        connectionOrQueryBuilder: DataSource | QueryBuilder<any>,
558✔
40
        queryRunner?: QueryRunner,
558✔
41
    ) {
558✔
42
        super(connectionOrQueryBuilder as any, queryRunner)
558✔
43
        this.expressionMap.aliasNamePrefixingEnabled = false
558✔
44
    }
558✔
45

28✔
46
    // -------------------------------------------------------------------------
28✔
47
    // Public Implemented Methods
28✔
48
    // -------------------------------------------------------------------------
28✔
49

28✔
50
    /**
28✔
51
     * Gets generated SQL query without parameters being replaced.
28✔
52
     */
28✔
53
    getQuery(): string {
28✔
54
        let sql = this.createComment()
525✔
55
        sql += this.createCteExpression()
525✔
56
        sql += this.createUpdateExpression()
525✔
57
        sql += this.createOrderByExpression()
525✔
58
        sql += this.createLimitExpression()
525✔
59
        return this.replacePropertyNamesForTheWholeQuery(sql.trim())
525✔
60
    }
525✔
61

28✔
62
    /**
28✔
63
     * Executes sql generated by query builder and returns raw database results.
28✔
64
     */
28✔
65
    async execute(): Promise<UpdateResult> {
28✔
66
        const queryRunner = this.obtainQueryRunner()
523✔
67
        let transactionStartedByUs: boolean = false
523✔
68

523✔
69
        try {
523✔
70
            // start transaction if it was enabled
523✔
71
            if (
523✔
72
                this.expressionMap.useTransaction === true &&
523!
73
                queryRunner.isTransactionActive === false
×
74
            ) {
523!
75
                await queryRunner.startTransaction()
×
76
                transactionStartedByUs = true
×
77
            }
×
78

523✔
79
            // call before updation methods in listeners and subscribers
523✔
80
            if (
523✔
81
                this.expressionMap.callListeners === true &&
523✔
82
                this.expressionMap.mainAlias!.hasMetadata
224✔
83
            ) {
523✔
84
                await queryRunner.broadcaster.broadcast(
215✔
85
                    "BeforeUpdate",
215✔
86
                    this.expressionMap.mainAlias!.metadata,
215✔
87
                    this.expressionMap.valuesSet,
215✔
88
                )
215✔
89
            }
215✔
90

523✔
91
            let declareSql: string | null = null
523✔
92
            let selectOutputSql: string | null = null
523✔
93

523✔
94
            // if update entity mode is enabled we may need extra columns for the returning statement
523✔
95
            const returningResultsEntityUpdator =
523✔
96
                new ReturningResultsEntityUpdator(
523✔
97
                    queryRunner,
523✔
98
                    this.expressionMap,
523✔
99
                )
523✔
100

523✔
101
            const returningColumns: ColumnMetadata[] = []
523✔
102

523✔
103
            if (
523✔
104
                Array.isArray(this.expressionMap.returning) &&
523!
105
                this.expressionMap.mainAlias!.hasMetadata
×
106
            ) {
523!
107
                for (const columnPath of this.expressionMap.returning) {
×
108
                    returningColumns.push(
×
109
                        ...this.expressionMap.mainAlias!.metadata.findColumnsWithPropertyPath(
×
110
                            columnPath,
×
111
                        ),
×
112
                    )
×
113
                }
×
114
            }
×
115

523✔
116
            if (
523✔
117
                this.expressionMap.updateEntity === true &&
523✔
118
                this.expressionMap.mainAlias!.hasMetadata &&
523✔
119
                this.expressionMap.whereEntities.length > 0
513✔
120
            ) {
523✔
121
                this.expressionMap.extraReturningColumns =
107✔
122
                    returningResultsEntityUpdator.getUpdationReturningColumns()
107✔
123

107✔
124
                returningColumns.push(
107✔
125
                    ...this.expressionMap.extraReturningColumns.filter(
107✔
126
                        (c) => !returningColumns.includes(c),
107✔
127
                    ),
107✔
128
                )
107✔
129
            }
107✔
130

523✔
131
            if (
523✔
132
                returningColumns.length > 0 &&
523✔
133
                this.connection.driver.options.type === "mssql"
11✔
134
            ) {
523!
135
                declareSql = (
×
136
                    this.connection.driver as SqlServerDriver
×
137
                ).buildTableVariableDeclaration(
×
138
                    "@OutputTable",
×
139
                    returningColumns,
×
140
                )
×
141
                selectOutputSql = `SELECT * FROM @OutputTable`
×
142
            }
×
143

523✔
144
            // execute update query
523✔
145
            const [updateSql, parameters] = this.getQueryAndParameters()
523✔
146

523✔
147
            const statements = [declareSql, updateSql, selectOutputSql]
523✔
148
            const queryResult = await queryRunner.query(
523✔
149
                statements.filter((sql) => sql != null).join(";\n\n"),
523✔
150
                parameters,
523✔
151
                true,
523✔
152
            )
523✔
153
            const updateResult = UpdateResult.from(queryResult)
517✔
154

517✔
155
            // if we are updating entities and entity updation is enabled we must update some of entity columns (like version, update date, etc.)
517✔
156
            if (
517✔
157
                this.expressionMap.updateEntity === true &&
517✔
158
                this.expressionMap.mainAlias!.hasMetadata &&
523✔
159
                this.expressionMap.whereEntities.length > 0
507✔
160
            ) {
523✔
161
                await returningResultsEntityUpdator.update(
107✔
162
                    updateResult,
107✔
163
                    this.expressionMap.whereEntities,
107✔
164
                )
107✔
165
            }
107✔
166

517✔
167
            // call after updation methods in listeners and subscribers
517✔
168
            if (
517✔
169
                this.expressionMap.callListeners === true &&
517✔
170
                this.expressionMap.mainAlias!.hasMetadata
218✔
171
            ) {
523✔
172
                await queryRunner.broadcaster.broadcast(
209✔
173
                    "AfterUpdate",
209✔
174
                    this.expressionMap.mainAlias!.metadata,
209✔
175
                    this.expressionMap.valuesSet,
209✔
176
                )
209✔
177
            }
209✔
178

517✔
179
            // close transaction if we started it
517✔
180
            if (transactionStartedByUs) await queryRunner.commitTransaction()
523!
181

517✔
182
            return updateResult
517✔
183
        } catch (error) {
523!
184
            // rollback transaction if we started it
6✔
185
            if (transactionStartedByUs) {
6!
186
                try {
×
187
                    await queryRunner.rollbackTransaction()
×
188
                } catch (rollbackError) {}
×
189
            }
×
190
            throw error
6✔
191
        } finally {
280!
192
            if (queryRunner !== this.queryRunner) {
523✔
193
                // means we created our own query runner
58✔
194
                await queryRunner.release()
58✔
195
            }
58✔
196
        }
523✔
197
    }
523✔
198

28✔
199
    // -------------------------------------------------------------------------
28✔
200
    // Public Methods
28✔
201
    // -------------------------------------------------------------------------
28✔
202

28✔
203
    /**
28✔
204
     * Values needs to be updated.
28✔
205
     * @param values
28✔
206
     */
28✔
207
    set(values: QueryDeepPartialEntity<Entity>): this {
28✔
208
        this.expressionMap.valuesSet = values
523✔
209
        return this
523✔
210
    }
523✔
211

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

28✔
239
    /**
28✔
240
     * Adds new AND WHERE condition in the query builder.
28✔
241
     * Additionally you can add parameters used in where expression.
28✔
242
     * @param where
28✔
243
     * @param parameters
28✔
244
     */
28✔
245
    andWhere(
28✔
246
        where:
×
247
            | string
×
248
            | ((qb: this) => string)
×
249
            | Brackets
×
250
            | ObjectLiteral
×
251
            | ObjectLiteral[],
×
252
        parameters?: ObjectLiteral,
×
253
    ): this {
×
254
        this.expressionMap.wheres.push({
×
255
            type: "and",
×
256
            condition: this.getWhereCondition(where),
×
257
        })
×
258
        if (parameters) this.setParameters(parameters)
×
259
        return this
×
260
    }
×
261

28✔
262
    /**
28✔
263
     * Adds new OR WHERE condition in the query builder.
28✔
264
     * Additionally you can add parameters used in where expression.
28✔
265
     * @param where
28✔
266
     * @param parameters
28✔
267
     */
28✔
268
    orWhere(
28✔
269
        where:
124✔
270
            | string
124✔
271
            | ((qb: this) => string)
124✔
272
            | Brackets
124✔
273
            | ObjectLiteral
124✔
274
            | ObjectLiteral[],
124✔
275
        parameters?: ObjectLiteral,
124✔
276
    ): this {
124✔
277
        this.expressionMap.wheres.push({
124✔
278
            type: "or",
124✔
279
            condition: this.getWhereCondition(where),
124✔
280
        })
124✔
281
        if (parameters) this.setParameters(parameters)
124!
282
        return this
124✔
283
    }
124✔
284

28✔
285
    /**
28✔
286
     * Sets WHERE condition in the query builder with a condition for the given ids.
28✔
287
     * If you had previously WHERE expression defined,
28✔
288
     * calling this function will override previously set WHERE conditions.
28✔
289
     * @param ids
28✔
290
     */
28✔
291
    whereInIds(ids: any | any[]): this {
28✔
292
        return this.where(this.getWhereInIdsCondition(ids))
25✔
293
    }
25✔
294

28✔
295
    /**
28✔
296
     * Adds new AND WHERE with conditions for the given ids.
28✔
297
     * @param ids
28✔
298
     */
28✔
299
    andWhereInIds(ids: any | any[]): this {
28✔
300
        return this.andWhere(this.getWhereInIdsCondition(ids))
×
301
    }
×
302

28✔
303
    /**
28✔
304
     * Adds new OR WHERE with conditions for the given ids.
28✔
305
     * @param ids
28✔
306
     */
28✔
307
    orWhereInIds(ids: any | any[]): this {
28✔
308
        return this.orWhere(this.getWhereInIdsCondition(ids))
108✔
309
    }
108✔
310
    /**
28✔
311
     * Optional returning/output clause.
28✔
312
     * This will return given column values.
28✔
313
     */
28✔
314
    output(columns: string[]): this
28✔
315

28✔
316
    /**
28✔
317
     * Optional returning/output clause.
28✔
318
     * Returning is a SQL string containing returning statement.
28✔
319
     */
28✔
320
    output(output: string): this
28✔
321

28✔
322
    /**
28✔
323
     * Optional returning/output clause.
28✔
324
     */
28✔
325
    output(output: string | string[]): this
28✔
326

28✔
327
    /**
28✔
328
     * Optional returning/output clause.
28✔
329
     * @param output
28✔
330
     */
28✔
331
    output(output: string | string[]): this {
28✔
332
        return this.returning(output)
×
333
    }
×
334

28✔
335
    /**
28✔
336
     * Optional returning/output clause.
28✔
337
     * This will return given column values.
28✔
338
     */
28✔
339
    returning(columns: string[]): this
28✔
340

28✔
341
    /**
28✔
342
     * Optional returning/output clause.
28✔
343
     * Returning is a SQL string containing returning statement.
28✔
344
     */
28✔
345
    returning(returning: string): this
28✔
346

28✔
347
    /**
28✔
348
     * Optional returning/output clause.
28✔
349
     */
28✔
350
    returning(returning: string | string[]): this
28✔
351

28✔
352
    /**
28✔
353
     * Optional returning/output clause.
28✔
354
     * @param returning
28✔
355
     */
28✔
356
    returning(returning: string | string[]): this {
28✔
357
        // not all databases support returning/output cause
×
358
        if (!this.connection.driver.isReturningSqlSupported("update")) {
×
359
            throw new ReturningStatementNotSupportedError()
×
360
        }
×
361

×
362
        this.expressionMap.returning = returning
×
363
        return this
×
364
    }
×
365

28✔
366
    /**
28✔
367
     * Sets ORDER BY condition in the query builder.
28✔
368
     * If you had previously ORDER BY expression defined,
28✔
369
     * calling this function will override previously set ORDER BY conditions.
28✔
370
     *
28✔
371
     * Calling order by without order set will remove all previously set order bys.
28✔
372
     */
28✔
373
    orderBy(): this
28✔
374

28✔
375
    /**
28✔
376
     * Sets ORDER BY condition in the query builder.
28✔
377
     * If you had previously ORDER BY expression defined,
28✔
378
     * calling this function will override previously set ORDER BY conditions.
28✔
379
     */
28✔
380
    orderBy(
28✔
381
        sort: string,
28✔
382
        order?: "ASC" | "DESC",
28✔
383
        nulls?: "NULLS FIRST" | "NULLS LAST",
28✔
384
    ): this
28✔
385

28✔
386
    /**
28✔
387
     * Sets ORDER BY condition in the query builder.
28✔
388
     * If you had previously ORDER BY expression defined,
28✔
389
     * calling this function will override previously set ORDER BY conditions.
28✔
390
     */
28✔
391
    orderBy(order: OrderByCondition): this
28✔
392

28✔
393
    /**
28✔
394
     * Sets ORDER BY condition in the query builder.
28✔
395
     * If you had previously ORDER BY expression defined,
28✔
396
     * calling this function will override previously set ORDER BY conditions.
28✔
397
     * @param sort
28✔
398
     * @param order
28✔
399
     * @param nulls
28✔
400
     */
28✔
401
    orderBy(
28✔
402
        sort?: string | OrderByCondition,
×
403
        order: "ASC" | "DESC" = "ASC",
×
404
        nulls?: "NULLS FIRST" | "NULLS LAST",
×
405
    ): this {
×
406
        if (sort) {
×
407
            if (typeof sort === "object") {
×
408
                this.expressionMap.orderBys = sort as OrderByCondition
×
409
            } else {
×
410
                if (nulls) {
×
411
                    this.expressionMap.orderBys = {
×
412
                        [sort as string]: { order, nulls },
×
413
                    }
×
414
                } else {
×
415
                    this.expressionMap.orderBys = { [sort as string]: order }
×
416
                }
×
417
            }
×
418
        } else {
×
419
            this.expressionMap.orderBys = {}
×
420
        }
×
421
        return this
×
422
    }
×
423

28✔
424
    /**
28✔
425
     * Adds ORDER BY condition in the query builder.
28✔
426
     * @param sort
28✔
427
     * @param order
28✔
428
     * @param nulls
28✔
429
     */
28✔
430
    addOrderBy(
28✔
431
        sort: string,
×
432
        order: "ASC" | "DESC" = "ASC",
×
433
        nulls?: "NULLS FIRST" | "NULLS LAST",
×
434
    ): this {
×
435
        if (nulls) {
×
436
            this.expressionMap.orderBys[sort] = { order, nulls }
×
437
        } else {
×
438
            this.expressionMap.orderBys[sort] = order
×
439
        }
×
440
        return this
×
441
    }
×
442

28✔
443
    /**
28✔
444
     * Sets LIMIT - maximum number of rows to be selected.
28✔
445
     * @param limit
28✔
446
     */
28✔
447
    limit(limit?: number): this {
28✔
448
        this.expressionMap.limit = limit
1✔
449
        return this
1✔
450
    }
1✔
451

28✔
452
    /**
28✔
453
     * Indicates if entity must be updated after update operation.
28✔
454
     * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
28✔
455
     * Enabled by default.
28✔
456
     * @param entity
28✔
457
     */
28✔
458
    whereEntity(entity: Entity | Entity[]): this {
28✔
459
        if (!this.expressionMap.mainAlias!.hasMetadata)
108✔
460
            throw new TypeORMError(
108!
461
                `.whereEntity method can only be used on queries which update real entity table.`,
×
462
            )
×
463

108✔
464
        this.expressionMap.wheres = []
108✔
465
        const entities: Entity[] = Array.isArray(entity) ? entity : [entity]
108!
466
        entities.forEach((entity) => {
108✔
467
            const entityIdMap =
108✔
468
                this.expressionMap.mainAlias!.metadata.getEntityIdMap(entity)
108✔
469
            if (!entityIdMap)
108✔
470
                throw new TypeORMError(
108!
471
                    `Provided entity does not have ids set, cannot perform operation.`,
×
472
                )
×
473

108✔
474
            this.orWhereInIds(entityIdMap)
108✔
475
        })
108✔
476

108✔
477
        this.expressionMap.whereEntities = entities
108✔
478
        return this
108✔
479
    }
108✔
480

28✔
481
    /**
28✔
482
     * Indicates if entity must be updated after update operation.
28✔
483
     * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
28✔
484
     * Enabled by default.
28✔
485
     * @param enabled
28✔
486
     */
28✔
487
    updateEntity(enabled: boolean): this {
28✔
488
        this.expressionMap.updateEntity = enabled
301✔
489
        return this
301✔
490
    }
301✔
491

28✔
492
    // -------------------------------------------------------------------------
28✔
493
    // Protected Methods
28✔
494
    // -------------------------------------------------------------------------
28✔
495

28✔
496
    /**
28✔
497
     * Creates UPDATE express used to perform insert query.
28✔
498
     */
28✔
499
    protected createUpdateExpression() {
28✔
500
        const valuesSet = this.getValueSet()
525✔
501
        const metadata = this.expressionMap.mainAlias!.hasMetadata
525✔
502
            ? this.expressionMap.mainAlias!.metadata
525✔
503
            : undefined
525!
504

525✔
505
        // it doesn't make sense to update undefined properties, so just skip them
525✔
506
        const valuesSetNormalized: ObjectLiteral = {}
525✔
507
        for (const key in valuesSet) {
525✔
508
            if (valuesSet[key] !== undefined) {
586✔
509
                valuesSetNormalized[key] = valuesSet[key]
579✔
510
            }
579✔
511
        }
586✔
512

523✔
513
        // prepare columns and values to be updated
523✔
514
        const updateColumnAndValues: string[] = []
523✔
515
        const updatedColumns: ColumnMetadata[] = []
523✔
516
        if (metadata) {
523✔
517
            this.createPropertyPath(metadata, valuesSetNormalized).forEach(
514✔
518
                (propertyPath) => {
514✔
519
                    // todo: make this and other query builder to work with properly with tables without metadata
559✔
520
                    const columns =
559✔
521
                        metadata.findColumnsWithPropertyPath(propertyPath)
559✔
522

559✔
523
                    if (columns.length <= 0) {
559!
524
                        throw new EntityPropertyNotFoundError(
1✔
525
                            propertyPath,
1✔
526
                            metadata,
1✔
527
                        )
1✔
528
                    }
1✔
529

558✔
530
                    columns.forEach((column) => {
558✔
531
                        if (
596✔
532
                            !column.isUpdate ||
596✔
533
                            updatedColumns.includes(column)
593✔
534
                        ) {
596!
535
                            return
4✔
536
                        }
4✔
537

592✔
538
                        updatedColumns.push(column)
592✔
539

592✔
540
                        //
592✔
541
                        let value = column.getEntityValue(valuesSetNormalized)
592✔
542
                        if (
592✔
543
                            column.referencedColumn &&
592✔
544
                            typeof value === "object" &&
596!
545
                            !(value instanceof Date) &&
596!
546
                            value !== null &&
596!
547
                            !isUint8Array(value)
×
548
                        ) {
596!
549
                            value =
×
550
                                column.referencedColumn.getEntityValue(value)
×
551
                        } else if (!(typeof value === "function")) {
596✔
552
                            value =
582✔
553
                                this.connection.driver.preparePersistentValue(
582✔
554
                                    value,
582✔
555
                                    column,
582✔
556
                                )
582✔
557
                        }
582✔
558

592✔
559
                        // todo: duplication zone
592✔
560
                        if (typeof value === "function") {
596!
561
                            // support for SQL expressions in update query
10✔
562
                            updateColumnAndValues.push(
10✔
563
                                this.escape(column.databaseName) +
10✔
564
                                    " = " +
10✔
565
                                    value(),
10✔
566
                            )
10✔
567
                        } else if (
596✔
568
                            (this.connection.driver.options.type === "sap" ||
582✔
569
                                this.connection.driver.options.type ===
582✔
570
                                    "spanner") &&
582!
571
                            value === null
×
572
                        ) {
582!
573
                            updateColumnAndValues.push(
×
574
                                this.escape(column.databaseName) + " = NULL",
×
575
                            )
×
576
                        } else {
582✔
577
                            if (
582✔
578
                                this.connection.driver.options.type === "mssql"
582✔
579
                            ) {
582!
580
                                value = (
×
581
                                    this.connection.driver as SqlServerDriver
×
582
                                ).parametrizeValue(column, value)
×
583
                            }
×
584

582✔
585
                            const paramName = this.createParameter(value)
582✔
586

582✔
587
                            let expression: string
582✔
588
                            if (
582✔
589
                                (DriverUtils.isMySQLFamily(
582✔
590
                                    this.connection.driver,
582✔
591
                                ) ||
582✔
592
                                    this.connection.driver.options.type ===
582✔
593
                                        "aurora-mysql") &&
582!
594
                                this.connection.driver.spatialTypes.indexOf(
×
595
                                    column.type,
×
596
                                ) !== -1
×
597
                            ) {
582!
598
                                const useLegacy = (
×
599
                                    this.connection.driver as
×
600
                                        | MysqlDriver
×
601
                                        | AuroraMysqlDriver
×
602
                                ).options.legacySpatialSupport
×
603
                                const geomFromText = useLegacy
×
604
                                    ? "GeomFromText"
×
605
                                    : "ST_GeomFromText"
×
606
                                if (column.srid != null) {
×
607
                                    expression = `${geomFromText}(${paramName}, ${column.srid})`
×
608
                                } else {
×
609
                                    expression = `${geomFromText}(${paramName})`
×
610
                                }
×
611
                            } else if (
582✔
612
                                DriverUtils.isPostgresFamily(
582✔
613
                                    this.connection.driver,
582✔
614
                                ) &&
582✔
615
                                this.connection.driver.spatialTypes.indexOf(
582✔
616
                                    column.type,
582✔
617
                                ) !== -1
582✔
618
                            ) {
582!
619
                                if (column.srid != null) {
2!
620
                                    expression = `ST_SetSRID(ST_GeomFromGeoJSON(${paramName}), ${column.srid})::${column.type}`
×
621
                                } else {
2✔
622
                                    expression = `ST_GeomFromGeoJSON(${paramName})::${column.type}`
2✔
623
                                }
2✔
624
                            } else if (
582✔
625
                                this.connection.driver.options.type ===
580✔
626
                                    "mssql" &&
580!
627
                                this.connection.driver.spatialTypes.indexOf(
×
628
                                    column.type,
×
629
                                ) !== -1
×
630
                            ) {
580!
631
                                expression =
×
632
                                    column.type +
×
633
                                    "::STGeomFromText(" +
×
634
                                    paramName +
×
635
                                    ", " +
×
636
                                    (column.srid || "0") +
×
637
                                    ")"
×
638
                            } else if (
580✔
639
                                DriverUtils.isSQLiteFamily(
580✔
640
                                    this.connection.driver,
580✔
641
                                )
580✔
642
                            ) {
580!
643
                                expression = (
×
644
                                    this.connection.driver as
×
645
                                        | AbstractSqliteDriver
×
646
                                        | ReactNativeDriver
×
647
                                ).wrapWithJsonFunction(paramName, column, true)
×
648
                            } else {
580✔
649
                                expression = paramName
580✔
650
                            }
580✔
651
                            updateColumnAndValues.push(
582✔
652
                                this.escape(column.databaseName) +
582✔
653
                                    " = " +
582✔
654
                                    expression,
582✔
655
                            )
582✔
656
                        }
582✔
657
                    })
558✔
658
                },
514✔
659
            )
514✔
660

514✔
661
            // Don't allow calling update only with columns that are `update: false`
514✔
662
            if (
514✔
663
                updateColumnAndValues.length > 0 ||
514!
664
                Object.keys(valuesSetNormalized).length === 0
2✔
665
            ) {
514✔
666
                if (
512✔
667
                    metadata.versionColumn &&
512!
668
                    updatedColumns.indexOf(metadata.versionColumn) === -1
6✔
669
                )
512✔
670
                    updateColumnAndValues.push(
512!
671
                        this.escape(metadata.versionColumn.databaseName) +
5✔
672
                            " = " +
5✔
673
                            this.escape(metadata.versionColumn.databaseName) +
5✔
674
                            " + 1",
5✔
675
                    )
5✔
676
                if (
512✔
677
                    metadata.updateDateColumn &&
512!
678
                    updatedColumns.indexOf(metadata.updateDateColumn) === -1
10✔
679
                )
512✔
680
                    updateColumnAndValues.push(
512!
681
                        this.escape(metadata.updateDateColumn.databaseName) +
9✔
682
                            " = CURRENT_TIMESTAMP",
9✔
683
                    ) // todo: fix issue with CURRENT_TIMESTAMP(6) being used, can "DEFAULT" be used?!
512✔
684
            }
512✔
685
        } else {
525!
686
            Object.keys(valuesSetNormalized).map((key) => {
9✔
687
                const value = valuesSetNormalized[key]
38✔
688

38✔
689
                // todo: duplication zone
38✔
690
                if (typeof value === "function") {
38!
691
                    // support for SQL expressions in update query
×
692
                    updateColumnAndValues.push(
×
693
                        this.escape(key) + " = " + value(),
×
694
                    )
×
695
                } else if (
38✔
696
                    (this.connection.driver.options.type === "sap" ||
38✔
697
                        this.connection.driver.options.type === "spanner") &&
38!
698
                    value === null
×
699
                ) {
38!
700
                    updateColumnAndValues.push(this.escape(key) + " = NULL")
×
701
                } else {
38✔
702
                    // we need to store array values in a special class to make sure parameter replacement will work correctly
38✔
703
                    // if (value instanceof Array)
38✔
704
                    //     value = new ArrayParameter(value);
38✔
705

38✔
706
                    const paramName = this.createParameter(value)
38✔
707
                    updateColumnAndValues.push(
38✔
708
                        this.escape(key) + " = " + paramName,
38✔
709
                    )
38✔
710
                }
38✔
711
            })
9✔
712
        }
9✔
713

522✔
714
        if (updateColumnAndValues.length <= 0) {
525!
715
            throw new UpdateValuesMissingError()
1✔
716
        }
1✔
717

521✔
718
        // get a table name and all column database names
521✔
719
        const whereExpression = this.createWhereExpression()
521✔
720
        const returningExpression = this.createReturningExpression("update")
521✔
721

521✔
722
        if (returningExpression === "") {
525✔
723
            return `UPDATE ${this.getTableName(
510✔
724
                this.getMainTableName(),
510✔
725
            )} SET ${updateColumnAndValues.join(", ")}${whereExpression}` // todo: how do we replace aliases in where to nothing?
510✔
726
        }
510✔
727
        if (this.connection.driver.options.type === "mssql") {
455!
728
            return `UPDATE ${this.getTableName(
×
729
                this.getMainTableName(),
×
730
            )} SET ${updateColumnAndValues.join(
×
731
                ", ",
×
732
            )} OUTPUT ${returningExpression}${whereExpression}`
×
733
        }
×
734
        if (this.connection.driver.options.type === "spanner") {
455!
735
            return `UPDATE ${this.getTableName(
×
736
                this.getMainTableName(),
×
737
            )} SET ${updateColumnAndValues.join(
×
738
                ", ",
×
739
            )}${whereExpression} THEN RETURN ${returningExpression}`
×
740
        }
×
741

11✔
742
        return `UPDATE ${this.getTableName(
11✔
743
            this.getMainTableName(),
11✔
744
        )} SET ${updateColumnAndValues.join(
11✔
745
            ", ",
11✔
746
        )}${whereExpression} RETURNING ${returningExpression}`
11✔
747
    }
11✔
748

28✔
749
    /**
28✔
750
     * Creates "ORDER BY" part of SQL query.
28✔
751
     */
28✔
752
    protected createOrderByExpression() {
28✔
753
        const orderBys = this.expressionMap.orderBys
521✔
754
        if (Object.keys(orderBys).length > 0)
521✔
755
            return (
521!
756
                " ORDER BY " +
×
757
                Object.keys(orderBys)
×
758
                    .map((columnName) => {
×
759
                        if (typeof orderBys[columnName] === "string") {
×
760
                            return columnName + " " + orderBys[columnName]
×
761
                        } else {
×
762
                            return (
×
763
                                columnName +
×
764
                                " " +
×
765
                                (orderBys[columnName] as any).order +
×
766
                                " " +
×
767
                                (orderBys[columnName] as any).nulls
×
768
                            )
×
769
                        }
×
770
                    })
×
771
                    .join(", ")
×
772
            )
×
773

521✔
774
        return ""
521✔
775
    }
521✔
776

28✔
777
    /**
28✔
778
     * Creates "LIMIT" parts of SQL query.
28✔
779
     */
28✔
780
    protected createLimitExpression(): string {
28✔
781
        const limit: number | undefined = this.expressionMap.limit
521✔
782

521✔
783
        if (limit) {
521!
784
            if (
1✔
785
                DriverUtils.isMySQLFamily(this.connection.driver) ||
1✔
786
                this.connection.driver.options.type === "aurora-mysql"
1✔
787
            ) {
1!
788
                return " LIMIT " + limit
×
789
            } else {
1✔
790
                throw new LimitOnUpdateNotSupportedError()
1✔
791
            }
1✔
792
        }
1✔
793

520✔
794
        return ""
520✔
795
    }
520✔
796

28✔
797
    /**
28✔
798
     * Gets array of values need to be inserted into the target table.
28✔
799
     */
28✔
800
    protected getValueSet(): ObjectLiteral {
28✔
801
        if (typeof this.expressionMap.valuesSet === "object")
525✔
802
            return this.expressionMap.valuesSet
525✔
803

2!
804
        throw new UpdateValuesMissingError()
2✔
805
    }
2✔
806
}
28✔
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