All Downloads are FREE. Search and download functionalities are using the official Maven repository.

skinny.orm.feature.AssociationsFeature.scala Maven / Gradle / Ivy

The newest version!
package skinny.orm.feature

import scala.language.existentials

import skinny.orm._
import skinny.orm.feature.associations._
import scalikejdbc._, SQLInterpolation._
import scala.collection.mutable
import skinny.util.JavaReflectAPI
import skinny.orm.feature.includes.IncludesQueryRepository
import skinny.orm.exception.AssociationSettingsException

object AssociationsFeature {

  def defaultIncludesMerge[Entity, A] = (es: Seq[Entity], as: Seq[A]) =>
    throw new AssociationSettingsException(
      """
        |--------- Invalid Association Settings ---------
        |
        |  Merge function for includes query is required.
        |
        |  e.g.
        |
        |  val company = belongsTo[Company](Company, (e, c) => e.copy(company = c))
        |    .includes[Company]((es, cs) => es.map { e =>
        |      cs.find(c => e.exists(_.id == c.id)).map(v => e.copy(company = Some(v))).getOrElse(e)
        |    }
        |
        |-------------------------------------------------
        |""".stripMargin)

}

/**
 * Associations support feature.
 *
 * @tparam Entity entity
 */
trait AssociationsFeature[Entity]
    extends SkinnyMapperBase[Entity]
    with ConnectionPoolFeature
    with AutoSessionFeature {

  import AssociationsFeature._

  /**
   * Associations
   */
  val associations = new mutable.LinkedHashSet[Association[_]]

  /**
   * Join definitions that are enabled by default.
   */
  val defaultJoinDefinitions = new mutable.LinkedHashSet[JoinDefinition[_]]()

  private def unshiftJoinDefinition(newOne: JoinDefinition[_], definitions: mutable.LinkedHashSet[JoinDefinition[_]]) = {
    val newDefinitions = new mutable.LinkedHashSet[JoinDefinition[_]]()
    newDefinitions.add(newOne)
    newDefinitions ++= definitions
  }

  // ----------------------
  // Join Definition
  // ----------------------

  /**
   * Creates a new join definition.
   *
   * @param joinType join type
   * @param left left mapper and table alias
   * @param right right mapper and table alias
   * @param on join condition
   * @return join definition
   */
  def createJoinDefinition(joinType: JoinType, left: (AssociationsFeature[_], Alias[_]), right: (AssociationsFeature[_], Alias[_]), on: SQLSyntax): JoinDefinition[Entity] = {
    val (leftMapper, leftAlias) = left
    val (rightMapper, rightAlias) = right
    JoinDefinition[Entity](
      joinType,
      this,
      leftMapper.asInstanceOf[AssociationsFeature[Any]],
      leftAlias.asInstanceOf[Alias[Any]],
      rightMapper.asInstanceOf[AssociationsFeature[Any]],
      rightAlias.asInstanceOf[Alias[Any]],
      on
    )
  }

  // ----------------------
  // Inner Join Definition
  // ----------------------

  // using default alias

  def joinWithDefaults(right: AssociationsFeature[_], on: SQLSyntax): JoinDefinition[Entity] = {
    innerJoinWithDefaults(right, on)
  }
  def joinWithDefaults(right: AssociationsFeature[_], on: (Alias[Entity], Alias[Any]) => SQLSyntax): JoinDefinition[Entity] = {
    innerJoinWithDefaults(right, on)
  }
  def joinWithDefaults[Left](left: AssociationsFeature[Left], right: AssociationsFeature[_], on: (Alias[Left], Alias[_]) => SQLSyntax): JoinDefinition[Entity] = {
    innerJoinWithDefaults[Left](left, right, on)
  }

  def innerJoinWithDefaults(right: AssociationsFeature[_], on: SQLSyntax): JoinDefinition[Entity] = {
    createJoinDefinition(InnerJoin, this -> this.defaultAlias, right -> right.defaultAlias, on)
  }
  def innerJoinWithDefaults(right: AssociationsFeature[_], on: (Alias[Entity], Alias[Any]) => SQLSyntax): JoinDefinition[Entity] = {
    createJoinDefinition(InnerJoin, this -> this.defaultAlias, right -> right.defaultAlias, on.apply(this.defaultAlias, right.defaultAlias.asInstanceOf[Alias[Any]]))
  }
  def innerJoinWithDefaults[Left](left: AssociationsFeature[Left], right: AssociationsFeature[_], on: (Alias[Left], Alias[_]) => SQLSyntax): JoinDefinition[Entity] = {
    createJoinDefinition(InnerJoin, left -> left.defaultAlias, right -> right.defaultAlias, on.apply(left.defaultAlias, right.defaultAlias))
  }

  // using specified alias

  def join(right: (AssociationsFeature[_], Alias[_]), on: (Alias[Entity], Alias[_]) => SQLSyntax): JoinDefinition[Entity] = {
    innerJoin(right, on)
  }
  def join[Left](left: (AssociationsFeature[Left], Alias[Left]), right: (AssociationsFeature[_], Alias[_]), on: (Alias[Left], Alias[_]) => SQLSyntax): JoinDefinition[Entity] = {
    innerJoin(left, right, on)
  }
  def innerJoin(right: (AssociationsFeature[_], Alias[_]), on: (Alias[Entity], Alias[_]) => SQLSyntax): JoinDefinition[Entity] = {
    createJoinDefinition(InnerJoin, this -> this.defaultAlias, right, on.apply(this.defaultAlias, right._2))
  }
  def innerJoin[Left](left: (AssociationsFeature[Left], Alias[Left]), right: (AssociationsFeature[_], Alias[_]), on: (Alias[Left], Alias[_]) => SQLSyntax): JoinDefinition[Entity] = {
    createJoinDefinition(InnerJoin, left, right, on.apply(left._2, right._2))
  }

  // ----------------------
  // Left Outer Join Definitions
  // ----------------------

  // using default alias

  def leftJoinWithDefaults(right: AssociationsFeature[_], on: SQLSyntax): JoinDefinition[_] = {
    createJoinDefinition(LeftOuterJoin, this -> this.defaultAlias, right -> right.defaultAlias, on)
  }
  def leftJoinWithDefaults(right: AssociationsFeature[_], on: (Alias[Entity], Alias[Any]) => SQLSyntax): JoinDefinition[_] = {
    createJoinDefinition(LeftOuterJoin, this -> this.defaultAlias, right -> right.defaultAlias, on.apply(this.defaultAlias, right.defaultAlias.asInstanceOf[Alias[Any]]))
  }
  def leftJoinWithDefaults(left: AssociationsFeature[_], right: AssociationsFeature[_], on: (Alias[_], Alias[_]) => SQLSyntax): JoinDefinition[_] = {
    createJoinDefinition(LeftOuterJoin, left -> left.defaultAlias, right -> right.defaultAlias, on.apply(left.defaultAlias, right.defaultAlias))
  }

  // using specified alias

  def leftJoin(right: (AssociationsFeature[_], Alias[_]), on: (Alias[Entity], Alias[_]) => SQLSyntax): JoinDefinition[_] = {
    createJoinDefinition(LeftOuterJoin, this -> this.defaultAlias, right, on.apply(this.defaultAlias, right._2))
  }
  def leftJoin(left: (AssociationsFeature[_], Alias[_]), right: (AssociationsFeature[_], Alias[_]), on: (Alias[_], Alias[_]) => SQLSyntax): JoinDefinition[_] = {
    createJoinDefinition(LeftOuterJoin, left, right, on.apply(left._2, right._2))
  }

  // ----------------------
  // One-to-one
  // ----------------------

  // belongs-to

  def setAsByDefault(extractor: BelongsToExtractor[Entity]): Unit = {
    extractor.byDefault = true
    defaultBelongsToExtractors.add(extractor)
  }

  def belongsTo[A](right: AssociationsFeature[A], merge: (Entity, Option[A]) => Entity): BelongsToAssociation[Entity] = {
    val fk = toDefaultForeignKeyName[A](right)
    belongsToWithJoinCondition[A](right, sqls.eq(this.defaultAlias.field(fk), right.defaultAlias.field(right.primaryKeyName)), merge)
  }

  def belongsToWithJoinCondition[A](right: AssociationsFeature[A], on: SQLSyntax, merge: (Entity, Option[A]) => Entity): BelongsToAssociation[Entity] = {
    val joinDef = leftJoinWithDefaults(right, on)
    val extractor = extractBelongsTo[A](right, toDefaultForeignKeyName[A](right), right.defaultAlias, merge)
    new BelongsToAssociation[Entity](this, unshiftJoinDefinition(joinDef, right.defaultJoinDefinitions.filter(_.enabledEvenIfAssociated)), extractor)
  }

  def belongsToWithFk[A](right: AssociationsFeature[A], fk: String, merge: (Entity, Option[A]) => Entity): BelongsToAssociation[Entity] = {
    belongsToWithFkAndJoinCondition(right, fk, sqls.eq(this.defaultAlias.field(fk), right.defaultAlias.field(right.primaryKeyName)), merge)
  }

  def belongsToWithFkAndJoinCondition[A](right: AssociationsFeature[A], fk: String, on: SQLSyntax, merge: (Entity, Option[A]) => Entity): BelongsToAssociation[Entity] = {
    val joinDef = leftJoinWithDefaults(right, on)
    val extractor = extractBelongsTo[A](right, fk, right.defaultAlias, merge)
    new BelongsToAssociation[Entity](this, unshiftJoinDefinition(joinDef, right.defaultJoinDefinitions.filter(_.enabledEvenIfAssociated)), extractor)
  }

  def belongsToWithAlias[A](right: (AssociationsFeature[A], Alias[A]), merge: (Entity, Option[A]) => Entity): BelongsToAssociation[Entity] = {
    val fk = if (right._1.defaultAlias != right._2) {
      right._2.tableAliasName + "Id"
    } else {
      toDefaultForeignKeyName[A](right._1)
    }
    belongsToWithAliasAndFk(right, fk, merge)
  }

  def belongsToWithAliasAndFk[A](right: (AssociationsFeature[A], Alias[A]), fk: String,
    merge: (Entity, Option[A]) => Entity): BelongsToAssociation[Entity] = {
    belongsToWithAliasAndFkAndJoinCondition(right, fk, sqls.eq(this.defaultAlias.field(fk), right._2.field(right._1.primaryKeyName)), merge)
  }

  def belongsToWithAliasAndFkAndJoinCondition[A](right: (AssociationsFeature[A], Alias[A]), fk: String, on: SQLSyntax,
    merge: (Entity, Option[A]) => Entity): BelongsToAssociation[Entity] = {
    val joinDef = createJoinDefinition(LeftOuterJoin, this -> this.defaultAlias, right, on)
    val extractor = extractBelongsTo[A](right._1, fk, right._2, merge)
    new BelongsToAssociation[Entity](this, unshiftJoinDefinition(joinDef, right._1.defaultJoinDefinitions.filter(_.enabledEvenIfAssociated)), extractor)
  }

  // has-one

  def setAsByDefault(extractor: HasOneExtractor[Entity]): Unit = {
    extractor.byDefault = true
    defaultHasOneExtractors.add(extractor)
  }

  def hasOne[A](right: AssociationsFeature[A], merge: (Entity, Option[A]) => Entity): HasOneAssociation[Entity] = {
    hasOneWithFk[A](right, toDefaultForeignKeyName[Entity](this), merge)
  }

  def hasOneWithJoinCondition[A](right: AssociationsFeature[A], on: SQLSyntax, merge: (Entity, Option[A]) => Entity): HasOneAssociation[Entity] = {
    hasOneWithFkAndJoinCondition(right, toDefaultForeignKeyName[Entity](this), on, merge)
  }

  def hasOneWithFk[A](right: AssociationsFeature[A], fk: String, merge: (Entity, Option[A]) => Entity): HasOneAssociation[Entity] = {
    hasOneWithFkAndJoinCondition(right, fk, sqls.eq(this.defaultAlias.field(this.primaryKeyName), right.defaultAlias.field(fk)), merge)
  }

  def hasOneWithFkAndJoinCondition[A](right: AssociationsFeature[A], fk: String, on: SQLSyntax, merge: (Entity, Option[A]) => Entity): HasOneAssociation[Entity] = {
    val joinDef = leftJoinWithDefaults(right, on)
    val extractor = extractHasOne[A](right, fk, right.defaultAlias, merge)
    new HasOneAssociation[Entity](this, unshiftJoinDefinition(joinDef, right.defaultJoinDefinitions.filter(_.enabledEvenIfAssociated)), extractor)
  }

  def hasOneWithAlias[A](right: (AssociationsFeature[A], Alias[A]), merge: (Entity, Option[A]) => Entity): HasOneAssociation[Entity] = {
    hasOneWithAliasAndFk(right, toDefaultForeignKeyName[Entity](this), merge)
  }

  def hasOneWithAliasAndJoinCondition[A](right: (AssociationsFeature[A], Alias[A]), on: SQLSyntax, merge: (Entity, Option[A]) => Entity): HasOneAssociation[Entity] = {
    hasOneWithAliasAndFkAndJoinCondition(right, toDefaultForeignKeyName[Entity](this), on, merge)
  }

  def hasOneWithAliasAndFk[A](right: (AssociationsFeature[A], Alias[A]), fk: String, merge: (Entity, Option[A]) => Entity): HasOneAssociation[Entity] = {
    hasOneWithAliasAndFkAndJoinCondition(right, fk, sqls.eq(this.defaultAlias.field(this.primaryKeyName), right._2.field(fk)), merge)
  }

  def hasOneWithAliasAndFkAndJoinCondition[A](right: (AssociationsFeature[A], Alias[A]), fk: String, on: SQLSyntax, merge: (Entity, Option[A]) => Entity): HasOneAssociation[Entity] = {
    val joinDef = createJoinDefinition(LeftOuterJoin, this -> this.defaultAlias, right, on)
    val extractor = extractHasOne[A](right._1, fk, right._2, merge)
    new HasOneAssociation[Entity](this, unshiftJoinDefinition(joinDef, right._1.defaultJoinDefinitions.filter(_.enabledEvenIfAssociated)), extractor)
  }

  // ----------------------
  // One-to-many
  // ----------------------

  // has-many

  def setAsByDefault(extractor: HasManyExtractor[Entity]): Unit = {
    extractor.byDefault = true
    defaultOneToManyExtractors.add(extractor)
  }

  def hasMany[M](many: (AssociationsFeature[M], Alias[M]), on: (Alias[Entity], Alias[M]) => SQLSyntax, merge: (Entity, Seq[M]) => Entity): HasManyAssociation[Entity] = {
    val joinDef = leftJoin(this -> this.defaultAlias, many, on.asInstanceOf[(Alias[_], Alias[_]) => SQLSyntax])
    val extractor = extractOneToMany[M](many._1, many._2, merge)
    new HasManyAssociation[Entity](this, (many._1.defaultJoinDefinitions + joinDef), extractor)
  }

  def hasManyThrough[M2](through: AssociationsFeature[_], many: AssociationsFeature[M2], merge: (Entity, Seq[M2]) => Entity): HasManyAssociation[Entity] = {
    val throughFk = toDefaultForeignKeyName[Entity](this)
    val manyFk = toDefaultForeignKeyName[M2](many)
    hasManyThrough(
      through = through.asInstanceOf[AssociationsFeature[Any]] -> through.defaultAlias.asInstanceOf[Alias[Any]],
      throughOn = (entity, m1: Alias[_]) => sqls.eq(entity.field(primaryKeyName), m1.field(throughFk)),
      many = many -> many.defaultAlias,
      on = (m1: Alias[_], m2: Alias[M2]) => sqls.eq(m1.field(manyFk), m2.field(many.primaryKeyName)),
      merge = merge)
  }

  def hasManyThroughWithFk[M2](through: AssociationsFeature[_], many: AssociationsFeature[M2], throughFk: String, manyFk: String, merge: (Entity, Seq[M2]) => Entity): HasManyAssociation[Entity] = {
    hasManyThrough(
      through = through.asInstanceOf[AssociationsFeature[Any]] -> through.defaultAlias.asInstanceOf[Alias[Any]],
      throughOn = (entity, m1: Alias[_]) => sqls.eq(entity.field(primaryKeyName), m1.field(throughFk)),
      many = many -> many.defaultAlias,
      on = (m1: Alias[_], m2: Alias[M2]) => sqls.eq(m1.field(manyFk), m2.field(many.primaryKeyName)),
      merge = merge)
  }

  def hasManyThrough[M1, M2](
    through: (AssociationsFeature[M1], Alias[M1]),
    throughOn: (Alias[Entity], Alias[M1]) => SQLSyntax,
    many: (AssociationsFeature[M2], Alias[M2]),
    on: (Alias[M1], Alias[M2]) => SQLSyntax,
    merge: (Entity, Seq[M2]) => Entity): HasManyAssociation[Entity] = {

    val joinDef1 = leftJoin(through, throughOn.asInstanceOf[(Alias[_], Alias[_]) => SQLSyntax])
    val joinDef2 = leftJoin(through, many, on.asInstanceOf[(Alias[_], Alias[_]) => SQLSyntax])
    val extractor = extractOneToMany[M2](many._1, many._2, merge)
    new HasManyAssociation[Entity](this, (many._1.defaultJoinDefinitions + joinDef1 + joinDef2), extractor)
  }

  // ----------------------
  // Query Builder
  // ----------------------

  /**
   * Returns a select query builder that all associations are joined.
   *
   * @param sql sql object
   * @param belongsToAssociations belongsTo associations
   * @param hasOneAssociations hasOne associations
   * @param hasManyAssociations hasMany associations
   * @return select query builder
   */
  def selectQueryWithAdditionalAssociations(
    sql: SelectSQLBuilder[Entity],
    belongsToAssociations: Set[BelongsToAssociation[Entity]],
    hasOneAssociations: Set[HasOneAssociation[Entity]],
    hasManyAssociations: Set[HasManyAssociation[Entity]]): SelectSQLBuilder[Entity] = {

    val mergedJoinDefinitions = (belongsToAssociations.flatMap(_.joinDefinitions)
      ++ hasOneAssociations.flatMap(_.joinDefinitions)
      ++ hasManyAssociations.flatMap(_.joinDefinitions)
    ).filterNot { df =>
        val currentName = df.rightAlias.tableAliasName
        val sameAsThis = this.defaultAlias.tableAliasName == currentName
        val foundInDefaults = defaultJoinDefinitions.exists(d =>
          d.leftAlias.tableAliasName == currentName || d.rightAlias.tableAliasName == currentName)
        foundInDefaults || sameAsThis
      }.foldLeft(mutable.LinkedHashSet[JoinDefinition[_]]()) { (dfs, df) =>
        val currentName = df.rightAlias.tableAliasName
        val duplicated = dfs.exists(d =>
          d.leftAlias.tableAliasName == currentName || d.rightAlias.tableAliasName == currentName)
        if (duplicated) dfs else dfs + df
      }

    mergedJoinDefinitions.foldLeft(sql) { (query, join) =>
      join.joinType match {
        case InnerJoin => query.innerJoin(join.rightMapper.as(join.rightAlias)).on(join.on)
        case LeftOuterJoin => query.leftJoin(join.rightMapper.as(join.rightAlias)).on(join.on)
        case jt => throw new IllegalStateException(s"Unexpected pattern ${jt}")
      }
    }
  }

  /**
   * Returns th default select query builder for this mapper.
   *
   * @return select query builder
   */
  override def defaultSelectQuery: SelectSQLBuilder[Entity] = {
    // Notice: LinkedHashSet because elements in the order they were inserted
    val definitions = defaultJoinDefinitions.foldLeft(mutable.LinkedHashSet[JoinDefinition[_]]()) { (dfs, df) =>
      val currentName = df.rightAlias.tableAliasName
      val duplicated = dfs.exists(d =>
        d.leftAlias.tableAliasName == currentName || d.rightAlias.tableAliasName == currentName)
      if (duplicated) dfs else dfs + df
    }
    definitions.foldLeft(super.defaultSelectQuery) { (query, join) =>
      join.joinType match {
        case InnerJoin if join.enabledByDefault => query.innerJoin(join.rightMapper.as(join.rightAlias)).on(join.on)
        case LeftOuterJoin if join.enabledByDefault => query.leftJoin(join.rightMapper.as(join.rightAlias)).on(join.on)
        case _ => query
      }
    }
  }

  // ----------------------
  // ResultSet Extractor
  // ----------------------

  def extract(sql: SQL[Entity, NoExtractor])(
    implicit includesRepository: IncludesQueryRepository[Entity] = IncludesQueryRepository[Entity]()): SQL[Entity, HasExtractor] = {

    val _belongsTo = associations.filter(_.isInstanceOf[BelongsToAssociation[Entity]]).map(_.asInstanceOf[BelongsToAssociation[Entity]])
    val _hasOne = associations.filter(_.isInstanceOf[HasOneAssociation[Entity]]).map(_.asInstanceOf[HasOneAssociation[Entity]])
    val _hasMany = associations.filter(_.isInstanceOf[HasManyAssociation[Entity]]).map(_.asInstanceOf[HasManyAssociation[Entity]])
    extractWithAssociations(sql, _belongsTo.toSet, _hasOne.toSet, _hasMany.toSet)
  }

  /**
   * Creates an extractor for this query.
   *
   * @param sql sql object
   * @param belongsToAssociations belongsTo associations
   * @param hasOneAssociations hasOne associations
   * @param oneToManyAssociations hasMany associations
   * @return sql object
   */
  def extractWithAssociations(
    sql: SQL[Entity, NoExtractor],
    belongsToAssociations: Set[BelongsToAssociation[Entity]],
    hasOneAssociations: Set[HasOneAssociation[Entity]],
    oneToManyAssociations: Set[HasManyAssociation[Entity]])(
      implicit includesRepository: IncludesQueryRepository[Entity] = IncludesQueryRepository[Entity]()): SQL[Entity, HasExtractor] = {

    val enabledJoinDefinitions = defaultJoinDefinitions
    val enabledOneToManyExtractors = defaultOneToManyExtractors ++ oneToManyAssociations.map(_.extractor)

    if (enabledJoinDefinitions.isEmpty) {
      sql.map(rs => extract(rs, defaultAlias.resultName))

    } else if (enabledOneToManyExtractors.size > 0) {

      val oneExtractedSql: OneToXSQL[Entity, NoExtractor, Entity] = sql.one(rs =>
        extractWithOneToOneTables(rs,
          belongsToAssociations.map(_.extractor).toSet,
          hasOneAssociations.map(_.extractor).toSet))

      if (enabledOneToManyExtractors.size == 1) {
        // one-to-many
        val ex: HasManyExtractor[Entity] = enabledOneToManyExtractors.head
        val mapper = ex.mapper.asInstanceOf[AssociationsFeature[Any]]
        val alias = ex.alias.asInstanceOf[Alias[Any]]
        val sql: OneToManySQL[Entity, _, HasExtractor, Entity] = oneExtractedSql
          .toMany { rs =>
            rs.longOpt(alias.resultName.field(mapper.primaryKeyName)).map[Any] { _ =>
              includesRepository.putAndReturn(ex, mapper.extract(rs, alias.resultName))
            }
          }.map { case (one, many) => ex.merge(one, many) }
        sql

      } else if (enabledOneToManyExtractors.size == 2) {
        // one-to-manies 2
        val Seq(ex1: HasManyExtractor[Entity], ex2: HasManyExtractor[Entity]) = enabledOneToManyExtractors.toSeq
        val mapper1 = ex1.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper2 = ex2.mapper.asInstanceOf[AssociationsFeature[Any]]
        val alias1 = ex1.alias.asInstanceOf[Alias[Any]]
        val alias2 = ex2.alias.asInstanceOf[Alias[Any]]
        val sql: OneToManies2SQL[Entity, _, _, HasExtractor, Entity] = oneExtractedSql
          .toManies(
            to1 = rs => rs.longOpt(alias1.resultName.field(mapper1.primaryKeyName)).map[Any] { _ =>
              includesRepository.putAndReturn(ex1, mapper1.extract(rs, alias1.resultName))
            },
            to2 = rs => rs.longOpt(alias2.resultName.field(mapper2.primaryKeyName)).map[Any] { _ =>
              includesRepository.putAndReturn(ex2, mapper2.extract(rs, alias2.resultName))
            }
          ).map { case (one, m1, m2) => ex2.merge(ex1.merge(one, m1), m2) }
        sql

      } else if (enabledOneToManyExtractors.size == 3) {
        // one-to-manies 3
        val Seq(ex1: HasManyExtractor[Entity], ex2: HasManyExtractor[Entity], ex3: HasManyExtractor[Entity]) = enabledOneToManyExtractors.toSeq
        val mapper1 = ex1.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper2 = ex2.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper3 = ex3.mapper.asInstanceOf[AssociationsFeature[Any]]
        val alias1 = ex1.alias.asInstanceOf[Alias[Any]]
        val alias2 = ex2.alias.asInstanceOf[Alias[Any]]
        val alias3 = ex3.alias.asInstanceOf[Alias[Any]]
        val sql: OneToManies3SQL[Entity, _, _, _, HasExtractor, Entity] = oneExtractedSql
          .toManies(
            to1 = rs => rs.longOpt(alias1.resultName.field(mapper1.primaryKeyName))
              .map[Any] { _ => includesRepository.putAndReturn(ex1, mapper1.extract(rs, alias1.resultName)) },
            to2 = rs => rs.longOpt(alias2.resultName.field(mapper2.primaryKeyName))
              .map[Any] { _ => includesRepository.putAndReturn(ex2, mapper2.extract(rs, alias2.resultName)) },
            to3 = rs => rs.longOpt(alias3.resultName.field(mapper3.primaryKeyName))
              .map[Any] { _ => includesRepository.putAndReturn(ex3, mapper3.extract(rs, alias3.resultName)) }

          ).map { case (one, m1, m2, m3) => ex3.merge(ex2.merge(ex1.merge(one, m1), m2), m3) }
        sql

      } else if (enabledOneToManyExtractors.size == 4) {
        // one-to-manies 4
        val Seq(ex1: HasManyExtractor[Entity], ex2: HasManyExtractor[Entity], ex3: HasManyExtractor[Entity], ex4: HasManyExtractor[Entity]) = enabledOneToManyExtractors.toSeq
        val mapper1 = ex1.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper2 = ex2.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper3 = ex3.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper4 = ex4.mapper.asInstanceOf[AssociationsFeature[Any]]
        val alias1 = ex1.alias.asInstanceOf[Alias[Any]]
        val alias2 = ex2.alias.asInstanceOf[Alias[Any]]
        val alias3 = ex3.alias.asInstanceOf[Alias[Any]]
        val alias4 = ex4.alias.asInstanceOf[Alias[Any]]
        val sql: OneToManies4SQL[Entity, _, _, _, _, HasExtractor, Entity] = oneExtractedSql
          .toManies(
            to1 = rs => rs.longOpt(alias1.resultName.field(mapper1.primaryKeyName))
              .map[Any](_ => includesRepository.putAndReturn(ex1, mapper1.extract(rs, alias1.resultName))),
            to2 = rs => rs.longOpt(alias2.resultName.field(mapper2.primaryKeyName))
              .map[Any](_ => includesRepository.putAndReturn(ex2, mapper2.extract(rs, alias2.resultName))),
            to3 = rs => rs.longOpt(alias3.resultName.field(mapper3.primaryKeyName))
              .map[Any](_ => includesRepository.putAndReturn(ex3, mapper3.extract(rs, alias3.resultName))),
            to4 = rs => rs.longOpt(alias4.resultName.field(mapper4.primaryKeyName))
              .map[Any](_ => includesRepository.putAndReturn(ex4, mapper4.extract(rs, alias4.resultName)))

          ).map { case (one, m1, m2, m3, m4) => ex4.merge(ex3.merge(ex2.merge(ex1.merge(one, m1), m2), m3), m4) }
        sql

      } else if (enabledOneToManyExtractors.size == 5) {
        // one-to-manies 5
        val Seq(ex1: HasManyExtractor[Entity], ex2: HasManyExtractor[Entity], ex3: HasManyExtractor[Entity], ex4: HasManyExtractor[Entity], ex5: HasManyExtractor[Entity]) = enabledOneToManyExtractors.toSeq
        val mapper1 = ex1.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper2 = ex2.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper3 = ex3.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper4 = ex4.mapper.asInstanceOf[AssociationsFeature[Any]]
        val mapper5 = ex5.mapper.asInstanceOf[AssociationsFeature[Any]]
        val alias1 = ex1.alias.asInstanceOf[Alias[Any]]
        val alias2 = ex2.alias.asInstanceOf[Alias[Any]]
        val alias3 = ex3.alias.asInstanceOf[Alias[Any]]
        val alias4 = ex4.alias.asInstanceOf[Alias[Any]]
        val alias5 = ex5.alias.asInstanceOf[Alias[Any]]
        val sql: OneToManies5SQL[Entity, _, _, _, _, _, HasExtractor, Entity] = oneExtractedSql
          .toManies(
            to1 = rs => rs.longOpt(alias1.resultName.field(mapper1.primaryKeyName))
              .map[Any](_ => includesRepository.putAndReturn(ex1, mapper1.extract(rs, alias1.resultName))),
            to2 = rs => rs.longOpt(alias2.resultName.field(mapper2.primaryKeyName))
              .map[Any](_ => includesRepository.putAndReturn(ex2, mapper2.extract(rs, alias2.resultName))),
            to3 = rs => rs.longOpt(alias3.resultName.field(mapper3.primaryKeyName))
              .map[Any](_ => includesRepository.putAndReturn(ex3, mapper3.extract(rs, alias3.resultName))),
            to4 = rs => rs.longOpt(alias4.resultName.field(mapper4.primaryKeyName))
              .map[Any](_ => includesRepository.putAndReturn(ex4, mapper4.extract(rs, alias4.resultName))),
            to5 = rs => rs.longOpt(alias5.resultName.field(mapper5.primaryKeyName))
              .map[Any](_ => includesRepository.putAndReturn(ex5, mapper5.extract(rs, alias5.resultName)))

          ).map { case (one, m1, m2, m3, m4, m5) => ex5.merge(ex4.merge(ex3.merge(ex2.merge(ex1.merge(one, m1), m2), m3), m4), m5) }
        sql

      } else {
        throw new IllegalStateException(s"Unsupported one-to-manies settings. (max: 5, actual: ${defaultOneToManyExtractors.size})")
      }

    } else {
      // several one-to-one and so on
      sql.map(rs => extractWithOneToOneTables(rs,
        belongsToAssociations.map(_.extractor).toSet,
        hasOneAssociations.map(_.extractor).toSet))
    }
  }

  /**
   * Extracts entity with one-to-one tables.
   *
   * @param rs result set
   * @param belongsToExtractors belongsTo extractors
   * @param hasOneExtractors hasOne extractors
   * @return entity
   */
  def extractWithOneToOneTables(
    rs: WrappedResultSet,
    belongsToExtractors: Set[BelongsToExtractor[Entity]],
    hasOneExtractors: Set[HasOneExtractor[Entity]])(implicit includesRepository: IncludesQueryRepository[Entity]): Entity = {

    val allBelongsTo = (defaultBelongsToExtractors ++ belongsToExtractors)
    val withBelongsTo = allBelongsTo.foldLeft(extract(rs, defaultAlias.resultName)) {
      case (entity, extractor) =>
        val mapper = extractor.mapper.asInstanceOf[AssociationsFeature[Any]]
        val toOne: Option[_] = rs.longOpt(this.defaultAlias.resultName.field(extractor.fk))
          .map { _ =>
            val entity = mapper.extract(rs, extractor.alias.resultName.asInstanceOf[ResultName[Any]])
            includesRepository.putAndReturn(extractor, entity)
          }
        extractor.merge(entity, toOne)
    }
    val allHasOne = (defaultHasOneExtractors ++ hasOneExtractors)
    val withAssociations = allHasOne.foldLeft(withBelongsTo) {
      case (entity, extractor) =>
        val mapper = extractor.mapper.asInstanceOf[AssociationsFeature[Any]]
        val toOne: Option[_] = rs.longOpt(extractor.alias.resultName.field(extractor.fk))
          .map { _ =>
            val entity = mapper.extract(rs, extractor.alias.resultName.asInstanceOf[ResultName[Any]])
            includesRepository.putAndReturn(extractor, entity)
          }
        extractor.merge(entity, toOne)
    }
    withAssociations
  }

  // -----------------------------------------
  // One to One Relation
  // -----------------------------------------

  val defaultBelongsToExtractors = new mutable.LinkedHashSet[BelongsToExtractor[Entity]]()

  def extractBelongsTo[That](mapper: AssociationsFeature[That], fk: String, alias: Alias[That], merge: (Entity, Option[That]) => Entity, includesMerge: (Seq[Entity], Seq[That]) => Seq[Entity] = defaultIncludesMerge[Entity, That]): BelongsToExtractor[Entity] = {
    BelongsToExtractor[Entity](mapper, fk, alias, merge.asInstanceOf[(Entity, Option[Any]) => Entity], includesMerge.asInstanceOf[(Seq[Entity], Seq[Any]) => Seq[Entity]])
  }

  val defaultHasOneExtractors = new mutable.LinkedHashSet[HasOneExtractor[Entity]]()

  def extractHasOne[That](mapper: AssociationsFeature[That], fk: String, alias: Alias[That], merge: (Entity, Option[That]) => Entity, includesMerge: (Seq[Entity], Seq[That]) => Seq[Entity] = defaultIncludesMerge[Entity, That]): HasOneExtractor[Entity] = {
    HasOneExtractor[Entity](mapper, fk, alias, merge.asInstanceOf[(Entity, Option[Any]) => Entity], includesMerge.asInstanceOf[(Seq[Entity], Seq[Any]) => Seq[Entity]])
  }

  // -----------------------------------------
  // One to Many Relation
  // -----------------------------------------

  val defaultOneToManyExtractors = new mutable.LinkedHashSet[HasManyExtractor[Entity]]()

  /**
   * One-to-Many relationship definition.
   *
   * {{{
   * object Member extends RelationshipFeature[Member] {
   *   oneToMany[Group](
   *     mapper = Group,
   *     merge = (m, c) => m.copy(company = c)
   *   )
   * }
   * }}}
   */
  def extractOneToManyWithDefaults[M1](mapper: AssociationsFeature[M1], merge: (Entity, Seq[M1]) => Entity, includesMerge: (Seq[Entity], Seq[M1]) => Seq[Entity] = defaultIncludesMerge[Entity, M1]): HasManyExtractor[Entity] = {
    extractOneToMany[M1](mapper, mapper.defaultAlias, merge, includesMerge)
  }

  def extractOneToMany[M1](mapper: AssociationsFeature[M1], alias: Alias[M1], merge: (Entity, Seq[M1]) => Entity, includesMerge: (Seq[Entity], Seq[M1]) => Seq[Entity] = defaultIncludesMerge[Entity, M1]): HasManyExtractor[Entity] = {
    if (defaultOneToManyExtractors.size > 5) {
      throw new IllegalStateException("Skinny ORM doesn't support more than 5 one-to-many tables.")
    }
    HasManyExtractor[Entity](
      mapper = mapper,
      alias = alias,
      merge = merge.asInstanceOf[(Entity, Seq[Any]) => Entity],
      includesMerge = includesMerge.asInstanceOf[(Seq[Entity], Seq[Any]) => Seq[Entity]])
  }

  /**
   * Expects mapper's name + "Id" by default.
   *
   * @param mapper mapper
   * @tparam A enitty type
   * @return fk name
   */
  protected def toDefaultForeignKeyName[A](mapper: AssociationsFeature[A]): String = {
    val name = JavaReflectAPI.classSimpleName(mapper).replaceFirst("\\$$", "") + "Id"
    name.head.toString.toLowerCase + name.tail
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy