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

sangria.execution.deferred.FetcherBasedDeferredResolver.scala Maven / Gradle / Ivy

There is a newer version: 2.0.1
Show newest version
package sangria.execution.deferred

import sangria.execution.DeferredWithInfo

import scala.annotation.unchecked.uncheckedVariance
import scala.collection.immutable.VectorBuilder
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}
import scala.collection.mutable.{Map => MutableMap, Set => MutableSet}

class FetcherBasedDeferredResolver[-Ctx](fetchers: Vector[Fetcher[Ctx, _, _, _]], fallback: Option[DeferredResolver[Ctx]]) extends DeferredResolver[Ctx] {
  private val fetchersMap: Map[AnyRef, Fetcher[Ctx, _, _, _]] @uncheckedVariance =
    fetchers.map(f => f -> f).toMap

  override def groupDeferred[T <: DeferredWithInfo](deferred: Vector[T]) =
    fallback match {
      case Some(f) => f.groupDeferred(deferred)
      case None => super.groupDeferred(deferred)
    }

  override val includeDeferredFromField =
    fallback.flatMap(_.includeDeferredFromField) orElse super.includeDeferredFromField

  override def initialQueryState = fetchers.flatMap(f => f.config.cacheConfig.map(cacheFn => (f: AnyRef) -> cacheFn())).toMap

  def resolve(deferred: Vector[Deferred[Any]], ctx: Ctx, queryState: Any)(implicit ec: ExecutionContext) =  {
    val fetcherCaches = queryState.asInstanceOf[Map[AnyRef, FetcherCache]]

    val grouped = deferred groupBy {
      case FetcherDeferredOne(s, _) => fetchersMap.get(s)
      case FetcherDeferredOpt(s, _) => fetchersMap.get(s)
      case FetcherDeferredOptOpt(s, _) => fetchersMap.get(s)
      case FetcherDeferredSeq(s, _) => fetchersMap.get(s)
      case FetcherDeferredSeqOpt(s, _) => fetchersMap.get(s)
      case FetcherDeferredSeqOptExplicit(s, _) => fetchersMap.get(s)
      case FetcherDeferredRel(s, _, _) => fetchersMap.get(s)
      case FetcherDeferredRelOpt(s, _, _) => fetchersMap.get(s)
      case FetcherDeferredRelSeq(s, _, _) => fetchersMap.get(s)
      case FetcherDeferredRelSeqMany(s, _, _) => fetchersMap.get(s)
      case _ => None
    }

    val resolved = MutableMap[Deferred[Any], Future[Any]]()

    grouped foreach {
      case (Some(fetcher), d) =>
        val fetcherCache = fetcherCaches.get(fetcher)
        val (relDeferred, normalDeferred) = d partition fetcher.isRel
        val fetcherContext = FetcherContext[Ctx](ctx, fetcher, fetcherCache, fetcherCaches, fetchers)

        resolveRelations(fetcherContext, relDeferred, resolved)
        resolveEntities(fetcherContext, normalDeferred, resolved)

      case (None, deferred) =>
        fallback match {
          case Some(f) =>
            val res = f.resolve(deferred, ctx, queryState)

            for (i <- deferred.indices) {
              resolved(deferred(i)) = res(i)
            }
          case None =>
            deferred.foreach(d => resolved(d) = Future.failed(UnsupportedDeferError(d)))

        }
    }

    deferred map (d => resolved(d))
  }

  private def resolveRelations(
    ctx: FetcherContext[Ctx] @uncheckedVariance,
    deferredToResolve: Vector[Deferred[Any]],
    resolved: MutableMap[Deferred[Any], Future[Any]]
  )(implicit ec: ExecutionContext) = {
    val f = ctx.fetcher.asInstanceOf[Fetcher[Ctx, Any, Any, Any]]
    val relIds = ctx.fetcher.relIds(deferredToResolve)

    val (nonCachedIds, cachedResults) = partitionCachedRel(ctx.cache, relIds)

    val groupedRelIds = ctx.fetcher.config.maxBatchSizeConfig match {
      case Some(size) => nonCachedIds.map { case (rel, ids) => (rel, ids.grouped(size))}
      case None => nonCachedIds.map { case (rel, ids) => (rel, Iterator.single(ids)) }
    }

    val results = groupedRelIds flatMap { case (rel, groupIds) =>
      groupIds map { group =>
        if (group.nonEmpty)
          f.fetchRel(ctx, RelationIds(Map(rel -> group)))
            .map(groupAndCacheRelations(ctx, Map(rel -> group), _))
        else
          Future.successful(MutableMap.empty[Relation[Any, Any, Any], MutableMap[Any, Seq[Any]]])
      }
    }

    val futureRes = Future.sequence(results).map { allResults =>
      val byRel = MutableMap[Relation[Any, _, _], MutableMap[Any, Seq[Any]]]()

      allResults.foreach(relResult => relResult.foreach{ case (rel, v) =>
        if (byRel.contains(rel))
          byRel(rel) ++= v
        else
          byRel(rel) = v
      })

      byRel
    }

    deferredToResolve foreach { deferred =>
      resolved(deferred) = futureRes.map { m =>
        val f = ctx.fetcher.asInstanceOf[Fetcher[Any, Any, Any, Any]]

        deferred match {
          case FetcherDeferredRel(_, rel, relId) if cachedResults.contains(rel) && cachedResults(rel).contains(relId) =>
            cachedResults(rel)(relId).headOption match {
              case Some(head) => head
              case None => throw AbsentDeferredRelValueError(f, deferred, rel, relId)
            }

          case FetcherDeferredRel(_, rel, relId) if m.contains(rel) && m(rel).contains(relId) =>
            m(rel)(relId).headOption match {
              case Some(head) => head
              case None => throw AbsentDeferredRelValueError(f, deferred, rel, relId)
            }

          case FetcherDeferredRel(_, rel, relId) =>
            throw AbsentDeferredRelValueError(f, deferred, rel, relId)

          case FetcherDeferredRelOpt(_, rel, relId) if cachedResults.contains(rel) && cachedResults(rel).contains(relId) =>
            cachedResults(rel)(relId).headOption

          case FetcherDeferredRelOpt(_, rel, relId) if m.contains(rel) && m(rel).contains(relId) =>
            m(rel)(relId).headOption

          case FetcherDeferredRelOpt(_, _, _) =>
            None

          case FetcherDeferredRelSeq(_, rel, relId) if cachedResults.contains(rel) && cachedResults(rel).contains(relId) =>
            cachedResults(rel)(relId)

          case FetcherDeferredRelSeq(_, rel, relId) if m.contains(rel) && m(rel).contains(relId) =>
            m(rel)(relId)

          case FetcherDeferredRelSeq(_, _, _) =>
            Vector.empty

          case FetcherDeferredRelSeqMany(_, rel, relIds) if cachedResults.contains(rel) =>
            removeDuplicates(f, relIds.flatMap(relId => cachedResults(rel).getOrElse(relId, Vector.empty)))

          case FetcherDeferredRelSeqMany(_, rel, relIds) if m.contains(rel) =>
            removeDuplicates(f, relIds.flatMap(relId => m(rel).getOrElse(relId, Vector.empty)))

          case FetcherDeferredRelSeqMany(_, _, _) =>
            Vector.empty
        }
      }
    }
  }

  private def removeDuplicates(fetcher: Fetcher[Any, Any, Any, Any], values: Seq[Any]) = {
    val seen = MutableSet[Any]()

    values.filter { v =>
      val id = fetcher.idFn(v)

      if (seen contains id) false
      else {
        seen += id
        true
      }
    }
  }

  private def resolveEntities(
    ctx: FetcherContext[Ctx] @uncheckedVariance,
    deferredToResolve: Vector[Deferred[Any]],
    resolved: MutableMap[Deferred[Any], Future[Any]]
  )(implicit ec: ExecutionContext) = {
    val f = ctx.fetcher.asInstanceOf[Fetcher[Ctx, Any, Any, Any]]
    val ids = ctx.fetcher.ids(deferredToResolve)
    val (nonCachedIds, cachedResults) = partitionCached(ctx.cache, ids)

    val groupedIds = ctx.fetcher.config.maxBatchSizeConfig match {
      case Some(size) => nonCachedIds.grouped(size)
      case None => Iterator(nonCachedIds)
    }

    val results = groupedIds map { group =>
      if (group.nonEmpty)
        f.fetch(ctx, group).map(r => group -> Success(r): (Vector[Any], Try[Seq[Any]])).recover {case e => group -> Failure(e)}
      else
        Future.successful(group -> Success(Seq.empty))
    }

    val futureRes = Future.sequence(results).map { allResults =>
      val byId = MutableMap[Any, Any]() // can contain either exception or actual value! (using `Any` to avoid unnecessary boxing)

      allResults.toVector.foreach { case (group, groupResult) =>
        groupResult match {
          case Success(values) =>
            values.foreach(v => byId(f.idFn(v)) = v)

          case Failure(e) =>
            group.foreach(id => byId(id) = e)
        }
      }

      byId
    }

    deferredToResolve foreach { deferred =>
      resolved(deferred) = futureRes.map { m =>
        val f = ctx.fetcher.asInstanceOf[Fetcher[Any, Any, Any, Any]]

        def updateCache[T](id: Any, v: T): T = ctx.cache match {
          case Some(cache) =>
            cache.update(id, v)

            v
          case None => v
        }

        deferred match {
          case FetcherDeferredOne(_, id) if cachedResults contains id =>
            cachedResults(id)

          case FetcherDeferredOne(_, id) =>
            m.get(id) match {
              case Some(t: Throwable) => throw t
              case Some(v) => updateCache(id, v)
              case None => throw AbsentDeferredValueError(f, deferred, id)
            }

          case FetcherDeferredOpt(_, id) if cachedResults contains id =>
            cachedResults.get(id)

          case FetcherDeferredOpt(_, id) =>
            m.get(id) match {
              case Some(t: Throwable) => throw t
              case v =>
                v foreach (updateCache(id, _))

                v
            }

          case FetcherDeferredOptOpt(_, None) =>
            None

          case FetcherDeferredOptOpt(_, Some(id)) if cachedResults contains id =>
            cachedResults.get(id)

          case FetcherDeferredOptOpt(_, Some(id)) =>
            m.get(id) match {
              case Some(t: Throwable) => throw t
              case v =>
                v foreach (updateCache(id, _))

                v
            }

          case FetcherDeferredSeq(_, ids) =>
            ids map { id =>
              if (cachedResults contains id)
                cachedResults(id)
              else
                m.get(id) match {
                  case Some(t: Throwable) => throw t
                  case Some(v) => updateCache(id, v)
                  case None => throw AbsentDeferredValueError(f, deferred, id)
                }
            }

          case FetcherDeferredSeqOpt(_, ids) =>
            ids flatMap { id =>
              if (cachedResults contains id)
                cachedResults.get(id)
              else
                m.get(id) match {
                  case Some(t: Throwable) => throw t
                  case v =>
                    v foreach (updateCache(id, _))

                    v
                }
            }

          case FetcherDeferredSeqOptExplicit(_, ids) =>
            ids map { id =>
              if (cachedResults contains id)
                cachedResults.get(id)
              else
                m.get(id) match {
                  case Some(t: Throwable) => throw t
                  case v =>
                    v foreach (updateCache(id, _))

                    v
                }
            }
        }
      }
    }
  }

  private def partitionCached(cache: Option[FetcherCache], ids: Vector[Any]): (Vector[Any], MutableMap[Any, Any]) =
    cache match {
      case Some(c) =>
        val misses = new VectorBuilder[Any]
        val hits = MutableMap[Any, Any]()

        ids.foreach { id =>
          c.get(id) match {
            case Some(v) => hits(id) = v
            case None => misses += id
          }
        }

        misses.result() -> hits

      case None =>
        ids -> MutableMap.empty
    }

  private def partitionCachedRel(cache: Option[FetcherCache], ids: Map[Relation[Any, Any, Any], Vector[Any]]): (Map[Relation[Any, Any, Any], Vector[Any]], MutableMap[Relation[Any, Any, Any], MutableMap[Any, Seq[Any]]]) =
    cache match {
      case Some(c) =>
        val misses = MutableMap[Relation[Any, Any, Any], MutableSet[Any]]()
        val hits = MutableMap[Relation[Any, Any, Any], MutableMap[Any, Seq[Any]]]()

        def addHit(rel: Relation[Any, Any, Any], relId: Any, res: Seq[Any]) =
          hits.get(rel) match {
            case Some(map) => map(relId) = res
            case None =>
              val map = MutableMap[Any, Seq[Any]]()
              map(relId) = res
              hits(rel) = map
          }

        def addMiss(rel: Relation[Any, Any, Any], relId: Any) =
          misses.get(rel) match {
            case Some(set) => set += relId
            case None =>
              val set = MutableSet[Any]()
              set += relId
              misses(rel) = set
          }

        ids.foreach { case (rel, ids) =>
          ids foreach { relId =>
            c.getRel(rel, relId) match {
              case Some(v) => addHit(rel, relId, v)
              case None => addMiss(rel, relId)
            }
          }
        }

        misses.map{case (k, v) => k -> v.toVector}.toMap -> hits

      case None =>
        ids -> MutableMap.empty
    }

  private def groupAndCacheRelations(ctx: FetcherContext[_], relIds: Map[Relation[Any, Any, Any], Vector[Any]], result: Seq[Any]): MutableMap[Relation[Any, Any, Any], MutableMap[Any, Seq[Any]]] = {
    val grouped = MutableMap[Relation[Any, Any, Any], MutableMap[Any, Seq[Any]]]()

    def updateCache[T](rel: Relation[Any, Any, Any], relId: Any, v: Seq[T]): Seq[T] = ctx.cache match {
      case Some(cache) =>
        cache.updateRel(rel, relId, ctx.fetcher.idFn.asInstanceOf[T => Any], v)

        v
      case None => v
    }

    relIds foreach { case (rel, relIdsForRel) =>
      val identified = MutableMap[Any, VectorBuilder[Any]]()

      result foreach { res =>
        val relIds = rel.relIds(res)
        val mappedRes = rel.map(res)

        relIds foreach { relId =>
          identified.get(relId) match {
            case Some(builder) => builder += mappedRes
            case None =>
              val builder = new VectorBuilder[Any]
              builder += mappedRes
              identified(relId) = builder
          }
        }
      }

      relIdsForRel foreach { relId =>
        val res = identified.get(relId).fold(Vector.empty[Any])(_.result())

        updateCache(rel, relId, res)

        grouped.get(rel) match {
          case Some(map) => map(relId) = res
          case None =>
            val map = MutableMap[Any, Seq[Any]]()
            map(relId) = res
            grouped(rel) = map
        }
      }
    }

    grouped
  }
}

case class AbsentDeferredValueError(fetcher: Fetcher[Any, Any, Any, Any], deferred: Deferred[Any], id: Any)
  extends Exception(s"Fetcher has not resolved non-optional ID '$id'.")

case class AbsentDeferredRelValueError(fetcher: Fetcher[Any, Any, Any, Any], deferred: Deferred[Any], rel: Relation[Any, Any, Any], relId: Any)
  extends Exception(s"Fetcher has not resolved non-optional relation ID '$relId' for relation '$rel'.")




© 2015 - 2024 Weber Informatics LLC | Privacy Policy