typeorm
Version:
Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL, MongoDB databases.
721 lines (720 loc) • 68.5 kB
JavaScript
"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 OrmUtils_1 = require("../util/OrmUtils");
var PromiseUtils_1 = require("../util/PromiseUtils");
var MongoDriver_1 = require("../driver/mongodb/MongoDriver");
var Broadcaster_1 = require("../subscriber/Broadcaster");
/**
* Executes all database operations (inserts, updated, deletes) that must be executed
* with given persistence subjects.
*/
var SubjectOperationExecutor = /** @class */ (function () {
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
function SubjectOperationExecutor(connection, transactionEntityManager, queryRunner, subjects) {
/*subjects.forEach(subject => {
console.log(subject.entity);
console.log("mustBeInserted: ", subject.mustBeInserted);
console.log("mustBeUpdated: ", subject.mustBeUpdated);
console.log("mustBeRemoved: ", subject.mustBeRemoved);
});*/
this.connection = connection;
this.transactionEntityManager = transactionEntityManager;
this.queryRunner = queryRunner;
// validate all subjects first
subjects.forEach(function (subject) { return subject.validate(); });
// set class properties for easy use
this.allSubjects = subjects;
this.insertSubjects = subjects.filter(function (subject) { return subject.mustBeInserted; });
this.updateSubjects = subjects.filter(function (subject) { return subject.mustBeUpdated; });
this.removeSubjects = subjects.filter(function (subject) { return subject.mustBeRemoved; });
this.relationUpdateSubjects = subjects.filter(function (subject) { return subject.hasRelationUpdates; });
this.broadcaster = new Broadcaster_1.Broadcaster(this.connection);
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
SubjectOperationExecutor.prototype.areExecutableOperations = function () {
return this.insertSubjects.length > 0 ||
this.updateSubjects.length > 0 ||
this.removeSubjects.length > 0 ||
this.relationUpdateSubjects.length > 0 ||
this.allSubjects.some(function (subject) { return subject.junctionInserts.length > 0; }) ||
this.allSubjects.some(function (subject) { return subject.junctionRemoves.length > 0; });
};
/**
* Executes all operations over given array of subjects.
* Executes queries using given query runner.
*/
SubjectOperationExecutor.prototype.execute = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// broadcast "before" events before we start updating
return [4 /*yield*/, this.broadcaster.broadcastBeforeEventsForAll(this.transactionEntityManager, this.insertSubjects, this.updateSubjects, this.removeSubjects)];
case 1:
// broadcast "before" events before we start updating
_a.sent();
// since events can trigger some internal changes (for example update depend property) we need to perform some re-computations here
this.updateSubjects.forEach(function (subject) { return subject.recompute(); });
return [4 /*yield*/, this.executeInsertOperations()];
case 2:
_a.sent();
return [4 /*yield*/, this.executeInsertClosureTableOperations()];
case 3:
_a.sent();
return [4 /*yield*/, this.executeInsertJunctionsOperations()];
case 4:
_a.sent();
return [4 /*yield*/, this.executeRemoveJunctionsOperations()];
case 5:
_a.sent();
return [4 /*yield*/, this.executeUpdateOperations()];
case 6:
_a.sent();
return [4 /*yield*/, this.executeUpdateRelations()];
case 7:
_a.sent();
return [4 /*yield*/, this.executeRemoveOperations()];
case 8:
_a.sent();
// update all special columns in persisted entities, like inserted id or remove ids from the removed entities
return [4 /*yield*/, this.updateSpecialColumnsInPersistedEntities()];
case 9:
// update all special columns in persisted entities, like inserted id or remove ids from the removed entities
_a.sent();
// finally broadcast "after" events
// todo: THIS SHOULD NOT BE TRUTH: note that we are broadcasting events after commit because we want to have ids of the entities inside them to be available in subscribers
return [4 /*yield*/, this.broadcaster.broadcastAfterEventsForAll(this.transactionEntityManager, this.insertSubjects, this.updateSubjects, this.removeSubjects)];
case 10:
// finally broadcast "after" events
// todo: THIS SHOULD NOT BE TRUTH: note that we are broadcasting events after commit because we want to have ids of the entities inside them to be available in subscribers
_a.sent();
return [2 /*return*/];
}
});
});
};
// -------------------------------------------------------------------------
// Private Methods: Insertion
// -------------------------------------------------------------------------
/**
* Executes insert operations.
*
* For insertion we separate two groups of entities:
* - first group of entities are entities which do not have any relations
* or entities which do not have any non-nullable relation
* - second group of entities are entities which does have non-nullable relations
*
* Insert process of the entities from the first group which can only have nullable relations are actually a two-step process:
* - first we insert entities without their relations, explicitly left them NULL
* - later we update inserted entity once again with id of the object inserted with it
*
* Yes, two queries are being executed, but this is by design.
* There is no better way to solve this problem and others at the same time.
*
* Insert process of the entities from the second group which can have only non nullable relations is a single-step process:
* - we simply insert all entities and get into attention all its dependencies which were inserted in the first group
*/
SubjectOperationExecutor.prototype.executeInsertOperations = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
var firstInsertSubjects, secondInsertSubjects, updatePromises;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
firstInsertSubjects = this.insertSubjects.filter(function (subject) { return !subject.metadata.hasNonNullableRelations; });
secondInsertSubjects = this.insertSubjects.filter(function (subject) { return subject.metadata.hasNonNullableRelations; });
// note: these operations should be executed in sequence, not in parallel
// because second group depend of obtained data from the first group
return [4 /*yield*/, Promise.all(firstInsertSubjects.map(function (subject) { return _this.insert(subject, []); }))];
case 1:
// note: these operations should be executed in sequence, not in parallel
// because second group depend of obtained data from the first group
_a.sent();
return [4 /*yield*/, Promise.all(secondInsertSubjects.map(function (subject) { return _this.insert(subject, firstInsertSubjects); }))];
case 2:
_a.sent();
updatePromises = [];
firstInsertSubjects.forEach(function (subject) {
// first update relations with join columns (one-to-one owner and many-to-one relations)
var updateOptions = {};
subject.metadata.relationsWithJoinColumns.forEach(function (relation) {
relation.joinColumns.forEach(function (joinColumn) {
var referencedColumn = joinColumn.referencedColumn;
var relatedEntity = relation.getEntityValue(subject.entity);
// if relation value is not set then nothing to do here
if (!relatedEntity)
return;
// check if relation reference column is a relation
var relationId;
var columnRelation = relation.inverseEntityMetadata.findRelationWithPropertyPath(joinColumn.referencedColumn.propertyPath);
if (columnRelation) {
var insertSubject = _this.insertSubjects.find(function (insertedSubject) { return insertedSubject.entity === referencedColumn.getEntityValue(relatedEntity); });
// if this relation was just inserted
if (insertSubject) {
// check if we have this relation id already
relationId = columnRelation.getEntityValue(referencedColumn.getEntityValue(relatedEntity));
if (!relationId) {
// if we don't have relation id then use special values
if (referencedColumn.isGenerated && insertSubject.generatedMap)
relationId = referencedColumn.getEntityValue(insertSubject.generatedMap);
// todo: handle other special types too
}
}
}
else {
var insertSubject = _this.insertSubjects.find(function (insertedSubject) { return insertedSubject.entity === relatedEntity; });
// if this relation was just inserted
if (insertSubject) {
// check if we have this relation id already
relationId = referencedColumn.getEntityValue(relatedEntity);
if (!relationId) {
// if we don't have relation id then use special values
if (referencedColumn.isGenerated && insertSubject.generatedMap)
relationId = referencedColumn.getEntityValue(insertSubject.generatedMap);
// todo: handle other special types too
}
}
}
if (relationId) {
updateOptions[joinColumn.databaseName] = relationId;
}
});
});
// if we found relations which we can update - then update them
if (Object.keys(updateOptions).length > 0 /*&& subject.hasEntity*/) {
// const relatedEntityIdMap = subject.getPersistedEntityIdMap; // todo: this works incorrectly
var columns = subject.metadata.parentEntityMetadata ? subject.metadata.primaryColumns : subject.metadata.primaryColumns;
var conditions_1 = {};
columns.forEach(function (column) {
var entityValue = column.getEntityValue(subject.entity);
// if entity id is a relation, then extract referenced column from that relation
var columnRelation = subject.metadata.relations.find(function (relation) { return relation.propertyName === column.propertyName; });
if (entityValue && columnRelation) {
columnRelation.joinColumns.forEach(function (joinColumn) {
var relationIdOfEntityValue = entityValue[joinColumn.referencedColumn.propertyName];
if (!relationIdOfEntityValue) {
var entityValueInsertSubject = _this.insertSubjects.find(function (subject) { return subject.entity === entityValue; });
if (entityValueInsertSubject) {
if (joinColumn.referencedColumn.isGenerated && entityValueInsertSubject.generatedMap)
relationIdOfEntityValue = joinColumn.referencedColumn.getEntityValue(entityValueInsertSubject.generatedMap);
}
}
if (relationIdOfEntityValue) {
conditions_1[column.databaseName] = relationIdOfEntityValue;
}
});
}
else {
if (entityValue) {
conditions_1[column.databaseName] = entityValue;
}
else {
if (subject.generatedMap)
conditions_1[column.databaseName] = column.getEntityValue(subject.generatedMap);
}
}
});
if (!Object.keys(conditions_1).length)
return;
var updatePromise = _this.queryRunner.update(subject.metadata.tablePath, updateOptions, conditions_1);
updatePromises.push(updatePromise);
}
// we need to update relation ids if newly inserted objects are used from inverse side in one-to-many inverse relation
// we also need to update relation ids if newly inserted objects are used from inverse side in one-to-one inverse relation
var oneToManyAndOneToOneNonOwnerRelations = subject.metadata.oneToManyRelations.concat(subject.metadata.oneToOneRelations.filter(function (relation) { return !relation.isOwning; }));
// console.log(oneToManyAndOneToOneNonOwnerRelations);
subject.metadata.extractRelationValuesFromEntity(subject.entity, oneToManyAndOneToOneNonOwnerRelations)
.forEach(function (_a) {
var relation = _a[0], subRelatedEntity = _a[1], inverseEntityMetadata = _a[2];
relation.inverseRelation.joinColumns.forEach(function (joinColumn) {
var referencedColumn = joinColumn.referencedColumn;
var columns = inverseEntityMetadata.parentEntityMetadata ? inverseEntityMetadata.primaryColumns : inverseEntityMetadata.primaryColumns;
var conditions = {};
columns.forEach(function (column) {
var entityValue = column.getEntityValue(subRelatedEntity);
// if entity id is a relation, then extract referenced column from that relation
var columnRelation = inverseEntityMetadata.relations.find(function (relation) { return relation.propertyName === column.propertyName; });
if (entityValue && columnRelation) {
columnRelation.joinColumns.forEach(function (columnRelationJoinColumn) {
var relationIdOfEntityValue = entityValue[columnRelationJoinColumn.referencedColumn.propertyName];
if (!relationIdOfEntityValue) {
var entityValueInsertSubject = _this.insertSubjects.find(function (subject) { return subject.entity === entityValue; });
if (entityValueInsertSubject) {
if (columnRelationJoinColumn.referencedColumn.isGenerated && entityValueInsertSubject.generatedMap) {
relationIdOfEntityValue = columnRelationJoinColumn.referencedColumn.getEntityValue(entityValueInsertSubject.generatedMap);
}
}
}
if (relationIdOfEntityValue) {
conditions[column.databaseName] = relationIdOfEntityValue;
}
});
}
else {
var entityValueInsertSubject = _this.insertSubjects.find(function (subject) { return subject.entity === subRelatedEntity; });
if (entityValue) {
conditions[column.databaseName] = entityValue;
}
else {
if (entityValueInsertSubject && entityValueInsertSubject.generatedMap)
conditions[column.databaseName] = column.getEntityValue(entityValueInsertSubject.generatedMap);
}
}
});
if (!Object.keys(conditions).length)
return;
var updateOptions = {};
var columnRelation = relation.inverseEntityMetadata.relations.find(function (rel) { return rel.propertyName === referencedColumn.propertyName; });
var columnValue = referencedColumn.getEntityValue(subject.entity);
if (columnRelation) {
var id = columnRelation.getEntityValue(columnValue);
if (!id) {
var insertSubject = _this.insertSubjects.find(function (subject) { return subject.entity === columnValue; });
if (insertSubject) {
if (insertSubject.generatedMap)
id = referencedColumn.getEntityValue(insertSubject.generatedMap);
}
}
updateOptions[joinColumn.databaseName] = id;
}
else {
var generatedColumnValue = subject.generatedMap ? referencedColumn.getEntityValue(subject.generatedMap) : undefined;
updateOptions[joinColumn.databaseName] = columnValue || generatedColumnValue;
}
var updatePromise = _this.queryRunner.update(relation.inverseEntityMetadata.tablePath, updateOptions, conditions);
updatePromises.push(updatePromise);
});
});
});
return [4 /*yield*/, Promise.all(updatePromises)];
case 3:
_a.sent();
return [2 /*return*/];
}
});
});
};
/**
* Inserts an entity from the given insert operation into the database.
* If entity has an generated column, then after saving new generated value will be stored to the InsertOperation.
* If entity uses class-table-inheritance, then multiple inserts may by performed to save all entities.
*/
SubjectOperationExecutor.prototype.insert = function (subject, alreadyInsertedSubjects) {
return __awaiter(this, void 0, void 0, function () {
var parentEntityMetadata, metadata, entity, insertResult, parentGeneratedId, parentValuesMap, childValuesMap, secondGeneratedId, valuesMap;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
parentEntityMetadata = subject.metadata.parentEntityMetadata;
metadata = subject.metadata;
entity = subject.entity;
if (!metadata.isClassTableChild) return [3 /*break*/, 3];
parentValuesMap = this.collectColumnsAndValues(parentEntityMetadata, entity, subject.date, undefined, metadata.discriminatorValue, alreadyInsertedSubjects, "insert");
return [4 /*yield*/, this.queryRunner.insert(parentEntityMetadata.tablePath, parentValuesMap)];
case 1:
insertResult = parentGeneratedId = _a.sent();
childValuesMap = this.collectColumnsAndValues(metadata, entity, subject.date, insertResult.generatedMap[parentEntityMetadata.primaryColumns[0].propertyName], undefined, alreadyInsertedSubjects, "insert");
return [4 /*yield*/, this.queryRunner.insert(metadata.tablePath, childValuesMap)];
case 2:
secondGeneratedId = _a.sent();
if (!insertResult && secondGeneratedId)
insertResult = secondGeneratedId;
return [3 /*break*/, 5];
case 3:
valuesMap = this.collectColumnsAndValues(metadata, entity, subject.date, undefined, undefined, alreadyInsertedSubjects, "insert");
return [4 /*yield*/, this.queryRunner.insert(metadata.tablePath, valuesMap)];
case 4:
// console.log("valuesMap", valuesMap);
insertResult = _a.sent();
_a.label = 5;
case 5:
if (parentGeneratedId)
subject.parentGeneratedId = parentGeneratedId.generatedMap[parentEntityMetadata.primaryColumns[0].propertyName];
// todo: better if insert method will return object with all generated ids, object id, etc.
if (insertResult.generatedMap)
subject.generatedMap = insertResult.generatedMap;
return [2 /*return*/];
}
});
});
};
SubjectOperationExecutor.prototype.collectColumns = function (columns, entity, object, operation) {
var _this = this;
columns.forEach(function (column) {
if (column.isVirtual || column.isParentId || column.isDiscriminator)
return;
if (operation === "update" && column.isReadonly)
return;
var value = entity[column.propertyName];
if (value === undefined)
return;
object[column.databaseNameWithoutPrefixes] = _this.connection.driver.preparePersistentValue(value, column); // todo: maybe preparePersistentValue is not responsibility of this class
});
};
SubjectOperationExecutor.prototype.collectEmbeds = function (embed, entity, object, operation) {
var _this = this;
if (embed.isArray) {
if (entity[embed.propertyName] instanceof Array) {
if (!object[embed.prefix])
object[embed.prefix] = [];
entity[embed.propertyName].forEach(function (subEntity, index) {
if (!object[embed.prefix][index])
object[embed.prefix][index] = {};
_this.collectColumns(embed.columns, subEntity, object[embed.prefix][index], operation);
embed.embeddeds.forEach(function (childEmbed) { return _this.collectEmbeds(childEmbed, subEntity, object[embed.prefix][index], operation); });
});
}
}
else {
if (entity[embed.propertyName] !== undefined) {
if (!object[embed.prefix])
object[embed.prefix] = {};
this.collectColumns(embed.columns, entity[embed.propertyName], object[embed.prefix], operation);
embed.embeddeds.forEach(function (childEmbed) { return _this.collectEmbeds(childEmbed, entity[embed.propertyName], object[embed.prefix], operation); });
}
}
};
/**
* Collects columns and values for the insert operation.
*/
SubjectOperationExecutor.prototype.collectColumnsAndValues = function (metadata, entity, date, parentIdColumnValue, discriminatorValue, alreadyInsertedSubjects, operation) {
var _this = this;
var values = {};
if (this.connection.driver instanceof MongoDriver_1.MongoDriver) {
this.collectColumns(metadata.ownColumns, entity, values, operation);
metadata.embeddeds.forEach(function (embed) { return _this.collectEmbeds(embed, entity, values, operation); });
}
else {
metadata.columns.forEach(function (column) {
if (column.isVirtual || column.isParentId || column.isDiscriminator)
return;
var value = column.getEntityValue(entity);
if (value === undefined)
return;
values[column.databaseName] = _this.connection.driver.preparePersistentValue(value, column); // todo: maybe preparePersistentValue is not responsibility of this class
});
}
metadata.relationsWithJoinColumns.forEach(function (relation) {
relation.joinColumns.forEach(function (joinColumn) {
var relationValue;
var value = relation.getEntityValue(entity);
if (value) {
// if relation value is stored in the entity itself then use it from there
var relationId = joinColumn.referencedColumn.getEntityValue(value); // relation.getInverseEntityRelationId(value); // todo: check it
if (relationId) {
relationValue = relationId;
}
// otherwise try to find relational value from just inserted subjects
var alreadyInsertedSubject = alreadyInsertedSubjects.find(function (insertedSubject) {
return insertedSubject.entity === value;
});
if (alreadyInsertedSubject) {
var referencedColumn = joinColumn.referencedColumn;
// if join column references to the primary generated column then seek in the newEntityId of the insertedSubject
if (referencedColumn.referencedColumn && referencedColumn.referencedColumn.isGenerated) {
if (referencedColumn.isParentId) {
relationValue = alreadyInsertedSubject.parentGeneratedId;
}
// todo: what if reference column is not generated?
// todo: what if reference column is not related to table inheritance?
}
if (referencedColumn.isGenerated && alreadyInsertedSubject.generatedMap)
relationValue = referencedColumn.getEntityValue(alreadyInsertedSubject.generatedMap);
// if it references to create or update date columns
if (referencedColumn.isCreateDate || referencedColumn.isUpdateDate)
relationValue = _this.connection.driver.preparePersistentValue(alreadyInsertedSubject.date, referencedColumn);
// if it references to version column
if (referencedColumn.isVersion)
relationValue = _this.connection.driver.preparePersistentValue(1, referencedColumn);
}
}
else if (relation.inverseRelation) {
var inverseSubject = _this.allSubjects.find(function (subject) {
if (!subject.hasEntity || subject.entityTarget !== relation.inverseRelation.target)
return false;
var inverseRelationValue = relation.inverseRelation.getEntityValue(subject.entity);
if (inverseRelationValue) {
if (inverseRelationValue instanceof Array) {
return inverseRelationValue.find(function (subValue) { return subValue === subValue; });
}
else {
return inverseRelationValue === entity;
}
}
});
if (inverseSubject && joinColumn.referencedColumn.getEntityValue(inverseSubject.entity)) {
relationValue = joinColumn.referencedColumn.getEntityValue(inverseSubject.entity);
}
}
if (relationValue) {
values[joinColumn.databaseName] = relationValue;
}
});
});
// add special column and value - date of creation
if (metadata.createDateColumn) {
var value = this.connection.driver.preparePersistentValue(date, metadata.createDateColumn);
values[metadata.createDateColumn.databaseName] = value;
}
// add special column and value - date of updating
if (metadata.updateDateColumn) {
var value = this.connection.driver.preparePersistentValue(date, metadata.updateDateColumn);
values[metadata.updateDateColumn.databaseName] = value;
}
// add special column and value - version column
if (metadata.versionColumn) {
var value = this.connection.driver.preparePersistentValue(1, metadata.versionColumn);
values[metadata.versionColumn.databaseName] = value;
}
// add special column and value - discriminator value (for tables using table inheritance)
if (metadata.discriminatorColumn) {
var value = this.connection.driver.preparePersistentValue(discriminatorValue || metadata.discriminatorValue, metadata.discriminatorColumn);
values[metadata.discriminatorColumn.databaseName] = value;
}
metadata.generatedColumns
.filter(function (column) { return column.generationStrategy === "uuid"; })
.forEach(function (column) {
if (column.isNullable && values[column.databaseName] === null)
return;
var uuid = _this.connection.driver.preparePersistentValue("", column);
if (uuid && !values[column.databaseName])
values[column.databaseName] = uuid;
});
// add special column and value - tree level and tree parents (for tree-type tables)
if (metadata.treeLevelColumn && metadata.treeParentRelation) {
var parentEntity = metadata.treeParentRelation.getEntityValue(entity);
var parentLevel = parentEntity ? (metadata.treeLevelColumn.getEntityValue(parentEntity) || 0) : 0;
values[metadata.treeLevelColumn.databaseName] = parentLevel + 1;
}
// add special column and value - parent id column (for tables using table inheritance)
if (metadata.parentEntityMetadata && metadata.parentIdColumns.length) {
values[metadata.parentIdColumns[0].databaseName] = parentIdColumnValue || metadata.parentEntityMetadata.primaryColumns[0].getEntityValue(entity);
}
return values;
};
// -------------------------------------------------------------------------
// Private Methods: Insertion into closure tables
// -------------------------------------------------------------------------
/**
* Inserts all given subjects into closure table.
*/
SubjectOperationExecutor.prototype.executeInsertClosureTableOperations = function () {
var _this = this;
var promises = this.insertSubjects
.filter(function (subject) { return subject.metadata.isClosure; })
.map(function (subject) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// const relationsUpdateMap = this.findUpdateOperationForEntity(updatesByRelations, insertSubjects, subject.entity);
// subject.treeLevel = await this.insertIntoClosureTable(subject, relationsUpdateMap);
return [4 /*yield*/, this.insertClosureTableValues(subject)];
case 1:
// const relationsUpdateMap = this.findUpdateOperationForEntity(updatesByRelations, insertSubjects, subject.entity);
// subject.treeLevel = await this.insertIntoClosureTable(subject, relationsUpdateMap);
_a.sent();
return [2 /*return*/];
}
});
}); });
return Promise.all(promises);
};
/**
* Inserts given subject into closure table.
*/
SubjectOperationExecutor.prototype.insertClosureTableValues = function (subject) {
return __awaiter(this, void 0, void 0, function () {
var tablePath, referencedColumn, newEntityId, parentEntity, parentEntityId, parentInsertedSubject, parentSubject, _a, values, _b, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
tablePath = subject.metadata.closureJunctionTable.tablePath;
referencedColumn = subject.metadata.treeParentRelation.joinColumns[0].referencedColumn;
newEntityId = referencedColumn.getEntityValue(subject.entity);
if (!newEntityId && referencedColumn.isGenerated && subject.generatedMap) {
newEntityId = referencedColumn.getEntityValue(subject.generatedMap);
// we should not handle object id here because closure tables are not supported by mongodb driver.
} // todo: implement other special column types too
parentEntity = subject.metadata.treeParentRelation.getEntityValue(subject.entity);
parentEntityId = 0;
if (parentEntity) {
parentEntityId = referencedColumn.getEntityValue(parentEntity);
if (!parentEntityId && referencedColumn.isGenerated) {
parentInsertedSubject = this.insertSubjects.find(function (subject) { return subject.entity === parentEntity; });
// todo: throw exception if parentInsertedSubject is not set
if (parentInsertedSubject.generatedMap)
parentEntityId = referencedColumn.getEntityValue(parentInsertedSubject.generatedMap);
} // todo: implement other special column types too
}
// try to find parent entity id in some other entity that has this entity in its children
if (!parentEntityId) {
parentSubject = this.allSubjects.find(function (allSubject) {
if (!allSubject.hasEntity || !allSubject.metadata.isClosure || !allSubject.metadata.treeChildrenRelation)
return false;
var children = subject.metadata.treeChildrenRelation.getEntityValue(allSubject.entity);
return children instanceof Array ? children.indexOf(subject.entity) !== -1 : false;
});
if (parentSubject) {
parentEntityId = referencedColumn.getEntityValue(parentSubject.entity);
if (!parentEntityId && parentSubject.generatedMap) {
parentEntityId = referencedColumn.getEntityValue(parentSubject.generatedMap);
}
}
}
// if parent entity exist then insert a new row into closure table
_a = subject;
return [4 /*yield*/, this.queryRunner.insertIntoClosureTable(tablePath, newEntityId, parentEntityId, !!subject.metadata.treeLevelColumn)];
case 1:
// if parent entity exist then insert a new row into closure table
_a.treeLevel = _d.sent();
if (!subject.metadata.treeLevelColumn) return [3 /*break*/, 3];
values = (_b = {}, _b[subject.metadata.treeLevelColumn.databaseName] = subject.treeLevel, _b);
return [4 /*yield*/, this.queryRunner.update(subject.metadata.tablePath, values, (_c = {}, _c[referencedColumn.databaseName] = newEntityId, _c))];
case 2:
_d.sent();
_d.label = 3;
case 3: return [2 /*return*/];
}
});
});
};
// -------------------------------------------------------------------------
// Private Methods: Update
// -------------------------------------------------------------------------
/**
* Updates all given subjects in the database.
*/
SubjectOperationExecutor.prototype.executeUpdateOperations = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, Promise.all(this.updateSubjects.map(function (subject) { return _this.update(subject); }))];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
/**
* Updates given subject in the database.
*/
SubjectOperationExecutor.prototype.update = function (subject) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
var entity, idMap, value_1, valueMaps, valueMap, valueMap, valueMap, valueMap;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
entity = subject.entity;
if (this.connection.driver instanceof MongoDriver_1.MongoDriver) {
idMap = subject.metadata.getDatabaseEntityIdMap(entity);
if (!idMap)
throw new Error("Internal error. Cannot get id of the updating entity.");
value_1 = {};
this.collectColumns(subject.metadata.ownColumns, entity, value_1, "update");
subject.metadata.embeddeds.forEach(function (embed) { return _this.collectEmbeds(embed, entity, value_1, "update"); });
// if number of updated columns = 0 no need to update updated date and version columns
if (Object.keys(value_1).length === 0)
return [2 /*return*/];
if (subject.metadata.updateDateColumn)
value_1[subject.metadata.updateDateColumn.databaseName] = this.connection.driver.preparePersistentValue(new Date(), subject.metadata.updateDateColumn);
if (subject.metadata.versionColumn)
value_1[subject.metadata.versionColumn.databaseName] = this.connection.driver.preparePersistentValue(subject.metadata.versionColumn.getEntityValue(entity) + 1, subject.metadata.versionColumn);
return [2 /*return*/, this.queryRunner.update(subject.metadata.tablePath, value_1, idMap)];
}
valueMaps = [];
// console.log(subject.diffColumns);
subject.diffColumns.forEach(function (column) {
// if (!column.entityTarget) return; // todo: how this can be possible?
var metadata = _this.connection.getMetadata(column.entityMetadata.target);
var valueMap = valueMaps.find(function (valueMap) { return valueMap.tablePath === metadata.tablePath; });
if (!valueMap) {
valueMap = { tablePath: metadata.tablePath, metadata: metadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[column.databaseName] = _this.connection.driver.preparePersistentValue(column.getEntityValue(entity), column);
});
subject.diffRelations.forEach(function (relation) {
var valueMap = valueMaps.find(function (valueMap) { return valueMap.tablePath === relation.entityMetadata.tablePath; });
if (!valueMap) {
valueMap = { tablePath: relation.entityMetadata.tablePath, metadata: relation.entityMetadata, values: {} };
valueMaps.push(valueMap);
}
var value = relation.getEntityValue(entity);
relation.joinColumns.forEach(function (joinColumn) {
valueMap.values[joinColumn.databaseName] = value !== null && value !== undefined ? value[joinColumn.referencedColumn.propertyName] : null; // todo: should not have a call to primaryColumn, instead join column metadata should be used
});
});
// if number of updated columns = 0 no need to update updated date and version columns
if (Object.keys(valueMaps).length === 0)
return [2 /*return*/];
if (subject.metadata.updateDateColumn) {
valueMap = valueMaps.find(function (valueMap) { return valueMap.tablePath === subject.metadata.tablePath; });
if (!valueMap) {
valueMap = { tablePath: subject.metadata.tablePath, metadata: subject.metadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[subject.metadata.updateDateColumn.databaseName] = this.connection.driver.preparePersistentValue(new Date(), subject.metadata.updateDateColumn);
}
if (subject.metadata.versionColumn) {
valueMap = valueMaps.find(function (valueMap) { return valueMap.tablePath === subject.metadata.tablePath; });
if (!valueMap) {
valueMap = { tablePath: subject.metadata.tablePath, metadata: subject.metadata, values: {} };
valueMaps.push(valueMap);
}
valueMap.values[subject.metadata.versionColumn.databaseName] = this.connection.driver.preparePersistentValue(subject.metadata.versionColumn.getEntityValue(entity) + 1, subject.metadata.versionColumn);
}
if (subject.metadata.parentEntityMetadata) {
if (subject.metadata.parentEntityMetadata.updateDateColumn) {
valueMap = valueMaps.find(function (valueMap) { return valueMap.tablePath === subject.metadata.parentEntityMetadata.tablePath; });
if (!valueMap) {
valueMap = {
tablePath: subject.metadata.parentEntityMetadata.tablePath,
metadata: subject.metadata.parentEntityMetadata,
values: {}
};
valueMaps.push(valueMap);
}
valueMap.values[subject.metadata.parentEntityMetadata.updateDateColumn.databaseName] = this.connection.driver.preparePersistentValue(new Date(), subject.metadata.parentEntityMetadata.updateDateColumn);
}