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

typeorm / typeorm / 15219332477

23 May 2025 09:13PM UTC coverage: 17.216% (-59.1%) from 76.346%
15219332477

Pull #11332

github

naorpeled
cr comments - move if block
Pull Request #11332: feat: add new undefined and null behavior flags

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

0 of 31 new or added lines in 3 files covered. (0.0%)

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

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

3.53
/src/query-builder/QueryExpressionMap.ts
1
import { Alias } from "./Alias"
1✔
2
import { ObjectLiteral } from "../common/ObjectLiteral"
3
import { OrderByCondition } from "../find-options/OrderByCondition"
4
import { JoinAttribute } from "./JoinAttribute"
1✔
5
import { QueryBuilder } from "./QueryBuilder"
6
import { QueryBuilderCteOptions } from "./QueryBuilderCte"
7
import { RelationIdAttribute } from "./relation-id/RelationIdAttribute"
1✔
8
import { RelationCountAttribute } from "./relation-count/RelationCountAttribute"
1✔
9
import { DataSource } from "../data-source/DataSource"
10
import { EntityMetadata } from "../metadata/EntityMetadata"
11
import { SelectQuery } from "./SelectQuery"
12
import { ColumnMetadata } from "../metadata/ColumnMetadata"
13
import { RelationMetadata } from "../metadata/RelationMetadata"
14
import { SelectQueryBuilderOption } from "./SelectQueryBuilderOption"
15
import { TypeORMError } from "../error"
1✔
16
import { WhereClause } from "./WhereClause"
17
import { UpsertType } from "../driver/types/UpsertType"
18
import { CockroachConnectionOptions } from "../driver/cockroachdb/CockroachConnectionOptions"
19

20
/**
21
 * Contains all properties of the QueryBuilder that needs to be build a final query.
22
 */
23
export class QueryExpressionMap {
1✔
24
    // -------------------------------------------------------------------------
25
    // Public Properties
26
    // -------------------------------------------------------------------------
27

28
    /**
29
     * Strategy to load relations.
30
     */
UNCOV
31
    relationLoadStrategy: "join" | "query" = "join"
×
32

33
    /**
34
     * Indicates if QueryBuilder used to select entities and not a raw results.
35
     */
UNCOV
36
    queryEntity: boolean = false
×
37

38
    /**
39
     * Main alias is a main selection object selected by QueryBuilder.
40
     */
41
    mainAlias?: Alias
42

43
    /**
44
     * All aliases (including main alias) used in the query.
45
     */
UNCOV
46
    aliases: Alias[] = []
×
47

48
    /**
49
     * Represents query type. QueryBuilder is able to build SELECT, UPDATE and DELETE queries.
50
     */
UNCOV
51
    queryType:
×
52
        | "select"
53
        | "update"
54
        | "delete"
55
        | "insert"
56
        | "relation"
57
        | "soft-delete"
58
        | "restore" = "select"
59

60
    /**
61
     * Data needs to be SELECT-ed.
62
     */
UNCOV
63
    selects: SelectQuery[] = []
×
64

65
    /**
66
     * Max execution time in millisecond.
67
     */
UNCOV
68
    maxExecutionTime: number = 0
×
69

70
    /**
71
     * Whether SELECT is DISTINCT.
72
     */
UNCOV
73
    selectDistinct: boolean = false
×
74

75
    /**
76
     * SELECT DISTINCT ON query (postgres).
77
     */
UNCOV
78
    selectDistinctOn: string[] = []
×
79

80
    /**
81
     * FROM-s to be selected.
82
     */
83
    // froms: { target: string, alias: string }[] = [];
84

85
    /**
86
     * If update query was used, it needs "update set" - properties which will be updated by this query.
87
     * If insert query was used, it needs "insert set" - values that needs to be inserted.
88
     */
89
    valuesSet?: ObjectLiteral | ObjectLiteral[]
90

91
    /**
92
     * Optional returning (or output) clause for insert, update or delete queries.
93
     */
94
    returning: string | string[]
95

96
    /**
97
     * Extra returning columns to be added to the returning statement if driver supports it.
98
     */
UNCOV
99
    extraReturningColumns: ColumnMetadata[] = []
×
100

101
    /**
102
     * Optional on conflict statement used in insertion query in postgres.
103
     */
UNCOV
104
    onConflict: string = ""
×
105

106
    /**
107
     * Optional on ignore statement used in insertion query in databases.
108
     */
UNCOV
109
    onIgnore: boolean = false
×
110

111
    /**
112
     * Optional on update statement used in insertion query in databases.
113
     */
114
    onUpdate: {
115
        conflict?: string | string[]
116
        columns?: string[]
117
        overwrite?: string[]
118
        skipUpdateIfNoValuesChanged?: boolean
119
        indexPredicate?: string
120
        upsertType?: UpsertType
121
    }
122

123
    /**
124
     * JOIN queries.
125
     */
UNCOV
126
    joinAttributes: JoinAttribute[] = []
×
127

128
    /**
129
     * RelationId queries.
130
     */
UNCOV
131
    relationIdAttributes: RelationIdAttribute[] = []
×
132

133
    /**
134
     * Relation count queries.
135
     */
UNCOV
136
    relationCountAttributes: RelationCountAttribute[] = []
×
137

138
    /**
139
     * WHERE queries.
140
     */
UNCOV
141
    wheres: WhereClause[] = []
×
142

143
    /**
144
     * HAVING queries.
145
     */
UNCOV
146
    havings: { type: "simple" | "and" | "or"; condition: string }[] = []
×
147

148
    /**
149
     * ORDER BY queries.
150
     */
UNCOV
151
    orderBys: OrderByCondition = {}
×
152

153
    /**
154
     * GROUP BY queries.
155
     */
UNCOV
156
    groupBys: string[] = []
×
157

158
    /**
159
     * LIMIT query.
160
     */
161
    limit?: number
162

163
    /**
164
     * OFFSET query.
165
     */
166
    offset?: number
167

168
    /**
169
     * Number of rows to skip of result using pagination.
170
     */
171
    skip?: number
172

173
    /**
174
     * Number of rows to take using pagination.
175
     */
176
    take?: number
177

178
    /**
179
     * Use certain index for the query.
180
     *
181
     * SELECT * FROM table_name USE INDEX (col1_index, col2_index) WHERE col1=1 AND col2=2 AND col3=3;
182
     */
183
    useIndex?: string
184

185
    /**
186
     * Locking mode.
187
     */
188
    lockMode?:
189
        | "optimistic"
190
        | "pessimistic_read"
191
        | "pessimistic_write"
192
        | "dirty_read"
193
        /*
194
            "pessimistic_partial_write" and "pessimistic_write_or_fail" are deprecated and
195
            will be removed in a future version.
196

197
            Use onLocked instead.
198
         */
199
        | "pessimistic_partial_write"
200
        | "pessimistic_write_or_fail"
201
        | "for_no_key_update"
202
        | "for_key_share"
203

204
    /**
205
     * Current version of the entity, used for locking.
206
     */
207
    lockVersion?: number | Date
208

209
    /**
210
     * Tables to be specified in the "FOR UPDATE OF" clause, referred by their alias
211
     */
212
    lockTables?: string[]
213

214
    /**
215
     * Modify behavior when encountering locked rows. NOWAIT or SKIP LOCKED
216
     */
217
    onLocked?: "nowait" | "skip_locked"
218

219
    /**
220
     * Indicates if soft-deleted rows should be included in entity result.
221
     * By default the soft-deleted rows are not included.
222
     */
UNCOV
223
    withDeleted: boolean = false
×
224

225
    /**
226
     * Parameters used to be escaped in final query.
227
     */
UNCOV
228
    parameters: ObjectLiteral = {}
×
229

230
    /**
231
     * Indicates if alias, table names and column names will be escaped by driver, or not.
232
     *
233
     * todo: rename to isQuotingDisabled, also think if it should be named "escaping"
234
     */
UNCOV
235
    disableEscaping: boolean = true
×
236

237
    /**
238
     * Indicates if virtual columns should be included in entity result.
239
     *
240
     * todo: what to do with it? is it properly used? what about persistence?
241
     */
UNCOV
242
    enableRelationIdValues: boolean = false
×
243

244
    /**
245
     * Extra where condition appended to the end of original where conditions with AND keyword.
246
     * Original condition will be wrapped into brackets.
247
     */
UNCOV
248
    extraAppendedAndWhereCondition: string = ""
×
249

250
    /**
251
     * Indicates if query builder creates a subquery.
252
     */
UNCOV
253
    subQuery: boolean = false
×
254

255
    /**
256
     * Indicates if property names are prefixed with alias names during property replacement.
257
     * By default this is enabled, however we need this because aliases are not supported in UPDATE and DELETE queries,
258
     * but user can use them in WHERE expressions.
259
     */
UNCOV
260
    aliasNamePrefixingEnabled: boolean = true
×
261

262
    /**
263
     * Indicates if query result cache is enabled or not.
264
     * It is undefined by default to avoid overriding the `alwaysEnabled` config
265
     */
266
    cache?: boolean
267

268
    /**
269
     * Time in milliseconds in which cache will expire.
270
     * If not set then global caching time will be used.
271
     */
272
    cacheDuration: number
273

274
    /**
275
     * Cache id.
276
     * Used to identifier your cache queries.
277
     */
278
    cacheId: string
279

280
    /**
281
     * Options that define QueryBuilder behaviour.
282
     */
UNCOV
283
    options: SelectQueryBuilderOption[] = []
×
284

285
    /**
286
     * Property path of relation to work with.
287
     * Used in relational query builder.
288
     */
289
    relationPropertyPath: string
290

291
    /**
292
     * Entity (target) which relations will be updated.
293
     */
294
    of: any | any[]
295

296
    /**
297
     * List of columns where data should be inserted.
298
     * Used in INSERT query.
299
     */
UNCOV
300
    insertColumns: string[] = []
×
301

302
    /**
303
     * Used if user wants to update or delete a specific entities.
304
     */
UNCOV
305
    whereEntities: ObjectLiteral[] = []
×
306

307
    /**
308
     * Indicates if entity must be updated after insertion / updation.
309
     * This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
310
     */
UNCOV
311
    updateEntity: boolean = true
×
312

313
    /**
314
     * Indicates if listeners and subscribers must be called before and after query execution.
315
     */
UNCOV
316
    callListeners: boolean = true
×
317

318
    /**
319
     * Indicates if query must be wrapped into transaction.
320
     */
UNCOV
321
    useTransaction: boolean = false
×
322

323
    /**
324
     * Indicates if query should be time travel query
325
     * https://www.cockroachlabs.com/docs/stable/as-of-system-time.html
326
     */
327
    timeTravel?: boolean | string
328

329
    /**
330
     * Extra parameters.
331
     *
332
     * @deprecated Use standard parameters instead
333
     */
UNCOV
334
    nativeParameters: ObjectLiteral = {}
×
335

336
    /**
337
     * Query Comment to include extra information for debugging or other purposes.
338
     */
339
    comment?: string
340

341
    /**
342
     * Items from an entity that have been locally generated & are recorded here for later use.
343
     * Examples include the UUID generation when the database does not natively support it.
344
     * These are included in the entity index order.
345
     */
UNCOV
346
    locallyGenerated: { [key: number]: ObjectLiteral } = {}
×
347

UNCOV
348
    commonTableExpressions: {
×
349
        queryBuilder: QueryBuilder<any> | string
350
        alias: string
351
        options: QueryBuilderCteOptions
352
    }[] = []
353

354
    /**
355
     * When true, null values will be treated as SQL NULL in find operations.
356
     * By default, null values are skipped in where conditions.
357
     */
358
    treatJsNullAsSqlNull: boolean
359

360
    /**
361
     * When true, throws an error if undefined is encountered in a find operation.
362
     * By default, undefined values are skipped in where conditions.
363
     */
364
    throwOnUndefinedInFind: boolean
365

366
    // -------------------------------------------------------------------------
367
    // Constructor
368
    // -------------------------------------------------------------------------
369

UNCOV
370
    constructor(protected connection: DataSource) {
×
UNCOV
371
        if (connection.options.relationLoadStrategy) {
×
UNCOV
372
            this.relationLoadStrategy = connection.options.relationLoadStrategy
×
373
        }
374

UNCOV
375
        this.timeTravel =
×
376
            (connection.options as CockroachConnectionOptions)
×
377
                ?.timeTravelQueries || false
NEW
378
        this.treatJsNullAsSqlNull = !!connection.options.treatJsNullAsSqlNull
×
NEW
379
        this.throwOnUndefinedInFind =
×
380
            !!connection.options.throwOnUndefinedInFind
381
    }
382

383
    // -------------------------------------------------------------------------
384
    // Accessors
385
    // -------------------------------------------------------------------------
386

387
    /**
388
     * Get all ORDER BY queries - if order by is specified by user then it uses them,
389
     * otherwise it uses default entity order by if it was set.
390
     */
391
    get allOrderBys() {
UNCOV
392
        if (
×
393
            !Object.keys(this.orderBys).length &&
×
394
            this.mainAlias!.hasMetadata &&
395
            this.options.indexOf("disable-global-order") === -1
396
        ) {
UNCOV
397
            const entityOrderBy = this.mainAlias!.metadata.orderBy || {}
×
UNCOV
398
            return Object.keys(entityOrderBy).reduce((orderBy, key) => {
×
UNCOV
399
                orderBy[this.mainAlias!.name + "." + key] = entityOrderBy[key]
×
UNCOV
400
                return orderBy
×
401
            }, {} as OrderByCondition)
402
        }
403

UNCOV
404
        return this.orderBys
×
405
    }
406

407
    // -------------------------------------------------------------------------
408
    // Public Methods
409
    // -------------------------------------------------------------------------
410

411
    /**
412
     * Creates a main alias and adds it to the current expression map.
413
     */
414
    setMainAlias(alias: Alias): Alias {
415
        // if main alias is already set then remove it from the array
416
        // if (this.mainAlias)
417
        //     this.aliases.splice(this.aliases.indexOf(this.mainAlias));
418

419
        // set new main alias
UNCOV
420
        this.mainAlias = alias
×
421

UNCOV
422
        return alias
×
423
    }
424

425
    /**
426
     * Creates a new alias and adds it to the current expression map.
427
     */
428
    createAlias(options: {
429
        type: "from" | "select" | "join" | "other"
430
        name?: string
431
        target?: Function | string
432
        tablePath?: string
433
        subQuery?: string
434
        metadata?: EntityMetadata
435
    }): Alias {
UNCOV
436
        let aliasName = options.name
×
UNCOV
437
        if (!aliasName && options.tablePath) aliasName = options.tablePath
×
UNCOV
438
        if (!aliasName && typeof options.target === "function")
×
439
            aliasName = options.target.name
×
UNCOV
440
        if (!aliasName && typeof options.target === "string")
×
441
            aliasName = options.target
×
442

UNCOV
443
        const alias = new Alias()
×
UNCOV
444
        alias.type = options.type
×
UNCOV
445
        if (aliasName) alias.name = aliasName
×
UNCOV
446
        if (options.metadata) alias.metadata = options.metadata
×
UNCOV
447
        if (options.target && !alias.hasMetadata)
×
448
            alias.metadata = this.connection.getMetadata(options.target)
×
UNCOV
449
        if (options.tablePath) alias.tablePath = options.tablePath
×
UNCOV
450
        if (options.subQuery) alias.subQuery = options.subQuery
×
451

UNCOV
452
        this.aliases.push(alias)
×
UNCOV
453
        return alias
×
454
    }
455

456
    /**
457
     * Finds alias with the given name.
458
     * If alias was not found it throw an exception.
459
     */
460
    findAliasByName(aliasName: string): Alias {
UNCOV
461
        const alias = this.aliases.find((alias) => alias.name === aliasName)
×
UNCOV
462
        if (!alias)
×
463
            throw new TypeORMError(
×
464
                `"${aliasName}" alias was not found. Maybe you forgot to join it?`,
465
            )
466

UNCOV
467
        return alias
×
468
    }
469

470
    findColumnByAliasExpression(
471
        aliasExpression: string,
472
    ): ColumnMetadata | undefined {
473
        const [aliasName, propertyPath] = aliasExpression.split(".")
×
474
        const alias = this.findAliasByName(aliasName)
×
475
        return alias.metadata.findColumnWithPropertyName(propertyPath)
×
476
    }
477

478
    /**
479
     * Gets relation metadata of the relation this query builder works with.
480
     *
481
     * todo: add proper exceptions
482
     */
483
    get relationMetadata(): RelationMetadata {
UNCOV
484
        if (!this.mainAlias)
×
485
            throw new TypeORMError(`Entity to work with is not specified!`) // todo: better message
×
486

487
        const relationMetadata =
UNCOV
488
            this.mainAlias.metadata.findRelationWithPropertyPath(
×
489
                this.relationPropertyPath,
490
            )
UNCOV
491
        if (!relationMetadata)
×
492
            throw new TypeORMError(
×
493
                `Relation ${this.relationPropertyPath} was not found in entity ${this.mainAlias.name}`,
494
            ) // todo: better message
495

UNCOV
496
        return relationMetadata
×
497
    }
498

499
    /**
500
     * Copies all properties of the current QueryExpressionMap into a new one.
501
     * Useful when QueryBuilder needs to create a copy of itself.
502
     */
503
    clone(): QueryExpressionMap {
UNCOV
504
        const map = new QueryExpressionMap(this.connection)
×
UNCOV
505
        map.queryType = this.queryType
×
UNCOV
506
        map.selects = this.selects.map((select) => select)
×
UNCOV
507
        map.maxExecutionTime = this.maxExecutionTime
×
UNCOV
508
        map.selectDistinct = this.selectDistinct
×
UNCOV
509
        map.selectDistinctOn = this.selectDistinctOn
×
UNCOV
510
        this.aliases.forEach((alias) => map.aliases.push(new Alias(alias)))
×
UNCOV
511
        map.relationLoadStrategy = this.relationLoadStrategy
×
UNCOV
512
        map.mainAlias = this.mainAlias
×
UNCOV
513
        map.valuesSet = this.valuesSet
×
UNCOV
514
        map.returning = this.returning
×
UNCOV
515
        map.onConflict = this.onConflict
×
UNCOV
516
        map.onIgnore = this.onIgnore
×
UNCOV
517
        map.onUpdate = this.onUpdate
×
UNCOV
518
        map.joinAttributes = this.joinAttributes.map(
×
UNCOV
519
            (join) => new JoinAttribute(this.connection, this, join),
×
520
        )
UNCOV
521
        map.relationIdAttributes = this.relationIdAttributes.map(
×
UNCOV
522
            (relationId) => new RelationIdAttribute(this, relationId),
×
523
        )
UNCOV
524
        map.relationCountAttributes = this.relationCountAttributes.map(
×
525
            (relationCount) => new RelationCountAttribute(this, relationCount),
×
526
        )
UNCOV
527
        map.wheres = this.wheres.map((where) => ({ ...where }))
×
UNCOV
528
        map.havings = this.havings.map((having) => ({ ...having }))
×
UNCOV
529
        map.orderBys = Object.assign({}, this.orderBys)
×
UNCOV
530
        map.groupBys = this.groupBys.map((groupBy) => groupBy)
×
UNCOV
531
        map.limit = this.limit
×
UNCOV
532
        map.offset = this.offset
×
UNCOV
533
        map.skip = this.skip
×
UNCOV
534
        map.take = this.take
×
UNCOV
535
        map.lockMode = this.lockMode
×
UNCOV
536
        map.onLocked = this.onLocked
×
UNCOV
537
        map.lockVersion = this.lockVersion
×
UNCOV
538
        map.lockTables = this.lockTables
×
UNCOV
539
        map.withDeleted = this.withDeleted
×
UNCOV
540
        map.parameters = Object.assign({}, this.parameters)
×
UNCOV
541
        map.disableEscaping = this.disableEscaping
×
UNCOV
542
        map.enableRelationIdValues = this.enableRelationIdValues
×
UNCOV
543
        map.extraAppendedAndWhereCondition = this.extraAppendedAndWhereCondition
×
UNCOV
544
        map.subQuery = this.subQuery
×
UNCOV
545
        map.aliasNamePrefixingEnabled = this.aliasNamePrefixingEnabled
×
UNCOV
546
        map.cache = this.cache
×
UNCOV
547
        map.cacheId = this.cacheId
×
UNCOV
548
        map.cacheDuration = this.cacheDuration
×
UNCOV
549
        map.relationPropertyPath = this.relationPropertyPath
×
UNCOV
550
        map.of = this.of
×
UNCOV
551
        map.insertColumns = this.insertColumns
×
UNCOV
552
        map.whereEntities = this.whereEntities
×
UNCOV
553
        map.updateEntity = this.updateEntity
×
UNCOV
554
        map.callListeners = this.callListeners
×
UNCOV
555
        map.useTransaction = this.useTransaction
×
UNCOV
556
        map.timeTravel = this.timeTravel
×
UNCOV
557
        map.nativeParameters = Object.assign({}, this.nativeParameters)
×
UNCOV
558
        map.comment = this.comment
×
UNCOV
559
        map.commonTableExpressions = this.commonTableExpressions.map(
×
560
            (cteOptions) => ({
×
561
                alias: cteOptions.alias,
562
                queryBuilder:
563
                    typeof cteOptions.queryBuilder === "string"
×
564
                        ? cteOptions.queryBuilder
565
                        : cteOptions.queryBuilder.clone(),
566
                options: cteOptions.options,
567
            }),
568
        )
NEW
569
        map.treatJsNullAsSqlNull = this.treatJsNullAsSqlNull
×
NEW
570
        map.throwOnUndefinedInFind = this.throwOnUndefinedInFind
×
UNCOV
571
        return map
×
572
    }
573
}
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