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
651eb8dd22
commit
c7aa4468c1
@ -45,7 +45,7 @@ export class Post {
|
||||
@ManyToOne(type => PostMetadata, metadata => metadata.posts, {
|
||||
cascadeRemove: true
|
||||
})
|
||||
metadata: PostMetadata|undefined;
|
||||
metadata: PostMetadata|null;
|
||||
|
||||
// post has relation with details. full cascades here
|
||||
@ManyToOne(type => PostInformation, information => information.posts, {
|
||||
|
||||
@ -27,6 +27,6 @@ export class PostDetails {
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
posts: Post[] = [];
|
||||
posts: Post[];
|
||||
|
||||
}
|
||||
@ -26,7 +26,7 @@ export class Post {
|
||||
cascadeRemove: true
|
||||
})
|
||||
@JoinTable()
|
||||
categories: PostCategory[] = [];
|
||||
categories: PostCategory[];
|
||||
|
||||
// post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
|
||||
// relation it will be inserted automatically to the db when you save this Post entity
|
||||
@ -34,7 +34,7 @@ export class Post {
|
||||
cascadeInsert: true
|
||||
})
|
||||
@JoinTable()
|
||||
details: PostDetails[] = [];
|
||||
details: PostDetails[];
|
||||
|
||||
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
|
||||
// it will be inserted automatically to the db when you save this Post entity
|
||||
@ -42,7 +42,7 @@ export class Post {
|
||||
cascadeUpdate: true
|
||||
})
|
||||
@JoinTable()
|
||||
images: PostImage[] = [];
|
||||
images: PostImage[];
|
||||
|
||||
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
|
||||
// it will be inserted automatically to the db when you save this Post entity
|
||||
@ -50,7 +50,7 @@ export class Post {
|
||||
cascadeRemove: true
|
||||
})
|
||||
@JoinTable()
|
||||
metadatas: PostMetadata[] = [];
|
||||
metadatas: PostMetadata[];
|
||||
|
||||
// post has relation with details. full cascades here
|
||||
@ManyToMany(type => PostInformation, information => information.posts, {
|
||||
@ -59,11 +59,11 @@ export class Post {
|
||||
cascadeRemove: true
|
||||
})
|
||||
@JoinTable()
|
||||
informations: PostInformation[] = [];
|
||||
informations: PostInformation[];
|
||||
|
||||
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
|
||||
@ManyToMany(type => PostAuthor, author => author.posts)
|
||||
@JoinTable()
|
||||
authors: PostAuthor[] = [];
|
||||
authors: PostAuthor[];
|
||||
|
||||
}
|
||||
@ -27,6 +27,6 @@ export class PostDetails {
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
posts: Post[] = [];
|
||||
posts: Post[];
|
||||
|
||||
}
|
||||
@ -179,13 +179,24 @@ export class MysqlQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
|
||||
@ -191,13 +191,24 @@ export class OracleQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
|
||||
@ -175,14 +175,25 @@ export class PostgresQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
const query = `DELETE FROM "${tableName}" WHERE ${conditionString}`;
|
||||
await this.query(query, parameters);
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -188,14 +188,25 @@ export class SqliteQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
const query = `DELETE FROM "${tableName}" WHERE ${conditionString}`;
|
||||
await this.query(query, parameters);
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -236,13 +236,24 @@ export class SqlServerQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ import {MissingJoinTableError} from "./error/MissingJoinTableError";
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {MissingPrimaryColumnError} from "./error/MissingPrimaryColumnError";
|
||||
import {CircularRelationsError} from "./error/CircularRelationsError";
|
||||
const DepGraph = require("dependency-graph").DepGraph;
|
||||
|
||||
/// todo: add check if there are multiple tables with the same name
|
||||
/// todo: add checks when generated column / table names are too long for the specific driver
|
||||
@ -32,6 +31,8 @@ export class EntityMetadataValidator {
|
||||
* Validates dependencies of the entity metadatas.
|
||||
*/
|
||||
validateDependencies(entityMetadatas: EntityMetadata[]) {
|
||||
|
||||
const DepGraph = require("dependency-graph").DepGraph;
|
||||
const graph = new DepGraph();
|
||||
entityMetadatas.forEach(entityMetadata => {
|
||||
graph.addNode(entityMetadata.name);
|
||||
|
||||
@ -561,6 +561,27 @@ export class EntityMetadata {
|
||||
return hasAllIds ? map : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getEntityIdMap, but instead of id column property names it returns database column names.
|
||||
*/
|
||||
getDatabaseEntityIdMap(entity: ObjectLiteral): ObjectLiteral|undefined {
|
||||
const map: ObjectLiteral = {};
|
||||
if (this.parentEntityMetadata) {
|
||||
this.primaryColumnsWithParentIdColumns.forEach(column => {
|
||||
map[column.name] = entity[column.propertyName];
|
||||
});
|
||||
|
||||
} else {
|
||||
this.primaryColumns.forEach(column => {
|
||||
map[column.name] = entity[column.propertyName];
|
||||
});
|
||||
}
|
||||
const hasAllIds = this.primaryColumns.every(primaryColumn => {
|
||||
return map[primaryColumn.name] !== undefined && map[primaryColumn.name] !== null;
|
||||
});
|
||||
return hasAllIds ? map : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
createSimpleIdMap(id: any): ObjectLiteral {
|
||||
@ -578,6 +599,24 @@ export class EntityMetadata {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as createSimpleIdMap, but instead of id column property names it returns database column names.
|
||||
*/
|
||||
createSimpleDatabaseIdMap(id: any): ObjectLiteral {
|
||||
const map: ObjectLiteral = {};
|
||||
if (this.parentEntityMetadata) {
|
||||
this.primaryColumnsWithParentIdColumns.forEach(column => {
|
||||
map[column.name] = id;
|
||||
});
|
||||
|
||||
} else {
|
||||
this.primaryColumns.forEach(column => {
|
||||
map[column.name] = id;
|
||||
});
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* todo: undefined entities should not go there??
|
||||
* todo: shouldnt be entity ObjectLiteral here?
|
||||
|
||||
@ -426,6 +426,17 @@ export class RelationMetadata {
|
||||
|
||||
/**
|
||||
* todo: lazy relations are not supported here? implement logic?
|
||||
*
|
||||
* examples:
|
||||
*
|
||||
* - isOneToOneNotOwner or isOneToMany:
|
||||
* Post has a Category.
|
||||
* Post is owner side.
|
||||
* Category is inverse side.
|
||||
* Post.category is mapped to Category.id
|
||||
*
|
||||
* if from Post relation we are passing Category here,
|
||||
* it should return a post.category
|
||||
*/
|
||||
getOwnEntityRelationId(ownEntity: ObjectLiteral): any {
|
||||
if (this.isManyToManyOwner) {
|
||||
@ -442,6 +453,21 @@ export class RelationMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* examples:
|
||||
*
|
||||
* - isOneToOneNotOwner or isOneToMany:
|
||||
* Post has a Category.
|
||||
* Post is owner side.
|
||||
* Category is inverse side.
|
||||
* Post.category is mapped to Category.id
|
||||
*
|
||||
* if from Post relation we are passing Category here,
|
||||
* it should return a category.id
|
||||
*
|
||||
* @deprecated Looks like this method does not make sence and does same as getOwnEntityRelationId ?
|
||||
*/
|
||||
getInverseEntityRelationId(inverseEntity: ObjectLiteral): any {
|
||||
if (this.isManyToManyOwner) {
|
||||
return inverseEntity[this.joinTable.inverseReferencedColumn.propertyName];
|
||||
|
||||
@ -69,8 +69,8 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
* Or from reused just extract databaseEntities from their subjects? (looks better)
|
||||
*/
|
||||
loadedSubjects: SubjectCollection = new SubjectCollection();
|
||||
junctionInsertOperations: NewJunctionInsertOperation[];
|
||||
junctionRemoveOperations: NewJunctionRemoveOperation[];
|
||||
junctionInsertOperations: NewJunctionInsertOperation[] = [];
|
||||
junctionRemoveOperations: NewJunctionRemoveOperation[] = [];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
@ -83,7 +83,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
async load(entity: Entity, metadata: EntityMetadata): Promise<void> {
|
||||
async persist(entity: Entity, metadata: EntityMetadata): Promise<void> {
|
||||
const persistedEntity = new Subject(metadata, entity);
|
||||
persistedEntity.canBeInserted = true;
|
||||
persistedEntity.canBeUpdated = true;
|
||||
@ -110,8 +110,32 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// when executing insert/update operations we need to exclude entities scheduled for remove
|
||||
// for junction operations we only get insert and update operations
|
||||
|
||||
// persistedEntity.mustBeRemoved = true;
|
||||
// todo: execute operations
|
||||
console.log("subjects: ", this.loadedSubjects);
|
||||
}
|
||||
|
||||
async remove(entity: Entity, metadata: EntityMetadata): Promise<void> {
|
||||
const persistedEntity = new Subject(metadata, entity);
|
||||
persistedEntity.mustBeRemoved = true;
|
||||
this.loadedSubjects.push(persistedEntity);
|
||||
this.populateSubjectsWithCascadeRemoveEntities(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);
|
||||
|
||||
// find subjects that needs to be inserted and removed from junction table
|
||||
const [junctionRemoveOperations] = await Promise.all([
|
||||
this.buildRemoveJunctionOperations()
|
||||
]);
|
||||
this.junctionRemoveOperations = junctionRemoveOperations;
|
||||
|
||||
// when executing insert/update operations we need to exclude entities scheduled for remove
|
||||
// for junction operations we only get insert and update operations
|
||||
|
||||
}
|
||||
|
||||
@ -138,8 +162,14 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
if (relation.isEntityDefined(value) && (relation.isCascadeInsert || relation.isCascadeUpdate)) {
|
||||
|
||||
// if we already has this entity in list of loaded subjects then skip it to avoid recursion
|
||||
if (this.loadedSubjects.hasWithEntity(value))
|
||||
const alreadyExistSubject = this.loadedSubjects.findByEntity(value);
|
||||
if (alreadyExistSubject) {
|
||||
if (alreadyExistSubject.canBeInserted === false)
|
||||
alreadyExistSubject.canBeInserted = relation.isCascadeInsert === true;
|
||||
if (alreadyExistSubject.canBeUpdated === false)
|
||||
alreadyExistSubject.canBeUpdated = relation.isCascadeUpdate === true;
|
||||
return;
|
||||
}
|
||||
|
||||
// add to the array of subjects to load only if there is no same entity there already
|
||||
const subject = new Subject(valueMetadata, value); // todo: store relations inside to create correct order then? // todo: try to find by likeDatabaseEntity and replace its persistment entity?
|
||||
@ -153,6 +183,34 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
protected populateSubjectsWithCascadeRemoveEntities(entity: ObjectLiteral, metadata: EntityMetadata): void {
|
||||
metadata
|
||||
.extractRelationValuesFromEntity(entity, metadata.relations)
|
||||
.forEach(([relation, value, valueMetadata]) => {
|
||||
|
||||
// if there is a value in the relation and remove cascades are set - it means we must load entity
|
||||
if (relation.isEntityDefined(value) && relation.isCascadeRemove) {
|
||||
|
||||
// if we already has this entity in list of loaded subjects then skip it to avoid recursion
|
||||
const alreadyExistSubject = this.loadedSubjects.findByEntity(value);
|
||||
if (alreadyExistSubject) {
|
||||
alreadyExistSubject.mustBeRemoved = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// add to the array of subjects to load only if there is no same entity there already
|
||||
const subject = new Subject(valueMetadata, value); // todo: store relations inside to create correct order then? // todo: try to find by likeDatabaseEntity and replace its persistment entity?
|
||||
subject.mustBeRemoved = true;
|
||||
this.loadedSubjects.push(subject); // todo: throw exception if same persistment entity already exist? or simply replace?
|
||||
|
||||
// go recursively and find other entities to load by cascades in currently inserted entities
|
||||
this.populateSubjectsWithCascadeRemoveEntities(value, valueMetadata);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads database entities for all loaded subjects which does not have database entities set.
|
||||
*/
|
||||
@ -266,7 +324,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
return;
|
||||
|
||||
// if this subject is persisted subject then we get its value to check if its not empty or its values changed
|
||||
let persistValueRelationId: any = null;
|
||||
let persistValueRelationId: any = undefined;
|
||||
if (subject.entity) {
|
||||
const persistValue = relation.getEntityValue(subject.entity);
|
||||
if (persistValue) persistValueRelationId = relation.getInverseEntityRelationId(persistValue);
|
||||
@ -337,8 +395,8 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// (example) "subject.databaseEntity" - is a details object
|
||||
|
||||
// if this subject is persisted subject then we get its value to check if its not empty or its values changed
|
||||
let persistValueRelationId: any = null;
|
||||
if (subject.entity) {
|
||||
let persistValueRelationId: any = undefined;
|
||||
if (subject.entity && !subject.mustBeRemoved) {
|
||||
const persistValue = relation.getEntityValue(subject.entity);
|
||||
if (persistValue) persistValueRelationId = relation.getInverseEntityRelationId(persistValue);
|
||||
if (persistValueRelationId === undefined) return; // skip undefined properties
|
||||
@ -361,7 +419,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// (example) here we seek a Post loaded from the database in the subjects
|
||||
// (example) here relatedSubject.databaseEntity is a Post
|
||||
// (example) and we need to compare post.detailsId === details.id
|
||||
return relation.getInverseEntityRelationId(relatedSubject.databaseEntity) === relationIdInDatabaseEntity;
|
||||
return relatedSubject.databaseEntity[relation.inverseRelation.joinColumn.propertyName] === relationIdInDatabaseEntity;
|
||||
});
|
||||
|
||||
// if not loaded yet then load it from the database
|
||||
@ -426,6 +484,8 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// (example) "valueMetadata" - is an entity metadata of the Post object.
|
||||
// (example) "subject.databaseEntity" - is a details object
|
||||
|
||||
console.log("one to many. I shall remove");
|
||||
|
||||
// (example) returns us referenced column (detail's id)
|
||||
const relationIdInDatabaseEntity = relation.getOwnEntityRelationId(subject.databaseEntity);
|
||||
|
||||
@ -434,7 +494,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
return;
|
||||
|
||||
// if this subject is persisted subject then we get its value to check if its not empty or its values changed
|
||||
let persistValue: any = null;
|
||||
let persistValue: any = undefined;
|
||||
if (subject.entity) {
|
||||
persistValue = relation.getEntityValue(subject.entity);
|
||||
if (persistValue === undefined) return; // skip undefined properties
|
||||
@ -525,11 +585,15 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
*/
|
||||
private async buildInsertJunctionOperations(): Promise<NewJunctionInsertOperation[]> {
|
||||
const junctionInsertOperations: NewJunctionInsertOperation[] = [];
|
||||
const promises = this.loadedSubjects.map(persistedSubject => { // todo: exclude if object is removed?
|
||||
const promises = persistedSubject.metadata.manyToManyRelations.map(async relation => {
|
||||
|
||||
// no need to insert junctions of the removed entities
|
||||
const persistedSubjects = this.loadedSubjects.filter(subject => !subject.mustBeRemoved);
|
||||
|
||||
const promises = persistedSubjects.map(subject => {
|
||||
const promises = subject.metadata.manyToManyRelations.map(async relation => {
|
||||
|
||||
// extract entity value - we only need to proceed if value is defined and its an array
|
||||
const value = relation.getEntityValue(persistedSubject.entity);
|
||||
const value = relation.getEntityValue(subject.entity);
|
||||
if (!(value instanceof Array))
|
||||
return;
|
||||
|
||||
@ -541,21 +605,21 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// load from db all relation ids of inverse entities "bind" to the currently persisted entity
|
||||
// this way we gonna check which relation ids are new
|
||||
const existInverseEntityRelationIds = await this.connection
|
||||
.getSpecificRepository(persistedSubject.entityTarget)
|
||||
.findRelationIds(relation, persistedSubject.entity, inverseEntityRelationIds);
|
||||
.getSpecificRepository(subject.entityTarget)
|
||||
.findRelationIds(relation, subject.entity, inverseEntityRelationIds);
|
||||
|
||||
// now from all entities in the persisted entity find only those which aren't found in the db
|
||||
/*const newRelationIds = inverseEntityRelationIds.filter(inverseEntityRelationId => {
|
||||
return !existInverseEntityRelationIds.find(relationId => inverseEntityRelationId === relationId);
|
||||
});*/
|
||||
});*/ // todo: remove later if not necessary
|
||||
const persistedEntities = value.filter(val => {
|
||||
const relationValue = relation.getInverseEntityRelationId(val);
|
||||
return !relationValue || !existInverseEntityRelationIds.find(relationId => relationValue === relationId);
|
||||
}); // todo: remove later if not necessary
|
||||
});
|
||||
|
||||
// finally create a new junction insert operation and push it to the array of such operations
|
||||
if (persistedEntities.length > 0) {
|
||||
const operation = new NewJunctionInsertOperation(relation, persistedSubject, persistedEntities);
|
||||
const operation = new NewJunctionInsertOperation(relation, subject, persistedEntities);
|
||||
junctionInsertOperations.push(operation);
|
||||
}
|
||||
});
|
||||
@ -572,29 +636,47 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
*/
|
||||
private async buildRemoveJunctionOperations(): Promise<NewJunctionRemoveOperation[]> {
|
||||
const junctionRemoveOperations: NewJunctionRemoveOperation[] = [];
|
||||
const promises = this.loadedSubjects.map(persistedSubject => { // todo: exclude if object is removed?
|
||||
const promises = persistedSubject.metadata.manyToManyRelations.map(async relation => {
|
||||
const promises = this.loadedSubjects.map(subject => {
|
||||
const promises = subject.metadata.manyToManyRelations.map(async relation => {
|
||||
|
||||
// extract entity value - we only need to proceed if value is defined and its an array
|
||||
const value = relation.getEntityValue(persistedSubject.entity);
|
||||
if (!(value instanceof Array))
|
||||
return;
|
||||
// if subject marked to be removed then all its junctions must be removed
|
||||
if (subject.mustBeRemoved) {
|
||||
|
||||
// get all inverse entities that are "bind" to the currently persisted entity
|
||||
const inverseEntityRelationIds = value
|
||||
.map(v => relation.getInverseEntityRelationId(v))
|
||||
.filter(v => v !== undefined && v !== null);
|
||||
// load from db all relation ids of inverse entities that are NOT "bind" to the currently persisted entity
|
||||
// this way we gonna check which relation ids are missing (e.g. removed)
|
||||
const removedInverseEntityRelationIds = await this.connection
|
||||
.getSpecificRepository(subject.entityTarget)
|
||||
.findRelationIds(relation, subject.databaseEntity);
|
||||
|
||||
// load from db all relation ids of inverse entities that are NOT "bind" to the currently persisted entity
|
||||
// this way we gonna check which relation ids are missing (e.g. removed)
|
||||
const removedInverseEntityRelationIds = await this.connection
|
||||
.getSpecificRepository(persistedSubject.entityTarget)
|
||||
.findRelationIds(relation, persistedSubject.entity, undefined, inverseEntityRelationIds);
|
||||
// finally create a new junction remove operation and push it to the array of such operations
|
||||
if (removedInverseEntityRelationIds.length > 0) {
|
||||
const operation = new NewJunctionRemoveOperation(relation, subject, removedInverseEntityRelationIds);
|
||||
junctionRemoveOperations.push(operation);
|
||||
}
|
||||
|
||||
// finally create a new junction remove operation and push it to the array of such operations
|
||||
if (removedInverseEntityRelationIds.length > 0) {
|
||||
const operation = new NewJunctionRemoveOperation(relation, persistedSubject.entity, removedInverseEntityRelationIds);
|
||||
junctionRemoveOperations.push(operation);
|
||||
} else { // else simply check changed junctions in the persisted entity
|
||||
|
||||
// extract entity value - we only need to proceed if value is defined and its an array
|
||||
const value = relation.getEntityValue(subject.entity);
|
||||
if (!(value instanceof Array))
|
||||
return;
|
||||
|
||||
// get all inverse entities that are "bind" to the currently persisted entity
|
||||
const inverseEntityRelationIds = value
|
||||
.map(v => relation.getInverseEntityRelationId(v))
|
||||
.filter(v => v !== undefined && v !== null);
|
||||
|
||||
// load from db all relation ids of inverse entities that are NOT "bind" to the currently persisted entity
|
||||
// this way we gonna check which relation ids are missing (e.g. removed)
|
||||
const removedInverseEntityRelationIds = await this.connection
|
||||
.getSpecificRepository(subject.entityTarget)
|
||||
.findRelationIds(relation, subject.entity, undefined, inverseEntityRelationIds);
|
||||
|
||||
// finally create a new junction remove operation and push it to the array of such operations
|
||||
if (removedInverseEntityRelationIds.length > 0) {
|
||||
const operation = new NewJunctionRemoveOperation(relation, subject, removedInverseEntityRelationIds);
|
||||
junctionRemoveOperations.push(operation);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
import {QueryBuilder} from "../query-builder/QueryBuilder";
|
||||
import {PlainObjectToDatabaseEntityTransformer} from "../query-builder/transformer/PlainObjectToDatabaseEntityTransformer";
|
||||
import {EntityPersistOperationBuilder} from "./EntityPersistOperationsBuilder";
|
||||
import {PersistOperationExecutor} from "./PersistOperationExecutor";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {QueryRunner} from "../query-runner/QueryRunner";
|
||||
import {Subject} from "./subject/Subject";
|
||||
@ -113,6 +110,38 @@ export class EntityPersister<Entity extends ObjectLiteral> {
|
||||
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Removes given entity from the database.
|
||||
*/
|
||||
async remove(entity: Entity): Promise<Entity> {
|
||||
|
||||
const databaseEntityLoader = new DatabaseEntityLoader(this.connection);
|
||||
await databaseEntityLoader.remove(entity, this.metadata);
|
||||
// console.log("all persistence subjects: ", databaseEntityLoader.loadedSubjects);
|
||||
|
||||
const executor = new PersistSubjectExecutor(this.connection, this.queryRunner);
|
||||
await executor.execute(databaseEntityLoader.loadedSubjects, databaseEntityLoader.junctionInsertOperations, databaseEntityLoader.junctionRemoveOperations);
|
||||
|
||||
/*
|
||||
const queryBuilder = new QueryBuilder(this.connection, this.queryRunner)
|
||||
.select(this.metadata.table.name)
|
||||
.from(this.metadata.target, this.metadata.table.name);
|
||||
const plainObjectToDatabaseEntityTransformer = new PlainObjectToDatabaseEntityTransformer();
|
||||
const dbEntity = await plainObjectToDatabaseEntityTransformer.transform<Entity>(entity, this.metadata, queryBuilder);
|
||||
|
||||
this.metadata.primaryColumnsWithParentPrimaryColumns.forEach(primaryColumn => entity[primaryColumn.name] = undefined);
|
||||
const dbEntities = this.flattenEntityRelationTree(dbEntity, this.metadata);
|
||||
const allPersistedEntities = this.flattenEntityRelationTree(entity, this.metadata);
|
||||
const entityWithId = new Subject(this.metadata, entity);
|
||||
const dbEntityWithId = new Subject(this.metadata, dbEntity);
|
||||
|
||||
const entityPersistOperationBuilder = new EntityPersistOperationBuilder(this.connection.entityMetadatas);
|
||||
const persistOperation = entityPersistOperationBuilder.buildOnlyRemovement(this.metadata, dbEntityWithId, entityWithId, dbEntities, allPersistedEntities);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists given entity in the database.
|
||||
* Persistence is a complex process:
|
||||
@ -121,9 +150,8 @@ export class EntityPersister<Entity extends ObjectLiteral> {
|
||||
*/
|
||||
async persist(entity: Entity): Promise<Entity> {
|
||||
const databaseEntityLoader = new DatabaseEntityLoader(this.connection);
|
||||
await databaseEntityLoader.load(entity, this.metadata);
|
||||
console.log("all persistence subjects: ", databaseEntityLoader.loadedSubjects);
|
||||
|
||||
await databaseEntityLoader.persist(entity, this.metadata);
|
||||
// console.log("all persistence subjects: ", databaseEntityLoader.loadedSubjects);
|
||||
|
||||
const executor = new PersistSubjectExecutor(this.connection, this.queryRunner);
|
||||
await executor.execute(databaseEntityLoader.loadedSubjects, databaseEntityLoader.junctionInsertOperations, databaseEntityLoader.junctionRemoveOperations);
|
||||
@ -504,29 +532,6 @@ export class EntityPersister<Entity extends ObjectLiteral> {
|
||||
return insertOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given entity from the database.
|
||||
*/
|
||||
async remove(entity: Entity): Promise<Entity> {
|
||||
const queryBuilder = new QueryBuilder(this.connection, this.queryRunner)
|
||||
.select(this.metadata.table.name)
|
||||
.from(this.metadata.target, this.metadata.table.name);
|
||||
const plainObjectToDatabaseEntityTransformer = new PlainObjectToDatabaseEntityTransformer();
|
||||
const dbEntity = await plainObjectToDatabaseEntityTransformer.transform<Entity>(entity, this.metadata, queryBuilder);
|
||||
|
||||
this.metadata.primaryColumnsWithParentPrimaryColumns.forEach(primaryColumn => entity[primaryColumn.name] = undefined);
|
||||
const dbEntities = this.flattenEntityRelationTree(dbEntity, this.metadata);
|
||||
const allPersistedEntities = this.flattenEntityRelationTree(entity, this.metadata);
|
||||
const entityWithId = new Subject(this.metadata, entity);
|
||||
const dbEntityWithId = new Subject(this.metadata, dbEntity);
|
||||
|
||||
const entityPersistOperationBuilder = new EntityPersistOperationBuilder(this.connection.entityMetadatas);
|
||||
const persistOperation = entityPersistOperationBuilder.buildOnlyRemovement(this.metadata, dbEntityWithId, entityWithId, dbEntities, allPersistedEntities);
|
||||
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;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import {PersistOperation} from "./operation/PersistOperation";
|
||||
import {InsertOperation} from "./operation/InsertOperation";
|
||||
import {JunctionRemoveOperation} from "./operation/JunctionRemoveOperation";
|
||||
import {UpdateByRelationOperation} from "./operation/UpdateByRelationOperation";
|
||||
import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOperation";
|
||||
import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
@ -43,11 +42,11 @@ export class PersistSubjectExecutor {
|
||||
// check if remove subject also must be inserted or updated - then we throw an exception
|
||||
const removeInInserts = removeSubjects.find(removeSubject => insertSubjects.indexOf(removeSubject) !== -1);
|
||||
if (removeInInserts)
|
||||
throw new Error(`Removed entity ${removeInInserts.entityTarget} is also scheduled for insert operation. This looks like ORM problem. Please report a github issue.`);
|
||||
throw new Error(`Removed entity ${removeInInserts.entityTargetName} is also scheduled for insert operation. This looks like ORM problem. Please report a github issue.`);
|
||||
|
||||
const removeInUpdates = removeSubjects.find(removeSubject => updateSubjects.indexOf(removeSubject) !== -1);
|
||||
if (removeInUpdates)
|
||||
throw new Error(`Removed entity "${removeInUpdates.entityTarget}" is also scheduled for update operation. ` +
|
||||
throw new Error(`Removed entity "${removeInUpdates.entityTargetName}" is also scheduled for update operation. ` +
|
||||
`Make sure you are not updating and removing same object (note that update or remove may be executed by cascade operations).`);
|
||||
|
||||
// todo: there is nothing to update in inserted entity too
|
||||
@ -64,22 +63,21 @@ export class PersistSubjectExecutor {
|
||||
}
|
||||
|
||||
await this.executeInsertOperations(insertSubjects);
|
||||
// await this.executeInsertClosureTableOperations(insertSubjects);
|
||||
// await this.executeUpdateTreeLevelOperations(insertSubjects);
|
||||
await this.executeInsertClosureTableOperations(insertSubjects);
|
||||
await this.executeInsertJunctionsOperations(junctionInsertOperations, insertSubjects);
|
||||
// await this.executeRemoveJunctionsOperations(junctionRemoveOperations);
|
||||
await this.executeRemoveJunctionsOperations(junctionRemoveOperations);
|
||||
// await this.executeRemoveRelationOperations(persistOperation); // todo: can we add these operations into list of updated?
|
||||
// await this.executeUpdateRelationsOperations(persistOperation); // todo: merge these operations with update operations?
|
||||
// await this.executeUpdateInverseRelationsOperations(persistOperation); // todo: merge these operations with update operations?
|
||||
// await this.executeUpdateOperations(updateSubjects);
|
||||
// await this.executeRemoveOperations(removeSubjects);
|
||||
await this.executeUpdateOperations(updateSubjects);
|
||||
await this.executeRemoveOperations(removeSubjects);
|
||||
|
||||
// commit transaction if it was started by us
|
||||
if (isTransactionStartedByItself === true)
|
||||
await this.queryRunner.commitTransaction();
|
||||
|
||||
// update all special columns in persisted entities, like inserted id or remove ids from the removed entities
|
||||
// await this.updateSpecialColumnsInPersistedEntities(insertSubjects, updateSubjects, removeSubjects);
|
||||
await this.updateSpecialColumnsInPersistedEntities(insertSubjects, updateSubjects, removeSubjects);
|
||||
|
||||
// finally broadcast events
|
||||
this.connection.broadcaster.broadcastAfterEventsForAll(insertSubjects, updateSubjects, removeSubjects);
|
||||
@ -154,7 +152,7 @@ export class PersistSubjectExecutor {
|
||||
});
|
||||
});
|
||||
if (Object.keys(updateOptions).length > 0) {
|
||||
const conditions = subject.metadata.getEntityIdMap(subject.entity) || subject.metadata.createSimpleIdMap(subject.newlyGeneratedId);
|
||||
const conditions = subject.metadata.getDatabaseEntityIdMap(subject.entity) || subject.metadata.createSimpleDatabaseIdMap(subject.newlyGeneratedId);
|
||||
const updatePromise = this.queryRunner.update(subject.metadata.table.name, updateOptions, conditions);
|
||||
updatePromises.push(updatePromise);
|
||||
}
|
||||
@ -170,7 +168,7 @@ export class PersistSubjectExecutor {
|
||||
if (subValue === insertedSubject.entity) {
|
||||
|
||||
if (referencedColumn.isGenerated) {
|
||||
const conditions = insertedSubject.metadata.getEntityIdMap(insertedSubject.entity) || insertedSubject.metadata.createSimpleIdMap(insertedSubject.newlyGeneratedId);
|
||||
const conditions = insertedSubject.metadata.getDatabaseEntityIdMap(insertedSubject.entity) || insertedSubject.metadata.createSimpleDatabaseIdMap(insertedSubject.newlyGeneratedId);
|
||||
const updateOptions = { [relation.inverseRelation.joinColumn.name]: subject.newlyGeneratedId };
|
||||
const updatePromise = this.queryRunner.update(relation.inverseRelation.entityMetadata.table.name, updateOptions, conditions);
|
||||
updatePromises.push(updatePromise);
|
||||
@ -191,7 +189,7 @@ export class PersistSubjectExecutor {
|
||||
if (subject.entity[relation.propertyName] === insertedSubject.entity) {
|
||||
|
||||
if (referencedColumn.isGenerated) {
|
||||
const conditions = insertedSubject.metadata.getEntityIdMap(insertedSubject.entity) || insertedSubject.metadata.createSimpleIdMap(insertedSubject.newlyGeneratedId);
|
||||
const conditions = insertedSubject.metadata.getDatabaseEntityIdMap(insertedSubject.entity) || insertedSubject.metadata.createSimpleDatabaseIdMap(insertedSubject.newlyGeneratedId);
|
||||
const updateOptions = { [relation.inverseRelation.joinColumn.name]: subject.newlyGeneratedId };
|
||||
const updatePromise = this.queryRunner.update(relation.inverseRelation.entityMetadata.table.name, updateOptions, conditions);
|
||||
updatePromises.push(updatePromise);
|
||||
@ -212,22 +210,23 @@ export class PersistSubjectExecutor {
|
||||
/**
|
||||
* Executes insert operations for closure tables.
|
||||
*/
|
||||
private executeInsertClosureTableOperations(insertSubjects: Subject[], updatesByRelations: Subject[]) { // todo: what to do with updatesByRelations
|
||||
private executeInsertClosureTableOperations(insertSubjects: Subject[]/*, updatesByRelations: Subject[]*/) { // todo: what to do with updatesByRelations
|
||||
const promises = insertSubjects
|
||||
.filter(subject => subject.metadata.table.isClosure)
|
||||
.map(async subject => {
|
||||
const relationsUpdateMap = this.findUpdateOperationForEntity(updatesByRelations, insertSubjects, subject.entity);
|
||||
subject.treeLevel = await this.insertIntoClosureTable(subject, relationsUpdateMap);
|
||||
// const relationsUpdateMap = this.findUpdateOperationForEntity(updatesByRelations, insertSubjects, subject.entity);
|
||||
// subject.treeLevel = await this.insertIntoClosureTable(subject, relationsUpdateMap);
|
||||
await this.insertClosureTableValues(subject, insertSubjects);
|
||||
});
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes update tree level operations in inserted entities right after data into closure table inserted.
|
||||
*/
|
||||
|
||||
private executeUpdateTreeLevelOperations(insertOperations: Subject[]) {
|
||||
return Promise.all(insertOperations.map(subject => this.updateTreeLevel(subject)));
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Executes insert junction operations.
|
||||
@ -287,8 +286,34 @@ export class PersistSubjectExecutor {
|
||||
/**
|
||||
* Executes remove operations.
|
||||
*/
|
||||
private executeRemoveOperations(removeSubjects: Subject[]) {
|
||||
return Promise.all(removeSubjects.map(subject => this.remove(subject)));
|
||||
private async executeRemoveOperations(removeSubjects: Subject[]): Promise<void> {
|
||||
// order subjects in a proper order
|
||||
|
||||
/*const DepGraph = require("dependency-graph").DepGraph;
|
||||
const graph = new DepGraph();
|
||||
removeSubjects.forEach(subject => {
|
||||
// console.log("adding node: ", subject.metadata.name);
|
||||
if (!graph.hasNode(subject.metadata.name))
|
||||
graph.addNode(subject.metadata.name);
|
||||
});
|
||||
removeSubjects.forEach(subject => {
|
||||
subject.metadata
|
||||
.relationsWithJoinColumns
|
||||
.filter(relation => relation.isCascadeRemove)
|
||||
.forEach(relation => {
|
||||
if (graph.hasNode(subject.metadata.name) && graph.hasNode(relation.inverseEntityMetadata.name))
|
||||
graph.addDependency(subject.metadata.name, relation.inverseEntityMetadata.name);
|
||||
});
|
||||
});
|
||||
try {
|
||||
const order = graph.overallOrder();
|
||||
console.log("order: ", order);
|
||||
|
||||
} catch (err) {
|
||||
throw new Error(err.toString().replace("Error: Dependency Cycle Found: ", ""));
|
||||
}*/
|
||||
|
||||
await Promise.all(removeSubjects.map(subject => this.remove(subject)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -299,12 +324,12 @@ export class PersistSubjectExecutor {
|
||||
// update entity ids of the newly inserted entities
|
||||
insertSubjects.forEach(subject => {
|
||||
subject.metadata.primaryColumns.forEach(primaryColumn => {
|
||||
if (subject.entityId)
|
||||
subject.entity[primaryColumn.propertyName] = subject.entityId[primaryColumn.propertyName];
|
||||
if (subject.newlyGeneratedId)
|
||||
subject.entity[primaryColumn.propertyName] = subject.newlyGeneratedId;
|
||||
});
|
||||
subject.metadata.parentPrimaryColumns.forEach(primaryColumn => {
|
||||
if (subject.entityId)
|
||||
subject.entity[primaryColumn.propertyName] = subject.entityId[primaryColumn.propertyName];
|
||||
if (subject.newlyGeneratedId)
|
||||
subject.entity[primaryColumn.propertyName] = subject.newlyGeneratedId;
|
||||
});
|
||||
});
|
||||
|
||||
@ -335,6 +360,7 @@ export class PersistSubjectExecutor {
|
||||
|
||||
// remove ids from the entities that were removed
|
||||
removeSubjects.forEach(subject => {
|
||||
if (!subject.entity) return;
|
||||
// const removedEntity = removeSubjects.allPersistedEntities.find(allNewEntity => {
|
||||
// return allNewEntity.entityTarget === subject.entityTarget && allNewEntity.compareId(subject.metadata.getEntityIdMap(subject.entity)!);
|
||||
// });
|
||||
@ -394,14 +420,14 @@ export class PersistSubjectExecutor {
|
||||
const metadata = this.connection.entityMetadatas.findByTarget(operation.entityTarget);
|
||||
let idInInserts: ObjectLiteral|undefined = undefined;
|
||||
if (relatedInsertOperation && relatedInsertOperation.entityId) {
|
||||
idInInserts = { [metadata.firstPrimaryColumn.propertyName]: relatedInsertOperation.entityId[metadata.firstPrimaryColumn.propertyName] };
|
||||
idInInserts = { [metadata.firstPrimaryColumn.name]: relatedInsertOperation.entityId[metadata.firstPrimaryColumn.propertyName] };
|
||||
} // todo: use join column instead of primary column here
|
||||
tableName = metadata.table.name;
|
||||
relationName = operation.updatedRelation.name;
|
||||
relationId = operation.insertOperation.entityId[metadata.firstPrimaryColumn.propertyName]; // todo: make sure entityId is always a map
|
||||
// idColumn = metadata.primaryColumn.name;
|
||||
// id = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
|
||||
updateMap = metadata.getEntityIdColumnMap(operation.targetEntity) || idInInserts; // todo: make sure idInInserts always object even when id is single!!!
|
||||
updateMap = metadata.getDatabaseEntityIdMap(operation.targetEntity) || idInInserts; // todo: make sure idInInserts always object even when id is single!!!
|
||||
}
|
||||
if (!updateMap)
|
||||
throw new Error(`Cannot execute update by relation operation, because cannot find update criteria`);
|
||||
@ -414,7 +440,7 @@ export class PersistSubjectExecutor {
|
||||
const fromEntityMetadata = this.connection.entityMetadatas.findByTarget(operation.fromEntityTarget);
|
||||
const tableName = targetEntityMetadata.table.name;
|
||||
const targetRelation = operation.fromRelation.inverseRelation;
|
||||
const updateMap = targetEntityMetadata.getEntityIdColumnMap(operation.targetEntity);
|
||||
const updateMap = targetEntityMetadata.getDatabaseEntityIdMap(operation.targetEntity);
|
||||
if (!updateMap) return; // todo: is return correct here?
|
||||
|
||||
const fromEntityInsertOperation = insertOperations.find(o => o.entity === operation.fromEntity);
|
||||
@ -439,7 +465,7 @@ export class PersistSubjectExecutor {
|
||||
const valueMaps: { tableName: string, metadata: EntityMetadata, values: ObjectLiteral }[] = [];
|
||||
|
||||
subject.diffColumns.forEach(column => {
|
||||
if (!column.entityTarget) return;
|
||||
if (!column.entityTarget) return; // todo: how this can be possible?
|
||||
const metadata = this.connection.entityMetadatas.findByTarget(column.entityTarget);
|
||||
let valueMap = valueMaps.find(valueMap => valueMap.tableName === metadata.table.name);
|
||||
if (!valueMap) {
|
||||
@ -543,17 +569,17 @@ export class PersistSubjectExecutor {
|
||||
if (subject.metadata.parentEntityMetadata) {
|
||||
const parentConditions: ObjectLiteral = {};
|
||||
subject.metadata.parentPrimaryColumns.forEach(column => {
|
||||
parentConditions[column.name] = subject.entity[column.propertyName];
|
||||
parentConditions[column.name] = subject.databaseEntity![column.propertyName];
|
||||
});
|
||||
await this.queryRunner.delete(subject.metadata.parentEntityMetadata.table.name, parentConditions);
|
||||
|
||||
const childConditions: ObjectLiteral = {};
|
||||
subject.metadata.primaryColumnsWithParentIdColumns.forEach(column => {
|
||||
childConditions[column.name] = subject.entity[column.propertyName];
|
||||
childConditions[column.name] = subject.databaseEntity![column.propertyName];
|
||||
});
|
||||
await this.queryRunner.delete(subject.metadata.table.name, childConditions);
|
||||
} else {
|
||||
await this.queryRunner.delete(subject.metadata.table.name, subject.metadata.getEntityIdColumnMap(subject.entity)!);
|
||||
await this.queryRunner.delete(subject.metadata.table.name, subject.metadata.getEntityIdColumnMap(subject.databaseEntity!)!);
|
||||
}
|
||||
}
|
||||
|
||||
@ -692,6 +718,46 @@ export class PersistSubjectExecutor {
|
||||
return this.zipObject(allColumnNames, allValues);
|
||||
}
|
||||
|
||||
private async insertClosureTableValues(subject: Subject, insertedSubjects: 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 tableName = subject.metadata.closureJunctionTable.table.name;
|
||||
const referencedColumn = subject.metadata.treeParentRelation.joinColumn.referencedColumn; // todo: check if joinColumn works
|
||||
|
||||
let newEntityId = subject.entity[referencedColumn.propertyName];
|
||||
if (!newEntityId && referencedColumn.isGenerated) {
|
||||
newEntityId = subject.newlyGeneratedId;
|
||||
} // todo: implement other special column types too
|
||||
|
||||
const parentEntity = subject.entity[subject.metadata.treeParentRelation.propertyName];
|
||||
let parentEntityId = parentEntity[referencedColumn.propertyName];
|
||||
if (!parentEntityId && referencedColumn.isGenerated) {
|
||||
const parentInsertedSubject = insertedSubjects.find(subject => subject.entity === parentEntity);
|
||||
// todo: throw exception if parentInsertedSubject is not set
|
||||
parentEntityId = parentInsertedSubject.newlyGeneratedId;
|
||||
} // todo: implement other special column types too
|
||||
|
||||
subject.treeLevel = await this.queryRunner.insertIntoClosureTable(tableName, newEntityId, parentEntityId, subject.metadata.hasTreeLevelColumn);
|
||||
|
||||
if (subject.metadata.hasTreeLevelColumn) {
|
||||
const values = { [subject.metadata.treeLevelColumn.name]: subject.treeLevel };
|
||||
await this.queryRunner.update(subject.metadata.table.name, values, { [referencedColumn.name]: newEntityId });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
|
||||
private async updateTreeLevel(subject: Subject): Promise<void> {
|
||||
if (subject.metadata.hasTreeLevelColumn && subject.treeLevel) {
|
||||
const values = { [subject.metadata.treeLevelColumn.name]: subject.treeLevel };
|
||||
await this.queryRunner.update(subject.metadata.table.name, values, subject.entityId);
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
private insertIntoClosureTable(subject: Subject, updateMap: ObjectLiteral) {
|
||||
// here we can only support to work only with single primary key entities
|
||||
|
||||
@ -721,16 +787,6 @@ export class PersistSubjectExecutor {
|
||||
})*/;
|
||||
}
|
||||
|
||||
private async updateTreeLevel(subject: Subject): Promise<void> {
|
||||
if (subject.metadata.hasTreeLevelColumn && subject.treeLevel) {
|
||||
if (!subject.entityId)
|
||||
throw new Error(`remove operation does not have entity id`);
|
||||
|
||||
const values = { [subject.metadata.treeLevelColumn.name]: subject.treeLevel };
|
||||
await this.queryRunner.update(subject.metadata.table.name, values, subject.entityId);
|
||||
}
|
||||
}
|
||||
|
||||
/*private insertJunctions(junctionOperation: NewJunctionInsertOperation, insertOperations: Subject[]) {
|
||||
// I think here we can only support to work only with single primary key entities
|
||||
|
||||
@ -815,15 +871,17 @@ export class PersistSubjectExecutor {
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
private removeJunctions(junctionOperation: JunctionRemoveOperation) {
|
||||
// I think here we can only support to work only with single primary key entities
|
||||
const junctionMetadata = junctionOperation.metadata;
|
||||
const metadata1 = this.connection.entityMetadatas.findByTarget(junctionOperation.entity1Target);
|
||||
const metadata2 = this.connection.entityMetadatas.findByTarget(junctionOperation.entity2Target);
|
||||
const columns = junctionMetadata.columns.map(column => column.name);
|
||||
const id1 = junctionOperation.entity1[metadata1.firstPrimaryColumn.propertyName];
|
||||
const id2 = junctionOperation.entity2[metadata2.firstPrimaryColumn.propertyName];
|
||||
return this.queryRunner.delete(junctionMetadata.table.name, { [columns[0]]: id1, [columns[1]]: id2 });
|
||||
private async removeJunctions(junctionOperation: NewJunctionRemoveOperation) {
|
||||
const junctionMetadata = junctionOperation.relation.junctionEntityMetadata;
|
||||
const ownId = junctionOperation.relation.getOwnEntityRelationId(junctionOperation.subject.entity || junctionOperation.subject.databaseEntity);
|
||||
const ownColumn = junctionOperation.relation.isOwning ? junctionMetadata.columns[0] : junctionMetadata.columns[1];
|
||||
const relateColumn = junctionOperation.relation.isOwning ? junctionMetadata.columns[1] : junctionMetadata.columns[0];
|
||||
|
||||
const removePromises = junctionOperation.junctionEntityRelationIds.map(async relationId => {
|
||||
await this.queryRunner.delete(junctionMetadata.table.name, { [ownColumn.name]: ownId, [relateColumn.name]: relationId });
|
||||
});
|
||||
|
||||
await Promise.all(removePromises);
|
||||
}
|
||||
|
||||
private zipObject(keys: any[], values: any[]): Object {
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import {ObjectLiteral} from "../../common/ObjectLiteral";
|
||||
import {RelationMetadata} from "../../metadata/RelationMetadata";
|
||||
import {Subject} from "../subject/Subject";
|
||||
|
||||
// todo: for both remove and insert can be used same object
|
||||
export class NewJunctionRemoveOperation {
|
||||
|
||||
// todo: we can send subjects instead of entities and junction entities if needed
|
||||
constructor(public relation: RelationMetadata,
|
||||
public entity: ObjectLiteral,
|
||||
public junctionEntityRelationIds: ObjectLiteral[]) {
|
||||
public subject: Subject,
|
||||
public junctionEntityRelationIds: any[]) {
|
||||
}
|
||||
|
||||
}
|
||||
@ -67,6 +67,19 @@ export class Subject { // todo: move entity with id creation into metadata? // t
|
||||
return this.metadata.target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns readable / loggable name of the entity target.
|
||||
*/
|
||||
get entityTargetName(): string {
|
||||
if (this.entityTarget instanceof Function) {
|
||||
if (this.entityTarget.name) {
|
||||
return this.entityTarget.name;
|
||||
}
|
||||
}
|
||||
|
||||
return this.entityTarget as string;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.metadata.getEntityIdMap(this.entity);
|
||||
}
|
||||
@ -90,8 +103,8 @@ export class Subject { // todo: move entity with id creation into metadata? // t
|
||||
set databaseEntity(databaseEntity: ObjectLiteral|undefined) {
|
||||
this._databaseEntity = databaseEntity;
|
||||
if (this.entity && databaseEntity) {
|
||||
this.buildDiffColumns();
|
||||
this.buildDiffRelationalColumns();
|
||||
this.diffColumns = this.buildDiffColumns();
|
||||
this.diffRelations = this.buildDiffRelationalColumns();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1038,7 +1038,7 @@ export class QueryBuilder<Entity> {
|
||||
const [sql, parameters] = this.getSqlWithParameters();
|
||||
|
||||
try {
|
||||
console.log(sql);
|
||||
// console.log(sql);
|
||||
return await queryRunner.query(sql, parameters)
|
||||
.then(results => {
|
||||
scalarResults = results;
|
||||
@ -1101,7 +1101,7 @@ export class QueryBuilder<Entity> {
|
||||
}).join(", ") + ") as cnt";
|
||||
|
||||
const countQuery = this
|
||||
.clone({ queryRunner: queryRunner, skipOrderBys: true, ignoreParentTablesJoins: true })
|
||||
.clone({ queryRunner: queryRunner, skipOrderBys: true, ignoreParentTablesJoins: true, skipLimit: true, skipOffset: true })
|
||||
.select(countSql);
|
||||
|
||||
const [countQuerySql, countQueryParameters] = countQuery.getSqlWithParameters();
|
||||
|
||||
@ -57,6 +57,11 @@ export interface QueryRunner {
|
||||
*/
|
||||
insert(tableName: string, valuesMap: Object, generatedColumn?: ColumnMetadata): Promise<any>;
|
||||
|
||||
/**
|
||||
* Performs a simple DELETE query by a given conditions in a given table.
|
||||
*/
|
||||
delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Performs a simple DELETE query by a given conditions in a given table.
|
||||
*/
|
||||
|
||||
@ -3,7 +3,6 @@ import {setupTestingConnections, closeConnections, reloadDatabases} from "../../
|
||||
import {Connection} from "../../../../src/connection/Connection";
|
||||
import {Post} from "./entity/Post";
|
||||
import {Category} from "./entity/Category";
|
||||
import {Photo} from "./entity/Photo";
|
||||
|
||||
describe("persistence > cascade operations", () => {
|
||||
|
||||
@ -16,9 +15,9 @@ describe("persistence > cascade operations", () => {
|
||||
beforeEach(() => reloadDatabases(connections));
|
||||
after(() => closeConnections(connections));
|
||||
|
||||
describe("cascade insert", function() {
|
||||
describe.skip("cascade insert", function() {
|
||||
|
||||
it.only("should work perfectly", () => Promise.all(connections.map(async connection => {
|
||||
it("should work perfectly", () => Promise.all(connections.map(async connection => {
|
||||
|
||||
|
||||
// create category
|
||||
@ -34,25 +33,41 @@ describe("persistence > cascade operations", () => {
|
||||
// create post
|
||||
const post1 = new Post();
|
||||
post1.title = "Hello Post #1";
|
||||
// post1.oneCategory = category1;
|
||||
post1.oneCategory = category1;
|
||||
|
||||
// todo(next): check out to one
|
||||
|
||||
// create photos
|
||||
const photo1 = new Photo();
|
||||
/*const photo1 = new Photo();
|
||||
photo1.url = "http://me.com/photo";
|
||||
photo1.post = post1;
|
||||
photo1.categories = [category1, category2];
|
||||
|
||||
const photo2 = new Photo();
|
||||
photo2.url = "http://me.com/photo";
|
||||
photo2.post = post1;
|
||||
photo2.post = post1;*/
|
||||
|
||||
// category1.photos = [photo1, photo2];
|
||||
|
||||
// post1.category = category1;
|
||||
// post1.category.photos = [photo1, photo2];
|
||||
await connection.entityManager.persist(photo1);
|
||||
await connection.entityManager.persist(post1);
|
||||
|
||||
console.log("********************************************************");
|
||||
console.log("updating: ", post1);
|
||||
console.log("********************************************************");
|
||||
|
||||
post1.title = "updated post #1";
|
||||
post1.oneCategory.name = "updated category";
|
||||
await connection.entityManager.persist(post1);
|
||||
|
||||
console.log("********************************************************");
|
||||
console.log("removing: ", post1);
|
||||
console.log("********************************************************");
|
||||
|
||||
await connection.entityManager.remove(post1);
|
||||
|
||||
// await connection.entityManager.persist(post1);
|
||||
|
||||
console.log("********************************************************");
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import {Connection} from "../../../../src/connection/Connection";
|
||||
import {Post} from "./entity/Post";
|
||||
import {Category} from "./entity/Category";
|
||||
|
||||
describe("persistence > cascade operations with custom name", () => {
|
||||
describe.only("persistence > cascade operations with custom name", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await setupTestingConnections({
|
||||
|
||||
@ -19,20 +19,51 @@ export class Category {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@OneToOne(type => Post, post => post.oneCategory, {
|
||||
@OneToMany(type => Post, post => post.manyToOneCategory, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
cascadeRemove: true,
|
||||
})
|
||||
oneToManyPosts: Post[];
|
||||
|
||||
@OneToMany(type => Post, post => post.noCascadeManyToOneCategory, {
|
||||
cascadeInsert: false,
|
||||
cascadeUpdate: false,
|
||||
cascadeRemove: false,
|
||||
})
|
||||
noCascadeOneToManyPosts: Post[];
|
||||
|
||||
@OneToOne(type => Post, post => post.oneToOneCategory, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true,
|
||||
})
|
||||
@JoinColumn()
|
||||
onePost: Post;
|
||||
oneToOneOwnerPost: Post;
|
||||
|
||||
@OneToMany(type => Post, post => post.category, {
|
||||
@OneToOne(type => Post, post => post.noCascadeOneToOneCategory, {
|
||||
cascadeInsert: false,
|
||||
cascadeUpdate: false,
|
||||
cascadeRemove: false,
|
||||
})
|
||||
@JoinColumn()
|
||||
noCascadeOneToOnePost: Post;
|
||||
|
||||
@ManyToMany(type => Post, post => post.manyToManyOwnerCategories, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
cascadeRemove: true,
|
||||
})
|
||||
posts: Post[];
|
||||
@JoinTable()
|
||||
manyToManyPosts: Post[];
|
||||
|
||||
@ManyToMany(type => Post, post => post.noCascadeManyToManyOwnerCategories, {
|
||||
cascadeInsert: false,
|
||||
cascadeUpdate: false,
|
||||
cascadeRemove: false,
|
||||
})
|
||||
@JoinTable()
|
||||
noCascadeManyToManyPosts: Post[];
|
||||
|
||||
@ManyToMany(type => Photo, {
|
||||
cascadeInsert: true,
|
||||
|
||||
@ -3,6 +3,7 @@ import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/Prima
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
|
||||
import {Post} from "../entity/Post";
|
||||
import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
|
||||
|
||||
@Table()
|
||||
export class Photo {
|
||||
@ -21,4 +22,11 @@ export class Photo {
|
||||
})
|
||||
post: Post|null;
|
||||
|
||||
@ManyToMany(type => Post, photo => photo.photos, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true,
|
||||
})
|
||||
posts: Post[];
|
||||
|
||||
}
|
||||
@ -17,26 +17,64 @@ export class Post {
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@ManyToOne(type => Category, category => category.posts, {
|
||||
@ManyToOne(type => Category, category => category.oneToManyPosts, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
cascadeRemove: true,
|
||||
})
|
||||
category: Category|null;
|
||||
manyToOneCategory: Category;
|
||||
|
||||
@ManyToMany(type => Photo, {
|
||||
@ManyToOne(type => Category, category => category.noCascadeOneToManyPosts, {
|
||||
cascadeInsert: false,
|
||||
cascadeUpdate: false,
|
||||
cascadeRemove: false,
|
||||
})
|
||||
noCascadeManyToOneCategory: Category;
|
||||
|
||||
@OneToOne(type => Category, category => category.oneToOneOwnerPost, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
cascadeRemove: true,
|
||||
})
|
||||
oneToOneCategory: Category;
|
||||
|
||||
@OneToOne(type => Category, category => category.noCascadeOneToOnePost, {
|
||||
cascadeInsert: false,
|
||||
cascadeUpdate: false,
|
||||
cascadeRemove: false,
|
||||
})
|
||||
noCascadeOneToOneCategory: Category;
|
||||
|
||||
@ManyToMany(type => Category, category => category.manyToManyPosts, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true,
|
||||
})
|
||||
@JoinTable()
|
||||
manyToManyOwnerCategories: Category[];
|
||||
|
||||
@ManyToMany(type => Category, category => category.noCascadeManyToManyPosts, {
|
||||
cascadeInsert: false,
|
||||
cascadeUpdate: false,
|
||||
cascadeRemove: false,
|
||||
})
|
||||
@JoinTable()
|
||||
noCascadeManyToManyOwnerCategories: Category[];
|
||||
|
||||
@ManyToMany(type => Photo, photo => photo.posts, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true,
|
||||
})
|
||||
@JoinTable()
|
||||
photos: Photo[];
|
||||
|
||||
@OneToOne(type => Category, category => category.onePost, {
|
||||
@ManyToMany(type => Photo, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
cascadeRemove: true,
|
||||
})
|
||||
oneCategory: Category;
|
||||
@JoinTable()
|
||||
noInversePhotos: Photo[];
|
||||
|
||||
}
|
||||
@ -3,9 +3,8 @@ import {setupTestingConnections, closeConnections, reloadDatabases} from "../../
|
||||
import {Connection} from "../../../../src/connection/Connection";
|
||||
import {Post} from "./entity/Post";
|
||||
import {Category} from "./entity/Category";
|
||||
import {Photo} from "./entity/Photo";
|
||||
|
||||
describe("persistence > insert operations", () => {
|
||||
describe.skip("persistence > insert operations", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await setupTestingConnections({
|
||||
@ -28,21 +27,21 @@ describe("persistence > insert operations", () => {
|
||||
// create post
|
||||
const post1 = new Post();
|
||||
post1.title = "Hello Post #1";
|
||||
post1.oneCategory = category1;
|
||||
|
||||
// todo(next): check out to one
|
||||
|
||||
// create photos
|
||||
const photo1 = new Photo();
|
||||
/* const photo1 = new Photo();
|
||||
photo1.url = "http://me.com/photo";
|
||||
photo1.post = post1;
|
||||
const photo2 = new Photo();
|
||||
photo2.url = "http://me.com/photo";
|
||||
photo2.post = post1;
|
||||
photo2.post = post1;*/
|
||||
|
||||
// post1.category = category1;
|
||||
// post1.category.photos = [photo1, photo2];
|
||||
await connection.entityManager.persist(post1);
|
||||
await connection.entityManager.persist(category1);
|
||||
|
||||
console.log("********************************************************");
|
||||
|
||||
@ -81,106 +80,6 @@ describe("persistence > insert operations", () => {
|
||||
|
||||
})));
|
||||
|
||||
it("should insert entity when cascade option is set", () => Promise.all(connections.map(async connection => {
|
||||
|
||||
// create first category and post and save them
|
||||
const category1 = new Category();
|
||||
category1.name = "Category saved by cascades #1";
|
||||
|
||||
const post1 = new Post();
|
||||
post1.title = "Hello Post #1";
|
||||
post1.category = category1;
|
||||
|
||||
await connection.entityManager.persist(post1);
|
||||
|
||||
// create second category and post and save them
|
||||
const category2 = new Category();
|
||||
category2.name = "Category saved by cascades #2";
|
||||
|
||||
const post2 = new Post();
|
||||
post2.title = "Hello Post #2";
|
||||
post2.category = category2;
|
||||
|
||||
await connection.entityManager.persist(post2);
|
||||
|
||||
// now check
|
||||
const posts = await connection.entityManager.find(Post, {
|
||||
alias: "post",
|
||||
innerJoinAndSelect: {
|
||||
category: "post.category"
|
||||
},
|
||||
orderBy: {
|
||||
"post.id": "ASC"
|
||||
}
|
||||
});
|
||||
|
||||
posts.should.be.eql([{
|
||||
id: 1,
|
||||
title: "Hello Post #1",
|
||||
category: {
|
||||
id: 1,
|
||||
name: "Category saved by cascades #1"
|
||||
}
|
||||
}, {
|
||||
id: 2,
|
||||
title: "Hello Post #2",
|
||||
category: {
|
||||
id: 2,
|
||||
name: "Category saved by cascades #2"
|
||||
}
|
||||
}]);
|
||||
})));
|
||||
|
||||
it("should insert from inverse side when cascade option is set", () => Promise.all(connections.map(async connection => {
|
||||
|
||||
// create first post and category and save them
|
||||
const post1 = new Post();
|
||||
post1.title = "Hello Post #1";
|
||||
|
||||
const category1 = new Category();
|
||||
category1.name = "Category saved by cascades #1";
|
||||
category1.posts = [post1];
|
||||
|
||||
await connection.entityManager.persist(category1);
|
||||
|
||||
// create first post and category and save them
|
||||
const post2 = new Post();
|
||||
post2.title = "Hello Post #2";
|
||||
|
||||
const category2 = new Category();
|
||||
category2.name = "Category saved by cascades #2";
|
||||
category2.posts = [post2];
|
||||
|
||||
await connection.entityManager.persist(category2);
|
||||
|
||||
// now check
|
||||
const posts = await connection.entityManager.find(Post, {
|
||||
alias: "post",
|
||||
innerJoinAndSelect: {
|
||||
category: "post.category"
|
||||
},
|
||||
orderBy: {
|
||||
"post.id": "ASC"
|
||||
}
|
||||
});
|
||||
|
||||
posts.should.be.eql([{
|
||||
id: 1,
|
||||
title: "Hello Post #1",
|
||||
category: {
|
||||
id: 1,
|
||||
name: "Category saved by cascades #1"
|
||||
}
|
||||
}, {
|
||||
id: 2,
|
||||
title: "Hello Post #2",
|
||||
category: {
|
||||
id: 2,
|
||||
name: "Category saved by cascades #2"
|
||||
}
|
||||
}]);
|
||||
})));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@ -19,7 +19,7 @@ describe("one-to-one", function() {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const options: ConnectionOptions = {
|
||||
driver: createTestingConnectionOptions("postgres"),
|
||||
driver: createTestingConnectionOptions("mysql"),
|
||||
entities: [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor]
|
||||
};
|
||||
|
||||
@ -356,6 +356,7 @@ describe("one-to-one", function() {
|
||||
.getSingleResult();
|
||||
|
||||
}).then(loadedPost => {
|
||||
console.log("loadedPost: ", loadedPost);
|
||||
loadedPost.image.url = "new-logo.png";
|
||||
return postRepository.persist(loadedPost);
|
||||
|
||||
|
||||
@ -138,6 +138,7 @@ describe("many-to-one", function() {
|
||||
expectedPost.text = savedPost.text;
|
||||
expectedPost.title = savedPost.title;
|
||||
|
||||
expectedDetails.posts = [];
|
||||
expectedDetails.posts.push(expectedPost);
|
||||
|
||||
return postDetailsRepository
|
||||
@ -407,7 +408,7 @@ describe("many-to-one", function() {
|
||||
.getSingleResult();
|
||||
|
||||
}).then(loadedPost => {
|
||||
loadedPost.metadata = undefined;
|
||||
loadedPost.metadata = null;
|
||||
return postRepository.persist(loadedPost);
|
||||
|
||||
}).then(() => {
|
||||
@ -437,6 +438,7 @@ describe("many-to-one", function() {
|
||||
|
||||
details = new PostDetails();
|
||||
details.comment = "post details comment";
|
||||
details.posts = [];
|
||||
details.posts.push(newPost);
|
||||
|
||||
return postDetailsRepository.persist(details).then(details => savedDetails = details);
|
||||
@ -474,6 +476,7 @@ describe("many-to-one", function() {
|
||||
const expectedDetails = new PostDetails();
|
||||
expectedDetails.id = savedDetails.id;
|
||||
expectedDetails.comment = savedDetails.comment;
|
||||
expectedDetails.posts = [];
|
||||
expectedDetails.posts.push(new Post());
|
||||
expectedDetails.posts[0].id = newPost.id;
|
||||
expectedDetails.posts[0].text = newPost.text;
|
||||
|
||||
@ -17,7 +17,7 @@ describe("many-to-many", function() {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const options: ConnectionOptions = {
|
||||
driver: createTestingConnectionOptions("postgres"),
|
||||
driver: createTestingConnectionOptions("mysql"),
|
||||
entities: [__dirname + "/../../sample/sample4-many-to-many/entity/*"],
|
||||
// logging: {
|
||||
// logQueries: true,
|
||||
@ -71,6 +71,7 @@ describe("many-to-many", function() {
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
newPost.details = [];
|
||||
newPost.details.push(details);
|
||||
|
||||
return postRepository.persist(newPost).then(post => savedPost = post);
|
||||
@ -113,6 +114,7 @@ describe("many-to-many", function() {
|
||||
expectedPost.id = savedPost.id;
|
||||
expectedPost.text = savedPost.text;
|
||||
expectedPost.title = savedPost.title;
|
||||
expectedPost.details = [];
|
||||
expectedPost.details.push(new PostDetails());
|
||||
expectedPost.details[0].id = savedPost.details[0].id;
|
||||
expectedPost.details[0].authorName = savedPost.details[0].authorName;
|
||||
@ -140,7 +142,8 @@ describe("many-to-many", function() {
|
||||
expectedPost.id = savedPost.id;
|
||||
expectedPost.text = savedPost.text;
|
||||
expectedPost.title = savedPost.title;
|
||||
|
||||
|
||||
expectedDetails.posts = [];
|
||||
expectedDetails.posts.push(expectedPost);
|
||||
|
||||
return postDetailsRepository
|
||||
@ -193,6 +196,7 @@ describe("many-to-many", function() {
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
newPost.categories = [];
|
||||
newPost.categories.push(category);
|
||||
|
||||
return postRepository.persist(newPost).then(post => savedPost = post);
|
||||
@ -231,6 +235,7 @@ describe("many-to-many", function() {
|
||||
expectedPost.id = savedPost.id;
|
||||
expectedPost.title = savedPost.title;
|
||||
expectedPost.text = savedPost.text;
|
||||
expectedPost.categories = [];
|
||||
expectedPost.categories.push(new PostCategory());
|
||||
expectedPost.categories[0].id = savedPost.categories[0].id;
|
||||
expectedPost.categories[0].name = savedPost.categories[0].name;
|
||||
@ -286,6 +291,7 @@ describe("many-to-many", function() {
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
newPost.details = [];
|
||||
newPost.details.push(details);
|
||||
|
||||
return postRepository
|
||||
@ -324,6 +330,7 @@ describe("many-to-many", function() {
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
newPost.details = [];
|
||||
newPost.details.push(details);
|
||||
|
||||
return postRepository
|
||||
@ -374,6 +381,7 @@ describe("many-to-many", function() {
|
||||
.persist(newImage)
|
||||
.then(image => {
|
||||
savedImage = image;
|
||||
newPost.images = [];
|
||||
newPost.images.push(image);
|
||||
return postRepository.persist(newPost);
|
||||
|
||||
@ -423,6 +431,7 @@ describe("many-to-many", function() {
|
||||
.persist(newMetadata)
|
||||
.then(metadata => {
|
||||
savedMetadata = metadata;
|
||||
newPost.metadatas = [];
|
||||
newPost.metadatas.push(metadata);
|
||||
return postRepository.persist(newPost);
|
||||
|
||||
@ -466,6 +475,7 @@ describe("many-to-many", function() {
|
||||
|
||||
details = new PostDetails();
|
||||
details.comment = "post details comment";
|
||||
details.posts = [];
|
||||
details.posts.push(newPost);
|
||||
|
||||
return postDetailsRepository.persist(details).then(details => savedDetails = details);
|
||||
@ -503,6 +513,7 @@ describe("many-to-many", function() {
|
||||
const expectedDetails = new PostDetails();
|
||||
expectedDetails.id = savedDetails.id;
|
||||
expectedDetails.comment = savedDetails.comment;
|
||||
expectedDetails.posts = [];
|
||||
expectedDetails.posts.push(new Post());
|
||||
expectedDetails.posts[0].id = newPost.id;
|
||||
expectedDetails.posts[0].text = newPost.text;
|
||||
@ -534,6 +545,7 @@ describe("many-to-many", function() {
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
newPost.details = [];
|
||||
newPost.details.push(details);
|
||||
|
||||
return postRepository
|
||||
@ -587,6 +599,7 @@ describe("many-to-many", function() {
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
newPost.categories = [];
|
||||
newPost.categories.push(category1, category2);
|
||||
|
||||
return postRepository
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user