schangxiang@126.com
2025-09-19 0821aa23eabe557c0d9ef5dbe6989c68be35d1fe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import { MissingPrimaryColumnError } from "../error/MissingPrimaryColumnError";
import { CircularRelationsError } from "../error/CircularRelationsError";
import { DepGraph } from "../util/DepGraph";
import { DataTypeNotSupportedError } from "../error/DataTypeNotSupportedError";
import { MongoDriver } from "../driver/mongodb/MongoDriver";
import { SqlServerDriver } from "../driver/sqlserver/SqlServerDriver";
import { MysqlDriver } from "../driver/mysql/MysqlDriver";
import { NoConnectionOptionError } from "../error/NoConnectionOptionError";
import { InitializedRelationError } from "../error/InitializedRelationError";
/// todo: add check if there are multiple tables with the same name
/// todo: add checks when generated column / table names are too long for the specific driver
// todo: type in function validation, inverse side function validation
// todo: check on build for duplicate names, since naming checking was removed from MetadataStorage
// todo: duplicate name checking for: table, relation, column, index, naming strategy, join tables/columns?
// todo: check if multiple tree parent metadatas in validator
// todo: tree decorators can be used only on closure table (validation)
// todo: throw error if parent tree metadata was not specified in a closure table
// todo: MetadataArgsStorage: type in function validation, inverse side function validation
// todo: MetadataArgsStorage: check on build for duplicate names, since naming checking was removed from MetadataStorage
// todo: MetadataArgsStorage: duplicate name checking for: table, relation, column, index, naming strategy, join tables/columns?
// todo: MetadataArgsStorage: check for duplicate targets too since this check has been removed too
// todo: check if relation decorator contains primary: true and nullable: true
// todo: check column length, precision. scale
// todo: MySQL index can be unique or spatial or fulltext
/**
 * Validates built entity metadatas.
 */
var EntityMetadataValidator = /** @class */ (function () {
    function EntityMetadataValidator() {
    }
    // -------------------------------------------------------------------------
    // Public Methods
    // -------------------------------------------------------------------------
    /**
     * Validates all given entity metadatas.
     */
    EntityMetadataValidator.prototype.validateMany = function (entityMetadatas, driver) {
        var _this = this;
        entityMetadatas.forEach(function (entityMetadata) { return _this.validate(entityMetadata, entityMetadatas, driver); });
        this.validateDependencies(entityMetadatas);
        this.validateEagerRelations(entityMetadatas);
    };
    /**
     * Validates given entity metadata.
     */
    EntityMetadataValidator.prototype.validate = function (entityMetadata, allEntityMetadatas, driver) {
        // check if table metadata has an id
        if (!entityMetadata.primaryColumns.length && !entityMetadata.isJunction)
            throw new MissingPrimaryColumnError(entityMetadata);
        // validate if table is using inheritance it has a discriminator
        // also validate if discriminator values are not empty and not repeated
        if (entityMetadata.inheritancePattern === "STI") {
            if (!entityMetadata.discriminatorColumn)
                throw new Error("Entity " + entityMetadata.name + " using single-table inheritance, it should also have a discriminator column. Did you forget to put discriminator column options?");
            if (["", undefined, null].indexOf(entityMetadata.discriminatorValue) !== -1)
                throw new Error("Entity " + entityMetadata.name + " has empty discriminator value. Discriminator value should not be empty.");
            var sameDiscriminatorValueEntityMetadata = allEntityMetadatas.find(function (metadata) {
                return metadata !== entityMetadata && metadata.discriminatorValue === entityMetadata.discriminatorValue;
            });
            if (sameDiscriminatorValueEntityMetadata)
                throw new Error("Entities " + entityMetadata.name + " and " + sameDiscriminatorValueEntityMetadata.name + " as equal discriminator values. Make sure their discriminator values are not equal using @DiscriminatorValue decorator.");
        }
        entityMetadata.relationCounts.forEach(function (relationCount) {
            if (relationCount.relation.isManyToOne || relationCount.relation.isOneToOne)
                throw new Error("Relation count can not be implemented on ManyToOne or OneToOne relations.");
        });
        if (!(driver instanceof MongoDriver)) {
            entityMetadata.columns.forEach(function (column) {
                var normalizedColumn = driver.normalizeType(column);
                if (driver.supportedDataTypes.indexOf(normalizedColumn) === -1)
                    throw new DataTypeNotSupportedError(column, normalizedColumn, driver.options.type);
                if (column.length && driver.withLengthColumnTypes.indexOf(normalizedColumn) === -1)
                    throw new Error("Column " + column.propertyName + " of Entity " + entityMetadata.name + " does not support length property.");
            });
        }
        if (driver instanceof MysqlDriver) {
            var generatedColumns = entityMetadata.columns.filter(function (column) { return column.isGenerated && column.generationStrategy !== "uuid"; });
            if (generatedColumns.length > 1)
                throw new Error("Error in " + entityMetadata.name + " entity. There can be only one auto-increment column in MySql table.");
        }
        // for mysql we are able to not define a default selected database, instead all entities can have their database
        // defined in their decorators. To make everything work either all entities must have database define and we
        // can live without database set in the connection options, either database in the connection options must be set
        if (driver instanceof MysqlDriver) {
            var metadatasWithDatabase = allEntityMetadatas.filter(function (metadata) { return metadata.database; });
            if (metadatasWithDatabase.length === 0 && !driver.database)
                throw new NoConnectionOptionError("database");
        }
        if (driver instanceof SqlServerDriver) {
            var charsetColumns = entityMetadata.columns.filter(function (column) { return column.charset; });
            if (charsetColumns.length > 1)
                throw new Error("Character set specifying is not supported in Sql Server");
        }
        // check if relations are all without initialized properties
        var entityInstance = entityMetadata.create();
        entityMetadata.relations.forEach(function (relation) {
            if (relation.isManyToMany || relation.isOneToMany) {
                // we skip relations for which persistence is disabled since initialization in them cannot harm somehow
                if (relation.persistenceEnabled === false)
                    return;
                // get entity relation value and check if its an array
                var relationInitializedValue = relation.getEntityValue(entityInstance);
                if (relationInitializedValue instanceof Array)
                    throw new InitializedRelationError(relation);
            }
        });
        // validate relations
        entityMetadata.relations.forEach(function (relation) {
            // check join tables:
            // using JoinTable is possible only on one side of the many-to-many relation
            // todo(dima): fix
            // if (relation.joinTable) {
            //     if (!relation.isManyToMany)
            //         throw new UsingJoinTableIsNotAllowedError(entityMetadata, relation);
            //     // if there is inverse side of the relation, then check if it does not have join table too
            //     if (relation.hasInverseSide && relation.inverseRelation.joinTable)
            //         throw new UsingJoinTableOnlyOnOneSideAllowedError(entityMetadata, relation);
            // }
            // check join columns:
            // using JoinColumn is possible only on one side of the relation and on one-to-one, many-to-one relation types
            // first check if relation is one-to-one or many-to-one
            // todo(dima): fix
            /*if (relation.joinColumn) {
 
                // join column can be applied only on one-to-one and many-to-one relations
                if (!relation.isOneToOne && !relation.isManyToOne)
                    throw new UsingJoinColumnIsNotAllowedError(entityMetadata, relation);
 
                // if there is inverse side of the relation, then check if it does not have join table too
                if (relation.hasInverseSide && relation.inverseRelation.joinColumn && relation.isOneToOne)
                    throw new UsingJoinColumnOnlyOnOneSideAllowedError(entityMetadata, relation);
 
                // check if join column really has referenced column
                if (relation.joinColumn && !relation.joinColumn.referencedColumn)
                    throw new Error(`Join column does not have referenced column set`);
 
            }
 
            // if its a one-to-one relation and JoinColumn is missing on both sides of the relation
            // or its one-side relation without JoinColumn we should give an error
            if (!relation.joinColumn && relation.isOneToOne && (!relation.hasInverseSide || !relation.inverseRelation.joinColumn))
                throw new MissingJoinColumnError(entityMetadata, relation);*/
            // if its a many-to-many relation and JoinTable is missing on both sides of the relation
            // or its one-side relation without JoinTable we should give an error
            // todo(dima): fix it
            // if (!relation.joinTable && relation.isManyToMany && (!relation.hasInverseSide || !relation.inverseRelation.joinTable))
            //     throw new MissingJoinTableError(entityMetadata, relation);
            // todo: validate if its one-to-one and side which does not have join column MUST have inverse side
            // todo: validate if its many-to-many and side which does not have join table MUST have inverse side
            // todo: if there is a relation, and inverse side is specified only on one side, shall we give error
            // todo: with message like: "Inverse side is specified only on one side of the relationship. Specify on other side too to prevent confusion".
            // todo: add validation if there two entities with the same target, and show error message with description of the problem (maybe file was renamed/moved but left in output directory)
            // todo: check if there are multiple columns on the same column applied.
            // todo: check column type if is missing in relational databases (throw new Error(`Column type of ${type} cannot be determined.`);)
            // todo: include driver-specific checks. for example in mongodb empty prefixes are not allowed
            // todo: if multiple columns with same name - throw exception, including cases when columns are in embeds with same prefixes or without prefix at all
            // todo: if multiple primary key used, at least one of them must be unique or @Index decorator must be set on entity
            // todo: check if entity with duplicate names, some decorators exist
        });
        // make sure cascade remove is not set for both sides of relationships (can be set in OneToOne decorators)
        entityMetadata.relations.forEach(function (relation) {
            var isCircularCascadeRemove = relation.isCascadeRemove && relation.inverseRelation && relation.inverseRelation.isCascadeRemove;
            if (isCircularCascadeRemove)
                throw new Error("Relation " + entityMetadata.name + "#" + relation.propertyName + " and " + relation.inverseRelation.entityMetadata.name + "#" + relation.inverseRelation.propertyName + " both has cascade remove set. " +
                    "This may lead to unexpected circular removals. Please set cascade remove only from one side of relationship.");
        }); // todo: maybe better just deny removal from one to one relation without join column?
        entityMetadata.eagerRelations.forEach(function (relation) {
        });
    };
    /**
     * Validates dependencies of the entity metadatas.
     */
    EntityMetadataValidator.prototype.validateDependencies = function (entityMetadatas) {
        var graph = new DepGraph();
        entityMetadatas.forEach(function (entityMetadata) {
            graph.addNode(entityMetadata.name);
        });
        entityMetadatas.forEach(function (entityMetadata) {
            entityMetadata.relationsWithJoinColumns
                .filter(function (relation) { return !relation.isNullable; })
                .forEach(function (relation) {
                graph.addDependency(entityMetadata.name, relation.inverseEntityMetadata.name);
            });
        });
        try {
            graph.overallOrder();
        }
        catch (err) {
            throw new CircularRelationsError(err.toString().replace("Error: Dependency Cycle Found: ", ""));
        }
    };
    /**
     * Validates eager relations to prevent circular dependency in them.
     */
    EntityMetadataValidator.prototype.validateEagerRelations = function (entityMetadatas) {
        entityMetadatas.forEach(function (entityMetadata) {
            entityMetadata.eagerRelations.forEach(function (relation) {
                if (relation.inverseRelation && relation.inverseRelation.isEager)
                    throw new Error("Circular eager relations are disallowed. " +
                        (entityMetadata.targetName + "#" + relation.propertyPath + " contains \"eager: true\", and its inverse side ") +
                        (relation.inverseEntityMetadata.targetName + "#" + relation.inverseRelation.propertyPath + " contains \"eager: true\" as well.") +
                        " Remove \"eager: true\" from one side of the relation.");
            });
        });
    };
    return EntityMetadataValidator;
}());
export { EntityMetadataValidator };
 
//# sourceMappingURL=EntityMetadataValidator.js.map