UNPKG

typeorm

Version:

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

220 lines (218 loc) • 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RelationLoader = void 0; var tslib_1 = require("tslib"); /** * Wraps entities and creates getters/setters for their relations * to be able to lazily load relations when accessing these relations. */ var RelationLoader = /** @class */ (function () { // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- function RelationLoader(connection) { this.connection = connection; } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Loads relation data for the given entity and its relation. */ RelationLoader.prototype.load = function (relation, entityOrEntities, queryRunner) { if (queryRunner && queryRunner.isReleased) queryRunner = undefined; // get new one if already closed if (relation.isManyToOne || relation.isOneToOneOwner) { return this.loadManyToOneOrOneToOneOwner(relation, entityOrEntities, queryRunner); } else if (relation.isOneToMany || relation.isOneToOneNotOwner) { return this.loadOneToManyOrOneToOneNotOwner(relation, entityOrEntities, queryRunner); } else if (relation.isManyToManyOwner) { return this.loadManyToManyOwner(relation, entityOrEntities, queryRunner); } else { // many-to-many non owner return this.loadManyToManyNotOwner(relation, entityOrEntities, queryRunner); } }; /** * Loads data for many-to-one and one-to-one owner relations. * * (ow) post.category<=>category.post * loaded: category from post * example: SELECT category.id AS category_id, category.name AS category_name FROM category category * INNER JOIN post Post ON Post.category=category.id WHERE Post.id=1 */ RelationLoader.prototype.loadManyToOneOrOneToOneOwner = function (relation, entityOrEntities, queryRunner) { var entities = Array.isArray(entityOrEntities) ? entityOrEntities : [entityOrEntities]; var columns = relation.entityMetadata.primaryColumns; var joinColumns = relation.isOwning ? relation.joinColumns : relation.inverseRelation.joinColumns; var conditions = joinColumns.map(function (joinColumn) { return relation.entityMetadata.name + "." + joinColumn.propertyName + " = " + relation.propertyName + "." + joinColumn.referencedColumn.propertyName; }).join(" AND "); var joinAliasName = relation.entityMetadata.name; var qb = this.connection .createQueryBuilder(queryRunner) .select(relation.propertyName) // category .from(relation.type, relation.propertyName) // Category, category .innerJoin(relation.entityMetadata.target, joinAliasName, conditions); if (columns.length === 1) { qb.where(joinAliasName + "." + columns[0].propertyPath + " IN (:..." + (joinAliasName + "_" + columns[0].propertyName) + ")"); qb.setParameter(joinAliasName + "_" + columns[0].propertyName, entities.map(function (entity) { return columns[0].getEntityValue(entity); })); } else { var condition = entities.map(function (entity, entityIndex) { return columns.map(function (column, columnIndex) { var paramName = joinAliasName + "_entity_" + entityIndex + "_" + columnIndex; qb.setParameter(paramName, column.getEntityValue(entity)); return joinAliasName + "." + column.propertyPath + " = :" + paramName; }).join(" AND "); }).map(function (condition) { return "(" + condition + ")"; }).join(" OR "); qb.where(condition); } return qb.getMany(); // return qb.getOne(); todo: fix all usages }; /** * Loads data for one-to-many and one-to-one not owner relations. * * SELECT post * FROM post post * WHERE post.[joinColumn.name] = entity[joinColumn.referencedColumn] */ RelationLoader.prototype.loadOneToManyOrOneToOneNotOwner = function (relation, entityOrEntities, queryRunner) { var entities = Array.isArray(entityOrEntities) ? entityOrEntities : [entityOrEntities]; var aliasName = relation.propertyName; var columns = relation.inverseRelation.joinColumns; var qb = this.connection .createQueryBuilder(queryRunner) .select(aliasName) .from(relation.inverseRelation.entityMetadata.target, aliasName); if (columns.length === 1) { qb.where(aliasName + "." + columns[0].propertyPath + " IN (:..." + (aliasName + "_" + columns[0].propertyName) + ")"); qb.setParameter(aliasName + "_" + columns[0].propertyName, entities.map(function (entity) { return columns[0].referencedColumn.getEntityValue(entity); })); } else { var condition = entities.map(function (entity, entityIndex) { return columns.map(function (column, columnIndex) { var paramName = aliasName + "_entity_" + entityIndex + "_" + columnIndex; qb.setParameter(paramName, column.referencedColumn.getEntityValue(entity)); return aliasName + "." + column.propertyPath + " = :" + paramName; }).join(" AND "); }).map(function (condition) { return "(" + condition + ")"; }).join(" OR "); qb.where(condition); } return qb.getMany(); // return relation.isOneToMany ? qb.getMany() : qb.getOne(); todo: fix all usages }; /** * Loads data for many-to-many owner relations. * * SELECT category * FROM category category * INNER JOIN post_categories post_categories * ON post_categories.postId = :postId * AND post_categories.categoryId = category.id */ RelationLoader.prototype.loadManyToManyOwner = function (relation, entityOrEntities, queryRunner) { var entities = Array.isArray(entityOrEntities) ? entityOrEntities : [entityOrEntities]; var mainAlias = relation.propertyName; var joinAlias = relation.junctionEntityMetadata.tableName; var joinColumnConditions = relation.joinColumns.map(function (joinColumn) { return joinAlias + "." + joinColumn.propertyName + " IN (:..." + joinColumn.propertyName + ")"; }); var inverseJoinColumnConditions = relation.inverseJoinColumns.map(function (inverseJoinColumn) { return joinAlias + "." + inverseJoinColumn.propertyName + "=" + mainAlias + "." + inverseJoinColumn.referencedColumn.propertyName; }); var parameters = relation.joinColumns.reduce(function (parameters, joinColumn) { parameters[joinColumn.propertyName] = entities.map(function (entity) { return joinColumn.referencedColumn.getEntityValue(entity); }); return parameters; }, {}); return this.connection .createQueryBuilder(queryRunner) .select(mainAlias) .from(relation.type, mainAlias) .innerJoin(joinAlias, joinAlias, tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(joinColumnConditions)), tslib_1.__read(inverseJoinColumnConditions)).join(" AND ")) .setParameters(parameters) .getMany(); }; /** * Loads data for many-to-many not owner relations. * * SELECT post * FROM post post * INNER JOIN post_categories post_categories * ON post_categories.postId = post.id * AND post_categories.categoryId = post_categories.categoryId */ RelationLoader.prototype.loadManyToManyNotOwner = function (relation, entityOrEntities, queryRunner) { var entities = Array.isArray(entityOrEntities) ? entityOrEntities : [entityOrEntities]; var mainAlias = relation.propertyName; var joinAlias = relation.junctionEntityMetadata.tableName; var joinColumnConditions = relation.inverseRelation.joinColumns.map(function (joinColumn) { return joinAlias + "." + joinColumn.propertyName + " = " + mainAlias + "." + joinColumn.referencedColumn.propertyName; }); var inverseJoinColumnConditions = relation.inverseRelation.inverseJoinColumns.map(function (inverseJoinColumn) { return joinAlias + "." + inverseJoinColumn.propertyName + " IN (:..." + inverseJoinColumn.propertyName + ")"; }); var parameters = relation.inverseRelation.inverseJoinColumns.reduce(function (parameters, joinColumn) { parameters[joinColumn.propertyName] = entities.map(function (entity) { return joinColumn.referencedColumn.getEntityValue(entity); }); return parameters; }, {}); return this.connection .createQueryBuilder(queryRunner) .select(mainAlias) .from(relation.type, mainAlias) .innerJoin(joinAlias, joinAlias, tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(joinColumnConditions)), tslib_1.__read(inverseJoinColumnConditions)).join(" AND ")) .setParameters(parameters) .getMany(); }; /** * Wraps given entity and creates getters/setters for its given relation * to be able to lazily load data when accessing this relation. */ RelationLoader.prototype.enableLazyLoad = function (relation, entity, queryRunner) { var relationLoader = this; var dataIndex = "__" + relation.propertyName + "__"; // in what property of the entity loaded data will be stored var promiseIndex = "__promise_" + relation.propertyName + "__"; // in what property of the entity loading promise will be stored var resolveIndex = "__has_" + relation.propertyName + "__"; // indicates if relation data already was loaded or not, we need this flag if loaded data is empty var setData = function (entity, value) { entity[dataIndex] = value; entity[resolveIndex] = true; delete entity[promiseIndex]; return value; }; var setPromise = function (entity, value) { delete entity[resolveIndex]; delete entity[dataIndex]; entity[promiseIndex] = value; value.then( // ensure different value is not assigned yet function (result) { return entity[promiseIndex] === value ? setData(entity, result) : result; }); return value; }; Object.defineProperty(entity, relation.propertyName, { get: function () { if (this[resolveIndex] === true || this[dataIndex] !== undefined) // if related data already was loaded then simply return it return Promise.resolve(this[dataIndex]); if (this[promiseIndex]) // if related data is loading then return a promise relationLoader loads it return this[promiseIndex]; // nothing is loaded yet, load relation data and save it in the model once they are loaded var loader = relationLoader.load(relation, this, queryRunner).then(function (result) { return relation.isOneToOne || relation.isManyToOne ? (result.length === 0 ? null : result[0]) : result; }); return setPromise(this, loader); }, set: function (value) { if (value instanceof Promise) { // if set data is a promise then wait for its resolve and save in the object setPromise(this, value); } else { // if its direct data set (non promise, probably not safe-typed) setData(this, value); } }, configurable: true }); }; return RelationLoader; }()); exports.RelationLoader = RelationLoader; //# sourceMappingURL=RelationLoader.js.map