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

.circumflex-orm.2.0.3.source-code.query.scala Maven / Gradle / Ivy

There is a newer version: 2.5
Show newest version
package ru.circumflex.orm

import java.sql.{ResultSet, PreparedStatement}
import ru.circumflex.core._

/*!# Querying

SQL and DML queries form the heart of Circumflex ORM DSL.

Common features implemented in the `Query` trait are _named parameters_
which allow query reuse and _ensuring alias uniqueness_ which prevents
implicit relation node aliases from colliding within a single query.

The `SQLQuery` trait represents data-retrieval queries which usually employ
the `executeQuery` method of JDBC `PreparedStatement` and process JDBC
`ResultSet`.

The `DMLQuery` trait represents data-manipulation queries which usually
employ the `executeUpdate` method of JDBC `PreparedStatement` and return
the number of affected rows.
*/
trait Query extends SQLable with Expression with Cloneable {

  // Keep track of last execution time
  protected var _executionTime = 0l
  def executionTime = _executionTime

  protected var _aliasCounter = 0;


  protected def nextAlias: String = {
    _aliasCounter += 1
    return "this_" + _aliasCounter
  }


  def setParams(st: PreparedStatement, index: Int): Int = {
    var paramsCounter = index;
    parameters.foreach(p => {
      typeConverter.write(st, convertNamedParam(p), paramsCounter)
      paramsCounter += 1
    })
    return paramsCounter
  }

  protected var _namedParams: Map[String, Any] = Map()

  def renderParams: Seq[Any] = parameters.map(p => convertNamedParam(p))

  def set(name: String, value: Any): this.type = {
    _namedParams += name -> value
    return this
  }

  protected def convertNamedParam(param: Any): Any = param match {
    case s: Symbol => lookupNamedParam(s.name)
    case s: String if (s.startsWith(":")) => lookupNamedParam(s)
    case _ => param
  }

  protected def lookupNamedParam(name: String): Any =
    _namedParams.get(name.replaceAll("^:", "")) match {
      case Some(p) => p
      case _ => name
    }

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

  override def toString = toSql
}

/*! The `SQLQuery` trait defines a contract for data-retrieval queries.
It's only type parameter `T` designates the query result type (it is
determined by specified `projections`).
*/
abstract class SQLQuery[T](val projection: Projection[T]) extends Query {


  def projections: Seq[Projection[_]] = List(projection)


  protected def ensureProjectionAlias[T](projection: Projection[T]): Unit =
    projection match {
      case p: AtomicProjection[_] if (p.alias == "this") => p.AS(nextAlias)
      case p: CompositeProjection[_] =>
        p.subProjections.foreach(ensureProjectionAlias(_))
      case _ =>
    }

  ensureProjectionAlias(projection)


  def resultSet[A](actions: ResultSet => A): A = {
    val result = time {
      tx.execute(toSql) { st =>
        setParams(st, 1)
        val rs = st.executeQuery
        try {
          actions(rs)
        } finally {
          rs.close
        }
      } { throw _ }
    }
    _executionTime = result._1
    Statistics.executeSql(this)
    return result._2
  }


  def read(rs: ResultSet): Option[T] = projection.read(rs)


  def list(): Seq[T] = resultSet { rs =>
    var result = List[T]()
    while (rs.next) read(rs) match {
      case Some(r) =>
        result ++= List(r)
      case _ =>
    }
    result
  }


  def unique(): Option[T] = resultSet(rs => {
    if (!rs.next)
      None
    else if (rs.isLast)
      read(rs)
    else throw new ORMException("Unique result expected, but multiple rows found.")
  })

}

class NativeSQLQuery[T](projection: Projection[T],
                        expression: Expression)
    extends SQLQuery[T](projection) {
  def parameters = expression.parameters
  def toSql = expression.toSql.replaceAll("\\{\\*\\}", projection.toSql)
}

/*! `SearchQuery` represents a query with a `WHERE` clause. */
trait SearchQuery extends Query {
  protected var _where: Predicate = EmptyPredicate

  def where: Predicate = this._where
  def WHERE(predicate: Predicate): this.type = {
    this._where = predicate
    return this
  }
  def WHERE(expression: String, params: Pair[String,Any]*): this.type =
    WHERE(prepareExpr(expression, params: _*))


  def add(predicates: Predicate*): this.type = {
    where match {
      case EmptyPredicate =>
        this._where = AND(predicates: _*)
      case p: AggregatePredicate if (p.operator == dialect.AND) =>
        p.add(predicates: _*)
      case p =>
        this._where = _where.AND(predicates: _*)
    }
    return this
  }
  def add(expression: String, params: Pair[String, Any]*): this.type =
    add(prepareExpr(expression, params: _*))
}

class Select[T](projection: Projection[T]) extends SQLQuery[T](projection)
    with SearchQuery {

  // Commons
  protected var _distinct: Boolean = false
  protected var _auxProjections: Seq[Projection[_]] = Nil
  protected var _relations: Seq[RelationNode[_, _]] = Nil

  protected var _having: Predicate = EmptyPredicate
  protected var _groupBy: Seq[Projection[_]] = Nil
  protected var _setOps: Seq[Pair[SetOperation, SQLQuery[T]]] = Nil
  protected var _orders: Seq[Order] = Nil
  protected var _limit: Int = -1
  protected var _offset: Int = 0

  def parameters: Seq[Any] = where.parameters ++
      _having.parameters ++
      _setOps.flatMap(p => p._2.parameters) ++
      _orders.flatMap(_.parameters)

  def setOps = _setOps

  // SELECT clause

  override def projections = List(projection) ++ _auxProjections

  def distinct_?(): Boolean = _distinct
  def DISTINCT(): Select[T] = {
    this._distinct = true
    return this
  }

  // FROM clause

  def from = _relations
  def FROM(nodes: RelationNode[_, _]*): Select[T] = {
    this._relations = nodes.toList
    from.foreach(ensureNodeAlias(_))
    return this
  }

  protected def ensureNodeAlias(node: RelationNode[_, _]): RelationNode[_, _] =
    node match {
      case j: JoinNode[_, _, _, _] =>
        ensureNodeAlias(j.left)
        ensureNodeAlias(j.right)
        j
      case n: RelationNode[_, _] if (n.alias == "this") => node.AS(nextAlias)
      case n => n
    }

  // HAVING clause

  def having: Predicate = this._having
  def HAVING(predicate: Predicate): Select[T] = {
    this._having = predicate
    return this
  }
  def HAVING(expression: String, params: Pair[String,Any]*): Select[T] =
    HAVING(prepareExpr(expression, params: _*))

  // GROUP BY clause

  def groupBy: Seq[Projection[_]] = _groupBy

  def GROUP_BY(proj: Projection[_]*): Select[T] = {
    proj.toList.foreach(p => addGroupByProjection(p))
    return this
  }

  protected def addGroupByProjection(proj: Projection[_]): Unit =
    findProjection(projection, p => p.equals(proj)) match {
      case None =>
        ensureProjectionAlias(proj)
        this._auxProjections ++= List(proj)
        this._groupBy ++= List(proj)
      case Some(p) => this._groupBy ++= List(p)
    }


  protected def findProjection(projection: Projection[_],
                               predicate: Projection[_] => Boolean): Option[Projection[_]] =
    if (predicate(projection)) return Some(projection)
    else projection match {
      case p: CompositeProjection[_] =>
        return p.subProjections.find(predicate)
      case _ => return None
    }

  // Set Operations

  protected def addSetOp(op: SetOperation, sql: SQLQuery[T]): Select[T] = {
    val q = clone()
    q._setOps ++= List(op -> sql)
    return q
  }

  def UNION(sql: SQLQuery[T]): Select[T] =
    addSetOp(OP_UNION, sql)
  def UNION_ALL(sql: SQLQuery[T]): Select[T] =
    addSetOp(OP_UNION_ALL, sql)
  def EXCEPT(sql: SQLQuery[T]): Select[T] =
    addSetOp(OP_EXCEPT, sql)
  def EXCEPT_ALL(sql: SQLQuery[T]): Select[T] =
    addSetOp(OP_EXCEPT_ALL, sql)
  def INTERSECT(sql: SQLQuery[T]): Select[T] =
    addSetOp(OP_INTERSECT, sql)
  def INTERSECT_ALL(sql: SQLQuery[T]): Select[T] =
    addSetOp(OP_INTERSECT_ALL, sql)

  // ORDER BY clause

  def orderBy = _orders
  def ORDER_BY(order: Order*): Select[T] = {
    this._orders ++= order.toList
    return this
  }

  // LIMIT and OFFSET clauses

  def limit = this._limit
  def LIMIT(value: Int): Select[T] = {
    _limit = value
    return this
  }

  def offset = this._offset
  def OFFSET(value: Int): Select[T] = {
    _offset = value
    return this
  }

  // Miscellaneous

  def toSql = dialect.select(this)

}

/*! The `DMLQuery` trait defines a contract for data-manipulation queries. */
trait DMLQuery extends Query {


  def execute(): Int = {
    val result = time {
      tx.execute(toSql){ st =>
        setParams(st, 1)
        st.executeUpdate
      } { throw _ }
    }
    _executionTime = result._1
    Statistics.executeDml(this)
    return result._2
  }
}

class NativeDMLQuery(expression: Expression) extends DMLQuery {
  def parameters = expression.parameters
  def toSql = expression.toSql
}

class Insert[PK, R <: Record[PK, R]](val relation: Relation[PK, R],
                                     val fields: Seq[Field[_, R]])
    extends DMLQuery {
  def parameters = fields.map(_.value)
  def toSql: String = dialect.insert(this)
}

class InsertSelect[PK, R <: Record[PK, R]](val relation: Relation[PK, R],
                                           val query: SQLQuery[_])
    extends DMLQuery {
  if (relation.readOnly_?)
    throw new ORMException("The relation " + relation.qualifiedName + " is read-only.")
  def parameters = query.parameters
  def toSql: String = dialect.insertSelect(this)
}

class InsertSelectHelper[PK, R <: Record[PK, R]](val relation: Relation[PK, R]) {
  def SELECT[T](projection: Projection[T]) = new InsertSelect(relation, new Select(projection))
}

class Delete[PK, R <: Record[PK, R]](val node: RelationNode[PK, R])
    extends DMLQuery with SearchQuery {
  val relation = node.relation
  if (relation.readOnly_?)
    throw new ORMException("The relation " + relation.qualifiedName + " is read-only.")

  def parameters = where.parameters
  def toSql: String = dialect.delete(this)
}

class Update[PK, R <: Record[PK, R]](val node: RelationNode[PK, R])
    extends DMLQuery with SearchQuery {
  val relation = node.relation
  if (relation.readOnly_?)
    throw new ORMException("The relation " + relation.qualifiedName + " is read-only.")

  private var _setClause: Seq[(Field[_, R], Option[Any])] = Nil
  def setClause = _setClause
  def SET[T](field: Field[T, R], value: T): Update[PK, R] = {
    _setClause ++= List(field -> Some(value))
    return this
  }
  def SET[K, P <: Record[K, P]](association: Association[K, R, P], value: P): Update[PK, R]=
    SET(association.field.asInstanceOf[Field[Any, R]], value.PRIMARY_KEY.value)
  def SET_NULL[T](field: Field[T, R]): Update[PK, R] = {
    _setClause ++= List(field -> None)
    return this
  }
  def SET_NULL[K, P <: Record[K, P]](association: Association[K, R, P]): Update[PK, R] =
    SET_NULL(association.field)

  def parameters = _setClause.map(_._2) ++ where.parameters
  def toSql: String = dialect.update(this)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy