• 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

90.71
/src/query-builder/JoinAttribute.ts
1
import { EntityMetadata } from "../metadata/EntityMetadata"
26✔
2
import { DataSource } from "../data-source/DataSource"
26✔
3
import { RelationMetadata } from "../metadata/RelationMetadata"
26✔
4
import { QueryBuilderUtils } from "./QueryBuilderUtils"
26✔
5
import { QueryExpressionMap } from "./QueryExpressionMap"
26✔
6
import { Alias } from "./Alias"
26✔
7
import { ObjectUtils } from "../util/ObjectUtils"
26✔
8
import { TypeORMError } from "../error"
26✔
9
import { DriverUtils } from "../driver/DriverUtils"
26✔
10

26✔
11
/**
26✔
12
 * Stores all join attributes which will be used to build a JOIN query.
26✔
13
 */
26✔
14
export class JoinAttribute {
26✔
15
    // -------------------------------------------------------------------------
26✔
16
    // Public Properties
26✔
17
    // -------------------------------------------------------------------------
26✔
18

26✔
19
    /**
26✔
20
     * Join direction.
26✔
21
     */
26✔
22
    direction: "LEFT" | "INNER"
26✔
23

26✔
24
    /**
26✔
25
     * Alias of the joined (destination) table.
26✔
26
     */
26✔
27
    alias: Alias
26✔
28

26✔
29
    /**
26✔
30
     * Joined table, entity target, or relation in "post.category" format.
26✔
31
     */
26✔
32
    entityOrProperty: Function | string
26✔
33

26✔
34
    /**
26✔
35
     * Extra condition applied to "ON" section of join.
26✔
36
     */
26✔
37
    condition?: string
26✔
38

26✔
39
    /**
26✔
40
     * Property + alias of the object where to joined data should be mapped.
26✔
41
     */
26✔
42
    mapToProperty?: string
26✔
43

26✔
44
    /**
26✔
45
     * Indicates if user maps one or many objects from the join.
26✔
46
     */
26✔
47
    isMappingMany?: boolean
26✔
48

26✔
49
    /**
26✔
50
     * Useful when the joined expression is a custom query to support mapping.
26✔
51
     */
26✔
52
    mapAsEntity?: Function | string
26✔
53

26✔
54
    // -------------------------------------------------------------------------
26✔
55
    // Constructor
26✔
56
    // -------------------------------------------------------------------------
26✔
57

26✔
58
    constructor(
26✔
59
        private connection: DataSource,
2,372,642✔
60
        private queryExpressionMap: QueryExpressionMap,
2,372,642✔
61
        joinAttribute?: JoinAttribute,
2,372,642✔
62
    ) {
2,372,642✔
63
        if (joinAttribute) {
2,372,642✔
64
            ObjectUtils.assign(this, joinAttribute)
18,410✔
65
        }
18,410✔
66
    }
2,372,642✔
67

26✔
68
    // -------------------------------------------------------------------------
26✔
69
    // Public Methods
26✔
70
    // -------------------------------------------------------------------------
26✔
71

26✔
72
    get isMany(): boolean {
26✔
73
        if (this.isMappingMany !== undefined) return this.isMappingMany
68,824✔
74

65,112✔
75
        if (this.relation)
65,112✔
76
            return this.relation.isManyToMany || this.relation.isOneToMany
65,112✔
77

×
78
        return false
×
79
    }
×
80

26✔
81
    isSelectedCache: boolean
26✔
82
    isSelectedEvaluated: boolean = false
26✔
83
    /**
26✔
84
     * Indicates if this join is selected.
26✔
85
     */
26✔
86
    get isSelected(): boolean {
26✔
87
        if (!this.isSelectedEvaluated) {
134,994✔
88
            const getValue = () => {
24,634✔
89
                for (const select of this.queryExpressionMap.selects) {
24,634✔
90
                    if (select.selection === this.alias.name) return true
55,342✔
91

35,310✔
92
                    if (
35,310✔
93
                        this.metadata &&
35,310✔
94
                        !!this.metadata.columns.find(
35,310✔
95
                            (column) =>
35,310✔
96
                                select.selection ===
108,906✔
97
                                this.alias.name + "." + column.propertyPath,
35,310✔
98
                        )
55,342✔
99
                    )
55,342✔
100
                        return true
55,342✔
101
                }
55,342✔
102

4,294✔
103
                return false
4,294✔
104
            }
4,294✔
105
            this.isSelectedCache = getValue()
24,634✔
106
            this.isSelectedEvaluated = true
24,634✔
107
        }
24,634✔
108
        return this.isSelectedCache
134,994✔
109
    }
134,994✔
110

26✔
111
    /**
26✔
112
     * Name of the table which we should join.
26✔
113
     */
26✔
114
    get tablePath(): string {
26✔
115
        return this.metadata
2,363,345✔
116
            ? this.metadata.tablePath
2,363,345✔
117
            : (this.entityOrProperty as string)
2,363,345✔
118
    }
2,363,345✔
119

26✔
120
    /**
26✔
121
     * Alias of the parent of this join.
26✔
122
     * For example, if we join ("post.category", "categoryAlias") then "post" is a parent alias.
26✔
123
     * This value is extracted from entityOrProperty value.
26✔
124
     * This is available when join was made using "post.category" syntax.
26✔
125
     */
26✔
126
    get parentAlias(): string | undefined {
26✔
127
        if (!QueryBuilderUtils.isAliasProperty(this.entityOrProperty))
4,741,788✔
128
            return undefined
4,741,788✔
129

4,728,763✔
130
        return this.entityOrProperty.substr(
4,728,763✔
131
            0,
4,728,763✔
132
            this.entityOrProperty.indexOf("."),
4,728,763✔
133
        )
4,728,763✔
134
    }
4,728,763✔
135

26✔
136
    /**
26✔
137
     * Relation property name of the parent.
26✔
138
     * This is used to understand what is joined.
26✔
139
     * For example, if we join ("post.category", "categoryAlias") then "category" is a relation property.
26✔
140
     * This value is extracted from entityOrProperty value.
26✔
141
     * This is available when join was made using "post.category" syntax.
26✔
142
     */
26✔
143
    get relationPropertyPath(): string | undefined {
26✔
144
        if (!QueryBuilderUtils.isAliasProperty(this.entityOrProperty))
2,373,796✔
145
            return undefined
2,373,796!
146

2,373,796✔
147
        return this.entityOrProperty.substr(
2,373,796✔
148
            this.entityOrProperty.indexOf(".") + 1,
2,373,796✔
149
        )
2,373,796✔
150
    }
2,373,796✔
151

26✔
152
    relationCache: RelationMetadata | undefined
26✔
153
    relationEvaluated: boolean = false
26✔
154
    /**
26✔
155
     * Relation of the parent.
26✔
156
     * This is used to understand what is joined.
26✔
157
     * This is available when join was made using "post.category" syntax.
26✔
158
     * Relation can be undefined if entityOrProperty is regular entity or custom table.
26✔
159
     */
26✔
160
    get relation(): RelationMetadata | undefined {
26✔
161
        if (!this.relationEvaluated) {
46,385,320✔
162
            const getValue = () => {
2,354,232✔
163
                if (!QueryBuilderUtils.isAliasProperty(this.entityOrProperty))
2,354,232✔
164
                    return undefined
2,354,232✔
165

2,341,207✔
166
                const relationOwnerSelection =
2,341,207✔
167
                    this.queryExpressionMap.findAliasByName(this.parentAlias!)
2,341,207✔
168
                let relation =
2,341,207✔
169
                    relationOwnerSelection.metadata.findRelationWithPropertyPath(
2,341,207✔
170
                        this.relationPropertyPath!,
2,341,207✔
171
                    )
2,341,207✔
172

2,341,207✔
173
                if (relation) {
2,341,207✔
174
                    return relation
2,341,207✔
175
                }
2,341,207✔
176

×
177
                if (relationOwnerSelection.metadata.parentEntityMetadata) {
×
178
                    relation =
×
179
                        relationOwnerSelection.metadata.parentEntityMetadata.findRelationWithPropertyPath(
×
180
                            this.relationPropertyPath!,
×
181
                        )
×
182
                    if (relation) {
×
183
                        return relation
×
184
                    }
×
185
                }
×
186

×
187
                throw new TypeORMError(
×
188
                    `Relation with property path ${this.relationPropertyPath} in entity was not found.`,
×
189
                )
×
190
            }
×
191
            this.relationCache = getValue.bind(this)()
2,354,232✔
192
            this.relationEvaluated = true
2,354,232✔
193
        }
2,354,232✔
194
        return this.relationCache
46,385,320✔
195
    }
46,385,320✔
196

26✔
197
    /**
26✔
198
     * Metadata of the joined entity.
26✔
199
     * If table without entity was joined, then it will return undefined.
26✔
200
     */
26✔
201
    get metadata(): EntityMetadata | undefined {
26✔
202
        // entityOrProperty is relation, e.g. "post.category"
14,524,214✔
203
        if (this.relation) return this.relation.inverseEntityMetadata
14,524,214✔
204

125,663✔
205
        // entityOrProperty is Entity class
125,663✔
206
        if (this.connection.hasMetadata(this.entityOrProperty))
125,663✔
207
            return this.connection.getMetadata(this.entityOrProperty)
14,524,214✔
208

1,571✔
209
        // Overriden mapping entity provided for leftJoinAndMapOne with custom query builder
1,571✔
210
        if (this.mapAsEntity && this.connection.hasMetadata(this.mapAsEntity)) {
14,524,214✔
211
            return this.connection.getMetadata(this.mapAsEntity)
896✔
212
        }
896✔
213

675✔
214
        return undefined
675✔
215

675✔
216
        /*if (typeof this.entityOrProperty === "string") { // entityOrProperty is a custom table
675✔
217

675✔
218
            // first try to find entity with such name, this is needed when entity does not have a target class,
675✔
219
            // and its target is a string name (scenario when plain old javascript is used or entity schema is loaded from files)
675✔
220
            const metadata = this.connection.entityMetadatas.find(metadata => metadata.name === this.entityOrProperty);
675✔
221
            if (metadata)
675✔
222
                return metadata;
675✔
223

675✔
224
            // check if we have entity with such table name, and use its metadata if found
675✔
225
            return this.connection.entityMetadatas.find(metadata => metadata.tableName === this.entityOrProperty);
675✔
226
        }*/
675✔
227
    }
675✔
228

26✔
229
    /**
26✔
230
     * Generates alias of junction table, whose ids we get.
26✔
231
     */
26✔
232
    get junctionAlias(): string {
26✔
233
        if (!this.relation) {
3,935,458!
234
            throw new TypeORMError(
×
235
                `Cannot get junction table for join without relation.`,
×
236
            )
×
237
        }
×
238
        if (typeof this.entityOrProperty !== "string") {
3,935,458!
239
            throw new TypeORMError(`Junction property is not defined.`)
×
240
        }
×
241

3,935,458✔
242
        const aliasProperty = this.entityOrProperty.substr(
3,935,458✔
243
            0,
3,935,458✔
244
            this.entityOrProperty.indexOf("."),
3,935,458✔
245
        )
3,935,458✔
246

3,935,458✔
247
        if (this.relation.isOwning) {
3,935,458✔
248
            return DriverUtils.buildAlias(
3,933,574✔
249
                this.connection.driver,
3,933,574✔
250
                undefined,
3,933,574✔
251
                aliasProperty,
3,933,574✔
252
                this.alias.name,
3,933,574✔
253
            )
3,933,574✔
254
        } else {
3,935,458✔
255
            return DriverUtils.buildAlias(
1,884✔
256
                this.connection.driver,
1,884✔
257
                undefined,
1,884✔
258
                this.alias.name,
1,884✔
259
                aliasProperty,
1,884✔
260
            )
1,884✔
261
        }
1,884✔
262
    }
3,935,458✔
263

26✔
264
    get mapToPropertyParentAlias(): string | undefined {
26✔
265
        if (!this.mapToProperty) return undefined
6,560!
266

6,560✔
267
        return this.mapToProperty!.split(".")[0]
6,560✔
268
    }
6,560✔
269

26✔
270
    get mapToPropertyPropertyName(): string | undefined {
26✔
271
        if (!this.mapToProperty) return undefined
36,268✔
272

3,712✔
273
        return this.mapToProperty!.split(".")[1]
3,712✔
274
    }
3,712✔
275
}
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