Skip to content

Commit

Permalink
Added support for relation-based deferred values
Browse files Browse the repository at this point in the history
  • Loading branch information
OlegIlyenko committed Oct 2, 2016
1 parent cee7e4c commit 8f97637
Show file tree
Hide file tree
Showing 6 changed files with 621 additions and 123 deletions.
3 changes: 2 additions & 1 deletion src/main/scala/sangria/execution/Resolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,8 @@ class Resolver[Ctx](
sourceMapper,
deprecationTracker,
astFields,
path)
path,
deferredResolverState)

if (allFields.exists(_.deprecationReason.isDefined))
deprecationTracker.deprecatedFieldUsed(ctx)
Expand Down
159 changes: 133 additions & 26 deletions src/main/scala/sangria/execution/deferred/Fetcher.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,45 @@
package sangria.execution.deferred

import scala.collection.mutable.{Set MutableSet}
import scala.collection.mutable.{Set MutableSet, Map MutableMap}
import scala.concurrent.Future
import scala.util.Try

class Fetcher[Ctx, Id, Res](val idFn: Res Id, val fetch: (Ctx, Seq[Id]) Future[Seq[Res]], val maxBatchSize: Option[Int], val cache: Option[() FetcherCache]) {
class Fetcher[Ctx, Res, Id](
val idFn: Res Id,
val fetch: (Ctx, Seq[Id]) Future[Seq[Res]],
val fetchRel: (Ctx, Map[Relation[Res, _], Seq[Id]]) Future[Seq[Res]],
val config: FetcherConfig
) {
def defer(id: Id) = FetcherDeferredOne(this, id)
def deferOpt(id: Id) = FetcherDeferredOpt(this, id)
def deferSeq(ids: Seq[Id]) = FetcherDeferredSeq(this, ids)
def deferSeqOpt(ids: Seq[Id]) = FetcherDeferredSeqOpt(this, ids)

def deferRel[RelId](rel: Relation[Res, RelId], relId: RelId) = FetcherDeferredRel(this, rel, relId)
def deferRelOpt[RelId](rel: Relation[Res, RelId], relId: RelId) = FetcherDeferredRelOpt(this, rel, relId)
def deferRelSeq[RelId](rel: Relation[Res, RelId], relId: RelId) = FetcherDeferredRelSeq(this, rel, relId)
def deferRelSeqMany[RelId](rel: Relation[Res, RelId], relIds: Seq[RelId]) = FetcherDeferredRelSeqMany(this, rel, relIds)

def clearCache(deferredResolverState: Any) =
findCache(deferredResolverState)(_.clear())

def clearCachedId(deferredResolverState: Any, id: Id) =
findCache(deferredResolverState)(_.clearId(id))

def clearCachedRel(deferredResolverState: Any, rel: Relation[Res, _]) =
findCache(deferredResolverState)(_.clearRel(rel))

def clearCachedRelId[RelId](deferredResolverState: Any, rel: Relation[Res, RelId], relId: RelId) =
findCache(deferredResolverState)(_.clearRelId(rel, relId))

private def findCache(deferredResolverState: Any)(op: FetcherCache Unit): Unit =
deferredResolverState match {
case map: Map[AnyRef, FetcherCache] @unchecked map.get(this) match {
case Some(cache) op(cache)
case None // just ignore
}
case _ // just ignore
}

def ids(deferred: Vector[Deferred[Any]]): Vector[Id] = {
val allIds = MutableSet[Id]()

Expand All @@ -23,48 +53,125 @@ class Fetcher[Ctx, Id, Res](val idFn: Res ⇒ Id, val fetch: (Ctx, Seq[Id]) ⇒

allIds.toVector
}

def relIds(deferred: Vector[Deferred[Any]]): Map[Relation[Any, Any], Vector[Any]] = {
val allIds = MutableMap[Relation[Any, Any], MutableSet[Any]]()

def addToSet(rel: Relation[Any, Any], id: Any) =
allIds.get(rel) match {
case Some(set) set += id
case None
val set = MutableSet[Any]()
set += id
allIds(rel) = set
}

deferred foreach {
case FetcherDeferredRel(s, rel, relId) if s eq this addToSet(rel, relId)
case FetcherDeferredRelOpt(s, rel, relId) if s eq this addToSet(rel, relId)
case FetcherDeferredRelSeq(s, rel, relId) if s eq this addToSet(rel, relId)
case FetcherDeferredRelSeqMany(s, rel, relIds) if s eq this relIds.foreach(addToSet(rel, _))
case _ // skip
}

allIds.map{case (k, v) k v.toVector}.toMap
}

def isRel(deferred: Deferred[Any]) = deferred match {
case FetcherDeferredRel(_, _, _) |
FetcherDeferredRelOpt(_, _, _) |
FetcherDeferredRelSeq(_, _, _) |
FetcherDeferredRelSeqMany(_, _, _) true
case _ false
}
}

object Fetcher {
def apply[Ctx, Id, Res](fetch: (Ctx, Seq[Id]) Future[Seq[Res]])(implicit id: HasId[Res, Id]): Fetcher[Ctx, Id, Res] =
new Fetcher[Ctx, Id, Res](i id.id(i), fetch, None, None)
private def relationUnsupported[Ctx, Id, Res]: (Ctx, Map[Relation[Res, _], Seq[Id]]) Future[Seq[Res]] =
(_, _) throw new RelationNotSupportedError

def apply[Ctx, Res, Id](fetch: (Ctx, Seq[Id]) Future[Seq[Res]], config: FetcherConfig = FetcherConfig.empty)(implicit id: HasId[Res, Id]): Fetcher[Ctx, Res, Id] =
new Fetcher[Ctx, Res, Id](i id.id(i), fetch, relationUnsupported, config)

def apply[Ctx, Id, Res](maxBatchSize: Int, fetch: (Ctx, Seq[Id]) Future[Seq[Res]])(implicit id: HasId[Res, Id]): Fetcher[Ctx, Id, Res] =
new Fetcher[Ctx, Id, Res](i id.id(i), fetch, Some(maxBatchSize), None)
def rel[Ctx, Res, Id](fetch: (Ctx, Seq[Id]) Future[Seq[Res]], fetchRel: (Ctx, Map[Relation[Res, _], Seq[Id]]) Future[Seq[Res]], config: FetcherConfig = FetcherConfig.empty)(implicit id: HasId[Res, Id]): Fetcher[Ctx, Res, Id] =
new Fetcher[Ctx, Res, Id](i id.id(i), fetch, fetchRel, config)

def sync[Ctx, Id, Res](fetch: (Ctx, Seq[Id]) Seq[Res])(implicit id: HasId[Res, Id]): Fetcher[Ctx, Id, Res] =
new Fetcher[Ctx, Id, Res](i id.id(i), (ctx: Ctx, ids: Seq[Id]) Future.fromTry(Try(fetch(ctx, ids))), None, None)
def caching[Ctx, Res, Id](fetch: (Ctx, Seq[Id]) Future[Seq[Res]], config: FetcherConfig = FetcherConfig.caching)(implicit id: HasId[Res, Id]): Fetcher[Ctx, Res, Id] =
new Fetcher[Ctx, Res, Id](i id.id(i), fetch, relationUnsupported, config)

def sync[Ctx, Id, Res](maxBatchSize: Int, fetch: (Ctx, Seq[Id]) Seq[Res])(implicit id: HasId[Res, Id]): Fetcher[Ctx, Id, Res] =
new Fetcher[Ctx, Id, Res](i id.id(i), (ctx: Ctx, ids: Seq[Id]) Future.fromTry(Try(fetch(ctx, ids))), Some(maxBatchSize), None)
def relCaching[Ctx, Res, Id](fetch: (Ctx, Seq[Id]) Future[Seq[Res]], fetchRel: (Ctx, Map[Relation[Res, _], Seq[Id]]) Future[Seq[Res]], config: FetcherConfig = FetcherConfig.caching)(implicit id: HasId[Res, Id]): Fetcher[Ctx, Res, Id] =
new Fetcher[Ctx, Res, Id](i id.id(i), fetch, fetchRel, config)
}

def caching[Ctx, Id, Res](fetch: (Ctx, Seq[Id]) Future[Seq[Res]])(implicit id: HasId[Res, Id]): Fetcher[Ctx, Id, Res] =
new Fetcher[Ctx, Id, Res](i id.id(i), fetch, None, Some(() FetcherCache.simple))
case class FetcherConfig(cacheConfig: Option[() FetcherCache] = None, maxBatchSizeConfig: Option[Int] = None) {
def caching = copy(cacheConfig = Some(() FetcherCache.simple))
def caching(cache: FetcherCache) = copy(cacheConfig = Some(() cache))

def caching[Ctx, Id, Res](maxBatchSize: Int, fetch: (Ctx, Seq[Id]) Future[Seq[Res]])(implicit id: HasId[Res, Id]): Fetcher[Ctx, Id, Res] =
new Fetcher[Ctx, Id, Res](i id.id(i), fetch, Some(maxBatchSize), Some(() FetcherCache.simple))
def maxBatchSize(size: Int) = copy(maxBatchSizeConfig = Some(size))
}

def caching[Ctx, Id, Res](cache: FetcherCache, fetch: (Ctx, Seq[Id]) Future[Seq[Res]])(implicit id: HasId[Res, Id]): Fetcher[Ctx, Id, Res] =
new Fetcher[Ctx, Id, Res](i id.id(i), fetch, None, Some(() cache))
object FetcherConfig {
val empty = FetcherConfig()

def caching[Ctx, Id, Res](cache: FetcherCache, maxBatchSize: Int, fetch: (Ctx, Seq[Id]) Future[Seq[Res]])(implicit id: HasId[Res, Id]): Fetcher[Ctx, Id, Res] =
new Fetcher[Ctx, Id, Res](i id.id(i), fetch, Some(maxBatchSize), Some(() cache))
def caching = empty.caching
def caching(cache: FetcherCache) = empty.caching(cache)
}

trait DeferredOne[Id, +T] extends Deferred[T] {
trait DeferredOne[+T, Id] extends Deferred[T] {
def id: Id
}

trait DeferredOpt[Id, +T] extends Deferred[Option[T]] {
trait DeferredOpt[+T, Id] extends Deferred[Option[T]] {
def id: Id
}

trait DeferredSeq[Id, +T] extends Deferred[Seq[T]] {
trait DeferredSeq[+T, Id] extends Deferred[Seq[T]] {
def ids: Seq[Id]
}

case class FetcherDeferredOne[Ctx, Id, T](source: Fetcher[Ctx, Id, T], id: Id) extends DeferredOne[Id, T]
case class FetcherDeferredOpt[Ctx, Id, T](source: Fetcher[Ctx, Id, T], id: Id) extends DeferredOne[Id, T]
case class FetcherDeferredSeq[Ctx, Id, T](source: Fetcher[Ctx, Id, T], ids: Seq[Id]) extends DeferredSeq[Id, T]
case class FetcherDeferredSeqOpt[Ctx, Id, T](source: Fetcher[Ctx, Id, T], ids: Seq[Id]) extends DeferredSeq[Id, T]
trait DeferredRel[T, RelId] extends Deferred[T] {
def rel: Relation[T, RelId]
def relId: RelId
}

trait DeferredRelOpt[T, RelId] extends Deferred[Option[T]] {
def rel: Relation[T, RelId]
def relId: RelId
}

trait DeferredRelSeq[T, RelId] extends Deferred[Seq[T]] {
def rel: Relation[T, RelId]
def relId: RelId
}

trait DeferredRelSeqMany[T, RelId] extends Deferred[Seq[T]] {
def rel: Relation[T, RelId]
def relIds: Seq[RelId]
}

case class FetcherDeferredOne[Ctx, T, Id](source: Fetcher[Ctx, T, Id], id: Id) extends DeferredOne[T, Id]
case class FetcherDeferredOpt[Ctx, T, Id](source: Fetcher[Ctx, T, Id], id: Id) extends DeferredOne[T, Id]
case class FetcherDeferredSeq[Ctx, T, Id](source: Fetcher[Ctx, T, Id], ids: Seq[Id]) extends DeferredSeq[T, Id]
case class FetcherDeferredSeqOpt[Ctx, T, Id](source: Fetcher[Ctx, T, Id], ids: Seq[Id]) extends DeferredSeq[T, Id]

case class FetcherDeferredRel[Ctx, RelId, T, Id](source: Fetcher[Ctx, T, Id], rel: Relation[T, RelId], relId: RelId) extends DeferredRel[T, RelId]
case class FetcherDeferredRelOpt[Ctx, RelId, T, Id](source: Fetcher[Ctx, T, Id], rel: Relation[T, RelId], relId: RelId) extends DeferredRelOpt[T, RelId]
case class FetcherDeferredRelSeq[Ctx, RelId, T, Id](source: Fetcher[Ctx, T, Id], rel: Relation[T, RelId], relId: RelId) extends DeferredRelSeq[T, RelId]
case class FetcherDeferredRelSeqMany[Ctx, RelId, T, Id](source: Fetcher[Ctx, T, Id], rel: Relation[T, RelId], relIds: Seq[RelId]) extends DeferredRelSeqMany[T, RelId]

trait Relation[T, RelId] {
def relIds(value: T): Seq[RelId]
}

object Relation {
def apply[T, RelId](name: String, idFn: T Seq[RelId]): Relation[T, RelId] =
SimpleRelation[T, RelId](name)(idFn)
}

abstract class AbstractRelation[T, RelId](idFn: T Seq[RelId]) extends Relation[T, RelId] {
def relIds(value: T) = idFn(value)
}

case class SimpleRelation[T, RelId](name: String)(idFn: T Seq[RelId]) extends AbstractRelation[T, RelId](idFn)

class RelationNotSupportedError extends Exception(s"Relations are not supported by Fetcher.")
Loading

0 comments on commit 8f97637

Please sign in to comment.