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

typeorm / typeorm / 15675468506

16 Jun 2025 08:14AM UTC coverage: 76.35% (+0.02%) from 76.328%
15675468506

Pull #11422

github

web-flow
Merge f8e29bdc2 into 03faa7867
Pull Request #11422: fix(tree-entity): closure junction table primary key definition should match parent table

9286 of 12872 branches covered (72.14%)

Branch coverage included in aggregate %.

18997 of 24172 relevant lines covered (78.59%)

196908.4 hits per line

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

86.75
/src/query-builder/QueryExpressionMap.ts
1
import { Alias } from "./Alias"
40✔
2
import { ObjectLiteral } from "../common/ObjectLiteral"
3
import { OrderByCondition } from "../find-options/OrderByCondition"
4
import { JoinAttribute } from "./JoinAttribute"
40✔
5
import { QueryBuilder } from "./QueryBuilder"
6
import { QueryBuilderCteOptions } from "./QueryBuilderCte"
7
import { RelationIdAttribute } from "./relation-id/RelationIdAttribute"
40✔
8
import { RelationCountAttribute } from "./relation-count/RelationCountAttribute"
40✔
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"
40✔
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 {
40✔
24
    // -------------------------------------------------------------------------
25
    // Public Properties
26
    // -------------------------------------------------------------------------
27

28
    /**
29
     * Strategy to load relations.
30
     */
31
    relationLoadStrategy: "join" | "query" = "join"
1,566,765✔
32

33
    /**
34
     * Indicates if QueryBuilder used to select entities and not a raw results.
35
     */
36
    queryEntity: boolean = false
1,566,765✔
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
     */
46
    aliases: Alias[] = []
1,566,765✔
47

48
    /**
49
     * Represents query type. QueryBuilder is able to build SELECT, UPDATE and DELETE queries.
50
     */
51
    queryType:
1,566,765✔
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
     */
63
    selects: SelectQuery[] = []
1,566,765✔
64

65
    /**
66
     * Max execution time in millisecond.
67
     */
68
    maxExecutionTime: number = 0
1,566,765✔
69

70
    /**
71
     * Whether SELECT is DISTINCT.
72
     */
73
    selectDistinct: boolean = false
1,566,765✔
74

75
    /**
76
     * SELECT DISTINCT ON query (postgres).
77
     */
78
    selectDistinctOn: string[] = []
1,566,765✔
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
     */
99
    extraReturningColumns: ColumnMetadata[] = []
1,566,765✔
100

101
    /**
102
     * Optional on conflict statement used in insertion query in postgres.
103
     */
104
    onConflict: string = ""
1,566,765✔
105

106
    /**
107
     * Optional on ignore statement used in insertion query in databases.
108
     */
109
    onIgnore: boolean = false
1,566,765✔
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
        overwriteCondition?: WhereClause[]
122
    }
123

124
    /**
125
     * JOIN queries.
126
     */
127
    joinAttributes: JoinAttribute[] = []
1,566,765✔
128

129
    /**
130
     * RelationId queries.
131
     */
132
    relationIdAttributes: RelationIdAttribute[] = []
1,566,765✔
133

134
    /**
135
     * Relation count queries.
136
     */
137
    relationCountAttributes: RelationCountAttribute[] = []
1,566,765✔
138

139
    /**
140
     * WHERE queries.
141
     */
142
    wheres: WhereClause[] = []
1,566,765✔
143

144
    /**
145
     * HAVING queries.
146
     */
147
    havings: { type: "simple" | "and" | "or"; condition: string }[] = []
1,566,765✔
148

149
    /**
150
     * ORDER BY queries.
151
     */
152
    orderBys: OrderByCondition = {}
1,566,765✔
153

154
    /**
155
     * GROUP BY queries.
156
     */
157
    groupBys: string[] = []
1,566,765✔
158

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

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

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

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

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

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

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

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

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

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

220
    /**
221
     * Indicates if soft-deleted rows should be included in entity result.
222
     * By default the soft-deleted rows are not included.
223
     */
224
    withDeleted: boolean = false
1,566,765✔
225

226
    /**
227
     * Parameters used to be escaped in final query.
228
     */
229
    parameters: ObjectLiteral = {}
1,566,765✔
230

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

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

245
    /**
246
     * Extra where condition appended to the end of original where conditions with AND keyword.
247
     * Original condition will be wrapped into brackets.
248
     */
249
    extraAppendedAndWhereCondition: string = ""
1,566,765✔
250

251
    /**
252
     * Indicates if query builder creates a subquery.
253
     */
254
    subQuery: boolean = false
1,566,765✔
255

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

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

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

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

281
    /**
282
     * Options that define QueryBuilder behaviour.
283
     */
284
    options: SelectQueryBuilderOption[] = []
1,566,765✔
285

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

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

297
    /**
298
     * List of columns where data should be inserted.
299
     * Used in INSERT query.
300
     */
301
    insertColumns: string[] = []
1,566,765✔
302

303
    /**
304
     * Used if user wants to update or delete a specific entities.
305
     */
306
    whereEntities: ObjectLiteral[] = []
1,566,765✔
307

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

314
    /**
315
     * Indicates if listeners and subscribers must be called before and after query execution.
316
     */
317
    callListeners: boolean = true
1,566,765✔
318

319
    /**
320
     * Indicates if query must be wrapped into transaction.
321
     */
322
    useTransaction: boolean = false
1,566,765✔
323

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

330
    /**
331
     * Extra parameters.
332
     *
333
     * @deprecated Use standard parameters instead
334
     */
335
    nativeParameters: ObjectLiteral = {}
1,566,765✔
336

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

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

349
    commonTableExpressions: {
1,566,765✔
350
        queryBuilder: QueryBuilder<any> | string
351
        alias: string
352
        options: QueryBuilderCteOptions
353
    }[] = []
354

355
    // -------------------------------------------------------------------------
356
    // Constructor
357
    // -------------------------------------------------------------------------
358

359
    constructor(protected connection: DataSource) {
1,566,765✔
360
        if (connection.options.relationLoadStrategy) {
1,566,765✔
361
            this.relationLoadStrategy = connection.options.relationLoadStrategy
158,403✔
362
        }
363

364
        this.timeTravel =
1,566,765✔
365
            (connection.options as CockroachConnectionOptions)
3,133,530✔
366
                ?.timeTravelQueries || false
367
    }
368

369
    // -------------------------------------------------------------------------
370
    // Accessors
371
    // -------------------------------------------------------------------------
372

373
    /**
374
     * Get all ORDER BY queries - if order by is specified by user then it uses them,
375
     * otherwise it uses default entity order by if it was set.
376
     */
377
    get allOrderBys() {
378
        if (
779,329✔
379
            !Object.keys(this.orderBys).length &&
2,259,608✔
380
            this.mainAlias!.hasMetadata &&
381
            this.options.indexOf("disable-global-order") === -1
382
        ) {
383
            const entityOrderBy = this.mainAlias!.metadata.orderBy || {}
735,699✔
384
            return Object.keys(entityOrderBy).reduce((orderBy, key) => {
735,699✔
385
                orderBy[this.mainAlias!.name + "." + key] = entityOrderBy[key]
334✔
386
                return orderBy
334✔
387
            }, {} as OrderByCondition)
388
        }
389

390
        return this.orderBys
43,630✔
391
    }
392

393
    // -------------------------------------------------------------------------
394
    // Public Methods
395
    // -------------------------------------------------------------------------
396

397
    /**
398
     * Creates a main alias and adds it to the current expression map.
399
     */
400
    setMainAlias(alias: Alias): Alias {
401
        // if main alias is already set then remove it from the array
402
        // if (this.mainAlias)
403
        //     this.aliases.splice(this.aliases.indexOf(this.mainAlias));
404

405
        // set new main alias
406
        this.mainAlias = alias
1,120,470✔
407

408
        return alias
1,120,470✔
409
    }
410

411
    /**
412
     * Creates a new alias and adds it to the current expression map.
413
     */
414
    createAlias(options: {
415
        type: "from" | "select" | "join" | "other"
416
        name?: string
417
        target?: Function | string
418
        tablePath?: string
419
        subQuery?: string
420
        metadata?: EntityMetadata
421
    }): Alias {
422
        let aliasName = options.name
7,346,728✔
423
        if (!aliasName && options.tablePath) aliasName = options.tablePath
7,346,728✔
424
        if (!aliasName && typeof options.target === "function")
7,346,728!
425
            aliasName = options.target.name
×
426
        if (!aliasName && typeof options.target === "string")
7,346,728!
427
            aliasName = options.target
×
428

429
        const alias = new Alias()
7,346,728✔
430
        alias.type = options.type
7,346,728✔
431
        if (aliasName) alias.name = aliasName
7,346,728✔
432
        if (options.metadata) alias.metadata = options.metadata
7,346,728✔
433
        if (options.target && !alias.hasMetadata)
7,346,728!
434
            alias.metadata = this.connection.getMetadata(options.target)
×
435
        if (options.tablePath) alias.tablePath = options.tablePath
7,346,728✔
436
        if (options.subQuery) alias.subQuery = options.subQuery
7,346,728✔
437

438
        this.aliases.push(alias)
7,346,728✔
439
        return alias
7,346,728✔
440
    }
441

442
    /**
443
     * Finds alias with the given name.
444
     * If alias was not found it throw an exception.
445
     */
446
    findAliasByName(aliasName: string): Alias {
447
        const alias = this.aliases.find((alias) => alias.name === aliasName)
7,046,709✔
448
        if (!alias)
4,416,381!
449
            throw new TypeORMError(
×
450
                `"${aliasName}" alias was not found. Maybe you forgot to join it?`,
451
            )
452

453
        return alias
4,416,381✔
454
    }
455

456
    findColumnByAliasExpression(
457
        aliasExpression: string,
458
    ): ColumnMetadata | undefined {
459
        const [aliasName, propertyPath] = aliasExpression.split(".")
×
460
        const alias = this.findAliasByName(aliasName)
×
461
        return alias.metadata.findColumnWithPropertyName(propertyPath)
×
462
    }
463

464
    /**
465
     * Gets relation metadata of the relation this query builder works with.
466
     *
467
     * todo: add proper exceptions
468
     */
469
    get relationMetadata(): RelationMetadata {
470
        if (!this.mainAlias)
5,052!
471
            throw new TypeORMError(`Entity to work with is not specified!`) // todo: better message
×
472

473
        const relationMetadata =
474
            this.mainAlias.metadata.findRelationWithPropertyPath(
5,052✔
475
                this.relationPropertyPath,
476
            )
477
        if (!relationMetadata)
5,052!
478
            throw new TypeORMError(
×
479
                `Relation ${this.relationPropertyPath} was not found in entity ${this.mainAlias.name}`,
480
            ) // todo: better message
481

482
        return relationMetadata
5,052✔
483
    }
484

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