mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
more experiments over new persistent mechanizm
This commit is contained in:
parent
102f78b024
commit
21bca6b826
@ -162,8 +162,8 @@ export abstract class BaseEntityManager {
|
||||
/**
|
||||
* Creates a new query builder that can be used to build an sql query.
|
||||
*/
|
||||
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>, alias: string): QueryBuilder<Entity> {
|
||||
return this.getRepository(entityClass).createQueryBuilder(alias);
|
||||
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string): QueryBuilder<Entity> {
|
||||
return this.getRepository(entityClass as any).createQueryBuilder(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -432,13 +432,13 @@ export class RelationMetadata {
|
||||
return ownEntity[this.joinTable.referencedColumn.propertyName];
|
||||
|
||||
} else if (this.isManyToManyNotOwner) {
|
||||
return ownEntity[this.joinTable.inverseReferencedColumn.propertyName];
|
||||
return ownEntity[this.inverseRelation.joinTable.inverseReferencedColumn.propertyName];
|
||||
|
||||
} else if (this.isOneToOneOwner || this.isManyToOne) {
|
||||
return ownEntity[this.joinColumn.propertyName];
|
||||
|
||||
} else if (this.isOneToOneNotOwner || this.isOneToMany) {
|
||||
return ownEntity[this.joinColumn.referencedColumn.propertyName];
|
||||
return ownEntity[this.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
}
|
||||
}
|
||||
|
||||
@ -447,13 +447,13 @@ export class RelationMetadata {
|
||||
return inverseEntity[this.joinTable.inverseReferencedColumn.propertyName];
|
||||
|
||||
} else if (this.isManyToManyNotOwner) {
|
||||
return inverseEntity[this.joinTable.referencedColumn.propertyName];
|
||||
return inverseEntity[this.inverseRelation.joinTable.referencedColumn.propertyName];
|
||||
|
||||
} else if (this.isOneToOneOwner || this.isManyToOne) {
|
||||
return inverseEntity[this.joinColumn.referencedColumn.propertyName];
|
||||
|
||||
} else if (this.isOneToOneNotOwner || this.isOneToMany) {
|
||||
return inverseEntity[this.joinColumn.propertyName];
|
||||
return inverseEntity[this.inverseRelation.joinColumn.propertyName];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
|
||||
/**
|
||||
* If this gonna be reused then what to do with marked flags?
|
||||
* One of solution can be clone this object and reset all marked states for this persistment.
|
||||
* One of solution can be clone this object and reset all marked states for this persistence.
|
||||
* Or from reused just extract databaseEntities from their subjects? (looks better)
|
||||
*/
|
||||
private loadedSubjects: SubjectCollection = new SubjectCollection();
|
||||
@ -79,18 +79,20 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
async load(entity: Entity, metadata: EntityMetadata): Promise<void> {
|
||||
// this.persistedEntity = new Subject(metadata, entity);
|
||||
// this.loadedSubjects.push(this.persistedEntity);
|
||||
const persistedEntity = new Subject(metadata, entity);
|
||||
this.loadedSubjects.push(persistedEntity);
|
||||
this.populateSubjectsWithCascadeUpdateAndInsertEntities(entity, metadata);
|
||||
await this.loadDatabaseEntities();
|
||||
// this.findCascadeInsertAndUpdateEntities(entity, metadata);
|
||||
|
||||
console.log("loadedSubjects: ", this.loadedSubjects);
|
||||
|
||||
const findCascadeRemoveOperations = this.loadedSubjects
|
||||
.filter(subject => !!subject.databaseEntity) // means we only attempt to load for non new entities
|
||||
.map(subject => this.findCascadeRemovedEntitiesToLoad(subject));
|
||||
await Promise.all(findCascadeRemoveOperations);
|
||||
|
||||
console.log(this.loadedSubjects);
|
||||
console.log("all persistence subjects: ", this.loadedSubjects);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -191,7 +193,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
* BIG NOTE: objects are being removed by cascades not only when relation is removed, but also when
|
||||
* relation is replaced (e.g. changed with different object).
|
||||
*/
|
||||
protected async findCascadeRemovedEntitiesToLoad(subject: Subject/*, forceRemove = false*/): Promise<void> {
|
||||
protected async findCascadeRemovedEntitiesToLoad(subject: Subject): Promise<void> {
|
||||
|
||||
// note: we can't use extractRelationValuesFromEntity here because it does not handle empty arrays
|
||||
const promises = subject.metadata.relations.map(async relation => {
|
||||
@ -236,7 +238,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
const relationIdInDatabaseEntity = relation.getOwnEntityRelationId(subject.databaseEntity); // (example) returns post.detailsId
|
||||
|
||||
// if database relation id does not exist in the database object then nothing to remove
|
||||
if (relationIdInDatabaseEntity !== null && relationIdInDatabaseEntity !== undefined)
|
||||
if (relationIdInDatabaseEntity === null || relationIdInDatabaseEntity === undefined)
|
||||
return;
|
||||
|
||||
// if this subject is persisted subject then we get its value to check if its not empty or its values changed
|
||||
@ -248,7 +250,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
}
|
||||
|
||||
// object is removed only if relation id in the persisted entity is empty or is changed
|
||||
if (persistValueRelationId === null || persistValueRelationId === relationIdInDatabaseEntity)
|
||||
if (persistValueRelationId !== null && persistValueRelationId === relationIdInDatabaseEntity)
|
||||
return;
|
||||
|
||||
// first check if we already loaded this object before load from the database
|
||||
@ -272,7 +274,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
.getRepository<ObjectLiteral>(valueMetadata.target)
|
||||
.createQueryBuilder(qbAlias)
|
||||
.where(qbAlias + "." + relation.joinColumn.referencedColumn.propertyName + "=:id") // todo: need to escape alias and propertyName?
|
||||
.setParameter("id", relation.getOwnEntityRelationId(subject.databaseEntity)) // (example) subject.entity is a post here
|
||||
.setParameter("id", relationIdInDatabaseEntity) // (example) subject.entity is a post here
|
||||
.enableOption("RELATION_ID_VALUES")
|
||||
.getSingleResult();
|
||||
|
||||
@ -283,6 +285,12 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
}
|
||||
|
||||
if (alreadyLoadedRelatedDatabaseSubject) {
|
||||
|
||||
// if object is already marked as removed then no need to proceed because it already was proceed
|
||||
// if we remove this it will cause a recursion
|
||||
if (alreadyLoadedRelatedDatabaseSubject.mustBeRemoved)
|
||||
return;
|
||||
|
||||
alreadyLoadedRelatedDatabaseSubject.mustBeRemoved = true;
|
||||
await this.findCascadeRemovedEntitiesToLoad(alreadyLoadedRelatedDatabaseSubject);
|
||||
}
|
||||
@ -316,7 +324,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
const relationIdInDatabaseEntity = relation.getOwnEntityRelationId(subject.databaseEntity);
|
||||
|
||||
// if database relation id does not exist then nothing to remove (but can this be possible?)
|
||||
if (relationIdInDatabaseEntity !== null && relationIdInDatabaseEntity !== undefined)
|
||||
if (relationIdInDatabaseEntity === null || relationIdInDatabaseEntity === undefined)
|
||||
return;
|
||||
|
||||
// first check if we already have this object loaded before load from the database
|
||||
@ -340,7 +348,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
.getRepository<ObjectLiteral>(valueMetadata.target)
|
||||
.createQueryBuilder(qbAlias)
|
||||
.where(qbAlias + "." + relation.inverseSideProperty + "=:id") // todo: need to escape alias and propertyName?
|
||||
.setParameter("id", relation.getOwnEntityRelationId(subject.entity)) // (example) subject.entity is a details here, and the value is details.id
|
||||
.setParameter("id", relationIdInDatabaseEntity) // (example) subject.entity is a details here, and the value is details.id
|
||||
.enableOption("RELATION_ID_VALUES")
|
||||
.getSingleResult();
|
||||
|
||||
@ -362,6 +370,11 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
relation.getInverseEntityRelationId(alreadyLoadedRelatedDatabaseSubject.databaseEntity))
|
||||
return;
|
||||
|
||||
// if object is already marked as removed then no need to proceed because it already was proceed
|
||||
// if we remove this it will cause a recursion
|
||||
if (alreadyLoadedRelatedDatabaseSubject.mustBeRemoved)
|
||||
return;
|
||||
|
||||
alreadyLoadedRelatedDatabaseSubject.mustBeRemoved = true;
|
||||
await this.findCascadeRemovedEntitiesToLoad(alreadyLoadedRelatedDatabaseSubject);
|
||||
}
|
||||
@ -393,7 +406,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
const relationIdInDatabaseEntity = relation.getOwnEntityRelationId(subject.databaseEntity);
|
||||
|
||||
// if database relation id does not exist then nothing to remove (but can this be possible?)
|
||||
if (relationIdInDatabaseEntity !== null && relationIdInDatabaseEntity !== undefined)
|
||||
if (relationIdInDatabaseEntity === null || relationIdInDatabaseEntity === undefined)
|
||||
return;
|
||||
|
||||
// if this subject is persisted subject then we get its value to check if its not empty or its values changed
|
||||
@ -413,8 +426,8 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
databaseEntities = await this.connection
|
||||
.getRepository<ObjectLiteral>(valueMetadata.target)
|
||||
.createQueryBuilder(qbAlias)
|
||||
.innerJoin(subject.metadata.target, "persistenceJoinedRelation", "ON", qbAlias + "." + relation.joinTable.joinColumnName + "=:id") // todo: need to escape alias and propertyName?
|
||||
.setParameter("id", relation.getOwnEntityRelationId(subject.entity))
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, "persistenceJoinedRelation", "ON", "persistenceJoinedRelation." + relation.joinTable.joinColumnName + "=:id") // todo: need to escape alias and propertyName?
|
||||
.setParameter("id", relationIdInDatabaseEntity)
|
||||
.enableOption("RELATION_ID_VALUES")
|
||||
.getResults();
|
||||
|
||||
@ -422,8 +435,8 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
databaseEntities = await this.connection
|
||||
.getRepository<ObjectLiteral>(valueMetadata.target)
|
||||
.createQueryBuilder(qbAlias)
|
||||
.innerJoin(subject.metadata.target, "persistenceJoinedRelation", "ON", qbAlias + "." + relation.joinTable.inverseJoinColumnName + "=:id") // todo: need to escape alias and propertyName?
|
||||
.setParameter("id", relation.getOwnEntityRelationId(subject.entity))
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, "persistenceJoinedRelation", "ON", "persistenceJoinedRelation." + relation.inverseRelation.joinTable.inverseJoinColumnName + "=:id") // todo: need to escape alias and propertyName?
|
||||
.setParameter("id", relationIdInDatabaseEntity)
|
||||
.enableOption("RELATION_ID_VALUES")
|
||||
.getResults();
|
||||
|
||||
@ -432,7 +445,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
.getRepository<ObjectLiteral>(valueMetadata.target)
|
||||
.createQueryBuilder(qbAlias)
|
||||
.where(qbAlias + "." + relation.inverseSideProperty + "=:id") // todo: need to escape alias and propertyName?
|
||||
.setParameter("id", relation.getOwnEntityRelationId(subject.entity))
|
||||
.setParameter("id", relationIdInDatabaseEntity)
|
||||
.enableOption("RELATION_ID_VALUES")
|
||||
.getResults();
|
||||
}
|
||||
@ -452,11 +465,16 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
//
|
||||
const promises = databaseEntities.map(async databaseEntity => {
|
||||
const relatedEntitySubject = this.loadedSubjects.findByDatabaseEntityLike(valueMetadata.target, databaseEntity);
|
||||
if (!relatedEntitySubject) return; // should not be possible
|
||||
if (!relatedEntitySubject) return; // should not be possible, anyway add it for type-safety
|
||||
|
||||
// if object is already marked as removed then no need to proceed because it already was proceed
|
||||
// if we remove this it will cause a recursion
|
||||
if (relatedEntitySubject.mustBeRemoved) return;
|
||||
|
||||
if (persistValue === null) {
|
||||
relatedEntitySubject.mustBeRemoved = true;
|
||||
await this.findCascadeRemovedEntitiesToLoad(relatedEntitySubject);
|
||||
return;
|
||||
}
|
||||
|
||||
const relatedValue = (persistValue as ObjectLiteral[]).find(persistedRelatedValue => {
|
||||
|
||||
@ -83,7 +83,7 @@ export class EntityPersistOperationBuilder {
|
||||
if (dbEntity)
|
||||
persistOperation.junctionRemoves = this.findJunctionRemoveOperations(metadata, dbEntity, allPersistedEntities);
|
||||
|
||||
persistOperation.log();
|
||||
// persistOperation.log();
|
||||
|
||||
return persistOperation;
|
||||
}
|
||||
|
||||
@ -7,8 +7,6 @@ import {PersistOperationExecutor} from "./PersistOperationExecutor";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {QueryRunner} from "../query-runner/QueryRunner";
|
||||
import {Subject} from "./subject/Subject";
|
||||
import {SubjectFactory} from "./subject/SubjectFactory";
|
||||
import {DatabaseSubjectsLoader} from "../query-builder/transformer/DatabaseSubjectsLoader";
|
||||
import {SubjectCollection} from "./subject/SubjectCollection";
|
||||
import {NewJunctionInsertOperation} from "./operation/NewJunctionInsertOperation";
|
||||
import {NewJunctionRemoveOperation} from "./operation/NewJunctionRemoveOperation";
|
||||
@ -123,10 +121,43 @@ export class EntityPersister<Entity extends ObjectLiteral> {
|
||||
* 2. load from the database all entities that has primary keys and might be updated
|
||||
*/
|
||||
async persist(entity: Entity): Promise<Entity> {
|
||||
|
||||
if (true === true) {
|
||||
const databaseEntityLoader = new DatabaseEntityLoader(this.connection);
|
||||
await databaseEntityLoader.load(entity, this.metadata);
|
||||
}
|
||||
|
||||
const allNewEntities = await this.flattenEntityRelationTree(entity, this.metadata);
|
||||
const persistedEntity = allNewEntities.find(operatedEntity => operatedEntity.entity === entity);
|
||||
if (!persistedEntity)
|
||||
throw new Error(`Internal error. Persisted entity was not found in the list of prepared operated entities`);
|
||||
|
||||
let dbEntity: Subject|undefined, allDbInNewEntities: Subject[] = [];
|
||||
|
||||
// if entity has an id then check
|
||||
if (this.metadata.hasId(entity)) {
|
||||
const queryBuilder = new QueryBuilder<Entity>(this.connection, this.queryRunner)
|
||||
.select(this.metadata.table.name)
|
||||
.from(this.metadata.target, this.metadata.table.name);
|
||||
const plainObjectToDatabaseEntityTransformer = new PlainObjectToDatabaseEntityTransformer();
|
||||
const loadedDbEntity = await plainObjectToDatabaseEntityTransformer.transform(entity, this.metadata, queryBuilder);
|
||||
if (loadedDbEntity) {
|
||||
dbEntity = new Subject(this.metadata, loadedDbEntity);
|
||||
allDbInNewEntities = await this.flattenEntityRelationTree(loadedDbEntity, this.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
// need to find db entities that were not loaded by initialize method
|
||||
const allDbEntities = await this.findNotLoadedIds(allNewEntities, allDbInNewEntities);
|
||||
const entityPersistOperationBuilder = new EntityPersistOperationBuilder(this.connection.entityMetadatas);
|
||||
const persistOperation = entityPersistOperationBuilder.buildFullPersistment(dbEntity, persistedEntity, allDbEntities, allNewEntities);
|
||||
|
||||
const persistOperationExecutor = new PersistOperationExecutor(this.connection.driver, this.connection.entityMetadatas, this.connection.broadcaster, this.queryRunner); // todo: better to pass connection?
|
||||
await persistOperationExecutor.executePersistOperation(persistOperation);
|
||||
return entity;
|
||||
|
||||
/*if (true === true) {
|
||||
const databaseEntityLoader = new DatabaseEntityLoader(this.connection);
|
||||
await databaseEntityLoader.load(entity, this.metadata);
|
||||
console.log();
|
||||
return entity;
|
||||
}
|
||||
@ -190,7 +221,7 @@ export class EntityPersister<Entity extends ObjectLiteral> {
|
||||
|
||||
|
||||
|
||||
/*let dbEntity: Subject|undefined, allDbInNewEntities: Subject[] = [];
|
||||
/!*let dbEntity: Subject|undefined, allDbInNewEntities: Subject[] = [];
|
||||
|
||||
// if entity has an id then check
|
||||
if (this.hasId(entity)) {
|
||||
@ -203,13 +234,13 @@ export class EntityPersister<Entity extends ObjectLiteral> {
|
||||
dbEntity = new Subject(this.metadata, loadedDbEntity);
|
||||
allDbInNewEntities = this.flattenEntityRelationTree(loadedDbEntity, this.metadata);
|
||||
}
|
||||
}*/
|
||||
}*!/
|
||||
|
||||
// need to find db entities that were not loaded by initialize method
|
||||
// const allDbEntities = await this.findNotLoadedIds(persistedSubjects, allDbInNewEntities);
|
||||
const persistOperation = entityPersistOperationBuilder.buildFullPersistment(databaseSubject, persistedSubject, databaseSubjects, persistedSubjects);
|
||||
await persistOperationExecutor.executePersistOperation(persistOperation);
|
||||
return entity;
|
||||
return entity;*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -34,8 +34,24 @@ describe("persistence > cascade operations", () => {
|
||||
const post1 = new Post();
|
||||
post1.title = "Hello Post #1";
|
||||
post1.category = category1;
|
||||
post1.category.photos = [photo1, photo2];
|
||||
await connection.entityManager.persist(post1);
|
||||
|
||||
console.log("********************************************************");
|
||||
|
||||
const posts = await connection.entityManager
|
||||
.createQueryBuilder(Post, "post")
|
||||
.leftJoinAndSelect("post.category", "category")
|
||||
// .innerJoinAndSelect("post.photos", "photos")
|
||||
.getResults();
|
||||
|
||||
console.log("********************************************************");
|
||||
console.log("posts: ", posts);
|
||||
|
||||
// posts[0].category = null; // todo: uncomment to check remove
|
||||
console.log("removing post's category: ", posts[0]);
|
||||
await connection.entityManager.persist(posts[0]);
|
||||
|
||||
/* await connection.entityManager.persist([photo1, photo2]);
|
||||
|
||||
post1.photos = [photo1];
|
||||
|
||||
@ -3,6 +3,9 @@ import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/Prima
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {Post} from "./Post";
|
||||
import {OneToMany} from "../../../../../src/decorator/relations/OneToMany";
|
||||
import {Photo} from "./Photo";
|
||||
import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
|
||||
import {JoinTable} from "../../../../../src/decorator/relations/JoinTable";
|
||||
|
||||
@Table()
|
||||
export class Category {
|
||||
@ -14,8 +17,18 @@ export class Category {
|
||||
name: string;
|
||||
|
||||
@OneToMany(type => Post, post => post.category, {
|
||||
cascadeInsert: true
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
posts: Post[];
|
||||
|
||||
@ManyToMany(type => Photo, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
@JoinTable()
|
||||
photos: Photo[];
|
||||
|
||||
}
|
||||
@ -17,12 +17,16 @@ export class Post {
|
||||
title: string;
|
||||
|
||||
@ManyToOne(type => Category, category => category.posts, {
|
||||
cascadeInsert: true
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
category: Category;
|
||||
category: Category|null;
|
||||
|
||||
@ManyToMany(type => Photo, {
|
||||
cascadeInsert: true
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
@JoinTable()
|
||||
photos: Photo[];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user