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

rcumflex.circumflex-orm.3.0-RC1.source-code.node.scala Maven / Gradle / Ivy

The newest version!
package pro.savant.circumflex
package orm

/*!# Relation Nodes

The `RelationNode` is essentially a wrapper around relation which provides
an `alias` so that it can participate in complex SQL query plans.

Generally a relation node is created implicitly from `Relation`.
A special alias `this` is assigned to new relation nodes, it is transformed
inside queries into a query-unique alias so that no collisions occur.

You may assign an alias explicitly using the `AS` method.
*/
class RelationNode[PK, R <: Record[PK, R]](val relation: Relation[PK, R])
    extends SQLable with Cloneable with Equals {

  protected var _alias: String = "this"
  def alias = _alias
  def AS(alias: String): this.type = {
    this._alias = alias
    this
  }

  /*! Relation nodes allow Scala-like syntactic sugar by using the `map` method.
  Following example gives table `City` an alias `ci` and uses it to construct a
  `Criteria` object.

  ``` {.scala}
  (City AS "ci").map(ci => ci.criteria.add(ci.name LIKE "Lausanne")).list
  ```
   */
  def map[T](f: RelationNode[PK, R] => T): T = f(this)

  /*! The `*` method creates a `RecordProjection` from this node and is
  widely used in the `SELECT` clause of SQL queries.
   */
  def * = new RecordProjection[PK, R](this)

  /*! Each relation node defines which projection it yields.
  When the nodes are joined the resulting node contains projections
  from both nodes.
   */
  def projections: Seq[Projection[_]] = List(*)

  /*! Creates new `Criteria` instance with this node as its query plan root. */
  def criteria = new Criteria[PK, R](this)


  def findAssociation[T, F <: Record[T, F]]
  (node: RelationNode[T, F]): Option[Association[T, R, F]] =
    this.relation.findAssociation(node.relation)

  /*! Relation nodes can be joined to allow building criteria based on
  associated relations. */
  def JOIN[T, J <: Record[T, J]]
  (node: RelationNode[T, J],
   on: Expression,
   joinType: JoinType): JoinNode[PK, R, T, J] =
    new JoinNode(this, node, joinType).ON(on)

  def JOIN[T, J <: Record[T, J]]
  (node: RelationNode[T, J],
   joinType: JoinType = LEFT): JoinNode[PK, R, T, J] =
    findAssociation(node) match {
      case Some(a: Association[T, R, J]) =>  // many-to-one join
        new ManyToOneJoin[PK, R, T, J](this, node, a, joinType)
      case _ => node.findAssociation(this) match {
        case Some(a: Association[PK, J, R]) =>  // one-to-many join
          new OneToManyJoin[PK, R, T, J](this, node, a, joinType)
        case _ =>
          new JoinNode(this, node, joinType)
      }
    }

  def INNER_JOIN[T, J <: Record[T, J]]
  (node: RelationNode[T, J]): JoinNode[PK, R, T, J] =
    JOIN(node, INNER)

  def LEFT_JOIN[T, J <: Record[T, J]]
  (node: RelationNode[T, J]): JoinNode[PK, R, T, J] =
    JOIN(node, LEFT)

  def RIGHT_JOIN[T, J <: Record[T, J]]
  (node: RelationNode[T, J]): JoinNode[PK, R, T, J] =
    JOIN(node, RIGHT)

  def FULL_JOIN[T, J <: Record[T, J]]
  (node: RelationNode[T, J]): JoinNode[PK, R, T, J] =
    JOIN(node, FULL)

  /*!## Equality & Others

  Two nodes are considered equal if they wrap the same relation and share
  same aliases.

  The `hashCode` method delegates to node's relation.

  The `canEqual` method indicates that two nodes wrap the same relation.

  The `clone` methods creates a shallow copy of this node (the underlying
  relation remains unchanged).

  Finally, both `toSql` and `toString` return dialect specific SQL expression
  which appends an alias to relation's qualified name.
  */
  override def equals(that: Any): Boolean = that match {
    case that: RelationNode[_, _] =>
      this.canEqual(that) && this.alias == that.alias
    case _ => false
  }

  override def hashCode: Int = relation.hashCode

  def canEqual(that: Any): Boolean = that match {
    case that: RelationNode[_, _] =>
      this.relation == that.relation
  }

  def toSql: String = ormConf.dialect.alias(relation.qualifiedName, alias)

  override def clone(): this.type = super.clone.asInstanceOf[this.type]

  override def toString: String = toSql
}

/*!## Implicit Convertions

`RelationNode` is implicitly converted to the `Relation` if necessary exposing
all the methods of the underlying relation. The alias is saved in a special
thread local stack so that it could become possible to refer to the alias
carried by this node, because otherwise it would be lost after conversion.

To understand this, consider following code:

    class User extends Record[...] {
      val id = "id".BIGINT
    }
    object User extends User[...] with Table[...] {...}

    val u = User AS "u"   // RelationNode of User with alias "u"
    SELECT(u.id).FROM(u)  // The `id` member does not exist in RelationNode,
                          // but it exists on the User, so the conversion works.
                          // But this eliminates the alias which will be refered
                          // by the projection. So it is saved into a stack and
                          // is recovered later.
*/
object RelationNode {
  implicit def toRelation[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): R = {
    aliasStack.push(node.alias)
    node.relation.asInstanceOf[R]
  }
}

/*! The `ProxyNode` wraps a node and provides functionality to arrange
joined nodes into a query plan (tree-like structure) by allowing to replace
an underlying `node` with it's equivalent `JoinNode`. Most methods delegate
to underlying `node`.
*/
class ProxyNode[PK, R <: Record[PK, R]](var node: RelationNode[PK, R])
    extends RelationNode[PK, R](node.relation) {

  override def alias = node.alias
  override def AS(alias: String): this.type = {
    node.AS(alias)
    this
  }

  override def projections = node.projections
  override def * = node.*

  override def canEqual(that: Any): Boolean = node.canEqual(that)
  override def equals(obj: Any) = node.equals(obj)
  override def hashCode = node.hashCode

  override def toSql = node.toSql

  override def clone(): this.type = {
    val newNode = super.clone()
    val n = node.clone()
    newNode.node = n
    newNode
  }

}

/*!# Joins

Relations can be joined within one query to allow applying restrictions on
associated relations. The `JoinNode` class represents a join between two relations.
We stick to a general convention called _left associativity_: two joined nodes
with equal left nodes are considered equal:

``` {.scala}
(ci JOIN co) == ci
(ci JOIN co JOIN ca) == ((ci JOIN co) JOIN ca)
```

This way you can compose arbitrary complex query plans. The join condition
(the `ON` subclause) can be either inferred from associations or specified
manually using the `ON` method.
*/
class JoinNode[PKL, L <: Record[PKL, L], PKR, R <: Record[PKR, R]]
(protected var _left: RelationNode[PKL, L],
 protected var _right: RelationNode[PKR, R],
 protected var _joinType: JoinType)
    extends ProxyNode[PKL, L](_left) {

  def left = _left
  def right = _right
  def joinType = _joinType

  protected var _on: Expression = EmptyPredicate
  def onClause = _on
  def ON(expr: Expression): this.type = {
    _on = expr
    this
  }

  def sqlOn = ormConf.dialect.on(this.onClause)

  override def projections = left.projections ++ right.projections

  def replaceLeft(newLeft: RelationNode[PKL, L]): this.type = {
    this._left = newLeft
    this
  }

  def replaceRight(newRight: RelationNode[PKR, R]): this.type = {
    this._right = newRight
    this
  }

  def isUndefined = onClause == EmptyPredicate

  override def toSql = ormConf.dialect.join(this)

  override def clone(): this.type = super.clone()
      .replaceLeft(this.left.clone())
      .replaceRight(this.right.clone())

  override def toString = "(" + left + " -> " + right + ")"

}

class ManyToOneJoin[PKL, L <: Record[PKL, L], PKR, R <: Record[PKR, R]]
(childNode: RelationNode[PKL, L],
 parentNode: RelationNode[PKR, R],
 val association: Association[PKR, L, R],
 joinType: JoinType)
    extends JoinNode[PKL, L, PKR, R](childNode, parentNode, joinType) {

  override def onClause =
    if (_on == EmptyPredicate)
      association.joinPredicate(childNode.alias, parentNode.alias)
    else _on

  override def isUndefined = false

}

class OneToManyJoin[PKL, L <: Record[PKL, L], PKR, R <: Record[PKR, R]]
(parentNode: RelationNode[PKL, L],
 childNode: RelationNode[PKR, R],
 val association: Association[PKL, R, L],
 joinType: JoinType)
    extends JoinNode[PKL, L, PKR, R](parentNode, childNode, joinType) {

  override def onClause =
    if (_on == EmptyPredicate)
      association.joinPredicate(childNode.alias, parentNode.alias)
    else _on

  override def isUndefined = false
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy