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

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

87.84
/src/metadata-builder/JunctionEntityMetadataBuilder.ts
1
import { ColumnMetadata } from "../metadata/ColumnMetadata"
4✔
2
import { DataSource } from "../data-source/DataSource"
3
import { EntityMetadata } from "../metadata/EntityMetadata"
4✔
4
import { ForeignKeyMetadata } from "../metadata/ForeignKeyMetadata"
4✔
5
import { IndexMetadata } from "../metadata/IndexMetadata"
4✔
6
import { JoinTableMetadataArgs } from "../metadata-args/JoinTableMetadataArgs"
7
import { RelationMetadata } from "../metadata/RelationMetadata"
8
import { TypeORMError } from "../error"
4✔
9
import { DriverUtils } from "../driver/DriverUtils"
4✔
10

11
/**
12
 * Creates EntityMetadata for junction tables.
13
 * Junction tables are tables generated by many-to-many relations.
14
 */
15
export class JunctionEntityMetadataBuilder {
4✔
16
    // -------------------------------------------------------------------------
17
    // Constructor
18
    // -------------------------------------------------------------------------
19

20
    constructor(private connection: DataSource) {}
3,704✔
21

22
    // -------------------------------------------------------------------------
23
    // Public Methods
24
    // -------------------------------------------------------------------------
25

26
    /**
27
     * Builds EntityMetadata for the junction of the given many-to-many relation.
28
     */
29
    build(
30
        relation: RelationMetadata,
31
        joinTable: JoinTableMetadataArgs,
32
    ): EntityMetadata {
33
        const referencedColumns = this.collectReferencedColumns(
439✔
34
            relation,
35
            joinTable,
36
        )
37
        const inverseReferencedColumns = this.collectInverseReferencedColumns(
439✔
38
            relation,
39
            joinTable,
40
        )
41

42
        const joinTableName =
43
            joinTable.name ||
439✔
44
            this.connection.namingStrategy.joinTableName(
45
                relation.entityMetadata.tableNameWithoutPrefix,
46
                relation.inverseEntityMetadata.tableNameWithoutPrefix,
47
                relation.propertyPath,
48
                relation.inverseRelation
375✔
49
                    ? relation.inverseRelation.propertyName
50
                    : "",
51
            )
52

53
        const entityMetadata = new EntityMetadata({
439✔
54
            connection: this.connection,
55
            args: {
56
                target: "",
57
                name: joinTableName,
58
                type: "junction",
59
                database:
60
                    joinTable.database || relation.entityMetadata.database,
878✔
61
                schema: joinTable.schema || relation.entityMetadata.schema,
878✔
62
                synchronize: joinTable.synchronize,
63
            },
64
        })
65
        entityMetadata.build()
439✔
66

67
        // create original side junction columns
68
        const junctionColumns = referencedColumns.map((referencedColumn) => {
439✔
69
            const joinColumn = joinTable.joinColumns
499✔
70
                ? joinTable.joinColumns.find((joinColumnArgs) => {
71
                      return (
48✔
72
                          (!joinColumnArgs.referencedColumnName ||
136✔
73
                              joinColumnArgs.referencedColumnName ===
74
                                  referencedColumn.propertyName) &&
75
                          !!joinColumnArgs.name
76
                      )
77
                  })
78
                : undefined
79
            const columnName =
80
                joinColumn && joinColumn.name
499✔
81
                    ? joinColumn.name
82
                    : this.connection.namingStrategy.joinTableColumnName(
83
                          relation.entityMetadata.tableNameWithoutPrefix,
84
                          referencedColumn.propertyName,
85
                          referencedColumn.databaseName,
86
                      )
87

88
            return new ColumnMetadata({
499✔
89
                connection: this.connection,
90
                entityMetadata: entityMetadata,
91
                referencedColumn: referencedColumn,
92
                args: {
93
                    target: "",
94
                    mode: "virtual",
95
                    propertyName: columnName,
96
                    options: {
97
                        name: columnName,
98
                        length:
99
                            !referencedColumn.length &&
1,964!
100
                            (DriverUtils.isMySQLFamily(
101
                                this.connection.driver,
102
                            ) ||
103
                                this.connection.driver.options.type ===
104
                                    "aurora-mysql") &&
105
                            // some versions of mariadb support the column type and should not try to provide the length property
106
                            this.connection.driver.normalizeType(
107
                                referencedColumn,
108
                            ) !== "uuid" &&
109
                            (referencedColumn.generationStrategy === "uuid" ||
110
                                referencedColumn.type === "uuid")
111
                                ? "36"
112
                                : referencedColumn.length, // fix https://github.com/typeorm/typeorm/issues/3604
113
                        width: referencedColumn.width,
114
                        type: referencedColumn.type,
115
                        precision: referencedColumn.precision,
116
                        scale: referencedColumn.scale,
117
                        charset: referencedColumn.charset,
118
                        collation: referencedColumn.collation,
119
                        zerofill: referencedColumn.zerofill,
120
                        unsigned: referencedColumn.zerofill
499!
121
                            ? true
122
                            : referencedColumn.unsigned,
123
                        enum: referencedColumn.enum,
124
                        enumName: referencedColumn.enumName,
125
                        foreignKeyConstraintName:
126
                            joinColumn?.foreignKeyConstraintName,
127
                        nullable: false,
128
                        primary: true,
129
                    },
130
                },
131
            })
132
        })
133

134
        // create inverse side junction columns
135
        const inverseJunctionColumns = inverseReferencedColumns.map(
439✔
136
            (inverseReferencedColumn) => {
137
                const joinColumn = joinTable.inverseJoinColumns
503✔
138
                    ? joinTable.inverseJoinColumns.find((joinColumnArgs) => {
139
                          return (
88✔
140
                              (!joinColumnArgs.referencedColumnName ||
232✔
141
                                  joinColumnArgs.referencedColumnName ===
142
                                      inverseReferencedColumn.propertyName) &&
143
                              !!joinColumnArgs.name
144
                          )
145
                      })
146
                    : undefined
147
                const columnName =
148
                    joinColumn && joinColumn.name
503✔
149
                        ? joinColumn.name
150
                        : this.connection.namingStrategy.joinTableInverseColumnName(
151
                              relation.inverseEntityMetadata
152
                                  .tableNameWithoutPrefix,
153
                              inverseReferencedColumn.propertyName,
154
                              inverseReferencedColumn.databaseName,
155
                          )
156

157
                return new ColumnMetadata({
503✔
158
                    connection: this.connection,
159
                    entityMetadata: entityMetadata,
160
                    referencedColumn: inverseReferencedColumn,
161
                    args: {
162
                        target: "",
163
                        mode: "virtual",
164
                        propertyName: columnName,
165
                        options: {
166
                            length:
167
                                !inverseReferencedColumn.length &&
1,956!
168
                                (DriverUtils.isMySQLFamily(
169
                                    this.connection.driver,
170
                                ) ||
171
                                    this.connection.driver.options.type ===
172
                                        "aurora-mysql") &&
173
                                // some versions of mariadb support the column type and should not try to provide the length property
174
                                this.connection.driver.normalizeType(
175
                                    inverseReferencedColumn,
176
                                ) !== "uuid" &&
177
                                (inverseReferencedColumn.generationStrategy ===
178
                                    "uuid" ||
179
                                    inverseReferencedColumn.type === "uuid")
180
                                    ? "36"
181
                                    : inverseReferencedColumn.length, // fix https://github.com/typeorm/typeorm/issues/3604
182
                            width: inverseReferencedColumn.width, // fix https://github.com/typeorm/typeorm/issues/6442
183
                            type: inverseReferencedColumn.type,
184
                            precision: inverseReferencedColumn.precision,
185
                            scale: inverseReferencedColumn.scale,
186
                            charset: inverseReferencedColumn.charset,
187
                            collation: inverseReferencedColumn.collation,
188
                            zerofill: inverseReferencedColumn.zerofill,
189
                            unsigned: inverseReferencedColumn.zerofill
503!
190
                                ? true
191
                                : inverseReferencedColumn.unsigned,
192
                            enum: inverseReferencedColumn.enum,
193
                            enumName: inverseReferencedColumn.enumName,
194
                            foreignKeyConstraintName:
195
                                joinColumn?.foreignKeyConstraintName,
196
                            name: columnName,
197
                            nullable: false,
198
                            primary: true,
199
                        },
200
                    },
201
                })
202
            },
203
        )
204

205
        this.changeDuplicatedColumnNames(
439✔
206
            junctionColumns,
207
            inverseJunctionColumns,
208
        )
209

210
        // set junction table columns
211
        entityMetadata.ownerColumns = junctionColumns
439✔
212
        entityMetadata.inverseColumns = inverseJunctionColumns
439✔
213
        entityMetadata.ownColumns = [
439✔
214
            ...junctionColumns,
215
            ...inverseJunctionColumns,
216
        ]
217
        entityMetadata.ownColumns.forEach(
439✔
218
            (column) => (column.relationMetadata = relation),
1,002✔
219
        )
220

221
        // create junction table foreign keys
222
        // Note: UPDATE CASCADE clause is not supported in Oracle.
223
        // Note: UPDATE/DELETE CASCADE clauses are not supported in Spanner.
224
        entityMetadata.foreignKeys = relation.createForeignKeyConstraints
439!
225
            ? [
226
                  new ForeignKeyMetadata({
227
                      entityMetadata: entityMetadata,
228
                      referencedEntityMetadata: relation.entityMetadata,
229
                      columns: junctionColumns,
230
                      referencedColumns: referencedColumns,
231
                      name: junctionColumns[0]?.foreignKeyConstraintName,
232
                      onDelete:
233
                          this.connection.driver.options.type === "spanner"
439!
234
                              ? "NO ACTION"
235
                              : relation.onDelete || "CASCADE",
874✔
236
                      onUpdate:
237
                          this.connection.driver.options.type === "oracle" ||
1,210✔
238
                          this.connection.driver.options.type === "spanner"
239
                              ? "NO ACTION"
240
                              : relation.onUpdate || "CASCADE",
664✔
241
                  }),
242
                  new ForeignKeyMetadata({
243
                      entityMetadata: entityMetadata,
244
                      referencedEntityMetadata: relation.inverseEntityMetadata,
245
                      columns: inverseJunctionColumns,
246
                      referencedColumns: inverseReferencedColumns,
247
                      name: inverseJunctionColumns[0]?.foreignKeyConstraintName,
248
                      onDelete:
249
                          this.connection.driver.options.type === "spanner"
439!
250
                              ? "NO ACTION"
251
                              : relation.inverseRelation
439✔
252
                              ? relation.inverseRelation.onDelete
253
                              : "CASCADE",
254
                      onUpdate:
255
                          this.connection.driver.options.type === "oracle" ||
1,210✔
256
                          this.connection.driver.options.type === "spanner"
257
                              ? "NO ACTION"
258
                              : relation.inverseRelation
332✔
259
                              ? relation.inverseRelation.onUpdate
260
                              : "CASCADE",
261
                  }),
262
              ]
263
            : []
264

265
        // create junction table indices
266
        entityMetadata.ownIndices = [
439✔
267
            new IndexMetadata({
268
                entityMetadata: entityMetadata,
269
                columns: junctionColumns,
270
                args: {
271
                    target: entityMetadata.target,
272
                    synchronize: true,
273
                },
274
            }),
275

276
            new IndexMetadata({
277
                entityMetadata: entityMetadata,
278
                columns: inverseJunctionColumns,
279
                args: {
280
                    target: entityMetadata.target,
281
                    synchronize: true,
282
                },
283
            }),
284
        ]
285

286
        // finally return entity metadata
287
        return entityMetadata
439✔
288
    }
289

290
    // -------------------------------------------------------------------------
291
    // Protected Methods
292
    // -------------------------------------------------------------------------
293

294
    /**
295
     * Collects referenced columns from the given join column args.
296
     */
297
    protected collectReferencedColumns(
298
        relation: RelationMetadata,
299
        joinTable: JoinTableMetadataArgs,
300
    ): ColumnMetadata[] {
301
        const hasAnyReferencedColumnName = joinTable.joinColumns
439✔
302
            ? joinTable.joinColumns.find(
303
                  (joinColumn) => !!joinColumn.referencedColumnName,
32✔
304
              )
305
            : false
306
        if (
439✔
307
            !joinTable.joinColumns ||
503✔
308
            (joinTable.joinColumns && !hasAnyReferencedColumnName)
309
        ) {
310
            return relation.entityMetadata.columns.filter(
407✔
311
                (column) => column.isPrimary,
1,495✔
312
            )
313
        } else {
314
            return joinTable.joinColumns.map((joinColumn) => {
32✔
315
                const referencedColumn = relation.entityMetadata.columns.find(
40✔
316
                    (column) =>
317
                        column.propertyName === joinColumn.referencedColumnName,
64✔
318
                )
319
                if (!referencedColumn)
40!
320
                    throw new TypeORMError(
×
321
                        `Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.entityMetadata.name}`,
322
                    )
323

324
                return referencedColumn
40✔
325
            })
326
        }
327
    }
328

329
    /**
330
     * Collects inverse referenced columns from the given join column args.
331
     */
332
    protected collectInverseReferencedColumns(
333
        relation: RelationMetadata,
334
        joinTable: JoinTableMetadataArgs,
335
    ): ColumnMetadata[] {
336
        const hasInverseJoinColumns = !!joinTable.inverseJoinColumns
439✔
337
        const hasAnyInverseReferencedColumnName = hasInverseJoinColumns
439✔
338
            ? joinTable.inverseJoinColumns!.find(
339
                  (joinColumn) => !!joinColumn.referencedColumnName,
32✔
340
              )
341
            : false
342
        if (
439✔
343
            !hasInverseJoinColumns ||
503✔
344
            (hasInverseJoinColumns && !hasAnyInverseReferencedColumnName)
345
        ) {
346
            return relation.inverseEntityMetadata.primaryColumns
407✔
347
        } else {
348
            return joinTable.inverseJoinColumns!.map((joinColumn) => {
32✔
349
                const referencedColumn =
350
                    relation.inverseEntityMetadata.ownColumns.find(
56✔
351
                        (column) =>
352
                            column.propertyName ===
136✔
353
                            joinColumn.referencedColumnName,
354
                    )
355
                if (!referencedColumn)
56!
356
                    throw new TypeORMError(
×
357
                        `Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.inverseEntityMetadata.name}`,
358
                    )
359

360
                return referencedColumn
56✔
361
            })
362
        }
363
    }
364

365
    protected changeDuplicatedColumnNames(
366
        junctionColumns: ColumnMetadata[],
367
        inverseJunctionColumns: ColumnMetadata[],
368
    ) {
369
        junctionColumns.forEach((junctionColumn) => {
439✔
370
            inverseJunctionColumns.forEach((inverseJunctionColumn) => {
499✔
371
                if (
611!
372
                    junctionColumn.givenDatabaseName ===
373
                    inverseJunctionColumn.givenDatabaseName
374
                ) {
375
                    const junctionColumnName =
376
                        this.connection.namingStrategy.joinTableColumnDuplicationPrefix(
×
377
                            junctionColumn.propertyName,
378
                            1,
379
                        )
380
                    junctionColumn.propertyName = junctionColumnName
×
381
                    junctionColumn.givenDatabaseName = junctionColumnName
×
382

383
                    const inverseJunctionColumnName =
384
                        this.connection.namingStrategy.joinTableColumnDuplicationPrefix(
×
385
                            inverseJunctionColumn.propertyName,
386
                            2,
387
                        )
388
                    inverseJunctionColumn.propertyName =
×
389
                        inverseJunctionColumnName
390
                    inverseJunctionColumn.givenDatabaseName =
×
391
                        inverseJunctionColumnName
392
                }
393
            })
394
        })
395
    }
396
}
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