packages feed

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)