mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
refactoring persistence
This commit is contained in:
parent
525eba2174
commit
6c39bffc0c
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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) || {};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -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);
|
||||
} */
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
});*/
|
||||
}
|
||||
|
||||
}
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
@ -5,7 +5,7 @@ import {VersionColumn} from "../../../../../src/decorator/columns/VersionColumn"
|
||||
|
||||
export class PostEmbedded {
|
||||
|
||||
@PrimaryColumn("uuid")
|
||||
@PrimaryColumn()
|
||||
secondId: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
|
||||
@ -11,7 +11,7 @@ export class PostMultiplePrimaryKeys {
|
||||
@PrimaryColumn()
|
||||
secondId: number;
|
||||
|
||||
@Column()
|
||||
@Column({ default: "Hello Multi Ids" })
|
||||
text: string;
|
||||
|
||||
}
|
||||
@ -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");
|
||||
})));
|
||||
|
||||
});
|
||||
|
||||
@ -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() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user