• 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

96.77
/src/query-builder/relation-count/RelationCountLoader.ts
1
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
26✔
2
import { DataSource } from "../../data-source/DataSource"
26✔
3
import { RelationCountAttribute } from "./RelationCountAttribute"
26✔
4
import { RelationCountLoadResult } from "./RelationCountLoadResult"
26✔
5
import { QueryRunner } from "../../query-runner/QueryRunner"
26✔
6

26✔
7
export class RelationCountLoader {
26✔
8
    // -------------------------------------------------------------------------
26✔
9
    // Constructor
26✔
10
    // -------------------------------------------------------------------------
26✔
11

26✔
12
    constructor(
26✔
13
        protected connection: DataSource,
173,120✔
14
        protected queryRunner: QueryRunner | undefined,
173,120✔
15
        protected relationCountAttributes: RelationCountAttribute[],
173,120✔
16
    ) {}
173,120✔
17

26✔
18
    // -------------------------------------------------------------------------
26✔
19
    // Public Methods
26✔
20
    // -------------------------------------------------------------------------
26✔
21

26✔
22
    async load(rawEntities: any[]): Promise<RelationCountLoadResult[]> {
26✔
23
        const onlyUnique = (value: any, index: number, self: any) => {
60,309✔
24
            return self.indexOf(value) === index
8,540✔
25
        }
8,540✔
26

60,309✔
27
        const promises = this.relationCountAttributes.map(
60,309✔
28
            async (relationCountAttr) => {
60,309✔
29
                if (relationCountAttr.relation.isOneToMany) {
3,136✔
30
                    // example: Post and Category
504✔
31
                    // loadRelationCountAndMap("post.categoryCount", "post.categories")
504✔
32
                    // we expect it to load array of post ids
504✔
33

504✔
34
                    // todo(dima): fix issues wit multiple primary keys and remove joinColumns[0]
504✔
35
                    const relation = relationCountAttr.relation // "category.posts"
504✔
36
                    const inverseRelation = relation.inverseRelation! // "post.category"
504✔
37
                    const referenceColumnName =
504✔
38
                        inverseRelation.joinColumns[0].referencedColumn!
504✔
39
                            .propertyName // post id
504✔
40
                    const inverseSideTable =
504✔
41
                        relation.inverseEntityMetadata.target // Post
504✔
42
                    const inverseSideTableName =
504✔
43
                        relation.inverseEntityMetadata.tableName // post
504✔
44
                    const inverseSideTableAlias =
504✔
45
                        relationCountAttr.alias || inverseSideTableName // if condition (custom query builder factory) is set then relationIdAttr.alias defined
504✔
46
                    const inverseSidePropertyName = inverseRelation.propertyName // "category" from "post.category"
504✔
47

504✔
48
                    let referenceColumnValues = rawEntities
504✔
49
                        .map(
504✔
50
                            (rawEntity) =>
504✔
51
                                rawEntity[
1,092✔
52
                                    relationCountAttr.parentAlias +
1,092✔
53
                                        "_" +
1,092✔
54
                                        referenceColumnName
1,092✔
55
                                ],
504✔
56
                        )
504✔
57
                        .filter((value) => !!value)
504✔
58
                    referenceColumnValues =
504✔
59
                        referenceColumnValues.filter(onlyUnique)
504✔
60

504✔
61
                    // ensure we won't perform redundant queries for joined data which was not found in selection
504✔
62
                    // example: if post.category was not found in db then no need to execute query for category.imageIds
504✔
63
                    if (referenceColumnValues.length === 0)
504✔
64
                        return {
504!
65
                            relationCountAttribute: relationCountAttr,
×
66
                            results: [],
×
67
                        }
×
68

504✔
69
                    // generate query:
504✔
70
                    // SELECT category.post as parentId, COUNT(*) AS cnt FROM category category WHERE category.post IN (1, 2) GROUP BY category.post
504✔
71
                    const qb = this.connection.createQueryBuilder(
504✔
72
                        this.queryRunner,
504✔
73
                    )
504✔
74
                    qb.select(
504✔
75
                        inverseSideTableAlias + "." + inverseSidePropertyName,
504✔
76
                        "parentId",
504✔
77
                    )
504✔
78
                        .addSelect("COUNT(*)", "cnt")
504✔
79
                        .from(inverseSideTable, inverseSideTableAlias)
504✔
80
                        .where(
504✔
81
                            inverseSideTableAlias +
504✔
82
                                "." +
504✔
83
                                inverseSidePropertyName +
504✔
84
                                " IN (:...ids)",
504✔
85
                        )
504✔
86
                        .addGroupBy(
504✔
87
                            inverseSideTableAlias +
504✔
88
                                "." +
504✔
89
                                inverseSidePropertyName,
504✔
90
                        )
504✔
91
                        .setParameter("ids", referenceColumnValues)
504✔
92

504✔
93
                    // apply condition (custom query builder factory)
504✔
94
                    if (relationCountAttr.queryBuilderFactory)
504✔
95
                        relationCountAttr.queryBuilderFactory(qb)
504✔
96

504✔
97
                    return {
504✔
98
                        relationCountAttribute: relationCountAttr,
504✔
99
                        results: await qb.getRawMany(),
504✔
100
                    }
504✔
101
                } else {
3,136✔
102
                    // example: Post and Category
2,632✔
103
                    // owner side: loadRelationIdAndMap("post.categoryIds", "post.categories")
2,632✔
104
                    // inverse side: loadRelationIdAndMap("category.postIds", "category.posts")
2,632✔
105
                    // we expect it to load array of post ids
2,632✔
106

2,632✔
107
                    let joinTableColumnName: string
2,632✔
108
                    let inverseJoinColumnName: string
2,632✔
109
                    let firstJunctionColumn: ColumnMetadata
2,632✔
110
                    let secondJunctionColumn: ColumnMetadata
2,632✔
111

2,632✔
112
                    if (relationCountAttr.relation.isOwning) {
2,632✔
113
                        // todo fix joinColumns[0] and inverseJoinColumns[0].
1,624✔
114
                        joinTableColumnName =
1,624✔
115
                            relationCountAttr.relation.joinColumns[0]
1,624✔
116
                                .referencedColumn!.databaseName
1,624✔
117
                        inverseJoinColumnName =
1,624✔
118
                            relationCountAttr.relation.inverseJoinColumns[0]
1,624✔
119
                                .referencedColumn!.databaseName
1,624✔
120
                        firstJunctionColumn =
1,624✔
121
                            relationCountAttr.relation.junctionEntityMetadata!
1,624✔
122
                                .columns[0]
1,624✔
123
                        secondJunctionColumn =
1,624✔
124
                            relationCountAttr.relation.junctionEntityMetadata!
1,624✔
125
                                .columns[1]
1,624✔
126
                    } else {
2,632✔
127
                        joinTableColumnName =
1,008✔
128
                            relationCountAttr.relation.inverseRelation!
1,008✔
129
                                .inverseJoinColumns[0].referencedColumn!
1,008✔
130
                                .databaseName
1,008✔
131
                        inverseJoinColumnName =
1,008✔
132
                            relationCountAttr.relation.inverseRelation!
1,008✔
133
                                .joinColumns[0].referencedColumn!.databaseName
1,008✔
134
                        firstJunctionColumn =
1,008✔
135
                            relationCountAttr.relation.junctionEntityMetadata!
1,008✔
136
                                .columns[1]
1,008✔
137
                        secondJunctionColumn =
1,008✔
138
                            relationCountAttr.relation.junctionEntityMetadata!
1,008✔
139
                                .columns[0]
1,008✔
140
                    }
1,008✔
141

2,632✔
142
                    let referenceColumnValues = rawEntities
2,632✔
143
                        .map(
2,632✔
144
                            (rawEntity) =>
2,632✔
145
                                rawEntity[
7,448✔
146
                                    relationCountAttr.parentAlias +
7,448✔
147
                                        "_" +
7,448✔
148
                                        joinTableColumnName
7,448✔
149
                                ],
2,632✔
150
                        )
2,632✔
151
                        .filter((value) => !!value)
2,632✔
152
                    referenceColumnValues =
2,632✔
153
                        referenceColumnValues.filter(onlyUnique)
2,632✔
154

2,632✔
155
                    // ensure we won't perform redundant queries for joined data which was not found in selection
2,632✔
156
                    // example: if post.category was not found in db then no need to execute query for category.imageIds
2,632✔
157
                    if (referenceColumnValues.length === 0)
2,632✔
158
                        return {
2,632!
159
                            relationCountAttribute: relationCountAttr,
×
160
                            results: [],
×
161
                        }
×
162

2,632✔
163
                    const junctionAlias = relationCountAttr.junctionAlias
2,632✔
164
                    const inverseSideTableName =
2,632✔
165
                        relationCountAttr.joinInverseSideMetadata.tableName
2,632✔
166
                    const inverseSideTableAlias =
2,632✔
167
                        relationCountAttr.alias || inverseSideTableName
2,632✔
168
                    const junctionTableName =
2,632✔
169
                        relationCountAttr.relation.junctionEntityMetadata!
2,632✔
170
                            .tableName
2,632✔
171

2,632✔
172
                    const condition =
2,632✔
173
                        junctionAlias +
2,632✔
174
                        "." +
2,632✔
175
                        firstJunctionColumn.propertyName +
2,632✔
176
                        " IN (" +
2,632✔
177
                        referenceColumnValues.map((vals) =>
2,632✔
178
                            isNaN(vals) ? "'" + vals + "'" : vals,
2,632✔
179
                        ) +
2,632✔
180
                        ")" +
2,632✔
181
                        " AND " +
2,632✔
182
                        junctionAlias +
2,632✔
183
                        "." +
2,632✔
184
                        secondJunctionColumn.propertyName +
2,632✔
185
                        " = " +
2,632✔
186
                        inverseSideTableAlias +
2,632✔
187
                        "." +
2,632✔
188
                        inverseJoinColumnName
2,632✔
189

2,632✔
190
                    const qb = this.connection.createQueryBuilder(
2,632✔
191
                        this.queryRunner,
2,632✔
192
                    )
2,632✔
193
                    qb.select(
2,632✔
194
                        junctionAlias + "." + firstJunctionColumn.propertyName,
2,632✔
195
                        "parentId",
2,632✔
196
                    )
2,632✔
197
                        .addSelect(
2,632✔
198
                            "COUNT(" +
2,632✔
199
                                qb.escape(inverseSideTableAlias) +
2,632✔
200
                                "." +
2,632✔
201
                                qb.escape(inverseJoinColumnName) +
2,632✔
202
                                ")",
2,632✔
203
                            "cnt",
2,632✔
204
                        )
2,632✔
205
                        .from(inverseSideTableName, inverseSideTableAlias)
2,632✔
206
                        .innerJoin(junctionTableName, junctionAlias, condition)
2,632✔
207
                        .addGroupBy(
2,632✔
208
                            junctionAlias +
2,632✔
209
                                "." +
2,632✔
210
                                firstJunctionColumn.propertyName,
2,632✔
211
                        )
2,632✔
212

2,632✔
213
                    // apply condition (custom query builder factory)
2,632✔
214
                    if (relationCountAttr.queryBuilderFactory)
2,632✔
215
                        relationCountAttr.queryBuilderFactory(qb)
2,632✔
216

2,632✔
217
                    return {
2,632✔
218
                        relationCountAttribute: relationCountAttr,
2,632✔
219
                        results: await qb.getRawMany(),
2,632✔
220
                    }
2,632✔
221
                }
2,632✔
222
            },
60,309✔
223
        )
60,309✔
224

60,309✔
225
        return Promise.all(promises)
60,309✔
226
    }
60,309✔
227
}
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