mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
bugfixes
This commit is contained in:
parent
c7aa4468c1
commit
d8d5aaf3d4
@ -195,7 +195,7 @@ export class Gulpfile {
|
||||
*/
|
||||
@SequenceTask()
|
||||
tests() {
|
||||
return [/*"compile", */"tslint", "coveragePost", "coverageRemap"];
|
||||
return ["compile", "tslint", "coveragePost", "coverageRemap"];
|
||||
}
|
||||
|
||||
}
|
||||
20
package.json
20
package.json
@ -37,9 +37,9 @@
|
||||
"gulp-mocha": "^3.0.1",
|
||||
"gulp-replace": "^0.5.4",
|
||||
"gulp-shell": "^0.5.1",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-tslint": "^6.1.1",
|
||||
"gulp-typescript": "^2.14.0",
|
||||
"gulp-sourcemaps": "^1.9.1",
|
||||
"gulp-tslint": "^7.0.1",
|
||||
"gulp-typescript": "^3.1.3",
|
||||
"gulpclass": "0.1.1",
|
||||
"mariasql": "^0.2.6",
|
||||
"mocha": "^3.0.2",
|
||||
@ -48,30 +48,30 @@
|
||||
"mysql2": "^1.1.0",
|
||||
"oracledb": "^1.11.0",
|
||||
"pg": "^6.1.0",
|
||||
"remap-istanbul": "^0.6.4",
|
||||
"remap-istanbul": "^0.7.0",
|
||||
"sinon": "^1.17.6",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"sqlite3": "^3.1.4",
|
||||
"ts-node": "^1.3.0",
|
||||
"tslint": "^3.15.0-dev.0",
|
||||
"ts-node": "^1.7.0",
|
||||
"tslint": "^4.0.1",
|
||||
"tslint-stylish": "^2.1.0-beta",
|
||||
"typescript": "^2.1.0-dev.20160919"
|
||||
"typescript": "^2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "4.14.35",
|
||||
"@types/node": "^6.0.39",
|
||||
"app-root-path": "^2.0.1",
|
||||
"dependency-graph": "^0.5.0",
|
||||
"glob": "^7.1.0",
|
||||
"glob": "^7.1.1",
|
||||
"lodash": "^4.16.1",
|
||||
"moment": "^2.15.0",
|
||||
"moment": "~2.15.0",
|
||||
"path": "^0.12.7",
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"require-all": "^2.0.0",
|
||||
"sha1": "^1.1.1",
|
||||
"url": "^0.11.0",
|
||||
"yargonaut": "^1.1.2",
|
||||
"yargs": "^5.0.0"
|
||||
"yargs": "^6.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/gulp tests"
|
||||
|
||||
@ -18,7 +18,7 @@ export class EverythingSubscriber implements EntitySubscriberInterface<any> {
|
||||
* Called before entity insertion.
|
||||
*/
|
||||
beforeUpdate(event: UpdateEvent<any>) {
|
||||
console.log(`BEFORE ENTITY UPDATED: `, event.entity);
|
||||
console.log(`BEFORE ENTITY UPDATED: `, event.persistEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,7 +39,7 @@ export class EverythingSubscriber implements EntitySubscriberInterface<any> {
|
||||
* Called after entity insertion.
|
||||
*/
|
||||
afterUpdate(event: UpdateEvent<any>) {
|
||||
console.log(`AFTER ENTITY UPDATED: `, event.entity);
|
||||
console.log(`AFTER ENTITY UPDATED: `, event.persistEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -104,6 +104,10 @@ export class EntityMetadataValidator {
|
||||
if (relation.hasInverseSide && relation.inverseRelation.joinColumn && relation.isOneToOne)
|
||||
throw new UsingJoinColumnOnlyOnOneSideAllowedError(entityMetadata, relation);
|
||||
|
||||
// check if join column really has referenced column
|
||||
if (relation.joinColumn && !relation.joinColumn.referencedColumn)
|
||||
throw new Error(`Join column does not have referenced column set`);
|
||||
|
||||
}
|
||||
|
||||
// if its a one-to-one relation and JoinColumn is missing on both sides of the relation
|
||||
|
||||
@ -110,7 +110,7 @@ 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
|
||||
|
||||
console.log("subjects: ", this.loadedSubjects);
|
||||
// console.log("subjects: ", this.loadedSubjects);
|
||||
}
|
||||
|
||||
async remove(entity: Entity, metadata: EntityMetadata): Promise<void> {
|
||||
@ -275,16 +275,13 @@ 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): Promise<void> {
|
||||
protected async findCascadeRemovedEntitiesToLoad(subject: Subject): Promise<void> { // todo: rename to findRemovedEntities ?
|
||||
|
||||
// note: we can't use extractRelationValuesFromEntity here because it does not handle empty arrays
|
||||
const promises = subject.metadata.relations.map(async relation => {
|
||||
const valueMetadata = relation.inverseEntityMetadata;
|
||||
const qbAlias = valueMetadata.table.name;
|
||||
|
||||
// we only need relations that has cascade remove set
|
||||
if (!relation.isCascadeRemove) return;
|
||||
|
||||
// todo: added here because of type safety. theoretically it should not be empty, but what to do? throw exception if its empty?
|
||||
if (!subject.databaseEntity) return;
|
||||
|
||||
@ -293,6 +290,9 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// and understand if relation was removed or not
|
||||
if (relation.isOneToOneOwner || relation.isManyToOne) {
|
||||
|
||||
// we only work with cascade removes here
|
||||
if (!relation.isCascadeRemove) return;
|
||||
|
||||
/**
|
||||
* By example (one-to-one owner). Let's say we have a one-to-one relation between Post and Details.
|
||||
* Post contains detailsId. It means he owns relation. Post has cascade remove with details.
|
||||
@ -382,6 +382,9 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// since column value that indicates relation is stored on inverse side
|
||||
if (relation.isOneToOneNotOwner) {
|
||||
|
||||
// we only work with cascade removes here
|
||||
if (!relation.isCascadeRemove) return; // todo: no
|
||||
|
||||
/**
|
||||
* By example. Let's say we have a one-to-one relation between Post and Details.
|
||||
* Post contains detailsId. It means he owns relation. Details has cascade remove with post.
|
||||
@ -466,6 +469,9 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// since column value that indicates relation is stored on inverse side
|
||||
if (relation.isOneToMany || relation.isManyToMany) {
|
||||
|
||||
// we only work with cascade removes here
|
||||
// if (!relation.isCascadeRemove && !relation.isCascadeUpdate) return;
|
||||
|
||||
/**
|
||||
* By example. Let's say we have a one-to-many relation between Post and Details.
|
||||
* Post contains detailsId. It means he owns relation.
|
||||
@ -484,8 +490,6 @@ 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);
|
||||
|
||||
@ -507,24 +511,41 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
let databaseEntities: ObjectLiteral[] = [];
|
||||
|
||||
if (relation.isManyToManyOwner) {
|
||||
|
||||
// we only need to load inverse entities if cascade removes are set
|
||||
// because remove by cascades is the only reason we need relational entities here
|
||||
if (!relation.isCascadeRemove) return;
|
||||
|
||||
databaseEntities = await this.connection
|
||||
.getRepository<ObjectLiteral>(valueMetadata.target)
|
||||
.createQueryBuilder(qbAlias)
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, "persistenceJoinedRelation", "ON", "persistenceJoinedRelation." + relation.joinTable.joinColumnName + "=:id") // todo: need to escape alias and propertyName?
|
||||
.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();
|
||||
|
||||
} else if (relation.isManyToManyNotOwner) {
|
||||
|
||||
// we only need to load inverse entities if cascade removes are set
|
||||
// because remove by cascades is the only reason we need relational entities here
|
||||
if (!relation.isCascadeRemove) return;
|
||||
|
||||
databaseEntities = await this.connection
|
||||
.getRepository<ObjectLiteral>(valueMetadata.target)
|
||||
.createQueryBuilder(qbAlias)
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, "persistenceJoinedRelation", "ON", "persistenceJoinedRelation." + relation.inverseRelation.joinTable.inverseJoinColumnName + "=:id") // todo: need to escape alias and propertyName?
|
||||
.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();
|
||||
|
||||
} else {
|
||||
} else { // this case can only be a oneToMany relation
|
||||
|
||||
// in this case we need inverse entities not only because of cascade removes
|
||||
// because we also need inverse entities to be able to perform update of entities
|
||||
// in the inverse side when entities is detached from one-to-many relation
|
||||
|
||||
databaseEntities = await this.connection
|
||||
.getRepository<ObjectLiteral>(valueMetadata.target)
|
||||
.createQueryBuilder(qbAlias)
|
||||
@ -546,29 +567,46 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// iterate throw loaded inverse entities to find out removed entities and inverse updated entities (only for one-to-many relation)
|
||||
const promises = databaseEntities.map(async databaseEntity => {
|
||||
|
||||
// find a subject object of the related database entity
|
||||
const relatedEntitySubject = this.loadedSubjects.findByDatabaseEntityLike(valueMetadata.target, databaseEntity);
|
||||
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 we remove this check it will cause a recursion
|
||||
if (relatedEntitySubject.mustBeRemoved) return; // todo: add another check for entity in unsetRelations?
|
||||
|
||||
if (persistValue === null) {
|
||||
relatedEntitySubject.mustBeRemoved = true;
|
||||
await this.findCascadeRemovedEntitiesToLoad(relatedEntitySubject);
|
||||
return;
|
||||
}
|
||||
|
||||
const relatedValue = (persistValue as ObjectLiteral[]).find(persistedRelatedValue => {
|
||||
// check if in persisted value there is a database value to understand if it was removed or not
|
||||
let relatedValue = ((persistValue || []) as ObjectLiteral[]).find(persistedRelatedValue => {
|
||||
const relatedId = relation.getInverseEntityRelationId(persistedRelatedValue);
|
||||
return relatedId === relation.getInverseEntityRelationId(relatedEntitySubject!.databaseEntity!);
|
||||
});
|
||||
|
||||
if (!relatedValue) {
|
||||
relatedEntitySubject.mustBeRemoved = true;
|
||||
await this.findCascadeRemovedEntitiesToLoad(relatedEntitySubject);
|
||||
// if relation value is set to undefined then we don't do anything - simply skip any check and remove
|
||||
// but if relation value is set to null then it means user wants to remove each entity in this relation
|
||||
// OR
|
||||
// value was removed from persisted value - means we need to mark it as removed
|
||||
// and check if mark as removed all underlying entities that has cascade remove
|
||||
if (persistValue === null || !relatedValue) {
|
||||
|
||||
// if cascade remove option is set then need to remove related entity
|
||||
if (relation.isCascadeRemove) {
|
||||
relatedEntitySubject.mustBeRemoved = true;
|
||||
|
||||
// mark as removed all underlying entities that has cascade remove
|
||||
await this.findCascadeRemovedEntitiesToLoad(relatedEntitySubject);
|
||||
return;
|
||||
|
||||
// if cascade remove option is not set then it means we simply need to remove
|
||||
// reference to this entity from inverse side (from loaded database entity)
|
||||
// this applies only on one-to-many relationship
|
||||
} else if (relation.isOneToMany && relation.inverseRelation) {
|
||||
relatedEntitySubject.unsetRelations.push(relation.inverseRelation);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ export class PersistOperationExecutor {
|
||||
// persistOperation.log();
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => this.broadcastBeforeEvents(persistOperation))
|
||||
// .then(() => this.broadcastBeforeEvents(persistOperation))
|
||||
.then(() => {
|
||||
if (!this.queryRunner.isTransactionActive()) {
|
||||
isTransactionStartedByItself = true;
|
||||
@ -70,7 +70,7 @@ export class PersistOperationExecutor {
|
||||
.then(() => this.updateIdsOfInsertedEntities(persistOperation))
|
||||
.then(() => this.updateIdsOfRemovedEntities(persistOperation))
|
||||
.then(() => this.updateSpecialColumnsInEntities(persistOperation))
|
||||
.then(() => this.broadcastAfterEvents(persistOperation))
|
||||
// .then(() => this.broadcastAfterEvents(persistOperation))
|
||||
.catch(error => {
|
||||
if (isTransactionStartedByItself === true) {
|
||||
return this.queryRunner.rollbackTransaction()
|
||||
@ -91,7 +91,7 @@ export class PersistOperationExecutor {
|
||||
|
||||
/**
|
||||
* Broadcast all before persistment events - beforeInsert, beforeUpdate and beforeRemove events.
|
||||
*/
|
||||
|
||||
private broadcastBeforeEvents(persistOperation: PersistOperation) {
|
||||
|
||||
const insertEvents = persistOperation.inserts.map(insertOperation => {
|
||||
@ -117,11 +117,11 @@ export class PersistOperationExecutor {
|
||||
return Promise.all(insertEvents)
|
||||
.then(() => Promise.all(updateEvents))
|
||||
.then(() => Promise.all(removeEvents)); // todo: do we really should send it in order?
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Broadcast all after persistment events - afterInsert, afterUpdate and afterRemove events.
|
||||
*/
|
||||
|
||||
private broadcastAfterEvents(persistOperation: PersistOperation) {
|
||||
|
||||
const insertEvents = persistOperation.inserts.map(insertOperation => {
|
||||
@ -147,7 +147,7 @@ export class PersistOperationExecutor {
|
||||
return Promise.all(insertEvents)
|
||||
.then(() => Promise.all(updateEvents))
|
||||
.then(() => Promise.all(removeEvents)); // todo: do we really should send it in order?
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Executes insert operations.
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import {PersistOperation} from "./operation/PersistOperation";
|
||||
import {InsertOperation} from "./operation/InsertOperation";
|
||||
import {UpdateByRelationOperation} from "./operation/UpdateByRelationOperation";
|
||||
import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOperation";
|
||||
@ -37,6 +36,7 @@ export class PersistSubjectExecutor {
|
||||
const insertSubjects = subjects.filter(subject => subject.mustBeInserted);
|
||||
const updateSubjects = subjects.filter(subject => subject.mustBeUpdated);
|
||||
const removeSubjects = subjects.filter(subject => subject.mustBeRemoved);
|
||||
const unsetRelationSubjects = subjects.filter(subject => subject.hasUnsetRelations);
|
||||
|
||||
// validation
|
||||
// check if remove subject also must be inserted or updated - then we throw an exception
|
||||
@ -70,6 +70,7 @@ export class PersistSubjectExecutor {
|
||||
// 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.executeUnsetRelationOperations(unsetRelationSubjects);
|
||||
await this.executeRemoveOperations(removeSubjects);
|
||||
|
||||
// commit transaction if it was started by us
|
||||
@ -141,15 +142,17 @@ export class PersistSubjectExecutor {
|
||||
subject.metadata.relationsWithJoinColumns.forEach(relation => {
|
||||
const referencedColumn = relation.joinColumn.referencedColumn;
|
||||
|
||||
insertSubjects.forEach(insertedSubject => {
|
||||
if (subject.entity[relation.propertyName] === insertedSubject.entity) {
|
||||
|
||||
if (referencedColumn.isGenerated)
|
||||
updateOptions[relation.name] = insertedSubject.newlyGeneratedId;
|
||||
|
||||
// todo: implement other special referenced column types (update date, create date, version, discriminator column, etc.)
|
||||
if (subject.entity[relation.propertyName]) {
|
||||
const relationId = subject.entity[relation.propertyName][referencedColumn.propertyName];
|
||||
if (relationId) {
|
||||
updateOptions[relation.name] = relationId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const insertSubject = insertSubjects.find(insertedSubject => subject.entity[relation.propertyName] === insertedSubject.entity);
|
||||
if (insertSubject && referencedColumn.isGenerated)
|
||||
updateOptions[relation.name] = insertSubject.newlyGeneratedId;
|
||||
|
||||
});
|
||||
if (Object.keys(updateOptions).length > 0) {
|
||||
const conditions = subject.metadata.getDatabaseEntityIdMap(subject.entity) || subject.metadata.createSimpleDatabaseIdMap(subject.newlyGeneratedId);
|
||||
@ -164,19 +167,22 @@ export class PersistSubjectExecutor {
|
||||
|
||||
if (value instanceof Array) {
|
||||
value.forEach(subValue => {
|
||||
insertSubjects.forEach(insertedSubject => {
|
||||
if (subValue === insertedSubject.entity) {
|
||||
|
||||
if (referencedColumn.isGenerated) {
|
||||
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);
|
||||
}
|
||||
let relationId = subValue[referencedColumn.propertyName];
|
||||
if (!relationId) {
|
||||
const insertSubject = insertSubjects.find(insertedSubject => subValue === insertedSubject.entity);
|
||||
|
||||
// todo: implement other special referenced column types (update date, create date, version, discriminator column, etc.)
|
||||
}
|
||||
});
|
||||
if (insertSubject && referencedColumn.isGenerated)
|
||||
relationId = insertSubject.newlyGeneratedId;
|
||||
|
||||
// todo: implement other special referenced column types (update date, create date, version, discriminator column, etc.)
|
||||
}
|
||||
|
||||
const id = subject.entity[referencedColumn.propertyName] || subject.newlyGeneratedId;
|
||||
const conditions = relation.inverseEntityMetadata.getDatabaseEntityIdMap(subValue) || relation.inverseEntityMetadata.createSimpleDatabaseIdMap(relationId);
|
||||
const updateOptions = { [relation.inverseRelation.joinColumn.name]: id }; // todo: what if subject's id is not generated?
|
||||
const updatePromise = this.queryRunner.update(relation.inverseEntityMetadata.table.name, updateOptions, conditions);
|
||||
updatePromises.push(updatePromise);
|
||||
|
||||
});
|
||||
}
|
||||
@ -248,22 +254,38 @@ export class PersistSubjectExecutor {
|
||||
|
||||
/**
|
||||
* Executes update relations operations.
|
||||
*/
|
||||
|
||||
private executeUpdateRelationsOperations(updateSubjects: Subject[], insertSubjects: Subject[]) {
|
||||
return Promise.all(updateSubjects.map(subject => {
|
||||
return this.updateByRelation(subject, insertSubjects);
|
||||
}));
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Executes update relations operations.
|
||||
*/
|
||||
private executeUpdateInverseRelationsOperations(persistOperation: PersistOperation) {
|
||||
return Promise.all(persistOperation.updatesByInverseRelations.map(updateInverseOperation => {
|
||||
return this.updateInverseRelation(updateInverseOperation, persistOperation.inserts);
|
||||
private executeUnsetRelationOperations(subjects: Subject[]) {
|
||||
return Promise.all(subjects.map(subject => {
|
||||
return this.unsetRelations(subject);
|
||||
}));
|
||||
}
|
||||
|
||||
private async unsetRelations(subject: Subject) {
|
||||
const values: ObjectLiteral = {};
|
||||
subject.unsetRelations.forEach(relation => {
|
||||
values[relation.name] = null;
|
||||
});
|
||||
|
||||
if (!subject.databaseEntity)
|
||||
throw new Error(`Internal error. Cannot unset relation of subject that does not have database entity.`);
|
||||
|
||||
const idMap = subject.metadata.getDatabaseEntityIdMap(subject.databaseEntity);
|
||||
if (!idMap)
|
||||
throw new Error(`Internal error. Cannot get id of the updating entity.`);
|
||||
|
||||
return this.queryRunner.update(subject.metadata.table.name, values, idMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes update operations.
|
||||
*/
|
||||
@ -273,7 +295,7 @@ export class PersistSubjectExecutor {
|
||||
|
||||
/**
|
||||
* Executes remove relations operations.
|
||||
*/
|
||||
|
||||
private executeRemoveRelationOperations(removeSubjects: Subject[]) {
|
||||
return Promise.all(removeSubjects
|
||||
// .filter(operation => {
|
||||
@ -281,7 +303,7 @@ export class PersistSubjectExecutor {
|
||||
// })
|
||||
.map(subject => this.updateDeletedRelations(subject))
|
||||
);
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Executes remove operations.
|
||||
@ -509,7 +531,7 @@ export class PersistSubjectExecutor {
|
||||
valueMaps.push(valueMap);
|
||||
}
|
||||
|
||||
valueMap.values[subject.metadata.versionColumn.name] = this.connection.driver.preparePersistentValue(entity[subject.metadata.versionColumn.propertyName] + 1, metadata.versionColumn);
|
||||
valueMap.values[subject.metadata.versionColumn.name] = this.connection.driver.preparePersistentValue(entity[subject.metadata.versionColumn.propertyName] + 1, subject.metadata.versionColumn);
|
||||
}
|
||||
|
||||
if (subject.metadata.parentEntityMetadata) {
|
||||
@ -535,22 +557,15 @@ export class PersistSubjectExecutor {
|
||||
}
|
||||
|
||||
await Promise.all(valueMaps.map(valueMap => {
|
||||
const conditions: ObjectLiteral = {};
|
||||
if (valueMap.metadata.parentEntityMetadata && valueMap.metadata.parentEntityMetadata.inheritanceType === "class-table") { // valueMap.metadata.getEntityIdMap(entity)!
|
||||
valueMap.metadata.parentIdColumns.forEach(column => {
|
||||
conditions[column.name] = entity[column.propertyName];
|
||||
});
|
||||
const idMap = valueMap.metadata.getDatabaseEntityIdMap(entity);
|
||||
if (!idMap)
|
||||
throw new Error(`Internal error. Cannot get id of the updating entity.`);
|
||||
|
||||
} else {
|
||||
valueMap.metadata.primaryColumns.forEach(column => {
|
||||
conditions[column.name] = entity[column.propertyName];
|
||||
});
|
||||
}
|
||||
return this.queryRunner.update(valueMap.tableName, valueMap.values, conditions);
|
||||
return this.queryRunner.update(valueMap.tableName, valueMap.values, idMap);
|
||||
}));
|
||||
}
|
||||
|
||||
private updateDeletedRelations(subject: Subject) { // todo: check if both many-to-one deletions work too
|
||||
/*private updateDeletedRelations(subject: Subject) { // todo: check if both many-to-one deletions work too
|
||||
if (!subject.fromEntityId)
|
||||
throw new Error(`remove operation does not have entity id`);
|
||||
|
||||
@ -563,7 +578,7 @@ export class PersistSubjectExecutor {
|
||||
}
|
||||
|
||||
throw new Error("Remove operation relation is not set"); // todo: find out how its possible
|
||||
}
|
||||
}*/
|
||||
|
||||
private async remove(subject: Subject): Promise<void> {
|
||||
if (subject.metadata.parentEntityMetadata) {
|
||||
@ -734,7 +749,7 @@ export class PersistSubjectExecutor {
|
||||
if (!parentEntityId && referencedColumn.isGenerated) {
|
||||
const parentInsertedSubject = insertedSubjects.find(subject => subject.entity === parentEntity);
|
||||
// todo: throw exception if parentInsertedSubject is not set
|
||||
parentEntityId = parentInsertedSubject.newlyGeneratedId;
|
||||
parentEntityId = parentInsertedSubject!.newlyGeneratedId;
|
||||
} // todo: implement other special column types too
|
||||
|
||||
subject.treeLevel = await this.queryRunner.insertIntoClosureTable(tableName, newEntityId, parentEntityId, subject.metadata.hasTreeLevelColumn);
|
||||
|
||||
@ -19,6 +19,12 @@ export class Subject { // todo: move entity with id creation into metadata? // t
|
||||
canBeUpdated: boolean = false;
|
||||
mustBeRemoved: boolean = false;
|
||||
|
||||
/**
|
||||
* List of relations which need to be unset.
|
||||
* This is used to update relation from inverse side.
|
||||
*/
|
||||
unsetRelations: RelationMetadata[] = [];
|
||||
|
||||
diffColumns: ColumnMetadata[] = [];
|
||||
diffRelations: RelationMetadata[] = [];
|
||||
|
||||
@ -96,6 +102,10 @@ export class Subject { // todo: move entity with id creation into metadata? // t
|
||||
return this.canBeUpdated && (this.diffColumns.length > 0 || this.diffRelations.length > 0);
|
||||
}
|
||||
|
||||
get hasUnsetRelations(): boolean {
|
||||
return this.unsetRelations.length > 0;
|
||||
}
|
||||
|
||||
get databaseEntity(): ObjectLiteral|undefined {
|
||||
return this._databaseEntity;
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import {Connection} from "../../../../src/connection/Connection";
|
||||
import {Post} from "./entity/Post";
|
||||
import {Category} from "./entity/Category";
|
||||
|
||||
describe.only("persistence > cascade operations with custom name", () => {
|
||||
describe("persistence > cascade operations with custom name", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await setupTestingConnections({
|
||||
|
||||
@ -13,7 +13,7 @@ export class User {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@ManyToOne(type => Post)
|
||||
@ManyToOne(type => Post, { cascadeUpdate: true })
|
||||
post: Post;
|
||||
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import {Table} from "../../../../../src/decorator/tables/Table";
|
||||
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {Post} from "./Post";
|
||||
import {OneToMany} from "../../../../../src/decorator/relations/OneToMany";
|
||||
|
||||
|
||||
@Table()
|
||||
export class Category {
|
||||
|
||||
@PrimaryColumn("int")
|
||||
categoryId: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@OneToMany(type => Post, post => post.category)
|
||||
posts: Post[];
|
||||
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
import {Table} from "../../../../../src/decorator/tables/Table";
|
||||
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
|
||||
import {Category} from "./Category";
|
||||
|
||||
@Table()
|
||||
export class Post {
|
||||
|
||||
@PrimaryColumn("int")
|
||||
firstId: number;
|
||||
|
||||
@PrimaryColumn("int")
|
||||
secondId: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@ManyToOne(type => Category, category => category.posts)
|
||||
category: Category;
|
||||
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
import "reflect-metadata";
|
||||
import {setupTestingConnections, closeConnections, reloadDatabases} from "../../../utils/test-utils";
|
||||
import {Connection} from "../../../../src/connection/Connection";
|
||||
import {Post} from "./entity/Post";
|
||||
import {Category} from "./entity/Category";
|
||||
|
||||
describe("persistence > multi primary keys", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await setupTestingConnections({
|
||||
entities: [__dirname + "/entity/*{.js,.ts}"],
|
||||
schemaCreate: true,
|
||||
dropSchemaOnConnection: true
|
||||
}));
|
||||
beforeEach(() => reloadDatabases(connections));
|
||||
after(() => closeConnections(connections));
|
||||
|
||||
describe("insert", function () {
|
||||
|
||||
it("should insert entity when when there are multi column primary keys", () => Promise.all(connections.map(async connection => {
|
||||
const post1 = new Post();
|
||||
post1.title = "Hello Post #1";
|
||||
post1.firstId = 1;
|
||||
post1.secondId = 2;
|
||||
|
||||
await connection.entityManager.persist(post1);
|
||||
|
||||
// create first category and post and save them
|
||||
const category1 = new Category();
|
||||
category1.categoryId = 123;
|
||||
category1.name = "Category saved by cascades #1";
|
||||
category1.posts = [post1];
|
||||
|
||||
await connection.entityManager.persist(category1);
|
||||
|
||||
// now check
|
||||
const posts = await connection.entityManager.find(Post, {
|
||||
alias: "post",
|
||||
innerJoinAndSelect: {
|
||||
category: "post.category"
|
||||
},
|
||||
orderBy: {
|
||||
"post.firstId": "ASC"
|
||||
}
|
||||
});
|
||||
|
||||
posts.should.be.eql([{
|
||||
firstId: 1,
|
||||
secondId: 2,
|
||||
title: "Hello Post #1",
|
||||
category: {
|
||||
categoryId: 123,
|
||||
name: "Category saved by cascades #1"
|
||||
}
|
||||
}]);
|
||||
})));
|
||||
});
|
||||
});
|
||||
@ -4,7 +4,7 @@ import {Connection} from "../../../../src/connection/Connection";
|
||||
import {Post} from "./entity/Post";
|
||||
import {Category} from "./entity/Category";
|
||||
|
||||
describe("persistence > mutli primary keys", () => {
|
||||
describe("persistence > multi primary keys", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await setupTestingConnections({
|
||||
@ -15,9 +15,9 @@ describe("persistence > mutli primary keys", () => {
|
||||
beforeEach(() => reloadDatabases(connections));
|
||||
after(() => closeConnections(connections));
|
||||
|
||||
describe("insertt", function () {
|
||||
describe("insert", function () {
|
||||
|
||||
it("should insert entity when when there are mutli column primary keys", () => Promise.all(connections.map(async connection => {
|
||||
it("should insert entity when when there are multi column primary keys", () => Promise.all(connections.map(async connection => {
|
||||
const post1 = new Post();
|
||||
post1.title = "Hello Post #1";
|
||||
post1.firstId = 1;
|
||||
|
||||
@ -356,7 +356,6 @@ describe("one-to-one", function() {
|
||||
.getSingleResult();
|
||||
|
||||
}).then(loadedPost => {
|
||||
console.log("loadedPost: ", loadedPost);
|
||||
loadedPost.image.url = "new-logo.png";
|
||||
return postRepository.persist(loadedPost);
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ describe("many-to-one", function() {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const options: ConnectionOptions = {
|
||||
driver: createTestingConnectionOptions("postgres"),
|
||||
driver: createTestingConnectionOptions("mysql"),
|
||||
entities: [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor]
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user