bugfixes;

This commit is contained in:
Zotov Dmitry 2017-05-18 18:26:45 +05:00
parent 0b7bdd15ab
commit 68f4cc48be
12 changed files with 93 additions and 56 deletions

View File

@ -42,7 +42,7 @@ export function Index(nameOrFieldsOrOptions: string|string[]|((object: any) => a
const fields = typeof nameOrFieldsOrOptions === "string" ? <((object?: any) => (any[]|{ [key: string]: number }))|string[]> maybeFieldsOrOptions : nameOrFieldsOrOptions as string[];
let options = (typeof nameOrFieldsOrOptions === "object" && !Array.isArray(nameOrFieldsOrOptions)) ? nameOrFieldsOrOptions as IndexOptions : maybeOptions;
if (!options)
options = (typeof maybeFieldsOrOptions === "object" && !Array.isArray(maybeFieldsOrOptions)) ? nameOrFieldsOrOptions as IndexOptions : maybeOptions;
options = (typeof maybeFieldsOrOptions === "object" && !Array.isArray(maybeFieldsOrOptions)) ? maybeFieldsOrOptions as IndexOptions : maybeOptions;
return function (clsOrObject: Function|Object, propertyName?: string) {
const args: IndexMetadataArgs = {

View File

@ -361,7 +361,8 @@ where constraint_type = 'PRIMARY KEY' AND c.table_schema = '${this.schemaName}'
throw new QueryRunnerAlreadyReleasedError();
const columnDefinitions = table.columns.map(column => this.buildCreateColumnSql(column, false)).join(", ");
let sql = `CREATE SCHEMA IF NOT EXISTS "${this.schemaName}";CREATE TABLE "${table.name}" (${columnDefinitions}`;
await this.query(`CREATE SCHEMA IF NOT EXISTS "${this.schemaName}"`);
let sql = `CREATE TABLE "${table.name}" (${columnDefinitions}`;
sql += table.columns
.filter(column => column.isUnique)
.map(column => `, CONSTRAINT "uk_${table.name}_${column.name}" UNIQUE ("${column.name}")`)

View File

@ -52,7 +52,7 @@ export class JunctionEntityMetadataBuilder {
return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === referencedColumn.propertyName) &&
!!joinColumnArgs.name;
}) : undefined;
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.driver.namingStrategy.joinTableColumnName(relation.entityMetadata.tableNameWithoutPrefix, referencedColumn.propertyName, referencedColumn.givenDatabaseName);
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.driver.namingStrategy.joinTableColumnName(relation.entityMetadata.tableNameWithoutPrefix, referencedColumn.propertyName, referencedColumn.databaseName);
return new ColumnMetadata({
entityMetadata: entityMetadata,
@ -78,7 +78,7 @@ export class JunctionEntityMetadataBuilder {
return (!joinColumnArgs.referencedColumnName || joinColumnArgs.referencedColumnName === inverseReferencedColumn.propertyName) &&
!!joinColumnArgs.name;
}) : undefined;
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.driver.namingStrategy.joinTableColumnName(relation.inverseEntityMetadata.tableNameWithoutPrefix, inverseReferencedColumn.propertyName, inverseReferencedColumn.givenDatabaseName);
const columnName = joinColumn && joinColumn.name ? joinColumn.name : this.connection.driver.namingStrategy.joinTableColumnName(relation.inverseEntityMetadata.tableNameWithoutPrefix, inverseReferencedColumn.propertyName, inverseReferencedColumn.databaseName);
return new ColumnMetadata({
entityMetadata: entityMetadata,
@ -153,10 +153,10 @@ export class JunctionEntityMetadataBuilder {
protected collectReferencedColumns(relation: RelationMetadata, joinTable: JoinTableMetadataArgs) {
const hasAnyReferencedColumnName = joinTable.joinColumns ? joinTable.joinColumns.find(joinColumn => !!joinColumn.referencedColumnName) : false;
if (!joinTable.joinColumns || (joinTable.joinColumns && !hasAnyReferencedColumnName)) {
return relation.entityMetadata.ownColumns.filter(column => column.isPrimary);
return relation.entityMetadata.columns.filter(column => column.isPrimary);
} else {
return joinTable.joinColumns.map(joinColumn => {
const referencedColumn = relation.entityMetadata.ownColumns.find(column => column.propertyName === joinColumn.referencedColumnName);
const referencedColumn = relation.entityMetadata.columns.find(column => column.propertyName === joinColumn.referencedColumnName);
if (!referencedColumn)
throw new Error(`Referenced column ${joinColumn.referencedColumnName} was not found in entity ${relation.entityMetadata.name}`);

View File

@ -351,16 +351,22 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
return;
// if this subject is persisted subject then we get its value to check if its not empty or its values changed
let persistValueRelationId: any = undefined;
let persistValueRelationId: any = undefined, persistValue: any = undefined;
if (subject.hasEntity) {
const persistValue = relation.getEntityValue(subject.entity);
persistValue = relation.getEntityValue(subject.entity);
if (persistValue === null) persistValueRelationId = null;
if (persistValue) persistValueRelationId = relation.joinColumns[0].referencedColumn!.getEntityValue(persistValue);
if (persistValue) persistValueRelationId = relation.joinColumns.reduce((map, column) => column.referencedColumn!.getEntityValueMap(persistValue), {} as ObjectLiteral);
if (persistValueRelationId === undefined) return; // skip undefined properties
}
// object is removed only if relation id in the persisted entity is empty or is changed
if (persistValueRelationId !== null && persistValueRelationId === relationIdInDatabaseEntity)
// if (persistValueRelationId !== null && persistValueRelationId === relationIdInDatabaseEntity)
// return;
// console.log("relationIdInDatabaseEntity:", relationIdInDatabaseEntity);
// console.log("persistValue:", persistValue);
// console.log("compareEntities:", relation.entityMetadata.compareEntities(relationIdInDatabaseEntity, persistValue));
// console.log("compareIds:", relation.entityMetadata.compareIds(relationIdInDatabaseEntity, persistValue));
if (persistValueRelationId !== null && relation.entityMetadata.compareIds(relationIdInDatabaseEntity, persistValue))
return;
// first check if we already loaded this object before load from the database
@ -373,20 +379,31 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
// (example) here we seek a Details loaded from the database in the subjects
// (example) here relatedSubject.databaseEntity is a Details
// (example) and we need to compare details.id === post.detailsId
return relation.joinColumns[0].referencedColumn!.getEntityValue(relatedSubject.databaseEntity) === relationIdInDatabaseEntity;
return relation.entityMetadata.compareIds(relationIdInDatabaseEntity, relation.getEntityValue(relatedSubject.databaseEntity)); // relation.joinColumns[0].referencedColumn!.getEntityValue(relatedSubject.databaseEntity) === relationIdInDatabaseEntity;
});
// if not loaded yet then load it from the database
if (!alreadyLoadedRelatedDatabaseSubject) {
// (example) we need to load a details where details.id = post.details
const databaseEntity = await this.connection
const qb = this.connection
.getRepository<ObjectLiteral>(valueMetadata.target)
.createQueryBuilder(qbAlias, this.queryRunnerProvider) // todo: this wont work for mongodb. implement this in some method and call it here instead?
.where(qbAlias + "." + relation.joinColumns[0].referencedColumn!.propertyPath + "=:id")
.setParameter("id", relationIdInDatabaseEntity) // (example) subject.entity is a post here
.enableAutoRelationIdsLoad()
.getOne();
.enableAutoRelationIdsLoad();
const condition = relation.joinColumns.map(joinColumn => {
return `${qbAlias}.${joinColumn.referencedColumn!.propertyPath} = :${joinColumn.databaseName}`;
}).join(" AND ");
const parameters = relation.joinColumns.reduce((parameters, joinColumn) => {
parameters[joinColumn.databaseName] = joinColumn.referencedColumn!.getEntityValue(relationIdInDatabaseEntity);
return parameters;
}, {} as ObjectLiteral);
qb.where(condition)
.setParameters(parameters);
const databaseEntity = await qb.getOne();
if (databaseEntity) {
alreadyLoadedRelatedDatabaseSubject = new Subject(valueMetadata, undefined, databaseEntity);
@ -460,7 +477,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
const databaseEntity = await this.connection
.getRepository<ObjectLiteral>(valueMetadata.target)
.createQueryBuilder(qbAlias, this.queryRunnerProvider) // todo: this wont work for mongodb. implement this in some method and call it here instead?
.where(qbAlias + "." + relation.inverseSidePropertyPath + "=:id")
.where(qbAlias + "." + relation.inverseSidePropertyPath + "=:id") // TODO relation.inverseRelation.joinColumns
.setParameter("id", relationIdInDatabaseEntity) // (example) subject.entity is a details here, and the value is details.id
.enableAutoRelationIdsLoad()
.getOne();
@ -770,7 +787,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
// if subject don't have database entity it means its new and we don't need to remove something that is not exist
if (subject.hasDatabaseEntity) {
existInverseEntityRelationIds = relation.getEntityValue(subject.databaseEntity);
// console.log("existInverseEntityRelationIds:", existInverseEntityRelationIds);
// console.log("existInverseEntityRelationIds:", existInverseEntityRelationIds[0]);
}
// get all inverse entities relation ids that are "bind" to the currently persisted entity
@ -782,6 +799,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
}, {} as ObjectLiteral);
})
.filter(subRelationValue => subRelationValue !== undefined && subRelationValue !== null);
// console.log("changedInverseEntityRelationIds:", changedInverseEntityRelationIds);
// now from all entities in the persisted entity find only those which aren't found in the db
const removedJunctionEntityIds = existInverseEntityRelationIds.filter(existRelationId => {

View File

@ -955,9 +955,8 @@ export class SubjectOperationExecutor {
const removePromises = junctionRemove.junctionRelationIds.map(relationIds => {
let inverseConditions: ObjectLiteral = {};
Object.keys(relationIds).forEach(key => {
const joinColumn = secondJoinColumns.find(column => column.referencedColumn!.propertyName === key);
inverseConditions[joinColumn!.databaseName] = relationIds[key];
secondJoinColumns.forEach(joinColumn => {
inverseConditions[joinColumn.databaseName] = joinColumn.referencedColumn!.getEntityValue(relationIds);
});
return this.queryRunner.delete(junctionMetadata.tableName, Object.assign({}, inverseConditions, conditions));
});

View File

@ -560,6 +560,13 @@ export class QueryBuilder<Entity> {
relationIdAttribute.queryBuilderFactory = queryBuilderFactory;
this.expressionMap.relationIdAttributes.push(relationIdAttribute);
if (relationIdAttribute.relation.junctionEntityMetadata) {
this.expressionMap.createAlias({
name: relationIdAttribute.junctionAlias,
metadata: relationIdAttribute.relation.junctionEntityMetadata
});
}
return this;
}
@ -578,6 +585,12 @@ export class QueryBuilder<Entity> {
this.expressionMap.createAlias({
name: relationCountAttribute.junctionAlias
});
if (relationCountAttribute.relation.junctionEntityMetadata) {
this.expressionMap.createAlias({
name: relationCountAttribute.junctionAlias,
metadata: relationCountAttribute.relation.junctionEntityMetadata
});
}
return this;
}
@ -1214,6 +1227,12 @@ export class QueryBuilder<Entity> {
name: aliasName,
metadata: joinAttribute.metadata!
});
if (joinAttribute.relation && joinAttribute.relation.junctionEntityMetadata) {
this.expressionMap.createAlias({
name: joinAttribute.junctionAlias,
metadata: joinAttribute.relation.junctionEntityMetadata
});
}
}
protected rawResultsToEntities(results: any[], rawRelationIdResults: RelationIdLoadResult[], rawRelationCountResults: RelationCountLoadResult[]) {
@ -1417,23 +1436,16 @@ export class QueryBuilder<Entity> {
protected replacePropertyNames(statement: string) {
this.expressionMap.aliases.forEach(alias => {
if (!alias.hasMetadata) return;
// alias.metadata.embeddeds.forEach(embedded => {
// embedded.columns.forEach(column => {
// const expression = "([ =]|^.{0})" + alias.name + "\\." + embedded.propertyName + "\\." + column.propertyName + "([ =]|.{0}$)";
// statement = statement.replace(new RegExp(expression, "gm"), "$1" + this.escapeAlias(alias.name) + "." + this.escapeColumn(column.fullName) + "$2");
// });
// todo: what about embedded relations here?
// });
alias.metadata.columns.forEach(column => {
const expression = "([ =\(]|^.{0})" + alias.name + "\\." + column.propertyPath + "([ =]|.{0}$)";
const expression = "([ =\(]|^.{0})" + alias.name + "\\." + column.propertyPath + "([ =\)]|.{0}$)";
statement = statement.replace(new RegExp(expression, "gm"), "$1" + this.escapeAlias(alias.name) + "." + this.escapeColumn(column.databaseName) + "$2");
});
alias.metadata.relationsWithJoinColumns.forEach(relation => {
relation.joinColumns.forEach(joinColumn => {
const expression = "([ =\(]|^.{0})" + alias.name + "\\." + relation.propertyPath + "\\." + joinColumn.referencedColumn!.propertyPath + "([ =]|.{0}$)";
const expression = "([ =\(]|^.{0})" + alias.name + "\\." + relation.propertyPath + "\\." + joinColumn.referencedColumn!.propertyPath + "([ =\)]|.{0}$)";
statement = statement.replace(new RegExp(expression, "gm"), "$1" + this.escapeAlias(alias.name) + "." + this.escapeColumn(joinColumn.databaseName) + "$2"); // todo: fix relation.joinColumns[0], what if multiple columns
});
const expression = "([ =\(]|^.{0})" + alias.name + "\\." + relation.propertyPath + "([ =]|.{0}$)";
const expression = "([ =\(]|^.{0})" + alias.name + "\\." + relation.propertyPath + "([ =\)]|.{0}$)";
statement = statement.replace(new RegExp(expression, "gm"), "$1" + this.escapeAlias(alias.name) + "." + this.escapeColumn(relation.joinColumns[0].databaseName) + "$2"); // todo: fix relation.joinColumns[0], what if multiple columns
});
});
@ -1665,8 +1677,6 @@ export class QueryBuilder<Entity> {
// parameters["parentId_" + index] = id;
// }
// }
// console.log(whereSubStrings);
// console.log(parameters);
return whereSubStrings.join(" AND ");
});

View File

@ -65,7 +65,7 @@ export class RelationIdLoader {
return joinColumns.map(joinColumn => {
const parameterName = joinColumn.databaseName + index;
parameters[parameterName] = rawEntity[relationIdAttr.parentAlias + "_" + joinColumn.referencedColumn!.databaseName];
return tableAlias + "." + joinColumn.databaseName + " = :" + parameterName;
return tableAlias + "." + joinColumn.propertyPath + " = :" + parameterName;
}).join(" AND ");
}).map(condition => "(" + condition + ")")
.join(" OR ");
@ -80,11 +80,11 @@ export class RelationIdLoader {
const qb = new QueryBuilder(this.connection, this.queryRunnerProvider);
joinColumns.forEach(joinColumn => {
qb.addSelect(tableAlias + "." + joinColumn.databaseName, joinColumn.databaseName);
qb.addSelect(tableAlias + "." + joinColumn.propertyPath, joinColumn.databaseName);
});
relation.inverseRelation!.entityMetadata.primaryColumns.forEach(primaryColumn => {
qb.addSelect(tableAlias + "." + primaryColumn.databaseName, primaryColumn.databaseName);
qb.addSelect(tableAlias + "." + primaryColumn.propertyPath, primaryColumn.databaseName);
});
qb.from(table, tableAlias)
@ -116,7 +116,7 @@ export class RelationIdLoader {
const mappedColumns = rawEntities.map(rawEntity => {
return joinColumns.reduce((map, joinColumn) => {
map[joinColumn.databaseName] = rawEntity[relationIdAttr.parentAlias + "_" + joinColumn.referencedColumn!.databaseName];
map[joinColumn.propertyPath] = rawEntity[relationIdAttr.parentAlias + "_" + joinColumn.referencedColumn!.databaseName];
return map;
}, {} as ObjectLiteral);
});
@ -133,7 +133,7 @@ export class RelationIdLoader {
});
const inverseJoinColumnCondition = inverseJoinColumns.map(joinColumn => {
return junctionAlias + "." + joinColumn.databaseName + " = " + inverseSideTableAlias + "." + joinColumn.referencedColumn!.databaseName;
return junctionAlias + "." + joinColumn.propertyPath + " = " + inverseSideTableAlias + "." + joinColumn.referencedColumn!.propertyPath;
}).join(" AND ");
const condition = joinColumnConditions.map(condition => {
@ -143,11 +143,11 @@ export class RelationIdLoader {
const qb = new QueryBuilder(this.connection, this.queryRunnerProvider);
inverseJoinColumns.forEach(joinColumn => {
qb.addSelect(junctionAlias + "." + joinColumn.databaseName, joinColumn.databaseName);
qb.addSelect(junctionAlias + "." + joinColumn.propertyPath, joinColumn.databaseName);
});
joinColumns.forEach(joinColumn => {
qb.addSelect(junctionAlias + "." + joinColumn.databaseName, joinColumn.databaseName);
qb.addSelect(junctionAlias + "." + joinColumn.propertyPath, joinColumn.databaseName);
});
qb.fromTable(inverseSideTableName, inverseSideTableAlias)

View File

@ -6,6 +6,7 @@ import {Alias} from "../Alias";
import {JoinAttribute} from "../JoinAttribute";
import {RelationCountLoadResult} from "../relation-count/RelationCountLoadResult";
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {OrmUtils} from "../../util/OrmUtils";
/**
* Transforms raw sql results returned from the database into entity object.
@ -165,18 +166,27 @@ export class RawSqlResultsToEntityTransformer {
}
}
return columns.reduce((idMap, joinColumn) => {
if (columns.length === 1 && rawRelationIdResult.relationIdAttribute.disableMixedMap === false) {
idMap = result[joinColumn.databaseName];
// const idMapColumns = (relation.isOneToMany || relation.isOneToOneNotOwner) ? columns : columns.map(column => column.referencedColumn!);
// const idMap = idMapColumns.reduce((idMap, column) => {
// return OrmUtils.mergeDeep(idMap, column.createValueMap(result[column.databaseName]));
// }, {} as ObjectLiteral); // need to create reusable function for this process
const idMap = columns.reduce((idMap, column) => {
if (relation.isOneToMany || relation.isOneToOneNotOwner) {
return OrmUtils.mergeDeep(idMap, column.createValueMap(result[column.databaseName]));
} else {
if (relation.isOneToMany || relation.isOneToOneNotOwner) {
idMap[joinColumn.propertyName] = result[joinColumn.databaseName];
} else {
idMap[joinColumn.referencedColumn!.propertyName] = result[joinColumn.databaseName];
}
return OrmUtils.mergeDeep(idMap, column.referencedColumn!.createValueMap(result[column.databaseName]));
}
return idMap;
}, {} as ObjectLiteral);
if (columns.length === 1 && rawRelationIdResult.relationIdAttribute.disableMixedMap === false) {
if (relation.isOneToMany || relation.isOneToOneNotOwner) {
return columns[0].getEntityValue(idMap);
} else {
return columns[0].referencedColumn!.getEntityValue(idMap);
}
}
return idMap;
}).filter(result => result);
const properties = rawRelationIdResult.relationIdAttribute.mapToPropertyPropertyPath.split(".");

View File

@ -401,7 +401,6 @@ export class Repository<Entity extends ObjectLiteral> {
if (!this.metadata.hasMultiplePrimaryKeys && !(id instanceof Object)) {
id = this.metadata.createEntityIdMap([id]);
}
console.log("me", [id]);
qb.andWhereInIds([id]);
return qb.getOne();
}

View File

@ -6,7 +6,7 @@ import {Index} from "../../../../../src/decorator/Index";
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
@Entity()
@Index(["id", "counters.code", "counters.subcounters.version"])
@Index(["id", "counters.code", "counters.subcounters.version"], { unique: true })
export class Post {
@PrimaryColumn()
@ -18,4 +18,4 @@ export class Post {
@Embedded(() => Counters)
counters: Counters;
}
}

View File

@ -6,7 +6,7 @@ import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn"
import {Post} from "./Post";
@Entity()
@Index(["id", "personId"])
@Index(["id", "personId"], { unique: true })
export class User {
@PrimaryColumn()

View File

@ -1,12 +1,12 @@
import "reflect-metadata";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";
import {expect} from "chai";
import {Post} from "./entity/Post";
import {Category} from "./entity/Category";
import {PostMetadata} from "./entity/PostMetadata";
describe.skip("github issues > #151 joinAndSelect can't find entity from inverse side of relation", () => {
describe("github issues > #151 joinAndSelect can't find entity from inverse side of relation", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
@ -49,7 +49,7 @@ describe.skip("github issues > #151 joinAndSelect can't find entity from inverse
})));
it.only("should cascade remove successfully with uni-directional relation", () => Promise.all(connections.map(async connection => {
it("should cascade remove successfully with uni-directional relation", () => Promise.all(connections.map(async connection => {
const category = new Category();
category.name = "post category";