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

com.outworkers.phantom.builder.query.CreateQuery.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.CassandraTable
import com.outworkers.phantom.builder._
import com.outworkers.phantom.builder.query.CreateQuery.DelegatedCreateQuery
import com.outworkers.phantom.builder.query.engine.CQLQuery
import com.outworkers.phantom.builder.query.execution.{ExecutableCqlQuery, QueryCollection}
import com.outworkers.phantom.builder.query.options.{CachingStrategies, TablePropertyClause}
import com.outworkers.phantom.builder.syntax.CQLSyntax
import com.outworkers.phantom.connectors.{KeySpace, SessionAugmenterImplicits}

import scala.annotation.implicitNotFound

class RootCreateQuery[
  Table <: CassandraTable[Table, _],
  Record
](val table: Table) {

  private[phantom] def default()(implicit keySpace: KeySpace): CQLQuery = {
    QueryBuilder.Create.defaultCreateQuery(
      keySpace.name,
      table.tableName,
      table.tableKey,
      table.columns.map(_.qb)
    )
  }

  private[this] def lightweight()(implicit keySpace: KeySpace): CQLQuery = {
    QueryBuilder.Create.createIfNotExists(
      keySpace.name,
      table.tableName,
      table.tableKey,
      table.columns.map(_.qb)
    )
  }

  /**
   * Creates a lightweight transaction Create query, only executed if a table with the given name is not found.
   * If the target keyspace already has a table with the given name, nothing will happen.
   * Cassandra will not attempt to merge, overrwrite or do anything, and the operation will be deemed
   * successful, but have no side effects.
   *
   * @param keySpace The name of the keySpace to target.
   * @return A create query executed with a lightweight transactions.
   */
  def ifNotExists()(implicit keySpace: KeySpace): CreateQuery.Default[Table, Record] = {
    if (table.clusteringColumns.nonEmpty) {
      new CreateQuery(
        table,
        lightweight(),
        WithPart.empty
      ).withClustering()
    } else {
      new CreateQuery(
        table,
        lightweight(),
        WithPart.empty
      )
    }
  }
}

case class CreateQuery[
  Table <: CassandraTable[Table, _],
  Record,
  Status <: ConsistencyBound
](
  table: Table,
  init: CQLQuery,
  withClause: WithPart = WithPart.empty,
  usingPart: UsingPart = UsingPart.empty,
  options: QueryOptions = QueryOptions.empty
)(implicit val keySpace: KeySpace) extends SessionAugmenterImplicits {

  def consistencyLevel_=(level: ConsistencyLevel)(implicit session: Session): CreateQuery[Table, Record, Specified] = {
    if (session.protocolConsistency) {
      copy(options = options.consistencyLevel_=(level))
    } else {
      copy(usingPart = usingPart append QueryBuilder.consistencyLevel(level.toString))
    }
  }

  @implicitNotFound("You cannot use 2 `with` clauses on the same create query. Use `and` instead.")
  final def `with`(clause: TablePropertyClause): CreateQuery[Table, Record, Status] = {
    copy(withClause = withClause append clause.qb)
  }

  /**
    * Used to automatically define a CLUSTERING ORDER BY clause using the columns already defined in the table.
    * This will use the built in reflection mechanism to fetch all columns defined inside a table.
    * It will then filter the columns that mix in a definition of a clustering key.
    *
    * @return A new Create query, where the builder contains a full clustering clause specified.
    */
  final def withClustering(): CreateQuery[Table, Record, Status] = {
    val clusteringPairs = table.clusteringColumns.map { col =>
        val order = if (col.isAscending) CQLSyntax.Ordering.asc else CQLSyntax.Ordering.desc
        (col.name, order)
    }.toList

    `with`(new TablePropertyClause {
      override def qb: CQLQuery = QueryBuilder.Create.clusteringOrder(clusteringPairs)
    })
  }

  @implicitNotFound("You cannot use 2 `with` clauses on the same create query. Use `and` instead.")
  final def option(clause: TablePropertyClause): CreateQuery[Table, Record, Status] = {
    `with`(clause)
  }

  @implicitNotFound("You have to use `with` before using `and` in a create query.")
  final def and(clause: TablePropertyClause): CreateQuery[Table, Record, Status] = {
    `with`(clause)
  }

  val qb: CQLQuery = (withClause merge WithPart.empty merge usingPart) build init

  def executableQuery: ExecutableCqlQuery = ExecutableCqlQuery(qb, options, Nil)

  def queryString: String = qb.queryString

  private[phantom] val indexList: QueryCollection[Seq] = {
    val name = keySpace.name

    new QueryCollection(table.secondaryKeys map { key =>
      if (key.isMapKeyIndex) {
        ExecutableCqlQuery(
          qb = QueryBuilder.Create.mapIndex(table.tableName, name, key.name),
          options = QueryOptions.empty,
          tokens = Nil
        )
      } else if (key.isMapEntryIndex) {
        ExecutableCqlQuery(
          qb = QueryBuilder.Create.mapEntries(table.tableName, name, key.name),
          options = QueryOptions.empty,
          tokens = Nil
        )
      } else {
        ExecutableCqlQuery(
          QueryBuilder.Create.index(table.tableName, name, key.name),
          QueryOptions.empty,
          Nil
        )
      }
    })
  }

  def delegate: DelegatedCreateQuery = DelegatedCreateQuery(
    executable = ExecutableCqlQuery(qb, options, Nil),
    indexList = indexList,
    sasiIndexes = table.sasiQueries
  )
}

object CreateQuery {
  type Default[T <: CassandraTable[T, _], R] = CreateQuery[T, R, Unspecified]

  case class DelegatedCreateQuery(
    executable: ExecutableCqlQuery,
    indexList: QueryCollection[Seq],
    sasiIndexes: QueryCollection[Seq]
  )
}

private[phantom] trait CreateImplicits extends TablePropertyClauses {

  val Cache: CachingStrategies = Caching

  def apply[
    T <: CassandraTable[T, _],
    R
  ](root: RootCreateQuery[T, R])(implicit keySpace: KeySpace): CreateQuery.Default[T, R] = {
    if (root.table.clusteringColumns.nonEmpty) {
      new CreateQuery[T, R, Unspecified](
        root.table,
        root.default()
      ).withClustering()
    } else {
      new CreateQuery[T, R, Unspecified](
        root.table,
        root.default()
      )
    }
  }

  implicit def rootCreateQueryToCreateQuery[
    T <: CassandraTable[T, _],
    R
  ](root: RootCreateQuery[T, R])(implicit keySpace: KeySpace): CreateQuery.Default[T, R] = apply(root)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy