typeorm
Version:
Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, MongoDB databases.
151 lines (149 loc) • 7.38 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubjectDatabaseEntityLoader = void 0;
/**
* 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.
*/
class SubjectDatabaseEntityLoader {
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(queryRunner, subjects) {
this.queryRunner = queryRunner;
this.subjects = subjects;
}
// ---------------------------------------------------------------------
// Public Methods
// ---------------------------------------------------------------------
/**
* Loads database entities for all subjects.
*
* loadAllRelations flag is used to load all relation ids of the object, no matter if they present in subject entity or not.
* This option is used for deletion.
*/
async load(operationType) {
// we are grouping subjects by target to perform more optimized queries using WHERE IN operator
// go through the groups and perform loading of database entities of each subject in the group
const promises = this.groupByEntityTargets().map(async (subjectGroup) => {
// prepare entity ids of the subjects we need to load
const allIds = [];
const allSubjects = [];
subjectGroup.subjects.forEach((subject) => {
// we don't load if subject already has a database entity loaded
if (subject.databaseEntity || !subject.identifier)
return;
allIds.push(subject.identifier);
allSubjects.push(subject);
});
// if there no ids found (means all entities are new and have generated ids) - then nothing to load there
if (!allIds.length)
return;
const loadRelationPropertyPaths = [];
// for the save, soft-remove and recover operation
// extract all property paths of the relations we need to load relation ids for
// this is for optimization purpose - this way we don't load relation ids for entities
// whose relations are undefined, and since they are undefined its really pointless to
// load something for them, since undefined properties are skipped by the orm
if (operationType === "save" ||
operationType === "soft-remove" ||
operationType === "recover") {
subjectGroup.subjects.forEach((subject) => {
// gets all relation property paths that exist in the persisted entity.
subject.metadata.relations.forEach((relation) => {
const value = relation.getEntityValue(subject.entityWithFulfilledIds);
if (value === undefined)
return;
if (loadRelationPropertyPaths.indexOf(relation.propertyPath) === -1)
loadRelationPropertyPaths.push(relation.propertyPath);
});
});
}
else {
// remove
// for remove operation
// we only need to load junction relation ids since only they are removed by cascades
loadRelationPropertyPaths.push(...subjectGroup.subjects[0].metadata.manyToManyRelations.map((relation) => relation.propertyPath));
}
const findOptions = {
loadEagerRelations: false,
loadRelationIds: {
relations: loadRelationPropertyPaths,
disableMixedMap: true,
},
// the soft-deleted entities should be included in the loaded entities for recover operation
withDeleted: true,
};
// load database entities for all given ids
let entities = [];
if (this.queryRunner.connection.driver.options.type ===
"mongodb") {
const mongoRepo = this.queryRunner.manager.getRepository(subjectGroup.target);
entities = await mongoRepo.findByIds(allIds, findOptions);
}
else {
entities = await this.queryRunner.manager
.getRepository(subjectGroup.target)
.createQueryBuilder()
.setFindOptions(findOptions)
.whereInIds(allIds)
.getMany();
}
// now when we have entities we need to find subject of each entity
// and insert that entity into database entity of the found subjects
entities.forEach((entity) => {
const subjects = this.findByPersistEntityLike(subjectGroup.target, entity);
subjects.forEach((subject) => {
subject.databaseEntity = entity;
if (!subject.identifier)
subject.identifier =
subject.metadata.hasAllPrimaryKeys(entity)
? subject.metadata.getEntityIdMap(entity)
: undefined;
});
});
// this way we tell what subjects we tried to load database entities of
for (let subject of allSubjects) {
subject.databaseEntityLoaded = true;
}
});
await Promise.all(promises);
}
// ---------------------------------------------------------------------
// Protected Methods
// ---------------------------------------------------------------------
/**
* Finds subjects where entity like given subject's entity.
* Comparison made by entity id.
* Multiple subjects may be returned if duplicates are present in the subject array.
* This will likely result in the same row being updated multiple times during a transaction.
*/
findByPersistEntityLike(entityTarget, entity) {
return this.subjects.filter((subject) => {
if (!subject.entity)
return false;
if (subject.entity === entity)
return true;
return (subject.metadata.target === entityTarget &&
subject.metadata.compareEntities(subject.entityWithFulfilledIds, entity));
});
}
/**
* Groups given Subject objects into groups separated by entity targets.
*/
groupByEntityTargets() {
return this.subjects.reduce((groups, operatedEntity) => {
let group = groups.find((group) => group.target === operatedEntity.metadata.target);
if (!group) {
group = { target: operatedEntity.metadata.target, subjects: [] };
groups.push(group);
}
group.subjects.push(operatedEntity);
return groups;
}, []);
}
}
exports.SubjectDatabaseEntityLoader = SubjectDatabaseEntityLoader;
//# sourceMappingURL=SubjectDatabaseEntityLoader.js.map
;