implemented basic class table inheritance support

This commit is contained in:
Umed Khudoiberdiev 2016-09-13 16:05:40 +05:00
parent 69371690c9
commit 0db64ebc6e
13 changed files with 474 additions and 142 deletions

View File

@ -35,19 +35,35 @@ createConnection(options).then(async connection => {
employee.id = 1;
employee.firstName = "umed";
employee.lastName = "khudoiberdiev";
employee.salary = 200000;
employee.salary = 300000;
console.log("saving the employee: ");
await employeeRepository.persist(employee);
console.log("employee has been saved: ", employee);
console.log("updating the employee: ");
employee.firstName = "zuma";
employee.lastName += "a";
await employeeRepository.persist(employee);
console.log("employee has been updated: ", employee);
console.log("now loading the employee: ");
const loadedEmployee = await employeeRepository.findOneById(1);
console.log("loaded employee: ", loadedEmployee);
loadedEmployee.firstName = "dima";
await employeeRepository.persist(loadedEmployee);
const allEmployees = await employeeRepository.findAndCount();
console.log("all employees: ", allEmployees);
console.log("deleting employee: ", loadedEmployee);
await employeeRepository.remove(loadedEmployee);
console.log("employee deleted");
console.log("-----------------");
let homesitterRepository = connection.getRepository(Homesitter);
/*let homesitterRepository = connection.getRepository(Homesitter);
const homesitter = new Homesitter();
homesitter.id = 2;
homesitter.firstName = "umed";
@ -93,6 +109,6 @@ createConnection(options).then(async connection => {
const secondStudent = await studentRepository.findOneById(1);
console.log("Non exist student: ", secondStudent);
const thirdStudent = await studentRepository.findOneById(2);
console.log("Non exist student: ", thirdStudent);
console.log("Non exist student: ", thirdStudent);*/
}).catch(error => console.log("Error: ", error));

View File

@ -9,7 +9,7 @@ import {PrimaryColumn} from "../../../src/decorator/columns/PrimaryColumn";
@DiscriminatorColumn({ name: "type", type: "string"})
export abstract class Person {
@PrimaryColumn("int")
@PrimaryColumn("int"/*, { generated: true }*/)
id: number;
@Column()

View File

@ -227,7 +227,7 @@ export class EntityMetadataBuilder {
indexMetadatas: indices,
embeddedMetadatas: embeddeds,
inheritanceType: mergedArgs.inheritance ? mergedArgs.inheritance.type : undefined,
discriminatorValue: discriminatorValueArgs ? discriminatorValueArgs.value : (tableArgs.target as any).name
discriminatorValue: discriminatorValueArgs ? discriminatorValueArgs.value : (tableArgs.target as any).name // todo: pass this to naming strategy to generate a name
}, lazyRelationsWrapper);
entityMetadatas.push(entityMetadata);
@ -251,7 +251,7 @@ export class EntityMetadataBuilder {
let joinColumnMetadata = mergedArgs.joinColumns.findByProperty(relation.propertyName);
if (!joinColumnMetadata && relation.isManyToOne) {
joinColumnMetadata = {
target: relation.target,
target: relation.entityMetadata.target,
propertyName: relation.propertyName
};
}
@ -423,14 +423,15 @@ export class EntityMetadataBuilder {
entityMetadatas
.filter(metadata => !!metadata.parentEntityMetadata)
.forEach(metadata => {
const parentEntityMetadataPrimaryColumn = metadata.parentEntityMetadata.firstPrimaryColumn;
const parentEntityMetadataPrimaryColumn = metadata.parentEntityMetadata.firstPrimaryColumn; // todo: make sure to create columns for all its primary columns
const columnName = namingStrategy.classTableInheritanceParentColumnName(metadata.parentEntityMetadata.table.name, parentEntityMetadataPrimaryColumn.propertyName);
const parentRelationColumn = new ColumnMetadata({
target: metadata.parentEntityMetadata.table.target,
propertyName: columnName,
propertyName: parentEntityMetadataPrimaryColumn.propertyName,
propertyType: parentEntityMetadataPrimaryColumn.propertyType,
mode: "virtual",
mode: "parentId",
options: <ColumnOptions> {
name: columnName,
type: parentEntityMetadataPrimaryColumn.type,
nullable: false,
primary: false

View File

@ -10,7 +10,7 @@ import {EmbeddedMetadata} from "./EmbeddedMetadata";
* For example, "primary" means that it will be a primary column, or "createDate" means that it will create a create
* date column.
*/
export type ColumnMode = "regular"|"virtual"|"createDate"|"updateDate"|"version"|"treeChildrenCount"|"treeLevel"|"discriminator";
export type ColumnMode = "regular"|"virtual"|"createDate"|"updateDate"|"version"|"treeChildrenCount"|"treeLevel"|"discriminator"|"parentId";
/**
* This metadata contains all information about entity's column.
@ -200,6 +200,13 @@ export class ColumnMetadata extends PropertyMetadata {
return this.mode === "virtual";
}
/**
* Indicates if column is a parent id. Parent id columns are not mapped to the entity.
*/
get isParentId() {
return this.mode === "parentId";
}
/**
* Indicates if column is discriminator. Discriminator columns are not mapped to the entity.
*/

View File

@ -131,9 +131,9 @@ export class EntityMetadata {
}
/**
* All columns of the entity, including columns that are coming from the embeddeds of this entity.
* Columns of the entity, including columns that are coming from the embeddeds of this entity.
*/
get columns() {
get columns(): ColumnMetadata[] {
let allColumns: ColumnMetadata[] = ([] as ColumnMetadata[]).concat(this._columns);
this.embeddeds.forEach(embedded => {
allColumns = allColumns.concat(embedded.columns);
@ -141,6 +141,29 @@ export class EntityMetadata {
return allColumns;
}
/**
* All columns of the entity, including columns that are coming from the embeddeds of this entity,
* and including columns from the parent entities.
*/
get allColumns(): ColumnMetadata[] {
let columns = this.columns;
if (this.parentEntityMetadata)
columns = columns.concat(this.parentEntityMetadata.columns);
return columns;
}
/**
* All relations of the entity, including relations from the parent entities.
*/
get allRelations(): RelationMetadata[] {
let relations = this.relations;
if (this.parentEntityMetadata)
relations = relations.concat(this.parentEntityMetadata.relations);
return relations;
}
/**
* Gets the name of the target.
*/
@ -178,20 +201,27 @@ export class EntityMetadata {
* Checks if table has generated column.
*/
get hasGeneratedColumn(): boolean {
return !!this._columns.find(column => column.isGenerated);
return !!this.generatedColumnIfExist;
}
/**
* Gets the column with generated flag.
*/
get generatedColumn(): ColumnMetadata {
const generatedColumn = this._columns.find(column => column.isGenerated);
const generatedColumn = this.generatedColumnIfExist;
if (!generatedColumn)
throw new Error(`Generated column was not found`);
return generatedColumn;
}
/**
* Gets the generated column if it exists, or returns undefined if it does not.
*/
get generatedColumnIfExist(): ColumnMetadata|undefined {
return this._columns.find(column => column.isGenerated);
}
/**
* Gets first primary column. In the case if table contains multiple primary columns it
* throws error.
@ -203,16 +233,52 @@ export class EntityMetadata {
return this.primaryColumns[0];
}
/**
* Checks if entity has any primary columns.
get hasPrimaryColumns(): ColumnMetadata[] {
}*/
/**
* Gets the primary columns.
*/
get primaryColumns(): ColumnMetadata[] {
// const originalPrimaryColumns = this._columns.filter(column => column.isPrimary);
// const parentEntityPrimaryColumns = this.hasParentIdColumn ? [this.parentIdColumn] : [];
// return originalPrimaryColumns.concat(parentEntityPrimaryColumns);
return this._columns.filter(column => column.isPrimary);
// const originalPrimaryColumns = this._columns.filter(column => column.isPrimary);
// const parentEntityPrimaryColumns = this.parentEntityMetadata ? this.parentEntityMetadata.primaryColumns : [];
// return originalPrimaryColumns.concat(parentEntityPrimaryColumns);
}
get primaryColumnsWithParentIdColumns(): ColumnMetadata[] {
return this.primaryColumns.concat(this.parentIdColumns);
}
get primaryColumnsWithParentPrimaryColumns(): ColumnMetadata[] {
return this.primaryColumns.concat(this.parentPrimaryColumns);
}
/**
* Gets the primary columns of the parent entity metadata.
* If parent entity metadata does not exist then it simply returns empty array.
*/
get parentPrimaryColumns(): ColumnMetadata[] {
if (this.parentEntityMetadata)
return this.parentEntityMetadata.primaryColumns;
return [];
}
/**
* Gets only primary columns owned by this entity.
*/
get ownPimaryColumns(): ColumnMetadata[] {
return this._columns.filter(column => column.isPrimary);
}
/**
* Checks if entity has a create date column.
*/
@ -300,6 +366,25 @@ export class EntityMetadata {
return column;
}
/**
* Checks if entity has a tree level column.
*/
get hasParentIdColumn(): boolean {
return !!this._columns.find(column => column.mode === "parentId");
}
get parentIdColumn(): ColumnMetadata {
const column = this._columns.find(column => column.mode === "parentId");
if (!column)
throw new Error(`Parent id column was not found in entity ${this.name}`);
return column;
}
get parentIdColumns(): ColumnMetadata[] {
return this._columns.filter(column => column.mode === "parentId");
}
/**
* Gets single (values of which does not contain arrays) relations.
*/
@ -447,7 +532,16 @@ export class EntityMetadata {
return undefined;
const map: ObjectLiteral = {};
this.primaryColumns.forEach(column => map[column.propertyName] = entity[column.propertyName]);
if (this.parentEntityMetadata) {
this.primaryColumnsWithParentIdColumns.forEach(column => {
map[column.propertyName] = entity[column.propertyName];
});
} else {
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;
});

View File

@ -145,7 +145,7 @@ export class RelationMetadata extends PropertyMetadata {
// ---------------------------------------------------------------------
constructor(args: RelationMetadataArgs) {
super(undefined, args.propertyName);
super(args.target, args.propertyName);
this.relationType = args.relationType;
if (args.inverseSideProperty)
@ -179,10 +179,6 @@ export class RelationMetadata extends PropertyMetadata {
// Accessors
// ---------------------------------------------------------------------
get target() {
return this.entityMetadata.target;
}
/**
* Gets the name of column in the database.
* //Cannot be used with many-to-many relations since they don't have a column in the database.

View File

@ -173,7 +173,6 @@ export class EntityPersistOperationBuilder {
const relMetadata = relation.inverseEntityMetadata;
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;
// const dbValue = this.getEntityRelationValue(relation, dbEntity);
@ -186,7 +185,7 @@ export class EntityPersistOperationBuilder {
return subDbEntity[relationIdColumnName] === subEntity[relationIdColumnName];
});*/
const dbValue = dbEntities.find(dbValue => {
return dbValue.entityTarget === valueTarget && dbValue.entity[referencedColumnName] === subEntity[relationIdColumnName];
return dbValue.entityTarget === relation.entityMetadata.target && dbValue.entity[referencedColumnName] === subEntity[relationIdColumnName];
});
if (dbValue) {
const dbValueWithId = new EntityWithId(relMetadata, dbValue.entity);
@ -197,7 +196,7 @@ export class EntityPersistOperationBuilder {
} else {
const dbValue = dbEntities.find(dbValue => {
return dbValue.entityTarget === valueTarget && dbValue.entity[referencedColumnName] === value[relationIdColumnName];
return dbValue.entityTarget === relation.entityMetadata.target && dbValue.entity[referencedColumnName] === value[relationIdColumnName];
});
if (dbValue) {
const dbValueWithId = new EntityWithId(relMetadata, dbValue.entity);
@ -454,34 +453,47 @@ export class EntityPersistOperationBuilder {
}
private diffColumns(metadata: EntityMetadata, newEntity: any, dbEntity: any) {
return metadata.columns
.filter(column => !column.isVirtual && !column.isDiscriminator && !column.isUpdateDate && !column.isVersion && !column.isCreateDate)
.filter(column => column.getEntityValue(newEntity) !== column.getEntityValue(dbEntity))
.filter(column => {
// filter out "relational columns" only in the case if there is a relation object in entity
if (!column.isInEmbedded && metadata.hasRelationWithDbName(column.propertyName)) {
const relation = metadata.findRelationWithDbName(column.propertyName);
if (newEntity[relation.propertyName] !== null && newEntity[relation.propertyName] !== undefined)
return false;
}
return true;
});
// console.log("differenting columns: newEntity: ", newEntity);
// console.log("differenting columns: dbEntity: ", dbEntity);
return metadata.allColumns.filter(column => {
if (column.isVirtual ||
column.isParentId ||
column.isDiscriminator ||
column.isUpdateDate ||
column.isVersion ||
column.isCreateDate ||
column.getEntityValue(newEntity) === column.getEntityValue(dbEntity))
return false;
// filter out "relational columns" only in the case if there is a relation object in entity
if (!column.isInEmbedded && metadata.hasRelationWithDbName(column.propertyName)) {
const relation = metadata.findRelationWithDbName(column.propertyName);
if (newEntity[relation.propertyName] !== null && newEntity[relation.propertyName] !== undefined)
return false;
}
return true;
});
}
private diffRelations(updatesByRelations: UpdateByRelationOperation[], metadata: EntityMetadata, newEntity: any, dbEntity: any) {
return metadata.relations
.filter(relation => relation.isManyToOne || (relation.isOneToOne && relation.isOwning))
.filter(relation => !updatesByRelations.find(operation => operation.targetEntity === newEntity && operation.updatedRelation === relation)) // try to find if there is update by relation operation - we dont need to generate update relation operation for this
.filter(relation => {
if (!newEntity[relation.propertyName] && !dbEntity[relation.propertyName])
return false;
if (!newEntity[relation.propertyName] || !dbEntity[relation.propertyName])
return true;
const entityTarget = relation.target;
const relationMetadata = this.entityMetadatas.findByTarget(entityTarget);
return !relationMetadata.compareEntities(newEntity[relation.propertyName], dbEntity[relation.propertyName]);
});
return metadata.allRelations.filter(relation => {
if (!relation.isManyToOne && !(relation.isOneToOne && relation.isOwning))
return false;
// try to find if there is update by relation operation - we dont need to generate update relation operation for this
if (updatesByRelations.find(operation => operation.targetEntity === newEntity && operation.updatedRelation === relation))
return false;
if (!newEntity[relation.propertyName] && !dbEntity[relation.propertyName])
return false;
if (!newEntity[relation.propertyName] || !dbEntity[relation.propertyName])
return true;
const relatedMetadata = this.entityMetadatas.findByTarget(relation.entityMetadata.target);
return !relatedMetadata.compareEntities(newEntity[relation.propertyName], dbEntity[relation.propertyName]);
});
}
private findEntityWithId(entityWithIds: EntityWithId[], entityTarget: Function|string, id: ObjectLiteral) {

View File

@ -12,6 +12,7 @@ import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOpera
import {RelationMetadata} from "../metadata/RelationMetadata";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {QueryRunner} from "../driver/QueryRunner";
import {EntityMetadata} from "../metadata/EntityMetadata";
/**
* Executes PersistOperation in the given connection.
@ -21,7 +22,7 @@ export class PersistOperationExecutor {
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(private driver: Driver,
private entityMetadatas: EntityMetadataCollection,
private broadcaster: Broadcaster,
@ -97,14 +98,14 @@ export class PersistOperationExecutor {
const persistedEntityWithId = persistOperation.allPersistedEntities.find(e => e.entity === insertOperation.entity);
if (!persistedEntityWithId)
throw new Error(`Persisted entity was not found`);
return this.broadcaster.broadcastBeforeInsertEvent(persistedEntityWithId.entity);
});
const updateEvents = persistOperation.updates.map(updateOperation => {
const persistedEntityWithId = persistOperation.allPersistedEntities.find(e => e.entity === updateOperation.entity);
if (!persistedEntityWithId)
throw new Error(`Persisted entity was not found`);
return this.broadcaster.broadcastBeforeUpdateEvent(persistedEntityWithId.entity, updateOperation.columns);
});
const removeEvents = persistOperation.removes.map(removeOperation => {
@ -112,7 +113,7 @@ export class PersistOperationExecutor {
// object does not exist anymore - its removed, and there is no way to find this removed object
return this.broadcaster.broadcastBeforeRemoveEvent(removeOperation.entity, removeOperation.entityId);
});
return Promise.all(insertEvents)
.then(() => Promise.all(updateEvents))
.then(() => Promise.all(removeEvents)); // todo: do we really should send it in order?
@ -122,19 +123,19 @@ export class PersistOperationExecutor {
* Broadcast all after persistment events - afterInsert, afterUpdate and afterRemove events.
*/
private broadcastAfterEvents(persistOperation: PersistOperation) {
const insertEvents = persistOperation.inserts.map(insertOperation => {
const persistedEntity = persistOperation.allPersistedEntities.find(e => e.entity === insertOperation.entity);
if (!persistedEntity)
throw new Error(`Persisted entity was not found`);
return this.broadcaster.broadcastAfterInsertEvent(persistedEntity.entity);
});
const updateEvents = persistOperation.updates.map(updateOperation => {
const persistedEntityWithId = persistOperation.allPersistedEntities.find(e => e.entity === updateOperation.entity);
if (!persistedEntityWithId)
throw new Error(`Persisted entity was not found`);
return this.broadcaster.broadcastAfterUpdateEvent(persistedEntityWithId.entity, updateOperation.columns);
});
const removeEvents = persistOperation.removes.map(removeOperation => {
@ -142,7 +143,7 @@ export class PersistOperationExecutor {
// object does not exist anymore - its removed, and there is no way to find this removed object
return this.broadcaster.broadcastAfterRemoveEvent(removeOperation.entity, removeOperation.entityId);
});
return Promise.all(insertEvents)
.then(() => Promise.all(updateEvents))
.then(() => Promise.all(removeEvents)); // todo: do we really should send it in order?
@ -151,14 +152,9 @@ export class PersistOperationExecutor {
/**
* Executes insert operations.
*/
private executeInsertOperations(persistOperation: PersistOperation) {
return Promise.all(persistOperation.inserts.map(operation => {
return this.insert(operation).then((insertId: any) => {
const metadata = this.entityMetadatas.findByTarget(operation.target);
if (insertId && metadata.hasGeneratedColumn) {
operation.entityId = { [metadata.generatedColumn.propertyName]: insertId };
}
});
private async executeInsertOperations(persistOperation: PersistOperation): Promise<void> {
await Promise.all(persistOperation.inserts.map(async operation => {
return this.insert(operation);
}));
}
@ -228,8 +224,8 @@ export class PersistOperationExecutor {
/**
* Executes update operations.
*/
private executeUpdateOperations(persistOperation: PersistOperation) {
return Promise.all(persistOperation.updates.map(updateOperation => {
private async executeUpdateOperations(persistOperation: PersistOperation): Promise<void> {
await Promise.all(persistOperation.updates.map(updateOperation => {
return this.update(updateOperation);
}));
}
@ -267,6 +263,10 @@ export class PersistOperationExecutor {
if (insertOperation.entityId)
insertOperation.entity[primaryColumn.propertyName] = insertOperation.entityId[primaryColumn.propertyName];
});
metadata.parentPrimaryColumns.forEach(primaryColumn => {
if (insertOperation.entityId)
insertOperation.entity[primaryColumn.propertyName] = insertOperation.entityId[primaryColumn.propertyName];
});
});
}
@ -328,6 +328,12 @@ export class PersistOperationExecutor {
.forEach(operation => { // duplication with updateByRelation method
const metadata = this.entityMetadatas.findByTarget(operation.insertOperation.target);
const relatedInsertOperation = insertOperations.find(o => o.entity === operation.targetEntity);
// todo: looks like first primary column should not be used there for two reasons:
// 1. there can be multiple primary columns, which one is mapped in the relation
// 2. parent primary column
// join column should be used instead
if (operation.updatedRelation.isOneToMany) {
const idInInserts = relatedInsertOperation && relatedInsertOperation.entityId ? relatedInsertOperation.entityId[metadata.firstPrimaryColumn.propertyName] : null;
if (operation.insertOperation.entity === target)
@ -352,7 +358,7 @@ export class PersistOperationExecutor {
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;
const idInInserts = relatedInsertOperation && relatedInsertOperation.entityId ? relatedInsertOperation.entityId[metadata.firstPrimaryColumn.propertyName] : null; // todo: use join column instead of primary column here
tableName = metadata.table.name;
relationName = operation.updatedRelation.inverseRelation.name;
relationId = operation.targetEntity[metadata.firstPrimaryColumn.propertyName] || idInInserts; // todo: make sure idInInserts is always a map
@ -364,7 +370,7 @@ export class PersistOperationExecutor {
} else {
const metadata = this.entityMetadatas.findByTarget(operation.entityTarget);
const idInInserts = relatedInsertOperation && relatedInsertOperation.entityId ? relatedInsertOperation.entityId[metadata.firstPrimaryColumn.propertyName] : null;
const idInInserts = relatedInsertOperation && relatedInsertOperation.entityId ? relatedInsertOperation.entityId[metadata.firstPrimaryColumn.propertyName] : null; // todo: use join column instead of primary column here
tableName = metadata.table.name;
relationName = operation.updatedRelation.name;
relationId = operation.insertOperation.entityId[metadata.firstPrimaryColumn.propertyName]; // todo: make sure entityId is always a map
@ -394,35 +400,101 @@ export class PersistOperationExecutor {
targetEntityId = operation.fromEntity[targetRelation.joinColumn.referencedColumn.name];
}
}
return this.queryRunner.update(tableName, { [targetRelation.name]: targetEntityId }, updateMap);
}
private update(updateOperation: UpdateOperation) {
private async update(updateOperation: UpdateOperation): Promise<void> {
const entity = updateOperation.entity;
const metadata = this.entityMetadatas.findByTarget(updateOperation.target);
const values: ObjectLiteral = {};
// we group by table name, because metadata can have different table names
const valueMaps: { tableName: string, metadata: EntityMetadata, values: ObjectLiteral }[] = [];
updateOperation.columns.forEach(column => {
values[column.name] = this.driver.preparePersistentValue(column.getEntityValue(entity), column);
if (!column.target) return;
const metadata = this.entityMetadatas.findByTarget(column.target);
let valueMap = valueMaps.find(valueMap => valueMap.tableName === metadata.table.name);
if (!valueMap) {
valueMap = { tableName: metadata.table.name, metadata: metadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[column.name] = this.driver.preparePersistentValue(column.getEntityValue(entity), column);
});
updateOperation.relations.forEach(relation => {
const metadata = this.entityMetadatas.findByTarget(relation.target);
let valueMap = valueMaps.find(valueMap => valueMap.tableName === metadata.table.name);
if (!valueMap) {
valueMap = { tableName: metadata.table.name, metadata: metadata, values: {} };
valueMaps.push(valueMap);
}
const value = this.getEntityRelationValue(relation, entity);
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
valueMap.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
if (Object.keys(values).length === 0)
return Promise.resolve();
if (Object.keys(valueMaps).length === 0)
return;
if (metadata.hasUpdateDateColumn)
values[metadata.updateDateColumn.name] = this.driver.preparePersistentValue(new Date(), metadata.updateDateColumn);
if (metadata.hasUpdateDateColumn) {
let valueMap = valueMaps.find(valueMap => valueMap.tableName === metadata.table.name);
if (!valueMap) {
valueMap = { tableName: metadata.table.name, metadata: metadata, values: {} };
valueMaps.push(valueMap);
}
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.getEntityIdMap(entity)!);
valueMap.values[metadata.updateDateColumn.name] = this.driver.preparePersistentValue(new Date(), metadata.updateDateColumn);
}
if (metadata.hasVersionColumn) {
let valueMap = valueMaps.find(valueMap => valueMap.tableName === metadata.table.name);
if (!valueMap) {
valueMap = { tableName: metadata.table.name, metadata: metadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[metadata.versionColumn.name] = this.driver.preparePersistentValue(entity[metadata.versionColumn.propertyName] + 1, metadata.versionColumn);
}
if (metadata.parentEntityMetadata) {
if (metadata.parentEntityMetadata.hasUpdateDateColumn) {
let valueMap = valueMaps.find(valueMap => valueMap.tableName === metadata.parentEntityMetadata.table.name);
if (!valueMap) {
valueMap = { tableName: metadata.parentEntityMetadata.table.name, metadata: metadata.parentEntityMetadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[metadata.parentEntityMetadata.updateDateColumn.name] = this.driver.preparePersistentValue(new Date(), metadata.parentEntityMetadata.updateDateColumn);
}
if (metadata.parentEntityMetadata.hasVersionColumn) {
let valueMap = valueMaps.find(valueMap => valueMap.tableName === metadata.parentEntityMetadata.table.name);
if (!valueMap) {
valueMap = { tableName: metadata.parentEntityMetadata.table.name, metadata: metadata.parentEntityMetadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[metadata.parentEntityMetadata.versionColumn.name] = this.driver.preparePersistentValue(entity[metadata.parentEntityMetadata.versionColumn.propertyName] + 1, metadata.parentEntityMetadata.versionColumn);
}
}
await Promise.all(valueMaps.map(valueMap => {
const conditions: ObjectLiteral = {};
if (valueMap.metadata.parentEntityMetadata && valueMap.metadata.parentEntityMetadata.inheritanceType === "class-table") { // valueMap.metadata.getEntityIdMap(entity)!
valueMap.metadata.parentIdColumns.forEach(column => {
conditions[column.propertyName] = entity[column.propertyName];
});
} else {
valueMap.metadata.primaryColumns.forEach(column => {
conditions[column.propertyName] = entity[column.propertyName];
});
}
return this.queryRunner.update(valueMap.tableName, valueMap.values, conditions);
}));
}
private updateDeletedRelations(removeOperation: RemoveOperation) { // todo: check if both many-to-one deletions work too
@ -434,27 +506,68 @@ export class PersistOperationExecutor {
removeOperation.fromMetadata.table.name,
{ [removeOperation.relation.name]: null },
removeOperation.fromEntityId
);
);
}
throw new Error("Remove operation relation is not set"); // todo: find out how its possible
}
private delete(target: Function|string, entity: any) {
private async delete(target: Function|string, entity: any): Promise<void> {
const metadata = this.entityMetadatas.findByTarget(target);
return this.queryRunner.delete(metadata.table.name, metadata.getEntityIdMap(entity)!);
if (metadata.parentEntityMetadata) {
const parentConditions: ObjectLiteral = {};
metadata.parentPrimaryColumns.forEach(column => {
parentConditions[column.name] = entity[column.propertyName];
});
await this.queryRunner.delete(metadata.parentEntityMetadata.table.name, parentConditions);
const childConditions: ObjectLiteral = {};
metadata.primaryColumnsWithParentIdColumns.forEach(column => {
childConditions[column.name] = entity[column.propertyName];
});
await this.queryRunner.delete(metadata.table.name, childConditions);
} else {
await this.queryRunner.delete(metadata.table.name, metadata.getEntityIdMap(entity)!);
}
}
private insert(operation: InsertOperation) {
const entity = operation.entity;
/**
* Inserts an entity from the given insert operation into the database.
* If entity has an generated column, then after saving new generated value will be stored to the InsertOperation.
* If entity uses class-table-inheritance, then multiple inserts may by performed to save all entities.
*/
private async insert(operation: InsertOperation): Promise<any> {
const metadata = this.entityMetadatas.findByTarget(operation.target);
let generatedId: any;
if (metadata.table.isClassTableChild) {
const parentValuesMap = this.collectColumnsAndValues(metadata.parentEntityMetadata, operation.entity, operation.date, undefined, metadata.discriminatorValue);
generatedId = await this.queryRunner.insert(metadata.parentEntityMetadata.table.name, parentValuesMap, metadata.parentEntityMetadata.generatedColumnIfExist);
const childValuesMap = this.collectColumnsAndValues(metadata, operation.entity, operation.date, generatedId);
const secondGeneratedId = await this.queryRunner.insert(metadata.table.name, childValuesMap, metadata.generatedColumnIfExist);
if (!generatedId && secondGeneratedId) generatedId = secondGeneratedId;
} else {
const valuesMap = this.collectColumnsAndValues(metadata, operation.entity, operation.date);
generatedId = await this.queryRunner.insert(metadata.table.name, valuesMap, metadata.generatedColumnIfExist);
}
// if there is a generated column and we have a generated id then store it in the insert operation for further use
if (metadata.parentEntityMetadata && metadata.parentEntityMetadata.hasGeneratedColumn && generatedId) {
operation.entityId = { [metadata.parentEntityMetadata.generatedColumn.propertyName]: generatedId };
} else if (metadata.hasGeneratedColumn && generatedId) {
operation.entityId = { [metadata.generatedColumn.propertyName]: generatedId };
}
}
private collectColumnsAndValues(metadata: EntityMetadata, entity: any, date: Date, parentIdColumnValue?: any, discriminatorValue?: any): ObjectLiteral {
const columns = metadata.columns
.filter(column => !column.isVirtual && !column.isDiscriminator && column.hasEntityValue(entity));
.filter(column => !column.isVirtual && !column.isParentId && !column.isDiscriminator && column.hasEntityValue(entity));
const columnNames = columns.map(column => column.name);
const values = columns.map(column => this.driver.preparePersistentValue(column.getEntityValue(entity), column));
const relationColumns = metadata.relations
.filter(relation => !relation.isManyToMany && relation.isOwning && !!relation.inverseEntityMetadata)
.filter(relation => entity.hasOwnProperty(relation.propertyName))
@ -476,12 +589,12 @@ export class PersistOperationExecutor {
if (metadata.hasCreateDateColumn) {
allColumns.push(metadata.createDateColumn.name);
allValues.push(this.driver.preparePersistentValue(operation.date, metadata.createDateColumn));
allValues.push(this.driver.preparePersistentValue(date, metadata.createDateColumn));
}
if (metadata.hasUpdateDateColumn) {
allColumns.push(metadata.updateDateColumn.name);
allValues.push(this.driver.preparePersistentValue(operation.date, metadata.updateDateColumn));
allValues.push(this.driver.preparePersistentValue(date, metadata.updateDateColumn));
}
if (metadata.hasVersionColumn) {
@ -491,25 +604,23 @@ export class PersistOperationExecutor {
if (metadata.hasDiscriminatorColumn) {
allColumns.push(metadata.discriminatorColumn.name);
allValues.push(this.driver.preparePersistentValue(metadata.discriminatorValue, metadata.discriminatorColumn));
allValues.push(this.driver.preparePersistentValue(discriminatorValue || metadata.discriminatorValue, metadata.discriminatorColumn));
}
if (metadata.hasTreeLevelColumn && metadata.hasTreeParentRelation) {
const parentEntity = entity[metadata.treeParentRelation.name]; // todo: are you sure here we should use name and not propertyName ?
const parentEntity = entity[metadata.treeParentRelation.propertyName];
const parentLevel = parentEntity ? (parentEntity[metadata.treeLevelColumn.propertyName] || 0) : 0;
allColumns.push(metadata.treeLevelColumn.name);
allValues.push(parentLevel + 1);
}
/*if (metadata.hasTreeChildrenCountColumn) {
allColumns.push(metadata.treeChildrenCountColumn.name);
allValues.push(0);
}*/
// console.log("inserting: ", this.zipObject(allColumns, allValues));
let generatedColumn = metadata.columns.find(column => column.isGenerated);
return this.queryRunner.insert(metadata.table.name, this.zipObject(allColumns, allValues), generatedColumn);
if (metadata.parentEntityMetadata && metadata.hasParentIdColumn) {
allColumns.push(metadata.parentIdColumn.name); // todo: should be array of primary keys
allValues.push(parentIdColumnValue || entity[metadata.parentEntityMetadata.firstPrimaryColumn.propertyName]); // todo: should be array of primary keys
}
return this.zipObject(allColumns, allValues);
}
private insertIntoClosureTable(operation: InsertOperation, updateMap: ObjectLiteral) {
@ -529,7 +640,7 @@ export class PersistOperationExecutor {
if (!operation.entityId)
throw new Error(`operation does not have entity id`);
// todo: this code does not take in count a primary column from the parent entity metadata
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
@ -566,6 +677,8 @@ export class PersistOperationExecutor {
const insertOperation1 = insertOperations.find(o => o.entity === junctionOperation.entity1);
const insertOperation2 = insertOperations.find(o => o.entity === junctionOperation.entity2);
// todo: firstPrimaryColumn should not be used there! use join column's properties instead!
let id1 = junctionOperation.entity1[metadata1.firstPrimaryColumn.propertyName];
let id2 = junctionOperation.entity2[metadata2.firstPrimaryColumn.propertyName];

View File

@ -77,6 +77,7 @@ export class QueryBuilder<Entity> {
private offset: number;
private firstResult: number;
private maxResults: number;
private ignoreParentTablesJoins: boolean = false;
// -------------------------------------------------------------------------
// Constructor
@ -553,6 +554,7 @@ export class QueryBuilder<Entity> {
const [sql, parameters] = this.getSqlWithParameters();
try {
// console.log(sql);
return await queryRunner.query(sql, parameters)
.then(results => {
scalarResults = results;
@ -722,7 +724,7 @@ export class QueryBuilder<Entity> {
const metadata = this.entityMetadatas.findByTarget(this.fromEntity.alias.target);
const distinctAlias = this.driver.escapeAliasName(mainAlias);
let countSql = `COUNT(` + metadata.primaryColumns.map((primaryColumn, index) => {
let countSql = `COUNT(` + metadata.primaryColumnsWithParentIdColumns.map((primaryColumn, index) => {
const propertyName = this.driver.escapeColumnName(primaryColumn.name);
if (index === 0) {
return `DISTINCT(${distinctAlias}.${propertyName})`;
@ -731,7 +733,8 @@ export class QueryBuilder<Entity> {
}
}).join(", ") + ") as cnt";
const countQuery = this.clone({ queryRunner: queryRunner, skipOrderBys: true })
const countQuery = this
.clone({ queryRunner: queryRunner, skipOrderBys: true, ignoreParentTablesJoins: true })
.select(countSql);
const [countQuerySql, countQueryParameters] = countQuery.getSqlWithParameters();
@ -757,8 +760,10 @@ export class QueryBuilder<Entity> {
]);
}
clone(options?: { queryRunner?: QueryRunner, skipOrderBys?: boolean, skipLimit?: boolean, skipOffset?: boolean }): QueryBuilder<Entity> {
clone(options?: { queryRunner?: QueryRunner, skipOrderBys?: boolean, skipLimit?: boolean, skipOffset?: boolean, ignoreParentTablesJoins?: boolean }): QueryBuilder<Entity> {
const qb = new QueryBuilder(this.driver, this.entityMetadatas, this.broadcaster, options ? options.queryRunner : undefined);
if (options && options.ignoreParentTablesJoins)
qb.ignoreParentTablesJoins = options.ignoreParentTablesJoins;
switch (this.type) {
case "select":
@ -884,6 +889,16 @@ export class QueryBuilder<Entity> {
}
});
if (!this.ignoreParentTablesJoins) {
const metadata = this.entityMetadatas.findByTarget(this.aliasMap.mainAlias.target);
if (metadata.parentEntityMetadata && metadata.parentIdColumns) {
const alias = "parentIdColumn_" + this.driver.escapeAliasName(metadata.parentEntityMetadata.table.name);
metadata.parentEntityMetadata.columns.forEach(column => {
allSelects.push(alias + "." + this.driver.escapeColumnName(column.name) + " AS " + alias + "_" + this.driver.escapeAliasName(column.name));
});
}
}
// add selects from relation id joins
this.joinRelationIds.forEach(join => {
// const joinMetadata = this.aliasMap.getEntityMetadataByAlias(join.alias);
@ -935,7 +950,6 @@ export class QueryBuilder<Entity> {
}
protected createWhereExpression() {
if (!this.wheres || !this.wheres.length) return "";
const conditions = this.wheres.map((where, index) => {
switch (where.type) {
@ -950,8 +964,9 @@ export class QueryBuilder<Entity> {
const mainMetadata = this.entityMetadatas.findByTarget(this.aliasMap.mainAlias.target);
if (mainMetadata.hasDiscriminatorColumn)
return " WHERE (" + conditions + ") AND " + mainMetadata.discriminatorColumn.name + "=:discriminatorColumnValue";
return ` WHERE ${ conditions.length ? "(" + conditions + ")" : "" } AND ${mainMetadata.discriminatorColumn.name}=:discriminatorColumnValue`;
if (!conditions.length) return "";
return " WHERE " + conditions;
}
@ -1015,7 +1030,7 @@ export class QueryBuilder<Entity> {
}
protected createJoinExpression() {
return this.joins.map(join => {
let joins = this.joins.map(join => {
const joinType = join.type; // === "INNER" ? "INNER" : "LEFT";
let joinTableName: string = join.tableName;
if (!joinTableName) {
@ -1077,6 +1092,20 @@ export class QueryBuilder<Entity> {
throw new Error("Unexpected relation type"); // this should not be possible
}
}).join(" ");
if (!this.ignoreParentTablesJoins) {
const metadata = this.entityMetadatas.findByTarget(this.aliasMap.mainAlias.target);
if (metadata.parentEntityMetadata && metadata.parentIdColumns) {
const alias = this.driver.escapeAliasName("parentIdColumn_" + metadata.parentEntityMetadata.table.name);
joins += " JOIN " + this.driver.escapeTableName(metadata.parentEntityMetadata.table.name)
+ " " + alias + " ON ";
joins += metadata.parentIdColumns.map(parentIdColumn => {
return this.aliasMap.mainAlias.name + "." + parentIdColumn.name + "=" + alias + "." + parentIdColumn.propertyName;
});
}
}
return joins;
}
protected createGroupByExpression() {

View File

@ -16,6 +16,9 @@ interface LoadMap {
*/
export class PlainObjectToDatabaseEntityTransformer<Entity extends ObjectLiteral> {
// constructor(protected namingStrategy: NamingStrategyInterface) {
// }
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
@ -33,9 +36,22 @@ export class PlainObjectToDatabaseEntityTransformer<Entity extends ObjectLiteral
metadata.primaryColumns.forEach(primaryColumn => {
queryBuilder
.andWhere(alias + "." + primaryColumn.name + "=:" + primaryColumn.name)
.setParameter(primaryColumn.name, plainObject[primaryColumn.name]);
.andWhere(alias + "." + primaryColumn.propertyName + "=:" + primaryColumn.propertyName)
.setParameter(primaryColumn.propertyName, plainObject[primaryColumn.propertyName]);
});
if (metadata.parentEntityMetadata) {
metadata.parentEntityMetadata.primaryColumns.forEach(primaryColumn => {
const parentIdColumn = metadata.parentIdColumns.find(parentIdColumn => {
return parentIdColumn.propertyName === primaryColumn.propertyName;
});
if (!parentIdColumn)
throw new Error(`Prent id column for the given primary column was not found.`);
queryBuilder
.andWhere(alias + "." + parentIdColumn.propertyName + "=:" + primaryColumn.propertyName)
.setParameter(primaryColumn.propertyName, plainObject[primaryColumn.propertyName]);
});
}
return await queryBuilder.getSingleResult();
}

View File

@ -48,15 +48,14 @@ export class RawSqlResultsToEntityTransformer {
const groupedResults = OrmUtils.groupBy(rawSqlResults, result => {
if (!metadata) return;
return metadata.primaryColumns.map(column => result[alias.name + "_" + column.name]).join("_"); // todo: check it
return metadata.primaryColumnsWithParentIdColumns.map(column => result[alias.name + "_" + column.name]).join("_"); // todo: check it
});
// console.log("groupedResults: ", groupedResults);
return groupedResults
.map(group => {
if (!metadata) return;
return this.transformIntoSingleResult(group.items, alias, metadata);
})
.filter(res => !!res);
return groupedResults.map(group => {
if (!metadata) return;
return this.transformIntoSingleResult(group.items, alias, metadata);
})
.filter(res => !!res);
}
@ -84,7 +83,7 @@ export class RawSqlResultsToEntityTransformer {
metadata.columns.forEach(column => {
const columnName = column.name;
const valueInObject = rawSqlResults[0][alias.name + "_" + columnName]; // we use zero index since its grouped data
if (valueInObject !== undefined && valueInObject !== null && column.propertyName && !column.isVirtual && !column.isDiscriminator) {
if (valueInObject !== undefined && valueInObject !== null && column.propertyName && !column.isVirtual && !column.isParentId && !column.isDiscriminator) {
const value = this.driver.prepareHydratedValue(valueInObject, column);
if (column.isInEmbedded) {
@ -99,6 +98,28 @@ export class RawSqlResultsToEntityTransformer {
}
});
// add parent tables metadata
// console.log(rawSqlResults);
if (metadata.parentEntityMetadata) {
metadata.parentEntityMetadata.columns.forEach(column => {
const columnName = column.name;
const valueInObject = rawSqlResults[0]["parentIdColumn_" + metadata.parentEntityMetadata.table.name + "_" + columnName]; // we use zero index since its grouped data
if (valueInObject !== undefined && valueInObject !== null && column.propertyName && !column.isVirtual && !column.isParentId && !column.isDiscriminator) {
const value = this.driver.prepareHydratedValue(valueInObject, column);
if (column.isInEmbedded) {
if (!entity[column.embeddedProperty])
entity[column.embeddedProperty] = column.embeddedMetadata.create();
entity[column.embeddedProperty][column.propertyName] = value;
} else {
entity[column.propertyName] = value;
}
hasData = true;
}
});
}
// if relation is loaded then go into it recursively and transform its values too
metadata.relations.forEach(relation => {
const relationAlias = this.aliasMap.findAliasByParent(alias.name, relation.propertyName);

View File

@ -3,7 +3,6 @@ import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetada
import {Broadcaster} from "../subscriber/Broadcaster";
import {Driver} from "../driver/Driver";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {Connection} from "../connection/Connection";
export class LazyRelationsWrapper {
@ -31,7 +30,7 @@ export class LazyRelationsWrapper {
if (relation.hasInverseSide) { // if we don't have inverse side then we can't select and join by relation from inverse side
qb.select(relation.propertyName)
.from(relation.inverseRelation.target, relation.propertyName)
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName);
} else {
qb.select(relation.propertyName)
@ -55,7 +54,7 @@ export class LazyRelationsWrapper {
if (relation.hasInverseSide) {
qb.select(relation.propertyName)
.from(relation.inverseRelation.target, relation.propertyName)
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName);
} else {
@ -63,7 +62,7 @@ export class LazyRelationsWrapper {
// loaded: category from post
qb.select(relation.propertyName) // category
.from(relation.type, relation.propertyName) // Category, category
.innerJoin(relation.target as Function, relation.entityMetadata.name, "ON",
.innerJoin(relation.entityMetadata.target as Function, relation.entityMetadata.name, "ON",
`${relation.entityMetadata.name}.${relation.propertyName}=:${relation.propertyName}Id`) // Post, post, post.category = categoryId
.setParameter(relation.propertyName + "Id", this[relation.referencedColumnName]);
}

View File

@ -52,14 +52,26 @@ export class Repository<Entity extends ObjectLiteral> {
* If entity contains compose ids, then it checks them all.
*/
hasId(entity: Entity): boolean {
return this.metadata.primaryColumns.every(primaryColumn => {
const columnName = primaryColumn.propertyName;
return !!entity &&
entity.hasOwnProperty(columnName) &&
entity[columnName] !== null &&
entity[columnName] !== undefined &&
entity[columnName] !== "";
});
// if (this.metadata.parentEntityMetadata) {
// return this.metadata.parentEntityMetadata.parentIdColumns.every(parentIdColumn => {
// const columnName = parentIdColumn.propertyName;
// return !!entity &&
// entity.hasOwnProperty(columnName) &&
// entity[columnName] !== null &&
// entity[columnName] !== undefined &&
// entity[columnName] !== "";
// });
// } else {
return this.metadata.primaryColumns.every(primaryColumn => {
const columnName = primaryColumn.propertyName;
return !!entity &&
entity.hasOwnProperty(columnName) &&
entity[columnName] !== null &&
entity[columnName] !== undefined &&
entity[columnName] !== "";
});
// }
}
/**
@ -200,7 +212,7 @@ export class Repository<Entity extends ObjectLiteral> {
.from(this.metadata.target, this.metadata.table.name);
const dbEntity = await this.plainObjectToDatabaseEntityTransformer.transform(entityOrEntities, this.metadata, queryBuilder);
this.metadata.primaryColumns.forEach(primaryColumn => entityOrEntities[primaryColumn.name] = undefined);
this.metadata.primaryColumnsWithParentPrimaryColumns.forEach(primaryColumn => entityOrEntities[primaryColumn.name] = undefined);
const [dbEntities, allPersistedEntities] = await Promise.all([
this.extractObjectsById(dbEntity, this.metadata),
this.extractObjectsById(entityOrEntities, this.metadata)
@ -311,8 +323,15 @@ export class Repository<Entity extends ObjectLiteral> {
this.metadata.primaryColumns.forEach(primaryColumn => {
conditions[primaryColumn.name] = id[primaryColumn.name];
});
this.metadata.parentIdColumns.forEach(primaryColumn => {
conditions[primaryColumn.name] = id[primaryColumn.propertyName];
});
} else {
conditions[this.metadata.firstPrimaryColumn.name] = id;
if (this.metadata.primaryColumns.length > 0) {
conditions[this.metadata.firstPrimaryColumn.name] = id;
} else if (this.metadata.parentIdColumns.length > 0) {
conditions[this.metadata.parentIdColumns[0].name] = id;
}
}
return this.createFindQueryBuilder(conditions, options)
.getSingleResult();
@ -411,10 +430,19 @@ export class Repository<Entity extends ObjectLiteral> {
.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 ");
let condition = "";
if (this.metadata.hasParentIdColumn) {
condition = this.metadata.parentIdColumns.map(parentIdColumn => {
parameters[parentIdColumn.propertyName] = entityWithId.id![parentIdColumn.propertyName];
return alias + "." + parentIdColumn.propertyName + "=:" + parentIdColumn.propertyName;
}).join(" AND ");
} else {
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
@ -433,7 +461,7 @@ export class Repository<Entity extends ObjectLiteral> {
/**
* Extracts unique objects from given entity and all its downside relations.
*/
private extractObjectsById(entity: any, metadata: EntityMetadata, entityWithIds: EntityWithId[] = []): Promise<EntityWithId[]> {
private extractObjectsById(entity: any, metadata: EntityMetadata, entityWithIds: EntityWithId[] = []): Promise<EntityWithId[]> { // todo: why promises used there?
const promises = metadata.relations.map(relation => {
const relMetadata = relation.inverseEntityMetadata;