• 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

85.71
/src/metadata-builder/RelationJoinColumnBuilder.ts
1
import { ColumnMetadata } from "../metadata/ColumnMetadata"
1✔
2
import { UniqueMetadata } from "../metadata/UniqueMetadata"
1✔
3
import { ForeignKeyMetadata } from "../metadata/ForeignKeyMetadata"
1✔
4
import { RelationMetadata } from "../metadata/RelationMetadata"
5
import { JoinColumnMetadataArgs } from "../metadata-args/JoinColumnMetadataArgs"
6
import { DataSource } from "../data-source/DataSource"
7
import { TypeORMError } from "../error"
1✔
8
import { DriverUtils } from "../driver/DriverUtils"
1✔
9

10
/**
11
 * Builds join column for the many-to-one and one-to-one owner relations.
12
 *
13
 * Cases it should cover:
14
 * 1. when join column is set with custom name and without referenced column name
15
 * we need automatically set referenced column name - primary ids by default
16
 * @JoinColumn({ name: "custom_name" })
17
 *
18
 * 2. when join column is set with only referenced column name
19
 * we need automatically set join column name - relation name + referenced column name
20
 * @JoinColumn({ referencedColumnName: "title" })
21
 *
22
 * 3. when join column is set without both referenced column name and join column name
23
 * we need to automatically set both of them
24
 * @JoinColumn()
25
 *
26
 * 4. when join column is not set at all (as in case of @ManyToOne relation)
27
 * we need to create join column for it with proper referenced column name and join column name
28
 *
29
 * 5. when multiple join columns set none of referencedColumnName and name can be optional
30
 * both options are required
31
 * @JoinColumn([
32
 *      { name: "category_title", referencedColumnName: "type" },
33
 *      { name: "category_title", referencedColumnName: "name" },
34
 * ])
35
 *
36
 * Since for many-to-one relations having JoinColumn decorator is not required,
37
 * we need to go through each many-to-one relation without join column decorator set
38
 * and create join column metadata args for them.
39
 */
40
export class RelationJoinColumnBuilder {
1✔
41
    // -------------------------------------------------------------------------
42
    // Constructor
43
    // -------------------------------------------------------------------------
44

45
    constructor(private connection: DataSource) {}
84✔
46

47
    // -------------------------------------------------------------------------
48
    // Public Methods
49
    // -------------------------------------------------------------------------
50

51
    /**
52
     * Builds a foreign key of the many-to-one or one-to-one owner relations.
53
     */
54
    build(
55
        joinColumns: JoinColumnMetadataArgs[],
56
        relation: RelationMetadata,
57
    ): {
58
        foreignKey: ForeignKeyMetadata | undefined
59
        columns: ColumnMetadata[]
60
        uniqueConstraint: UniqueMetadata | undefined
61
    } {
62
        const referencedColumns = this.collectReferencedColumns(
15✔
63
            joinColumns,
64
            relation,
65
        )
66
        const columns = this.collectColumns(
15✔
67
            joinColumns,
68
            relation,
69
            referencedColumns,
70
        )
71
        if (!referencedColumns.length || !relation.createForeignKeyConstraints)
15✔
72
            return {
6✔
73
                foreignKey: undefined,
74
                columns,
75
                uniqueConstraint: undefined,
76
            } // this case is possible for one-to-one non owning side and relations with createForeignKeyConstraints = false
77

78
        const foreignKey = new ForeignKeyMetadata({
9✔
79
            name: joinColumns[0]?.foreignKeyConstraintName,
80
            entityMetadata: relation.entityMetadata,
81
            referencedEntityMetadata: relation.inverseEntityMetadata,
82
            namingStrategy: this.connection.namingStrategy,
83
            columns,
84
            referencedColumns,
85
            onDelete: relation.onDelete,
86
            onUpdate: relation.onUpdate,
87
            deferrable: relation.deferrable,
88
        })
89

90
        // SQL requires UNIQUE/PK constraints on columns referenced by a FK
91
        // Skip creating the unique constraint for the referenced columns if
92
        // they are already contained in the PK of the referenced entity
93
        if (
9✔
94
            columns.every((column) => column.isPrimary) ||
9✔
95
            !relation.isOneToOne
96
        ) {
97
            return { foreignKey, columns, uniqueConstraint: undefined }
2✔
98
        }
99

100
        const uniqueConstraint = new UniqueMetadata({
7✔
101
            entityMetadata: relation.entityMetadata,
102
            columns: foreignKey.columns,
103
            args: {
104
                name: this.connection.namingStrategy.relationConstraintName(
105
                    relation.entityMetadata.tableName,
106
                    foreignKey.columns.map((column) => column.databaseName),
7✔
107
                ),
108
                target: relation.entityMetadata.target,
109
            },
110
        })
111
        uniqueConstraint.build(this.connection.namingStrategy)
7✔
112

113
        return { foreignKey, columns, uniqueConstraint }
7✔
114
    }
115

116
    // -------------------------------------------------------------------------
117
    // Protected Methods
118
    // -------------------------------------------------------------------------
119

120
    /**
121
     * Collects referenced columns from the given join column args.
122
     */
123
    protected collectReferencedColumns(
124
        joinColumns: JoinColumnMetadataArgs[],
125
        relation: RelationMetadata,
126
    ): ColumnMetadata[] {
127
        const hasAnyReferencedColumnName = joinColumns.find(
15✔
128
            (joinColumnArgs) => !!joinColumnArgs.referencedColumnName,
7✔
129
        )
130
        const manyToOneWithoutJoinColumn =
131
            joinColumns.length === 0 && relation.isManyToOne
15✔
132
        const hasJoinColumnWithoutAnyReferencedColumnName =
133
            joinColumns.length > 0 && !hasAnyReferencedColumnName
15✔
134

135
        if (
15✔
136
            manyToOneWithoutJoinColumn ||
28✔
137
            hasJoinColumnWithoutAnyReferencedColumnName
138
        ) {
139
            // covers case3 and case1
140
            return relation.inverseEntityMetadata.primaryColumns
9✔
141
        } else {
142
            // cases with referenced columns defined
143
            return joinColumns.map((joinColumn) => {
6✔
144
                const referencedColumn =
UNCOV
145
                    relation.inverseEntityMetadata.ownColumns.find(
×
146
                        (column) =>
UNCOV
147
                            column.propertyName ===
×
148
                            joinColumn.referencedColumnName,
149
                    ) // todo: can we also search in relations?
UNCOV
150
                if (!referencedColumn)
×
151
                    throw new TypeORMError(
×
152
                        `Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.inverseEntityMetadata.name}`,
153
                    )
154

UNCOV
155
                return referencedColumn
×
156
            })
157
        }
158
    }
159

160
    /**
161
     * Collects columns from the given join column args.
162
     */
163
    private collectColumns(
164
        joinColumns: JoinColumnMetadataArgs[],
165
        relation: RelationMetadata,
166
        referencedColumns: ColumnMetadata[],
167
    ): ColumnMetadata[] {
168
        return referencedColumns.map((referencedColumn) => {
15✔
169
            // in the case if relation has join column with only name set we need this check
170
            const joinColumnMetadataArg = joinColumns.find((joinColumn) => {
9✔
171
                return (
7✔
172
                    (!joinColumn.referencedColumnName ||
14!
173
                        joinColumn.referencedColumnName ===
174
                            referencedColumn.propertyName) &&
175
                    !!joinColumn.name
176
                )
177
            })
178
            const joinColumnName = joinColumnMetadataArg
9!
179
                ? joinColumnMetadataArg.name
180
                : this.connection.namingStrategy.joinColumnName(
181
                      relation.propertyName,
182
                      referencedColumn.propertyName,
183
                  )
184

185
            const relationalColumns = relation.embeddedMetadata
9!
186
                ? relation.embeddedMetadata.columns
187
                : relation.entityMetadata.ownColumns
188
            let relationalColumn = relationalColumns.find(
9✔
189
                (column) =>
190
                    column.databaseNameWithoutPrefixes === joinColumnName,
21✔
191
            )
192
            if (!relationalColumn) {
9✔
193
                relationalColumn = new ColumnMetadata({
9✔
194
                    connection: this.connection,
195
                    entityMetadata: relation.entityMetadata,
196
                    embeddedMetadata: relation.embeddedMetadata,
197
                    args: {
198
                        target: "",
199
                        mode: "virtual",
200
                        propertyName: relation.propertyName,
201
                        options: {
202
                            name: joinColumnName,
203
                            type: referencedColumn.type,
204
                            length:
205
                                !referencedColumn.length &&
54!
206
                                (DriverUtils.isMySQLFamily(
207
                                    this.connection.driver,
208
                                ) ||
209
                                    this.connection.driver.options.type ===
210
                                        "aurora-mysql") &&
211
                                // some versions of mariadb support the column type and should not try to provide the length property
212
                                this.connection.driver.normalizeType(
213
                                    referencedColumn,
214
                                ) !== "uuid" &&
215
                                (referencedColumn.generationStrategy ===
216
                                    "uuid" ||
217
                                    referencedColumn.type === "uuid")
218
                                    ? "36"
219
                                    : referencedColumn.length, // fix https://github.com/typeorm/typeorm/issues/3604
220
                            width: referencedColumn.width,
221
                            charset: referencedColumn.charset,
222
                            collation: referencedColumn.collation,
223
                            precision: referencedColumn.precision,
224
                            scale: referencedColumn.scale,
225
                            zerofill: referencedColumn.zerofill,
226
                            unsigned: referencedColumn.unsigned,
227
                            comment: referencedColumn.comment,
228
                            enum: referencedColumn.enum,
229
                            enumName: referencedColumn.enumName,
230
                            primary: relation.isPrimary,
231
                            nullable: relation.isNullable,
232
                        },
233
                    },
234
                })
235
                relation.entityMetadata.registerColumn(relationalColumn)
9✔
236
            }
237
            relationalColumn.referencedColumn = referencedColumn // its important to set it here because we need to set referenced column for user defined join column
9✔
238
            relationalColumn.type = referencedColumn.type // also since types of relational column and join column must be equal we override user defined column type
9✔
239
            relationalColumn.relationMetadata = relation
9✔
240
            relationalColumn.build(this.connection)
9✔
241
            return relationalColumn
9✔
242
        })
243
    }
244
}
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