morpheus-graphql-app-0.28.2: src/Data/Morpheus/App/Internal/Resolving/Batching.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Data.Morpheus.App.Internal.Resolving.Batching
( ResolverMapT (..),
SelectionRef,
runBatchedT,
MonadBatching (..),
)
where
import Control.Monad.Except (MonadError (throwError))
import Data.ByteString.Lazy.Char8 (unpack)
import Data.HashMap.Lazy (keys)
import Data.Morpheus.App.Internal.Resolving.Cache
( CacheKey (..),
CacheT,
CacheValue (..),
cacheResolverValues,
cacheValue,
isCached,
printSelectionKey,
useCached,
withDebug,
)
import Data.Morpheus.App.Internal.Resolving.Refs (scanRefs)
import Data.Morpheus.App.Internal.Resolving.ResolverState (ResolverContext)
import Data.Morpheus.App.Internal.Resolving.Types
( NamedResolver (..),
NamedResolverResult (..),
ResolverMap,
)
import Data.Morpheus.App.Internal.Resolving.Utils
( NamedResolverRef (..),
ResolverMonad,
ResolverValue (ResEnum, ResNull, ResObject, ResRef, ResScalar),
)
import Data.Morpheus.Core (render)
import Data.Morpheus.Internal.Utils (Empty (empty), IsMap (..), selectOr)
import Data.Morpheus.Types.Internal.AST
( GQLError,
Msg (..),
SelectionContent,
TypeName,
VALID,
ValidValue,
internal,
)
import GHC.Show (Show (show))
import Relude hiding (empty, show)
data BatchEntry = BatchEntry
{ batchedSelection :: SelectionContent VALID,
batchedType :: TypeName,
batchedArguments :: [ValidValue]
}
instance Show BatchEntry where
show BatchEntry {..} =
"\nBATCH("
<> toString batchedType
<> "):"
<> "\n sel:"
<> printSelectionKey batchedSelection
<> "\n dep:"
<> show (map (unpack . render) batchedArguments)
type SelectionRef = (SelectionContent VALID, NamedResolverRef)
uniq :: (Eq a, Hashable a) => [a] -> [a]
uniq = keys . unsafeFromList . map (,True)
buildBatches :: [SelectionRef] -> [BatchEntry]
buildBatches inputs =
let entityTypes = uniq $ map (second resolverTypeName) inputs
in mapMaybe (selectByEntity inputs) entityTypes
selectByEntity :: [SelectionRef] -> (SelectionContent VALID, TypeName) -> Maybe BatchEntry
selectByEntity inputs (tSel, tName) = case gerArgs (filter areEq inputs) of
[] -> Nothing
args -> Just (BatchEntry tSel tName args)
where
where
gerArgs = uniq . concatMap (resolverArgument . snd)
areEq (sel, v) = sel == tSel && tName == resolverTypeName v
newtype ResolverMapT m a = ResolverMapT
{ _runResMapT :: ReaderT (ResolverMap m) (CacheT m) a
}
deriving
( Functor,
Applicative,
Monad
)
instance (MonadReader ResolverContext m) => MonadReader ResolverContext (ResolverMapT m) where
ask = ResolverMapT (lift ask)
local f (ResolverMapT m) = ResolverMapT (ReaderT (local f . runReaderT m))
instance MonadTrans ResolverMapT where
lift = ResolverMapT . lift . lift
deriving instance (MonadError GQLError m) => MonadError GQLError (ResolverMapT m)
runBatchedT :: (Monad m) => ResolverMapT m a -> ResolverMap m -> m a
runBatchedT (ResolverMapT m) rmap = fst <$> runStateT (runReaderT m rmap) empty
toKeys :: BatchEntry -> [CacheKey]
toKeys (BatchEntry sel name deps) = map (CacheKey sel name) deps
inCache :: (Monad m) => CacheT m a -> ResolverMapT m a
inCache = ResolverMapT . lift
class (MonadTrans t) => MonadBatching t where
resolveRef :: (ResolverMonad m) => SelectionContent VALID -> NamedResolverRef -> t m (CacheKey, CacheValue m)
storeValue :: (ResolverMonad m) => CacheKey -> ValidValue -> t m ValidValue
instance MonadBatching IdentityT where
resolveRef _ _ = throwError $ internal "batching is only allowed with named resolvers"
storeValue _ _ = throwError $ internal "batching is only allowed with named resolvers"
instance MonadBatching ResolverMapT where
resolveRef sel (NamedResolverRef typename [arg]) = do
let key = CacheKey sel typename arg
alreadyCached <- inCache (isCached key)
if alreadyCached
then pure ()
else prefetch (BatchEntry sel typename [arg])
inCache $ do
value <- useCached key
pure (key, value)
resolveRef _ ref = throwError (internal ("expected only one resolved value for " <> msg (show ref :: String)))
storeValue key = inCache . cacheValue key
prefetch :: (ResolverMonad m) => BatchEntry -> ResolverMapT m ()
prefetch batch = do
value <- run batch
batches <- buildBatches . concat <$> traverse (lift . scanRefs (batchedSelection batch)) value
resolvedEntries <- traverse (\b -> (b,) <$> run b) batches
let caches = foldMap zipCaches $ (batch, value) : resolvedEntries
inCache $ cacheResolverValues caches
where
zipCaches (b, res) = zip (toKeys b) res
run = withDebug >=> runBatch
runBatch :: (MonadError GQLError m, MonadReader ResolverContext m) => BatchEntry -> ResolverMapT m [ResolverValue m]
runBatch (BatchEntry _ name deps)
| null deps = pure []
| otherwise = do
resolvers <- ResolverMapT ask
NamedResolver {resolverFun} <- lift (selectOr notFound pure name resolvers)
map (toResolverValue name) <$> lift (resolverFun deps)
where
notFound = throwError ("resolver type " <> msg name <> "can't found")
toResolverValue :: (Monad m) => TypeName -> NamedResolverResult m -> ResolverValue m
toResolverValue typeName (NamedObjectResolver res) = ResObject (Just typeName) res
toResolverValue _ (NamedUnionResolver unionRef) = ResRef $ pure unionRef
toResolverValue _ (NamedEnumResolver value) = ResEnum value
toResolverValue _ NamedNullResolver = ResNull
toResolverValue _ (NamedScalarResolver v) = ResScalar v