1 10
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
2 10
import {ColumnMetadata} from "../metadata/ColumnMetadata";
3 10
import {UniqueMetadata} from "../metadata/UniqueMetadata";
4 10
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
5
import {RelationMetadata} from "../metadata/RelationMetadata";
6
import {JoinColumnMetadataArgs} from "../metadata-args/JoinColumnMetadataArgs";
7
import {Connection} from "../connection/Connection";
8 10
import {OracleDriver} from "../driver/oracle/OracleDriver";
9 10
import {AuroraDataApiDriver} from "../driver/aurora-data-api/AuroraDataApiDriver";
10

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

43
    // -------------------------------------------------------------------------
44
    // Constructor
45
    // -------------------------------------------------------------------------
46

47 10
    constructor(private connection: Connection) {
48
    }
49

50
    // -------------------------------------------------------------------------
51
    // Public Methods
52
    // -------------------------------------------------------------------------
53

54
    /**
55
     * Builds a foreign key of the many-to-one or one-to-one owner relations.
56
     */
57 10
    build(joinColumns: JoinColumnMetadataArgs[], relation: RelationMetadata): {
58
      foreignKey: ForeignKeyMetadata|undefined,
59
      uniqueConstraint: UniqueMetadata|undefined,
60
    } {
61 10
        const referencedColumns = this.collectReferencedColumns(joinColumns, relation);
62 10
        if (!referencedColumns.length)
63 10
            return { foreignKey: undefined, uniqueConstraint: undefined }; // this case is possible only for one-to-one non owning side
64

65 10
        const columns = this.collectColumns(joinColumns, relation, referencedColumns);
66 10
        const foreignKey = new ForeignKeyMetadata({
67
            entityMetadata: relation.entityMetadata,
68
            referencedEntityMetadata: relation.inverseEntityMetadata,
69
            namingStrategy: this.connection.namingStrategy,
70
            columns: columns,
71
            referencedColumns: referencedColumns,
72
            onDelete: relation.onDelete,
73
            onUpdate: relation.onUpdate,
74
            deferrable: relation.deferrable,
75
        });
76

77
        // Oracle does not allow both primary and unique constraints on the same column
78 10
        if (this.connection.driver instanceof OracleDriver && columns.every(column => column.isPrimary))
79 2
            return { foreignKey, uniqueConstraint: undefined };
80

81
        // CockroachDB requires UNIQUE constraints on referenced columns
82 10
        if (referencedColumns.length > 0 && relation.isOneToOne) {
83 10
            const uniqueConstraint = new UniqueMetadata({
84
                entityMetadata: relation.entityMetadata,
85
                columns: foreignKey.columns,
86
                args: {
87 10
                    name: this.connection.namingStrategy.relationConstraintName(relation.entityMetadata.tablePath, foreignKey.columns.map(c => c.databaseName)),
88
                    target: relation.entityMetadata.target,
89
                }
90
            });
91 10
            uniqueConstraint.build(this.connection.namingStrategy);
92 10
            return {foreignKey, uniqueConstraint};
93
        }
94

95 10
        return { foreignKey, uniqueConstraint: undefined };
96
    }
97
    // -------------------------------------------------------------------------
98
    // Protected Methods
99
    // -------------------------------------------------------------------------
100

101
    /**
102
     * Collects referenced columns from the given join column args.
103
     */
104 10
    protected collectReferencedColumns(joinColumns: JoinColumnMetadataArgs[], relation: RelationMetadata): ColumnMetadata[] {
105 10
        const hasAnyReferencedColumnName = joinColumns.find(joinColumnArgs => !!joinColumnArgs.referencedColumnName);
106 10
        const manyToOneWithoutJoinColumn = joinColumns.length === 0 && relation.isManyToOne;
107 10
        const hasJoinColumnWithoutAnyReferencedColumnName = joinColumns.length > 0 && !hasAnyReferencedColumnName;
108

109 10
        if (manyToOneWithoutJoinColumn || hasJoinColumnWithoutAnyReferencedColumnName) { // covers case3 and case1
110 10
            return relation.inverseEntityMetadata.primaryColumns;
111

112
        } else { // cases with referenced columns defined
113 10
            return joinColumns.map(joinColumn => {
114 10
                const referencedColumn = relation.inverseEntityMetadata.ownColumns.find(column => column.propertyName === joinColumn.referencedColumnName); // todo: can we also search in relations?
115 10
                if (!referencedColumn)
116 0
                    throw new Error(`Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.inverseEntityMetadata.name}`);
117

118 10
                return referencedColumn;
119
            });
120
        }
121
    }
122

123
    /**
124
     * Collects columns from the given join column args.
125
     */
126 10
    private collectColumns(joinColumns: JoinColumnMetadataArgs[], relation: RelationMetadata, referencedColumns: ColumnMetadata[]): ColumnMetadata[] {
127 10
        return referencedColumns.map(referencedColumn => {
128

129
            // in the case if relation has join column with only name set we need this check
130 10
            const joinColumnMetadataArg = joinColumns.find(joinColumn => {
131 10
                return (!joinColumn.referencedColumnName || joinColumn.referencedColumnName === referencedColumn.propertyName) &&
132 10
                    !!joinColumn.name;
133
            });
134 10
            const joinColumnName = joinColumnMetadataArg ? joinColumnMetadataArg.name : this.connection.namingStrategy.joinColumnName(relation.propertyName, referencedColumn.propertyName);
135

136 10
            let relationalColumn = relation.entityMetadata.ownColumns.find(column => column.databaseName === joinColumnName);
137 10
            if (!relationalColumn) {
138 10
                relationalColumn = new ColumnMetadata({
139
                    connection: this.connection,
140
                    entityMetadata: relation.entityMetadata,
141
                    args: {
142
                        target: "",
143
                        mode: "virtual",
144
                        propertyName: relation.propertyName,
145
                        options: {
146
                            name: joinColumnName,
147
                            type: referencedColumn.type,
148 10
                            length: !referencedColumn.length
149 10
                                        && (this.connection.driver instanceof MysqlDriver || this.connection.driver instanceof AuroraDataApiDriver)
150 10
                                        && (referencedColumn.generationStrategy === "uuid" || referencedColumn.type === "uuid")
151 6
                                    ? "36"
152 10
                                    : referencedColumn.length, // fix https://github.com/typeorm/typeorm/issues/3604
153
                            width: referencedColumn.width,
154
                            charset: referencedColumn.charset,
155
                            collation: referencedColumn.collation,
156
                            precision: referencedColumn.precision,
157
                            scale: referencedColumn.scale,
158
                            zerofill: referencedColumn.zerofill,
159
                            unsigned: referencedColumn.unsigned,
160
                            comment: referencedColumn.comment,
161
                            primary: relation.isPrimary,
162
                            nullable: relation.isNullable
163
                        }
164
                    }
165
                });
166 10
                relation.entityMetadata.registerColumn(relationalColumn);
167
            }
168 10
            relationalColumn.referencedColumn = referencedColumn; // its important to set it here because we need to set referenced column for user defined join column
169 10
            relationalColumn.type = referencedColumn.type; // also since types of relational column and join column must be equal we override user defined column type
170 10
            relationalColumn.relationMetadata = relation;
171 10
            relationalColumn.build(this.connection);
172 10
            return relationalColumn;
173
        });
174
    }
175 10
}

Read our documentation on viewing source code .

Loading