----------------------------------------------------------------------------- -- | -- Module : Database.Muesli.Allocator -- Copyright : (c) 2015 Călin Ardelean -- License : MIT -- -- Maintainer : Călin Ardelean -- Stability : experimental -- Portability : portable -- -- Document data allocator. -- -- This module should be imported qualified. ---------------------------------------------------------------------------- module Database.Muesli.Allocator ( empty , add , build , buildExtra , alloc ) where import Control.Exception (throw) import qualified Data.IntMap.Strict as IntMap import Data.List (foldl', sortOn) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe) import Database.Muesli.Backend.Types (LogRecord (..)) import Database.Muesli.State (GapsIndex, MainIndex) import Database.Muesli.Types (DatabaseError (..), DocAddress, DocSize) -- | Creates an index holding a single gap starting at the given address. empty :: DocAddress -> GapsIndex empty addr = Map.singleton (maxBound - addr) [addr] -- | Adds a gap to the index. add :: DocSize -> DocAddress -> GapsIndex -> GapsIndex add sz addr gs = Map.insert sz (addr:as) gs where as = fromMaybe [] $ Map.lookup sz gs -- | Builds the index from the 'LogRecord' data held in an 'MainIndex'. -- -- __O(n*log(n))__ operation used by 'Database.Muesli.Handle.open' after -- loading the log. build :: MainIndex -> GapsIndex build idx = addTail . foldl' f (Map.empty, 0) . sortOn recAddress . filter (not . recDeleted) . map head $ IntMap.elems idx where f (gs, addr) r = (gs', recAddress r + recSize r) where gs' = if addr == recAddress r then gs else add sz addr gs sz = recAddress r - addr addTail (gs, addr) = add (maxBound - addr) addr gs -- | Builds the index from a set of 'LogRecord's, with all space before the -- given address considered reserved. -- -- Used by the garbage collector (see module "Database.Muesli.GC"). buildExtra :: DocAddress -> [LogRecord] -> GapsIndex buildExtra pos = foldl' f (empty pos) where f gs r = add (recSize r) (recAddress r) gs -- | Allocates a new slot of the given size. -- The smallest available size in the index is preferred. -- -- Throws 'DataAllocationError' if no gap big enough is found. alloc :: GapsIndex -> DocSize -> (DocAddress, GapsIndex) alloc gs sz = case Map.lookupGE sz gs of Nothing -> throw $ DataAllocationError sz (fst <$> Map.lookupLT maxBound gs) "Data allocation error." Just (gsz, a:as) -> if delta == 0 then (a, gs') else (a, add delta (a + sz) gs') where gs' = if null as then Map.delete gsz gs else Map.insert gsz as gs delta = gsz - sz