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

com.stratio.cassandra.lucene.mapping.ExpressionMapper.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014 Stratio (http://stratio.com)
 *
 * 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.stratio.cassandra.lucene.mapping

import com.stratio.cassandra.lucene.IndexException
import com.stratio.cassandra.lucene.mapping.ExpressionMapper.parse
import com.stratio.cassandra.lucene.search.{Search, SearchBuilder}
import org.apache.cassandra.cql3.Operator
import org.apache.cassandra.cql3.statements.schema.IndexTarget._
import org.apache.cassandra.db.ReadCommand
import org.apache.cassandra.db.filter.RowFilter
import org.apache.cassandra.db.filter.RowFilter.{CustomExpression, Expression}
import org.apache.cassandra.db.marshal.UTF8Type
import org.apache.cassandra.db.rows.{BTreeRow, BufferCell, Row}
import org.apache.cassandra.schema.{ColumnMetadata, IndexMetadata, TableMetadata}
import org.apache.commons.lang3.StringUtils.isBlank
import org.apache.lucene.search.ScoreDoc

import scala.collection.JavaConverters._

/** Class for several [[Expression]] mappings between Cassandra and Lucene.
  *
  * @param tableMetadata the indexed table metadata
  * @param indexMetadata the index metadata
  * @author Andres de la Pena `[email protected]`
  */
case class ExpressionMapper(tableMetadata: TableMetadata, indexMetadata: IndexMetadata) {

  val name = indexMetadata.name
  val column = Option(indexMetadata.options.get(TARGET_OPTION_NAME)).filterNot(isBlank)
  val columns = tableMetadata.columns().asScala.toSet
  val columnDefinition = column.flatMap(name => columns.find(_.name.toString == name))

  /** Returns the first [[Search]] contained in the specified read command.
    *
    * @param command a command
    * @return the `string` JSON search represented by `command`
    * @throws IndexException if there is no such search
    */
  def search(command: ReadCommand): Search = parse(json(command))

  /** Returns the [[Search]] represented by the specified CQL expression.
    *
    * @param expression a expression
    * @return the `string` JSON search represented by `expression`
    * @throws IndexException if there is no such search
    */
  def search(expression: Expression): Search = parse(json(expression))

  /** Returns the first `string` JSON search contained in the specified read command.
    *
    * @param command a command
    * @return the `string` JSON search represented by `command`
    * @throws IndexException if there is no such expression
    */
  def json(command: ReadCommand): String = {
    command.rowFilter.getExpressions.asScala.collect {
      case e: CustomExpression if name == e.getTargetIndex.name => e.getValue
      case e if supports(e) => e.getIndexValue
    }.map(UTF8Type.instance.compose).head
  }

  /** Returns the `string` JSON search represented by the specified CQL expression.
    *
    * @param expression a expression
    * @return the `string` JSON search represented by `expression`
    * @throws IndexException if there is no such expression
    */
  def json(expression: Expression): String = {
    UTF8Type.instance.compose(
      expression match {
        case e: CustomExpression if name == e.getTargetIndex.name => e.getValue
        case e if supports(e) => e.getIndexValue
        case _ => throw new IndexException(s"Unsupported expression $expression")
      })
  }

  /** Returns if the specified expression is targeted to this index
    *
    * @param expression a CQL query expression
    * @return `true` if `expression` is targeted to this index, `false` otherwise
    */
  def supports(expression: RowFilter.Expression): Boolean = {
    supports(expression.column, expression.operator)
  }

  /** Returns if a CQL expression with the specified column definition and operator is targeted to
    * this index.
    *
    * @param definition the expression column definition
    * @param operator   the expression operator
    * @return `true` if the expression is targeted to this index, `false` otherwise
    */
  def supports(definition: ColumnMetadata, operator: Operator): Boolean = {
    operator == Operator.EQ && column.contains(definition.name.toString)
  }

  /** Returns a copy of the specified [[RowFilter]] without any Lucene expressions.
    *
    * @param filter a row filter
    * @return a copy of `filter` without Lucene expressions
    */
  def postIndexQueryFilter(filter: RowFilter): RowFilter = {
    if (column.isEmpty) return filter
    (filter /: filter.asScala) ((f, e) => if (supports(e)) f.without(e) else f)
  }

  /** Returns a new row decorating the specified row with the specified Lucene score.
    *
    * @param row      the row to be decorated
    * @param score    a Lucene search score
    * @param nowInSec the operation time in seconds
    * @return a new decorated row
    */
  def decorate(row: Row, score: ScoreDoc, nowInSec: Int): Row = {

    // Skip if there is no base column or score
    if (columnDefinition.isEmpty) return row

    // Copy row
    val builder = BTreeRow.unsortedBuilder()
    builder.newRow(row.clustering())
    builder.addRowDeletion(row.deletion)
    builder.addPrimaryKeyLivenessInfo(row.primaryKeyLivenessInfo)
    row.cells.forEach(builder addCell _)

    // Add score cell
    val timestamp = row.primaryKeyLivenessInfo.timestamp
    val scoreCellValue = UTF8Type.instance.decompose(score.score.toString)
    builder.addCell(BufferCell.live(columnDefinition.get, timestamp, scoreCellValue))

    builder.build
  }
}

/** Companion object for [[ExpressionMapper]]. */
object ExpressionMapper {

  def parse(json: String): Search = SearchBuilder.fromJson(json).build

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy