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

com.websudos.phantom.CassandraTable.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

import com.datastax.driver.core.{Row, Session}
import com.websudos.phantom.builder.clauses.DeleteClause
import com.websudos.phantom.builder.query._
import com.websudos.phantom.column.AbstractColumn
import com.websudos.phantom.connectors.KeySpace
import com.websudos.phantom.exceptions.{InvalidClusteringKeyException, InvalidPrimaryKeyException}
import org.slf4j.LoggerFactory

import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContextExecutor}
import scala.reflect.runtime.{currentMirror => cm, universe => ru}

/**
 * Main representation of a Cassandra table.
 * @tparam T Type of this table.
 * @tparam R Type of record.
 */
abstract class CassandraTable[T <: CassandraTable[T, R], R] extends SelectTable[T, R] { self =>

  type ListColumn[RR] = com.websudos.phantom.column.ListColumn[T, R, RR]
  type SetColumn[RR] =  com.websudos.phantom.column.SetColumn[T, R, RR]
  type MapColumn[KK, VV] =  com.websudos.phantom.column.MapColumn[T, R, KK, VV]
  type JsonColumn[RR] = com.websudos.phantom.column.JsonColumn[T, R, RR]
  type EnumColumn[RR <: Enumeration] = com.websudos.phantom.column.EnumColumn[T, R, RR]
  type OptionalEnumColumn[RR <: Enumeration] = com.websudos.phantom.column.OptionalEnumColumn[T, R, RR]
  type JsonSetColumn[RR] = com.websudos.phantom.column.JsonSetColumn[T, R, RR]
  type JsonListColumn[RR] = com.websudos.phantom.column.JsonListColumn[T, R, RR]

  private[phantom] def insertSchema()(
    implicit session: Session,
    keySpace: KeySpace,
    ec: ExecutionContextExecutor
  ): Unit = {
    Await.result(create.ifNotExists().future(), 10.seconds)
  }

  private[this] val instanceMirror = cm.reflect(this)

  protected[phantom] lazy val _name: String = {
    instanceMirror.symbol.name.toTypeName.decodedName.toString
  }

  lazy val logger = LoggerFactory.getLogger(getClass.getName.stripSuffix("$"))

  def tableName: String = _name

  def fromRow(r: Row): R

  /**
   * The new create mechanism introduced in Phantom 1.6.0.
   * This uses the phantom proprietary QueryBuilder instead of the already available one in the underlying Java Driver.
   * @return A root create block, with full support for all CQL Create query options.
   */
  final def create: RootCreateQuery[T, R] = new RootCreateQuery(this.asInstanceOf[T])

  final def alter()(implicit keySpace: KeySpace): AlterQuery.Default[T, R] = AlterQuery(this.asInstanceOf[T])

  final def update()(implicit keySpace: KeySpace): UpdateQuery.Default[T, R] = UpdateQuery(this.asInstanceOf[T])

  final def insert()(implicit keySpace: KeySpace): InsertQuery.Default[T, R] = InsertQuery(this.asInstanceOf[T])

  final def delete()(implicit keySpace: KeySpace): DeleteQuery.Default[T, R] = DeleteQuery[T, R](this.asInstanceOf[T])

  final def delete(conditions: (T => DeleteClause.Condition)*)(implicit keySpace: KeySpace): DeleteQuery.Default[T, R] = {
    val tb = this.asInstanceOf[T]

    val queries = conditions.map(_(tb).qb)

    DeleteQuery[T, R](tb, queries: _*)
  }

  final def truncate()(implicit keySpace: KeySpace): TruncateQuery.Default[T, R] = TruncateQuery[T, R](this.asInstanceOf[T])

  def secondaryKeys: Seq[AbstractColumn[_]] = columns.filter(_.isSecondaryKey)

  def primaryKeys: Seq[AbstractColumn[_]] = columns.filter(_.isPrimary).filterNot(_.isPartitionKey)

  def partitionKeys: Seq[AbstractColumn[_]] = columns.filter(_.isPartitionKey)

  def clusteringColumns: Seq[AbstractColumn[_]] = columns.filter(_.isClusteringKey)

  def clustered: Boolean = clusteringColumns.nonEmpty

  /**
   * This method will filter the columns from a Clustering Order definition.
   * It is used to define TimeSeries tables, using the ClusteringOrder trait
   * combined with a directional trait, either Ascending or Descending.
   *
   * This method will simply add to the trailing of a query.
   * @return The clustering key, defined as a string or the empty string.
   */
  private[phantom] def clusteringKey: String = {
    if (clusteringColumns.nonEmpty) {
      val key = clusteringColumns.map(col => {
        val direction = if (col.isAscending) {
          "ASC"
        } else {
          "DESC"
        }
        s"${col.name} $direction"
      })
      s"WITH CLUSTERING ORDER BY (${key.mkString(", ")})"
    } else {
      ""
    }
  }

  /**
   * This method will define the PRIMARY_KEY of the table.
   * 
    *
  • * For more than one partition key, it will define a Composite Key. * Example: PRIMARY_KEY((partition_key_1, partition_key2), primary_key_1, etc..) *
  • *
  • * For a single partition key, it will define a Compound Key. * Example: PRIMARY_KEY(partition_key_1, primary_key_1, primary_key_2) *
  • *
  • * For no partition key, it will throw an exception. *
  • *
* @return A string value representing the primary key of the table. */ @throws(classOf[InvalidPrimaryKeyException]) private[phantom] def defineTableKey(): String = { preconditions() // Get the list of primary keys that are not partition keys. val primaries = primaryKeys val primaryString = primaryKeys.map(_.name).mkString(", ") // Get the list of partition keys that are not primary keys // This is done to avoid including the same columns twice. val partitions = partitionKeys.toList val partitionString = s"(${partitions.map(_.name).mkString(", ")})" val operand = partitions.lengthCompare(1) val key = if (operand < 0) { throw InvalidPrimaryKeyException(tableName) } else if (operand == 0) { val partitionKey = partitions.headOption.map(_.name).orNull if (primaries.isEmpty) { partitionKey } else { s"$partitionKey, $primaryString" } } else { if (primaries.isEmpty) { partitionString } else { s"$partitionString, $primaryString" } } s"PRIMARY KEY ($key)" } /** * This method will check for common Cassandra anti-patterns during the intialisation of a schema. * If the Schema definition violates valid CQL standard, this function will throw an error. * * A perfect example is using a mixture of Primary keys and Clustering keys in the same schema. * While a Clustering key is also a primary key, when defining a clustering key all other keys must become clustering keys and specify their order. * * We could auto-generate this order but we wouldn't be making false assumptions about the desired ordering. */ private[this] def preconditions(): Unit = { if (clustered && primaryKeys.diff(clusteringColumns).nonEmpty) { logger.error("When using CLUSTERING ORDER all PrimaryKey definitions" + " must become a ClusteringKey definition and specify order.") throw InvalidClusteringKeyException(tableName) } } val columns: Seq[AbstractColumn[_]] = Lock.synchronized { val selfType = instanceMirror.symbol.toType val members: Seq[ru.Symbol] = (for { baseClass <- selfType.baseClasses.reverse symbol <- baseClass.typeSignature.members.sorted if symbol.typeSignature <:< ru.typeOf[AbstractColumn[_]] } yield symbol)(collection.breakOut) for { symbol <- members.distinct table = if (symbol.isModule) { instanceMirror.reflectModule(symbol.asModule).instance } else if (symbol.isTerm && symbol.asTerm.isVal) { instanceMirror.reflectField(symbol.asTerm).get } } yield table.asInstanceOf[AbstractColumn[_]] } } private[phantom] case object Lock




© 2015 - 2025 Weber Informatics LLC | Privacy Policy