text: import { FindOptionsUtils } from "../find-options/FindOptionsUtils";
  /**
   * Wraps entities and creates getters/setters for their relations
   * to be able to lazily load relations when accessing these relations.
   */
  export class RelationLoader {
      // -------------------------------------------------------------------------
      // Constructor
      // -------------------------------------------------------------------------
      constructor(connection) {
          this.connection = connection;
      }
      // -------------------------------------------------------------------------
      // Public Methods
      // -------------------------------------------------------------------------
      /**
       * Loads relation data for the given entity and its relation.
       */
      load(relation, entityOrEntities, queryRunner, queryBuilder) {
          // todo: check all places where it uses non array
          if (queryRunner && queryRunner.isReleased)
              queryRunner = undefined; // get new one if already closed
          if (relation.isManyToOne || relation.isOneToOneOwner) {
              return this.loadManyToOneOrOneToOneOwner(relation, entityOrEntities, queryRunner, queryBuilder);
          }
          else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
              return this.loadOneToManyOrOneToOneNotOwner(relation, entityOrEntities, queryRunner, queryBuilder);
          }
          else if (relation.isManyToManyOwner) {
              return this.loadManyToManyOwner(relation, entityOrEntities, queryRunner, queryBuilder);
          }
          else {
              // many-to-many non owner
              return this.loadManyToManyNotOwner(relation, entityOrEntities, queryRunner, queryBuilder);
          }
      }
      /**
       * 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
       */
      loadManyToOneOrOneToOneOwner(relation, entityOrEntities, queryRunner, queryBuilder) {
          const entities = Array.isArray(entityOrEntities)
              ? entityOrEntities
              : [entityOrEntities];
          const joinAliasName = relation.entityMetadata.name;
          const qb = queryBuilder
              ? queryBuilder
              : this.connection
                  .createQueryBuilder(queryRunner)
                  .select(relation.propertyName) // category
                  .from(relation.type, relation.propertyName);
          const mainAlias = qb.expressionMap.mainAlias.name;
          const columns = relation.entityMetadata.primaryColumns;
          const joinColumns = relation.isOwning
              ? relation.joinColumns
              : relation.inverseRelation.joinColumns;
          const conditions = joinColumns
              .map((joinColumn) => {
              return `${relation.entityMetadata.name}.${joinColumn.propertyName} = ${mainAlias}.${joinColumn.referencedColumn.propertyName}`;
          })
              .join(" AND ");
          qb.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((entity) => columns[0].getEntityValue(entity, true)));
          }
          else {
              const condition = entities
                  .map((entity, entityIndex) => {
                  return columns
                      .map((column, columnIndex) => {
                      const paramName = joinAliasName +
                          "_entity_" +
                          entityIndex +
                          "_" +
                          columnIndex;
                      qb.setParameter(paramName, column.getEntityValue(entity, true));
                      return (joinAliasName +
                          "." +
                          column.propertyPath +
                          " = :" +
                          paramName);
                  })
                      .join(" AND ");
              })
                  .map((condition) => "(" + condition + ")")
                  .join(" OR ");
              qb.where(condition);
          }
          FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias.metadata);
          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]
       */
      loadOneToManyOrOneToOneNotOwner(relation, entityOrEntities, queryRunner, queryBuilder) {
          const entities = Array.isArray(entityOrEntities)
              ? entityOrEntities
              : [entityOrEntities];
          const columns = relation.inverseRelation.joinColumns;
          const qb = queryBuilder
              ? queryBuilder
              : this.connection
                  .createQueryBuilder(queryRunner)
                  .select(relation.propertyName)
                  .from(relation.inverseRelation.entityMetadata.target, relation.propertyName);
          const aliasName = qb.expressionMap.mainAlias.name;
          if (columns.length === 1) {
              qb.where(`${aliasName}.${columns[0].propertyPath} IN (:...${aliasName + "_" + columns[0].propertyName})`);
              qb.setParameter(aliasName + "_" + columns[0].propertyName, entities.map((entity) => columns[0].referencedColumn.getEntityValue(entity, true)));
          }
          else {
              const condition = entities
                  .map((entity, entityIndex) => {
                  return columns
                      .map((column, columnIndex) => {
                      const paramName = aliasName +
                          "_entity_" +
                          entityIndex +
                          "_" +
                          columnIndex;
                      qb.setParameter(paramName, column.referencedColumn.getEntityValue(entity, true));
                      return (aliasName +
                          "." +
                          column.propertyPath +
                          " = :" +
                          paramName);
                  })
                      .join(" AND ");
              })
                  .map((condition) => "(" + condition + ")")
                  .join(" OR ");
              qb.where(condition);
          }
          FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias.metadata);
          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
       */
      loadManyToManyOwner(relation, entityOrEntities, queryRunner, queryBuilder) {
          const entities = Array.isArray(entityOrEntities)
              ? entityOrEntities
              : [entityOrEntities];
          const parameters = relation.joinColumns.reduce((parameters, joinColumn) => {
              parameters[joinColumn.propertyName] = entities.map((entity) => joinColumn.referencedColumn.getEntityValue(entity, true));
              return parameters;
          }, {});
          const qb = queryBuilder
              ? queryBuilder
              : this.connection
                  .createQueryBuilder(queryRunner)
                  .select(relation.propertyName)
                  .from(relation.type, relation.propertyName);
          const mainAlias = qb.expressionMap.mainAlias.name;
          const joinAlias = relation.junctionEntityMetadata.tableName;
          const joinColumnConditions = relation.joinColumns.map((joinColumn) => {
              return `${joinAlias}.${joinColumn.propertyName} IN (:...${joinColumn.propertyName})`;
          });
          const inverseJoinColumnConditions = relation.inverseJoinColumns.map((inverseJoinColumn) => {
              return `${joinAlias}.${inverseJoinColumn.propertyName}=${mainAlias}.${inverseJoinColumn.referencedColumn.propertyName}`;
          });
          qb.innerJoin(joinAlias, joinAlias, [...joinColumnConditions, ...inverseJoinColumnConditions].join(" AND ")).setParameters(parameters);
          FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias.metadata);
          return qb.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
       */
      loadManyToManyNotOwner(relation, entityOrEntities, queryRunner, queryBuilder) {
          const entities = Array.isArray(entityOrEntities)
              ? entityOrEntities
              : [entityOrEntities];
          const qb = queryBuilder
              ? queryBuilder
              : this.connection
                  .createQueryBuilder(queryRunner)
                  .select(relation.propertyName)
                  .from(relation.type, relation.propertyName);
          const mainAlias = qb.expressionMap.mainAlias.name;
          const joinAlias = relation.junctionEntityMetadata.tableName;
          const joinColumnConditions = relation.inverseRelation.joinColumns.map((joinColumn) => {
              return `${joinAlias}.${joinColumn.propertyName} = ${mainAlias}.${joinColumn.referencedColumn.propertyName}`;
          });
          const inverseJoinColumnConditions = relation.inverseRelation.inverseJoinColumns.map((inverseJoinColumn) => {
              return `${joinAlias}.${inverseJoinColumn.propertyName} IN (:...${inverseJoinColumn.propertyName})`;
          });
          const parameters = relation.inverseRelation.inverseJoinColumns.reduce((parameters, joinColumn) => {
              parameters[joinColumn.propertyName] = entities.map((entity) => joinColumn.referencedColumn.getEntityValue(entity, true));
              return parameters;
          }, {});
          qb.innerJoin(joinAlias, joinAlias, [...joinColumnConditions, ...inverseJoinColumnConditions].join(" AND ")).setParameters(parameters);
          FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias.metadata);
          return qb.getMany();
      }
      /**
       * Wraps given entity and creates getters/setters for its given relation
       * to be able to lazily load data when accessing this relation.
       */
      enableLazyLoad(relation, entity, queryRunner) {
          const relationLoader = this;
          const dataIndex = "__" + relation.propertyName + "__"; // in what property of the entity loaded data will be stored
          const promiseIndex = "__promise_" + relation.propertyName + "__"; // in what property of the entity loading promise will be stored
          const resolveIndex = "__has_" + relation.propertyName + "__"; // indicates if relation data already was loaded or not, we need this flag if loaded data is empty
          const setData = (entity, value) => {
              entity[dataIndex] = value;
              entity[resolveIndex] = true;
              delete entity[promiseIndex];
              return value;
          };
          const setPromise = (entity, value) => {
              delete entity[resolveIndex];
              delete entity[dataIndex];
              entity[promiseIndex] = value;
              value.then(
              // ensure different value is not assigned yet
              (result) => 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
                  const loader = relationLoader
                      .load(relation, this, queryRunner)
                      .then((result) => 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,
          });
      }
  }
  
  //# sourceMappingURL=RelationLoader.js.map
  