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

com.couchbase.client.scala.query.handlers.AnalyticsHandler.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.handlers

import com.couchbase.client.core.Core
import com.couchbase.client.core.cnc.TracingIdentifiers
import com.couchbase.client.core.deps.io.netty.util.CharsetUtil
import com.couchbase.client.core.error.ErrorCodeAndMessage
import com.couchbase.client.core.msg.analytics.AnalyticsRequest
import com.couchbase.client.scala.HandlerBasicParams
import com.couchbase.client.scala.analytics.{
  AnalyticsMetaData,
  AnalyticsMetrics,
  AnalyticsOptions,
  AnalyticsResult,
  AnalyticsStatus,
  AnalyticsWarning,
  ReactiveAnalyticsResult
}
import com.couchbase.client.scala.env.ClusterEnvironment
import com.couchbase.client.scala.transformers.JacksonTransformers
import com.couchbase.client.scala.util.{DurationConversions, FutureConversions, Validate}
import reactor.core.scala.publisher.SMono

import scala.concurrent.Future
import scala.concurrent.duration.Duration
import scala.util.{Failure, Success, Try}
import scala.compat.java8.OptionConverters._
import scala.concurrent.duration.Duration
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
import scala.jdk.CollectionConverters._

/**
  * Handles requests and responses for Analytics operations.
  *
  * @author Graham Pople
  * @since 1.0.0
  */
private[scala] class AnalyticsHandler(hp: HandlerBasicParams) {
  import DurationConversions._

  def request[T](
      statement: String,
      options: AnalyticsOptions,
      core: Core,
      environment: ClusterEnvironment,
      bucket: Option[String],
      scope: Option[String]
  ): Try[AnalyticsRequest] = {

    val validations: Try[AnalyticsRequest] = for {
      _ <- Validate.notNullOrEmpty(statement, "statement")
      _ <- Validate.notNull(options, "options")
      _ <- Validate.optNotNull(options.parameters, "parameters")
      _ <- Validate.optNotNull(options.clientContextId, "clientContextId")
      _ <- Validate.optNotNull(options.retryStrategy, "retryStrategy")
      _ <- Validate.optNotNull(options.timeout, "timeout")
      _ <- Validate.notNull(options.priority, "priority")
      _ <- Validate.optNotNull(options.readonly, "readonly")
      _ <- Validate.optNotNull(options.parentSpan, "parentSpan")
    } yield null

    if (validations.isFailure) {
      validations
    } else {
      val params = options.encode()
      params.put("statement", statement)

      if (bucket.isDefined && scope.isDefined) {
        params.put("query_context", AnalyticsRequest.queryContext(bucket.get, scope.get))
      }

      Try(JacksonTransformers.MAPPER.writeValueAsString(params)).map(queryStr => {
        val queryBytes = queryStr.getBytes(CharsetUtil.UTF_8)

        val timeout: Duration =
          options.timeout.getOrElse(environment.timeoutConfig.analyticsTimeout())
        val retryStrategy = options.retryStrategy.getOrElse(environment.retryStrategy)

        new AnalyticsRequest(
          timeout,
          core.context(),
          retryStrategy,
          core.context().authenticator(),
          queryBytes,
          if (options.priority) -1 else 0,
          options.readonly.getOrElse(false),
          params.str("client_context_id"),
          statement,
          hp.tracer
            .requestSpan(TracingIdentifiers.SPAN_REQUEST_ANALYTICS, options.parentSpan.orNull),
          bucket.orNull,
          scope.orNull,
          true,
          options._endpointIdx
        )
      })
    }
  }

  def queryAsync(
      request: AnalyticsRequest
  )(implicit ec: ExecutionContext): Future[AnalyticsResult] = {
    hp.core.send(request)

    val ret: Future[AnalyticsResult] = FutureConversions
      .javaCFToScalaMono(request, request.response(), propagateCancellation = true)
      .flatMap(
        response =>
          FutureConversions
            .javaFluxToScalaFlux(response.rows())
            .collectSeq()
            .flatMap(
              rows =>
                FutureConversions
                  .javaMonoToScalaMono(response.trailer())
                  .map(trailer => {
                    val warnings: collection.Seq[AnalyticsWarning] = trailer.warnings.asScala
                      .map(
                        warnings =>
                          ErrorCodeAndMessage
                            .fromJsonArray(warnings)
                            .asScala
                            .map(codeAndMessage => AnalyticsWarning(codeAndMessage))
                      )
                      .getOrElse(Seq.empty)

                    AnalyticsResult(
                      rows,
                      AnalyticsMetaData(
                        response.header().requestId(),
                        response.header().clientContextId().orElse(""),
                        response.header().signature.asScala,
                        AnalyticsMetrics.fromBytes(trailer.metrics),
                        warnings,
                        AnalyticsStatus.from(trailer.status)
                      )
                    )
                  })
            )
      )
      .toFuture

    ret onComplete {
      case Success(_)   => request.context.logicallyComplete()
      case Failure(err) => request.context.logicallyComplete(err)
    }
    ret
  }

  def queryReactive(request: AnalyticsRequest): SMono[ReactiveAnalyticsResult] = {
    SMono.defer(() => {
      hp.core.send(request)

      FutureConversions
        .javaCFToScalaMono(request, request.response(), false)
        .map(response => {
          val meta: SMono[AnalyticsMetaData] = FutureConversions
            .javaMonoToScalaMono(response.trailer())
            .map(trailer => {
              val warnings: collection.Seq[AnalyticsWarning] = trailer.warnings.asScala
                .map(
                  warnings =>
                    ErrorCodeAndMessage
                      .fromJsonArray(warnings)
                      .asScala
                      .map(codeAndMessage => AnalyticsWarning(codeAndMessage))
                )
                .getOrElse(Seq.empty)

              AnalyticsMetaData(
                response.header().requestId(),
                response.header().clientContextId().orElse(""),
                response.header().signature.asScala,
                AnalyticsMetrics.fromBytes(trailer.metrics()),
                warnings,
                AnalyticsStatus.from(trailer.status)
              )
            })
            .doOnNext(_ => request.context.logicallyComplete)
            .doOnError(err => request.context().logicallyComplete(err))

          val rows = FutureConversions.javaFluxToScalaFlux(response.rows())

          ReactiveAnalyticsResult(
            rows,
            meta
          )
        })
    })
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy