replaced all single primary keys usages

This commit is contained in:
Umed Khudoiberdiev 2016-09-07 18:02:35 +05:00
parent e64dd9576d
commit f8278ebc04
18 changed files with 358 additions and 272 deletions

View File

@ -19,21 +19,21 @@ const options: ConnectionOptions = {
entities: [Post]
};
createConnection(options).then(connection => {
createConnection(options).then(async connection => {
let postRepository = connection.getRepository(Post);
const post = new Post();
post.id = 1;
post.type = "person";
post.text = "this is test post";
post.text = "this is test post!";
postRepository.persist(post)
.then(savedPost => {
console.log("Post has been saved: ", savedPost);
})
.catch(error => {
console.log("error: ", error);
});
console.log("saving the post: ");
await postRepository.persist(post);
console.log("Post has been saved: ", post);
}, error => console.log("Cannot connect: ", error));
console.log("now loading the post: ");
const loadedPost = await postRepository.findOneById({ id: 1, type: "person" });
console.log("loaded post: ", loadedPost);
}, error => console.log("Error: ", error));

View File

@ -330,11 +330,14 @@ export class EntityMetadataBuilder {
entityMetadatas.forEach(metadata => {
if (!metadata.table.isClosure)
return;
if (metadata.primaryColumns.length > 1)
throw new Error(`Cannot use given entity ${metadata.name} as a closure table, because it have multiple primary keys. Entities with multiple primary keys are not supported in closure tables.`);
const closureJunctionEntityMetadata = getFromContainer(ClosureJunctionEntityMetadataBuilder).build(lazyRelationsWrapper, {
namingStrategy: namingStrategy,
table: metadata.table,
primaryColumn: metadata.primaryColumn,
primaryColumn: metadata.firstPrimaryColumn,
hasTreeLevelColumn: metadata.hasTreeLevelColumn
});
metadata.closureJunctionTable = closureJunctionEntityMetadata;

View File

@ -31,7 +31,7 @@ export class EntityMetadataValidator {
validate(entityMetadata: EntityMetadata) {
// check if table metadata has an id
if (!entityMetadata.primaryColumn)
if (!entityMetadata.primaryColumns.length)
throw new MissingPrimaryColumnError(entityMetadata);
entityMetadata.relations.forEach(relation => {

View File

@ -134,6 +134,13 @@ export class EntityMetadata {
return "";
}
/**
* Checks if entity's table has multiple primary columns.
*/
get hasMultiplePrimaryKeys() {
return this.primaryColumns.length > 1;
}
/**
* Gets the primary column.
*
@ -147,6 +154,35 @@ export class EntityMetadata {
return primaryKey;
}
/**
* Checks if table has generated column.
*/
get hasGeneratedColumn(): boolean {
return !!this._columns.find(column => column.isGenerated);
}
/**
* Gets the column with generated flag.
*/
get generatedColumn(): ColumnMetadata {
const generatedColumn = this._columns.find(column => column.isGenerated);
if (!generatedColumn)
throw new Error(`Generated column was not found`);
return generatedColumn;
}
/**
* Gets first primary column. In the case if table contains multiple primary columns it
* throws error.
*/
get firstPrimaryColumn(): ColumnMetadata {
if (this.hasMultiplePrimaryKeys)
throw new Error(`Entity ${this.name} has multiple primary keys. This operation is not supported on entities with multiple primary keys`);
return this.primaryColumns[0];
}
/**
* Gets the primary columns.
*/
@ -365,11 +401,16 @@ export class EntityMetadata {
return typeof nameOrFn === "string" ? nameOrFn : nameOrFn(this.createPropertiesMap());
}
/**
* Returns entity id of the given entity.
*/
getEntityId(entity: any) {
return entity ? entity[this.primaryColumn.propertyName] : undefined;
getEntityIdMap(entity: any): ObjectLiteral|undefined {
if (!entity)
return undefined;
const map: ObjectLiteral = {};
this.primaryColumns.forEach(column => map[column.propertyName] = entity[column.propertyName]);
const hasAllIds = this.primaryColumns.every(primaryColumn => {
return map[primaryColumn.propertyName] !== undefined && map[primaryColumn.propertyName] !== null;
});
return hasAllIds ? map : undefined;
}
/**
@ -464,5 +505,20 @@ export class EntityMetadata {
return object.hasOwnProperty(primaryColumn.propertyName);
});
}
compareEntities(firstEntity: any, secondEntity: any) {
const firstEntityIds = this.getEntityIdMap(firstEntity);
const secondEntityIds = this.getEntityIdMap(secondEntity);
return this.compareIds(firstEntityIds, secondEntityIds);
}
compareIds(firstIds: ObjectLiteral|undefined, secondIds: ObjectLiteral|undefined): boolean {
if (!firstIds || !secondIds)
return false;
return Object.keys(firstIds).every(key => {
return firstIds[key] === secondIds[key];
});
}
}

View File

@ -62,7 +62,10 @@ export class JoinColumnMetadata extends PropertyMetadata {
throw new Error(`Referenced column ${this.referencedColumnName} was not found in entity ${this.name}`);
}
return this.relation.inverseEntityMetadata.primaryColumn;
if (this.relation.inverseEntityMetadata.primaryColumns.length > 1)
throw new Error(`Cannot automatically determine a referenced column of the "${this.relation.inverseEntityMetadata.name}", because it has multiple primary columns. Try to specify a referenced column explicitly.`);
return this.relation.inverseEntityMetadata.firstPrimaryColumn;
}
}

View File

@ -140,7 +140,10 @@ export class JoinTableMetadata extends PropertyMetadata {
return referencedColumn;
}
return this.relation.entityMetadata.primaryColumn;
if (this.relation.entityMetadata.primaryColumns.length > 1)
throw new Error(`Cannot automatically determine a referenced column of the "${this.relation.entityMetadata.name}", because it has multiple primary columns. Try to specify a referenced column explicitly.`);
return this.relation.entityMetadata.firstPrimaryColumn;
}
/**
@ -155,7 +158,10 @@ export class JoinTableMetadata extends PropertyMetadata {
return referencedColumn;
}
return this.relation.inverseEntityMetadata.primaryColumn;
if (this.relation.inverseEntityMetadata.primaryColumns.length > 1)
throw new Error(`Cannot automatically determine inverse referenced column of the "${this.relation.inverseEntityMetadata.name}", because it has multiple primary columns. Try to specify a referenced column explicitly.`);
return this.relation.inverseEntityMetadata.firstPrimaryColumn;
}
}

View File

@ -10,6 +10,7 @@ import {RemoveOperation} from "./operation/RemoveOperation";
import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection";
import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOperation";
import {JunctionRemoveOperation} from "./operation/JunctionRemoveOperation";
import {ObjectLiteral} from "../common/ObjectLiteral";
/**
* 1. collect all exist objects from the db entity
@ -115,7 +116,7 @@ export class EntityPersistOperationBuilder {
operations: InsertOperation[] = []): InsertOperation[] {
const newEntity = newEntityWithId.entity;
const metadata = this.entityMetadatas.findByTarget(newEntityWithId.entityTarget);
const isObjectNew = !this.findEntityWithId(dbEntities, metadata.target, newEntity[metadata.primaryColumn.propertyName]);
const isObjectNew = !this.findEntityWithId(dbEntities, metadata.target, metadata.getEntityIdMap(newEntity)!);
// if object is new and should be inserted, we check if cascades are allowed before add it to operations list
if (isObjectNew && fromRelation && !this.checkCascadesAllowed("insert", metadata, fromRelation)) {
@ -132,19 +133,11 @@ export class EntityPersistOperationBuilder {
if (value instanceof Array) {
value.forEach((subValue: any) => {
const subValueWithId: EntityWithId = {
id: inverseMetadata.getEntityId(subValue),
entity: subValue,
entityTarget: inverseMetadata.target
};
const subValueWithId = new EntityWithId(inverseMetadata, subValue);
this.findCascadeInsertedEntities(subValueWithId, dbEntities, relation, operations);
});
} else {
const valueWithId: EntityWithId = {
id: inverseMetadata.getEntityId(value),
entity: value,
entityTarget: inverseMetadata.target
};
const valueWithId = new EntityWithId(inverseMetadata, value);
this.findCascadeInsertedEntities(valueWithId, dbEntities, relation, operations);
}
});
@ -171,14 +164,14 @@ export class EntityPersistOperationBuilder {
return operations;
} else if (diffColumns.length || diffRelations.length) {
const entityId = newEntity[metadata.primaryColumn.propertyName];
const entityId = metadata.getEntityIdMap(newEntity);
if (entityId)
operations.push(new UpdateOperation(newEntityWithId.entityTarget, newEntity, entityId, diffColumns, diffRelations));
}
metadata.relations.forEach(relation => {
const relMetadata = relation.inverseEntityMetadata;
const relationIdColumnName = relMetadata.primaryColumn.propertyName;
const relationIdColumnName = relMetadata.firstPrimaryColumn.propertyName; // todo: join column metadata should be used here instead of primary column
const value = this.getEntityRelationValue(relation, newEntity);
const valueTarget = relation.target;
const referencedColumnName = relation.isOwning ? relation.referencedColumnName : relation.inverseRelation.referencedColumnName;
@ -196,16 +189,8 @@ export class EntityPersistOperationBuilder {
return dbValue.entityTarget === valueTarget && dbValue.entity[referencedColumnName] === subEntity[relationIdColumnName];
});
if (dbValue) {
const dbValueWithId: EntityWithId = {
id: relMetadata.getEntityId(dbValue.entity),
entity: dbValue.entity,
entityTarget: relMetadata.target
};
const subEntityWithId: EntityWithId = {
id: relMetadata.getEntityId(subEntity),
entity: subEntity,
entityTarget: relMetadata.target
};
const dbValueWithId = new EntityWithId(relMetadata, dbValue.entity);
const subEntityWithId = new EntityWithId(relMetadata, subEntity);
this.findCascadeUpdateEntities(updatesByRelations, relMetadata, dbValueWithId, subEntityWithId, dbEntities, relation, operations);
}
});
@ -215,18 +200,8 @@ export class EntityPersistOperationBuilder {
return dbValue.entityTarget === valueTarget && dbValue.entity[referencedColumnName] === value[relationIdColumnName];
});
if (dbValue) {
const dbValueWithId: EntityWithId = {
id: relMetadata.getEntityId(dbValue.entity),
entity: dbValue.entity,
entityTarget: relMetadata.target
};
const valueWithId: EntityWithId = {
id: relMetadata.getEntityId(value),
entity: value,
entityTarget: relMetadata.target
};
const dbValueWithId = new EntityWithId(relMetadata, dbValue.entity);
const valueWithId = new EntityWithId(relMetadata, value);
this.findCascadeUpdateEntities(updatesByRelations, relMetadata, dbValueWithId, valueWithId, dbEntities, relation, operations);
}
}
@ -240,7 +215,7 @@ export class EntityPersistOperationBuilder {
allPersistedEntities: EntityWithId[],
fromRelation: RelationMetadata|undefined,
fromMetadata: EntityMetadata|undefined,
fromEntityId: any,
fromEntityId: ObjectLiteral|undefined,
parentAlreadyRemoved: boolean = false): RemoveOperation[] {
const dbEntity = dbEntityWithId.entity;
@ -248,7 +223,7 @@ export class EntityPersistOperationBuilder {
if (!dbEntity)
return operations;
const entityId = dbEntity[metadata.primaryColumn.propertyName];
const entityId = metadata.getEntityIdMap(dbEntity)!;
const isObjectRemoved = parentAlreadyRemoved || !this.findEntityWithId(allPersistedEntities, metadata.target, entityId);
// if object is removed and should be removed, we check if cascades are allowed before add it to operations list
@ -266,23 +241,15 @@ export class EntityPersistOperationBuilder {
if (dbValue instanceof Array) {
dbValue.forEach((subDbEntity: any) => {
const subDbEntityWithId: EntityWithId = {
id: relMetadata.getEntityId(subDbEntity),
entity: subDbEntity,
entityTarget: relMetadata.target
};
const subDbEntityWithId = new EntityWithId(relMetadata, subDbEntity);
const relationOperations = this.findCascadeRemovedEntities(relMetadata, subDbEntityWithId, allPersistedEntities, relation, metadata, dbEntity[metadata.primaryColumn.propertyName], isObjectRemoved);
const relationOperations = this.findCascadeRemovedEntities(relMetadata, subDbEntityWithId, allPersistedEntities, relation, metadata, metadata.getEntityIdMap(dbEntity), isObjectRemoved);
relationOperations.forEach(o => operations.push(o));
});
} else {
const dbValueWithId: EntityWithId = {
id: relMetadata.getEntityId(dbValue),
entity: dbValue,
entityTarget: relMetadata.target
};
const dbValueWithId = new EntityWithId(relMetadata, dbValue);
const relationOperations = this.findCascadeRemovedEntities(relMetadata, dbValueWithId, allPersistedEntities, relation, metadata, dbEntity[metadata.primaryColumn.propertyName], isObjectRemoved);
const relationOperations = this.findCascadeRemovedEntities(relMetadata, dbValueWithId, allPersistedEntities, relation, metadata, metadata.getEntityIdMap(dbEntity), isObjectRemoved);
relationOperations.forEach(o => operations.push(o));
}
}, []);
@ -309,7 +276,7 @@ export class EntityPersistOperationBuilder {
return true;
return !dbEntity[relation.propertyName].find((dbSubEntity: any) => {
return relation.inverseEntityMetadata.getEntityId(newSubEntity) === relation.inverseEntityMetadata.getEntityId(dbSubEntity);
return relation.inverseEntityMetadata.compareEntities(newSubEntity, dbSubEntity);
});
}).forEach((subEntity: any) => {
operations.push(new UpdateByInverseSideOperation(relationMetadata.target, newEntityWithId.entityTarget, "update", subEntity, newEntity, relation));
@ -323,7 +290,7 @@ export class EntityPersistOperationBuilder {
return true;
return !newEntity[relation.propertyName].find((newSubEntity: any) => {
return relation.inverseEntityMetadata.getEntityId(dbSubEntity) === relation.inverseEntityMetadata.getEntityId(newSubEntity);
return relation.inverseEntityMetadata.compareEntities(dbSubEntity, newSubEntity);
});
}).forEach((subEntity: any) => {
operations.push(new UpdateByInverseSideOperation(relationMetadata.target, newEntityWithId.entityTarget, "remove", subEntity, newEntity, relation));
@ -365,11 +332,7 @@ export class EntityPersistOperationBuilder {
if (!relation.isManyToMany && sub === insertOperation.entity)
operations.push(new UpdateByRelationOperation(entityToSearchInWithId.entityTarget, entityToSearchIn, insertOperation, relation));
const subWithId: EntityWithId = {
id: inverseMetadata.getEntityId(sub),
entity: sub,
entityTarget: inverseMetadata.target
};
const subWithId = new EntityWithId(inverseMetadata, sub);
const subOperations = this.findRelationsWithEntityInside(insertOperation, subWithId);
subOperations.forEach(o => operations.push(o));
});
@ -380,11 +343,7 @@ export class EntityPersistOperationBuilder {
operations.push(new UpdateByRelationOperation(entityToSearchInWithId.entityTarget, entityToSearchIn, insertOperation, relation));
}
const valueWithId: EntityWithId = {
id: inverseMetadata.getEntityId(value),
entity: value,
entityTarget: inverseMetadata.target
};
const valueWithId = new EntityWithId(inverseMetadata, value);
const subOperations = this.findRelationsWithEntityInside(insertOperation, valueWithId);
subOperations.forEach(o => operations.push(o));
@ -397,11 +356,11 @@ export class EntityPersistOperationBuilder {
private findJunctionInsertOperations(metadata: EntityMetadata, newEntityWithId: EntityWithId, dbEntities: EntityWithId[], isRoot = true): JunctionInsertOperation[] {
const newEntity = newEntityWithId.entity;
const dbEntity = dbEntities.find(dbEntity => {
return dbEntity.id === newEntity[metadata.primaryColumn.propertyName] && dbEntity.entityTarget === metadata.target;
return dbEntity.compareId(metadata.getEntityIdMap(newEntity)!) && dbEntity.entityTarget === metadata.target;
});
return metadata.relations.reduce((operations, relation) => {
const relationMetadata = relation.inverseEntityMetadata;
const relationIdProperty = relationMetadata.primaryColumn.propertyName;
const relationIdProperty = relationMetadata.firstPrimaryColumn.propertyName; // todo: join column metadata should be used instead of primaryColumn
const value = this.getEntityRelationValue(relation, newEntity);
if (value === null || value === undefined)
return operations;
@ -426,22 +385,14 @@ export class EntityPersistOperationBuilder {
}
if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) {
const subEntityWithId: EntityWithId = {
id: relationMetadata.getEntityId(subEntity),
entity: subEntity,
entityTarget: relationMetadata.target
};
const subEntityWithId = new EntityWithId(relationMetadata, subEntity);
const subOperations = this.findJunctionInsertOperations(relationMetadata, subEntityWithId, dbEntities, false);
subOperations.forEach(o => operations.push(o));
}
});
} else {
if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) {
const valueWithId: EntityWithId = {
id: relationMetadata.getEntityId(value),
entity: value,
entityTarget: relationMetadata.target
};
const valueWithId = new EntityWithId(relationMetadata, value);
const subOperations = this.findJunctionInsertOperations(relationMetadata, valueWithId, dbEntities, false);
subOperations.forEach(o => operations.push(o));
}
@ -457,13 +408,13 @@ export class EntityPersistOperationBuilder {
return [];
const newEntity = newEntities.find(newEntity => {
return newEntity.id === dbEntity[metadata.primaryColumn.propertyName] && newEntity.entityTarget === metadata.target;
return newEntity.compareId(dbEntity) && newEntity.entityTarget === metadata.target;
});
return metadata.relations
.filter(relation => dbEntity[relation.propertyName] !== null && dbEntity[relation.propertyName] !== undefined)
.reduce((operations, relation) => {
const relationMetadata = relation.inverseEntityMetadata;
const relationIdProperty = relationMetadata.primaryColumn.propertyName;
const relationIdProperty = relationMetadata.firstPrimaryColumn.propertyName; // todo: this should be got from join table metadata, not primaryColumn
const value = newEntity ? this.getEntityRelationValue(relation, newEntity.entity) : null;
const dbValue = this.getEntityRelationValue(relation, dbEntity);
@ -485,24 +436,14 @@ export class EntityPersistOperationBuilder {
}
if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) {
const subEntityWithId: EntityWithId = {
id: relationMetadata.getEntityId(subEntity),
entity: subEntity,
entityTarget: relationMetadata.target
};
const subEntityWithId = new EntityWithId(relationMetadata, subEntity);
const subOperations = this.findJunctionRemoveOperations(relationMetadata, subEntityWithId, newEntities, false);
subOperations.forEach(o => operations.push(o));
}
});
} else {
if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) {
const dbValueWithId: EntityWithId = {
id: relationMetadata.getEntityId(dbValue),
entity: dbValue,
entityTarget: relationMetadata.target
};
const dbValueWithId = new EntityWithId(relationMetadata, dbValue);
const subOperations = this.findJunctionRemoveOperations(relationMetadata, dbValueWithId, newEntities, false);
subOperations.forEach(o => operations.push(o));
}
@ -538,14 +479,13 @@ export class EntityPersistOperationBuilder {
return true;
const entityTarget = relation.target;
const newEntityRelationMetadata = this.entityMetadatas.findByTarget(entityTarget);
const dbEntityRelationMetadata = this.entityMetadatas.findByTarget(entityTarget);
return newEntityRelationMetadata.getEntityId(newEntity[relation.propertyName]) !== dbEntityRelationMetadata.getEntityId(dbEntity[relation.propertyName]);
const relationMetadata = this.entityMetadatas.findByTarget(entityTarget);
return !relationMetadata.compareEntities(newEntity[relation.propertyName], dbEntity[relation.propertyName]);
});
}
private findEntityWithId(entityWithIds: EntityWithId[], entityTarget: Function|string, id: any) {
return entityWithIds.find(entityWithId => entityWithId.id === id && entityWithId.entityTarget === entityTarget);
private findEntityWithId(entityWithIds: EntityWithId[], entityTarget: Function|string, id: ObjectLiteral) {
return entityWithIds.find(entityWithId => entityWithId.compareId(id) && entityWithId.entityTarget === entityTarget);
}
private checkCascadesAllowed(type: "insert"|"update"|"remove", metadata: EntityMetadata, relation: RelationMetadata) {

View File

@ -37,7 +37,9 @@ export class PersistOperationExecutor {
*/
executePersistOperation(persistOperation: PersistOperation) {
let isTransactionStartedByItself = false;
// persistOperation.log();
return Promise.resolve()
.then(() => this.broadcastBeforeEvents(persistOperation))
.then(() => {
@ -152,7 +154,10 @@ export class PersistOperationExecutor {
private executeInsertOperations(persistOperation: PersistOperation) {
return Promise.all(persistOperation.inserts.map(operation => {
return this.insert(operation).then((insertId: any) => {
operation.entityId = insertId;
const metadata = this.entityMetadatas.findByTarget(operation.target);
if (insertId && metadata.hasGeneratedColumn) {
operation.entityId = { [metadata.generatedColumn.propertyName]: insertId };
}
});
}));
}
@ -258,7 +263,10 @@ export class PersistOperationExecutor {
private updateIdsOfInsertedEntities(persistOperation: PersistOperation) {
persistOperation.inserts.forEach(insertOperation => {
const metadata = this.entityMetadatas.findByTarget(insertOperation.target);
insertOperation.entity[metadata.primaryColumn.propertyName] = insertOperation.entityId;
metadata.primaryColumns.forEach(primaryColumn => {
if (insertOperation.entityId)
insertOperation.entity[primaryColumn.propertyName] = insertOperation.entityId[primaryColumn.propertyName];
});
});
}
@ -301,28 +309,33 @@ export class PersistOperationExecutor {
persistOperation.removes.forEach(removeOperation => {
const metadata = this.entityMetadatas.findByTarget(removeOperation.target);
const removedEntity = persistOperation.allPersistedEntities.find(allNewEntity => {
return allNewEntity.entityTarget === removeOperation.target && allNewEntity.id === removeOperation.entity[metadata.primaryColumn.propertyName];
return allNewEntity.entityTarget === removeOperation.target && allNewEntity.compareId(metadata.getEntityIdMap(removeOperation.entity)!);
});
if (removedEntity)
removedEntity.entity[metadata.primaryColumn.propertyName] = undefined;
if (removedEntity) {
metadata.primaryColumns.forEach(primaryColumn => {
removedEntity.entity[primaryColumn.propertyName] = undefined;
});
}
});
}
private findUpdateOperationForEntity(operations: UpdateByRelationOperation[], insertOperations: InsertOperation[], target: any): ObjectLiteral {
// we are using firstPrimaryColumn here because this method is used only in executeInsertClosureTableOperations method
// which means only for tree tables, but multiple primary keys are not supported in tree tables
let updateMap: ObjectLiteral = {};
operations
.forEach(operation => { // duplication with updateByRelation method
const metadata = this.entityMetadatas.findByTarget(operation.insertOperation.target);
const relatedInsertOperation = insertOperations.find(o => o.entity === operation.targetEntity);
const idInInserts = relatedInsertOperation ? relatedInsertOperation.entityId : null;
if (operation.updatedRelation.isOneToMany) {
const metadata = this.entityMetadatas.findByTarget(operation.insertOperation.target);
const idInInserts = relatedInsertOperation && relatedInsertOperation.entityId ? relatedInsertOperation.entityId[metadata.firstPrimaryColumn.propertyName] : null;
if (operation.insertOperation.entity === target)
updateMap[operation.updatedRelation.inverseRelation.propertyName] = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
updateMap[operation.updatedRelation.inverseRelation.propertyName] = operation.targetEntity[metadata.firstPrimaryColumn.propertyName] || idInInserts;
} else {
if (operation.targetEntity === target)
updateMap[operation.updatedRelation.propertyName] = operation.insertOperation.entityId;
if (operation.targetEntity === target && operation.insertOperation.entityId)
updateMap[operation.updatedRelation.propertyName] = operation.insertOperation.entityId[metadata.firstPrimaryColumn.propertyName];
}
});
@ -330,26 +343,36 @@ export class PersistOperationExecutor {
}
private updateByRelation(operation: UpdateByRelationOperation, insertOperations: InsertOperation[]) {
let tableName: string, relationName: string, relationId: any, idColumn: string, id: any;
if (!operation.insertOperation.entityId)
throw new Error(`insert operation does not have entity id`);
let tableName: string, relationName: string, relationId: ObjectLiteral, idColumn: string, id: any, updateMap: ObjectLiteral;
const relatedInsertOperation = insertOperations.find(o => o.entity === operation.targetEntity);
const idInInserts = relatedInsertOperation ? relatedInsertOperation.entityId : null;
if (operation.updatedRelation.isOneToMany || operation.updatedRelation.isOneToOneNotOwner) {
const metadata = this.entityMetadatas.findByTarget(operation.insertOperation.target);
const idInInserts = relatedInsertOperation && relatedInsertOperation.entityId ? relatedInsertOperation.entityId[metadata.firstPrimaryColumn.propertyName] : null;
tableName = metadata.table.name;
relationName = operation.updatedRelation.inverseRelation.name;
relationId = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
idColumn = metadata.primaryColumn.name;
id = operation.insertOperation.entityId;
relationId = operation.targetEntity[metadata.firstPrimaryColumn.propertyName] || idInInserts; // todo: make sure idInInserts is always a map
// relationId = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
// idColumn = metadata.primaryColumn.name;
// id = operation.insertOperation.entityId;
updateMap = operation.insertOperation.entityId;
} else {
const metadata = this.entityMetadatas.findByTarget(operation.entityTarget);
const idInInserts = relatedInsertOperation && relatedInsertOperation.entityId ? relatedInsertOperation.entityId[metadata.firstPrimaryColumn.propertyName] : null;
tableName = metadata.table.name;
relationName = operation.updatedRelation.name;
relationId = operation.insertOperation.entityId;
idColumn = metadata.primaryColumn.name;
id = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
relationId = operation.insertOperation.entityId[metadata.firstPrimaryColumn.propertyName]; // todo: make sure entityId is always a map
// idColumn = metadata.primaryColumn.name;
// id = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
updateMap = metadata.getEntityIdMap(operation.targetEntity) || idInInserts; // todo: make sure idInInserts always object even when id is single!!!
}
return this.queryRunner.update(tableName, { [relationName]: relationId }, { [idColumn]: id });
return this.queryRunner.update(tableName, { [relationName]: relationId }, updateMap);
}
private updateInverseRelation(operation: UpdateByInverseSideOperation, insertOperations: InsertOperation[]) {
@ -357,22 +380,22 @@ export class PersistOperationExecutor {
const fromEntityMetadata = this.entityMetadatas.findByTarget(operation.fromEntityTarget);
const tableName = targetEntityMetadata.table.name;
const targetRelation = operation.fromRelation.inverseRelation;
const idColumn = targetEntityMetadata.primaryColumn.name;
const id = targetEntityMetadata.getEntityId(operation.targetEntity);
const updateMap = targetEntityMetadata.getEntityIdMap(operation.targetEntity);
if (!updateMap) return; // todo: is return correct here?
const fromEntityInsertOperation = insertOperations.find(o => o.entity === operation.fromEntity);
let targetEntityId: any; // todo: better do it during insertion - pass UpdateByInverseSideOperation[] to insert and do it there
if (operation.operationType === "remove") {
targetEntityId = null;
} else {
if (fromEntityInsertOperation && targetRelation.joinColumn.referencedColumn === fromEntityMetadata.primaryColumn) {
targetEntityId = fromEntityInsertOperation.entityId;
if (fromEntityInsertOperation && fromEntityInsertOperation.entityId && targetRelation.joinColumn.referencedColumn === fromEntityMetadata.firstPrimaryColumn) {
targetEntityId = fromEntityInsertOperation.entityId[fromEntityMetadata.firstPrimaryColumn.name];
} else {
targetEntityId = operation.fromEntity[targetRelation.joinColumn.referencedColumn.name];
}
}
return this.queryRunner.update(tableName, { [targetRelation.name]: targetEntityId }, { [idColumn]: id });
return this.queryRunner.update(tableName, { [targetRelation.name]: targetEntityId }, updateMap);
}
private update(updateOperation: UpdateOperation) {
@ -386,7 +409,7 @@ export class PersistOperationExecutor {
updateOperation.relations.forEach(relation => {
const value = this.getEntityRelationValue(relation, entity);
values[relation.name] = value !== null && value !== undefined ? value[relation.inverseEntityMetadata.primaryColumn.propertyName] : null;
values[relation.name] = value !== null && value !== undefined ? value[relation.inverseEntityMetadata.firstPrimaryColumn.propertyName] : null; // todo: should not have a call to primaryColumn, instead join column metadata should be used
});
// if number of updated columns = 0 no need to update updated date and version columns
@ -399,15 +422,18 @@ export class PersistOperationExecutor {
if (metadata.hasVersionColumn)
values[metadata.versionColumn.name] = this.driver.preparePersistentValue(entity[metadata.versionColumn.propertyName] + 1, metadata.versionColumn);
return this.queryRunner.update( metadata.table.name, values, { [metadata.primaryColumn.name]: metadata.getEntityId(entity) });
return this.queryRunner.update(metadata.table.name, values, metadata.getEntityIdMap(entity)!);
}
private updateDeletedRelations(removeOperation: RemoveOperation) { // todo: check if both many-to-one deletions work too
if (!removeOperation.fromEntityId)
throw new Error(`remove operation does not have entity id`);
if (removeOperation.relation) {
return this.queryRunner.update(
removeOperation.fromMetadata.table.name,
{ [removeOperation.relation.name]: null },
{ [removeOperation.fromMetadata.primaryColumn.name]: removeOperation.fromEntityId }
removeOperation.fromEntityId
);
}
@ -416,7 +442,8 @@ export class PersistOperationExecutor {
private delete(target: Function|string, entity: any) {
const metadata = this.entityMetadatas.findByTarget(target);
return this.queryRunner.delete(metadata.table.name, { [metadata.primaryColumn.name]: entity[metadata.primaryColumn.propertyName] });
console.log("getEntityIdOrIds: " , metadata.getEntityIdMap(entity));
return this.queryRunner.delete(metadata.table.name, metadata.getEntityIdMap(entity)!);
}
private insert(operation: InsertOperation) {
@ -440,7 +467,7 @@ export class PersistOperationExecutor {
.map(relation => {
const value = this.getEntityRelationValue(relation, entity);
if (value !== null && value !== undefined) // in the case if relation has null, which can be saved
return value[relation.inverseEntityMetadata.primaryColumn.propertyName];
return value[relation.inverseEntityMetadata.firstPrimaryColumn.propertyName]; // todo: it should be get by field set in join column in the relation metadata
return value;
});
@ -482,19 +509,24 @@ export class PersistOperationExecutor {
}
private insertIntoClosureTable(operation: InsertOperation, updateMap: ObjectLiteral) {
// here we can only support to work only with single primary key entities
const entity = operation.entity;
const metadata = this.entityMetadatas.findByTarget(operation.target);
const parentEntity = entity[metadata.treeParentRelation.propertyName];
let parentEntityId: any = 0;
if (parentEntity && parentEntity[metadata.primaryColumn.propertyName]) {
parentEntityId = parentEntity[metadata.primaryColumn.propertyName];
if (parentEntity && parentEntity[metadata.firstPrimaryColumn.propertyName]) {
parentEntityId = parentEntity[metadata.firstPrimaryColumn.propertyName];
} else if (updateMap && updateMap[metadata.treeParentRelation.propertyName]) { // todo: name or propertyName: depend how update will be implemented. or even find relation of this treeParent and use its name?
parentEntityId = updateMap[metadata.treeParentRelation.propertyName];
}
return this.queryRunner.insertIntoClosureTable(metadata.closureJunctionTable.table.name, operation.entityId, parentEntityId, metadata.hasTreeLevelColumn)
if (!operation.entityId)
throw new Error(`operation does not have entity id`);
return this.queryRunner.insertIntoClosureTable(metadata.closureJunctionTable.table.name, operation.entityId[metadata.firstPrimaryColumn.propertyName], parentEntityId, metadata.hasTreeLevelColumn)
/*.then(() => {
// we also need to update children count in parent
if (parentEntity && parentEntityId) {
@ -509,8 +541,11 @@ export class PersistOperationExecutor {
const metadata = this.entityMetadatas.findByTarget(operation.target);
if (metadata.hasTreeLevelColumn && operation.treeLevel) {
if (!operation.entityId)
throw new Error(`remove operation does not have entity id`);
const values = { [metadata.treeLevelColumn.name]: operation.treeLevel };
return this.queryRunner.update(metadata.table.name, values, { [metadata.primaryColumn.name]: operation.entityId });
return this.queryRunner.update(metadata.table.name, values, operation.entityId);
}
return Promise.resolve();
@ -518,6 +553,8 @@ export class PersistOperationExecutor {
}
private insertJunctions(junctionOperation: JunctionInsertOperation, insertOperations: InsertOperation[]) {
// I think here we can only support to work only with single primary key entities
const junctionMetadata = junctionOperation.metadata;
const metadata1 = this.entityMetadatas.findByTarget(junctionOperation.entity1Target);
const metadata2 = this.entityMetadatas.findByTarget(junctionOperation.entity2Target);
@ -525,20 +562,20 @@ export class PersistOperationExecutor {
const insertOperation1 = insertOperations.find(o => o.entity === junctionOperation.entity1);
const insertOperation2 = insertOperations.find(o => o.entity === junctionOperation.entity2);
let id1 = junctionOperation.entity1[metadata1.primaryColumn.propertyName];
let id2 = junctionOperation.entity2[metadata2.primaryColumn.propertyName];
let id1 = junctionOperation.entity1[metadata1.firstPrimaryColumn.propertyName];
let id2 = junctionOperation.entity2[metadata2.firstPrimaryColumn.propertyName];
if (!id1) {
if (insertOperation1) {
id1 = insertOperation1.entityId;
if (insertOperation1 && insertOperation1.entityId) {
id1 = insertOperation1.entityId[metadata1.firstPrimaryColumn.propertyName];
} else {
throw new Error(`Insert operation for ${junctionOperation.entity1} was not found.`);
}
}
if (!id2) {
if (insertOperation2) {
id2 = insertOperation2.entityId;
if (insertOperation2 && insertOperation2.entityId) {
id2 = insertOperation2.entityId[metadata2.firstPrimaryColumn.propertyName];
} else {
throw new Error(`Insert operation for ${junctionOperation.entity2} was not found.`);
}
@ -556,12 +593,13 @@ export class PersistOperationExecutor {
}
private removeJunctions(junctionOperation: JunctionRemoveOperation) {
// I think here we can only support to work only with single primary key entities
const junctionMetadata = junctionOperation.metadata;
const metadata1 = this.entityMetadatas.findByTarget(junctionOperation.entity1Target);
const metadata2 = this.entityMetadatas.findByTarget(junctionOperation.entity2Target);
const columns = junctionMetadata.columns.map(column => column.name);
const id1 = junctionOperation.entity1[metadata1.primaryColumn.propertyName];
const id2 = junctionOperation.entity2[metadata2.primaryColumn.propertyName];
const id1 = junctionOperation.entity1[metadata1.firstPrimaryColumn.propertyName];
const id2 = junctionOperation.entity2[metadata2.firstPrimaryColumn.propertyName];
return this.queryRunner.delete(junctionMetadata.table.name, { [columns[0]]: id1, [columns[1]]: id2 });
}

View File

@ -1,3 +1,4 @@
import {ObjectLiteral} from "../../common/ObjectLiteral";
/**
*/
export class InsertOperation {
@ -6,7 +7,7 @@ export class InsertOperation {
constructor(public target: Function|string, // todo: probably should be metadata here
public entity: any,
public entityId?: number,
public entityId?: ObjectLiteral|undefined, // entity ids it should be instead
public date = new Date()) {
}
}

View File

@ -5,13 +5,28 @@ import {JunctionInsertOperation} from "./JunctionInsertOperation";
import {JunctionRemoveOperation} from "./JunctionRemoveOperation";
import {UpdateByRelationOperation} from "./UpdateByRelationOperation";
import {UpdateByInverseSideOperation} from "./UpdateByInverseSideOperation";
import {EntityMetadata} from "../../metadata/EntityMetadata";
import {ObjectLiteral} from "../../common/ObjectLiteral";
/**
*/
export interface EntityWithId {
id: any;
export class EntityWithId { // todo: move entity with id creation into metadata?
entityTarget: Function|string;
entity: any;
constructor(public metadata: EntityMetadata, entity: ObjectLiteral) {
// todo: check id usage
this.entity = entity;
this.entityTarget = metadata.target;
}
get id() {
return this.metadata.getEntityIdMap(this.entity);
}
compareId(id: ObjectLiteral): boolean { // todo: store metadata in this class and use compareIds of the metadata class instead of this duplication
return this.metadata.compareIds(this.id, id);
}
}
/**

View File

@ -1,14 +1,15 @@
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {EntityMetadata} from "../../metadata/EntityMetadata";
import {ObjectLiteral} from "../../common/ObjectLiteral";
/**
*/
export class RemoveOperation {
constructor(public target: Function|string, // todo: probably should be metadata here
public entity: any,
public entityId: any,
public entityId: ObjectLiteral,
public fromMetadata: EntityMetadata, // todo: use relation.metadata instead?
public relation: RelationMetadata|undefined,
public fromEntityId: any) {
public fromEntityId: ObjectLiteral|undefined) {
}
}

View File

@ -1,12 +1,13 @@
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {ObjectLiteral} from "../../common/ObjectLiteral";
/**
*/
export class UpdateOperation {
constructor(public target: Function|string,
public entity: any,
public entityId: any,
public entityId: ObjectLiteral,
public columns: ColumnMetadata[],
public relations: RelationMetadata[],
public date = new Date()) {

View File

@ -482,9 +482,18 @@ export class QueryBuilder<Entity> {
if (this.firstResult || this.maxResults) {
const [sql, parameters] = this.getSqlWithParameters();
const distinctAlias = this.driver.escapeTableName("distinctAlias");
const metadata = this.entityMetadatas.findByTarget(this.fromEntity.alias.target);
let idsQuery = `SELECT DISTINCT(${this.driver.escapeTableName("distinctAlias")}.${this.driver.escapeAliasName(mainAliasName + "_" + metadata.primaryColumn.name)}) as ids`;
idsQuery += ` FROM (${sql}) ${this.driver.escapeTableName("distinctAlias")}`; // TODO: WHAT TO DO WITH PARAMETERS HERE? DO THEY WORK?
let idsQuery = `SELECT `;
idsQuery += metadata.primaryColumns.map((primaryColumn, index) => {
const propertyName = this.driver.escapeAliasName(mainAliasName + "_" + primaryColumn.name);
if (index === 0) {
return `DISTINCT(${distinctAlias}.${propertyName}) as ids_${primaryColumn.name}`;
} else {
return `${distinctAlias}.${propertyName}) as ids_${primaryColumn.name}`;
}
}).join(", ");
idsQuery += ` FROM (${sql}) ${distinctAlias}`; // TODO: WHAT TO DO WITH PARAMETERS HERE? DO THEY WORK?
if (this.maxResults)
idsQuery += " LIMIT " + this.maxResults;
if (this.firstResult)
@ -493,13 +502,25 @@ export class QueryBuilder<Entity> {
const results = await queryRunner.query(idsQuery, parameters)
.then((results: any[]) => {
scalarResults = results;
const ids = results.map(result => result["ids"]);
if (ids.length === 0)
if (results.length === 0)
return [];
const queryWithIds = this.clone({ queryRunner: queryRunner })
.andWhere(mainAliasName + "." + metadata.primaryColumn.propertyName + " IN (:ids)", { ids: ids });
const [queryWithIdsSql, queryWithIdsParameters] = queryWithIds.getSqlWithParameters();
let condition = "";
const parameters: ObjectLiteral = {};
if (metadata.hasMultiplePrimaryKeys) {
condition = results.map(result => {
return metadata.primaryColumns.map(primaryColumn => {
parameters["ids_" + primaryColumn.propertyName] = result["ids_" + primaryColumn.propertyName];
return mainAliasName + "." + primaryColumn.propertyName + "=:ids_" + primaryColumn.propertyName;
}).join(" AND ");
}).join(" OR ");
} else {
parameters["ids"] = results.map(result => result["ids_" + metadata.firstPrimaryColumn.propertyName]);
condition = mainAliasName + "." + metadata.firstPrimaryColumn.propertyName + " IN (:ids)";
}
const [queryWithIdsSql, queryWithIdsParameters] = this.clone({ queryRunner: queryRunner })
.andWhere(condition, parameters)
.getSqlWithParameters();
return (queryRunner as QueryRunner).query(queryWithIdsSql, queryWithIdsParameters);
})
.then(results => {
@ -551,6 +572,7 @@ export class QueryBuilder<Entity> {
await queryRunner.release();
}
// console.log("qb results : ", results);
return results;
}
}
@ -630,10 +652,12 @@ export class QueryBuilder<Entity> {
// if (relationCountMeta.condition)
// condition += relationCountMeta.condition;
// relationCountMeta.alias.target;
// todo: FIX primaryColumn usages
const ids = relationCountMeta.entities
.map(entityWithMetadata => entityWithMetadata.entity[entityWithMetadata.metadata.primaryColumn.propertyName])
.filter(id => id !== undefined);
.map(entityWithMetadata => entityWithMetadata.metadata.getEntityIdMap(entityWithMetadata.entity))
.filter(idMap => idMap !== undefined)
.map(idMap => idMap![parentMetadata.primaryColumn.propertyName]);
if (!ids || !ids.length)
throw new Error(`No ids found to load relation counters`);
@ -688,8 +712,19 @@ export class QueryBuilder<Entity> {
const mainAlias = this.aliasMap.mainAlias.name;
const metadata = this.entityMetadatas.findByTarget(this.fromEntity.alias.target);
const distinctAlias = this.driver.escapeAliasName(mainAlias);
let countSql = `COUNT(` + metadata.primaryColumns.map((primaryColumn, index) => {
const propertyName = this.driver.escapeColumnName(primaryColumn.name);
if (index === 0) {
return `DISTINCT(${distinctAlias}.${propertyName})`;
} else {
return `${distinctAlias}.${propertyName})`;
}
}).join(", ") + ") as cnt";
const countQuery = this.clone({ queryRunner: queryRunner, skipOrderBys: true })
.select(`COUNT(DISTINCT(${this.driver.escapeAliasName(mainAlias)}.${this.driver.escapeColumnName(metadata.primaryColumn.name)})) as cnt`);
.select(countSql);
const [countQuerySql, countQueryParameters] = countQuery.getSqlWithParameters();
const results = await queryRunner.query(countQuerySql, countQueryParameters);
@ -711,7 +746,7 @@ export class QueryBuilder<Entity> {
]);
}
clone(options?: { queryRunner?: QueryRunner, skipOrderBys?: boolean, skipLimit?: boolean, skipOffset?: boolean }) {
clone(options?: { queryRunner?: QueryRunner, skipOrderBys?: boolean, skipLimit?: boolean, skipOffset?: boolean }): QueryBuilder<Entity> {
const qb = new QueryBuilder(this.driver, this.entityMetadatas, this.broadcaster, options ? options.queryRunner : undefined);
switch (this.type) {

View File

@ -33,7 +33,7 @@ export class PlainObjectToDatabaseEntityTransformer<Entity extends ObjectLiteral
metadata.primaryColumns.forEach(primaryColumn => {
queryBuilder
.where(alias + "." + primaryColumn.name + "=:" + primaryColumn.name)
.andWhere(alias + "." + primaryColumn.name + "=:" + primaryColumn.name)
.setParameter(primaryColumn.name, plainObject[primaryColumn.name]);
});

View File

@ -4,7 +4,6 @@ import {EntityMetadata} from "../../metadata/EntityMetadata";
import {OrmUtils} from "../../util/OrmUtils";
import {Driver} from "../../driver/Driver";
import {JoinMapping, RelationCountMeta} from "../QueryBuilder";
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
/**
* Transforms raw sql results returned from the database into entity object.
@ -29,6 +28,7 @@ export class RawSqlResultsToEntityTransformer {
// -------------------------------------------------------------------------
transform(rawSqlResults: any[]): any[] {
// console.log("rawSqlResults: ", rawSqlResults);
return this.groupAndTransform(rawSqlResults, this.aliasMap.mainAlias);
}
@ -48,9 +48,9 @@ export class RawSqlResultsToEntityTransformer {
const groupedResults = OrmUtils.groupBy(rawSqlResults, result => {
if (!metadata) return;
const columnName = metadata.primaryColumn.name;
return result[alias.name + "_" + columnName];
return metadata.primaryColumns.map(column => result[alias.name + "_" + column.name]).join("_"); // todo: check it
});
// console.log("groupedResults: ", groupedResults);
return groupedResults
.map(group => {
if (!metadata) return;

View File

@ -7,11 +7,7 @@ import {EntityPersistOperationBuilder} from "../persistment/EntityPersistOperati
import {PersistOperationExecutor} from "../persistment/PersistOperationExecutor";
import {EntityWithId} from "../persistment/operation/PersistOperation";
import {FindOptions, FindOptionsUtils} from "./FindOptions";
import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection";
import {Broadcaster} from "../subscriber/Broadcaster";
import {Driver} from "../driver/Driver";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {DatabaseConnection} from "../driver/DatabaseConnection";
import {QueryRunner} from "../driver/QueryRunner";
/**
@ -53,14 +49,17 @@ export class Repository<Entity extends ObjectLiteral> {
/**
* Checks if entity has an id.
* If entity contains compose ids, then it checks them all.
*/
hasId(entity: Entity): boolean {
const columnName = this.metadata.primaryColumn.propertyName;
return !!entity &&
entity.hasOwnProperty(columnName) &&
entity[columnName] !== null &&
entity[columnName] !== undefined &&
entity[columnName] !== "";
return this.metadata.primaryColumns.every(primaryColumn => {
const columnName = primaryColumn.propertyName;
return !!entity &&
entity.hasOwnProperty(columnName) &&
entity[columnName] !== null &&
entity[columnName] !== undefined &&
entity[columnName] !== "";
});
}
/**
@ -162,16 +161,8 @@ export class Repository<Entity extends ObjectLiteral> {
// need to find db entities that were not loaded by initialize method
const allDbEntities = await this.findNotLoadedIds(queryRunner, entityWithIds, allPersistedEntities);
const persistedEntity: EntityWithId = {
id: this.metadata.getEntityId(entityOrEntities),
entityTarget: this.metadata.target,
entity: entityOrEntities
};
const dbEntity: EntityWithId = {
id: this.metadata.getEntityId(loadedDbEntity),
entityTarget: this.metadata.target,
entity: loadedDbEntity
};
const persistedEntity = new EntityWithId(this.metadata, entityOrEntities);
const dbEntity = new EntityWithId(this.metadata, loadedDbEntity!); // todo: find if this can be executed if loadedDbEntity is empty
const persistOperation = this.entityPersistOperationBuilder.buildFullPersistment(this.metadata, dbEntity, persistedEntity, allDbEntities, allPersistedEntities);
const persistOperationExecutor = new PersistOperationExecutor(this.connection.driver, this.connection.entityMetadatas, this.connection.broadcaster, queryRunner); // todo: better to pass connection?
@ -204,21 +195,15 @@ export class Repository<Entity extends ObjectLiteral> {
.from(this.metadata.target, this.metadata.table.name);
const dbEntity = await this.plainObjectToDatabaseEntityTransformer.transform(entityOrEntities, this.metadata, queryBuilder);
(<any> entityOrEntities)[this.metadata.primaryColumn.name] = undefined;
this.metadata.primaryColumns.forEach(primaryColumn => {
(<any> entityOrEntities)[primaryColumn.name] = undefined;
});
const [dbEntities, allPersistedEntities] = await Promise.all([
this.extractObjectsById(dbEntity, this.metadata),
this.extractObjectsById(entityOrEntities, this.metadata)
]);
const entityWithId: EntityWithId = {
id: this.metadata.getEntityId(entityOrEntities),
entityTarget: this.metadata.target,
entity: entityOrEntities
};
const dbEntityWithId: EntityWithId = {
id: this.metadata.getEntityId(dbEntity),
entityTarget: this.metadata.target,
entity: dbEntity
};
const entityWithId = new EntityWithId(this.metadata, entityOrEntities);
const dbEntityWithId = new EntityWithId(this.metadata, dbEntity);
const persistOperation = this.entityPersistOperationBuilder.buildOnlyRemovement(this.metadata, dbEntityWithId, entityWithId, dbEntities, allPersistedEntities);
const persistOperationExecutor = new PersistOperationExecutor(this.connection.driver, this.connection.entityMetadatas, this.connection.broadcaster, queryRunner); // todo: better to pass connection?
@ -315,7 +300,15 @@ export class Repository<Entity extends ObjectLiteral> {
* Finds entity with given id.
*/
async findOneById(id: any, options?: FindOptions): Promise<Entity> {
return this.createFindQueryBuilder({ [this.metadata.primaryColumn.name]: id }, options)
const conditions: ObjectLiteral = {};
if (this.metadata.hasMultiplePrimaryKeys) {
this.metadata.primaryColumns.forEach(primaryColumn => {
conditions[primaryColumn.name] = id[primaryColumn.name];
});
} else {
conditions[this.metadata.firstPrimaryColumn.name] = id;
}
return this.createFindQueryBuilder(conditions, options)
.getSingleResult();
}
@ -395,25 +388,27 @@ export class Repository<Entity extends ObjectLiteral> {
*/
private findNotLoadedIds(queryRunner: QueryRunner, dbEntities: EntityWithId[], persistedEntities: EntityWithId[]): Promise<EntityWithId[]> {
const missingDbEntitiesLoad = persistedEntities
.filter(entityWithId => entityWithId.id !== null && entityWithId.id !== undefined)
.filter(entityWithId => !dbEntities.find(dbEntity => dbEntity.entityTarget === entityWithId.entityTarget && dbEntity.id === entityWithId.id))
.filter(entityWithId => entityWithId.id !== null && entityWithId.id !== undefined) // todo: not sure if this condition will work
.filter(entityWithId => !dbEntities.find(dbEntity => dbEntity.entityTarget === entityWithId.entityTarget && dbEntity.compareId(entityWithId.id!)))
.map(entityWithId => {
const metadata = this.connection.entityMetadatas.findByTarget(entityWithId.entityTarget);
const alias = (entityWithId.entityTarget as any).name;
const qb = new QueryBuilder(this.connection.driver, this.connection.entityMetadatas, this.connection.broadcaster, queryRunner)
.select(alias)
.from(entityWithId.entityTarget, alias)
.where(alias + "." + this.metadata.primaryColumn.propertyName + "=:id", { id: entityWithId.id })
.getSingleResult();
.from(entityWithId.entityTarget, alias);
const parameters: ObjectLiteral = {};
const condition = this.metadata.primaryColumns.map(primaryColumn => {
parameters[primaryColumn.propertyName] = entityWithId.id![primaryColumn.propertyName];
return alias + "." + primaryColumn.propertyName + "=:" + primaryColumn.propertyName;
}).join(" AND ");
const qbResult = qb.where(condition, parameters).getSingleResult();
// const repository = this.connection.getRepository(entityWithId.entityTarget as any); // todo: fix type
return qb.then(loadedEntity => {
return qbResult.then(loadedEntity => {
if (!loadedEntity) return undefined;
return <EntityWithId> {
id: (<any> loadedEntity)[metadata.primaryColumn.name],
entityTarget: metadata.target,
entity: loadedEntity
};
return new EntityWithId(metadata, loadedEntity);
});
});
@ -446,11 +441,8 @@ export class Repository<Entity extends ObjectLiteral> {
return Promise.all<any>(promises.filter(result => !!result)).then(() => {
if (!entityWithIds.find(entityWithId => entityWithId.entity === entity)) {
entityWithIds.push({
id: entity[metadata.primaryColumn.name],
entityTarget: metadata.target,
entity: entity
});
const entityWithId = new EntityWithId(metadata, entity);
entityWithIds.push(entityWithId);
}
return entityWithIds;

View File

@ -4,7 +4,6 @@ import {EntityWithId} from "../persistment/operation/PersistOperation";
import {FindOptions, FindOptionsUtils} from "./FindOptions";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {Repository} from "./Repository";
import {DatabaseConnection} from "../driver/DatabaseConnection";
import {QueryRunner} from "../driver/QueryRunner";
/**
@ -264,9 +263,23 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
*/
async removeById(id: any) {
const alias = this.metadata.table.name;
const parameters: ObjectLiteral = {};
let condition = "";
if (this.metadata.hasMultiplePrimaryKeys) {
condition = this.metadata.primaryColumns.map(primaryColumn => {
parameters[primaryColumn.propertyName] = id[primaryColumn.propertyName];
return alias + "." + primaryColumn.propertyName + "=:" + primaryColumn.propertyName;
}).join(" AND ");
} else {
condition = alias + "." + this.metadata.firstPrimaryColumn.propertyName + "=:id";
parameters["id"] = id;
}
await this.repository.createQueryBuilder(alias)
.delete()
.where(alias + "." + this.metadata.primaryColumn.propertyName + "=:id", { id: id })
.where(condition, parameters)
.execute();
}
@ -276,9 +289,24 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
*/
async removeByIds(ids: any[]) {
const alias = this.metadata.table.name;
const parameters: ObjectLiteral = {};
let condition = "";
if (this.metadata.hasMultiplePrimaryKeys) {
condition = ids.map((id, idIndex) => {
this.metadata.primaryColumns.map(primaryColumn => {
parameters[primaryColumn.propertyName + "_" + idIndex] = id[primaryColumn.propertyName];
return alias + "." + primaryColumn.propertyName + "=:" + primaryColumn.propertyName + "_" + idIndex;
}).join(" AND ");
}).join(" OR ");
} else {
condition = alias + "." + this.metadata.firstPrimaryColumn.propertyName + " IN (:ids)";
parameters["ids"] = ids;
}
await this.repository.createQueryBuilder(alias)
.delete()
.where(alias + "." + this.metadata.primaryColumn.propertyName + " IN (:ids)", { ids: ids })
.where(condition, parameters)
.execute();
}
@ -320,36 +348,6 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
return qb;
}
/**
* When ORM loads dbEntity it uses joins to load all entity dependencies. However when dbEntity is newly persisted
* to the db, but uses already exist in the db relational entities, those entities cannot be loaded, and will
* absent in dbEntities. To fix it, we need to go throw all persistedEntities we have, find out those which have
* ids, check if we did not load them yet and try to load them. This algorithm will make sure that all dbEntities
* are loaded. Further it will help insert operations to work correctly.
*/
private findNotLoadedIds(dbEntities: EntityWithId[], persistedEntities: EntityWithId[]): Promise<EntityWithId[]> {
const missingDbEntitiesLoad = persistedEntities
.filter(entityWithId => entityWithId.id !== null && entityWithId.id !== undefined)
.filter(entityWithId => !dbEntities.find(dbEntity => dbEntity.entityTarget === entityWithId.entityTarget && dbEntity.id === entityWithId.id))
.map(entityWithId => {
const metadata = this.connection.getMetadata(entityWithId.entityTarget as any); // todo: fix type
const repository = this.connection.getRepository(entityWithId.entityTarget as any); // todo: fix type
return repository.findOneById(entityWithId.id).then(loadedEntity => {
if (!loadedEntity) return undefined;
return <EntityWithId> {
id: (<any> loadedEntity)[metadata.primaryColumn.name],
entityTarget: metadata.target,
entity: loadedEntity
};
});
});
return Promise.all<EntityWithId>(missingDbEntitiesLoad).then(missingDbEntities => {
return dbEntities.concat(missingDbEntities.filter(dbEntity => !!dbEntity));
});
}
/**
* Extracts unique objects from given entity and all its downside relations.
*/
@ -374,11 +372,8 @@ export class SpecificRepository<Entity extends ObjectLiteral> {
return Promise.all<any>(promises.filter(result => !!result)).then(() => {
if (!entityWithIds.find(entityWithId => entityWithId.entity === entity)) {
entityWithIds.push({
id: entity[metadata.primaryColumn.name],
entityTarget: metadata.target,
entity: entity
});
const entityWithId = new EntityWithId(metadata, entity);
entityWithIds.push(entityWithId);
}
return entityWithIds;

View File

@ -27,10 +27,10 @@ export class TreeRepository<Entity> extends Repository<Entity> {
* Creates a query builder used to get descendants of the entities in a tree.
*/
createDescendantsQueryBuilder(alias: string, closureTableAlias: string, entity: Entity): QueryBuilder<Entity> {
const joinCondition = `${alias}.${this.metadata.primaryColumn.name}=${closureTableAlias}.descendant`;
const joinCondition = `${alias}.${this.metadata.firstPrimaryColumn.name}=${closureTableAlias}.descendant`;
return this.createQueryBuilder(alias)
.innerJoin(this.metadata.closureJunctionTable.table.name, closureTableAlias, "ON", joinCondition)
.where(`${closureTableAlias}.ancestor=${this.metadata.getEntityId(entity)}`);
.where(`${closureTableAlias}.ancestor=${this.metadata.getEntityIdMap(entity)![this.metadata.firstPrimaryColumn.propertyName]}`);
}
/**
@ -70,10 +70,10 @@ export class TreeRepository<Entity> extends Repository<Entity> {
* Creates a query builder used to get ancestors of the entities in the tree.
*/
createAncestorsQueryBuilder(alias: string, closureTableAlias: string, entity: Entity): QueryBuilder<Entity> {
const joinCondition = `${alias}.${this.metadata.primaryColumn.name}=${closureTableAlias}.ancestor`;
const joinCondition = `${alias}.${this.metadata.firstPrimaryColumn.name}=${closureTableAlias}.ancestor`;
return this.createQueryBuilder(alias)
.innerJoin(this.metadata.closureJunctionTable.table.name, closureTableAlias, "ON", joinCondition)
.where(`${closureTableAlias}.descendant=${this.metadata.getEntityId(entity)}`);
.where(`${closureTableAlias}.descendant=${this.metadata.getEntityIdMap(entity)![this.metadata.firstPrimaryColumn.propertyName]}`);
}
/**
@ -124,7 +124,7 @@ export class TreeRepository<Entity> extends Repository<Entity> {
private createRelationMaps(alias: string, scalarResults: any[]): { id: any, parentId: any }[] {
return scalarResults.map(scalarResult => {
return {
id: scalarResult[alias + "_" + this.metadata.primaryColumn.name],
id: scalarResult[alias + "_" + this.metadata.firstPrimaryColumn.name],
parentId: scalarResult[alias + "_" + this.metadata.treeParentRelation.name]
};
});
@ -132,10 +132,10 @@ export class TreeRepository<Entity> extends Repository<Entity> {
private buildChildrenEntityTree(entity: any, entities: any[], relationMaps: { id: any, parentId: any }[]): void {
const childProperty = this.metadata.treeChildrenRelation.propertyName;
const parentEntityId = entity[this.metadata.primaryColumn.propertyName];
const parentEntityId = entity[this.metadata.firstPrimaryColumn.propertyName];
const childRelationMaps = relationMaps.filter(relationMap => relationMap.parentId === parentEntityId);
const childIds = childRelationMaps.map(relationMap => relationMap.id);
entity[childProperty] = entities.filter(entity => childIds.indexOf(entity[this.metadata.primaryColumn.propertyName]) !== -1);
entity[childProperty] = entities.filter(entity => childIds.indexOf(entity[this.metadata.firstPrimaryColumn.propertyName]) !== -1);
entity[childProperty].forEach((childEntity: any) => {
this.buildChildrenEntityTree(childEntity, entities, relationMaps);
});
@ -143,13 +143,13 @@ export class TreeRepository<Entity> extends Repository<Entity> {
private buildParentEntityTree(entity: any, entities: any[], relationMaps: { id: any, parentId: any }[]): void {
const parentProperty = this.metadata.treeParentRelation.propertyName;
const entityId = entity[this.metadata.primaryColumn.propertyName];
const entityId = entity[this.metadata.firstPrimaryColumn.propertyName];
const parentRelationMap = relationMaps.find(relationMap => relationMap.id === entityId);
const parentEntity = entities.find(entity => {
if (!parentRelationMap)
return false;
return entity[this.metadata.primaryColumn.propertyName] === parentRelationMap.parentId;
return entity[this.metadata.firstPrimaryColumn.propertyName] === parentRelationMap.parentId;
});
if (parentEntity) {
entity[parentProperty] = parentEntity;