mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
refactoring entity metadata stuff
This commit is contained in:
parent
f0e47cb17f
commit
311773b942
@ -16,7 +16,7 @@ import {InheritanceMetadataArgs} from "./InheritanceMetadataArgs";
|
||||
import {DiscriminatorValueMetadataArgs} from "./DiscriminatorValueMetadataArgs";
|
||||
import {EntityRepositoryMetadataArgs} from "./EntityRepositoryMetadataArgs";
|
||||
import {TransactionEntityMetadataArgs} from "./TransactionEntityMetadataArgs";
|
||||
import {MetadataArgsUtils} from "./MetadataArgsUtils";
|
||||
import {MetadataUtils} from "./MetadataUtils";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
|
||||
/**
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Metadata args utility functions.
|
||||
*/
|
||||
export class MetadataArgsUtils {
|
||||
export class MetadataUtils {
|
||||
|
||||
/**
|
||||
* Gets given's entity all inherited classes.
|
||||
@ -1,7 +1,6 @@
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
|
||||
import {IndexMetadata} from "../metadata/IndexMetadata";
|
||||
import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
|
||||
@ -11,8 +10,11 @@ import {Driver} from "../driver/Driver";
|
||||
import {EmbeddedMetadataArgs} from "../metadata-args/EmbeddedMetadataArgs";
|
||||
import {RelationIdMetadata} from "../metadata/RelationIdMetadata";
|
||||
import {RelationCountMetadata} from "../metadata/RelationCountMetadata";
|
||||
import {ColumnTypes} from "../metadata/types/ColumnTypes";
|
||||
import {MetadataArgsUtils} from "../metadata-args/MetadataArgsUtils";
|
||||
import {MetadataUtils} from "../metadata-args/MetadataUtils";
|
||||
import {TableMetadataArgs} from "../metadata-args/TableMetadataArgs";
|
||||
import {JunctionEntityMetadataBuilder} from "./JunctionEntityMetadataBuilder";
|
||||
import {ClosureJunctionEntityMetadataBuilder} from "./ClosureJunctionEntityMetadataBuilder";
|
||||
import {RelationJoinColumnBuilder} from "./RelationJoinColumnBuilder";
|
||||
|
||||
/**
|
||||
* Aggregates all metadata: table, column, relation into one collection grouped by tables for a given set of classes.
|
||||
@ -26,10 +28,25 @@ export class AllEntityMetadataBuilder {
|
||||
// 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
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
protected junctionEntityMetadataBuilder: JunctionEntityMetadataBuilder;
|
||||
protected closureJunctionEntityMetadataBuilder: ClosureJunctionEntityMetadataBuilder;
|
||||
protected relationJoinColumnBuilder: RelationJoinColumnBuilder;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(private driver: Driver,
|
||||
private lazyRelationsWrapper: LazyRelationsWrapper,
|
||||
private metadataArgsStorage: MetadataArgsStorage,
|
||||
private namingStrategy: NamingStrategyInterface) {
|
||||
this.junctionEntityMetadataBuilder = new JunctionEntityMetadataBuilder(driver, lazyRelationsWrapper, namingStrategy);
|
||||
this.closureJunctionEntityMetadataBuilder = new ClosureJunctionEntityMetadataBuilder(driver, lazyRelationsWrapper, namingStrategy);
|
||||
this.relationJoinColumnBuilder = new RelationJoinColumnBuilder(namingStrategy);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -37,101 +54,77 @@ export class AllEntityMetadataBuilder {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Builds a complete metadata aggregations for the given entity classes.
|
||||
* Builds a complete entity metadatas for the given entity classes.
|
||||
*/
|
||||
build(entityClasses?: Function[]): EntityMetadata[] {
|
||||
|
||||
// if entity classes to filter entities by are given then do filtering, otherwise use all
|
||||
const allTables = entityClasses ? this.metadataArgsStorage.filterTables(entityClasses) : this.metadataArgsStorage.tables.toArray();
|
||||
|
||||
// filter out table metadata args for those we really create entity metadatas and tables in the db
|
||||
const realTables = allTables.filter(table => table.type === "regular" || table.type === "closure" || table.type === "class-table-child");
|
||||
|
||||
const entityMetadatas: EntityMetadata[] = realTables.map(tableArgs => {
|
||||
const inheritanceTree = tableArgs.target instanceof Function ? MetadataArgsUtils.getInheritanceTree(tableArgs.target) : [tableArgs.target]; // todo: implement later here inheritance for string-targets
|
||||
|
||||
// collect all table embeddeds
|
||||
const entityMetadata = new EntityMetadata({
|
||||
namingStrategy: this.namingStrategy,
|
||||
lazyRelationsWrapper: this.lazyRelationsWrapper,
|
||||
tablesPrefix: this.driver.options.tablesPrefix,
|
||||
args: tableArgs
|
||||
});
|
||||
entityMetadata.embeddeds = this.createEmbeddedsRecursively(entityMetadata, this.metadataArgsStorage.filterEmbeddeds(inheritanceTree));
|
||||
|
||||
entityMetadata.ownColumns = this.metadataArgsStorage.filterColumns(inheritanceTree).map(args => {
|
||||
return new ColumnMetadata({ entityMetadata, args });
|
||||
});
|
||||
entityMetadata.ownRelations = this.metadataArgsStorage.filterRelations(inheritanceTree).map(args => {
|
||||
return new RelationMetadata({ entityMetadata, args });
|
||||
});
|
||||
entityMetadata.relationIds = this.metadataArgsStorage.filterRelationIds(inheritanceTree).map(args => {
|
||||
return new RelationIdMetadata({ entityMetadata, args });
|
||||
});
|
||||
entityMetadata.relationCounts = this.metadataArgsStorage.filterRelationCounts(inheritanceTree).map(args => {
|
||||
return new RelationCountMetadata({ entityMetadata, args });
|
||||
});
|
||||
entityMetadata.indices = this.metadataArgsStorage.filterIndices(inheritanceTree).map(args => {
|
||||
return new IndexMetadata({ entityMetadata, args });
|
||||
});
|
||||
return entityMetadata;
|
||||
});
|
||||
// create entity metadatas for a user defined entities (marked with @Entity decorator or loaded from entity schemas)
|
||||
const entityMetadatas = realTables.map(tableArgs => this.createEntityMetadata(tableArgs));
|
||||
|
||||
// calculate entity metadata computed properties and all its sub-metadatas
|
||||
entityMetadatas.forEach(entityMetadata => this.buildEntityMetadata(entityMetadata));
|
||||
entityMetadatas.forEach(entityMetadata => this.computeEntityMetadata(entityMetadata));
|
||||
|
||||
// calculate entity metadata computed properties and all its sub-metadatas
|
||||
entityMetadatas.forEach(entityMetadata => this.buildInverseProperties(entityMetadata, entityMetadatas));
|
||||
// calculate entity metadata's inverse properties
|
||||
entityMetadatas.forEach(entityMetadata => this.computeInverseProperties(entityMetadata, entityMetadatas));
|
||||
|
||||
// go through all entity metadatas and create foreign keys / junction entity metadatas for their relations
|
||||
entityMetadatas.forEach(entityMetadata => {
|
||||
|
||||
// create entity's relations join columns
|
||||
// create entity's relations join columns (for many-to-one and one-to-one owner)
|
||||
entityMetadata.relations.filter(relation => relation.isOneToOne || relation.isManyToOne).forEach(relation => {
|
||||
const foreignKey = this.createJoinColumnRelationForeignKey(relation);
|
||||
if (!foreignKey) return; // this case is possible only for one-to-one non owning side
|
||||
foreignKey.build(this.namingStrategy);
|
||||
relation.foreignKeys.push(foreignKey);
|
||||
entityMetadata.foreignKeys.push(foreignKey);
|
||||
relation.buildOnForeignKeysChange();
|
||||
const joinColumns = this.metadataArgsStorage.filterJoinColumns(relation.target, relation.propertyName);
|
||||
const foreignKey = this.relationJoinColumnBuilder.build(joinColumns, relation); // create a foreign key based on its metadata args
|
||||
if (foreignKey) {
|
||||
relation.registerForeignKeys(foreignKey); // push it to the relation and thus register there a join column
|
||||
entityMetadata.foreignKeys.push(foreignKey);
|
||||
}
|
||||
});
|
||||
|
||||
// create junction entity metadatas for entity many-to-many relations
|
||||
entityMetadata.relations.filter(relation => relation.isManyToMany).forEach(relation => {
|
||||
const joinTableMetadataArgs = this.metadataArgsStorage.findJoinTable(relation.target, relation.propertyName);
|
||||
if (!joinTableMetadataArgs) return; // no join table set - no need to do anything
|
||||
const joinTable = this.metadataArgsStorage.findJoinTable(relation.target, relation.propertyName);
|
||||
if (!joinTable) return; // no join table set - no need to do anything (it means this is many-to-many inverse side)
|
||||
|
||||
const referencedColumns = this.collectRelationReferencedColumns(relation);
|
||||
const inverseReferencedColumns = this.collectRelationInverseReferencedColumns(relation);
|
||||
const junctionEntityMetadata = this.createJunctionEntityMetadata(relation, referencedColumns, inverseReferencedColumns);
|
||||
|
||||
relation.foreignKeys = junctionEntityMetadata.foreignKeys;
|
||||
relation.buildOnForeignKeysChange();
|
||||
// here we create a junction entity metadata for a new junction table of many-to-many relation
|
||||
const junctionEntityMetadata = this.junctionEntityMetadataBuilder.build(relation, joinTable);
|
||||
relation.registerForeignKeys(...junctionEntityMetadata.foreignKeys);
|
||||
relation.junctionEntityMetadata = junctionEntityMetadata;
|
||||
if (relation.hasInverseSide)
|
||||
if (relation.inverseRelation)
|
||||
relation.inverseRelation.junctionEntityMetadata = junctionEntityMetadata;
|
||||
|
||||
// compute new entity metadata properties and push it to entity metadatas pool
|
||||
this.computeEntityMetadata(junctionEntityMetadata);
|
||||
this.computeInverseProperties(junctionEntityMetadata, entityMetadatas);
|
||||
entityMetadatas.push(junctionEntityMetadata);
|
||||
this.buildEntityMetadata(junctionEntityMetadata);
|
||||
this.buildInverseProperties(junctionEntityMetadata, entityMetadatas);
|
||||
});
|
||||
|
||||
// update entity metadata depend properties
|
||||
entityMetadata.relationsWithJoinColumns = entityMetadata.relations.filter(relation => relation.isWithJoinColumn);
|
||||
entityMetadata.hasNonNullableRelations = entityMetadata.relationsWithJoinColumns.some(relation => !relation.isNullable || relation.isPrimary);
|
||||
});
|
||||
|
||||
// generate closure junction tables for all closure tables
|
||||
entityMetadatas
|
||||
.filter(metadata => metadata.isClosure)
|
||||
.forEach(entityMetadata => {
|
||||
const closureJunctionEntityMetadata = this.closureJunctionEntityMetadataBuilder.build(entityMetadata);
|
||||
entityMetadata.closureJunctionTable = closureJunctionEntityMetadata;
|
||||
this.computeEntityMetadata(closureJunctionEntityMetadata);
|
||||
this.computeInverseProperties(closureJunctionEntityMetadata, entityMetadatas);
|
||||
entityMetadatas.push(closureJunctionEntityMetadata);
|
||||
});
|
||||
|
||||
// generate keys for tables with single-table inheritance
|
||||
entityMetadatas
|
||||
.filter(metadata => metadata.inheritanceType === "single-table")
|
||||
.forEach(entityMetadata => this.createKeysForTableInheritance(entityMetadata));
|
||||
|
||||
// generate junction tables for all closure tables
|
||||
entityMetadatas
|
||||
.filter(metadata => metadata.isClosure)
|
||||
.forEach(entityMetadata => {
|
||||
const closureJunctionEntityMetadata = this.createClosureJunctionEntityMetadata(entityMetadata);
|
||||
entityMetadata.closureJunctionTable = closureJunctionEntityMetadata;
|
||||
this.buildEntityMetadata(closureJunctionEntityMetadata);
|
||||
this.buildInverseProperties(closureJunctionEntityMetadata, entityMetadatas);
|
||||
entityMetadatas.push(closureJunctionEntityMetadata);
|
||||
});
|
||||
|
||||
// add lazy initializer for entity relations
|
||||
entityMetadatas
|
||||
.filter(metadata => metadata.target instanceof Function)
|
||||
@ -143,11 +136,6 @@ export class AllEntityMetadataBuilder {
|
||||
});
|
||||
});
|
||||
|
||||
entityMetadatas.forEach(metadata => {
|
||||
metadata.relations.forEach(relation => relation.buildOnForeignKeysChange());
|
||||
metadata.buildOnRelationsChange();
|
||||
});
|
||||
|
||||
return entityMetadatas;
|
||||
}
|
||||
|
||||
@ -155,188 +143,49 @@ export class AllEntityMetadataBuilder {
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
protected createJunctionEntityMetadata(relation: RelationMetadata, referencedColumns: ColumnMetadata[], inverseReferencedColumns: ColumnMetadata[]) {
|
||||
const joinTableMetadataArgs = this.metadataArgsStorage.findJoinTable(relation.target, relation.propertyName)!;
|
||||
/**
|
||||
* Creates entity metadata from the given table args.
|
||||
* Creates column, relation, etc. metadatas for everything this entity metadata owns.
|
||||
*/
|
||||
protected createEntityMetadata(tableArgs: TableMetadataArgs): EntityMetadata {
|
||||
|
||||
const joinTableName = joinTableMetadataArgs.name || this.namingStrategy.joinTableName(
|
||||
relation.entityMetadata.tableNameWithoutPrefix,
|
||||
relation.inverseEntityMetadata.tableNameWithoutPrefix,
|
||||
relation.propertyPath,
|
||||
relation.hasInverseSide ? relation.inverseRelation.propertyName : ""
|
||||
);
|
||||
// we take all "inheritance tree" from a target entity to collect all stored metadata args
|
||||
// (by decorators or inside entity schemas). For example for target Post < ContentModel < Unit
|
||||
// it will be an array of [Post, ContentModel, Unit] and we can then get all metadata args of those classes
|
||||
const inheritanceTree = tableArgs.target instanceof Function
|
||||
? MetadataUtils.getInheritanceTree(tableArgs.target)
|
||||
: [tableArgs.target]; // todo: implement later here inheritance for string-targets
|
||||
|
||||
const junctionEntityMetadata = new EntityMetadata({
|
||||
lazyRelationsWrapper: this.lazyRelationsWrapper,
|
||||
namingStrategy: this.namingStrategy,
|
||||
tablesPrefix: this.driver.options.tablesPrefix,
|
||||
args: {
|
||||
target: "",
|
||||
name: joinTableName,
|
||||
type: "junction"
|
||||
}
|
||||
});
|
||||
const junctionColumns = referencedColumns.map(referencedColumn => {
|
||||
const joinColumn = joinTableMetadataArgs.joinColumns ? joinTableMetadataArgs.joinColumns.find(joinColumnArgs => {
|
||||
return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === referencedColumn.propertyName) &&
|
||||
!!joinColumnArgs.name;
|
||||
}) : undefined;
|
||||
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.namingStrategy.joinTableColumnName(relation.entityMetadata.tableNameWithoutPrefix, referencedColumn.propertyName, referencedColumn.givenDatabaseName);
|
||||
|
||||
return new ColumnMetadata({
|
||||
entityMetadata: junctionEntityMetadata,
|
||||
referencedColumn: referencedColumn,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: columnName,
|
||||
options: {
|
||||
name: columnName,
|
||||
length: referencedColumn.length,
|
||||
type: referencedColumn.type,
|
||||
nullable: false,
|
||||
primary: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const inverseJunctionColumns = inverseReferencedColumns.map(inverseReferencedColumn => {
|
||||
const joinColumn = joinTableMetadataArgs.inverseJoinColumns ? joinTableMetadataArgs.inverseJoinColumns.find(joinColumnArgs => {
|
||||
return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === inverseReferencedColumn.propertyName) &&
|
||||
!!joinColumnArgs.name;
|
||||
}) : undefined;
|
||||
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.namingStrategy.joinTableColumnName(relation.inverseEntityMetadata.tableNameWithoutPrefix, inverseReferencedColumn.propertyName, inverseReferencedColumn.givenDatabaseName);
|
||||
|
||||
return new ColumnMetadata({
|
||||
entityMetadata: junctionEntityMetadata,
|
||||
referencedColumn: inverseReferencedColumn,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: columnName,
|
||||
options: {
|
||||
length: inverseReferencedColumn.length,
|
||||
type: inverseReferencedColumn.type,
|
||||
name: columnName,
|
||||
nullable: false,
|
||||
primary: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
junctionEntityMetadata.foreignKeys = [
|
||||
new ForeignKeyMetadata({
|
||||
entityMetadata: junctionEntityMetadata,
|
||||
referencedEntityMetadata: relation.entityMetadata,
|
||||
columns: junctionColumns,
|
||||
referencedColumns: referencedColumns
|
||||
}),
|
||||
new ForeignKeyMetadata({
|
||||
entityMetadata: junctionEntityMetadata,
|
||||
referencedEntityMetadata: relation.inverseEntityMetadata,
|
||||
columns: inverseJunctionColumns,
|
||||
referencedColumns: inverseReferencedColumns
|
||||
}),
|
||||
];
|
||||
|
||||
junctionColumns.concat(inverseJunctionColumns).forEach(column => column.relationMetadata = relation);
|
||||
junctionEntityMetadata.ownColumns = junctionColumns.concat(inverseJunctionColumns);
|
||||
junctionEntityMetadata.indices = [
|
||||
new IndexMetadata({
|
||||
entityMetadata: junctionEntityMetadata,
|
||||
columns: junctionColumns,
|
||||
args: {
|
||||
target: "",
|
||||
unique: false
|
||||
}
|
||||
}),
|
||||
|
||||
new IndexMetadata({
|
||||
entityMetadata: junctionEntityMetadata,
|
||||
columns: inverseJunctionColumns,
|
||||
args: {
|
||||
target: "",
|
||||
unique: false
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
return junctionEntityMetadata;
|
||||
}
|
||||
|
||||
protected createClosureJunctionEntityMetadata(parentClosureEntityMetadata: EntityMetadata): EntityMetadata {
|
||||
const entityMetadata = new EntityMetadata({
|
||||
parentClosureEntityMetadata: parentClosureEntityMetadata,
|
||||
lazyRelationsWrapper: this.lazyRelationsWrapper,
|
||||
namingStrategy: this.namingStrategy,
|
||||
lazyRelationsWrapper: this.lazyRelationsWrapper,
|
||||
tablesPrefix: this.driver.options.tablesPrefix,
|
||||
args: {
|
||||
target: "",
|
||||
name: parentClosureEntityMetadata.tableNameWithoutPrefix,
|
||||
type: "closure-junction"
|
||||
}
|
||||
args: tableArgs
|
||||
});
|
||||
entityMetadata.embeddeds = this.createEmbeddedsRecursively(entityMetadata, this.metadataArgsStorage.filterEmbeddeds(inheritanceTree));
|
||||
|
||||
parentClosureEntityMetadata.primaryColumns.forEach(primaryColumn => {
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: "ancestor_" + primaryColumn.databaseName, // todo: naming strategy
|
||||
options: {
|
||||
length: primaryColumn.length,
|
||||
type: primaryColumn.type,
|
||||
}
|
||||
}
|
||||
}));
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: "descendant_" + primaryColumn.databaseName,
|
||||
options: {
|
||||
length: primaryColumn.length,
|
||||
type: primaryColumn.type,
|
||||
}
|
||||
}
|
||||
}));
|
||||
entityMetadata.ownColumns = this.metadataArgsStorage.filterColumns(inheritanceTree).map(args => {
|
||||
return new ColumnMetadata({ entityMetadata, args });
|
||||
});
|
||||
entityMetadata.ownRelations = this.metadataArgsStorage.filterRelations(inheritanceTree).map(args => {
|
||||
return new RelationMetadata({ entityMetadata, args });
|
||||
});
|
||||
entityMetadata.relationIds = this.metadataArgsStorage.filterRelationIds(inheritanceTree).map(args => {
|
||||
return new RelationIdMetadata({ entityMetadata, args });
|
||||
});
|
||||
entityMetadata.relationCounts = this.metadataArgsStorage.filterRelationCounts(inheritanceTree).map(args => {
|
||||
return new RelationCountMetadata({ entityMetadata, args });
|
||||
});
|
||||
entityMetadata.indices = this.metadataArgsStorage.filterIndices(inheritanceTree).map(args => {
|
||||
return new IndexMetadata({ entityMetadata, args });
|
||||
});
|
||||
|
||||
if (parentClosureEntityMetadata.treeLevelColumn) {
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: "level",
|
||||
options: {
|
||||
type: ColumnTypes.INTEGER,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
entityMetadata.foreignKeys = [
|
||||
new ForeignKeyMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
referencedEntityMetadata: parentClosureEntityMetadata,
|
||||
columns: [entityMetadata.ownColumns[0]],
|
||||
referencedColumns: parentClosureEntityMetadata.primaryColumns
|
||||
}),
|
||||
new ForeignKeyMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
referencedEntityMetadata: parentClosureEntityMetadata,
|
||||
columns: [entityMetadata.ownColumns[1]],
|
||||
referencedColumns: parentClosureEntityMetadata.primaryColumns
|
||||
}),
|
||||
];
|
||||
|
||||
return entityMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates from the given embedded metadata args real embedded metadatas with its columns and relations,
|
||||
* and does the same for all its sub-embeddeds (goes recursively).
|
||||
*/
|
||||
protected createEmbeddedsRecursively(entityMetadata: EntityMetadata, embeddedArgs: EmbeddedMetadataArgs[]): EmbeddedMetadata[] {
|
||||
return embeddedArgs.map(embeddedArgs => {
|
||||
const embeddedMetadata = new EmbeddedMetadata({ entityMetadata: entityMetadata, args: embeddedArgs });
|
||||
@ -352,24 +201,10 @@ export class AllEntityMetadataBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
protected createJoinColumnRelationForeignKey(relation: RelationMetadata): ForeignKeyMetadata|undefined {
|
||||
const referencedColumns = this.collectRelationReferencedColumns(relation);
|
||||
if (!referencedColumns.length)
|
||||
return undefined; // this case is possible only for one-to-one non owning side
|
||||
|
||||
const columns = this.collectRelationColumns(relation, referencedColumns);
|
||||
return new ForeignKeyMetadata({
|
||||
entityMetadata: relation.entityMetadata,
|
||||
referencedEntityMetadata: relation.inverseEntityMetadata,
|
||||
columns: columns,
|
||||
referencedColumns: referencedColumns,
|
||||
onDelete: relation.onDelete,
|
||||
});
|
||||
}
|
||||
|
||||
protected buildEntityMetadata(entityMetadata: EntityMetadata) {
|
||||
|
||||
// first build all embeddeds, because all columns and relations including owning by embedded depend on embeds
|
||||
/**
|
||||
* Computes all entity metadata's computed properties, and all its sub-metadatas (relations, columns, embeds, etc).
|
||||
*/
|
||||
protected computeEntityMetadata(entityMetadata: EntityMetadata) {
|
||||
entityMetadata.embeddeds.forEach(embedded => embedded.build(this.namingStrategy));
|
||||
entityMetadata.embeddeds.forEach(embedded => {
|
||||
embedded.columnsFromTree.forEach(column => column.build(this.namingStrategy));
|
||||
@ -377,9 +212,6 @@ export class AllEntityMetadataBuilder {
|
||||
});
|
||||
entityMetadata.ownColumns.forEach(column => column.build(this.namingStrategy));
|
||||
entityMetadata.ownRelations.forEach(relation => relation.build(this.namingStrategy));
|
||||
|
||||
entityMetadata.buildOnRelationsChange();
|
||||
|
||||
entityMetadata.relations = entityMetadata.embeddeds.reduce((relations, embedded) => relations.concat(embedded.relationsFromTree), entityMetadata.ownRelations);
|
||||
entityMetadata.oneToOneRelations = entityMetadata.relations.filter(relation => relation.isOneToOne);
|
||||
entityMetadata.oneToManyRelations = entityMetadata.relations.filter(relation => relation.isOneToMany);
|
||||
@ -389,8 +221,9 @@ export class AllEntityMetadataBuilder {
|
||||
entityMetadata.ownerManyToManyRelations = entityMetadata.relations.filter(relation => relation.isManyToManyOwner);
|
||||
entityMetadata.treeParentRelation = entityMetadata.relations.find(relation => relation.isTreeParent)!; // todo: fix ! later
|
||||
entityMetadata.treeChildrenRelation = entityMetadata.relations.find(relation => relation.isTreeChildren)!; // todo: fix ! later
|
||||
|
||||
entityMetadata.buildOnColumnsChange();
|
||||
entityMetadata.columns = entityMetadata.embeddeds.reduce((columns, embedded) => columns.concat(embedded.columnsFromTree), entityMetadata.ownColumns);
|
||||
entityMetadata.primaryColumns = entityMetadata.columns.filter(column => column.isPrimary);
|
||||
entityMetadata.hasMultiplePrimaryKeys = entityMetadata.primaryColumns.length > 1;
|
||||
entityMetadata.generatedColumn = entityMetadata.columns.find(column => column.isGenerated)!; // todo: fix ! later
|
||||
entityMetadata.createDateColumn = entityMetadata.columns.find(column => column.mode === "createDate")!; // todo: fix ! later
|
||||
entityMetadata.updateDateColumn = entityMetadata.columns.find(column => column.mode === "updateDate")!; // todo: fix ! later
|
||||
@ -399,12 +232,15 @@ export class AllEntityMetadataBuilder {
|
||||
entityMetadata.treeLevelColumn = entityMetadata.columns.find(column => column.mode === "treeLevel")!; // todo: fix ! later
|
||||
entityMetadata.parentIdColumns = entityMetadata.columns.filter(column => column.mode === "parentId")!; // todo: fix ! later
|
||||
entityMetadata.objectIdColumn = entityMetadata.columns.find(column => column.mode === "objectId")!; // todo: fix ! later
|
||||
|
||||
entityMetadata.foreignKeys.forEach(foreignKey => foreignKey.build(this.namingStrategy));
|
||||
entityMetadata.indices.forEach(index => index.build(this.namingStrategy));
|
||||
entityMetadata.propertiesMap = entityMetadata.createPropertiesMap();
|
||||
}
|
||||
|
||||
protected buildInverseProperties(entityMetadata: EntityMetadata, entityMetadatas: EntityMetadata[]) {
|
||||
/**
|
||||
* Computes entity metadata's relations inverse side properties.
|
||||
*/
|
||||
protected computeInverseProperties(entityMetadata: EntityMetadata, entityMetadatas: EntityMetadata[]) {
|
||||
entityMetadata.relations.forEach(relation => {
|
||||
|
||||
// compute inverse side (related) entity metadatas for all relation metadatas
|
||||
@ -443,119 +279,6 @@ export class AllEntityMetadataBuilder {
|
||||
entityMetadata.indices.push(indexForKeyWithPrimary);
|
||||
}
|
||||
|
||||
// cases it should cover:
|
||||
// 1. when join column is set with custom name and without referenced column name
|
||||
// we need automatically set referenced column name - primary ids by default
|
||||
// @JoinColumn({ name: "custom_name" })
|
||||
// 2. when join column is set with only referenced column name
|
||||
// we need automatically set join column name - relation name + referenced column name
|
||||
// @JoinColumn({ referencedColumnName: "title" })
|
||||
// 3. when join column is set without both referenced column name and join column name
|
||||
// we need to automatically set both of them
|
||||
// @JoinColumn()
|
||||
// 4. when join column is not set at all (as in case of @ManyToOne relation)
|
||||
// we need to create join column for it with proper referenced column name and join column name
|
||||
// 5. when multiple join columns set none of referencedColumnName and name can be optional
|
||||
// both options are required
|
||||
// @JoinColumn([
|
||||
// { name: "category_title", referencedColumnName: "type" },
|
||||
// { name: "category_title", referencedColumnName: "name" },
|
||||
// ])
|
||||
|
||||
// since for many-to-one relations having JoinColumn decorator is not required,
|
||||
// we need to go thought each many-to-one relation without join column decorator set
|
||||
// and create join column metadata args for them
|
||||
protected collectRelationInverseReferencedColumns(relation: RelationMetadata) {
|
||||
const joinTableMetadataArgs = this.metadataArgsStorage.findJoinTable(relation.target, relation.propertyName)!;
|
||||
const hasInverseJoinColumns = !!joinTableMetadataArgs.inverseJoinColumns;
|
||||
const hasAnyInverseReferencedColumnName = hasInverseJoinColumns ? joinTableMetadataArgs.inverseJoinColumns!.find(joinColumn => !!joinColumn.referencedColumnName) : false;
|
||||
if (!hasInverseJoinColumns || (hasInverseJoinColumns && !hasAnyInverseReferencedColumnName)) {
|
||||
return relation.inverseEntityMetadata.primaryColumns;
|
||||
} else {
|
||||
return joinTableMetadataArgs.inverseJoinColumns!.map(joinColumn => {
|
||||
const referencedColumn = relation.inverseEntityMetadata.ownColumns.find(column => column.propertyName === joinColumn.referencedColumnName);
|
||||
if (!referencedColumn)
|
||||
throw new Error(`Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.inverseEntityMetadata.name}`);
|
||||
|
||||
return referencedColumn;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected collectRelationReferencedColumns(relation: RelationMetadata) {
|
||||
if (relation.isManyToMany) {
|
||||
const joinTableMetadataArgs = this.metadataArgsStorage.findJoinTable(relation.target, relation.propertyName)!;
|
||||
const hasAnyReferencedColumnName = joinTableMetadataArgs.joinColumns ? joinTableMetadataArgs.joinColumns.find(joinColumn => !!joinColumn.referencedColumnName) : false;
|
||||
if (!joinTableMetadataArgs.joinColumns || (joinTableMetadataArgs.joinColumns && !hasAnyReferencedColumnName)) {
|
||||
return relation.entityMetadata.ownColumns.filter(column => column.isPrimary);
|
||||
} else {
|
||||
return joinTableMetadataArgs.joinColumns.map(joinColumn => {
|
||||
const referencedColumn = relation.entityMetadata.ownColumns.find(column => column.propertyName === joinColumn.referencedColumnName);
|
||||
if (!referencedColumn)
|
||||
throw new Error(`Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.entityMetadata.name}`);
|
||||
|
||||
return referencedColumn;
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
const joinColumnArgsArray = this.metadataArgsStorage.filterJoinColumns(relation.target, relation.propertyName);
|
||||
const hasAnyReferencedColumnName = joinColumnArgsArray.find(joinColumnArgs => !!joinColumnArgs.referencedColumnName);
|
||||
const manyToOneWithoutJoinColumn = joinColumnArgsArray.length === 0 && relation.isManyToOne;
|
||||
const hasJoinColumnWithoutAnyReferencedColumnName = joinColumnArgsArray.length > 0 && !hasAnyReferencedColumnName;
|
||||
|
||||
if (manyToOneWithoutJoinColumn || hasJoinColumnWithoutAnyReferencedColumnName) { // covers case3 and case1
|
||||
return relation.inverseEntityMetadata.primaryColumns;
|
||||
|
||||
} else { // cases with referenced columns defined
|
||||
return joinColumnArgsArray.map(joinColumnArgs => {
|
||||
const referencedColumn = relation.inverseEntityMetadata.ownColumns.find(column => column.propertyName === joinColumnArgs.referencedColumnName); // todo: can we also search in relations?
|
||||
if (!referencedColumn)
|
||||
throw new Error(`Referenced column ${joinColumnArgs.referencedColumnName} was not found in entity ${relation.inverseEntityMetadata.name}`);
|
||||
|
||||
return referencedColumn;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private collectRelationColumns(relation: RelationMetadata, referencedColumns: ColumnMetadata[]) {
|
||||
const joinColumnArgsArray = this.metadataArgsStorage.filterJoinColumns(relation.target, relation.propertyName);
|
||||
return referencedColumns.map(referencedColumn => {
|
||||
|
||||
// in the case if relation has join column with only name set we need this check
|
||||
const joinColumnMetadataArg = joinColumnArgsArray.find(joinColumnArgs => {
|
||||
return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === referencedColumn.propertyName) &&
|
||||
!!joinColumnArgs.name;
|
||||
});
|
||||
const joinColumnName = joinColumnMetadataArg ? joinColumnMetadataArg.name : this.namingStrategy.joinColumnName(relation.propertyName, referencedColumn.propertyName);
|
||||
|
||||
let relationalColumn = relation.entityMetadata.ownColumns.find(column => column.databaseName === joinColumnName);
|
||||
if (!relationalColumn) {
|
||||
relationalColumn = new ColumnMetadata({
|
||||
entityMetadata: relation.entityMetadata,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: joinColumnName!,
|
||||
options: {
|
||||
name: joinColumnName,
|
||||
type: referencedColumn.type,
|
||||
primary: relation.isPrimary,
|
||||
nullable: relation.isNullable,
|
||||
}
|
||||
}
|
||||
});
|
||||
relationalColumn.build(this.namingStrategy);
|
||||
relation.entityMetadata.ownColumns.push(relationalColumn);
|
||||
relation.entityMetadata.buildOnColumnsChange();
|
||||
}
|
||||
relationalColumn.referencedColumn = referencedColumn; // its important to set it here because we need to set referenced column for user defined join column
|
||||
relationalColumn.type = referencedColumn.type; // also since types of relational column and join column must be equal we override user defined column type
|
||||
relationalColumn.relationMetadata = relation;
|
||||
return relationalColumn;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// generate virtual column with foreign key for class-table inheritance
|
||||
|
||||
96
src/metadata-builder/ClosureJunctionEntityMetadataBuilder.ts
Normal file
96
src/metadata-builder/ClosureJunctionEntityMetadataBuilder.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
|
||||
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
|
||||
import {Driver} from "../driver/Driver";
|
||||
import {ColumnTypes} from "../metadata/types/ColumnTypes";
|
||||
|
||||
export class ClosureJunctionEntityMetadataBuilder {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(private driver: Driver,
|
||||
private lazyRelationsWrapper: LazyRelationsWrapper,
|
||||
private namingStrategy: NamingStrategyInterface) {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
build(parentClosureEntityMetadata: EntityMetadata) {
|
||||
const entityMetadata = new EntityMetadata({
|
||||
parentClosureEntityMetadata: parentClosureEntityMetadata,
|
||||
lazyRelationsWrapper: this.lazyRelationsWrapper,
|
||||
namingStrategy: this.namingStrategy,
|
||||
tablesPrefix: this.driver.options.tablesPrefix,
|
||||
args: {
|
||||
target: "",
|
||||
name: parentClosureEntityMetadata.tableNameWithoutPrefix,
|
||||
type: "closure-junction"
|
||||
}
|
||||
});
|
||||
|
||||
parentClosureEntityMetadata.primaryColumns.forEach(primaryColumn => {
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: "ancestor_" + primaryColumn.databaseName, // todo: naming strategy
|
||||
options: {
|
||||
length: primaryColumn.length,
|
||||
type: primaryColumn.type,
|
||||
}
|
||||
}
|
||||
}));
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: "descendant_" + primaryColumn.databaseName,
|
||||
options: {
|
||||
length: primaryColumn.length,
|
||||
type: primaryColumn.type,
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
if (parentClosureEntityMetadata.treeLevelColumn) {
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: "level",
|
||||
options: {
|
||||
type: ColumnTypes.INTEGER,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
entityMetadata.foreignKeys = [
|
||||
new ForeignKeyMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
referencedEntityMetadata: parentClosureEntityMetadata,
|
||||
columns: [entityMetadata.ownColumns[0]],
|
||||
referencedColumns: parentClosureEntityMetadata.primaryColumns
|
||||
}),
|
||||
new ForeignKeyMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
referencedEntityMetadata: parentClosureEntityMetadata,
|
||||
columns: [entityMetadata.ownColumns[1]],
|
||||
referencedColumns: parentClosureEntityMetadata.primaryColumns
|
||||
}),
|
||||
];
|
||||
|
||||
return entityMetadata;
|
||||
}
|
||||
|
||||
}
|
||||
172
src/metadata-builder/JunctionEntityMetadataBuilder.ts
Normal file
172
src/metadata-builder/JunctionEntityMetadataBuilder.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
|
||||
import {IndexMetadata} from "../metadata/IndexMetadata";
|
||||
import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
|
||||
import {Driver} from "../driver/Driver";
|
||||
import {JoinTableMetadataArgs} from "../metadata-args/JoinTableMetadataArgs";
|
||||
|
||||
export class JunctionEntityMetadataBuilder {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(private driver: Driver,
|
||||
private lazyRelationsWrapper: LazyRelationsWrapper,
|
||||
private namingStrategy: NamingStrategyInterface) {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
build(relation: RelationMetadata, joinTable: JoinTableMetadataArgs) {
|
||||
const referencedColumns = this.collectRelationReferencedColumns(relation, joinTable);
|
||||
const inverseReferencedColumns = this.collectRelationInverseReferencedColumns(relation, joinTable);
|
||||
|
||||
const joinTableName = joinTable.name || this.namingStrategy.joinTableName(
|
||||
relation.entityMetadata.tableNameWithoutPrefix,
|
||||
relation.inverseEntityMetadata.tableNameWithoutPrefix,
|
||||
relation.propertyPath,
|
||||
relation.hasInverseSide ? relation.inverseRelation.propertyName : ""
|
||||
);
|
||||
|
||||
const entityMetadata = new EntityMetadata({
|
||||
lazyRelationsWrapper: this.lazyRelationsWrapper,
|
||||
namingStrategy: this.namingStrategy,
|
||||
tablesPrefix: this.driver.options.tablesPrefix,
|
||||
args: {
|
||||
target: "",
|
||||
name: joinTableName,
|
||||
type: "junction"
|
||||
}
|
||||
});
|
||||
const junctionColumns = referencedColumns.map(referencedColumn => {
|
||||
const joinColumn = joinTable.joinColumns ? joinTable.joinColumns.find(joinColumnArgs => {
|
||||
return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === referencedColumn.propertyName) &&
|
||||
!!joinColumnArgs.name;
|
||||
}) : undefined;
|
||||
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.namingStrategy.joinTableColumnName(relation.entityMetadata.tableNameWithoutPrefix, referencedColumn.propertyName, referencedColumn.givenDatabaseName);
|
||||
|
||||
return new ColumnMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
referencedColumn: referencedColumn,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: columnName,
|
||||
options: {
|
||||
name: columnName,
|
||||
length: referencedColumn.length,
|
||||
type: referencedColumn.type,
|
||||
nullable: false,
|
||||
primary: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const inverseJunctionColumns = inverseReferencedColumns.map(inverseReferencedColumn => {
|
||||
const joinColumn = joinTable.inverseJoinColumns ? joinTable.inverseJoinColumns.find(joinColumnArgs => {
|
||||
return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === inverseReferencedColumn.propertyName) &&
|
||||
!!joinColumnArgs.name;
|
||||
}) : undefined;
|
||||
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.namingStrategy.joinTableColumnName(relation.inverseEntityMetadata.tableNameWithoutPrefix, inverseReferencedColumn.propertyName, inverseReferencedColumn.givenDatabaseName);
|
||||
|
||||
return new ColumnMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
referencedColumn: inverseReferencedColumn,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: columnName,
|
||||
options: {
|
||||
length: inverseReferencedColumn.length,
|
||||
type: inverseReferencedColumn.type,
|
||||
name: columnName,
|
||||
nullable: false,
|
||||
primary: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
entityMetadata.foreignKeys = [
|
||||
new ForeignKeyMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
referencedEntityMetadata: relation.entityMetadata,
|
||||
columns: junctionColumns,
|
||||
referencedColumns: referencedColumns
|
||||
}),
|
||||
new ForeignKeyMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
referencedEntityMetadata: relation.inverseEntityMetadata,
|
||||
columns: inverseJunctionColumns,
|
||||
referencedColumns: inverseReferencedColumns
|
||||
}),
|
||||
];
|
||||
|
||||
junctionColumns.concat(inverseJunctionColumns).forEach(column => column.relationMetadata = relation);
|
||||
entityMetadata.ownColumns = junctionColumns.concat(inverseJunctionColumns);
|
||||
entityMetadata.indices = [
|
||||
new IndexMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
columns: junctionColumns,
|
||||
args: {
|
||||
target: "",
|
||||
unique: false
|
||||
}
|
||||
}),
|
||||
|
||||
new IndexMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
columns: inverseJunctionColumns,
|
||||
args: {
|
||||
target: "",
|
||||
unique: false
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
return entityMetadata;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
protected collectRelationReferencedColumns(relation: RelationMetadata, joinTable: JoinTableMetadataArgs) {
|
||||
const hasAnyReferencedColumnName = joinTable.joinColumns ? joinTable.joinColumns.find(joinColumn => !!joinColumn.referencedColumnName) : false;
|
||||
if (!joinTable.joinColumns || (joinTable.joinColumns && !hasAnyReferencedColumnName)) {
|
||||
return relation.entityMetadata.ownColumns.filter(column => column.isPrimary);
|
||||
} else {
|
||||
return joinTable.joinColumns.map(joinColumn => {
|
||||
const referencedColumn = relation.entityMetadata.ownColumns.find(column => column.propertyName === joinColumn.referencedColumnName);
|
||||
if (!referencedColumn)
|
||||
throw new Error(`Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.entityMetadata.name}`);
|
||||
|
||||
return referencedColumn;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected collectRelationInverseReferencedColumns(relation: RelationMetadata, joinTable: JoinTableMetadataArgs) {
|
||||
const hasInverseJoinColumns = !!joinTable.inverseJoinColumns;
|
||||
const hasAnyInverseReferencedColumnName = hasInverseJoinColumns ? joinTable.inverseJoinColumns!.find(joinColumn => !!joinColumn.referencedColumnName) : false;
|
||||
if (!hasInverseJoinColumns || (hasInverseJoinColumns && !hasAnyInverseReferencedColumnName)) {
|
||||
return relation.inverseEntityMetadata.primaryColumns;
|
||||
} else {
|
||||
return joinTable.inverseJoinColumns!.map(joinColumn => {
|
||||
const referencedColumn = relation.inverseEntityMetadata.ownColumns.find(column => column.propertyName === joinColumn.referencedColumnName);
|
||||
if (!referencedColumn)
|
||||
throw new Error(`Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.inverseEntityMetadata.name}`);
|
||||
|
||||
return referencedColumn;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
118
src/metadata-builder/RelationJoinColumnBuilder.ts
Normal file
118
src/metadata-builder/RelationJoinColumnBuilder.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
|
||||
import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {MetadataArgsStorage} from "../metadata-args/MetadataArgsStorage";
|
||||
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
|
||||
import {Driver} from "../driver/Driver";
|
||||
import {JoinColumnMetadataArgs} from "../metadata-args/JoinColumnMetadataArgs";
|
||||
|
||||
// cases it should cover:
|
||||
// 1. when join column is set with custom name and without referenced column name
|
||||
// we need automatically set referenced column name - primary ids by default
|
||||
// @JoinColumn({ name: "custom_name" })
|
||||
// 2. when join column is set with only referenced column name
|
||||
// we need automatically set join column name - relation name + referenced column name
|
||||
// @JoinColumn({ referencedColumnName: "title" })
|
||||
// 3. when join column is set without both referenced column name and join column name
|
||||
// we need to automatically set both of them
|
||||
// @JoinColumn()
|
||||
// 4. when join column is not set at all (as in case of @ManyToOne relation)
|
||||
// we need to create join column for it with proper referenced column name and join column name
|
||||
// 5. when multiple join columns set none of referencedColumnName and name can be optional
|
||||
// both options are required
|
||||
// @JoinColumn([
|
||||
// { name: "category_title", referencedColumnName: "type" },
|
||||
// { name: "category_title", referencedColumnName: "name" },
|
||||
// ])
|
||||
|
||||
// since for many-to-one relations having JoinColumn decorator is not required,
|
||||
// we need to go thought each many-to-one relation without join column decorator set
|
||||
// and create join column metadata args for them
|
||||
export class RelationJoinColumnBuilder {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(private namingStrategy: NamingStrategyInterface) {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
build(joinColumns: JoinColumnMetadataArgs[], relation: RelationMetadata): ForeignKeyMetadata|undefined {
|
||||
const referencedColumns = this.collectRelationReferencedColumns(joinColumns, relation);
|
||||
if (!referencedColumns.length)
|
||||
return undefined; // this case is possible only for one-to-one non owning side
|
||||
|
||||
const columns = this.collectRelationColumns(joinColumns, relation, referencedColumns);
|
||||
return new ForeignKeyMetadata({
|
||||
entityMetadata: relation.entityMetadata,
|
||||
referencedEntityMetadata: relation.inverseEntityMetadata,
|
||||
namingStrategy: this.namingStrategy,
|
||||
columns: columns,
|
||||
referencedColumns: referencedColumns,
|
||||
onDelete: relation.onDelete,
|
||||
});
|
||||
}
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
protected collectRelationReferencedColumns(joinColumns: JoinColumnMetadataArgs[], relation: RelationMetadata): ColumnMetadata[] {
|
||||
const hasAnyReferencedColumnName = joinColumns.find(joinColumnArgs => !!joinColumnArgs.referencedColumnName);
|
||||
const manyToOneWithoutJoinColumn = joinColumns.length === 0 && relation.isManyToOne;
|
||||
const hasJoinColumnWithoutAnyReferencedColumnName = joinColumns.length > 0 && !hasAnyReferencedColumnName;
|
||||
|
||||
if (manyToOneWithoutJoinColumn || hasJoinColumnWithoutAnyReferencedColumnName) { // covers case3 and case1
|
||||
return relation.inverseEntityMetadata.primaryColumns;
|
||||
|
||||
} else { // cases with referenced columns defined
|
||||
return joinColumns.map(joinColumn => {
|
||||
const referencedColumn = relation.inverseEntityMetadata.ownColumns.find(column => column.propertyName === joinColumn.referencedColumnName); // todo: can we also search in relations?
|
||||
if (!referencedColumn)
|
||||
throw new Error(`Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.inverseEntityMetadata.name}`);
|
||||
|
||||
return referencedColumn;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private collectRelationColumns(joinColumns: JoinColumnMetadataArgs[], relation: RelationMetadata, referencedColumns: ColumnMetadata[]): ColumnMetadata[] {
|
||||
return referencedColumns.map(referencedColumn => {
|
||||
|
||||
// in the case if relation has join column with only name set we need this check
|
||||
const joinColumnMetadataArg = joinColumns.find(joinColumn => {
|
||||
return (!joinColumn.referencedColumnName || joinColumn.referencedColumnName === referencedColumn.propertyName) &&
|
||||
!!joinColumn.name;
|
||||
});
|
||||
const joinColumnName = joinColumnMetadataArg ? joinColumnMetadataArg.name : this.namingStrategy.joinColumnName(relation.propertyName, referencedColumn.propertyName);
|
||||
|
||||
let relationalColumn = relation.entityMetadata.ownColumns.find(column => column.databaseName === joinColumnName);
|
||||
if (!relationalColumn) {
|
||||
relationalColumn = new ColumnMetadata({
|
||||
entityMetadata: relation.entityMetadata,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: joinColumnName!,
|
||||
options: {
|
||||
name: joinColumnName,
|
||||
type: referencedColumn.type,
|
||||
primary: relation.isPrimary,
|
||||
nullable: relation.isNullable,
|
||||
}
|
||||
}
|
||||
});
|
||||
relationalColumn.build(this.namingStrategy);
|
||||
relation.entityMetadata.registerColumn(relationalColumn);
|
||||
}
|
||||
relationalColumn.referencedColumn = referencedColumn; // its important to set it here because we need to set referenced column for user defined join column
|
||||
relationalColumn.type = referencedColumn.type; // also since types of relational column and join column must be equal we override user defined column type
|
||||
relationalColumn.relationMetadata = relation;
|
||||
return relationalColumn;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -331,12 +331,10 @@ export class RelationMetadataBuilder {
|
||||
metadata.isOneToOne = this.isOneToOne;
|
||||
metadata.isOneToOneOwner = this.isOneToOneOwner;
|
||||
metadata.isWithJoinColumn = this.isWithJoinColumn;
|
||||
metadata.isOneToOneNotOwner = this.isOneToOneNotOwner;
|
||||
metadata.isOneToMany = this.isOneToMany;
|
||||
metadata.isManyToOne = this.isManyToOne;
|
||||
metadata.isManyToMany = this.isManyToMany;
|
||||
metadata.isManyToManyOwner = this.isManyToManyOwner;
|
||||
metadata.isManyToManyNotOwner = this.isManyToManyNotOwner;
|
||||
metadata.hasInverseSide = this.hasInverseSide;
|
||||
metadata.propertyPath = this.propertyPath;
|
||||
metadata.inverseSidePropertyPath = this.inverseSidePropertyPath;
|
||||
|
||||
@ -583,30 +583,38 @@ export class EntityMetadata {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Builder Methods
|
||||
// Public Builder Methods
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
buildOnRelationsChange() {
|
||||
this.relationsWithJoinColumns = this.relations.filter(relation => relation.isWithJoinColumn);
|
||||
this.hasNonNullableRelations = this.relationsWithJoinColumns.some(relation => !relation.isNullable || relation.isPrimary);
|
||||
}
|
||||
|
||||
buildOnColumnsChange() {
|
||||
registerColumn(column: ColumnMetadata) {
|
||||
this.ownColumns.push(column);
|
||||
this.columns = this.embeddeds.reduce((columns, embedded) => columns.concat(embedded.columnsFromTree), this.ownColumns);
|
||||
this.primaryColumns = this.columns.filter(column => column.isPrimary);
|
||||
this.hasMultiplePrimaryKeys = this.primaryColumns.length > 1;
|
||||
this.propertiesMap = this.createPropertiesMap();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
protected createPropertiesMap(): { [name: string]: string|any } {
|
||||
createPropertiesMap(): { [name: string]: string|any } {
|
||||
const map: { [name: string]: string|any } = {};
|
||||
this.columns.forEach(column => OrmUtils.mergeDeep(map, column.createValueMap(column.propertyPath)));
|
||||
this.relations.forEach(relation => OrmUtils.mergeDeep(map, relation.createValueMap(relation.propertyPath)));
|
||||
return map;
|
||||
}
|
||||
|
||||
// buildOnRelationsChange() {
|
||||
// this.relationsWithJoinColumns = this.relations.filter(relation => relation.isWithJoinColumn);
|
||||
// this.hasNonNullableRelations = this.relationsWithJoinColumns.some(relation => !relation.isNullable || relation.isPrimary);
|
||||
// }
|
||||
|
||||
// buildOnColumnsChange() {
|
||||
// this.columns = this.embeddeds.reduce((columns, embedded) => columns.concat(embedded.columnsFromTree), this.ownColumns);
|
||||
// this.primaryColumns = this.columns.filter(column => column.isPrimary);
|
||||
// this.hasMultiplePrimaryKeys = this.primaryColumns.length > 1;
|
||||
// this.propertiesMap = this.createPropertiesMap();
|
||||
// }
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
}
|
||||
@ -82,19 +82,18 @@ export class ForeignKeyMetadata {
|
||||
constructor(options: {
|
||||
entityMetadata: EntityMetadata,
|
||||
referencedEntityMetadata: EntityMetadata,
|
||||
namingStrategy?: NamingStrategyInterface,
|
||||
columns: ColumnMetadata[],
|
||||
referencedColumns: ColumnMetadata[],
|
||||
onDelete?: OnDeleteType
|
||||
}) {
|
||||
this.entityMetadata = options.entityMetadata;
|
||||
this.referencedEntityMetadata = options.referencedEntityMetadata;
|
||||
// this.tableName = options.entityMetadata.tableName;
|
||||
// this.referencedTableName = options.referencedEntityMetadata.tableName;
|
||||
this.columns = options.columns;
|
||||
// this.columnNames = options.columns.map(column => column.databaseName);
|
||||
this.referencedColumns = options.referencedColumns;
|
||||
// this.referencedColumnNames = options.referencedColumns.map(column => column.databaseName);
|
||||
this.onDelete = options.onDelete;
|
||||
if (options.namingStrategy)
|
||||
this.build(options.namingStrategy);
|
||||
}
|
||||
|
||||
build(namingStrategy: NamingStrategyInterface) {
|
||||
|
||||
@ -145,7 +145,7 @@ export class RelationMetadata {
|
||||
* If this relation is a many-to-one/one-to-one then it takes join columns from the current entity.
|
||||
* If this relation is many-to-many then it takes all owner join columns from the junction entity.
|
||||
*/
|
||||
joinColumns: ColumnMetadata[];
|
||||
joinColumns: ColumnMetadata[] = [];
|
||||
|
||||
/**
|
||||
* Inverse join table columns.
|
||||
@ -153,7 +153,7 @@ export class RelationMetadata {
|
||||
* and can be obtained only from owner side of the relation.
|
||||
* From non-owner side of the relation join columns will be undefined.
|
||||
*/
|
||||
inverseJoinColumns: ColumnMetadata[];
|
||||
inverseJoinColumns: ColumnMetadata[] = [];
|
||||
|
||||
/**
|
||||
* Gets the property's type to which this relation is applied.
|
||||
@ -270,6 +270,8 @@ export class RelationMetadata {
|
||||
this.isOneToMany = this.relationType === "one-to-many";
|
||||
this.isManyToOne = this.relationType === "many-to-one";
|
||||
this.isManyToMany = this.relationType === "many-to-many";
|
||||
this.isOneToOneNotOwner = this.isOneToOne ? true : false;
|
||||
this.isManyToManyNotOwner = this.isManyToMany ? true : false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@ -384,15 +386,14 @@ export class RelationMetadata {
|
||||
this.propertyPath = this.buildPropertyPath();
|
||||
}
|
||||
|
||||
buildOnForeignKeysChange() {
|
||||
this.isOwning = this.isManyToOne || ((this.isManyToMany || this.isOneToOne) && this.foreignKeys.length > 0);
|
||||
this.isOneToOneOwner = this.isOneToOne && this.isOwning;
|
||||
this.isOneToOneNotOwner = this.isOneToOne && !this.isOwning;
|
||||
this.isManyToManyOwner = this.isManyToMany && this.isOwning;
|
||||
this.isManyToManyNotOwner = this.isManyToMany && !this.isOwning;
|
||||
this.isWithJoinColumn = this.isManyToOne || this.isOneToOneOwner;
|
||||
registerForeignKeys(...foreignKeys: ForeignKeyMetadata[]) {
|
||||
this.foreignKeys.push(...foreignKeys);
|
||||
this.joinColumns = this.foreignKeys[0] ? this.foreignKeys[0].columns : [];
|
||||
this.inverseJoinColumns = this.foreignKeys[1] ? this.foreignKeys[1].columns : [];
|
||||
this.isOwning = this.isManyToOne || ((this.isManyToMany || this.isOneToOne) && this.joinColumns.length > 0);
|
||||
this.isOneToOneOwner = this.isOneToOne && this.isOwning;
|
||||
this.isManyToManyOwner = this.isManyToMany && this.isOwning;
|
||||
this.isWithJoinColumn = this.isManyToOne || this.isOneToOneOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -2,12 +2,12 @@ import "reflect-metadata";
|
||||
import {Post} from "./entity/Post";
|
||||
import {ContentModule} from "./entity/ContentModule";
|
||||
import {Unit} from "./entity/Unit";
|
||||
import {MetadataArgsUtils} from "../../../../src/metadata-args/MetadataArgsUtils";
|
||||
import {MetadataUtils} from "../../../../src/metadata-args/MetadataUtils";
|
||||
|
||||
describe("metadata builder > MetadataArgsUtils", () => {
|
||||
|
||||
it("getInheritanceTree", () => {
|
||||
const inheritanceTree = MetadataArgsUtils.getInheritanceTree(Post);
|
||||
const inheritanceTree = MetadataUtils.getInheritanceTree(Post);
|
||||
inheritanceTree.should.be.eql([
|
||||
Post,
|
||||
ContentModule,
|
||||
@ -16,7 +16,7 @@ describe("metadata builder > MetadataArgsUtils", () => {
|
||||
});
|
||||
|
||||
it("filterByTargetClasses", () => {
|
||||
MetadataArgsUtils.filterByTarget([
|
||||
MetadataUtils.filterByTarget([
|
||||
{ },
|
||||
{ target: undefined },
|
||||
{ target: null },
|
||||
@ -30,7 +30,7 @@ describe("metadata builder > MetadataArgsUtils", () => {
|
||||
{ target: Unit },
|
||||
]);
|
||||
|
||||
MetadataArgsUtils.filterByTarget([
|
||||
MetadataUtils.filterByTarget([
|
||||
{ },
|
||||
{ target: undefined },
|
||||
{ target: null },
|
||||
@ -42,7 +42,7 @@ describe("metadata builder > MetadataArgsUtils", () => {
|
||||
{ target: Unit },
|
||||
]);
|
||||
|
||||
MetadataArgsUtils.filterByTarget([
|
||||
MetadataUtils.filterByTarget([
|
||||
{ },
|
||||
{ target: undefined },
|
||||
{ target: null },
|
||||
@ -57,11 +57,11 @@ describe("metadata builder > MetadataArgsUtils", () => {
|
||||
{ target: Unit },
|
||||
]);
|
||||
|
||||
MetadataArgsUtils.filterByTarget([
|
||||
MetadataUtils.filterByTarget([
|
||||
], [Post, Unit, ContentModule]).should.be.eql([
|
||||
]);
|
||||
|
||||
MetadataArgsUtils.filterByTarget([
|
||||
MetadataUtils.filterByTarget([
|
||||
{ },
|
||||
{ target: undefined },
|
||||
{ target: null },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user