refactoring persistence

This commit is contained in:
Umed Khudoiberdiev 2017-10-31 15:47:53 +05:00
parent 525eba2174
commit 6c39bffc0c
19 changed files with 206 additions and 986 deletions

View File

@ -307,7 +307,7 @@ export class EntityManager {
* Executes fast and efficient UPDATE query.
* Does not check if entity exist in the database.
*/
async update<Entity>(target: ObjectType<Entity>|string, conditions: Partial<Entity>, partialEntity: DeepPartial<Entity>, options?: SaveOptions): Promise<void> {
async update<Entity>(target: ObjectType<Entity>|string, conditions: DeepPartial<Entity>, partialEntity: DeepPartial<Entity>, options?: SaveOptions): Promise<void> {
// todo: in the future create UpdateResult with query result information
// todo: think if subscribers and listeners can be executed here as well
@ -379,7 +379,7 @@ export class EntityManager {
* Executes fast and efficient DELETE query.
* Does not check if entity exist in the database.
*/
async delete<Entity>(targetOrEntity: ObjectType<Entity>|string, conditions: Partial<Entity>, options?: RemoveOptions): Promise<void> {
async delete<Entity>(targetOrEntity: ObjectType<Entity>|string, conditions: DeepPartial<Entity>, options?: RemoveOptions): Promise<void> {
// todo: in the future create DeleteResult with query result information
// todo: think if subscribers and listeners can be executed here as well
@ -436,13 +436,13 @@ export class EntityManager {
* Counts entities that match given conditions.
* Useful for pagination.
*/
count<Entity>(entityClass: ObjectType<Entity>|string, conditions?: Partial<Entity>): Promise<number>;
count<Entity>(entityClass: ObjectType<Entity>|string, conditions?: DeepPartial<Entity>): Promise<number>;
/**
* Counts entities that match given find options or conditions.
* Useful for pagination.
*/
count<Entity>(entityClass: ObjectType<Entity>|string, optionsOrConditions?: FindManyOptions<Entity>|Partial<Entity>): Promise<number> {
count<Entity>(entityClass: ObjectType<Entity>|string, optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<number> {
const metadata = this.connection.getMetadata(entityClass);
const qb = this.createQueryBuilder(entityClass, FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || metadata.name);
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getCount();
@ -456,12 +456,12 @@ export class EntityManager {
/**
* Finds entities that match given conditions.
*/
find<Entity>(entityClass: ObjectType<Entity>|string, conditions?: Partial<Entity>): Promise<Entity[]>;
find<Entity>(entityClass: ObjectType<Entity>|string, conditions?: DeepPartial<Entity>): Promise<Entity[]>;
/**
* Finds entities that match given find options or conditions.
*/
find<Entity>(entityClass: ObjectType<Entity>|string, optionsOrConditions?: FindManyOptions<Entity>|Partial<Entity>): Promise<Entity[]> {
find<Entity>(entityClass: ObjectType<Entity>|string, optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<Entity[]> {
const metadata = this.connection.getMetadata(entityClass);
const qb = this.createQueryBuilder(entityClass, FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || metadata.name);
this.joinEagerRelations(qb, qb.alias, metadata);
@ -480,14 +480,14 @@ export class EntityManager {
* Also counts all entities that match given conditions,
* but ignores pagination settings (from and take options).
*/
findAndCount<Entity>(entityClass: ObjectType<Entity>|string, conditions?: Partial<Entity>): Promise<[Entity[], number]>;
findAndCount<Entity>(entityClass: ObjectType<Entity>|string, conditions?: DeepPartial<Entity>): Promise<[Entity[], number]>;
/**
* Finds entities that match given find options and conditions.
* Also counts all entities that match given conditions,
* but ignores pagination settings (from and take options).
*/
findAndCount<Entity>(entityClass: ObjectType<Entity>|string, optionsOrConditions?: FindManyOptions<Entity>|Partial<Entity>): Promise<[Entity[], number]> {
findAndCount<Entity>(entityClass: ObjectType<Entity>|string, optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<[Entity[], number]> {
const metadata = this.connection.getMetadata(entityClass);
const qb = this.createQueryBuilder(entityClass, FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || metadata.name);
this.joinEagerRelations(qb, qb.alias, metadata);
@ -504,13 +504,13 @@ export class EntityManager {
* Finds entities with ids.
* Optionally conditions can be applied.
*/
findByIds<Entity>(entityClass: ObjectType<Entity>|string, ids: any[], conditions?: Partial<Entity>): Promise<Entity[]>;
findByIds<Entity>(entityClass: ObjectType<Entity>|string, ids: any[], conditions?: DeepPartial<Entity>): Promise<Entity[]>;
/**
* Finds entities with ids.
* Optionally find options or conditions can be applied.
*/
findByIds<Entity>(entityClass: ObjectType<Entity>|string, ids: any[], optionsOrConditions?: FindManyOptions<Entity>|Partial<Entity>): Promise<Entity[]> {
findByIds<Entity>(entityClass: ObjectType<Entity>|string, ids: any[], optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<Entity[]> {
const metadata = this.connection.getMetadata(entityClass);
const qb = this.createQueryBuilder(entityClass, FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || metadata.name);
FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions);
@ -532,12 +532,12 @@ export class EntityManager {
/**
* Finds first entity that matches given conditions.
*/
findOne<Entity>(entityClass: ObjectType<Entity>|string, conditions?: Partial<Entity>, options?: FindOneOptions<Entity>): Promise<Entity|undefined>;
findOne<Entity>(entityClass: ObjectType<Entity>|string, conditions?: DeepPartial<Entity>, options?: FindOneOptions<Entity>): Promise<Entity|undefined>;
/**
* Finds first entity that matches given conditions.
*/
findOne<Entity>(entityClass: ObjectType<Entity>|string, idOrOptionsOrConditions: string|number|Date|ObjectID|FindOneOptions<Entity>|Partial<Entity>, maybeOptions?: FindOneOptions<Entity>): Promise<Entity|undefined> {
findOne<Entity>(entityClass: ObjectType<Entity>|string, idOrOptionsOrConditions: string|number|Date|ObjectID|FindOneOptions<Entity>|DeepPartial<Entity>, maybeOptions?: FindOneOptions<Entity>): Promise<Entity|undefined> {
const metadata = this.connection.getMetadata(entityClass);
let alias: string = metadata.name;
@ -582,7 +582,7 @@ export class EntityManager {
*
* @deprecated Use findOne(id) instead
*/
findOneById<Entity>(entityClass: ObjectType<Entity>|string, id: any, conditions?: Partial<Entity>): Promise<Entity|undefined>;
findOneById<Entity>(entityClass: ObjectType<Entity>|string, id: any, conditions?: DeepPartial<Entity>): Promise<Entity|undefined>;
/**
* Finds entity with given id.
@ -590,7 +590,7 @@ export class EntityManager {
*
* @deprecated Use findOne(id) instead
*/
findOneById<Entity>(entityClass: ObjectType<Entity>|string, id: any, optionsOrConditions?: FindOneOptions<Entity>|Partial<Entity>): Promise<Entity|undefined> {
findOneById<Entity>(entityClass: ObjectType<Entity>|string, id: any, optionsOrConditions?: FindOneOptions<Entity>|DeepPartial<Entity>): Promise<Entity|undefined> {
return (this.findOne as any)(entityClass, id, optionsOrConditions);
}

View File

@ -42,6 +42,7 @@ import {FindManyOptions} from "../find-options/FindManyOptions";
import {FindOptionsUtils} from "../find-options/FindOptionsUtils";
import {FindOneOptions} from "../find-options/FindOneOptions";
import {PlatformTools} from "../platform/PlatformTools";
import {DeepPartial} from "../common/DeepPartial";
/**
* Entity manager supposed to work with any entity, automatically find its repository and call its methods,
@ -145,7 +146,7 @@ export class MongoEntityManager extends EntityManager {
* Finds first entity that matches given conditions and/or find options.
*/
async findOne<Entity>(entityClassOrName: ObjectType<Entity>|string,
optionsOrConditions?: string|ObjectID|FindOneOptions<Entity>|Partial<Entity>,
optionsOrConditions?: string|ObjectID|FindOneOptions<Entity>|DeepPartial<Entity>,
maybeOptions?: FindOneOptions<Entity>): Promise<Entity|undefined> {
const id = optionsOrConditions instanceof ObjectID || typeof optionsOrConditions === "string" ? optionsOrConditions : undefined;
const query = this.convertFindOneOptionsOrConditionsToMongodbQuery((id ? maybeOptions : optionsOrConditions) as any) || {};

View File

@ -347,8 +347,10 @@ export class RelationMetadata {
/**
* Sets given entity's relation's value.
* Using of this method helps to set entity relation's value of the lazy and non-lazy relations.
*
* If merge is set to true, it merges given value into currently
*/
setEntityValue(entity: ObjectLiteral, value: any): void {
setEntityValue(entity: ObjectLiteral, value: any, merge = false): void {
const propertyName = this.isLazy ? "__" + this.propertyName + "__" : this.propertyName;
if (this.embeddedMetadata) {
@ -366,13 +368,23 @@ export class RelationMetadata {
extractEmbeddedColumnValue(embeddedMetadatas, map[embeddedMetadata.propertyName]);
return map;
}
map[propertyName] = value;
if (merge && map[propertyName] instanceof Object) {
map[propertyName] = Object.assign(map[propertyName], value);
} else {
map[propertyName] = value;
}
return map;
};
return extractEmbeddedColumnValue([...this.embeddedMetadata.embeddedMetadataTree], entity);
} else {
entity[propertyName] = value;
if (merge && entity[propertyName] instanceof Object) {
entity[propertyName] = Object.assign(entity[propertyName], value);
} else {
entity[propertyName] = value;
}
}
}

View File

@ -2,6 +2,7 @@ import {ObjectLiteral} from "../common/ObjectLiteral";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {OrmUtils} from "../util/OrmUtils";
import {SubjectChangeMap} from "./SubjectChangeMap";
import {InsertResult} from "../query-builder/result/InsertResult";
/**
* Subject is a subject of persistence.
@ -67,10 +68,9 @@ export class Subject {
mustBeRemoved: boolean = false;
/**
* If there are generated by the database values this generatedMap will contain them.
* Examples are: increment, uuid, create/update dates, column with default values.
* If subject was just inserted, its insert result stored here.
*/
generatedMap?: ObjectLiteral;
insertResult?: InsertResult;
// -------------------------------------------------------------------------
// Constructor
@ -150,33 +150,4 @@ export class Subject {
return changeSet;
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Deprecated
// -------------------------------------------------------------------------
/**
* Date when this entity is persisted.
*
* @deprecated dates must be generated by a database
*/
date: Date = new Date();
/**
* Generated id of the parent entity. Used in the class-table-inheritance.
*
* @deprecated will be implemented in the future (0.3.0)
*/
parentGeneratedId?: any;
/**
* Used in newly persisted entities which are tree tables.
*
* @deprecated will be implemented in the future (0.3.0)
*/
treeLevel?: number;
}

View File

@ -1,11 +1,6 @@
import {ObjectLiteral} from "../common/ObjectLiteral";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {QueryRunner} from "../query-runner/QueryRunner";
import {Subject} from "./Subject";
import {PromiseUtils} from "../util/PromiseUtils";
import {MongoDriver} from "../driver/mongodb/MongoDriver";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
import {DateUtils} from "../util/DateUtils";
import {InsertSubjectsSorter} from "./InsertSubjectsSorter";
@ -260,7 +255,7 @@ export class SubjectExecutor {
await PromiseUtils.runInSequence(this.insertSubjects, async subject => {
const changeSet = subject.popChangeSet();
const insertResult = await this.queryRunner.manager
subject.insertResult = await this.queryRunner.manager
.createQueryBuilder()
.insert()
.into(subject.metadata.target)
@ -268,8 +263,7 @@ export class SubjectExecutor {
// .returning(returningColumnNames) // todo: add "updateEntity(true)" option?
.execute();
subject.generatedMap = insertResult.generatedMaps[0]; // todo: do we really need it?
subject.identifier = insertResult.identifiers[0];
subject.identifier = subject.insertResult.identifiers[0];
// if (subject.entity) {
// subject.identifier = subject.buildIdentifier();
@ -324,37 +318,9 @@ export class SubjectExecutor {
// update entity columns that gets updated on each entity insert
this.insertSubjects.forEach(subject => {
// if (subject.generatedObjectId && subject.metadata.objectIdColumn)
// subject.metadata.objectIdColumn.setEntityValue(subject.entity, subject.generatedObjectId);
if (subject.generatedMap) {
subject.metadata.generatedColumns.forEach(generatedColumn => {
const generatedValue = generatedColumn.getEntityValue(subject.generatedMap!);
if (!generatedValue)
return;
generatedColumn.setEntityValue(subject.entity!, generatedValue);
});
}
subject.metadata.primaryColumns.forEach(primaryColumn => {
if (subject.parentGeneratedId)
primaryColumn.setEntityValue(subject.entity!, subject.parentGeneratedId);
});
if (subject.metadata.updateDateColumn)
subject.metadata.updateDateColumn.setEntityValue(subject.entity!, subject.date);
if (subject.metadata.createDateColumn)
subject.metadata.createDateColumn.setEntityValue(subject.entity!, subject.date);
if (subject.metadata.versionColumn)
subject.metadata.versionColumn.setEntityValue(subject.entity!, 1);
if (subject.metadata.treeLevelColumn) {
// const parentEntity = insertOperation.entity[metadata.treeParentMetadata.propertyName];
// const parentLevel = parentEntity ? (parentEntity[metadata.treeLevelColumn.propertyName] || 0) : 0;
subject.metadata.treeLevelColumn.setEntityValue(subject.entity!, subject.treeLevel);
}
/*if (subject.metadata.hasTreeChildrenCountColumn) {
subject.entity[subject.metadata.treeChildrenCountColumn.propertyName] = 0;
}*/
// merge into entity all values returned by a database
this.queryRunner.manager.merge(subject.metadata.target, subject.entity, subject.insertResult!.valueSets[0]);
// set values to "null" for nullable columns that did not have values
subject.metadata.columns.forEach(column => {
@ -372,10 +338,10 @@ export class SubjectExecutor {
if (!subject.entity)
return;
if (subject.metadata.updateDateColumn)
subject.metadata.updateDateColumn.setEntityValue(subject.entity, subject.date);
if (subject.metadata.versionColumn)
subject.metadata.versionColumn.setEntityValue(subject.entity, subject.metadata.versionColumn.getEntityValue(subject.entity) + 1);
// if (subject.metadata.updateDateColumn)
// subject.metadata.updateDateColumn.setEntityValue(subject.entity, subject.date);
// if (subject.metadata.versionColumn)
// subject.metadata.versionColumn.setEntityValue(subject.entity, subject.metadata.versionColumn.getEntityValue(subject.entity) + 1);
});
// remove ids from the entities that were removed
@ -394,837 +360,4 @@ export class SubjectExecutor {
});
}
// -------------------------------------------------------------------------
// Deprecated Methods
// -------------------------------------------------------------------------
/**
* 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.
*
* @deprecated
*/
protected async insert(subject: Subject, alreadyInsertedSubjects: Subject[]): Promise<any> {
// from executeInsertOperations
// we need to update relation ids of the newly inserted objects (where we inserted NULLs in relations)
// once we inserted all entities, we need to update relations which were bind to inserted entities.
// For example we have a relation many-to-one Post<->Category. Relation is nullable.
// New category was set to the new post and post where persisted.
// Here this method executes two inserts: one for post, one for category,
// but category in post is inserted with "null".
// now we need to update post table - set category with a newly persisted category id.
/*const updatePromises: Promise<any>[] = [];
([] as Subject[]).forEach(subject => {
// first update relations with join columns (one-to-one owner and many-to-one relations)
const updateOptions: ObjectLiteral = {};
subject.metadata.relationsWithJoinColumns.forEach(relation => {
relation.joinColumns.forEach(joinColumn => {
const referencedColumn = joinColumn.referencedColumn!;
const relatedEntity = relation.getEntityValue(subject.entity);
// if relation value is not set then nothing to do here
if (relatedEntity === undefined || relatedEntity === null)
return;
// if relation entity is just a relation id set (for example post.tag = 1)
// then we don't really need to check cascades since there is no object to insert or update
if (!(relatedEntity instanceof Object)) {
updateOptions[joinColumn.databaseName] = relatedEntity;
return;
}
// check if relation reference column is a relation
let relationId: any;
const columnRelation = relation.inverseEntityMetadata.findRelationWithPropertyPath(joinColumn.referencedColumn!.propertyPath);
if (columnRelation) { // if referenced column is a relation
const insertSubject = this.insertSubjects.find(insertedSubject => insertedSubject.entity === referencedColumn.getEntityValue(relatedEntity));
// if this relation was just inserted
if (insertSubject) {
// check if we have this relation id already
relationId = columnRelation.getEntityValue(referencedColumn.getEntityValue(relatedEntity));
if (!relationId) {
// if we don't have relation id then use special values
if (referencedColumn.isGenerated && insertSubject.generatedMap)
relationId = referencedColumn.getEntityValue(insertSubject.generatedMap);
// todo: handle other special types too
}
}
} else { // if referenced column is a simple non relational column
const insertSubject = this.insertSubjects.find(insertedSubject => insertedSubject.entity === relatedEntity);
// if this relation was just inserted
if (insertSubject) {
// check if we have this relation id already
relationId = referencedColumn.getEntityValue(relatedEntity);
if (!relationId) {
// if we don't have relation id then use special values
if (referencedColumn.isGenerated && insertSubject.generatedMap)
relationId = referencedColumn.getEntityValue(insertSubject.generatedMap);
// todo: handle other special types too
}
}
}
if (relationId) {
updateOptions[joinColumn.databaseName] = relationId;
}
});
});
// if we found relations which we can update - then update them
if (Object.keys(updateOptions).length > 0) {
// const relatedEntityIdMap = subject.getPersistedEntityIdMap; // todo: this works incorrectly
const columns = subject.metadata.parentEntityMetadata ? subject.metadata.primaryColumns : subject.metadata.primaryColumns;
const conditions: ObjectLiteral = {};
columns.forEach(column => {
const entityValue = column.getEntityValue(subject.entity);
// if entity id is a relation, then extract referenced column from that relation
const columnRelation = subject.metadata.relations.find(relation => relation.propertyName === column.propertyName);
if (entityValue && columnRelation) { // not sure if we need handle join column from inverse side
columnRelation.joinColumns.forEach(joinColumn => {
let relationIdOfEntityValue = entityValue[joinColumn.referencedColumn!.propertyName];
if (!relationIdOfEntityValue) {
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === entityValue);
if (entityValueInsertSubject) {
if (joinColumn.referencedColumn!.isGenerated && entityValueInsertSubject.generatedMap)
relationIdOfEntityValue = joinColumn.referencedColumn!.getEntityValue(entityValueInsertSubject.generatedMap);
}
}
if (relationIdOfEntityValue) {
conditions[column.databaseName] = relationIdOfEntityValue;
}
});
} else {
if (entityValue) {
conditions[column.databaseName] = entityValue;
} else {
if (subject.generatedMap)
conditions[column.databaseName] = column.getEntityValue(subject.generatedMap);
}
}
});
if (!Object.keys(conditions).length)
return;
const updatePromise = this.queryRunner.update(subject.metadata.tablePath, updateOptions, conditions);
updatePromises.push(updatePromise);
}*/
// we need to update relation ids if newly inserted objects are used from inverse side in one-to-many inverse relation
// we also need to update relation ids if newly inserted objects are used from inverse side in one-to-one inverse relation
/*const oneToManyAndOneToOneNonOwnerRelations = subject.metadata.oneToManyRelations.concat(subject.metadata.oneToOneRelations.filter(relation => !relation.isOwning));
// console.log(oneToManyAndOneToOneNonOwnerRelations);
subject.metadata.extractRelationValuesFromEntity(subject.entity, oneToManyAndOneToOneNonOwnerRelations)
.forEach(([relation, subRelatedEntity, inverseEntityMetadata]) => {
relation.inverseRelation!.joinColumns.forEach(joinColumn => {
const referencedColumn = joinColumn.referencedColumn!;
const columns = inverseEntityMetadata.parentEntityMetadata ? inverseEntityMetadata.primaryColumns : inverseEntityMetadata.primaryColumns;
const conditions: ObjectLiteral = {};
columns.forEach(column => {
const entityValue = column.getEntityValue(subRelatedEntity);
// if entity id is a relation, then extract referenced column from that relation
const columnRelation = inverseEntityMetadata.relations.find(relation => relation.propertyName === column.propertyName);
if (entityValue && columnRelation) { // not sure if we need handle join column from inverse side
columnRelation.joinColumns.forEach(columnRelationJoinColumn => {
let relationIdOfEntityValue = entityValue[columnRelationJoinColumn.referencedColumn!.propertyName];
if (!relationIdOfEntityValue) {
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === entityValue);
if (entityValueInsertSubject) {
if (columnRelationJoinColumn.referencedColumn!.isGenerated && entityValueInsertSubject.generatedMap) {
relationIdOfEntityValue = columnRelationJoinColumn.referencedColumn!.getEntityValue(entityValueInsertSubject.generatedMap);
}
}
}
if (relationIdOfEntityValue) {
conditions[column.databaseName] = relationIdOfEntityValue;
}
});
} else {
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === subRelatedEntity);
if (entityValue) {
conditions[column.databaseName] = entityValue;
} else {
if (entityValueInsertSubject && entityValueInsertSubject.generatedMap)
conditions[column.databaseName] = column.getEntityValue(entityValueInsertSubject.generatedMap);
}
}
});
if (!Object.keys(conditions).length)
return;
const updateOptions: ObjectLiteral = {};
const columnRelation = relation.inverseEntityMetadata.relations.find(rel => rel.propertyName === referencedColumn.propertyName);
const columnValue = referencedColumn.getEntityValue(subject.entity);
if (columnRelation) {
let id = columnRelation.getEntityValue(columnValue);
if (!id) {
const insertSubject = this.insertSubjects.find(subject => subject.entity === columnValue);
if (insertSubject) {
if (insertSubject.generatedMap)
id = referencedColumn.getEntityValue(insertSubject.generatedMap);
}
}
updateOptions[joinColumn.databaseName] = id;
} else {
const generatedColumnValue = subject.generatedMap ? referencedColumn.getEntityValue(subject.generatedMap) : undefined;
updateOptions[joinColumn.databaseName] = columnValue || generatedColumnValue;
}
console.log("or this update?");
const updatePromise = this.queryRunner.update(relation.inverseEntityMetadata.tablePath, updateOptions, conditions);
updatePromises.push(updatePromise);
});
});*/
// });
//
// await Promise.all(updatePromises);
const parentEntityMetadata = subject.metadata.parentEntityMetadata;
const metadata = subject.metadata;
const entity = subject.entity!;
let insertResult: any, parentGeneratedId: any;
// if entity uses class table inheritance then we need to separate entity into sub values that will be inserted into multiple tables
if (metadata.isClassTableChild) { // todo: with current implementation inheritance of multiple class table children will not work
// first insert entity values into parent class table
const parentValuesMap = this.collectColumnsAndValues(parentEntityMetadata, entity, subject.date, undefined, metadata.discriminatorValue, alreadyInsertedSubjects, "insert");
insertResult = parentGeneratedId = await this.queryRunner.insert(parentEntityMetadata.tablePath, parentValuesMap);
// second insert entity values into child class table
const childValuesMap = this.collectColumnsAndValues(metadata, entity, subject.date, insertResult.generatedMap[parentEntityMetadata.primaryColumns[0].propertyName], undefined, alreadyInsertedSubjects, "insert");
const secondGeneratedId = await this.queryRunner.insert(metadata.tablePath, childValuesMap);
if (!insertResult && secondGeneratedId) insertResult = secondGeneratedId;
if (parentGeneratedId)
subject.parentGeneratedId = parentGeneratedId.generatedMap[parentEntityMetadata.primaryColumns[0].propertyName];
// todo: better if insert method will return object with all generated ids, object id, etc.
if (insertResult.generatedMap)
subject.generatedMap = insertResult.generatedMap;
} else { // in the case when class table inheritance is not used
// const valuesMap = this.collectColumnsAndValues(metadata, entity, subject.date, undefined, undefined, alreadyInsertedSubjects, "insert");
// console.log("valuesMap", valuesMap);
// insertResult = await this.queryRunner.insert(metadata.tablePath, valuesMap);
// if (parentGeneratedId)
// subject.parentGeneratedId = parentGeneratedId.generatedMap[parentEntityMetadata.primaryColumns[0].propertyName];
// todo: better if insert method will return object with all generated ids, object id, etc.
// if (insertResult.generatedMap)
// subject.generatedMap = insertResult.generatedMap;
}
}
/**
* @deprecated
*/
protected collectColumns(columns: ColumnMetadata[], entity: ObjectLiteral, object: ObjectLiteral, operation: "insert"|"update") {
columns.forEach(column => {
if (column.isVirtual || column.isParentId || column.isDiscriminator)
return;
if (operation === "update" && column.isReadonly)
return;
const value = entity[column.propertyName];
if (value === undefined)
return;
object[column.databaseNameWithoutPrefixes] = this.queryRunner.connection.driver.preparePersistentValue(value, column); // todo: maybe preparePersistentValue is not responsibility of this class
});
}
/**
* @deprecated
*/
protected collectEmbeds(embed: EmbeddedMetadata, entity: ObjectLiteral, object: ObjectLiteral, operation: "insert"|"update") {
if (embed.isArray) {
if (entity[embed.propertyName] instanceof Array) {
if (!object[embed.prefix])
object[embed.prefix] = [];
entity[embed.propertyName].forEach((subEntity: any, index: number) => {
if (!object[embed.prefix][index])
object[embed.prefix][index] = {};
this.collectColumns(embed.columns, subEntity, object[embed.prefix][index], operation);
embed.embeddeds.forEach(childEmbed => this.collectEmbeds(childEmbed, subEntity, object[embed.prefix][index], operation));
});
}
} else {
if (entity[embed.propertyName] !== undefined) {
if (!object[embed.prefix])
object[embed.prefix] = {};
this.collectColumns(embed.columns, entity[embed.propertyName], object[embed.prefix], operation);
embed.embeddeds.forEach(childEmbed => this.collectEmbeds(childEmbed, entity[embed.propertyName], object[embed.prefix], operation));
}
}
}
/**
* Collects columns and values for the insert operation.
*
* @deprecated
*/
protected collectColumnsAndValues(metadata: EntityMetadata, entity: ObjectLiteral, date: Date, parentIdColumnValue: any, discriminatorValue: any, alreadyInsertedSubjects: Subject[], operation: "insert"|"update"): ObjectLiteral {
const values: ObjectLiteral = {};
if (this.queryRunner.connection.driver instanceof MongoDriver) {
this.collectColumns(metadata.ownColumns, entity, values, operation);
metadata.embeddeds.forEach(embed => this.collectEmbeds(embed, entity, values, operation));
} else {
metadata.columns.forEach(column => {
if (column.isVirtual || column.isParentId || column.isDiscriminator)
return;
const value = column.getEntityValue(entity);
if (value === undefined)
return;
values[column.databaseName] = this.queryRunner.connection.driver.preparePersistentValue(value, column); // todo: maybe preparePersistentValue is not responsibility of this class
});
}
metadata.relationsWithJoinColumns.forEach(relation => {
relation.joinColumns.forEach(joinColumn => {
let relationValue: any;
const value = relation.getEntityValue(entity);
if (value) {
// if relation value is stored in the entity itself then use it from there
const relationId = joinColumn.referencedColumn!.getEntityValue(value); // relation.getInverseEntityRelationId(value); // todo: check it
if (relationId) {
relationValue = relationId;
}
// otherwise try to find relational value from just inserted subjects
const alreadyInsertedSubject = alreadyInsertedSubjects.find(insertedSubject => {
return insertedSubject.entity === value;
});
if (alreadyInsertedSubject) {
const referencedColumn = joinColumn.referencedColumn!;
// if join column references to the primary generated column then seek in the newEntityId of the insertedSubject
if (referencedColumn.referencedColumn && referencedColumn.referencedColumn!.isGenerated) {
if (referencedColumn.isParentId) {
relationValue = alreadyInsertedSubject.parentGeneratedId;
}
// todo: what if reference column is not generated?
// todo: what if reference column is not related to table inheritance?
}
if (referencedColumn.isGenerated && alreadyInsertedSubject.generatedMap)
relationValue = referencedColumn.getEntityValue(alreadyInsertedSubject.generatedMap);
// if it references to create or update date columns
if (referencedColumn.isCreateDate || referencedColumn.isUpdateDate)
relationValue = this.queryRunner.connection.driver.preparePersistentValue(alreadyInsertedSubject.date, referencedColumn);
// if it references to version column
if (referencedColumn.isVersion)
relationValue = this.queryRunner.connection.driver.preparePersistentValue(1, referencedColumn);
}
} else if (relation.inverseRelation) {
const inverseSubject = this.allSubjects.find(subject => {
if (!subject.entity || subject.metadata.target !== relation.inverseRelation!.target)
return false;
const inverseRelationValue = relation.inverseRelation!.getEntityValue(subject.entity);
if (inverseRelationValue) {
if (inverseRelationValue instanceof Array) {
return inverseRelationValue.find(subValue => subValue === subValue);
} else {
return inverseRelationValue === entity;
}
}
});
if (inverseSubject && joinColumn.referencedColumn!.getEntityValue(inverseSubject.entity!)) {
relationValue = joinColumn.referencedColumn!.getEntityValue(inverseSubject.entity!);
}
}
if (relationValue) {
values[joinColumn.databaseName] = relationValue;
}
});
});
// add special column and value - date of creation
if (metadata.createDateColumn) {
const value = this.queryRunner.connection.driver.preparePersistentValue(date, metadata.createDateColumn);
values[metadata.createDateColumn.databaseName] = value;
}
// add special column and value - date of updating
if (metadata.updateDateColumn) {
const value = this.queryRunner.connection.driver.preparePersistentValue(date, metadata.updateDateColumn);
values[metadata.updateDateColumn.databaseName] = value;
}
// add special column and value - version column
if (metadata.versionColumn) {
const value = this.queryRunner.connection.driver.preparePersistentValue(1, metadata.versionColumn);
values[metadata.versionColumn.databaseName] = value;
}
// add special column and value - discriminator value (for tables using table inheritance)
if (metadata.discriminatorColumn) {
const value = this.queryRunner.connection.driver.preparePersistentValue(discriminatorValue || metadata.discriminatorValue, metadata.discriminatorColumn);
values[metadata.discriminatorColumn.databaseName] = value;
}
metadata.generatedColumns
.filter(column => column.generationStrategy === "uuid")
.forEach(column => {
if (column.isNullable && values[column.databaseName] === null)
return;
const uuid = this.queryRunner.connection.driver.preparePersistentValue("", column);
if (uuid && !values[column.databaseName])
values[column.databaseName] = uuid;
});
// add special column and value - tree level and tree parents (for tree-type tables)
if (metadata.treeLevelColumn && metadata.treeParentRelation) {
const parentEntity = metadata.treeParentRelation.getEntityValue(entity);
const parentLevel = parentEntity ? (metadata.treeLevelColumn.getEntityValue(parentEntity) || 0) : 0;
values[metadata.treeLevelColumn.databaseName] = parentLevel + 1;
}
// add special column and value - parent id column (for tables using table inheritance)
if (metadata.parentEntityMetadata && metadata.parentIdColumns.length) { // todo: should be array of primary keys
values[metadata.parentIdColumns[0].databaseName] = parentIdColumnValue || metadata.parentEntityMetadata.primaryColumns[0].getEntityValue(entity);
}
return values;
}
/**
* Inserts all given subjects into closure table.
*
* @deprecated
*/
protected executeInsertClosureTableOperations(/*, updatesByRelations: Subject[]*/) { // todo: what to do with updatesByRelations
const promises = this.insertSubjects
.filter(subject => subject.metadata.isClosure)
.map(async subject => {
// const relationsUpdateMap = this.findUpdateOperationForEntity(updatesByRelations, insertSubjects, subject.entity);
// subject.treeLevel = await this.insertIntoClosureTable(subject, relationsUpdateMap);
await this.insertClosureTableValues(subject);
});
return Promise.all(promises);
}
/**
* Inserts given subject into closure table.
*
* @deprecated
*/
protected async insertClosureTableValues(subject: Subject): Promise<void> {
// todo: since closure tables do not support compose primary keys - throw an exception?
// todo: what if parent entity or parentEntityId is empty?!
const tablePath = subject.metadata.closureJunctionTable.tablePath;
const referencedColumn = subject.metadata.treeParentRelation!.joinColumns[0].referencedColumn!; // todo: check if joinColumn works
// todo: fix joinColumns[0] usage
let newEntityId = referencedColumn.getEntityValue(subject.entity!);
if (!newEntityId && referencedColumn.isGenerated && subject.generatedMap) {
newEntityId = referencedColumn.getEntityValue(subject.generatedMap);
// we should not handle object id here because closure tables are not supported by mongodb driver.
} // todo: implement other special column types too
const parentEntity = subject.metadata.treeParentRelation!.getEntityValue(subject.entity!);
let parentEntityId: any = 0; // zero is important
if (parentEntity) {
parentEntityId = referencedColumn.getEntityValue(parentEntity);
if (!parentEntityId && referencedColumn.isGenerated) {
const parentInsertedSubject = this.insertSubjects.find(subject => subject.entity === parentEntity);
// todo: throw exception if parentInsertedSubject is not set
if (parentInsertedSubject!.generatedMap)
parentEntityId = referencedColumn.getEntityValue(parentInsertedSubject!.generatedMap!);
} // todo: implement other special column types too
}
// try to find parent entity id in some other entity that has this entity in its children
if (!parentEntityId) {
const parentSubject = this.allSubjects.find(allSubject => {
if (!allSubject.entity || !allSubject.metadata.isClosure || !allSubject.metadata.treeChildrenRelation)
return false;
const children = subject.metadata.treeChildrenRelation!.getEntityValue(allSubject.entity);
return children instanceof Array ? children.indexOf(subject.entity) !== -1 : false;
});
if (parentSubject) {
parentEntityId = referencedColumn.getEntityValue(parentSubject.entity!);
if (!parentEntityId && parentSubject.generatedMap) { // if still not found then it means parent just inserted with generated column
parentEntityId = referencedColumn.getEntityValue(parentSubject.generatedMap);
}
}
}
// if parent entity exist then insert a new row into closure table
subject.treeLevel = await this.queryRunner.insertIntoClosureTable(tablePath, newEntityId, parentEntityId, !!subject.metadata.treeLevelColumn);
if (subject.metadata.treeLevelColumn) {
const values = { [subject.metadata.treeLevelColumn.databaseName]: subject.treeLevel };
await this.queryRunner.update(subject.metadata.tablePath, values, { [referencedColumn.databaseName]: newEntityId });
}
}
/**
* Updates all given subjects in the database.
*
* @deprecated
*/
protected async executeUpdateOperationsOld(): Promise<void> {
await Promise.all(this.updateSubjects.map(subject => this.updateOld(subject)));
}
/**
* Updates given subject in the database.
*
* @deprecated
*/
protected async updateOld(subject: Subject): Promise<void> {
if (this.queryRunner.connection.driver instanceof MongoDriver) {
const entity = subject.entity!;
const idMap = subject.metadata.getDatabaseEntityIdMap(entity);
if (!idMap)
throw new Error(`Internal error. Cannot get id of the updating entity.`);
/*const value: ObjectLiteral = {};
subject.metadata.columns.forEach(column => {
const columnValue = column.getEntityValue(entity);
if (columnValue !== undefined)
value[column.databaseName] = columnValue;
});*/
// addEmbeddedValuesRecursively(entity, value, subject.metadata.embeddeds);
const value: ObjectLiteral = {};
this.collectColumns(subject.metadata.ownColumns, entity, value, "update");
subject.metadata.embeddeds.forEach(embed => this.collectEmbeds(embed, entity, value, "update"));
// if number of updated columns = 0 no need to update updated date and version columns
if (Object.keys(value).length === 0)
return;
if (subject.metadata.updateDateColumn)
value[subject.metadata.updateDateColumn.databaseName] = this.queryRunner.connection.driver.preparePersistentValue(new Date(), subject.metadata.updateDateColumn);
if (subject.metadata.versionColumn)
value[subject.metadata.versionColumn.databaseName] = this.queryRunner.connection.driver.preparePersistentValue(subject.metadata.versionColumn.getEntityValue(entity) + 1, subject.metadata.versionColumn);
return this.queryRunner.update(subject.metadata.tablePath, value, idMap);
}
/*
// we group by table name, because metadata can have different table names
// const valueMaps: { tablePath: string, metadata: EntityMetadata, values: ObjectLiteral }[] = [];
const updateMap = subject.createChangeSet();
// if number of updated columns = 0 no need to update updated date and version columns
// if (Object.keys(updateMap).length === 0) // can this be possible?!
// return;
// console.log(subject);
if (!subject.identifier) {
throw new Error(`Subject does not have identifier`);
}
return this.queryRunner.manager
.createQueryBuilder()
.update(subject.metadata.target)
.set(updateMap)
.where(subject.identifier)
.execute();
*/
// console.log(subject.diffColumns);
/*subject.diffColumns.forEach(column => {
// if (!column.entityTarget) return; // todo: how this can be possible?
const metadata = this.queryRunner.connection.getMetadata(column.entityMetadata.target);
let valueMap = valueMaps.find(valueMap => valueMap.tablePath === metadata.tablePath);
if (!valueMap) {
valueMap = { tablePath: metadata.tablePath, metadata: metadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[column.databaseName] = this.queryRunner.connection.driver.preparePersistentValue(column.getEntityValue(entity), column);
});*/
/*subject.diffRelations.forEach(relation => {
let valueMap = valueMaps.find(valueMap => valueMap.tablePath === relation.entityMetadata.tablePath);
if (!valueMap) {
valueMap = { tablePath: relation.entityMetadata.tablePath, metadata: relation.entityMetadata, values: {} };
valueMaps.push(valueMap);
}
const value = relation.getEntityValue(entity);
relation.joinColumns.forEach(joinColumn => {
if (value === undefined)
return;
if (value === null) {
valueMap!.values[joinColumn.databaseName] = null;
} else if (value instanceof Object) {
valueMap!.values[joinColumn.databaseName] = joinColumn.referencedColumn!.getEntityValue(value);
} else {
valueMap!.values[joinColumn.databaseName] = value;
}
});
});*/
// todo: this must be a database-level updation
/*if (subject.metadata.updateDateColumn) {
let valueMap = valueMaps.find(valueMap => valueMap.tablePath === subject.metadata.tablePath);
if (!valueMap) {
valueMap = { tablePath: subject.metadata.tablePath, metadata: subject.metadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[subject.metadata.updateDateColumn.databaseName] = this.queryRunner.connection.driver.preparePersistentValue(new Date(), subject.metadata.updateDateColumn);
}*/
// todo: this must be a database-level updation
/*if (subject.metadata.versionColumn) {
let valueMap = valueMaps.find(valueMap => valueMap.tablePath === subject.metadata.tablePath);
if (!valueMap) {
valueMap = { tablePath: subject.metadata.tablePath, metadata: subject.metadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[subject.metadata.versionColumn.databaseName] = this.queryRunner.connection.driver.preparePersistentValue(subject.metadata.versionColumn.getEntityValue(entity) + 1, subject.metadata.versionColumn);
}*/
// todo: table inheritance. most probably this will be removed and implemented later in 0.3.0
/*if (subject.metadata.parentEntityMetadata) {
if (subject.metadata.parentEntityMetadata.updateDateColumn) {
let valueMap = valueMaps.find(valueMap => valueMap.tablePath === subject.metadata.parentEntityMetadata.tablePath);
if (!valueMap) {
valueMap = {
tablePath: subject.metadata.parentEntityMetadata.tablePath,
metadata: subject.metadata.parentEntityMetadata,
values: {}
};
valueMaps.push(valueMap);
}
valueMap.values[subject.metadata.parentEntityMetadata.updateDateColumn.databaseName] = this.queryRunner.connection.driver.preparePersistentValue(new Date(), subject.metadata.parentEntityMetadata.updateDateColumn);
}
if (subject.metadata.parentEntityMetadata.versionColumn) {
let valueMap = valueMaps.find(valueMap => valueMap.tablePath === subject.metadata.parentEntityMetadata.tablePath);
if (!valueMap) {
valueMap = {
tablePath: subject.metadata.parentEntityMetadata.tablePath,
metadata: subject.metadata.parentEntityMetadata,
values: {}
};
valueMaps.push(valueMap);
}
valueMap.values[subject.metadata.parentEntityMetadata.versionColumn.databaseName] = this.queryRunner.connection.driver.preparePersistentValue(subject.metadata.parentEntityMetadata.versionColumn.getEntityValue(entity) + 1, subject.metadata.parentEntityMetadata.versionColumn);
}
}*/
/*await Promise.all(valueMaps.map(valueMap => {
const idMap = valueMap.metadata.getDatabaseEntityIdMap(entity);
if (!idMap)
throw new Error(`Internal error. Cannot get id of the updating entity.`);
return this.queryRunner.update(valueMap.tablePath, valueMap.values, idMap);
}));*/
}
/**
* Updates relations of all given subjects in the database.
private executeUpdateRelations() {
return Promise.all(this.allSubjects.map(subject => this.updateRelations(subject)));
}*/
/**
* Updates relations of the given subject in the database.
private async updateRelations(subject: Subject): Promise<void> {
await Promise.all(subject.oneToManyUpdateOperations.map(relationUpdate => {
return this.queryRunner.manager
.createQueryBuilder()
.update(relationUpdate.metadata.target)
.set(relationUpdate.updateValues)
.whereInIds(relationUpdate.condition)
.execute();
}));
}*/
/**
* Removes all given subjects from the database.
*
* @deprecated
*/
protected async executeRemoveOperationsOld(): Promise<void> {
await PromiseUtils.runInSequence(this.removeSubjects, subject => this.removeOld(subject));
}
/**
* Updates given subject from the database.
*
* @deprecated
*/
protected async removeOld(subject: Subject): Promise<void> {
if (subject.metadata.parentEntityMetadata) { // this code should not be there. it should be handled by subject.metadata.getEntityIdColumnMap
const parentConditions: ObjectLiteral = {};
subject.metadata.primaryColumns.forEach(column => {
parentConditions[column.databaseName] = column.getEntityValue(subject.databaseEntity!);
});
await this.queryRunner.delete(subject.metadata.parentEntityMetadata.tableName, parentConditions);
const childConditions: ObjectLiteral = {};
subject.metadata.primaryColumns.forEach(column => {
childConditions[column.databaseName] = column.getEntityValue(subject.databaseEntity!);
});
await this.queryRunner.delete(subject.metadata.tableName, childConditions);
return;
}
// await this.queryRunner.delete(subject.metadata.tableName, subject.metadata.getDatabaseEntityIdMap(subject.databaseEntity)!);
await this.queryRunner.manager
.createQueryBuilder()
.delete()
.from(subject.metadata.target)
.where(subject.identifier!) // todo: what if identified will be undefined?!
.execute();
}
/**
* Inserts into database junction tables all given array of subjects junction data.
private async executeInsertJunctionsOperations(): Promise<void> {
const promises: Promise<any>[] = [];
this.allSubjects.forEach(subject => {
subject.junctionInserts.forEach(junctionInsert => {
promises.push(this.insertJunctions(subject, junctionInsert));
});
});
await Promise.all(promises);
}*/
/**
* Inserts into database junction table given subject's junction insert data.
private async insertJunctions(subject: Subject, junctionInsert: JunctionInsert): Promise<void> {
// I think here we can only support to work only with single primary key entities
const getRelationId = (entity: ObjectLiteral, joinColumns: ColumnMetadata[]): any[] => {
return joinColumns.map(joinColumn => {
const id = joinColumn.referencedColumn!.getEntityValue(entity);
if (!id && joinColumn.referencedColumn!.isGenerated) {
const insertSubject = this.insertSubjects.find(subject => subject.entity === entity);
if (insertSubject && insertSubject.generatedMap)
return joinColumn.referencedColumn!.getEntityValue(insertSubject.generatedMap);
}
// todo: implement other special referenced column types (update date, create date, version, discriminator column, etc.)
return id;
});
};
const relation = junctionInsert.relation;
const joinColumns = relation.isManyToManyOwner ? relation.joinColumns : relation.inverseRelation!.inverseJoinColumns;
const ownId = getRelationId(subject.entity, joinColumns);
if (!ownId.length)
throw new Error(`Cannot insert object of ${subject.entityTarget} type. Looks like its not persisted yet, or cascades are not set on the relation.`); // todo: better error message
const promises = junctionInsert.junctionEntities.map(newBindEntity => {
// get relation id from the newly bind entity
const joinColumns = relation.isManyToManyOwner ? relation.inverseJoinColumns : relation.inverseRelation!.joinColumns;
const relationId = getRelationId(newBindEntity, joinColumns);
// if relation id still does not exist - we arise an error
if (!relationId)
throw new Error(`Cannot insert object of ${(newBindEntity.constructor as any).name} type. Looks like its not persisted yet, or cascades are not set on the relation.`); // todo: better error message
const columns = relation.junctionEntityMetadata!.columns.map(column => column.databaseName);
const values = relation.isOwning ? [...ownId, ...relationId] : [...relationId, ...ownId];
return this.queryRunner.insert(relation.junctionEntityMetadata!.tablePath, OrmUtils.zipObject(columns, values));
});
await Promise.all(promises);
} */
/**
* Removes from database junction tables all given array of subjects removal junction data.
private async executeRemoveJunctionsOperations(): Promise<void> {
const promises: Promise<any>[] = [];
this.allSubjects.forEach(subject => {
subject.junctionRemoves.forEach(junctionRemove => {
promises.push(this.removeJunctions(subject, junctionRemove));
});
});
await Promise.all(promises);
}*/
/**
* Removes from database junction table all given subject's removal junction data.
private async removeJunctions(subject: Subject, junctionRemove: JunctionRemove) {
const junctionMetadata = junctionRemove.relation.junctionEntityMetadata!;
const entity = subject.hasEntity ? subject.entity : subject.databaseEntity;
const firstJoinColumns = junctionRemove.relation.isOwning ? junctionRemove.relation.joinColumns : junctionRemove.relation.inverseRelation!.inverseJoinColumns;
const secondJoinColumns = junctionRemove.relation.isOwning ? junctionRemove.relation.inverseJoinColumns : junctionRemove.relation.inverseRelation!.joinColumns;
let conditions: ObjectLiteral = {};
firstJoinColumns.forEach(joinColumn => {
conditions[joinColumn.databaseName] = joinColumn.referencedColumn!.getEntityValue(entity);
});
const removePromises = junctionRemove.junctionRelationIds.map(relationIds => {
let inverseConditions: ObjectLiteral = {};
secondJoinColumns.forEach(joinColumn => {
inverseConditions[joinColumn.databaseName] = joinColumn.referencedColumn!.getEntityValue(relationIds);
});
return this.queryRunner.delete(junctionMetadata.tableName, Object.assign({}, inverseConditions, conditions));
});
await Promise.all(removePromises);
} */
}

View File

@ -42,7 +42,6 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
await Promise.all(this.getValueSets().map(async (valueSet, valueSetIndex) => {
const generatedMap = this.connection.driver.createGeneratedMap(metadata, {}, insertResult.raw) || {};
// const uuidMap: ObjectLiteral = {};
metadata.generatedColumns.forEach(generatedColumn => {
if (generatedColumn.generationStrategy === "uuid") {
// uuid can be defined by user in a model, that's why first we get it
@ -54,22 +53,10 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
}
});
console.log("generatedMap", generatedMap);
queryRunner.manager.merge(metadata.target, valueSet, generatedMap);
const identifier = metadata.getEntityIdMap(valueSet);
const identifier = metadata.primaryColumns.reduce((identifier, column) => {
if (column.isGenerated && Object.keys(generatedMap).length > 0) {
return OrmUtils.mergeDeep(identifier, column.getEntityValueMap(generatedMap));
} else {
return OrmUtils.mergeDeep(identifier, column.getEntityValueMap(valueSet));
}
}, {} as ObjectLiteral);
console.log("identifier:", identifier);
// insertResult.identifiers.push(returningColumns.reduce((map, column) => {
// return OrmUtils.mergeDeep(map, column.createValueMap(returningResult[column.databaseName]));
// }, {} as ObjectLiteral));
if (Object.keys(identifier).length === 0)
if (!identifier)
throw new Error(`Inserted entity identifier is empty, cannot finish insert operation execution.`);
// for postgres and mssql we use returning/output statement to get values of inserted default and generated values
@ -83,22 +70,20 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
// (since its the only thing that can be generated by those databases)
// or (and) other primary key which is defined by a user and inserted value has it
const returningResult = await queryRunner.manager
const returningResult: any = await queryRunner.manager
.createQueryBuilder()
.select(returningColumns.map(column => metadata.targetName + "." + column.propertyPath)) // todo: check why they aren't getting escaped
.select(metadata.primaryColumns.map(column => metadata.targetName + "." + column.propertyPath))
.addSelect(returningColumns.map(column => metadata.targetName + "." + column.propertyPath)) // todo: check why they aren't getting escaped
.from(metadata.target, metadata.targetName)
.where(identifier)
.getRawOne();
.getOne();
console.log("returningResult:", returningResult);
// subject.identifier = returningColumns.reduce((map, column) => {
// return OrmUtils.mergeDeep(map, column.createValueMap(returningResult[column.databaseName]));
// }, {} as ObjectLiteral);
queryRunner.manager.merge(metadata.target, valueSet, returningResult);
}
insertResult.generatedMaps.push(generatedMap);
insertResult.identifiers.push(identifier);
insertResult.valueSets.push(valueSet);
}));
}

View File

@ -618,14 +618,30 @@ export abstract class QueryBuilder<Entity> {
if (!this.expressionMap.updateEntity || !this.expressionMap.mainAlias!.hasMetadata)
return [];
// filter out the columns of which we need database inserted values to update our entity
return this.expressionMap.mainAlias!.metadata.columns.filter(column => {
return column.default !== undefined ||
column.isGenerated ||
column.isCreateDate ||
column.isUpdateDate ||
column.isVersion;
});
// for databases which support returning statement we need to return extra columns like id
if (this.isReturningSqlSupported()) {
// filter out the columns of which we need database inserted values to update our entity
return this.expressionMap.mainAlias!.metadata.columns.filter(column => {
return column.default !== undefined ||
column.isGenerated ||
column.isCreateDate ||
column.isUpdateDate ||
column.isVersion;
});
} else { // for other databases we don't need to return id column since its returned by a driver already
// filter out the columns of which we need database inserted values to update our entity
return this.expressionMap.mainAlias!.metadata.columns.filter(column => {
return column.default !== undefined ||
column.isCreateDate ||
column.isUpdateDate ||
column.isVersion;
});
}
}
/**

View File

@ -996,12 +996,12 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
const metadata = this.expressionMap.mainAlias!.metadata;
if (this.expressionMap.lockVersion instanceof Date) {
const actualVersion = result[metadata.updateDateColumn!.propertyName]; // what if columns arent set?
const actualVersion = metadata.updateDateColumn!.getEntityValue(result); // what if columns arent set?
if (actualVersion.getTime() !== this.expressionMap.lockVersion.getTime())
throw new OptimisticLockVersionMismatchError(metadata.name, this.expressionMap.lockVersion, actualVersion);
} else {
const actualVersion = result[metadata.versionColumn!.propertyName]; // what if columns arent set?
const actualVersion = metadata.versionColumn!.getEntityValue(result); // what if columns arent set?
if (actualVersion !== this.expressionMap.lockVersion)
throw new OptimisticLockVersionMismatchError(metadata.name, this.expressionMap.lockVersion, actualVersion);
}
@ -1516,7 +1516,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
columns.push(...metadata.columns.filter(column => column.isSelect === true));
}
columns.push(...metadata.columns.filter(column => {
return this.expressionMap.selects.some(select => select.selection === aliasName + "." + column.propertyName);
return this.expressionMap.selects.some(select => select.selection === aliasName + "." + column.propertyPath);
}));
// if user used partial selection and did not select some primary columns which are required to be selected
@ -1526,7 +1526,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
const allColumns = [...columns, ...nonSelectedPrimaryColumns];
return allColumns.map(column => {
const selection = this.expressionMap.selects.find(select => select.selection === aliasName + "." + column.propertyName);
const selection = this.expressionMap.selects.find(select => select.selection === aliasName + "." + column.propertyPath);
return {
selection: this.escape(aliasName) + "." + this.escape(column.databaseName),
aliasName: selection && selection.aliasName ? selection.aliasName : aliasName + "_" + column.databaseName,
@ -1542,7 +1542,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
return [mainSelect];
return this.expressionMap.selects.filter(select => {
return metadata.columns.some(column => select.selection === aliasName + "." + column.propertyName);
return metadata.columns.some(column => select.selection === aliasName + "." + column.propertyPath);
});
}
@ -1654,8 +1654,8 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
if (metadata.hasMultiplePrimaryKeys) {
condition = rawResults.map(result => {
return metadata.primaryColumns.map(primaryColumn => {
parameters["ids_" + primaryColumn.propertyName] = result["ids_" + primaryColumn.databaseName];
return mainAliasName + "." + primaryColumn.propertyName + "=:ids_" + primaryColumn.databaseName;
parameters["ids_" + primaryColumn.databaseName] = result["ids_" + primaryColumn.databaseName];
return mainAliasName + "." + primaryColumn.propertyPath + "=:ids_" + primaryColumn.databaseName;
}).join(" AND ");
}).join(" OR ");
} else {
@ -1663,10 +1663,10 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
const areAllNumbers = ids.every((id: any) => typeof id === "number");
if (areAllNumbers) {
// fixes #190. if all numbers then its safe to perform query without parameter
condition = `${mainAliasName}.${metadata.primaryColumns[0].propertyName} IN (${ids.join(", ")})`;
condition = `${mainAliasName}.${metadata.primaryColumns[0].propertyPath} IN (${ids.join(", ")})`;
} else {
parameters["ids"] = ids;
condition = mainAliasName + "." + metadata.primaryColumns[0].propertyName + " IN (:ids)";
condition = mainAliasName + "." + metadata.primaryColumns[0].propertyPath + " IN (:ids)";
}
}
rawResults = await this.clone()

View File

@ -17,6 +17,12 @@ export class InsertResult {
*/
generatedMaps: ObjectLiteral[] = [];
/**
* Inserted values with updated values of special and default columns.
* Has entity-like structure (not just column database name and values).
*/
valueSets: ObjectLiteral[] = [];
/**
* Raw SQL result returned by executed query.
*/

View File

@ -27,7 +27,24 @@ export class PlainObjectToNewEntityTransformer {
private groupAndTransform(entity: any, object: ObjectLiteral, metadata: EntityMetadata): void {
// copy regular column properties from the given object
metadata.columns
metadata.columns.forEach(column => {
if (column.isVirtual) // we don't need to merge virtual columns
return;
const objectColumnValue = column.getEntityValue(object);
if (objectColumnValue !== undefined)
column.setEntityValue(entity, objectColumnValue);
});
// // copy relation properties from the given object
metadata.relations.forEach(relation => {
const objectRelationValue = relation.getEntityValue(object);
if (objectRelationValue !== undefined)
relation.setEntityValue(entity, objectRelationValue, true);
});
// copy regular column properties from the given object
/*metadata.columns
.filter(column => object.hasOwnProperty(column.propertyName))
.forEach(column => entity[column.propertyName] = object[column.propertyName]); // todo: also need to be sure that type is correct
@ -70,7 +87,7 @@ export class PlainObjectToNewEntityTransformer {
entity[relation.propertyName] = object[relation.propertyName];
}
}
});
});*/
}
}

View File

@ -101,7 +101,7 @@ export class RawSqlResultsToEntityTransformer {
// if user does not selected the whole entity or he used partial selection and does not select this particular column
// then we don't add this column and its value into the entity
if (!this.expressionMap.selects.find(select => select.selection === alias.name || select.selection === alias.name + "." + column.propertyName))
if (!this.expressionMap.selects.find(select => select.selection === alias.name || select.selection === alias.name + "." + column.propertyPath))
return;
column.setEntityValue(entity, this.driver.prepareHydratedValue(value, column));

View File

@ -150,7 +150,7 @@ export class BaseEntity {
/**
* Updates entity partially. Entity can be found by a given conditions.
*/
static update<T extends BaseEntity>(this: ObjectType<T>, conditions: Partial<T>, partialEntity: DeepPartial<T>, options?: SaveOptions): Promise<void>;
static update<T extends BaseEntity>(this: ObjectType<T>, conditions: DeepPartial<T>, partialEntity: DeepPartial<T>, options?: SaveOptions): Promise<void>;
/**
* Updates entity partially. Entity can be found by a given find options.
@ -160,7 +160,7 @@ export class BaseEntity {
/**
* Updates entity partially. Entity can be found by a given conditions.
*/
static update<T extends BaseEntity>(this: ObjectType<T>, conditionsOrFindOptions: Partial<T>|FindOneOptions<T>, partialEntity: DeepPartial<T>, options?: SaveOptions): Promise<void> {
static update<T extends BaseEntity>(this: ObjectType<T>, conditionsOrFindOptions: DeepPartial<T>|FindOneOptions<T>, partialEntity: DeepPartial<T>, options?: SaveOptions): Promise<void> {
return (this as any).getRepository().update(conditionsOrFindOptions as any, partialEntity, options);
}

View File

@ -140,14 +140,14 @@ export class Repository<Entity extends ObjectLiteral> {
* Executes fast and efficient INSERT query.
* Does not check if entity exist in the database, so query will fail if duplicate entity is being inserted.
*/
async insert(entity: Partial<Entity>|Partial<Entity>[], options?: SaveOptions): Promise<void> {
async insert(entity: DeepPartial<Entity>|DeepPartial<Entity>[], options?: SaveOptions): Promise<void> {
return this.manager.insert(this.metadata.target, entity, options);
}
/**
* Updates entity partially. Entity can be found by a given conditions.
*/
async update(conditions: Partial<Entity>, partialEntity: DeepPartial<Entity>, options?: SaveOptions): Promise<void> {
async update(conditions: DeepPartial<Entity>, partialEntity: DeepPartial<Entity>, options?: SaveOptions): Promise<void> {
return this.manager.update(this.metadata.target, conditions, partialEntity, options);
}
@ -182,7 +182,7 @@ export class Repository<Entity extends ObjectLiteral> {
* Executes fast and efficient DELETE query.
* Does not check if entity exist in the database.
*/
async delete(conditions: Partial<Entity>, options?: RemoveOptions): Promise<void> {
async delete(conditions: DeepPartial<Entity>, options?: RemoveOptions): Promise<void> {
return this.manager.delete(this.metadata.target, conditions, options);
}

View File

@ -1,15 +1,15 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {Column} from "../../../../../src/decorator/columns/Column";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {PostEmbedded} from "./PostEmbedded";
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
@Entity()
export class PostComplex {
@PrimaryGeneratedColumn("uuid")
@PrimaryColumn()
firstId: number;
@Column()
@Column({ default: "Hello Complexity" })
text: string;
@Column(type => PostEmbedded)

View File

@ -9,6 +9,21 @@ export class PostDefaultValues {
id: number;
@Column()
title: string;
@Column({ default: "hello post" })
text: string;
@Column({ default: true })
isActive: boolean;
@Column({ default: () => "NOW()" })
addDate: Date;
@Column({ default: 0 })
views: number;
@Column({ nullable: true })
description: string;
}

View File

@ -5,7 +5,7 @@ import {VersionColumn} from "../../../../../src/decorator/columns/VersionColumn"
export class PostEmbedded {
@PrimaryColumn("uuid")
@PrimaryColumn()
secondId: number;
@CreateDateColumn()

View File

@ -11,7 +11,7 @@ export class PostMultiplePrimaryKeys {
@PrimaryColumn()
secondId: number;
@Column()
@Column({ default: "Hello Multi Ids" })
text: string;
}

View File

@ -1,6 +1,14 @@
import "reflect-metadata";
import {Connection} from "../../../../src/connection/Connection";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {PostIncrement} from "./entity/PostIncrement";
import {PostUuid} from "./entity/PostUuid";
import {PostDefaultValues} from "./entity/PostDefaultValues";
import {PostSpecialColumns} from "./entity/PostSpecialColumns";
import {expect} from "chai";
import {PostMultiplePrimaryKeys} from "./entity/PostMultiplePrimaryKeys";
import {PostComplex} from "./entity/PostComplex";
import {PostEmbedded} from "./entity/PostEmbedded";
describe("persistence > entity updation", () => {
@ -10,19 +18,74 @@ describe("persistence > entity updation", () => {
after(() => closeTestingConnections(connections));
it("should update generated auto-increment id after saving", () => Promise.all(connections.map(async connection => {
const post = new PostIncrement();
post.text = "Hello Post";
await connection.manager.save(post);
post.id.should.be.equal(1);
})));
it("should update generated uuid after saving", () => Promise.all(connections.map(async connection => {
const post = new PostUuid();
post.text = "Hello Post";
await connection.manager.save(post);
const loadedPost = await connection.manager.findOne(PostUuid);
post.id.should.be.equal(loadedPost!.id);
})));
it("should update default values after saving", () => Promise.all(connections.map(async connection => {
const post = new PostDefaultValues();
post.title = "Post #1";
await connection.manager.save(post);
post.id.should.be.equal(1);
post.title.should.be.equal("Post #1");
post.text.should.be.equal("hello post");
post.isActive.should.be.equal(true);
post.addDate.should.be.instanceof(Date);
post.views.should.be.equal(0);
expect(post.description).to.be.equal(null);
})));
it("should update special columns saving", () => Promise.all(connections.map(async connection => {
it("should update special columns after saving", () => Promise.all(connections.map(async connection => {
const post = new PostSpecialColumns();
post.title = "Post #1";
await connection.manager.save(post);
post.id.should.be.equal(1);
post.title.should.be.equal("Post #1");
post.createDate.should.be.instanceof(Date);
post.updateDate.should.be.instanceof(Date);
post.version.should.be.equal(1);
})));
it("should update even when multiple primary keys are used", () => Promise.all(connections.map(async connection => {
const post = new PostMultiplePrimaryKeys();
post.firstId = 1;
post.secondId = 3;
await connection.manager.save(post);
post.firstId.should.be.equal(1);
post.secondId.should.be.equal(3);
post.text.should.be.equal("Hello Multi Ids");
})));
it("should update even with embeddeds", () => Promise.all(connections.map(async connection => {
const post = new PostComplex();
post.firstId = 1;
post.embed = new PostEmbedded();
post.embed.secondId = 3;
await connection.manager.save(post);
post!.firstId.should.be.equal(1);
post!.embed.secondId.should.be.equal(3);
post!.embed.createDate.should.be.instanceof(Date);
post!.embed.updateDate.should.be.instanceof(Date);
post!.embed.version.should.be.equal(1);
post!.text.should.be.equal("Hello Complexity");
const loadedPost = await connection.manager.findOne(PostComplex, { firstId: 1, embed: { secondId: 3 }})
loadedPost!.firstId.should.be.equal(1);
loadedPost!.embed.secondId.should.be.equal(3);
loadedPost!.embed.createDate.should.be.instanceof(Date);
loadedPost!.embed.updateDate.should.be.instanceof(Date);
loadedPost!.embed.version.should.be.equal(1);
loadedPost!.text.should.be.equal("Hello Complexity");
})));
});

View File

@ -90,14 +90,15 @@ describe("one-to-one", function() {
return postRepository.findOne(savedPost.id).should.eventually.eql(expectedPost);
});
it("should have inserted post details in the database", function() {
it("should have inserted post details in the database", async function() {
const expectedDetails = new PostDetails();
expectedDetails.id = savedPost.details.id;
expectedDetails.authorName = savedPost.details.authorName;
expectedDetails.comment = savedPost.details.comment;
expectedDetails.metadata = savedPost.details.metadata;
return postDetailsRepository.findOne(savedPost.details.id).should.eventually.eql(expectedDetails);
const loadedPostDetails = await postDetailsRepository.findOne(savedPost.details.id);
loadedPostDetails!.should.be.eql(expectedDetails);
});
it("should load post and its details if left join used", async function() {