UNPKG

typeorm

Version:

Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL, MongoDB databases.

650 lines (649 loc) • 61.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [0, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var Subject_1 = require("./Subject"); var MongoDriver_1 = require("../driver/mongodb/MongoDriver"); var OrmUtils_1 = require("../util/OrmUtils"); /** * To be able to execute persistence operations we need to load all entities from the database we need. * Loading should be efficient - we need to load entities in as few queries as possible + load as less data as we can. * This is how we determine which entities needs to be loaded from db: * * 1. example with cascade updates and inserts: * * [Y] - means "yes, we load" * [N] - means "no, we don't load" * in {} braces we specify what cascade options are set between relations * * if Post is new, author is not set in the post * * [Y] Post -> {all} // yes because of "update" and "insert" cascades, no because of "remove" * [Y] Author -> {all} // no because author is not set * [Y] Photo -> {all} // no because author and its photo are not set * [Y] Tag -> {all} // no because author and its photo and its tag are not set * * if Post is new, author is new (or anything else is new) * if Post is updated * if Post and/or Author are updated * * [Y] Post -> {all} // yes because of "update" and "insert" cascades, no because of "remove" * [Y] Author -> {all} // yes because of "update" and "insert" cascades, no because of "remove" * [Y] Photo -> {all} // yes because of "update" and "insert" cascades, no because of "remove" * [Y] Tag -> {all} // yes because of "update" and "insert" cascades, no because of "remove" * * Here we load post, author, photo, tag to check if they are new or not to persist insert or update operation. * We load post, author, photo, tag only if they exist in the relation. * From these examples we can see that we always load entity relations when it has "update" or "insert" cascades. * * 2. example with cascade removes * * if entity is new its remove operations by cascades should not be executed * if entity is updated then values that are null or missing in array (not undefined!, undefined means skip - don't do anything) are treated as removed * if entity is removed then all its downside relations which has cascade remove should be removed * * Once we find removed entity - we load it, and every downside entity which has "remove" cascade set. * * At the end we have all entities we need to operate with. * Next step is to store all loaded entities to manipulate them efficiently. * * Rules of updating by cascades. * Insert operation can lead to: * - insert operations * - update operations * Update operation can lead to: * - insert operations * - update operations * - remove operations * Remove operation can lead to: * - remove operation */ var SubjectBuilder = /** @class */ (function () { // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- function SubjectBuilder(connection, queryRunner) { this.connection = connection; this.queryRunner = queryRunner; // ------------------------------------------------------------------------- // Protected properties // ------------------------------------------------------------------------- /** * If this gonna be reused then what to do with marked flags? * One of solution can be clone this object and reset all marked states for this persistence. * Or from reused just extract databaseEntities from their subjects? (looks better) */ this.operateSubjects = []; } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Builds operations for entity that is being inserted/updated. */ SubjectBuilder.prototype.persist = function (entity, metadata) { return __awaiter(this, void 0, void 0, function () { var _this = this; var mainSubject, operateSubjectsWithDatabaseEntities; return __generator(this, function (_a) { switch (_a.label) { case 0: mainSubject = new Subject_1.Subject(metadata, entity); mainSubject.canBeInserted = true; mainSubject.canBeUpdated = true; this.operateSubjects.push(mainSubject); // next step we build list of subjects we will operate with // these subjects are subjects that we need to insert or update alongside with main persisted entity this.buildCascadeUpdateAndInsertOperateSubjects(mainSubject); // next step is to load database entities of all operate subjects return [4 /*yield*/, this.loadOperateSubjectsDatabaseEntities()]; case 1: // next step is to load database entities of all operate subjects _a.sent(); operateSubjectsWithDatabaseEntities = this.operateSubjects.filter(function (subject) { return subject.hasDatabaseEntity; }); return [4 /*yield*/, Promise.all(operateSubjectsWithDatabaseEntities.map(function (subject) { return _this.buildCascadeRemovedAndRelationUpdateOperateSubjects(subject); }))]; case 2: _a.sent(); // finally find which operate subjects have insert and remove operations in their junction tables return [4 /*yield*/, this.buildJunctionOperations({ insert: true, remove: true })]; case 3: // finally find which operate subjects have insert and remove operations in their junction tables _a.sent(); return [2 /*return*/]; } }); }); }; /** * Builds only remove operations for entity that is being removed. */ SubjectBuilder.prototype.remove = function (entity, metadata) { return __awaiter(this, void 0, void 0, function () { var _this = this; var mainSubject, operateSubjectsWithDatabaseEntities; return __generator(this, function (_a) { switch (_a.label) { case 0: mainSubject = new Subject_1.Subject(metadata, entity); mainSubject.mustBeRemoved = true; this.operateSubjects.push(mainSubject); // next step we build list of subjects we will operate with // these subjects are subjects that we need to remove alongside with main removed entity this.buildCascadeRemoveOperateSubjects(mainSubject); // next step is to load database entities for all operate subjects return [4 /*yield*/, this.loadOperateSubjectsDatabaseEntities()]; case 1: // next step is to load database entities for all operate subjects _a.sent(); operateSubjectsWithDatabaseEntities = this.operateSubjects.filter(function (subject) { return subject.hasDatabaseEntity; }); return [4 /*yield*/, Promise.all(operateSubjectsWithDatabaseEntities.map(function (subject) { return _this.buildCascadeRemovedAndRelationUpdateOperateSubjects(subject); }))]; case 2: _a.sent(); // finally find which operate subjects have remove operations in their junction tables return [4 /*yield*/, this.buildJunctionOperations({ insert: false, remove: true })]; case 3: // finally find which operate subjects have remove operations in their junction tables _a.sent(); return [2 /*return*/]; } }); }); }; // ------------------------------------------------------------------------- // Protected Methods // ------------------------------------------------------------------------- /** * Builds and pushes to array of operate entities all entities that we will work with. * These are only relational entities which has insert and update cascades. * All such entities will be loaded from the database, because they can be inserted or updated. * That's why we load them - to understand if they should be inserted or updated, or which columns we need to update. * We can't add removed entities here, because to know which entity was removed we need first to * load original entity (particularly its id) from the database. * That's why we first need to load all changed entities, then extract ids of the removed entities from them, * and only then load removed entities by extracted ids. */ SubjectBuilder.prototype.buildCascadeUpdateAndInsertOperateSubjects = function (subject) { var _this = this; subject.metadata .extractRelationValuesFromEntity(subject.entity, subject.metadata.relations) .filter(function (_a) { var relation = _a[0], relationEntity = _a[1], relationEntityMetadata = _a[2]; // we need only defined values and insert or update cascades of the relation should be set return relationEntity !== undefined && relationEntity !== null && (relation.isCascadeInsert || relation.isCascadeUpdate); }) .forEach(function (_a) { var relation = _a[0], relationEntity = _a[1], relationEntityMetadata = _a[2]; // if we already has this entity in list of operated subjects then skip it to avoid recursion var alreadyExistRelationEntitySubject = _this.findByEntityLike(relationEntityMetadata.target, relationEntity); if (alreadyExistRelationEntitySubject) { if (alreadyExistRelationEntitySubject.canBeInserted === false) alreadyExistRelationEntitySubject.canBeInserted = relation.isCascadeInsert === true; if (alreadyExistRelationEntitySubject.canBeUpdated === false) alreadyExistRelationEntitySubject.canBeUpdated = relation.isCascadeUpdate === true; return; } // mark subject with what we can do with it // and add to the array of subjects to load only if there is no same entity there already var relationEntitySubject = new Subject_1.Subject(relationEntityMetadata, relationEntity); relationEntitySubject.canBeInserted = relation.isCascadeInsert === true; relationEntitySubject.canBeUpdated = relation.isCascadeUpdate === true; _this.operateSubjects.push(relationEntitySubject); // go recursively and find other entities we need to insert/update _this.buildCascadeUpdateAndInsertOperateSubjects(relationEntitySubject); }); }; /** * Builds and pushes to array of operate entities all entities that must be removed. */ SubjectBuilder.prototype.buildCascadeRemoveOperateSubjects = function (subject) { var _this = this; subject.metadata .extractRelationValuesFromEntity(subject.entity, subject.metadata.relations) .filter(function (_a) { var relation = _a[0], relationEntity = _a[1], relationEntityMetadata = _a[2]; // we need only defined values and insert cascades of the relation should be set return relationEntity !== undefined && relationEntity !== null && relation.isCascadeRemove; }) .forEach(function (_a) { var relation = _a[0], relationEntity = _a[1], relationEntityMetadata = _a[2]; // if we already has this entity in list of operated subjects then skip it to avoid recursion var alreadyExistValueSubject = _this.findByEntityLike(relationEntityMetadata.target, relationEntity); if (alreadyExistValueSubject) { alreadyExistValueSubject.mustBeRemoved = true; return; } // add to the array of subjects to load only if there is no same entity there already var valueSubject = new Subject_1.Subject(relationEntityMetadata, relationEntity); valueSubject.mustBeRemoved = true; _this.operateSubjects.push(valueSubject); // go recursively and find other entities we need to remove _this.buildCascadeRemoveOperateSubjects(valueSubject); }); }; /** * Loads database entities for all operate subjects which do not have database entity set. * All entities that we load database entities for are marked as updated or inserted. * To understand which of them really needs to be inserted or updated we need to load * their original representations from the database. */ SubjectBuilder.prototype.loadOperateSubjectsDatabaseEntities = function () { return __awaiter(this, void 0, void 0, function () { var _this = this; var promises; return __generator(this, function (_a) { switch (_a.label) { case 0: promises = this.groupByEntityTargets().map(function (subjectGroup) { return __awaiter(_this, void 0, void 0, function () { var _this = this; var allIds, entities; return __generator(this, function (_a) { switch (_a.label) { case 0: allIds = subjectGroup.subjects .filter(function (subject) { return !subject.hasDatabaseEntity; }) // we don't load if subject already has a database entity loaded .filter(function (subject) { return !subject.metadata.isEntityMapEmpty(subject.entity); }) // we only need entity id .map(function (subject) { // console.log(subject.entity); return subject.metadata.getEntityIdMap(subject.entity); // if (mixedId instanceof Object) // return Object.keys(mixedId).every(key => mixedId[key] !== undefined && mixedId[key] !== null && mixedId[key] !== ""); // // return mixedId !== undefined && mixedId !== null && mixedId !== ""; }); // if there no ids found (which means all entities are new and have generated ids) - then nothing to load there // console.log("allIds: ", allIds); // console.log("subject.entity: ", subjectGroup.subjects); // console.log("allIds: ", allIds); if (!allIds.length) return [2 /*return*/]; if (!(this.connection.driver instanceof MongoDriver_1.MongoDriver)) return [3 /*break*/, 2]; return [4 /*yield*/, this.connection .getMongoRepository(subjectGroup.target) .findByIds(allIds)]; case 1: entities = _a.sent(); return [3 /*break*/, 4]; case 2: return [4 /*yield*/, this.connection .getRepository(subjectGroup.target) .createQueryBuilder("subject", this.queryRunner) .whereInIds(allIds) .loadAllRelationIds() .getMany()]; case 3: entities = _a.sent(); _a.label = 4; case 4: // now when we have entities we need to find subject of each entity // and insert that entity into database entity of the found subject entities.forEach(function (entity) { // console.log(1); var subject = _this.findByEntityLike(subjectGroup.target, entity); if (subject) subject.databaseEntity = entity; }); return [2 /*return*/]; } }); }); }); return [4 /*yield*/, Promise.all(promises)]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; /** * We need to load removed entity when: * - entity with relations is not new (this can be determined only after entity is loaded from db) * (note: simple "id" check will not work because id can be not generated) * - entity missing relation. When relation is simple * - in the case of one-to-one owner (with join column) relation we need to load owner entity * - in the case of one-to-one (without join column) relation we need to load inverse side entity * - in the case of many-to-one relations we need to load entity itself * - in the case of one-to-many relations we need to load entities by relation from inverse side * * Before loading each entity we need to check in the loaded subjects - maybe it was already loaded. * * 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). */ SubjectBuilder.prototype.buildCascadeRemovedAndRelationUpdateOperateSubjects = function (subject) { return __awaiter(this, void 0, void 0, function () { var _this = this; var promises; return __generator(this, function (_a) { switch (_a.label) { case 0: promises = subject.metadata.relations.map(function (relation) { return __awaiter(_this, void 0, void 0, function () { var _this = this; var valueMetadata, qbAlias, relationIdInDatabaseEntity_1, persistValueRelationId, persistValue_1, alreadyLoadedRelatedDatabaseSubject, qb, condition, parameters, databaseEntity, persistValueRelationId, persistValue, relationIdInDatabaseEntity_2, alreadyLoadedRelatedDatabaseSubject, databaseEntity, inverseEntityRelationId, persistValue_2, databaseEntities_1, escape_1, joinAlias_1, joinColumnConditions, inverseJoinColumnConditions, conditions, parameters, joinAlias_2, joinColumnConditions, inverseJoinColumnConditions, conditions, parameters, relationIdInDatabaseEntity, promises_1, promises_2; return __generator(this, function (_a) { switch (_a.label) { case 0: valueMetadata = relation.inverseEntityMetadata; qbAlias = valueMetadata.tableName; // added for type-safety, but subject without databaseEntity cant come here anyway because of checks on upper levels if (!subject.hasDatabaseEntity) return [2 /*return*/]; if (!(relation.isOneToOneOwner || relation.isManyToOne)) return [3 /*break*/, 4]; // we only work with cascade removes here if (!relation.isCascadeRemove) return [2 /*return*/]; relationIdInDatabaseEntity_1 = relation.getEntityValue(subject.databaseEntity); // if database relation id does not exist in the database object then nothing to remove if (relationIdInDatabaseEntity_1 === null || relationIdInDatabaseEntity_1 === undefined) return [2 /*return*/]; persistValueRelationId = undefined, persistValue_1 = undefined; if (subject.hasEntity) { persistValue_1 = relation.getEntityValue(subject.entity); if (persistValue_1 === null) persistValueRelationId = null; if (persistValue_1) persistValueRelationId = relation.joinColumns.reduce(function (map, column) { return column.referencedColumn.getEntityValueMap(persistValue_1); }, {}); if (persistValueRelationId === undefined) return [2 /*return*/]; // skip undefined properties } // object is removed only if relation id in the persisted entity is empty or is changed // if (persistValueRelationId !== null && persistValueRelationId === relationIdInDatabaseEntity) // return; // console.log("relationIdInDatabaseEntity:", relationIdInDatabaseEntity); // console.log("persistValue:", persistValue); // console.log("compareEntities:", relation.entityMetadata.compareEntities(relationIdInDatabaseEntity, persistValue)); // console.log("compareIds:", relation.entityMetadata.compareIds(relationIdInDatabaseEntity, persistValue)); if (persistValueRelationId !== null && relation.entityMetadata.compareIds(relationIdInDatabaseEntity_1, persistValue_1)) return [2 /*return*/]; alreadyLoadedRelatedDatabaseSubject = this.operateSubjects.find(function (relatedSubject) { // (example) filter only subject that has database entity loaded and its target is Details if (!relatedSubject.hasDatabaseEntity || relatedSubject.entityTarget !== valueMetadata.target) return false; // (example) here we seek a Details loaded from the database in the subjects // (example) here relatedSubject.databaseEntity is a Details // (example) and we need to compare details.id === post.detailsId return relation.entityMetadata.compareIds(relationIdInDatabaseEntity_1, relation.getEntityValue(relatedSubject.databaseEntity)); }); if (!!alreadyLoadedRelatedDatabaseSubject) return [3 /*break*/, 2]; qb = this.connection .getRepository(valueMetadata.target) .createQueryBuilder(qbAlias, this.queryRunner) // todo: this wont work for mongodb. implement this in some method and call it here instead? .loadAllRelationIds(); condition = relation.joinColumns.map(function (joinColumn) { return qbAlias + "." + joinColumn.referencedColumn.propertyPath + " = :" + joinColumn.databaseName; }).join(" AND "); parameters = relation.joinColumns.reduce(function (parameters, joinColumn) { parameters[joinColumn.databaseName] = joinColumn.referencedColumn.getEntityValue(relationIdInDatabaseEntity_1); return parameters; }, {}); qb.where(condition) .setParameters(parameters); return [4 /*yield*/, qb.getOne()]; case 1: databaseEntity = _a.sent(); if (databaseEntity) { alreadyLoadedRelatedDatabaseSubject = new Subject_1.Subject(valueMetadata, undefined, databaseEntity); this.operateSubjects.push(alreadyLoadedRelatedDatabaseSubject); } _a.label = 2; case 2: if (!alreadyLoadedRelatedDatabaseSubject) return [3 /*break*/, 4]; // if object is already marked as removed then no need to proceed because it already was proceed // if we remove this it will cause a recursion if (alreadyLoadedRelatedDatabaseSubject.mustBeRemoved) return [2 /*return*/]; alreadyLoadedRelatedDatabaseSubject.mustBeRemoved = true; return [4 /*yield*/, this.buildCascadeRemovedAndRelationUpdateOperateSubjects(alreadyLoadedRelatedDatabaseSubject)]; case 3: _a.sent(); _a.label = 4; case 4: if (!relation.isOneToOneNotOwner) return [3 /*break*/, 8]; // we only work with cascade removes here if (!relation.isCascadeRemove) return [2 /*return*/]; // todo: no persistValueRelationId = undefined; if (subject.hasEntity && !subject.mustBeRemoved) { persistValue = relation.getEntityValue(subject.entity); if (persistValue) persistValueRelationId = relation.inverseRelation.getEntityValue(persistValue); if (persistValueRelationId === undefined) return [2 /*return*/]; // skip undefined properties } relationIdInDatabaseEntity_2 = relation.inverseRelation.joinColumns[0].referencedColumn.getEntityValue(subject.databaseEntity); // if database relation id does not exist then nothing to remove (but can this be possible?) if (relationIdInDatabaseEntity_2 === null || relationIdInDatabaseEntity_2 === undefined) return [2 /*return*/]; alreadyLoadedRelatedDatabaseSubject = this.operateSubjects.find(function (relatedSubject) { // (example) filter only subject that has database entity loaded and its target is Post if (!relatedSubject.hasDatabaseEntity || relatedSubject.entityTarget !== valueMetadata.target) return false; // (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.inverseRelation.getEntityValue(relatedSubject.databaseEntity) === relationIdInDatabaseEntity_2; }); if (!!alreadyLoadedRelatedDatabaseSubject) return [3 /*break*/, 6]; return [4 /*yield*/, this.connection .getRepository(valueMetadata.target) .createQueryBuilder(qbAlias, this.queryRunner) // todo: this wont work for mongodb. implement this in some method and call it here instead? .where(qbAlias + "." + relation.inverseSidePropertyPath + "=:id") // TODO relation.inverseRelation.joinColumns .setParameter("id", relationIdInDatabaseEntity_2) // (example) subject.entity is a details here, and the value is details.id .loadAllRelationIds() .getOne()]; case 5: databaseEntity = _a.sent(); // add only if database entity exist - because in the case of inverse side of the one-to-one relation // we cannot check if it was removed or not until we query the database // and it can be a situation that relation wasn't exist at all. This is particular that case alreadyLoadedRelatedDatabaseSubject = new Subject_1.Subject(valueMetadata, undefined, databaseEntity); this.operateSubjects.push(alreadyLoadedRelatedDatabaseSubject); _a.label = 6; case 6: if (!(alreadyLoadedRelatedDatabaseSubject && alreadyLoadedRelatedDatabaseSubject.hasDatabaseEntity)) return [3 /*break*/, 8]; inverseEntityRelationId = relation.inverseRelation.getEntityValue(alreadyLoadedRelatedDatabaseSubject.databaseEntity); if (persistValueRelationId && persistValueRelationId === inverseEntityRelationId) return [2 /*return*/]; // if object is already marked as removed then no need to proceed because it already was proceed // if we remove this it will cause a recursion if (alreadyLoadedRelatedDatabaseSubject.mustBeRemoved) return [2 /*return*/]; alreadyLoadedRelatedDatabaseSubject.mustBeRemoved = true; return [4 /*yield*/, this.buildCascadeRemovedAndRelationUpdateOperateSubjects(alreadyLoadedRelatedDatabaseSubject)]; case 7: _a.sent(); _a.label = 8; case 8: if (!(relation.isOneToMany || relation.isManyToMany)) return [3 /*break*/, 18]; persistValue_2 = undefined; if (subject.hasEntity) { persistValue_2 = relation.getEntityValue(subject.entity); if (persistValue_2 === undefined) return [2 /*return*/]; // skip undefined properties } databaseEntities_1 = []; escape_1 = function (name) { return _this.connection.driver.escape(name); }; if (!relation.isManyToManyOwner) return [3 /*break*/, 10]; // 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 [2 /*return*/]; joinAlias_1 = escape_1("persistenceJoinedRelation"); joinColumnConditions = relation.joinColumns.map(function (joinColumn) { return joinAlias_1 + "." + joinColumn.propertyName + " = :" + joinColumn.propertyName; }); inverseJoinColumnConditions = relation.inverseJoinColumns.map(function (inverseJoinColumn) { return joinAlias_1 + "." + inverseJoinColumn.propertyName + " = " + escape_1(qbAlias) + "." + escape_1(inverseJoinColumn.referencedColumn.propertyName); }); conditions = joinColumnConditions.concat(inverseJoinColumnConditions).join(" AND "); parameters = relation.joinColumns.reduce(function (parameters, joinColumn) { parameters[joinColumn.propertyName] = joinColumn.referencedColumn.getEntityValue(subject.databaseEntity); return parameters; }, {}); return [4 /*yield*/, this.connection .getRepository(valueMetadata.target) .createQueryBuilder(qbAlias, this.queryRunner) // todo: this wont work for mongodb. implement this in some method and call it here instead? .innerJoin(relation.junctionEntityMetadata.tableName, joinAlias_1, conditions) .setParameters(parameters) .loadAllRelationIds() .getMany()]; case 9: databaseEntities_1 = _a.sent(); return [3 /*break*/, 14]; case 10: if (!relation.isManyToManyNotOwner) return [3 /*break*/, 12]; // 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 [2 /*return*/]; joinAlias_2 = escape_1("persistenceJoinedRelation"); joinColumnConditions = relation.joinColumns.map(function (joinColumn) { return joinAlias_2 + "." + joinColumn.propertyName + " = " + escape_1(qbAlias) + "." + escape_1(joinColumn.referencedColumn.propertyName); }); inverseJoinColumnConditions = relation.inverseJoinColumns.map(function (inverseJoinColumn) { return joinAlias_2 + "." + inverseJoinColumn.propertyName + " = :" + inverseJoinColumn.propertyName; }); conditions = joinColumnConditions.concat(inverseJoinColumnConditions).join(" AND "); parameters = relation.inverseRelation.inverseJoinColumns.reduce(function (parameters, joinColumn) { parameters[joinColumn.propertyName] = joinColumn.referencedColumn.getEntityValue(subject.databaseEntity); return parameters; }, {}); return [4 /*yield*/, this.connection .getRepository(valueMetadata.target) .createQueryBuilder(qbAlias, this.queryRunner) // todo: this wont work for mongodb. implement this in some method and call it here instead? .innerJoin(relation.junctionEntityMetadata.tableName, joinAlias_2, conditions) .setParameters(parameters) .loadAllRelationIds() .getMany()]; case 11: databaseEntities_1 = _a.sent(); return [3 /*break*/, 14]; case 12: relationIdInDatabaseEntity = relation.inverseRelation.joinColumns[0].referencedColumn.getEntityValue(subject.databaseEntity); return [4 /*yield*/, this.connection .getRepository(valueMetadata.target) .createQueryBuilder(qbAlias, this.queryRunner) // todo: this wont work for mongodb. implement this in some method and call it here instead? .where(qbAlias + "." + relation.inverseSidePropertyPath + "=:id") .setParameter("id", relationIdInDatabaseEntity) .loadAllRelationIds() .getMany()]; case 13: // 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_1 = _a.sent(); _a.label = 14; case 14: // add to loadMap loaded entities if some of them are missing databaseEntities_1.forEach(function (databaseEntity) { var subjectInLoadMap = _this.findByEntityLike(valueMetadata.target, databaseEntity); if (subjectInLoadMap && !subjectInLoadMap.hasDatabaseEntity) { subjectInLoadMap.databaseEntity = databaseEntity; } else if (!subjectInLoadMap) { var subject_1 = new Subject_1.Subject(valueMetadata, undefined, databaseEntity); _this.operateSubjects.push(subject_1); } }); if (!(relation.isOneToMany && persistValue_2)) return [3 /*break*/, 16]; promises_1 = persistValue_2.map(function (persistValue) { return __awaiter(_this, void 0, void 0, function () { var persistedValueInDatabaseEntity, loadedSubject, id, databaseEntity; return __generator(this, function (_a) { switch (_a.label) { case 0: persistedValueInDatabaseEntity = databaseEntities_1.find(function (databaseEntity) { return valueMetadata.compareEntities(persistValue, databaseEntity); }); if (!!persistedValueInDatabaseEntity) return [3 /*break*/, 3]; loadedSubject = this.findByDatabaseEntityLike(valueMetadata.target, persistValue); if (!!loadedSubject) return [3 /*break*/, 2]; id = valueMetadata.getEntityIdMap(persistValue); if (!id) return [3 /*break*/, 2]; return [4 /*yield*/, this.connection .getRepository(valueMetadata.target) .createQueryBuilder(qbAlias, this.queryRunner) // todo: this wont work for mongodb. implement this in some method and call it here instead? .whereInIds([id]) .loadAllRelationIds() .getOne()]; case 1: databaseEntity = _a.sent(); if (databaseEntity) { loadedSubject = new Subject_1.Subject(valueMetadata, undefined, databaseEntity); // todo: what if entity like object exist in the loaded subjects but without databaseEntity? this.operateSubjects.push(loadedSubject); } _a.label = 2; case 2: if (loadedSubject) { loadedSubject.relationUpdates.push({ relation: relation.inverseRelation, value: subject.entity }); } _a.label = 3; case 3: return [2 /*return*/]; } }); }); }); return [4 /*yield*/, Promise.all(promises_1)]; case 15: _a.sent(); _a.label = 16; case 16: promises_2 = databaseEntities_1.map(function (databaseEntity) { return __awaiter(_this, void 0, void 0, function () { var relatedEntitySubject, relatedValue; return __generator(this, function (_a) { switch (_a.label) { case 0: relatedEntitySubject = this.findByDatabaseEntityLike(valueMetadata.target, databaseEntity); if (!relatedEntitySubject) return [2 /*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 check it will cause a recursion if (relatedEntitySubject.mustBeRemoved) return [2 /*return*/]; // todo: add another check for entity in unsetRelations? relatedValue = (persistValue_2 || []).find(function (persistValueItem) { return valueMetadata.compareEntities(relatedEntitySubject.databaseEntity, persistValueItem); }); if (!(persistValue_2 === null || !relatedValue)) return [3 /*break*/, 3]; if (!relation.isCascadeRemove) return [3 /*break*/, 2]; relatedEntitySubject.mustBeRemoved = true; // mark as removed all underlying entities that has cascade remove