{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
module Database.Haskey.Alloc.Concurrent.Internal.FreePages.Query where
import Control.Applicative ((<|>), (<$>))
import Control.Concurrent.STM
import Control.Monad.State
import Control.Monad.Trans.Maybe
import Data.List.NonEmpty (NonEmpty((:|)))
import qualified Data.List.NonEmpty as NE
import Data.BTree.Alloc.Class
import Data.BTree.Primitives
import qualified Data.BTree.Impure as B
import qualified Data.BTree.Impure.NonEmpty as NEB
import Database.Haskey.Alloc.Concurrent.Internal.Environment
import Database.Haskey.Alloc.Concurrent.Internal.FreePages.Tree
import Database.Haskey.Utils.Monad (ifM)
import qualified Database.Haskey.Utils.STM.Map as Map
getFreePageId :: (Functor m, AllocM m, MonadIO m, MonadState (WriterEnv hnd) m)
=> S stateType ()
-> m (Maybe PageId)
getFreePageId t =
runMaybeT $ MaybeT (getCachedFreePageId t)
<|> MaybeT (queryNewFreePageIds t)
getCachedFreePageId :: (Functor m, MonadState (WriterEnv hnd) m)
=> S stateType ()
-> m (Maybe PageId)
getCachedFreePageId stateType =
case stateType of
DataState () -> do
s <- writerDataFileState <$> get
let (pid, s') = query DataState s
modify' $ \env -> env { writerDataFileState = s' }
return pid
IndexState () -> do
s <- writerIndexFileState <$> get
let (pid, s') = query IndexState s
modify' $ \env -> env { writerIndexFileState = s' }
return pid
where
query :: (forall a. a -> S t a)
-> FileState t
-> (Maybe PageId, FileState t)
query cons env = case getSValue $ fileStateCachedFreePages env of
[] -> (Nothing, env)
FreePage pid : pageIds ->
let env' = env { fileStateCachedFreePages = cons pageIds } in
(Just pid, env')
queryNewFreePageIds :: (AllocM m, MonadIO m, MonadState (WriterEnv hnd) m)
=> S stateType ()
-> m (Maybe PageId)
queryNewFreePageIds stateType = ifM (not . writerQueryFreeTreeOn <$> get) (return Nothing) $ do
flag <- case stateType of
DataState () ->
query DataState
writerDataFileState
(\e s -> e { writerDataFileState = s })
IndexState () ->
query IndexState
writerIndexFileState
(\e s -> e { writerIndexFileState = s })
if flag then getFreePageId stateType
else return Nothing
where
query :: (AllocM m, MonadIO m, MonadState (WriterEnv hnd) m)
=> (forall a. a -> S t a)
-> (forall h. WriterEnv h -> FileState t)
-> (forall h. WriterEnv h -> FileState t -> WriterEnv h)
-> m Bool
query cons getState setState = do
tree <- gets $ getSValue . fileStateFreeTree . getState
lookupValidFreePageIds tree >>= \case
Nothing -> return False
Just (txId, x :| xs) -> do
modify' $ \e ->
let s = getState e
pids = map FreePage (x:xs)
in setState e $
s { fileStateCachedFreePages =
cons $ pids ++ getSValue (fileStateCachedFreePages s) }
modify' $ \e -> e { writerQueryFreeTreeOn = False }
tree' <- txId `deleteSubtree` tree
modify' $ \e -> e { writerQueryFreeTreeOn = True }
modify' $ \e -> setState e $
(getState e) { fileStateFreeTree = cons tree' }
return True
lookupValidFreePageIds :: (MonadIO m, AllocReaderM m, MonadState (WriterEnv hnd) m)
=> FreeTree
-> m (Maybe (TxId, NonEmpty PageId))
lookupValidFreePageIds tree = runMaybeT $
MaybeT (lookupFreePageIds tree) >>= (MaybeT . checkFreePages)
lookupFreePageIds :: (Functor m, AllocReaderM m, MonadState (WriterEnv hnd) m)
=> FreeTree
-> m (Maybe (Unchecked (TxId, NonEmpty PageId)))
lookupFreePageIds tree = B.lookupMin tree >>= \case
Nothing -> return Nothing
Just (tx, subtree) -> do
pids <- subtreeToList subtree
return . Just $ Unchecked (tx, pids)
where
subtreeToList subtree = NE.map fst <$> NEB.toList subtree
newtype Unchecked a = Unchecked a
checkFreePages :: (Functor m, MonadIO m, MonadState (WriterEnv hnd) m)
=> Unchecked (TxId, NonEmpty PageId)
-> m (Maybe (TxId, NonEmpty PageId))
checkFreePages (Unchecked v) = do
readers <- writerReaders <$> get
oldest <- liftIO . atomically $ Map.lookupMinKey readers
tx <- writerTxId <$> get
if maybe True (> fst v) oldest && fst v + 1 < tx
then return (Just v)
else return Nothing