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

zio.query.CompletedRequestMap.scala Maven / Gradle / Ivy

/*
 * Copyright 2019-2023 John A. De Goes and the ZIO Contributors
 *
 * 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 zio.query

import zio.stacktracer.TracingImplicits.disableAutoTrace
import zio.{Cause, Chunk, Exit}

import scala.collection.compat._
import scala.collection.{immutable, mutable}

/**
 * A `CompletedRequestMap` is a universally quantified mapping from requests of
 * type `Request[E, A]` to results of type `Exit[E, A]` for all types `E` and
 * `A`. The guarantee is that for any request of type `Request[E, A]`, if there
 * is a corresponding value in the map, that value is of type `Exit[E, A]`. This
 * is used by the library to support data sources that return different result
 * types for different requests while guaranteeing that results will be of the
 * type requested.
 */

sealed abstract class CompletedRequestMap { self =>

  protected val map: collection.Map[Any, Exit[Any, Any]]

  /**
   * Appends the specified result to the completed requests map.
   *
   * @deprecated
   *   Usage of this method is deprecated as it leads to performance
   *   degradation. Prefer using one of the constructors in the companion object
   *   instead
   */
  @deprecated("Use one of the constructors in the companion object instead. See scaladoc for more info", "0.7.0")
  final def ++(that: CompletedRequestMap): CompletedRequestMap =
    new CompletedRequestMap.Immutable(immutable.HashMap.from(map) ++ that.map)

  /**
   * Returns whether a result exists for the specified request.
   */
  final def contains(request: Any): Boolean =
    map.contains(request)

  /**
   * Whether the completed requests map is empty.
   */
  final def isEmpty: Boolean =
    map.isEmpty

  /**
   * Appends the specified result to the completed requests map.
   *
   * @deprecated
   *   Usage of this method is deprecated as it leads to performance
   *   degradation. Prefer using one of the constructors in the companion object
   *   instead
   */
  @deprecated("Use one of the constructors in the companion object instead. See scaladoc for more info", "0.7.0")
  final def insert[E, A](request: Request[E, A], result: Exit[E, A]): CompletedRequestMap =
    new CompletedRequestMap.Immutable(immutable.HashMap.from(map).updated(request, result))

  /**
   * Appends the specified optional result to the completed request map.
   *
   * @deprecated
   *   Usage of this method is deprecated as it leads to performance
   *   degradation. Prefer using one of the constructors in the companion object
   *   instead
   */
  @deprecated("Use one of the constructors in the companion object instead. See scaladoc for more info", "0.7.0")
  final def insertOption[E, A](request: Request[E, A], result: Exit[E, Option[A]]): CompletedRequestMap =
    result match {
      case Exit.Failure(e)       => insert(request, Exit.failCause(e))
      case Exit.Success(Some(a)) => insert(request, Exit.succeed(a))
      case Exit.Success(None)    => self
    }

  /**
   * Retrieves the result of the specified request if it exists.
   */
  final def lookup[E, A](request: Request[E, A]): Option[Exit[E, A]] =
    map.get(request).asInstanceOf[Option[Exit[E, A]]]

  /**
   * Collects all requests in a set.
   */
  final def requests: Set[Request[_, _]] =
    map.keySet.asInstanceOf[Set[Request[_, _]]]

  final override def toString: String =
    s"CompletedRequestMap(${map.mkString(", ")})"

  final private[query] def underlying: collection.Map[Request[?, ?], Exit[Any, Any]] =
    map.asInstanceOf[collection.Map[Request[?, ?], Exit[Any, Any]]]
}

object CompletedRequestMap {

  def apply[E, A](entries: (Request[E, A], Exit[E, A])*): CompletedRequestMap =
    entries match {
      case Seq()            => empty
      case Seq((req, resp)) => single(req, resp)
      case _                => fromIterable(entries)
    }

  val empty: CompletedRequestMap =
    new Immutable(immutable.HashMap.empty)

  /**
   * Combines all completed request maps into a single one.
   *
   * This method is left-associated, meaning that if a request is present in
   * multiple maps, it will be overriden by the last map in the list.
   */
  def combine(maps: Iterable[CompletedRequestMap]): CompletedRequestMap = {
    val map = Mutable.empty(maps.foldLeft(0)(_ + _.map.size))
    maps.foreach(map.addAll)
    map
  }

  /**
   * Constructs a completed requests map that "dies" all the specified requests
   * with the specified throwable
   */
  def die[E, A](requests: Chunk[Request[E, A]], error: Throwable): CompletedRequestMap = {
    val map  = Mutable.empty(requests.size)
    val exit = Exit.die(error)
    requests.foreach(map.update(_, exit))
    map
  }

  /**
   * Constructs a completed requests map that fails all the specified requests
   * with the specified error
   */
  def fail[E, A](requests: Chunk[Request[E, A]], error: E): CompletedRequestMap = {
    val map  = Mutable.empty(requests.size)
    val exit = Exit.fail(error)
    requests.foreach(map.update(_, exit))
    map
  }

  /**
   * Constructs a completed requests map that fails all the specified requests
   * with the specified cause
   */
  def failCause[E, A](requests: Chunk[Request[E, A]], cause: Cause[E]): CompletedRequestMap = {
    val map  = Mutable.empty(requests.size)
    val exit = Exit.failCause(cause)
    requests.foreach(map.update(_, exit))
    map
  }

  /**
   * Constructs a completed requests map from the specified results.
   */
  def fromIterable[E, A](iterable: Iterable[(Request[E, A], Exit[E, A])]): CompletedRequestMap =
    Mutable(mutable.HashMap.from(iterable))

  /**
   * Constructs a completed requests map from the specified optional results.
   */
  def fromIterableOption[E, A](iterable: Iterable[(Request[E, A], Exit[E, Option[A]])]): CompletedRequestMap = {
    val map = Mutable.empty(iterable.size)
    iterable.foreach {
      case (request, Exit.Failure(e))       => map.update(request, Exit.failCause(e))
      case (request, Exit.Success(Some(a))) => map.update(request, Exit.succeed(a))
      case (_, Exit.Success(None))          => ()
    }
    map
  }

  /**
   * Constructs a completed requests map an iterable of A and functions that map
   * each A to a request and a result
   *
   * @param f1
   *   function that maps each element of A to a request
   * @param f2
   *   function that maps each element of A to an Exit, e.g., Exit.succeed(_)
   */
  def fromIterableWith[E, A, B](
    iterable: Iterable[A]
  )(
    f1: A => Request[E, B],
    f2: A => Exit[E, B]
  ): CompletedRequestMap = {
    val map = Mutable.empty(iterable.size)
    iterable.foreach(req => map.update(f1(req), f2(req)))
    map
  }

  /**
   * Constructs a completed requests map containing a single entry
   */
  def single[E, A](request: Request[E, A], exit: Exit[E, A]): CompletedRequestMap =
    new Immutable(immutable.HashMap((request, exit)))

  /**
   * Unsafe API for constructing completed request maps.
   *
   * Constructing a [[CompletedRequestMap]] via these methods can improve
   * performance in some cases as they allow skipping the creation of
   * intermediate `Iterable[ Tuple2[_, _] ]`.
   *
   * NOTE: These methods are marked as unsafe because they do not check that the
   * requests and responses are of the same size. It is the responsibility of
   * the caller to ensure that the provided requests maps elements 1-to-1 to the
   * responses.
   */
  val unsafe: UnsafeApi = new UnsafeApi {}

  trait UnsafeApi {

    def fromExits[E, A](requests: Chunk[Request[E, A]], responses: Chunk[Exit[E, A]]): CompletedRequestMap =
      fromWith(requests, responses)(identity, identity)

    def fromSuccesses[E, A](requests: Chunk[Request[E, A]], responses: Chunk[A]): CompletedRequestMap =
      fromWith(requests, responses)(identity, Exit.succeed)

    def fromWith[E, A1, A2, B](requests: Chunk[A1], responses: Chunk[A2])(
      f1: A1 => Request[E, B],
      f2: A2 => Exit[E, B]
    ): CompletedRequestMap = {
      val size  = requests.size min responses.size
      val map   = Mutable.empty(size)
      val reqs  = requests.chunkIterator
      val resps = responses.chunkIterator
      var i     = 0
      while (i < size) {
        map.update(f1(reqs.nextAt(i)), f2(resps.nextAt(i)))
        i += 1
      }
      map
    }

  }

  final private class Immutable(override protected val map: immutable.HashMap[Any, Exit[Any, Any]])
      extends CompletedRequestMap

  final private[query] class Mutable private (
    override protected val map: mutable.HashMap[Any, Exit[Any, Any]]
  ) extends CompletedRequestMap { self =>
    import UtilsVersionSpecific._

    def addAll(that: CompletedRequestMap): Unit =
      if (!that.isEmpty) self.map.addAll(that.map)

    def update[E, A](request: Request[E, A], exit: Exit[E, A]): Unit =
      map.update(request, exit)

  }

  private[query] object Mutable {

    def apply(map: mutable.HashMap[Any, Exit[Any, Any]]): CompletedRequestMap.Mutable =
      new Mutable(map)

    def empty(size: Int): CompletedRequestMap.Mutable =
      new Mutable(UtilsVersionSpecific.newHashMap(size))

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy