mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
implemented basic class table inheritance support
This commit is contained in:
parent
69371690c9
commit
0db64ebc6e
@ -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));
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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];
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user