refactoring entity metadata builder stuff

This commit is contained in:
Umed Khudoiberdiev 2017-05-15 16:36:56 +05:00
parent 311773b942
commit 46a24cec0c
11 changed files with 366 additions and 2671 deletions

View File

@ -7,7 +7,7 @@ import {EntityListenerMetadata} from "../metadata/EntityListenerMetadata";
import {EntityManager} from "../entity-manager/EntityManager";
import {importClassesFromDirectories, importJsonsFromDirectories} from "../util/DirectoryExportedClassesLoader";
import {getFromContainer, getMetadataArgsStorage} from "../index";
import {AllEntityMetadataBuilder} from "../metadata-builder/AllEntityMetadataBuilder";
import {EntityMetadataBuilder} from "../metadata-builder/EntityMetadataBuilder";
import {DefaultNamingStrategy} from "../naming-strategy/DefaultNamingStrategy";
import {CannotImportAlreadyConnectedError} from "./error/CannotImportAlreadyConnectedError";
import {CannotCloseNotConnectedError} from "./error/CannotCloseNotConnectedError";
@ -699,7 +699,7 @@ export class Connection {
.forEach(metadata => this.entityListeners.push(new EntityListenerMetadata(metadata)));
// build entity metadatas from metadata args storage (collected from decorators)
new AllEntityMetadataBuilder(this.driver, lazyRelationsWrapper, getMetadataArgsStorage(), namingStrategy)
new EntityMetadataBuilder(this.driver, lazyRelationsWrapper, getMetadataArgsStorage(), namingStrategy)
.build(this.entityClasses)
.forEach(metadata => {
this.entityMetadatas.push(metadata);
@ -710,7 +710,7 @@ export class Connection {
// build entity metadatas from given entity schemas
if (this.entitySchemas && this.entitySchemas.length) {
const metadataArgsStorage = getFromContainer(EntitySchemaTransformer).transform(this.entitySchemas);
new AllEntityMetadataBuilder(this.driver, lazyRelationsWrapper, metadataArgsStorage, namingStrategy)
new EntityMetadataBuilder(this.driver, lazyRelationsWrapper, metadataArgsStorage, namingStrategy)
.build()
.forEach(metadata => {
this.entityMetadatas.push(metadata);

View File

@ -1,413 +0,0 @@
import {EntityMetadata} from "../metadata/EntityMetadata";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {IndexMetadata} from "../metadata/IndexMetadata";
import {RelationMetadata} from "../metadata/RelationMetadata";
import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
import {MetadataArgsStorage} from "../metadata-args/MetadataArgsStorage";
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
import {Driver} from "../driver/Driver";
import {EmbeddedMetadataArgs} from "../metadata-args/EmbeddedMetadataArgs";
import {RelationIdMetadata} from "../metadata/RelationIdMetadata";
import {RelationCountMetadata} from "../metadata/RelationCountMetadata";
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.
*/
export class AllEntityMetadataBuilder {
// 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
// -------------------------------------------------------------------------
// 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);
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* 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");
// 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.computeEntityMetadata(entityMetadata));
// 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 (for many-to-one and one-to-one owner)
entityMetadata.relations.filter(relation => relation.isOneToOne || relation.isManyToOne).forEach(relation => {
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 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)
// 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.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);
});
// 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));
// add lazy initializer for entity relations
entityMetadatas
.filter(metadata => metadata.target instanceof Function)
.forEach(entityMetadata => {
entityMetadata.relations
.filter(relation => relation.isLazy)
.forEach(relation => {
this.lazyRelationsWrapper.wrap((entityMetadata.target as Function).prototype, relation);
});
});
return entityMetadatas;
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Creates entity metadata from the given table args.
* Creates column, relation, etc. metadatas for everything this entity metadata owns.
*/
protected createEntityMetadata(tableArgs: TableMetadataArgs): EntityMetadata {
// 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 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;
}
/**
* 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 });
embeddedMetadata.columns = this.metadataArgsStorage.filterColumns(embeddedMetadata.type).map(args => {
return new ColumnMetadata({ entityMetadata, embeddedMetadata, args});
});
embeddedMetadata.relations = this.metadataArgsStorage.filterRelations(embeddedMetadata.type).map(args => {
return new RelationMetadata({ entityMetadata, embeddedMetadata, args });
});
embeddedMetadata.embeddeds = this.createEmbeddedsRecursively(entityMetadata, this.metadataArgsStorage.filterEmbeddeds(embeddedMetadata.type));
embeddedMetadata.embeddeds.forEach(subEmbedded => subEmbedded.parentEmbeddedMetadata = embeddedMetadata);
return embeddedMetadata;
});
}
/**
* 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));
embedded.relationsFromTree.forEach(relation => relation.build(this.namingStrategy));
});
entityMetadata.ownColumns.forEach(column => column.build(this.namingStrategy));
entityMetadata.ownRelations.forEach(relation => relation.build(this.namingStrategy));
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);
entityMetadata.manyToOneRelations = entityMetadata.relations.filter(relation => relation.isManyToOne);
entityMetadata.manyToManyRelations = entityMetadata.relations.filter(relation => relation.isManyToMany);
entityMetadata.ownerOneToOneRelations = entityMetadata.relations.filter(relation => relation.isOneToOneOwner);
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.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
entityMetadata.versionColumn = entityMetadata.columns.find(column => column.mode === "version")!; // todo: fix ! later
entityMetadata.discriminatorColumn = entityMetadata.columns.find(column => column.mode === "discriminator")!; // todo: fix ! later
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();
}
/**
* 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
const inverseEntityMetadata = entityMetadatas.find(m => m.target === relation.type || (typeof relation.type === "string" && m.targetName === relation.type));
if (!inverseEntityMetadata)
throw new Error("Entity metadata for " + entityMetadata.name + "#" + relation.propertyPath + " was not found. Check if you specified a correct entity object, check its really entity and its connected in the connection options.");
relation.inverseEntityMetadata = inverseEntityMetadata;
relation.inverseSidePropertyPath = relation.buildInverseSidePropertyPath();
// and compute inverse relation and mark if it has such
relation.inverseRelation = inverseEntityMetadata.relations.find(foundRelation => foundRelation.propertyPath === relation.inverseSidePropertyPath)!; // todo: remove ! later
relation.hasInverseSide = !!relation.inverseRelation; // todo: do we really need this flag
});
}
protected createKeysForTableInheritance(entityMetadata: EntityMetadata) {
const indexForKey = new IndexMetadata({
entityMetadata: entityMetadata,
columns: [entityMetadata.discriminatorColumn],
args: {
target: entityMetadata.target,
unique: false
}
});
entityMetadata.indices.push(indexForKey);
const indexForKeyWithPrimary = new IndexMetadata({
entityMetadata: entityMetadata,
columns: [entityMetadata.primaryColumns[0], entityMetadata.discriminatorColumn],
args: {
target: entityMetadata.target,
unique: false
}
});
entityMetadata.indices.push(indexForKeyWithPrimary);
}
}
// generate virtual column with foreign key for class-table inheritance
/*entityMetadatas.forEach(entityMetadata => {
if (!entityMetadata.parentEntityMetadata)
return;
const parentPrimaryColumns = entityMetadata.parentEntityMetadata.primaryColumns;
const parentIdColumns = parentPrimaryColumns.map(primaryColumn => {
const columnName = this.namingStrategy.classTableInheritanceParentColumnName(entityMetadata.parentEntityMetadata.tableName, primaryColumn.propertyName);
const column = new ColumnMetadataBuilder(entityMetadata);
column.type = primaryColumn.type;
column.propertyName = primaryColumn.propertyName; // todo: check why needed
column.givenName = columnName;
column.mode = "parentId";
column.isUnique = true;
column.isNullable = false;
// column.entityTarget = entityMetadata.target;
return column;
});
// add foreign key
const foreignKey = new ForeignKeyMetadataBuilder(
entityMetadata,
parentIdColumns,
entityMetadata.parentEntityMetadata,
parentPrimaryColumns,
"CASCADE"
);
entityMetadata.ownColumns.push(...parentIdColumns);
entityMetadata.foreignKeys.push(foreignKey);
});*/
/*protected createEntityMetadata(metadata: EntityMetadata, options: {
userSpecifiedTableName?: string,
closureOwnerTableName?: string,
}) {
const tableNameUserSpecified = options.userSpecifiedTableName;
const isClosureJunction = metadata.tableType === "closure-junction";
const targetName = metadata.target instanceof Function ? (metadata.target as any).name : metadata.target;
const tableNameWithoutPrefix = isClosureJunction
? this.namingStrategy.closureJunctionTableName(options.closureOwnerTableName!)
: this.namingStrategy.tableName(targetName, options.userSpecifiedTableName);
const tableName = this.namingStrategy.prefixTableName(this.driver.options.tablesPrefix, tableNameWithoutPrefix);
// for virtual tables (like junction table) target is equal to undefined at this moment
// we change this by setting virtual's table name to a target name
// todo: add validation so targets with same schema names won't conflicts with virtual table names
metadata.target = metadata.target ? metadata.target : tableName;
metadata.targetName = targetName;
metadata.givenTableName = tableNameUserSpecified;
metadata.tableNameWithoutPrefix = tableNameWithoutPrefix;
metadata.tableName = tableName;
metadata.name = targetName ? targetName : tableName;
// metadata.namingStrategy = this.namingStrategy;
}*/
/*protected createEntityMetadata(tableArgs: any, argsForTable: any, ): EntityMetadata {
const metadata = new EntityMetadata({
junction: false,
target: tableArgs.target,
tablesPrefix: this.driver.options.tablesPrefix,
namingStrategy: this.namingStrategy,
tableName: argsForTable.name,
tableType: argsForTable.type,
orderBy: argsForTable.orderBy,
engine: argsForTable.engine,
skipSchemaSync: argsForTable.skipSchemaSync,
columnMetadatas: columns,
relationMetadatas: relations,
relationIdMetadatas: relationIds,
relationCountMetadatas: relationCounts,
indexMetadatas: indices,
embeddedMetadatas: embeddeds,
inheritanceType: mergedArgs.inheritance ? mergedArgs.inheritance.type : undefined,
discriminatorValue: discriminatorValueArgs ? discriminatorValueArgs.value : (tableArgs.target as any).name // todo: pass this to naming strategy to generate a name
}, this.lazyRelationsWrapper);
return metadata;
}*/
// const tables = [mergedArgs.table].concat(mergedArgs.children);
// tables.forEach(tableArgs => {
// find embeddable tables for embeddeds registered in this table and create EmbeddedMetadatas from them
// const findEmbeddedsRecursively = (embeddedArgs: EmbeddedMetadataArgs[]) => {
// const embeddeds: EmbeddedMetadata[] = [];
// embeddedArgs.forEach(embedded => {
// const embeddableTable = embeddableMergedArgs.find(embeddedMergedArgs => embeddedMergedArgs.table.target === embedded.type());
// if (embeddableTable) {
// const columns = embeddableTable.columns.toArray().map(args => new ColumnMetadata(args));
// const relations = embeddableTable.relations.toArray().map(args => new RelationMetadata(args));
// const subEmbeddeds = findEmbeddedsRecursively(embeddableTable.embeddeds.toArray());
// embeddeds.push(new EmbeddedMetadata(columns, relations, subEmbeddeds, embedded));
// }
// });
// return embeddeds;
// };
// const embeddeds = findEmbeddedsRecursively(mergedArgs.embeddeds.toArray());
// create metadatas from args
// const argsForTable = mergedArgs.inheritance && mergedArgs.inheritance.type === "single-table" ? mergedArgs.table : tableArgs;
// const table = new TableMetadata(argsForTable);
// const columns = mergedArgs.columns.toArray().map(args => {
//
// // if column's target is a child table then this column should have all nullable columns
// if (mergedArgs.inheritance &&
// mergedArgs.inheritance.type === "single-table" &&
// args.target !== mergedArgs.table.target && !!mergedArgs.children.find(childTable => childTable.target === args.target)) {
// args.options.nullable = true;
// }
// return new ColumnMetadata(args);
// });
// const discriminatorValueArgs = mergedArgs.discriminatorValues.find(discriminatorValueArgs => {
// return discriminatorValueArgs.target === tableArgs.target;
// });
// after all metadatas created we set parent entity metadata for class-table inheritance
// entityMetadatas.forEach(entityMetadata => {
// const mergedArgs = realTables.find(args => args.target === entityMetadata.target);
// if (mergedArgs && mergedArgs.parent) {
// const parentEntityMetadata = entityMetadatas.find(entityMetadata => entityMetadata.target === (mergedArgs!.parent! as any).target); // todo: weird compiler error here, thats why type casing is used
// if (parentEntityMetadata)
// entityMetadata.parentEntityMetadata = parentEntityMetadata;
// }
// });

View File

@ -1,317 +0,0 @@
import {ColumnMetadataArgs} from "../metadata-args/ColumnMetadataArgs";
import {ColumnType} from "../metadata/types/ColumnTypes";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
import {RelationMetadata} from "../metadata/RelationMetadata";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
/**
* Kinda type of the column. Not a type in the database, but locally used type to determine what kind of column
* we are working with.
* For example, "primary" means that it will be a primary column, or "createDate" means that it will create a create
* date column.
*/
export type ColumnMode = "regular"|"virtual"|"createDate"|"updateDate"|"version"|"treeChildrenCount"|"treeLevel"|"discriminator"|"parentId"|"objectId"|"array";
/**
* This metadata contains all information about entity's column.
*/
export class ColumnMetadataBuilder {
// ---------------------------------------------------------------------
// Public Properties
// ---------------------------------------------------------------------
/**
* Entity metadata where this column metadata is.
*/
entityMetadata: EntityMetadata;
/**
* Embedded metadata where this column metadata is.
* If this column is not in embed then this property value is undefined.
*/
embeddedMetadata: EmbeddedMetadata;
/**
* If column is a foreign key of some relation then this relation's metadata will be there.
* If this column does not have a foreign key then this property value is undefined.
*/
relationMetadata: RelationMetadata;
/**
* Column's mode in which this column is working.
*/
mode: ColumnMode;
/**
* Class's property name on which this column is applied.
*/
propertyName: string;
/**
* The database type of the column.
*/
type: ColumnType;
/**
* Type's length in the database.
*/
length: string = "";
/**
* Indicates if this column is a primary key.
*/
isPrimary: boolean = false;
/**
* Indicates if this column is generated (auto increment or generated other way).
*/
isGenerated: boolean = false;
/**
* Indicates if value in the database should be unique or not.
*/
isUnique: boolean = false;
/**
* Indicates if column can contain nulls or not.
*/
isNullable: boolean = false;
/**
* Column comment.
* This feature is not supported by all databases.
*/
comment: string = "";
/**
* Default database value.
*/
default: any;
/**
* The precision for a decimal (exact numeric) column (applies only for decimal column),
* which is the maximum number of digits that are stored for the values.
*/
precision: number;
/**
* The scale for a decimal (exact numeric) column (applies only for decimal column),
* which represents the number of digits to the right of the decimal point and must not be greater than precision.
*/
scale: number;
/**
* Indicates if date column will contain a timezone.
* Used only for date-typed column types.
* Note that timezone option is not supported by all databases (only postgres for now).
*/
timezone: boolean;
/**
* Indicates if date object must be stored in given date's timezone.
* By default date is saved in UTC timezone.
* Works only with "datetime" columns.
*/
localTimezone?: boolean;
/**
* Indicates if column's type will be set as a fixed-length data type.
* Works only with "string" columns.
*/
fixedLength?: boolean;
/**
* User's specified custom column name.
*/
givenName: string;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(entityMetadata: EntityMetadata, args?: ColumnMetadataArgs) {
this.entityMetadata = entityMetadata;
// this.entityTarget = entityMetadata.target;
if (args) {
this.propertyName = args.propertyName;
if (args.mode)
this.mode = args.mode;
if (args.options.name)
this.givenName = args.options.name;
if (args.options.type)
this.type = args.options.type;
if (args.options.length)
this.length = String(args.options.length);
if (args.options.primary)
this.isPrimary = args.options.primary;
if (args.options.generated)
this.isGenerated = args.options.generated;
if (args.options.unique)
this.isUnique = args.options.unique;
if (args.options.nullable)
this.isNullable = args.options.nullable;
if (args.options.comment)
this.comment = args.options.comment;
if (args.options.default !== undefined && args.options.default !== null)
this.default = args.options.default;
if (args.options.scale)
this.scale = args.options.scale;
if (args.options.precision)
this.precision = args.options.precision;
if (args.options.timezone)
this.timezone = args.options.timezone;
if (args.options.localTimezone)
this.localTimezone = args.options.localTimezone;
if (args.options.fixedLength)
this.fixedLength = args.options.fixedLength;
}
}
// ---------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------
/**
* Gets column's entity target.
* Original target returns target of the class where column is.
* This class can be an abstract class, but column even is from that class,
* but its more related to a specific entity. That's why we need this field.
*/
get entityTarget(): Function|string {
return this.entityMetadata.target;
}
/**
* Gets full path to this column property (including column property name).
* Full path is relevant when column is used in embeds (one or multiple nested).
* For example it will return "counters.subcounters.likes".
* If property is not in embeds then it returns just property name of the column.
*
* @stable
*/
get propertyPath(): string {
if (!this.embeddedMetadata || !this.embeddedMetadata.parentPropertyNames.length)
return this.propertyName;
return this.embeddedMetadata.parentPropertyNames.join(".") + "." + this.propertyName;
}
/**
* Complete column name in the database including its embedded prefixes.
*/
get databaseName(): string {
// if this column is embedded's column then apply different entity
if (this.embeddedMetadata) {
// because embedded can be inside other embedded we need to go recursively and collect all prefix name
const prefixes: string[] = [];
const buildPrefixRecursively = (embeddedMetadata: EmbeddedMetadata) => {
if (embeddedMetadata.parentEmbeddedMetadata)
buildPrefixRecursively(embeddedMetadata.parentEmbeddedMetadata);
prefixes.push(embeddedMetadata.prefix);
};
buildPrefixRecursively(this.embeddedMetadata);
return (this as any).namingStrategy.embeddedColumnName(prefixes, this.propertyName, this.givenName);
}
// if there is a naming strategy then use it to normalize propertyName as column name
if (this.entityMetadata)
return (this as any).namingStrategy.columnName(this.propertyName, this.givenName);
return this.givenName;
// throw new Error(`Column ${this._name ? this._name + " " : ""}is not attached to any entity or embedded.`);
}
/**
* Indicates if column is virtual. Virtual columns are not mapped to the entity.
*/
get isVirtual() {
return this.mode === "virtual";
}
/**
* Indicates if column is a parent id. Parent id columns are not mapped to the entity.
*/
get isParentId() {
return this.mode === "parentId";
}
/**
* Indicates if column is discriminator. Discriminator columns are not mapped to the entity.
*/
get isDiscriminator() {
return this.mode === "discriminator";
}
/**
* Indicates if this column contains an entity creation date.
*/
get isCreateDate() {
return this.mode === "createDate";
}
/**
* Indicates if this column contains an entity update date.
*/
get isUpdateDate() {
return this.mode === "updateDate";
}
/**
* Indicates if this column contains an entity version.
*/
get isVersion() {
return this.mode === "version";
}
/**
* Indicates if this column contains an object id.
*/
get isObjectId() {
return this.mode === "objectId";
}
/**
* If this column is foreign key then it references some other column,
* and this property will contain reference to this column.
*/
get referencedColumn(): ColumnMetadata|undefined {
const foreignKeys = this.relationMetadata ? this.relationMetadata.foreignKeys : this.entityMetadata.foreignKeys;
const foreignKey = foreignKeys.find(foreignKey => foreignKey.columns.indexOf(this as any) !== -1);
if (foreignKey) {
const columnIndex = foreignKey.columns.indexOf(this as any);
return foreignKey.referencedColumns[columnIndex];
}
return undefined!;
}
// ---------------------------------------------------------------------
// Public Methods
// ---------------------------------------------------------------------
build(options: {
namingStrategy: NamingStrategyInterface,
entityMetadata: EntityMetadata,
userSpecifiedName: string,
propertyName: string,
propertyPath: string,
}) {
this.entityMetadata = options.entityMetadata;
// this.entityTarget = options.entityMetadata.target;
this.propertyName = options.propertyName;
// this.name = options.namingStrategy.columnName(options.propertyName, options.userSpecifiedName);
// return this;
}
}

View File

@ -1,170 +0,0 @@
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {EmbeddedMetadataArgs} from "../metadata-args/EmbeddedMetadataArgs";
import {RelationMetadata} from "../metadata/RelationMetadata";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
/**
* Contains all information about entity's embedded property.
*/
export class EmbeddedMetadataBuilder {
// ---------------------------------------------------------------------
// Public Properties
// ---------------------------------------------------------------------
/**
* Entity metadata where this embedded is.
*/
entityMetadata: EntityMetadata;
/**
* Parent embedded in the case if this embedded inside other embedded.
*/
parentEmbeddedMetadata: EmbeddedMetadata;
/**
* Property name on which this embedded is attached.
*/
propertyName: string;
/**
* Columns inside this embed.
*/
columns: ColumnMetadata[];
/**
* Relations inside this embed.
*/
relations: RelationMetadata[];
/**
* Nested embeddable in this embeddable (which has current embedded as parent embedded).
*/
embeddeds: EmbeddedMetadata[];
/**
* Embedded target type.
*/
type?: Function;
/**
* Indicates if this embedded is in array mode.
*
* This option works only in monogodb.
*/
isArray: boolean;
/**
* Prefix of the embedded, used instead of propertyName.
* If set to empty string, then prefix is not set at all.
*/
customPrefix: string|boolean|undefined;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(entityMetadata: EntityMetadata,
columns: ColumnMetadata[],
relations: RelationMetadata[],
embeddeds: EmbeddedMetadata[],
args: EmbeddedMetadataArgs) {
this.entityMetadata = entityMetadata;
this.type = args.type ? args.type() : undefined;
this.propertyName = args.propertyName;
this.isArray = args.isArray;
this.customPrefix = args.prefix;
this.columns = columns;
this.relations = relations;
this.embeddeds = embeddeds;
this.embeddeds.forEach(embedded => {
// embedded.parentEmbeddedMetadata = this;
});
this.columns.forEach(column => {
// column.embeddedMetadata = this;
});
this.relations.forEach(relation => {
// relation.embeddedMetadata = this;
});
}
// ---------------------------------------------------------------------
// Public Methods
// ---------------------------------------------------------------------
/**
* Creates a new embedded object.
*
* @stable
*/
create() {
return new (this.type as any);
}
/**
* Gets the prefix of the columns.
* By default its a property name of the class where this prefix is.
* But if custom prefix is set then it takes its value as a prefix.
* However if custom prefix is set to empty string prefix to column is not applied at all.
*/
get prefix(): string {
let prefixes: string[] = [];
if (this.parentEmbeddedMetadata)
prefixes.push(this.parentEmbeddedMetadata.prefix);
if (this.customPrefix === undefined) {
prefixes.push(this.propertyName);
} else if (typeof this.customPrefix === "string") {
prefixes.push(this.customPrefix);
}
return prefixes.join("_"); // todo: use naming strategy instead of "_" !!!
}
/**
* Returns array of property names of current embed and all its parent embeds.
*
* example: post[data][information][counters].id where "data", "information" and "counters" are embeds
* we need to get value of "id" column from the post real entity object.
* this method will return ["data", "information", "counters"]
*
* @stable just need move to builder process
*/
get parentPropertyNames(): string[] {
return this.parentEmbeddedMetadata ? this.parentEmbeddedMetadata.parentPropertyNames.concat(this.propertyName) : [this.propertyName];
}
/**
* Returns embed metadatas from all levels of the parent tree.
*
* example: post[data][information][counters].id where "data", "information" and "counters" are embeds
* this method will return [embed metadata of data, embed metadata of information, embed metadata of counters]
*
* @stable just need move to builder process
*/
get embeddedMetadataTree(): EmbeddedMetadata[] {
return [];
// return this.parentEmbeddedMetadata ? this.parentEmbeddedMetadata.embeddedMetadataTree.concat(this as any) : [this];
}
/**
* Returns all columns of this embed and all columns from its child embeds.
*
* @stable just need move to builder process
*/
get columnsFromTree(): ColumnMetadata[] {
return this.embeddeds.reduce((columns, embedded) => columns.concat(embedded.columnsFromTree), this.columns);
}
/**
* Returns all relations of this embed and all relations from its child embeds.
*
* @stable just need move to builder process
*/
get relationsFromTree(): RelationMetadata[] {
return this.embeddeds.reduce((relations, embedded) => relations.concat(embedded.relationsFromTree), this.relations);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,99 +0,0 @@
import {EntityMetadata} from "../metadata/EntityMetadata";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {OnDeleteType} from "../metadata/ForeignKeyMetadata";
/**
* Contains all information about entity's foreign key.
*/
export class ForeignKeyMetadataBuilder {
// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------
/**
* Entity metadata where this foreign key is.
*/
entityMetadata: EntityMetadata;
/**
* Entity metadata which this foreign key is references.
*/
referencedEntityMetadata: EntityMetadata;
// -------------------------------------------------------------------------
// Public Readonly Properties
// -------------------------------------------------------------------------
/**
* Array of columns of this foreign key.
*/
readonly columns: ColumnMetadata[];
/**
* Array of referenced columns.
*/
readonly referencedColumns: ColumnMetadata[];
/**
* What to do with a relation on deletion of the row containing a foreign key.
*/
readonly onDelete: OnDeleteType;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(entityMetadata: EntityMetadata,
columns: ColumnMetadata[],
referencedEntityMetadata: EntityMetadata,
referencedColumns: ColumnMetadata[],
onDelete?: OnDeleteType) {
this.entityMetadata = entityMetadata;
this.columns = columns;
this.referencedEntityMetadata = referencedEntityMetadata;
this.referencedColumns = referencedColumns;
if (onDelete)
this.onDelete = onDelete;
}
// -------------------------------------------------------------------------
// Accessors
// -------------------------------------------------------------------------
/**
* Gets the table name to which this foreign key is applied.
*/
get tableName() {
return this.entityMetadata.tableName;
}
/**
* Gets the table name to which this foreign key is referenced.
*/
get referencedTableName() {
return this.referencedEntityMetadata.tableName;
}
/**
* Gets foreign key name.
*/
get name() {
return (this as any).namingStrategy.foreignKeyName(this.tableName, this.columnNames, this.referencedEntityMetadata.tableName, this.referencedColumnNames);
}
/**
* Gets array of column names.
*/
get columnNames(): string[] {
return this.columns.map(column => column.databaseName);
}
/**
* Gets array of referenced column names.
*/
get referencedColumnNames(): string[] {
return this.referencedColumns.map(column => column.databaseName);
}
}

View File

@ -1,143 +0,0 @@
import {IndexMetadataArgs} from "../metadata-args/IndexMetadataArgs";
import {EntityMetadata} from "../metadata/EntityMetadata";
/**
* Index metadata contains all information about table's index.
*/
export class IndexMetadataBuilder {
// ---------------------------------------------------------------------
// Public Properties
// ---------------------------------------------------------------------
/**
* Entity metadata of the class to which this index is applied.
*/
entityMetadata: EntityMetadata;
// ---------------------------------------------------------------------
// Readonly Properties
// ---------------------------------------------------------------------
/**
* Indicates if this index must be unique.
*/
readonly isUnique: boolean;
/**
* Target class to which metadata is applied.
*/
readonly target?: Function|string;
// ---------------------------------------------------------------------
// Private Properties
// ---------------------------------------------------------------------
/**
* Composite index name.
*/
private readonly _name: string|undefined;
/**
* Columns combination to be used as index.
*/
private readonly _columns: ((object?: any) => (any[]|{ [key: string]: number }))|string[];
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(entityMetadata: EntityMetadata, args: IndexMetadataArgs) {
this.entityMetadata = entityMetadata;
this.target = args.target;
// this._columns = args.columns;
this._name = args.name;
this.isUnique = args.unique;
}
// ---------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------
/**
* Gets index's name.
*/
get name() {
return (this as any).namingStrategy.indexName(this._name, this.entityMetadata.tableName, this.columns);
}
/**
* Gets the table name on which index is applied.
*/
get tableName() {
return this.entityMetadata.tableName;
}
/**
* Gets the column names which are in this index.
*/
get columns(): string[] {
// if columns already an array of string then simply return it
let columnPropertyNames: string[] = [];
if (this._columns instanceof Array) {
columnPropertyNames = this._columns;
} else {
// if columns is a function that returns array of field names then execute it and get columns names from it
const columnsFnResult = this._columns(this.entityMetadata.propertiesMap);
const columnsNamesFromFnResult = columnsFnResult instanceof Array ? columnsFnResult : Object.keys(columnsFnResult);
columnPropertyNames = columnsNamesFromFnResult.map((i: any) => String(i));
}
const columns = this.entityMetadata.columns.filter(column => columnPropertyNames.indexOf(column.propertyPath) !== -1);
this.entityMetadata.relations
.filter(relation => relation.isWithJoinColumn && columnPropertyNames.indexOf(relation.propertyName) !== -1)
.forEach(relation => columns.push(...relation.joinColumns));
// todo: better to extract all validation into single place if possible
const missingColumnNames = columnPropertyNames.filter(columnPropertyName => {
return !this.entityMetadata.columns.find(column => column.propertyPath === columnPropertyName) &&
!this.entityMetadata.relations.find(relation => relation.isWithJoinColumn && columnPropertyNames.indexOf(relation.propertyName) !== -1);
});
if (missingColumnNames.length > 0) {
// console.log(this.entityMetadata.columns);
throw new Error(`Index ${this._name ? "\"" + this._name + "\" " : ""}contains columns that are missing in the entity: ` + missingColumnNames.join(", "));
}
return columns.map(column => column.databaseName);
}
/**
* Builds columns as a map of values where column name is key of object and value is a value provided by
* function or default value given to this function.
*/
buildColumnsAsMap(defaultValue = 0): { [key: string]: number } {
const map: { [key: string]: number } = {};
// if columns already an array of string then simply create a map from it
if (this._columns instanceof Array) {
this._columns.forEach(columnName => map[columnName] = defaultValue);
} else {
// if columns is a function that returns array of field names then execute it and get columns names from it
const columnsFnResult = this._columns(this.entityMetadata.propertiesMap);
if (columnsFnResult instanceof Array) {
columnsFnResult.forEach(columnName => map[columnName] = defaultValue);
} else {
Object.keys(columnsFnResult).forEach(columnName => map[columnName] = columnsFnResult[columnName]);
}
}
// replace each propertyNames with column names
return Object.keys(map).reduce((updatedMap, key) => {
const column = this.entityMetadata.columns.find(column => column.propertyName === key);
if (!column)
throw new Error(`Index ${this._name ? "\"" + this._name + "\" " : ""}contains columns that are missing in the entity: ${key}`);
updatedMap[column.databaseName] = map[key];
return updatedMap;
}, {} as { [key: string]: number });
}
}

View File

@ -1,78 +0,0 @@
import {RelationCountMetadataArgs} from "../metadata-args/RelationCountMetadataArgs";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {RelationMetadata} from "../metadata/RelationMetadata";
/**
* Contains all information about entity's relation count.
*/
export class RelationCountMetadataBuilder {
// ---------------------------------------------------------------------
// Public Properties
// ---------------------------------------------------------------------
/**
* Entity metadata where this column metadata is.
*/
entityMetadata: EntityMetadata;
// ---------------------------------------------------------------------
// Readonly Properties
// ---------------------------------------------------------------------
/**
* Relation name which need to count.
*/
readonly relationNameOrFactory: string|((object: any) => any);
/**
* Target class to which metadata is applied.
*/
readonly target: Function|string;
/**
* Target's property name to which this metadata is applied.
*/
readonly propertyName: string;
/**
* Alias of the joined (destination) table.
*/
readonly alias?: string;
/**
* Extra condition applied to "ON" section of join.
*/
readonly queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(entityMetadata: EntityMetadata, args: RelationCountMetadataArgs) {
this.entityMetadata = entityMetadata;
this.target = args.target;
this.propertyName = args.propertyName;
this.relationNameOrFactory = args.relation;
this.alias = args.alias;
this.queryBuilderFactory = args.queryBuilderFactory;
}
// ---------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------
/**
* Relation which need to count.
*/
get relation(): RelationMetadata {
const propertyName = this.relationNameOrFactory instanceof Function ? this.relationNameOrFactory(this.entityMetadata.propertiesMap) : this.relationNameOrFactory;
const relation = this.entityMetadata.relations.find(relation => relation.propertyName === propertyName);
if (!relation)
throw new Error(`Cannot find relation ${propertyName}. Wrong relation specified for @RelationCount decorator.`);
return relation;
}
}

View File

@ -1,78 +0,0 @@
import {RelationIdMetadataArgs} from "../metadata-args/RelationIdMetadataArgs";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {RelationMetadata} from "../metadata/RelationMetadata";
/**
* Contains all information about entity's relation count.
*/
export class RelationIdMetadataBuilder {
// ---------------------------------------------------------------------
// Public Properties
// ---------------------------------------------------------------------
/**
* Entity metadata where this column metadata is.
*/
entityMetadata: EntityMetadata;
// ---------------------------------------------------------------------
// Readonly Properties
// ---------------------------------------------------------------------
/**
* Relation name which need to count.
*/
readonly relationNameOrFactory: string|((object: any) => any);
/**
* Target class to which metadata is applied.
*/
readonly target: Function|string;
/**
* Target's property name to which this metadata is applied.
*/
readonly propertyName: string;
/**
* Alias of the joined (destination) table.
*/
readonly alias?: string;
/**
* Extra condition applied to "ON" section of join.
*/
readonly queryBuilderFactory?: (qb: QueryBuilder<any>) => QueryBuilder<any>;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(entityMetadata: EntityMetadata, args: RelationIdMetadataArgs) {
this.entityMetadata = entityMetadata;
this.target = args.target;
this.propertyName = args.propertyName;
this.relationNameOrFactory = args.relation;
this.alias = args.alias;
this.queryBuilderFactory = args.queryBuilderFactory;
}
// ---------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------
/**
* Relation which need to count.
*/
get relation(): RelationMetadata {
const propertyName = this.relationNameOrFactory instanceof Function ? this.relationNameOrFactory(this.entityMetadata.propertiesMap) : this.relationNameOrFactory;
const relation = this.entityMetadata.relations.find(relation => relation.propertyName === propertyName);
if (!relation)
throw new Error(`Cannot find relation ${propertyName}. Wrong relation specified for @RelationId decorator.`);
return relation;
}
}

View File

@ -2,9 +2,6 @@ import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterfac
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:

View File

@ -1,370 +0,0 @@
import {PropertyTypeInFunction, RelationMetadata, RelationTypeInFunction} from "../metadata/RelationMetadata";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
import {ForeignKeyMetadata, OnDeleteType} from "../metadata/ForeignKeyMetadata";
import {RelationType} from "../metadata/types/RelationTypes";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
/**
* Contains all information about some entity's relation.
*/
export class RelationMetadataBuilder {
// ---------------------------------------------------------------------
// Public Properties
// ---------------------------------------------------------------------
/**
* Its own entity metadata.
*/
entityMetadata: EntityMetadata;
/**
* Embedded metadata where this relation is.
* If this relation is not in embed then this property value is undefined.
*/
embeddedMetadata: EmbeddedMetadata;
/**
* Related entity metadata.
*/
inverseEntityMetadata: EntityMetadata;
/**
* Junction entity metadata.
*/
junctionEntityMetadata: EntityMetadata;
foreignKeys: ForeignKeyMetadata[] = [];
// ---------------------------------------------------------------------
// Readonly Properties
// ---------------------------------------------------------------------
/**
* Target class to which metadata is applied.
*/
target: Function|string;
/**
* Gets relation's entity target.
* Original target returns target of the class where relation is.
* This class can be an abstract class, but relation even is from that class,
* but its more related to a specific entity. That's why we need this field.
*
* Note: this property is available only after relation metadata complete build
*/
entityTarget: Function|string;
/**
* Target's property name to which this metadata is applied.
*/
propertyName: string;
/**
* Indicates if this is a parent (can be only many-to-one relation) relation in the tree tables.
*/
isTreeParent: boolean = false;
/**
* Indicates if this is a children (can be only one-to-many relation) relation in the tree tables.
*/
isTreeChildren: boolean = false;
/**
* Relation type.
*/
relationType: RelationType;
/**
* Indicates if this relation will be a primary key.
* Can be used only for many-to-one and owner one-to-one relations.
*/
isPrimary: boolean;
/**
* Indicates if this relation will be lazily loaded.
*/
isLazy: boolean;
/**
* If set to true then it means that related object can be allowed to be inserted to the db.
*/
isCascadeInsert: boolean;
/**
* If set to true then it means that related object can be allowed to be updated in the db.
*/
isCascadeUpdate: boolean;
/**
* If set to true then it means that related object can be allowed to be remove from the db.
*/
isCascadeRemove: boolean;
/**
* Indicates if relation column value can be nullable or not.
*/
isNullable: boolean = true;
/**
* What to do with a relation on deletion of the row containing a foreign key.
*/
onDelete: OnDeleteType;
/**
* The real reflected property type.
*/
// propertyType: any;
/**
* Join table name.
*/
joinTableName: string;
// ---------------------------------------------------------------------
// Private Properties
// ---------------------------------------------------------------------
/**
* The type of the field.
*/
private _type: RelationTypeInFunction;
/**
* Inverse side of the relation.
*/
private _inverseSideProperty: PropertyTypeInFunction<any>;
// ---------------------------------------------------------------------
// Private Properties
// ---------------------------------------------------------------------
private relationMetadata: RelationMetadata;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor() {
this.relationMetadata = new RelationMetadata({} as any);
}
// ---------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------
/**
* Gets full path to this column property (including relation name).
* Full path is relevant when column is used in embeds (one or multiple nested).
* For example it will return "counters.subcounters.likes".
* If property is not in embeds then it returns just property name of the column.
*/
get propertyPath(): string {
if (!this.embeddedMetadata || !this.embeddedMetadata.parentPropertyNames.length)
return this.propertyName;
return this.embeddedMetadata.parentPropertyNames.join(".") + "." + this.propertyName;
}
/**
* Join table columns.
*/
get joinColumns(): ColumnMetadata[] {
if (!this.isOwning)
throw new Error(`Inverse join columns are only supported from owning side`);
return this.foreignKeys[0].columns;
}
/**
* Join table columns.
*/
get inverseJoinColumns(): ColumnMetadata[] {
if (!this.isOwning)
throw new Error(`Inverse join columns are only supported from owning side`);
if (!this.isManyToMany)
throw new Error(`Inverse join columns are not supported by non-many-to-many relations`);
return this.foreignKeys[1].columns;
}
/**
* Gets the property's type to which this relation is applied.
*/
get type(): Function|string { // todo: when this can be a string?
return this._type instanceof Function ? (this._type as () => any)() : this._type;
}
/**
* Indicates if this side is an owner of this relation.
*/
get isOwning() {
return !!(this.isManyToOne ||
(this.isManyToMany && this.foreignKeys.length > 0) ||
(this.isOneToOne && this.foreignKeys.length > 0));
}
/**
* Checks if this relation's type is "one-to-one".
*/
get isOneToOne(): boolean {
return this.relationType === "one-to-one";
}
/**
* Checks if this relation is owner side of the "one-to-one" relation.
* Owner side means this side of relation has a join column in the table.
*/
get isOneToOneOwner(): boolean {
return this.isOneToOne && this.isOwning;
}
/**
* Checks if this relation has a join column (e.g. is it many-to-one or one-to-one owner side).
*/
get isWithJoinColumn(): boolean {
return this.isManyToOne || this.isOneToOneOwner;
}
/**
* Checks if this relation is NOT owner side of the "one-to-one" relation.
* NOT owner side means this side of relation does not have a join column in the table.
*/
get isOneToOneNotOwner(): boolean {
return this.isOneToOne && !this.isOwning;
}
/**
* Checks if this relation's type is "one-to-many".
*/
get isOneToMany(): boolean {
return this.relationType === "one-to-many";
}
/**
* Checks if this relation's type is "many-to-one".
*/
get isManyToOne(): boolean {
return this.relationType === "many-to-one";
}
/**
* Checks if this relation's type is "many-to-many".
*/
get isManyToMany(): boolean {
return this.relationType === "many-to-many";
}
/**
* Checks if this relation's type is "many-to-many", and is owner side of the relationship.
* Owner side means this side of relation has a join table.
*/
get isManyToManyOwner(): boolean {
return this.isManyToMany && this.isOwning;
}
/**
* Checks if this relation's type is "many-to-many", and is NOT owner side of the relationship.
* Not owner side means this side of relation does not have a join table.
*/
get isManyToManyNotOwner(): boolean {
return this.isManyToMany && !this.isOwning;
}
/**
* Checks if inverse side is specified by a relation.
*/
get hasInverseSide(): boolean {
return this.inverseEntityMetadata && this.inverseEntityMetadata.hasRelationWithPropertyPath(this.inverseSidePropertyPath);
}
/**
* Gets the property name of the inverse side of the relation.
*/
get inverseSidePropertyPath(): string { // todo: should be called inverseSidePropertyName ?
if (this._inverseSideProperty) {
return this.computeInverseSide(this._inverseSideProperty);
} else if (this.isTreeParent && this.entityMetadata.treeChildrenRelation) {
return this.entityMetadata.treeChildrenRelation.propertyName;
} else if (this.isTreeChildren && this.entityMetadata.treeParentRelation) {
return this.entityMetadata.treeParentRelation.propertyName;
}
return "";
}
/**
* Gets the relation metadata of the inverse side of this relation.
*/
get inverseRelation(): RelationMetadata {
const relation = this.inverseEntityMetadata.findRelationWithPropertyPath(this.inverseSidePropertyPath);
if (!relation)
throw new Error(`Inverse side was not found in the relation ${this.entityMetadata.name}#${this.inverseSidePropertyPath}`);
return relation;
}
// ---------------------------------------------------------------------
// Public Methods
// ---------------------------------------------------------------------
build(): RelationMetadata {
const metadata = this.relationMetadata;
metadata.relationType = this.relationType;
metadata.entityMetadata = this.entityMetadata;
metadata.inverseEntityMetadata = this.inverseEntityMetadata;
metadata.junctionEntityMetadata = this.junctionEntityMetadata;
metadata.embeddedMetadata = this.embeddedMetadata;
metadata.foreignKeys = this.foreignKeys;
// metadata.entityTarget = this.entityMetadata.target;
metadata.propertyPath = this.propertyPath;
metadata.joinColumns = this.joinColumns;
metadata.inverseJoinColumns = this.inverseJoinColumns;
metadata.type = this.type;
metadata.isOwning = this.isOwning;
metadata.isOneToOne = this.isOneToOne;
metadata.isOneToOneOwner = this.isOneToOneOwner;
metadata.isWithJoinColumn = this.isWithJoinColumn;
metadata.isOneToMany = this.isOneToMany;
metadata.isManyToOne = this.isManyToOne;
metadata.isManyToMany = this.isManyToMany;
metadata.isManyToManyOwner = this.isManyToManyOwner;
metadata.hasInverseSide = this.hasInverseSide;
metadata.propertyPath = this.propertyPath;
metadata.inverseSidePropertyPath = this.inverseSidePropertyPath;
metadata.inverseRelation = this.inverseRelation;
if (this.junctionEntityMetadata) {
metadata.junctionEntityMetadata = this.junctionEntityMetadata;
metadata.joinTableName = this.junctionEntityMetadata.tableName;
}
return metadata;
}
// ---------------------------------------------------------------------
// Protected Methods
// ---------------------------------------------------------------------
/**
* Inverse side set in the relation can be either string - property name of the column on inverse side,
* either can be a function that accepts a map of properties with the object and returns one of them.
* Second approach is used to achieve type-safety.
*/
protected computeInverseSide(inverseSide: PropertyTypeInFunction<any>): string {
const ownerEntityPropertiesMap = this.inverseEntityMetadata.propertiesMap;
if (typeof inverseSide === "function")
return (<Function> inverseSide)(ownerEntityPropertiesMap);
if (typeof inverseSide === "string")
return <string> inverseSide;
// throw new Error("Cannot compute inverse side of the relation");
return "";
}
}