• 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

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

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

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

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

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

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

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

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

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

54
    // -------------------------------------------------------------------------
55
    // Constructor
56
    // -------------------------------------------------------------------------
57

58
    constructor(
UNCOV
59
        private connection: DataSource,
×
UNCOV
60
        private queryExpressionMap: QueryExpressionMap,
×
61
        joinAttribute?: JoinAttribute,
62
    ) {
UNCOV
63
        if (joinAttribute) {
×
UNCOV
64
            ObjectUtils.assign(this, joinAttribute)
×
65
        }
66
    }
67

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

72
    get isMany(): boolean {
UNCOV
73
        if (this.isMappingMany !== undefined) return this.isMappingMany
×
74

UNCOV
75
        if (this.relation)
×
UNCOV
76
            return this.relation.isManyToMany || this.relation.isOneToMany
×
77

78
        return false
×
79
    }
80

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

UNCOV
92
                    if (
×
93
                        this.metadata &&
×
94
                        !!this.metadata.columns.find(
95
                            (column) =>
UNCOV
96
                                select.selection ===
×
97
                                this.alias.name + "." + column.propertyPath,
98
                        )
99
                    )
UNCOV
100
                        return true
×
101
                }
102

UNCOV
103
                return false
×
104
            }
UNCOV
105
            this.isSelectedCache = getValue()
×
UNCOV
106
            this.isSelectedEvaluated = true
×
107
        }
UNCOV
108
        return this.isSelectedCache
×
109
    }
110

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

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

UNCOV
130
        return this.entityOrProperty.substr(
×
131
            0,
132
            this.entityOrProperty.indexOf("."),
133
        )
134
    }
135

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

UNCOV
147
        return this.entityOrProperty.substr(
×
148
            this.entityOrProperty.indexOf(".") + 1,
149
        )
150
    }
151

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

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

UNCOV
173
                if (relation) {
×
UNCOV
174
                    return relation
×
175
                }
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
            }
UNCOV
191
            this.relationCache = getValue.bind(this)()
×
UNCOV
192
            this.relationEvaluated = true
×
193
        }
UNCOV
194
        return this.relationCache
×
195
    }
196

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

205
        // entityOrProperty is Entity class
UNCOV
206
        if (this.connection.hasMetadata(this.entityOrProperty))
×
UNCOV
207
            return this.connection.getMetadata(this.entityOrProperty)
×
208

209
        // Overriden mapping entity provided for leftJoinAndMapOne with custom query builder
UNCOV
210
        if (this.mapAsEntity && this.connection.hasMetadata(this.mapAsEntity)) {
×
UNCOV
211
            return this.connection.getMetadata(this.mapAsEntity)
×
212
        }
213

UNCOV
214
        return undefined
×
215

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

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

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

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

UNCOV
242
        const aliasProperty = this.entityOrProperty.substr(
×
243
            0,
244
            this.entityOrProperty.indexOf("."),
245
        )
246

UNCOV
247
        if (this.relation.isOwning) {
×
UNCOV
248
            return DriverUtils.buildAlias(
×
249
                this.connection.driver,
250
                undefined,
251
                aliasProperty,
252
                this.alias.name,
253
            )
254
        } else {
UNCOV
255
            return DriverUtils.buildAlias(
×
256
                this.connection.driver,
257
                undefined,
258
                this.alias.name,
259
                aliasProperty,
260
            )
261
        }
262
    }
263

264
    get mapToPropertyParentAlias(): string | undefined {
UNCOV
265
        if (!this.mapToProperty) return undefined
×
266

UNCOV
267
        return this.mapToProperty!.split(".")[0]
×
268
    }
269

270
    get mapToPropertyPropertyName(): string | undefined {
UNCOV
271
        if (!this.mapToProperty) return undefined
×
272

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