lsm-tree-1.0.0.0: src-core/Database/LSMTree/Internal/MergingTree/Lookup.hs
{-# OPTIONS_HADDOCK not-home #-}
module Database.LSMTree.Internal.MergingTree.Lookup (
LookupTree (..)
, mapMStrict
, mkLookupNode
, buildLookupTree
, foldLookupTree
) where
import Control.ActionRegistry
import Control.Concurrent.Class.MonadMVar.Strict
import Control.Exception (assert)
import Control.Monad
import Control.Monad.Class.MonadAsync (Async, MonadAsync)
import qualified Control.Monad.Class.MonadAsync as Async
import Control.Monad.Class.MonadThrow (MonadMask)
import Control.Monad.Primitive
import Control.RefCount
import qualified Data.Vector as V
import qualified Database.LSMTree.Internal.Entry as Entry
import Database.LSMTree.Internal.Lookup (LookupAcc)
import qualified Database.LSMTree.Internal.MergingRun as MR
import qualified Database.LSMTree.Internal.MergingTree as MT
import Database.LSMTree.Internal.Run (Run)
import Database.LSMTree.Internal.Serialise (ResolveSerialisedValue)
import qualified Database.LSMTree.Internal.Vector as V
-- | A simplified representation of the shape of a 'MT.MergingTree'.
data LookupTree a =
LookupBatch !a
-- | Use 'mkLookupNode' to construct this.
| LookupNode !MR.TreeMergeType !(V.Vector (LookupTree a)) -- ^ length 2 or more
deriving stock (Foldable)
-- | Deriving 'Traversable' leads to functions that are not strict in the
-- elements of the vector of children. This function avoids that issue.
{-# SPECIALISE mapMStrict ::
(a -> IO b)
-> LookupTree a
-> IO (LookupTree b) #-}
mapMStrict :: Monad m => (a -> m b) -> LookupTree a -> m (LookupTree b)
mapMStrict f = \case
LookupBatch a -> LookupBatch <$!> f a
LookupNode t v -> LookupNode t <$!> V.mapMStrict (mapMStrict f) v
-- | Asserts that the vector is non-empty. Collapses singleton nodes.
mkLookupNode :: MR.TreeMergeType -> V.Vector (LookupTree a) -> LookupTree a
mkLookupNode ty ts
| assert (not (null ts)) (V.length ts == 1) = V.head ts
| otherwise = LookupNode ty ts
-- | Combine a tree of accs into a single one, using the 'MR.TreeMergeType' of
-- each node.
{-# SPECIALISE foldLookupTree ::
ResolveSerialisedValue
-> LookupTree (Async IO (LookupAcc IO h))
-> IO (LookupAcc IO h) #-}
foldLookupTree ::
MonadAsync m
=> ResolveSerialisedValue
-> LookupTree (Async m (LookupAcc m h))
-> m (LookupAcc m h)
foldLookupTree resolve = \case
LookupBatch batch ->
Async.wait batch
LookupNode mt children ->
mergeLookupAcc resolve mt <$> traverse (foldLookupTree resolve) children
-- | Requires multiple inputs, all of the same length.
--
-- TODO: do more efficiently on mutable vectors?
mergeLookupAcc ::
ResolveSerialisedValue
-> MR.TreeMergeType
-> V.Vector (LookupAcc m h)
-> LookupAcc m h
mergeLookupAcc resolve mt accs =
assert (V.length accs > 1) $
assert (V.all ((== V.length (V.head accs)) . V.length) accs) $
foldl1 (V.zipWith updateEntry) accs
where
updateEntry Nothing old = old
updateEntry new Nothing = new
updateEntry (Just new) (Just old) = Just (combine new old)
combine = case mt of
MR.MergeLevel -> Entry.combine resolve
MR.MergeUnion -> Entry.combineUnion resolve
-- | Create a 'LookupTree' of batches of runs, e.g. to do lookups on. The
-- entries within each batch are to be combined using 'MR.MergeLevel'.
--
-- Assumes that the merging tree is not 'MT.isStructurallyEmpty'.
--
-- This function duplicates the references to all the tree's runs. These
-- references later need to be released.
{-# SPECIALISE buildLookupTree ::
ActionRegistry IO
-> Ref (MT.MergingTree IO h)
-> IO (LookupTree (V.Vector (Ref (Run IO h)))) #-}
buildLookupTree ::
(PrimMonad m, MonadMVar m, MonadMask m)
=> ActionRegistry m
-> Ref (MT.MergingTree m h)
-> m (LookupTree (V.Vector (Ref (Run m h))))
buildLookupTree reg (DeRef mt) =
-- we make sure the state is not updated while we look at it, so no runs get
-- dropped before we duplicated the reference.
withMVar (MT.mergeState mt) $ \case
MT.CompletedTreeMerge r ->
LookupBatch . V.singleton <$!> dupRun r
MT.OngoingTreeMerge mr -> do
!rs <- withRollback reg (MR.duplicateRuns mr) (V.mapM_ releaseRef)
ty <- MR.mergeType mr
pure $ case ty of
Nothing -> LookupBatch rs -- just one run
Just MR.MergeLevel -> LookupBatch rs -- combine runs
Just MR.MergeUnion -> mkLookupNode MR.MergeUnion -- separate
(LookupBatch . V.singleton <$!> rs)
MT.PendingTreeMerge (MT.PendingLevelMerge prs Nothing) -> do
LookupBatch . V.concatMap id <$!> -- combine runs
V.mapMStrict duplicatePreExistingRun prs
MT.PendingTreeMerge (MT.PendingLevelMerge prs (Just tree)) -> do
!child <- buildLookupTree reg tree
if V.null prs
then pure child
else do
preExisting <- do
LookupBatch . V.concatMap id <$!> -- combine runs
V.mapMStrict duplicatePreExistingRun prs
pure $ mkLookupNode MR.MergeLevel $ V.fromList [preExisting, child]
MT.PendingTreeMerge (MT.PendingUnionMerge trees) ->
mkLookupNode MR.MergeUnion <$!> V.mapMStrict (buildLookupTree reg) trees
where
dupRun r = withRollback reg (dupRef r) releaseRef
duplicatePreExistingRun (MT.PreExistingRun r) =
V.singleton <$!> dupRun r
duplicatePreExistingRun (MT.PreExistingMergingRun mr) =
withRollback reg (MR.duplicateRuns mr) (V.mapM_ releaseRef)