entity metadata refactoring - phase I

This commit is contained in:
Umed Khudoiberdiev 2017-05-05 11:41:08 +05:00
parent 05f8c25869
commit 280fcd5af6
10 changed files with 221 additions and 44 deletions

View File

@ -19,6 +19,8 @@ each for its own `findOne*` or `find*` methods
* `QueryBuilder#setFirstResult` has been renamed to `QueryBuilder#skip`
* `QueryBuilder#setMaxResults` has been renamed to `QueryBuilder#take`
* renamed `entityManager` to `manager`
* `@AbstractEntity` is deprecated. Now there is no need to mark class with a decorator, it can extend any class with columns
*
### NEW FEATURES

View File

@ -16,6 +16,7 @@ import {InheritanceMetadataArgs} from "./InheritanceMetadataArgs";
import {DiscriminatorValueMetadataArgs} from "./DiscriminatorValueMetadataArgs";
import {EntityRepositoryMetadataArgs} from "./EntityRepositoryMetadataArgs";
import {TransactionEntityMetadataArgs} from "./TransactionEntityMetadataArgs";
import {MetadataArgsUtils} from "./MetadataArgsUtils";
/**
* Storage all metadatas of all available types: tables, fields, subscribers, relations, etc.
@ -57,10 +58,14 @@ export class MetadataArgsStorage {
* Gets merged (with all abstract classes) table metadatas for the given classes.
*/
getMergedTableMetadatas(classes?: Function[]) {
const allTableMetadataArgs = classes ? this.tables.filterByTargets(classes) : this.tables;
const tableMetadatas = allTableMetadataArgs.filter(table => table.type === "regular" || table.type === "closure" || table.type === "class-table-child");
return tableMetadatas.toArray().map(tableMetadata => {
// filter out tables by a given allowed classes
const allTableMetadataArgs = MetadataArgsUtils.filterByTargetClasses(this.tables.toArray(), classes);
// filter out table metadata args for those we really create entity metadatas and tables in the db
const realTables = allTableMetadataArgs.filter(table => table.type === "regular" || table.type === "closure" || table.type === "class-table-child");
return realTables.map(tableMetadata => {
return this.mergeWithAbstract(this.tables, tableMetadata);
});
}

View File

@ -0,0 +1,34 @@
/**
* Metadata args utility functions.
*/
export class MetadataArgsUtils {
/**
* Gets given's entity all inherited classes.
* Gives in order from parents to children.
* For example Post extends ContentModel which extends Unit it will give
* [Unit, ContentModel, Post]
*/
static getInheritanceTree(entity: Function): Function[] {
const tree: Function[] = [entity];
const getPrototypeOf = (object: Function): void => {
const proto = Object.getPrototypeOf(object);
if (proto && proto.name) {
tree.push(proto);
getPrototypeOf(proto);
}
};
getPrototypeOf(entity);
return tree;
}
/**
* Filters given array of targets by a given classes.
* If classes are not given, then it returns array itself.
*/
static filterByTargetClasses<T extends { target?: any }>(array: T[], classes?: Function[]): T[] {
if (!classes) return array;
return array.filter(item => item.target && classes.indexOf(item.target) !== -1);
}
}

View File

@ -45,8 +45,7 @@ export class EntityMetadataBuilder {
/**
* Builds a complete metadata aggregations for the given entity classes.
*/
build(
entityClasses?: Function[]): EntityMetadata[] {
build(entityClasses?: Function[]): EntityMetadata[] {
const embeddableMergedArgs = this.metadataArgsStorage.getMergedEmbeddableTableMetadatas(entityClasses);
const entityMetadatas: EntityMetadata[] = [];
const allMergedArgs = this.metadataArgsStorage.getMergedTableMetadatas(entityClasses);
@ -101,8 +100,6 @@ export class EntityMetadataBuilder {
tableName: argsForTable.name,
tableType: argsForTable.type,
orderBy: argsForTable.orderBy,
engine: argsForTable.engine,
skipSchemaSync: argsForTable.skipSchemaSync,
columnMetadatas: columns,
relationMetadatas: relations,
relationIdMetadatas: relationIds,
@ -116,6 +113,8 @@ export class EntityMetadataBuilder {
target: tableArgs.target,
tableType: argsForTable.type,
userSpecifiedTableName: argsForTable.name,
engine: argsForTable.engine,
skipSchemaSync: argsForTable.skipSchemaSync,
});
entityMetadatas.push(entityMetadata);
@ -590,7 +589,9 @@ export class EntityMetadataBuilder {
target?: Function|string,
tableType: TableType,
userSpecifiedTableName?: string,
closureOwnerTableName?: string
closureOwnerTableName?: string,
engine?: string,
skipSchemaSync?: boolean,
}) {
let target = options.target;
@ -613,6 +614,8 @@ export class EntityMetadataBuilder {
metadata.tableNameWithoutPrefix = tableNameWithoutPrefix;
metadata.tableName = tableName;
metadata.name = targetName ? targetName : tableName;
metadata.engine = options.engine;
metadata.skipSchemaSync = options.skipSchemaSync || false;
}
/*protected createEntityMetadata(tableArgs: any, argsForTable: any, ): EntityMetadata {

View File

@ -44,19 +44,6 @@ export class EntityMetadata {
*/
readonly namingStrategy: NamingStrategyInterface;
/**
* Target class to which this entity metadata is bind.
* Note, that when using table inheritance patterns target can be different rather then table's target.
* For virtual tables which lack of real entity (like junction tables) target is equal to their table name.
*/
target: Function|string;
/**
* Indicates if this entity metadata of a junction table, or not.
* Junction table is a table created by many-to-many relationship.
*/
readonly isJunction: boolean;
/**
* Specifies a default order by used for queries from this table when no explicit order by is specified.
*/
@ -104,26 +91,6 @@ export class EntityMetadata {
*/
readonly discriminatorValue?: string;
/**
* Global tables prefix. Customer can set a global table prefix for all tables in the database.
*/
readonly tablesPrefix?: string;
/**
* Table's database engine type (like "InnoDB", "MyISAM", etc).
*/
readonly engine?: string;
/**
* Whether table must be synced during schema build or not
*/
readonly skipSchemaSync?: boolean;
/**
* Table type. Tables can be abstract, closure, junction, embedded, etc.
*/
tableType: TableType = "regular";
// -------------------------------------------------------------------------
// Private properties
// -------------------------------------------------------------------------
@ -141,7 +108,6 @@ export class EntityMetadata {
private lazyRelationsWrapper: LazyRelationsWrapper) {
this.target = args.target;
this.isJunction = args.junction;
this.tablesPrefix = args.tablesPrefix;
this.namingStrategy = args.namingStrategy;
this.tableType = args.tableType;
this._columns = args.columnMetadatas || [];
@ -153,8 +119,6 @@ export class EntityMetadata {
this.embeddeds = args.embeddedMetadatas || [];
this.discriminatorValue = args.discriminatorValue;
this.inheritanceType = args.inheritanceType;
this.engine = args.engine;
this.skipSchemaSync = args.skipSchemaSync;
this._orderBy = args.orderBy;
this._columns.forEach(column => column.entityMetadata = this);
this._relations.forEach(relation => relation.entityMetadata = this);
@ -177,6 +141,26 @@ export class EntityMetadata {
// Accessors
// -------------------------------------------------------------------------
/**
* Table type. Tables can be abstract, closure, junction, embedded, etc.
*/
tableType: TableType = "regular";
/**
* Target class to which this entity metadata is bind.
* Note, that when using table inheritance patterns target can be different rather then table's target.
* For virtual tables which lack of real entity (like junction tables) target is equal to their table name.
*/
target: Function|string;
/**
* Indicates if this entity metadata of a junction table, or not.
* Junction table is a table created by many-to-many relationship.
*
* Its also possible to understand if entity is junction via tableType.
*/
isJunction: boolean;
/**
* Entity's name.
* Equal to entity target class's name if target is set to table.
@ -221,6 +205,18 @@ export class EntityMetadata {
*/
tableNameWithoutPrefix: string;
/**
* Indicates if schema sync is skipped for this entity.
*/
skipSchemaSync: boolean;
/**
* Table's database engine type (like "InnoDB", "MyISAM", etc).
*/
engine?: string;
// propertiesMap: ObjectLiteral;
/**
* Specifies a default order by used for queries from this table when no explicit order by is specified.
* If default order by was not set, then returns undefined.

View File

@ -6,6 +6,8 @@ export type TableType = "regular"|"abstract"|"junction"|"closure"|"closure-junct
/**
* Represents a class with constants - list of all possible table types.
*
* todo: remove if only regular table will left here
*/
export class TableTypes {
@ -17,37 +19,52 @@ export class TableTypes {
/**
* This type is for the tables that does not exist in the database,
* but provide columns and relations for the tables of the child classes who inherit them.
*
* @deprecated
*/
static ABSTRACT: TableType = "abstract";
/**
* Junction table is a table automatically created by many-to-many relationship.
*
* todo: remove and isJunction condition is enough in entity metadata?
*/
static JUNCTION: TableType = "junction";
/**
* Closure table is one of the tree-specific tables that supports closure database pattern.
*
* todo: maybe we can determine if it is closure if it has some closure-specific decorator?
* todo: or if its not possible then maybe create a separate decorator for closure?
*/
static CLOSURE: TableType = "closure";
/**
* This type is for tables that contain junction metadata of the closure tables.
*
* todo: remove and isClosureJunction condition is enough in entity metadata?
*/
static CLOSURE_JUNCTION: TableType = "closure-junction";
/**
* Embeddable tables are not stored in the database as separate tables.
* Instead their columns are embed into tables who owns them.
*
* @deprecated
*/
static EMBEDDABLE: TableType = "embeddable";
/**
* Special table type for tables that are mapped into single table using Single Table Inheritance pattern.
*
* todo: create separate decorators?
*/
static SINGLE_TABLE_CHILD: TableType = "single-table-child";
/**
* Special table type for tables that are mapped into multiple tables using Class Table Inheritance pattern.
*
* todo: create separate decorators?
*/
static CLASS_TABLE_CHILD: TableType = "class-table-child";
}

View File

@ -0,0 +1,9 @@
import {Column} from "../../../../../src/decorator/columns/Column";
import {Unit} from "./Unit";
export class ContentModule extends Unit {
@Column()
tag: string;
}

View File

@ -0,0 +1,14 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {Column} from "../../../../../src/decorator/columns/Column";
import {ContentModule} from "./ContentModule";
@Entity()
export class Post extends ContentModule {
@Column()
title: string;
@Column()
text: string;
}

View File

@ -0,0 +1,12 @@
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
export class Unit {
@PrimaryGeneratedColumn()
id: string;
@Column()
type: string;
}

View File

@ -0,0 +1,85 @@
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";
describe("metadata builder > MetadataArgsUtils", () => {
it("getInheritanceTree", () => {
const inheritanceTree = MetadataArgsUtils.getInheritanceTree(Post);
inheritanceTree.should.be.eql([
Post,
ContentModule,
Unit,
]);
});
it("filterByTargetClasses", () => {
MetadataArgsUtils.filterByTargetClasses([
{ },
{ target: undefined },
{ target: null },
{ target: 1 },
{ target: "" },
{ target: Post },
{ target: ContentModule },
{ target: Unit },
], [Post, Unit]).should.be.eql([
{ target: Post },
{ target: Unit },
]);
MetadataArgsUtils.filterByTargetClasses([
{ },
{ target: undefined },
{ target: null },
{ target: 1 },
{ target: "" },
{ target: ContentModule },
{ target: Unit },
], [Post, Unit]).should.be.eql([
{ target: Unit },
]);
MetadataArgsUtils.filterByTargetClasses([
{ },
{ target: undefined },
{ target: null },
{ target: 1 },
{ target: "" },
{ target: ContentModule },
{ target: Post },
{ target: Unit },
], [Post, Unit, ContentModule]).should.be.eql([
{ target: ContentModule },
{ target: Post },
{ target: Unit },
]);
MetadataArgsUtils.filterByTargetClasses([
], [Post, Unit, ContentModule]).should.be.eql([
]);
MetadataArgsUtils.filterByTargetClasses([
{ },
{ target: undefined },
{ target: null },
{ target: 1 },
{ target: "" },
{ target: ContentModule },
{ target: Post },
{ target: Unit },
]).should.be.eql([
{ },
{ target: undefined },
{ target: null },
{ target: 1 },
{ target: "" },
{ target: ContentModule },
{ target: Post },
{ target: Unit },
]);
});
});