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

com.couchbase.client.scala.query.QueryOptions.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2019 Couchbase, Inc.
 *
 * 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.couchbase.client.scala.query

import com.couchbase.client.core.annotation.SinceCouchbase
import com.couchbase.client.core.api.query.{
  CoreQueryOptions,
  CoreQueryProfile,
  CoreQueryScanConsistency
}
import com.couchbase.client.core.api.shared.CoreMutationState
import com.couchbase.client.core.cnc.RequestSpan
import com.couchbase.client.core.deps.com.fasterxml.jackson.core.JsonProcessingException
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.JsonNode
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.{ArrayNode, ObjectNode}
import com.couchbase.client.core.endpoint.http.CoreCommonOptions
import com.couchbase.client.core.error.InvalidArgumentException
import com.couchbase.client.core.json.Mapper
import com.couchbase.client.core.logging.RedactableArgument.redactUser
import com.couchbase.client.core.msg.kv.MutationToken
import com.couchbase.client.core.retry.RetryStrategy
import com.couchbase.client.core.transaction.config.CoreSingleQueryTransactionOptions
import com.couchbase.client.scala.json.{JsonArray, JsonArraySafe, JsonObject, JsonObjectSafe}
import com.couchbase.client.scala.util.DurationConversions._

import java.{lang, time}
import scala.compat.java8.OptionConverters._
import scala.concurrent.duration.Duration
import scala.jdk.CollectionConverters._

/** Customize the execution of a N1QL query.
  *
  * @author Graham Pople
  * @since 1.0.0
  */
case class QueryOptions(
    private[scala] val parameters: Option[QueryParameters] = None,
    private[scala] val clientContextId: Option[String] = None,
    private[scala] val maxParallelism: Option[Int] = None,
    private[scala] val metrics: Boolean = false,
    private[scala] val pipelineBatch: Option[Int] = None,
    private[scala] val pipelineCap: Option[Int] = None,
    private[scala] val profile: Option[QueryProfile] = None,
    private[scala] val readonly: Option[Boolean] = None,
    private[scala] val retryStrategy: Option[RetryStrategy] = None,
    private[scala] val scanCap: Option[Int] = None,
    private[scala] val scanConsistency: Option[QueryScanConsistency] = None,
    private[scala] val consistentWith: Option[Seq[MutationToken]] = None,
    private[scala] val timeout: Option[Duration] = None,
    private[scala] val adhoc: Boolean = true,
    private[scala] val deferredException: Option[RuntimeException] = None,
    private[scala] val parentSpan: Option[RequestSpan] = None,
    private[scala] val raw: Option[Map[String, Any]] = None,
    private[scala] val flexIndex: Boolean = false,
    @SinceCouchbase("7.1") private[scala] val preserveExpiry: Option[Boolean] = None,
    @SinceCouchbase("7.6") private[scala] val useReplica: Option[Boolean] = None
) {

  /** Sets the parent `RequestSpan`.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def parentSpan(value: RequestSpan): QueryOptions = {
    copy(parentSpan = Some(value))
  }

  /** Provides named or positional parameters, for queries parameterised that way.
    *
    * See [[QueryParameters]] for details.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def parameters(values: QueryParameters): QueryOptions = {
    val de = deferredException.orElse(values match {
      case QueryParameters.Named(params) => checkTypes(params.values)
      case v: QueryParameters.Positional => checkTypes(v.parameters)
      case _                             => None
    })
    copy(
      parameters = Some(values),
      deferredException = de
    )
  }

  private def checkTypes(in: Iterable[Any]): Option[RuntimeException] = {
    var out: Option[RuntimeException] = None

    in.foreach(value => {
      if (value != null) {
        value match {
          case _: String         =>
          case _: Int            =>
          case _: Long           =>
          case _: Double         =>
          case _: Float          =>
          case _: Short          =>
          case _: Boolean        =>
          case _: JsonObject     =>
          case _: JsonObjectSafe =>
          case _: JsonArray      =>
          case _: JsonArraySafe  =>
          case _ =>
            out = Some(
              new IllegalArgumentException(s"Value '${redactUser(value)}' is not a valid JSON type")
            )
        }
      }
    })

    out
  }

  /** Adds a client context ID to the request, that will be sent back in the response, allowing clients
    * to meaningfully trace requests/responses when many are exchanged.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def clientContextId(contextId: String): QueryOptions = copy(clientContextId = Option(contextId))

  /** Allows to override the default maximum parallelism for the query execution on the server side.
    *
    * @param maxParallelism the maximum parallelism for this query, 0 or negative values disable it.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def maxParallelism(maxParallelism: Int): QueryOptions =
    copy(maxParallelism = Option(maxParallelism))

  /** Advanced: Maximum number of items each execution operator can buffer between various operators.
    *
    * @param pipelineCap the pipeline_cap param.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def pipelineCap(pipelineCap: Int): QueryOptions = copy(pipelineCap = Some(pipelineCap))

  /** Advanced: Controls the number of items execution operators can batch for Fetch from the KV.
    *
    * @param pipelineBatch the pipeline_batch param.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def pipelineBatch(pipelineBatch: Int): QueryOptions = copy(pipelineBatch = Some(pipelineBatch))

  /** Controls where metrics are returned by the server.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def metrics(metrics: Boolean): QueryOptions = copy(metrics = metrics)

  /** Set the profiling information level for query execution
    *
    * This is an Enterprise Edition feature.  On Community Edition the parameter will be accepted, but no profiling
    * information returned.
    *
    * @param profile the query profile level to be used
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def profile(profile: QueryProfile): QueryOptions = copy(profile = Option(profile))

  /** If set to true, it will signal the query engine on the server that only non-data modifying requests
    * are allowed. Note that this rule is enforced on the server and not the SDK side.
    *
    * Controls whether a query can change a resulting record set.
    *
    * If readonly is true, then the following statements are not allowed:
    *  - CREATE INDEX
    *  - DROP INDEX
    *  - INSERT
    *  - MERGE
    *  - UPDATE
    *  - UPSERT
    *  - DELETE
    *
    * @param readonly true if readonly should be forced, false is the default and will use the server side
    *                        default.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def readonly(readonly: Boolean): QueryOptions = copy(readonly = Option(readonly))

  /** Advanced: Maximum buffered channel size between the indexer client and the query service for index scans.
    *
    * This parameter controls when to use scan backfill. Use 0 or a negative number to disable.
    *
    * @param scanCap the scan_cap param, use 0 or negative number to disable.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def scanCap(scanCap: Int): QueryOptions = copy(scanCap = Option(scanCap))

  /** Scan consistency for the query
    *
    * @param scanConsistency the index scan consistency to be used
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def scanConsistency(scanConsistency: QueryScanConsistency): QueryOptions =
    copy(scanConsistency = Some(scanConsistency))

  /** Sets a maximum timeout for processing.
    *
    * @param timeout the duration of the timeout.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def timeout(timeout: Duration): QueryOptions = {
    copy(timeout = Option(timeout))
  }

  /** Sets what retry strategy to use if the operation fails.
    *
    * @param strategy the retry strategy to use
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def retryStrategy(strategy: RetryStrategy): QueryOptions = {
    copy(retryStrategy = Some(strategy))
  }

  /** If true (the default), adhoc mode is enabled: queries are just run.
    *
    * If false, adhoc mode is disabled and transparent prepared statement mode is enabled: queries
    * are first prepared so they can be executed more efficiently in the future.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def adhoc(adhoc: Boolean): QueryOptions = {
    copy(adhoc = adhoc)
  }

  /** Allows providing custom JSON key/value pairs for advanced usage.
    *
    * If available, it is recommended to use the methods on this object to customize the query. This method should
    * only be used if no such setter can be found (i.e. if an undocumented property should be set or you are using
    * an older client and a new server-configuration property has been added to the cluster).
    *
    * Note that the values will be passed through a JSON encoder, so do not provide already encoded JSON as the value. If
    * you want to pass objects or arrays, you can use `JsonObject` and `JsonArray` respectively.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def raw(raw: Map[String, Any]): QueryOptions = {
    copy(raw = Some(raw))
  }

  /** Tells the query engine to use a flex index (utilizing the search service).
    *
    * The default is false.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  def flexIndex(flexIndex: Boolean): QueryOptions = {
    copy(flexIndex = flexIndex)
  }

  /** Tells the query engine to preserve expiration values set on any documents modified by this query.
    *
    * The default is false.
    *
    * This feature works from Couchbase Server 7.1.0 onwards.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  @SinceCouchbase("7.1")
  def preserveExpiry(preserveExpiry: Boolean): QueryOptions = {
    copy(preserveExpiry = Some(preserveExpiry))
  }

  /** Tells the query engine that replicas can be used.
    *
    * The default is not set
    *
    * This feature works from Couchbase Server 7.6.0 onwards.
    *
    * @return a copy of this with the change applied, for chaining.
    */
  @SinceCouchbase("7.6")
  def useReplica(useReplica: Boolean): QueryOptions = {
    copy(useReplica = Some(useReplica))
  }

  private[scala] def toCore: CoreQueryOptions = {
    val x = this

    val t: java.util.Optional[java.time.Duration] = timeout
      .map(v => com.couchbase.client.scala.util.DurationConversions.scalaDurationToJava(v))
      .asJava
    val common = CoreCommonOptions.ofOptional(t, retryStrategy.asJava, parentSpan.asJava)

    new CoreQueryOptions {
      override def adhoc(): Boolean = x.adhoc

      override def clientContextId(): String = x.clientContextId.orNull

      override def consistentWith(): CoreMutationState = x.scanConsistency match {
        case Some(QueryScanConsistency.ConsistentWith(cw)) =>
          new CoreMutationState(cw.tokens.asJava)
        case _ => null
      }

      override def maxParallelism(): Integer = x.maxParallelism match {
        case Some(value) => value
        case _           => null
      }

      override def metrics(): Boolean = x.metrics

      override def namedParameters(): ObjectNode = parameters match {
        case Some(v: QueryParameters.Named) =>
          try {
            Mapper.convertValue(v.parameters.asJava, classOf[ObjectNode])
          } catch {
            case e: JsonProcessingException =>
              throw new InvalidArgumentException("Unable to convert named parameters", e, null)
          }
        case _ => null
      }

      override def pipelineBatch(): Integer = x.pipelineBatch match {
        case Some(value) => value
        case _           => null
      }

      override def pipelineCap(): Integer = x.pipelineCap match {
        case Some(value) => value
        case _           => null
      }

      override def positionalParameters(): ArrayNode = parameters match {
        case Some(v: QueryParameters.Positional) =>
          try {
            Mapper.convertValue(v.parameters.asJava, classOf[ArrayNode])
          } catch {
            case e: JsonProcessingException =>
              throw new InvalidArgumentException("Unable to convert named parameters", e, null)
          }
        case _ => null
      }

      override def profile(): CoreQueryProfile = x.profile match {
        case Some(QueryProfile.Off)     => CoreQueryProfile.OFF
        case Some(QueryProfile.Phases)  => CoreQueryProfile.PHASES
        case Some(QueryProfile.Timings) => CoreQueryProfile.TIMINGS
        case _                          => null
      }

      override def raw(): JsonNode = x.raw match {
        case Some(value) => Mapper.convertValue(value.asJava, classOf[ObjectNode])
        case _           => null
      }

      override def readonly(): Boolean = x.readonly.getOrElse(false)

      override def scanWait(): time.Duration = x.scanConsistency match {
        case Some(QueryScanConsistency.RequestPlus(Some(scanWait))) => scanWait
        case _                                                      => null
      }

      override def scanCap(): Integer = x.scanCap match {
        case Some(value) => value
        case _           => null
      }

      override def scanConsistency(): CoreQueryScanConsistency = x.scanConsistency match {
        case Some(_: QueryScanConsistency.RequestPlus) => CoreQueryScanConsistency.REQUEST_PLUS
        case Some(QueryScanConsistency.NotBounded)     => CoreQueryScanConsistency.NOT_BOUNDED
        case _                                         => null
      }

      override def flexIndex(): Boolean = x.flexIndex

      override def preserveExpiry(): lang.Boolean = x.preserveExpiry match {
        case Some(value) => value
        case _           => null
      }

      override def asTransactionOptions(): CoreSingleQueryTransactionOptions = null

      override def commonOptions(): CoreCommonOptions = common

      override def useReplica(): lang.Boolean = x.useReplica match {
        case Some(value) => value
        case _           => null
      }
    }
  }
}

object QueryOptions {
  def apply() = new QueryOptions()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy