added indices generation to single-table inheritance, added validation and default nullable columns

This commit is contained in:
Umed Khudoiberdiev 2016-09-11 22:14:37 +05:00
parent 036d7016d1
commit cf2d6f5f9b
15 changed files with 79 additions and 19 deletions

View File

@ -5,7 +5,7 @@ import {ChildTable} from "../../../src/decorator/tables/ChildTable";
@ChildTable()
export class Employee extends Person {
@Column({ nullable: true })
@Column()
salary: number;
}

View File

@ -7,7 +7,7 @@ import {ChildTable} from "../../../src/decorator/tables/ChildTable";
@DiscriminatorValue("home-sitter") // can be omitted
export class Homesitter extends Person {
@Column({ nullable: true })
@Column()
numberOfKids: number;
}

View File

@ -6,10 +6,9 @@ import {PrimaryColumn} from "../../../src/decorator/columns/PrimaryColumn";
// todo: some things left to do:
// * check how it works when is join (conditions are not added in the joins right now)
// * add key for discriminator column
@Table()
@TableInheritance("single-table") // also can be a class-table
@TableInheritance("single-table")
@DiscriminatorColumn({ name: "type", type: "string"})
export abstract class Person {

View File

@ -5,7 +5,7 @@ import {ChildTable} from "../../../src/decorator/tables/ChildTable";
@ChildTable()
export class Student extends Person {
@Column({ nullable: true })
@Column()
faculty: string;
}

View File

@ -18,6 +18,7 @@ export function DiscriminatorColumn(discriminatorOptions: { name: string, type:
const args: ColumnMetadataArgs = {
target: target,
mode: "discriminator",
propertyName: discriminatorOptions.name,
options: options
};
getMetadataArgsStorage().columns.add(args);

View File

@ -40,7 +40,7 @@ export interface ColumnOptions {
/**
* Indicates if column's value can be set to NULL.
*/
readonly nullable?: boolean;
nullable?: boolean;
/**
* Extra column definition. Should be used only in emergency situations. Note that if you'll use this property

View File

@ -11,6 +11,7 @@ import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
*/
export interface EntityMetadataArgs {
readonly inheritanceType?: "single-table"|"class-table";
readonly discriminatorValue?: string;
readonly namingStrategy: NamingStrategyInterface;
readonly tableMetadata: TableMetadata;

View File

@ -7,7 +7,7 @@ export interface IndexMetadataArgs {
/**
* Class to which index is applied.
*/
readonly target?: Function;
readonly target?: Function|string;
/**
* Index name.

View File

@ -199,7 +199,17 @@ export class EntityMetadataBuilder {
// create metadatas from args
const table = new TableMetadata(mergedArgs.table);
const columns = mergedArgs.columns.map(args => new ColumnMetadata(args));
const columns = mergedArgs.columns.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 relations = mergedArgs.relations.map(args => new RelationMetadata(args));
const indices = mergedArgs.indices.map(args => new IndexMetadata(args));
const discriminatorValueArgs = mergedArgs.discriminatorValues.find(discriminatorValueArgs => {
@ -214,6 +224,7 @@ export class EntityMetadataBuilder {
relationMetadatas: relations,
indexMetadatas: indices,
embeddedMetadatas: embeddeds,
inheritanceType: mergedArgs.inheritance ? mergedArgs.inheritance.type : undefined,
discriminatorValue: discriminatorValueArgs ? discriminatorValueArgs.value : (tableArgs.target as any).name
}, lazyRelationsWrapper);
entityMetadatas.push(entityMetadata);
@ -373,6 +384,27 @@ export class EntityMetadataBuilder {
});
});
// generate keys for tables with single-table inheritance
entityMetadatas
.filter(metadata => metadata.inheritanceType === "single-table" && metadata.hasDiscriminatorColumn)
.forEach(metadata => {
const indexForKey = new IndexMetadata({
target: metadata.target,
columns: [metadata.discriminatorColumn.name],
unique: false
});
indexForKey.entityMetadata = metadata;
metadata.indices.push(indexForKey);
const indexForKeyWithPrimary = new IndexMetadata({
target: metadata.target,
columns: [metadata.firstPrimaryColumn.propertyName, metadata.discriminatorColumn.propertyName],
unique: false
});
indexForKeyWithPrimary.entityMetadata = metadata;
metadata.indices.push(indexForKeyWithPrimary);
});
return entityMetadatas;
}

View File

@ -22,18 +22,35 @@ export class EntityMetadataValidator {
* Validates all given entity metadatas.
*/
validateMany(entityMetadatas: EntityMetadata[]) {
entityMetadatas.forEach(entityMetadata => this.validate(entityMetadata));
entityMetadatas.forEach(entityMetadata => this.validate(entityMetadata, entityMetadatas));
}
/**
* Validates given entity metadata.
*/
validate(entityMetadata: EntityMetadata) {
validate(entityMetadata: EntityMetadata, allEntityMetadatas: EntityMetadata[]) {
// check if table metadata has an id
if (!entityMetadata.primaryColumns.length)
throw new MissingPrimaryColumnError(entityMetadata);
// validate if table is using inheritance it has a discriminator
// also validate if discriminator values are not empty and not repeated
if (entityMetadata.inheritanceType === "single-table") {
if (!entityMetadata.hasDiscriminatorColumn)
throw new Error(`Entity ${entityMetadata.name} using single-table inheritance, it should also have a discriminator column. Did you forget to put @DiscriminatorColumn decorator?`);
if (["", undefined, null].indexOf(entityMetadata.discriminatorValue) !== -1)
throw new Error(`Entity ${entityMetadata.name} has empty discriminator value. Discriminator value should not be empty.`);
const sameDiscriminatorValueEntityMetadata = allEntityMetadatas.find(metadata => {
return metadata !== entityMetadata && metadata.discriminatorValue === entityMetadata.discriminatorValue;
});
if (sameDiscriminatorValueEntityMetadata)
throw new Error(`Entities ${entityMetadata.name} and ${sameDiscriminatorValueEntityMetadata.name} as equal discriminator values. Make sure their discriminator values are not equal using @DiscriminatorValue decorator.`);
}
// validate relations
entityMetadata.relations.forEach(relation => {
// check join tables:

View File

@ -129,7 +129,7 @@ export class ColumnMetadata extends PropertyMetadata {
// ---------------------------------------------------------------------
constructor(args: ColumnMetadataArgs) {
super(undefined, args.propertyName);
super(args.target, args.propertyName);
if (args.mode)
this.mode = args.mode;
@ -186,10 +186,6 @@ export class ColumnMetadata extends PropertyMetadata {
throw new Error(`Column ${this._name ? this._name + " " : ""}is not attached to any entity or embedded.`);
}
get target() {
return this.entityMetadata.target;
}
/**
* Indicates if this column is in embedded, not directly in the table.
*/
@ -204,6 +200,13 @@ export class ColumnMetadata extends PropertyMetadata {
return this.mode === "virtual";
}
/**
* 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.
*/

View File

@ -64,6 +64,12 @@ export class EntityMetadata {
*/
readonly embeddeds: EmbeddedMetadata[];
/**
* If this entity metadata's table using one of the inheritance patterns,
* then this will contain what pattern it uses.
*/
readonly inheritanceType?: "single-table"|"class-table";
/**
* If this entity metadata is a child table of some table, it should have a discriminator value.
* Used to store a value in a discriminator column.
@ -95,6 +101,7 @@ export class EntityMetadata {
this.foreignKeys = args.foreignKeyMetadatas || [];
this.embeddeds = args.embeddedMetadatas || [];
this.discriminatorValue = args.discriminatorValue;
this.inheritanceType = args.inheritanceType;
this.table.entityMetadata = this;
this._columns.forEach(column => column.entityMetadata = this);

View File

@ -455,7 +455,7 @@ export class EntityPersistOperationBuilder {
private diffColumns(metadata: EntityMetadata, newEntity: any, dbEntity: any) {
return metadata.columns
.filter(column => !column.isVirtual && !column.isUpdateDate && !column.isVersion && !column.isCreateDate)
.filter(column => !column.isVirtual && !column.isDiscriminator && !column.isUpdateDate && !column.isVersion && !column.isCreateDate)
.filter(column => column.getEntityValue(newEntity) !== column.getEntityValue(dbEntity))
.filter(column => {
// filter out "relational columns" only in the case if there is a relation object in entity

View File

@ -450,7 +450,7 @@ export class PersistOperationExecutor {
const metadata = this.entityMetadatas.findByTarget(operation.target);
const columns = metadata.columns
.filter(column => !column.isVirtual && column.hasEntityValue(entity));
.filter(column => !column.isVirtual && !column.isDiscriminator && column.hasEntityValue(entity));
const columnNames = columns.map(column => column.name);
const values = columns.map(column => this.driver.preparePersistentValue(column.getEntityValue(entity), column));

View File

@ -84,7 +84,7 @@ export class RawSqlResultsToEntityTransformer {
metadata.columns.forEach(column => {
const columnName = column.name;
const valueInObject = rawSqlResults[0][alias.name + "_" + columnName]; // we use zero index since its grouped data
if (valueInObject !== undefined && valueInObject !== null && column.propertyName && !column.isVirtual) {
if (valueInObject !== undefined && valueInObject !== null && column.propertyName && !column.isVirtual && !column.isDiscriminator) {
const value = this.driver.prepareHydratedValue(valueInObject, column);
if (column.isInEmbedded) {