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

com.websudos.phantom.builder.query.SelectQuery.scala Maven / Gradle / Ivy

There is a newer version: 1.29.6
Show newest version
/*
 * Copyright 2013-2015 Websudos, Limited.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * - Explicit consent must be obtained from the copyright owner, Outworkers Limited before any redistribution is made.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package com.websudos.phantom.builder.query

import com.datastax.driver.core.{ConsistencyLevel, Row, Session}
import com.websudos.phantom.CassandraTable
import com.websudos.phantom.builder._
import com.websudos.phantom.builder.clauses._
import com.websudos.phantom.builder.query.prepared.PreparedSelectBlock
import com.websudos.phantom.connectors.KeySpace
import shapeless.ops.hlist.Reverse
import shapeless.{::, =:!=, HList, HNil}

import scala.annotation.implicitNotFound
import scala.concurrent.{ExecutionContextExecutor, Future => ScalaFuture}
import scala.util.Try

class SelectQuery[
  Table <: CassandraTable[Table, _],
  Record,
  Limit <: LimitBound,
  Order <: OrderBound,
  Status <: ConsistencyBound,
  Chain <: WhereBound,
  PS <: HList
](
  protected[phantom] val table: Table,
  protected[phantom] val rowFunc: Row => Record,
  val init: CQLQuery,
  protected[phantom] val wherePart: WherePart = WherePart.empty,
  protected[phantom] val orderPart: OrderPart = OrderPart.empty,
  protected[phantom] val limitedPart: LimitedPart = LimitedPart.empty,
  protected[phantom] val filteringPart: FilteringPart = FilteringPart.empty,
  protected[phantom] val usingPart: UsingPart = UsingPart.empty,
  protected[phantom] val count: Boolean = false,
  override val options: QueryOptions = QueryOptions.empty
) extends Query[Table, Record, Limit, Order, Status, Chain, PS](
  table, qb = init,
  rowFunc,
  usingPart,
  options
) with ExecutableQuery[Table, Record, Limit] {

  def fromRow(row: Row): Record = rowFunc(row)

  override val qb: CQLQuery = {
    (wherePart merge orderPart merge limitedPart merge filteringPart merge usingPart) build init
  }

  override protected[this] type QueryType[
    T <: CassandraTable[T, _],
    R,
    L <: LimitBound,
    O <: OrderBound,
    S <: ConsistencyBound,
    C <: WhereBound,
    P <: HList
  ] = SelectQuery[T, R, L, O, S, C, P]

  protected[this] def create[
    T <: CassandraTable[T, _],
    R,
    L <: LimitBound,
    O <: OrderBound,
    S <: ConsistencyBound,
    C <: WhereBound,
    P <: HList
  ](t: T, q: CQLQuery, r: Row => R, part: UsingPart, opts: QueryOptions): QueryType[T, R, L, O, S, C, P] = {
    new SelectQuery[T, R, L, O, S, C, P](
      table = t,
      rowFunc = r,
      init = q,
      wherePart = wherePart,
      orderPart = orderPart,
      limitedPart = limitedPart,
      filteringPart = filteringPart,
      usingPart = part,
      count = count,
      options = opts
    )
  }

  def allowFiltering(): SelectQuery[Table, Record, Limit, Order, Status, Chain, PS] = {
    new SelectQuery(
      table = table,
      rowFunc = rowFunc,
      init = init,
      wherePart = wherePart,
      orderPart = orderPart,
      limitedPart = limitedPart,
      filteringPart = filteringPart append QueryBuilder.Select.allowFiltering(),
      usingPart = usingPart,
      count = count,
      options = options
    )
  }

  def prepare[Rev <: HList]()(
    implicit session: Session,
    keySpace: KeySpace,
    ev: PS =:!= HNil,
    rev: Reverse.Aux[PS, Rev]
  ): PreparedSelectBlock[Table, Record, Limit, Rev] = {
    new PreparedSelectBlock(qb, rowFunc, options)
  }

  /**
   * The where method of a select query.
   * @param condition A where clause condition restricted by path dependant types.
   * @param ev An evidence request guaranteeing the user cannot chain multiple where clauses on the same query.
   * @return
   */
  override def where(condition: Table => WhereClause.Condition)(
    implicit ev: Chain =:= Unchainned
  ): QueryType[Table, Record, Limit, Order, Status, Chainned, PS] = {
    new SelectQuery(
      table = table,
      rowFunc = rowFunc,
      init = init,
      wherePart = wherePart append QueryBuilder.Update.where(condition(table).qb),
      orderPart = orderPart,
      limitedPart = limitedPart,
      filteringPart = filteringPart,
      usingPart = usingPart,
      count = count,
      options = options
    )
  }

  override def and(condition: Table => WhereClause.Condition)(
    implicit ev: Chain =:= Chainned
  ): QueryType[Table, Record, Limit, Order, Status, Chainned, PS] = {
    new SelectQuery(
      table = table,
      rowFunc = rowFunc,
      init = init,
      wherePart = wherePart append QueryBuilder.Update.and(condition(table).qb),
      orderPart = orderPart,
      limitedPart = limitedPart,
      filteringPart = filteringPart,
      usingPart = usingPart,
      count = count,
      options = options
    )
  }

  /**
   * The where method of a select query that takes parametric predicate as an argument.
   * @param condition A where clause condition restricted by path dependant types.
   * @param ev An evidence request guaranteeing the user cannot chain multiple where clauses on the same query.
   * @return
   */
  @implicitNotFound("You cannot use multiple where clauses in the same builder")
  def p_where[RR](condition: Table => PreparedWhereClause.ParametricCondition[RR])(
    implicit ev: Chain =:= Unchainned
  ): SelectQuery[Table, Record, Limit, Order, Status, Chainned, RR :: PS] = {
    new SelectQuery(
       table = table,
       rowFunc = rowFunc,
       init = init,
       wherePart = wherePart append QueryBuilder.Update.where(condition(table).qb),
       orderPart = orderPart,
       limitedPart = limitedPart,
       filteringPart = filteringPart,
       usingPart = usingPart,
       count = count,
       options = options
     )
  }


  /**
   * The and operator that adds parametric condition to the where predicates.
   * @param condition A where clause condition restricted by path dependant types.
   * @param ev An evidence request guaranteeing the user cannot chain multiple where clauses on the same query.
   * @return
   */
  @implicitNotFound("You cannot add condition in this place of the query")
  def p_and[RR](condition: Table => PreparedWhereClause.ParametricCondition[RR])(
    implicit ev: Chain =:= Chainned
  ): SelectQuery[Table, Record, Limit, Order, Status, Chainned, RR :: PS] = {
    new SelectQuery(
      table = table,
      rowFunc = rowFunc,
      init = init,
      wherePart = wherePart append QueryBuilder.Update.and(condition(table).qb),
      orderPart = orderPart,
      limitedPart = limitedPart,
      filteringPart = filteringPart,
      usingPart = usingPart,
      count = count,
      options = options
    )
  }

  def using(clause: UsingClause.Condition): SelectQuery[Table, Record, Limit, Order, Status, Chainned, PS] = {
    new SelectQuery(
      table = table,
      rowFunc = rowFunc,
      init = init,
      wherePart = wherePart,
      orderPart = orderPart,
      limitedPart = limitedPart,
      filteringPart = filteringPart,
      usingPart = usingPart append clause.qb,
      count = count,
      options = options
    )
  }

  override def consistencyLevel_=(level: ConsistencyLevel)(
    implicit ev: Status =:= Unspecified,
    session: Session
  ): SelectQuery[Table, Record, Limit, Order, Specified, Chain, PS] = {
    if (session.v3orNewer) {
      new SelectQuery(
        table = table,
        rowFunc = rowFunc,
        init = init,
        wherePart = wherePart,
        orderPart = orderPart,
        limitedPart = limitedPart,
        filteringPart = filteringPart,
        usingPart = usingPart,
        count = count,
        options = options.consistencyLevel_=(level)
      )
    } else {
      new SelectQuery(
        table = table,
        rowFunc = rowFunc,
        init = init,
        wherePart = wherePart,
        orderPart = orderPart,
        limitedPart = limitedPart,
        filteringPart = filteringPart,
        usingPart = usingPart append QueryBuilder.consistencyLevel(level.toString),
        count = count,
        options = options
      )
    }
  }


  @implicitNotFound("A limit was already specified for this query.")
  override def limit(limit: Int)(
    implicit ev: Limit =:= Unlimited
  ): QueryType[Table, Record, Limited, Order, Status, Chain, PS] = {
    new SelectQuery(
      table = table,
      rowFunc = rowFunc,
      init = init,
      wherePart = wherePart,
      orderPart = orderPart,
      limitedPart = limitedPart append QueryBuilder.limit(limit),
      filteringPart = filteringPart,
      usingPart = usingPart,
      count = count,
      options = options
    )
  }


  @implicitNotFound("You have already defined an ordering clause on this query.")
  final def orderBy(clauses: (Table => OrderingClause.Condition)*)(
    implicit ev: Order =:= Unordered
  ): SelectQuery[Table, Record, Limit, Ordered, Status, Chain, PS] = {
    new SelectQuery(
      table,
      rowFunc,
      init,
      wherePart,
      orderPart append clauses.map(_(table).qb).toList,
      limitedPart,
      filteringPart,
      usingPart = usingPart,
      count,
      options = options
    )
  }

  /**
   * Returns the first row from the select ignoring everything else
   * @param session The implicit session provided by a [[com.websudos.phantom.connectors.Connector]].
   * @param keySpace The implicit keySpace definition provided by a [[com.websudos.phantom.connectors.Connector]].
   * @param ev The implicit limit for the query.
   * @param ec The implicit Scala execution context.
   * @return A Scala future guaranteed to contain a single result wrapped as an Option.
   */
  @implicitNotFound("You have already defined limit on this Query. You cannot specify multiple limits on the same builder.")
  def one()(
    implicit session: Session,
    keySpace: KeySpace,
    ev: Limit =:= Unlimited,
    ec: ExecutionContextExecutor
  ): ScalaFuture[Option[Record]] = {
    val enforceLimit = if (count) LimitedPart.empty else limitedPart append QueryBuilder.limit(1)

    new SelectQuery(
      table = table,
      rowFunc = rowFunc,
      init = init,
      wherePart = wherePart,
      orderPart = orderPart,
      limitedPart = enforceLimit,
      filteringPart = filteringPart,
      usingPart = usingPart,
      count = count,
      options = options
    ).singleFetch()

  }
}

private[phantom] class RootSelectBlock[
  T <: CassandraTable[T, _],
  R
](table: T, val rowFunc: Row => R, columns: List[String], clause: Option[CQLQuery] = None) {

  @implicitNotFound("You haven't provided a KeySpace in scope. Use a Connector to automatically inject one.")
  private[phantom] def all()(implicit keySpace: KeySpace): SelectQuery.Default[T, R] = {

    clause match {
      case Some(opt) => {
        new SelectQuery(
          table,
          rowFunc,
          QueryBuilder.Select.select(table.tableName, keySpace.name, opt)
        )
      }
      case None => {
        new SelectQuery(
          table,
          rowFunc,
          QueryBuilder.Select.select(table.tableName, keySpace.name, columns: _*)
        )
      }
    }
  }

  @implicitNotFound("You haven't provided a KeySpace in scope. Use a Connector to automatically inject one.")
  def distinct()(implicit keySpace: KeySpace): SelectQuery.Default[T, R] = {
    new SelectQuery(table, rowFunc, QueryBuilder.Select.distinct(table.tableName, keySpace.name, columns: _*))
  }

  private[this] def extractCount(r: Row): Long = {
    Try(r.getLong("count")).getOrElse(0L)
  }


  private[this] def extractWritetime(r: Row): Long = {
    Try(r.getLong("writetime")).getOrElse(0L)
  }

  def function[RR](f1: T => TypedClause.Condition[RR])(implicit keySpace: KeySpace): SelectQuery.Default[T, RR] = {
    new SelectQuery(
      table,
      f1(table).extractor,
      QueryBuilder.Select.select(table.tableName, keySpace.name, f1(table).qb),
      WherePart.empty,
      OrderPart.empty,
      LimitedPart.empty,
      FilteringPart.empty,
      UsingPart.empty,
      count = false
    )
  }

  @implicitNotFound("You haven't provided a KeySpace in scope. Use a Connector to automatically inject one.")
  def count()(implicit keySpace: KeySpace): SelectQuery.Default[T, Long] = {
    new SelectQuery(
      table,
      extractCount,
      QueryBuilder.Select.count(table.tableName, keySpace.name),
      WherePart.empty,
      OrderPart.empty,
      LimitedPart.empty,
      FilteringPart.empty,
      UsingPart.empty,
      count = true
    )
  }
}

object RootSelectBlock {

  def apply[T <: CassandraTable[T, _], R](table: T, columns: List[String], row: Row => R): RootSelectBlock[T, R] = {
    new RootSelectBlock(table, row, columns)
  }
}

object SelectQuery {

  type Default[T <: CassandraTable[T, _], R] = SelectQuery[T, R, Unlimited, Unordered, Unspecified, Unchainned, HNil]

  def apply[T <: CassandraTable[T, _], R](table: T, qb: CQLQuery, row: Row => R): SelectQuery.Default[T, R] = {
    new SelectQuery(table, row, qb)
  }
}


private[phantom] trait SelectImplicits {
  @implicitNotFound("You haven't provided a KeySpace in scope. Use a Connector to automatically inject one.")
  final implicit def rootSelectBlockToSelectQuery[
    T <: CassandraTable[T, _],
    R
  ]( root: RootSelectBlock[T, R])(implicit keySpace: KeySpace): SelectQuery.Default[T, R] = {
    root.all
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy