• 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

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

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

20
    constructor(private connection: DataSource) {}
84✔
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(
2✔
34
            relation,
35
            joinTable,
36
        )
37
        const inverseReferencedColumns = this.collectInverseReferencedColumns(
2✔
38
            relation,
39
            joinTable,
40
        )
41

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

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

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

88
            return new ColumnMetadata({
2✔
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 &&
12!
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
2!
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(
2✔
136
            (inverseReferencedColumn) => {
137
                const joinColumn = joinTable.inverseJoinColumns
2!
138
                    ? joinTable.inverseJoinColumns.find((joinColumnArgs) => {
UNCOV
139
                          return (
×
140
                              (!joinColumnArgs.referencedColumnName ||
×
141
                                  joinColumnArgs.referencedColumnName ===
142
                                      inverseReferencedColumn.propertyName) &&
143
                              !!joinColumnArgs.name
144
                          )
145
                      })
146
                    : undefined
147
                const columnName =
148
                    joinColumn && joinColumn.name
2!
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({
2✔
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 &&
12!
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
2!
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(
2✔
206
            junctionColumns,
207
            inverseJunctionColumns,
208
        )
209

210
        // set junction table columns
211
        entityMetadata.ownerColumns = junctionColumns
2✔
212
        entityMetadata.inverseColumns = inverseJunctionColumns
2✔
213
        entityMetadata.ownColumns = [
2✔
214
            ...junctionColumns,
215
            ...inverseJunctionColumns,
216
        ]
217
        entityMetadata.ownColumns.forEach(
2✔
218
            (column) => (column.relationMetadata = relation),
4✔
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
2!
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"
2!
234
                              ? "NO ACTION"
235
                              : relation.onDelete || "CASCADE",
4✔
236
                      onUpdate:
237
                          this.connection.driver.options.type === "oracle" ||
6!
238
                          this.connection.driver.options.type === "spanner"
239
                              ? "NO ACTION"
240
                              : relation.onUpdate || "CASCADE",
4✔
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"
2!
250
                              ? "NO ACTION"
251
                              : relation.inverseRelation
2!
252
                              ? relation.inverseRelation.onDelete
253
                              : "CASCADE",
254
                      onUpdate:
255
                          this.connection.driver.options.type === "oracle" ||
6!
256
                          this.connection.driver.options.type === "spanner"
257
                              ? "NO ACTION"
258
                              : relation.inverseRelation
2!
259
                              ? relation.inverseRelation.onUpdate
260
                              : "CASCADE",
261
                  }),
262
              ]
263
            : []
264

265
        // create junction table indices
266
        entityMetadata.ownIndices = [
2✔
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
2✔
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
2!
302
            ? joinTable.joinColumns.find(
UNCOV
303
                  (joinColumn) => !!joinColumn.referencedColumnName,
×
304
              )
305
            : false
306
        if (
2!
307
            !joinTable.joinColumns ||
2!
308
            (joinTable.joinColumns && !hasAnyReferencedColumnName)
309
        ) {
310
            return relation.entityMetadata.columns.filter(
2✔
311
                (column) => column.isPrimary,
5✔
312
            )
313
        } else {
UNCOV
314
            return joinTable.joinColumns.map((joinColumn) => {
×
UNCOV
315
                const referencedColumn = relation.entityMetadata.columns.find(
×
316
                    (column) =>
UNCOV
317
                        column.propertyName === joinColumn.referencedColumnName,
×
318
                )
UNCOV
319
                if (!referencedColumn)
×
320
                    throw new TypeORMError(
×
321
                        `Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.entityMetadata.name}`,
322
                    )
323

UNCOV
324
                return referencedColumn
×
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
2✔
337
        const hasAnyInverseReferencedColumnName = hasInverseJoinColumns
2!
338
            ? joinTable.inverseJoinColumns!.find(
UNCOV
339
                  (joinColumn) => !!joinColumn.referencedColumnName,
×
340
              )
341
            : false
342
        if (
2!
343
            !hasInverseJoinColumns ||
2!
344
            (hasInverseJoinColumns && !hasAnyInverseReferencedColumnName)
345
        ) {
346
            return relation.inverseEntityMetadata.primaryColumns
2✔
347
        } else {
UNCOV
348
            return joinTable.inverseJoinColumns!.map((joinColumn) => {
×
349
                const referencedColumn =
UNCOV
350
                    relation.inverseEntityMetadata.ownColumns.find(
×
351
                        (column) =>
UNCOV
352
                            column.propertyName ===
×
353
                            joinColumn.referencedColumnName,
354
                    )
UNCOV
355
                if (!referencedColumn)
×
356
                    throw new TypeORMError(
×
357
                        `Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.inverseEntityMetadata.name}`,
358
                    )
359

UNCOV
360
                return referencedColumn
×
361
            })
362
        }
363
    }
364

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

383
                    const inverseJunctionColumnName =
UNCOV
384
                        this.connection.namingStrategy.joinTableColumnDuplicationPrefix(
×
385
                            inverseJunctionColumn.propertyName,
386
                            2,
387
                        )
UNCOV
388
                    inverseJunctionColumn.propertyName =
×
389
                        inverseJunctionColumnName
UNCOV
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