refactored entity metadata builder

This commit is contained in:
Umed Khudoiberdiev 2016-05-27 09:01:01 +05:00
parent b2c3fe0151
commit b3002b9fda
7 changed files with 143 additions and 130 deletions

View File

@ -0,0 +1,76 @@
import {EntityMetadata} from "../metadata/EntityMetadata";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {ColumnOptions} from "../decorator/options/ColumnOptions";
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
import {TableMetadata} from "../metadata/TableMetadata";
import {ColumnMetadataArgs} from "../metadata-args/ColumnMetadataArgs";
import {ColumnTypes} from "../metadata/types/ColumnTypes";
/**
* Helps to create EntityMetadatas for junction tables.
*
* @internal
*/
export interface ClosureJunctionEntityMetadataBuilderArgs {
namingStrategy: NamingStrategyInterface;
table: TableMetadata;
primaryColumn: ColumnMetadata;
hasTreeLevelColumn: boolean;
}
/**
* Helps to create EntityMetadatas for junction tables for closure tables.
*
* @internal
*/
export class ClosureJunctionEntityMetadataBuilder {
build(args: ClosureJunctionEntityMetadataBuilderArgs) {
const columns = [
new ColumnMetadata(<ColumnMetadataArgs> {
propertyType: args.primaryColumn.type,
mode: "virtual",
options: <ColumnOptions> {
length: args.primaryColumn.length,
type: args.primaryColumn.type,
name: "ancestor"
}
}),
new ColumnMetadata(<ColumnMetadataArgs> {
propertyType: args.primaryColumn.type,
mode: "virtual",
options: <ColumnOptions> {
length: args.primaryColumn.length,
type: args.primaryColumn.type,
name: "descendant"
}
})
];
if (args.hasTreeLevelColumn) {
columns.push(new ColumnMetadata(<ColumnMetadataArgs> {
propertyType: ColumnTypes.INTEGER,
mode: "virtual",
options: {
type: ColumnTypes.INTEGER,
name: "level"
}
}));
}
const closureJunctionTableMetadata = new TableMetadata(undefined, args.table.name, "closureJunction");
return new EntityMetadata({
namingStrategy: args.namingStrategy,
tableMetadata: closureJunctionTableMetadata,
columnMetadatas: columns,
foreignKeyMetadatas: [
new ForeignKeyMetadata([columns[0]], args.table, [args.primaryColumn]),
new ForeignKeyMetadata([columns[1]], args.table, [args.primaryColumn])
]
});
}
}

View File

@ -6,16 +6,12 @@ import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
import {EntityMetadataValidator} from "./EntityMetadataValidator";
import {IndexMetadata} from "../metadata/IndexMetadata";
import {JoinColumnMetadata} from "../metadata/JoinColumnMetadata";
import {JoinColumnOptions} from "../decorator/options/JoinColumnOptions";
import {TableMetadata} from "../metadata/TableMetadata";
import {ColumnTypes} from "../metadata/types/ColumnTypes";
import {getMetadataArgsStorage} from "../index";
import {getMetadataArgsStorage, getFromContainer} from "../index";
import {RelationMetadata} from "../metadata/RelationMetadata";
import {JoinTableMetadata} from "../metadata/JoinTableMetadata";
import {JoinTableMetadataArgs} from "../metadata-args/JoinTableMetadataArgs";
import {PropertyMetadataArgsCollection} from "../metadata-args/collection/PropertyMetadataArgsCollection";
import {ColumnMetadataArgs} from "../metadata-args/ColumnMetadataArgs";
import {JunctionEntityMetadataBuilder} from "./JunctionEntityMetadataBuilder";
import {ClosureJunctionEntityMetadataBuilder} from "./ClosureJunctionEntityMetadataBuilder";
/**
* Aggregates all metadata: table, column, relation into one collection grouped by tables for a given set of classes.
@ -31,9 +27,6 @@ export class EntityMetadataBuilder {
// 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
private entityValidator = new EntityMetadataValidator();
private junctionEntityMetadataBuilder = new JunctionEntityMetadataBuilder();
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
@ -59,12 +52,6 @@ export class EntityMetadataBuilder {
relationMetadatas: relations,
indexMetadatas: indices
});
// set entity metadata everywhere its used
table.entityMetadata = entityMetadata;
columns.forEach(column => column.entityMetadata = entityMetadata);
relations.forEach(relation => relation.entityMetadata = entityMetadata);
indices.forEach(index => index.entityMetadata = entityMetadata);
// create entity's relations join tables
entityMetadata.manyToManyRelations.forEach(relation => {
@ -113,35 +100,33 @@ export class EntityMetadataBuilder {
});
// check for errors in a built metadata schema (we need to check after relationEntityMetadata is set)
this.entityValidator.validateMany(entityMetadatas);
getFromContainer(EntityMetadataValidator).validateMany(entityMetadatas);
// generate columns and foreign keys for tables with relations
entityMetadatas.forEach(metadata => {
metadata.relationsWithJoinColumns.forEach(relation => {
// find relational column and if it does not exist - add it
// todo: later add support for propertyInFunction
const inverseSideColumn = relation.joinColumn.referencedColumn;
let relationalColumn = metadata.columns.find(column => column.name === relation.name); // todo?: ColumnCollection.findByName
let relationalColumn = metadata.columns.find(column => column.name === relation.name);
if (!relationalColumn) {
const options: ColumnOptions = {
type: inverseSideColumn.type,
oldColumnName: relation.oldColumnName,
nullable: relation.isNullable
};
relationalColumn = new ColumnMetadata(metadata, {
relationalColumn = new ColumnMetadata({
target: metadata.target,
propertyName: relation.name,
propertyType: inverseSideColumn.propertyType,
mode: "virtual",
options: options
options: <ColumnOptions> {
type: inverseSideColumn.type,
oldColumnName: relation.oldColumnName,
nullable: relation.isNullable
}
});
relationalColumn.entityMetadata = metadata;
metadata.columns.push(relationalColumn);
}
// create and add foreign key
const foreignKey = new ForeignKeyMetadata(
metadata.table,
[relationalColumn],
relation.inverseEntityMetadata.table,
[inverseSideColumn],
@ -152,93 +137,39 @@ export class EntityMetadataBuilder {
});
});
// generate closure tables
const closureJunctionEntityMetadatas: EntityMetadata[] = [];
entityMetadatas
.filter(metadata => metadata.table.isClosure)
.forEach(metadata => {
const closureTableName = namingStrategy.closureJunctionTableName(metadata.table.name);
const closureJunctionTableMetadata = new TableMetadata(undefined, closureTableName, "closureJunction");
const column1Args: ColumnMetadataArgs = {
propertyType: metadata.primaryColumn.type,
mode: "virtual",
options: <ColumnOptions> {
length: metadata.primaryColumn.length,
type: metadata.primaryColumn.type,
name: "ancestor"
}
};
const column2Args: ColumnMetadataArgs = {
propertyType: metadata.primaryColumn.type,
mode: "virtual",
options: <ColumnOptions> {
length: metadata.primaryColumn.length,
type: metadata.primaryColumn.type,
name: "descendant"
}
};
const column3Args: ColumnMetadataArgs = {
propertyType: ColumnTypes.INTEGER,
mode: "virtual",
options: {
type: ColumnTypes.INTEGER,
name: "level"
}
};
const closureJunctionColumn1 = new ColumnMetadata(column1Args);
const closureJunctionColumn2 = new ColumnMetadata(column2Args);
const closureJunctionColumn3 = new ColumnMetadata(column3Args);
const columns = [closureJunctionColumn1, closureJunctionColumn2];
if (metadata.hasTreeLevelColumn)
columns.push(closureJunctionColumn3);
const foreignKey1 = new ForeignKeyMetadata(closureJunctionTableMetadata, [columns[0]], metadata.table, [metadata.primaryColumn]);
const foreignKey2 = new ForeignKeyMetadata(closureJunctionTableMetadata, [columns[1]], metadata.table, [metadata.primaryColumn]);
const foreignKeys = [foreignKey1, foreignKey2];
const closureJunctionEntityMetadata = new EntityMetadata({
namingStrategy: namingStrategy,
tableMetadata: closureJunctionTableMetadata,
columnMetadatas: columns,
foreignKeyMetadatas: foreignKeys
});
columns.forEach(column => column.entityMetadata = closureJunctionEntityMetadata);
foreignKeys.forEach(foreignKey => foreignKey.entityMetadata = closureJunctionEntityMetadata);
closureJunctionTableMetadata.entityMetadata = closureJunctionEntityMetadata;
closureJunctionEntityMetadatas.push(closureJunctionEntityMetadata);
metadata.closureJunctionTable = closureJunctionEntityMetadata;
// generate junction tables for all closure tables
entityMetadatas.forEach(metadata => {
if (!metadata.table.isClosure)
return;
const closureJunctionEntityMetadata = getFromContainer(ClosureJunctionEntityMetadataBuilder).build({
namingStrategy: namingStrategy,
table: metadata.table,
primaryColumn: metadata.primaryColumn,
hasTreeLevelColumn: metadata.hasTreeLevelColumn
});
metadata.closureJunctionTable = closureJunctionEntityMetadata;
entityMetadatas.push(closureJunctionEntityMetadata);
});
// generate junction tables with its columns and foreign keys
const junctionEntityMetadatas: EntityMetadata[] = [];
// generate junction tables for all many-to-many tables
entityMetadatas.forEach(metadata => {
metadata.ownerManyToManyRelations.forEach(relation => {
const junctionEntityMetadata = this.junctionEntityMetadataBuilder.createJunctionEntityMetadata({
const junctionEntityMetadata = getFromContainer(JunctionEntityMetadataBuilder).build({
namingStrategy: namingStrategy,
firstTable: metadata.table,
secondTable: relation.inverseEntityMetadata.table,
joinTable: relation.joinTable
});
junctionEntityMetadatas.push(junctionEntityMetadata);
relation.junctionEntityMetadata = junctionEntityMetadata;
if (relation.hasInverseSide)
relation.inverseRelation.junctionEntityMetadata = junctionEntityMetadata;
entityMetadatas.push(junctionEntityMetadata);
});
});
return entityMetadatas
.concat(junctionEntityMetadatas)
.concat(closureJunctionEntityMetadatas);
return entityMetadatas;
}
// -------------------------------------------------------------------------
// Private Methods
// -------------------------------------------------------------------------
}

View File

@ -25,7 +25,7 @@ export interface JunctionEntityMetadataBuilderArgs {
*/
export class JunctionEntityMetadataBuilder {
createJunctionEntityMetadata(args: JunctionEntityMetadataBuilderArgs) {
build(args: JunctionEntityMetadataBuilderArgs) {
const column1 = args.joinTable.referencedColumn;
const column2 = args.joinTable.inverseReferencedColumn;
@ -34,7 +34,7 @@ export class JunctionEntityMetadataBuilder {
name: args.joinTable.name,
type: "junction"
});
const junctionColumn1 = new ColumnMetadata({
propertyType: column1.type,
mode: "virtual",
@ -53,22 +53,19 @@ export class JunctionEntityMetadataBuilder {
name: args.joinTable.inverseJoinColumnName
}
});
const junctionColumns = [junctionColumn1, junctionColumn2];
const foreignKey1 = new ForeignKeyMetadata(tableMetadata, [junctionColumns[0]], args.firstTable, [column2]);
const foreignKey2 = new ForeignKeyMetadata(tableMetadata, [junctionColumns[1]], args.secondTable, [column2]);
const foreignKeys = [foreignKey1, foreignKey2];
const junctionEntityMetadata = new EntityMetadata({
return new EntityMetadata({
namingStrategy: args.namingStrategy,
tableMetadata: tableMetadata,
columnMetadatas: junctionColumns,
foreignKeyMetadatas: foreignKeys,
columnMetadatas: [
junctionColumn1,
junctionColumn2
],
foreignKeyMetadatas: [
new ForeignKeyMetadata([junctionColumn1], args.firstTable, [column1]),
new ForeignKeyMetadata([junctionColumn2], args.secondTable, [column2])
]
});
junctionColumns.forEach(column => column.entityMetadata = junctionEntityMetadata);
foreignKeys.forEach(column => column.entityMetadata = junctionEntityMetadata);
tableMetadata.entityMetadata = junctionEntityMetadata;
return junctionEntityMetadata;
}
}

View File

@ -66,6 +66,12 @@ export class EntityMetadata {
this.relations = args.relationMetadatas || [];
this.indices = args.indexMetadatas || [];
this.foreignKeys = args.foreignKeyMetadatas || [];
this.table.entityMetadata = this;
this.columns.forEach(column => column.entityMetadata = this);
this.relations.forEach(relation => relation.entityMetadata = this);
this.foreignKeys.forEach(foreignKey => foreignKey.entityMetadata = this);
this.indices.forEach(index => index.entityMetadata = this);
}
// -------------------------------------------------------------------------

View File

@ -25,11 +25,6 @@ export class ForeignKeyMetadata {
// Public Readonly Properties
// -------------------------------------------------------------------------
/**
* Table to which this foreign key is applied.
*/
readonly table: TableMetadata;
/**
* Array of columns of this foreign key.
*/
@ -54,12 +49,10 @@ export class ForeignKeyMetadata {
// Constructor
// -------------------------------------------------------------------------
constructor(table: TableMetadata,
columns: ColumnMetadata[],
constructor(columns: ColumnMetadata[],
referencedTable: TableMetadata,
referencedColumns: ColumnMetadata[],
onDelete?: OnDeleteType) {
this.table = table;
this.columns = columns;
this.referencedTable = referencedTable;
this.referencedColumns = referencedColumns;
@ -71,6 +64,20 @@ export class ForeignKeyMetadata {
// Accessors
// -------------------------------------------------------------------------
/**
* Gets the table name to which this foreign key is applied.
*/
get tableName() {
return this.entityMetadata.table.name;
}
/**
* Gets foreign key name.
*/
get name() {
return this.entityMetadata.namingStrategy.foreignKeyName(this.tableName, this.columnNames, this.referencedTable.name, this.referencedColumnNames);
}
/**
* Gets array of column names.
*/
@ -85,11 +92,4 @@ export class ForeignKeyMetadata {
return this.referencedColumns.map(column => column.name);
}
/**
* Gets foreign key name.
*/
get name() {
return this.entityMetadata.namingStrategy.foreignKeyName(this.table.name, this.columnNames, this.referencedTable.name, this.referencedColumnNames);
}
}

View File

@ -63,6 +63,9 @@ export class TableMetadata extends TargetMetadata {
*/
get name() {
if (this.isClosure && this._name)
return this.entityMetadata.namingStrategy.closureJunctionTableName(this._name);
// if custom name is given then use it
if (this._name)
return this.entityMetadata.namingStrategy.tableNameCustomized(this._name);

View File

@ -57,7 +57,7 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
}
addForeignKeyQuery(foreignKey: ForeignKeyMetadata): Promise<void> {
let sql = `ALTER TABLE ${foreignKey.table.name} ADD CONSTRAINT \`${foreignKey.name}\` ` +
let sql = `ALTER TABLE ${foreignKey.tableName} ADD CONSTRAINT \`${foreignKey.name}\` ` +
`FOREIGN KEY (${foreignKey.columnNames.join(", ")}) ` +
`REFERENCES ${foreignKey.referencedTable.name}(${foreignKey.referencedColumnNames.join(",")})`;
if (foreignKey.onDelete)
@ -70,7 +70,7 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
dropForeignKeyQuery(tableNameOrForeignKey: string|ForeignKeyMetadata, foreignKeyName?: string): Promise<void> {
let tableName = <string> tableNameOrForeignKey;
if (tableNameOrForeignKey instanceof ForeignKeyMetadata) {
tableName = tableNameOrForeignKey.table.name;
tableName = tableNameOrForeignKey.tableName;
foreignKeyName = tableNameOrForeignKey.name;
}