fixed bug with duplicate entity columns in inheritance tree

This commit is contained in:
Umed Khudoiberdiev 2017-09-29 11:14:35 +05:00
parent 85f7def856
commit 9d5288d82f
7 changed files with 121 additions and 45 deletions

View File

@ -1,7 +1,7 @@
{
"name": "typeorm",
"private": true,
"version": "0.1.0-alpha.49",
"version": "0.1.0-alpha.50",
"description": "Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL, MongoDB databases.",
"license": "MIT",
"readmeFilename": "README.md",

View File

@ -52,20 +52,16 @@ export class MetadataArgsStorage {
// Public Methods
// -------------------------------------------------------------------------
filterTables(target?: Function|string): TableMetadataArgs[];
filterTables(target?: (Function|string)[]): TableMetadataArgs[];
filterTables(target?: (Function|string)|(Function|string)[]): TableMetadataArgs[] {
return this.tables.filter(table => {
return target instanceof Array ? target.indexOf(table.target) !== -1 : table.target === target;
});
filterTables(target: Function|string): TableMetadataArgs[];
filterTables(target: (Function|string)[]): TableMetadataArgs[];
filterTables(target: (Function|string)|(Function|string)[]): TableMetadataArgs[] {
return this.filterByTarget(this.tables, target);
}
filterColumns(target: Function|string): ColumnMetadataArgs[];
filterColumns(target: (Function|string)[]): ColumnMetadataArgs[];
filterColumns(target: (Function|string)|(Function|string)[]): ColumnMetadataArgs[] {
return this.columns.filter(column => {
return target instanceof Array ? target.indexOf(column.target) !== -1 : column.target === target;
});
return this.filterByTargetAndWithoutDuplicateProperties(this.columns, target);
}
findGenerated(target: Function|string, propertyName: string): GeneratedMetadataArgs|undefined;
@ -79,30 +75,25 @@ export class MetadataArgsStorage {
filterRelations(target: Function|string): RelationMetadataArgs[];
filterRelations(target: (Function|string)[]): RelationMetadataArgs[];
filterRelations(target: (Function|string)|(Function|string)[]): RelationMetadataArgs[] {
return this.relations.filter(relation => {
return target instanceof Array ? target.indexOf(relation.target) !== -1 : relation.target === target;
});
return this.filterByTargetAndWithoutDuplicateProperties(this.relations, target);
}
filterRelationIds(target: Function|string): RelationIdMetadataArgs[];
filterRelationIds(target: (Function|string)[]): RelationIdMetadataArgs[];
filterRelationIds(target: (Function|string)|(Function|string)[]): RelationIdMetadataArgs[] {
return this.relationIds.filter(relationId => {
return target instanceof Array ? target.indexOf(relationId.target) !== -1 : relationId.target === target;
});
return this.filterByTargetAndWithoutDuplicateProperties(this.relationIds, target);
}
filterRelationCounts(target: Function|string): RelationCountMetadataArgs[];
filterRelationCounts(target: (Function|string)[]): RelationCountMetadataArgs[];
filterRelationCounts(target: (Function|string)|(Function|string)[]): RelationCountMetadataArgs[] {
return this.relationCounts.filter(relationCount => {
return target instanceof Array ? target.indexOf(relationCount.target) !== -1 : relationCount.target === target;
});
return this.filterByTargetAndWithoutDuplicateProperties(this.relationCounts, target);
}
filterIndices(target: Function|string): IndexMetadataArgs[];
filterIndices(target: (Function|string)[]): IndexMetadataArgs[];
filterIndices(target: (Function|string)|(Function|string)[]): IndexMetadataArgs[] {
// todo: implement parent-entity overrides?
return this.indices.filter(index => {
return target instanceof Array ? target.indexOf(index.target) !== -1 : index.target === target;
});
@ -111,17 +102,13 @@ export class MetadataArgsStorage {
filterListeners(target: Function|string): EntityListenerMetadataArgs[];
filterListeners(target: (Function|string)[]): EntityListenerMetadataArgs[];
filterListeners(target: (Function|string)|(Function|string)[]): EntityListenerMetadataArgs[] {
return this.entityListeners.filter(index => {
return target instanceof Array ? target.indexOf(index.target) !== -1 : index.target === target;
});
return this.filterByTargetAndWithoutDuplicateProperties(this.entityListeners, target);
}
filterEmbeddeds(target: Function|string): EmbeddedMetadataArgs[];
filterEmbeddeds(target: (Function|string)[]): EmbeddedMetadataArgs[];
filterEmbeddeds(target: (Function|string)|(Function|string)[]): EmbeddedMetadataArgs[] {
return this.embeddeds.filter(embedded => {
return target instanceof Array ? target.indexOf(embedded.target) !== -1 : embedded.target === target;
});
return this.filterByTargetAndWithoutDuplicateProperties(this.embeddeds, target);
}
findJoinTable(target: Function|string, propertyName: string): JoinTableMetadataArgs|undefined {
@ -131,6 +118,7 @@ export class MetadataArgsStorage {
}
filterJoinColumns(target: Function|string, propertyName: string): JoinColumnMetadataArgs[] {
// todo: implement parent-entity overrides?
return this.joinColumns.filter(joinColumn => {
return joinColumn.target === target && joinColumn.propertyName === propertyName;
});
@ -139,33 +127,25 @@ export class MetadataArgsStorage {
filterSubscribers(target: Function|string): EntitySubscriberMetadataArgs[];
filterSubscribers(target: (Function|string)[]): EntitySubscriberMetadataArgs[];
filterSubscribers(target: (Function|string)|(Function|string)[]): EntitySubscriberMetadataArgs[] {
return this.entitySubscribers.filter(subscriber => {
return target instanceof Array ? target.indexOf(subscriber.target) !== -1 : subscriber.target === target;
});
return this.filterByTarget(this.entitySubscribers, target);
}
filterNamingStrategies(target: Function|string): NamingStrategyMetadataArgs[];
filterNamingStrategies(target: (Function|string)[]): NamingStrategyMetadataArgs[];
filterNamingStrategies(target: (Function|string)|(Function|string)[]): NamingStrategyMetadataArgs[] {
return this.namingStrategies.filter(subscriber => {
return target instanceof Array ? target.indexOf(subscriber.target) !== -1 : subscriber.target === target;
});
return this.filterByTarget(this.namingStrategies, target);
}
filterTransactionEntityManagers(target: Function|string): TransactionEntityMetadataArgs[];
filterTransactionEntityManagers(target: (Function|string)[]): TransactionEntityMetadataArgs[];
filterTransactionEntityManagers(target: (Function|string)|(Function|string)[]): TransactionEntityMetadataArgs[] {
return this.transactionEntityManagers.filter(subscriber => {
return target instanceof Array ? target.indexOf(subscriber.target) !== -1 : subscriber.target === target;
});
return this.filterByTarget(this.transactionEntityManagers, target);
}
filterTransactionRepository(target: Function|string): TransactionRepositoryMetadataArgs[];
filterTransactionRepository(target: (Function|string)[]): TransactionRepositoryMetadataArgs[];
filterTransactionRepository(target: (Function|string)|(Function|string)[]): TransactionRepositoryMetadataArgs[] {
return this.transactionRepositories.filter(subscriber => {
return target instanceof Array ? target.indexOf(subscriber.target) !== -1 : subscriber.target === target;
});
return this.filterByTarget(this.transactionRepositories, target);
}
filterSingleTableChildren(target: Function|string): TableMetadataArgs[] {
@ -185,4 +165,32 @@ export class MetadataArgsStorage {
return this.discriminatorValues.find(discriminatorValue => discriminatorValue.target === target);
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Filters given array by a given target or targets.
*/
protected filterByTarget<T extends { target: Function|string }>(array: T[], target: (Function|string)|(Function|string)[]): T[] {
return array.filter(table => {
return target instanceof Array ? target.indexOf(table.target) !== -1 : table.target === target;
});
}
/**
* Filters given array by a given target or targets and prevents duplicate property names.
*/
protected filterByTargetAndWithoutDuplicateProperties<T extends { target: Function|string, propertyName: string }>(array: T[], target: (Function|string)|(Function|string)[]): T[] {
const newArray: T[] = [];
array.forEach(item => {
const sameTarget = target instanceof Array ? target.indexOf(item.target) !== -1 : item.target === target;
if (sameTarget) {
if (!newArray.find(newItem => newItem.propertyName === item.propertyName))
newArray.push(item);
}
});
return newArray;
}
}

View File

@ -280,14 +280,16 @@ export class EntityMetadataBuilder {
entityMetadata.discriminatorValue = discriminatorValue ? discriminatorValue.value : (tableArgs.target as any).name; // todo: pass this to naming strategy to generate a name
entityMetadata.embeddeds = this.createEmbeddedsRecursively(entityMetadata, this.metadataArgsStorage.filterEmbeddeds(inheritanceTree));
entityMetadata.ownColumns = this.metadataArgsStorage.filterColumns(inheritanceTree).map(args => {
const column = new ColumnMetadata({ connection: this.connection, entityMetadata, args });
// console.log(column.propertyName);
// if single table inheritance used, we need to mark all inherit table columns as nullable
if (singleTableChildrenTargets && singleTableChildrenTargets.indexOf(args.target) !== -1)
column.isNullable = true;
return column;
});
entityMetadata.ownColumns = this.metadataArgsStorage
.filterColumns(inheritanceTree)
.map(args => {
const column = new ColumnMetadata({ connection: this.connection, entityMetadata, args });
// console.log(column.propertyName);
// if single table inheritance used, we need to mark all inherit table columns as nullable
if (singleTableChildrenTargets && singleTableChildrenTargets.indexOf(args.target) !== -1)
column.isNullable = true;
return column;
});
entityMetadata.ownRelations = this.metadataArgsStorage.filterRelations(inheritanceTree).map(args => {
return new RelationMetadata({ entityMetadata, args });

View File

@ -0,0 +1,8 @@
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
export class BaseContent {
@PrimaryGeneratedColumn()
id: number;
}

View File

@ -0,0 +1,9 @@
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {BaseContent} from "./BaseContent";
export class BasePost extends BaseContent {
@PrimaryGeneratedColumn()
id: number;
}

View File

@ -0,0 +1,18 @@
import {Entity} from "../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../src/decorator/columns/Column";
import {BasePost} from "./BasePost";
@Entity()
export class Post extends BasePost {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column({ default: false })
active: boolean;
}

View File

@ -0,0 +1,31 @@
import "reflect-metadata";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";
import {Post} from "./entity/Post";
import {expect} from "chai";
describe("other issues > double inheritance produces multiple duplicated columns", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
dropSchema: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
it("should not produce duplicate columns", () => Promise.all(connections.map(async function(connection) {
// insert a post
const post = new Post();
post.title = "hello";
await connection.manager.save(post);
// check if it was inserted correctly
const loadedPost = await connection.manager.findOne(Post);
expect(loadedPost).not.to.be.empty;
loadedPost!.title.should.be.equal("hello");
})));
});