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

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

/*
 * Copyright 2013 - 2020 Outworkers Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.outworkers.phantom.builder.query

import com.datastax.driver.core.{ConsistencyLevel, Session}
import com.outworkers.phantom.builder.clauses._
import com.outworkers.phantom.builder.ops.TokenizerKey
import com.outworkers.phantom.builder.primitives.Primitive
import com.outworkers.phantom.builder.query.engine.CQLQuery
import com.outworkers.phantom.builder.query.execution._
import com.outworkers.phantom.builder.query.prepared.{PrepareMark, PreparedFlattener, PreparedSelectBlock}
import com.outworkers.phantom.builder.syntax.CQLSyntax
import com.outworkers.phantom.builder.{ConsistencyBound, LimitBound, OrderBound, WhereBound, _}
import com.outworkers.phantom.column.AbstractColumn
import com.outworkers.phantom.connectors.KeySpace
import com.outworkers.phantom.{CassandraTable, Row}
import shapeless.ops.hlist.{Prepend, Reverse, Tupler}
import shapeless.{::, =:!=, HList, HNil}

import scala.annotation.implicitNotFound
import scala.concurrent.ExecutionContextExecutor

case 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,
  init: CQLQuery,
  tokens: List[TokenizerKey] = Nil,
  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,
  options: QueryOptions = QueryOptions.empty
) extends RootQuery[Table, Record, Status] {

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

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

  def allowFiltering(): SelectQuery[Table, Record, Limit, Order, Status, Chain, PS] = {
    copy(filteringPart = filteringPart append QueryBuilder.Select.allowFiltering())
  }

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

  def prepareAsync[P[_], F[_], Rev <: HList]()(
    implicit session: Session,
    executor: ExecutionContextExecutor,
    keySpace: KeySpace,
    ev: PS =:!= HNil,
    rev: Reverse.Aux[PS, Rev],
    fMonad: FutureMonad[F],
    interface: PromiseInterface[P, F]
  ): F[PreparedSelectBlock[Table, Record, Limit, Rev]] = {
    val flatten = new PreparedFlattener(qb)

    flatten.async map { ps =>
      new PreparedSelectBlock[Table, Record, Limit, Rev](ps, flatten.protocolVersion, 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
    */
  def where[
    RR,
    HL <: HList,
    Out <: HList
  ](condition: Table => QueryCondition[HL])(
    implicit ev: Chain =:= Unchainned,
    prepend: Prepend.Aux[HL, PS, Out]
  ): SelectQuery[Table, Record, Limit, Order, Status, Chainned, Out] = {
    copy(
      wherePart = wherePart append QueryBuilder.Update.where(condition(table).qb),
      tokens = tokens ::: condition(table).tokens
    )
  }

  /**
    * 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
    */
  def and[
    RR,
    HL <: HList,
    Out <: HList
  ](condition: Table => QueryCondition[HL])(
    implicit ev: Chain =:= Chainned,
    prepend: Prepend.Aux[HL, PS, Out]
  ): SelectQuery[Table, Record, Limit, Order, Status, Chainned, Out] = {
    copy(
      wherePart = wherePart append QueryBuilder.Update.and(condition(table).qb),
      tokens = tokens ::: condition(table).tokens
    )
  }

  def using(clause: UsingClause.Condition): SelectQuery[Table, Record, Limit, Order, Status, Chainned, PS] = {
    copy(usingPart = usingPart append clause.qb)
  }

  def withOptions(opts: QueryOptions => QueryOptions): SelectQuery[Table, Record, Limit, Order, Status, Chain, PS] = {
    copy(options = opts(this.options))
  }

  def consistencyLevel_=(level: ConsistencyLevel)(
    implicit ev: Status =:= Unspecified,
    session: Session
  ): SelectQuery[Table, Record, Limit, Order, Specified, Chain, PS] = {
    if (session.protocolConsistency) {
      copy(options = options.consistencyLevel_=(level))
    } else {
      copy(usingPart = usingPart append QueryBuilder.consistencyLevel(level.toString))
    }
  }

  @implicitNotFound("A limit was already specified for this query.")
  final def limit(ps: PrepareMark)(
    implicit ev: Limit =:= Unlimited
  ): SelectQuery[Table, Record, Limited, Order, Status, Chain, Int :: PS] = {
    copy(limitedPart = limitedPart append QueryBuilder.limit(ps.qb.queryString))
  }


  @implicitNotFound("A limit was already specified for this query.")
  def limit(limit: Int)(
    implicit ev: Limit =:= Unlimited
  ): SelectQuery[Table, Record, Limited, Order, Status, Chain, PS] = {
    copy(limitedPart = limitedPart append QueryBuilder.limit(limit.toString))
  }


  @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] = {
    copy(orderPart = orderPart append clauses.map(_(table).qb).toList)
  }

  override def executableQuery: ExecutableCqlQuery = ExecutableCqlQuery(qb, options, tokens)
}

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.")
  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)(
    implicit ev: Primitive[Long]
  ): Long = {
    ev.fromRow(CQLSyntax.Selection.count, r).getOrElse(0L)
  }

  private[this] def namedCount(r: Row, name: String)(
    implicit ev: Primitive[Long]
  ): Long = {
    ev.fromRow(
      CQLQuery(CQLSyntax.Selection.systemCount).wrapn(name).queryString,
      r
    ).getOrElse(0L)
  }

  def json()(
    implicit keySpace: KeySpace,
    ev: Primitive[String]
  ): SelectQuery.Default[T, String] = {
    val jsonParser: (Row) => String = row => {
      ev.deserialize(
        row.getBytesUnsafe(CQLSyntax.JSON_EXTRACTOR),
        row.version
      )
    }

    clause match {
      case Some(_) =>
        new SelectQuery(
          table,
          jsonParser,
          QueryBuilder.Select.selectJson(table.tableName, keySpace.name)
        )

      case None =>
        new SelectQuery(
          table,
          jsonParser,
          QueryBuilder.Select.selectJson(table.tableName, keySpace.name, columns: _*)
        )
    }
  }

  def function[
    HL <: HList,
    Rev <: HList,
    TP
  ](projection: T => TypedClause.TypedProjection[HL])(
    implicit keySpace: KeySpace,
    rev: Reverse.Aux[HL, Rev],
    ev: Tupler.Aux[Rev, TP]
  ): SelectQuery.Default[T, TP] = {
    new SelectQuery(
      table,
      row => ev.apply(rev.apply(projection(table).extractor(row))),
      QueryBuilder.Select.select(table.tableName, keySpace.name, projection(table).qb),
      Nil,
      WherePart.empty,
      OrderPart.empty,
      LimitedPart.empty,
      FilteringPart.empty,
      UsingPart.empty,
      count = false
    )
  }

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


  def count[RR](fn: T => AbstractColumn[RR])(
    implicit keySpace: KeySpace,
    ev: Primitive[Long]
  ): SelectQuery.Default[T, Long] = {
    new SelectQuery(
      table,
      row => namedCount(row, fn(table).name),
      QueryBuilder.Select.count(table.tableName, keySpace.name, fn(table).name),
      Nil,
      WherePart.empty,
      OrderPart.empty,
      LimitedPart.empty,
      FilteringPart.empty,
      UsingPart.empty,
      count = true
    )
  }

  def count()(
    implicit keySpace: KeySpace,
    ev: Primitive[Long]
  ): SelectQuery.Default[T, Long] = {
    new SelectQuery(
      table,
      extractCount,
      QueryBuilder.Select.count(table.tableName, keySpace.name),
      Nil,
      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