module Database.Haskey.Store.File (
Page(..)
, FileStoreConfig(..)
, defFileStoreConfig
, fileStoreConfigWithPageSize
, FileStoreT
, runFileStoreT
, encodeAndPad
, FileNotFoundError(..)
, PageOverflowError(..)
, WrongNodeTypeError(..)
, WrongOverflowValueError(..)
) where
import Control.Applicative (Applicative, (<$>))
import Control.Monad
import Control.Monad.Catch
import Control.Monad.IO.Class
import Control.Monad.Reader
import Control.Monad.State.Class
import Control.Monad.Trans.State.Strict ( StateT, evalStateT)
import Data.Map (Map)
import Data.Maybe (fromJust)
import Data.Monoid ((<>))
import Data.Typeable (Typeable, cast)
import Data.Word (Word64)
import qualified Data.ByteString.Lazy as BL
import qualified Data.Map as M
import qualified FileIO as IO
import System.Directory (createDirectoryIfMissing, removeFile, getDirectoryContents)
import System.FilePath (takeDirectory)
import System.IO.Error (ioError, isDoesNotExistError)
import Data.BTree.Impure.Structures
import Data.BTree.Primitives
import Database.Haskey.Alloc.Concurrent
import Database.Haskey.Store.Class
import Database.Haskey.Store.Page
import Database.Haskey.Utils.IO (readByteString, writeLazyByteString)
import Database.Haskey.Utils.Monad.Catch (justErrM)
encodeAndPad :: PageSize -> Page t -> Maybe BL.ByteString
encodeAndPad size page
| Just n <- padding = Just . prependChecksum $
enc <> BL.replicate n 0
| otherwise = Nothing
where
enc = encodeNoChecksum page
padding | n <- fromIntegral size BL.length enc 8, n >= 0 = Just n
| otherwise = Nothing
type Files fp = Map fp IO.FHandle
lookupHandle :: (Functor m, MonadThrow m, Ord fp, Show fp, Typeable fp)
=> fp -> Files fp -> m IO.FHandle
lookupHandle fp m = justErrM (FileNotFoundError fp) $ M.lookup fp m
newtype FileStoreT fp m a = FileStoreT
{ fromFileStoreT :: ReaderT FileStoreConfig (StateT (Files fp) m) a
} deriving (Applicative, Functor, Monad,
MonadIO, MonadThrow, MonadCatch, MonadMask,
MonadReader FileStoreConfig, MonadState (Files fp))
data FileStoreConfig = FileStoreConfig {
fileStoreConfigPageSize :: !PageSize
, fileStoreConfigMaxKeySize :: !Word64
, fileStoreConfigMaxValueSize :: !Word64
} deriving (Show)
defFileStoreConfig :: FileStoreConfig
defFileStoreConfig = fromJust (fileStoreConfigWithPageSize 4096)
fileStoreConfigWithPageSize :: PageSize -> Maybe FileStoreConfig
fileStoreConfigWithPageSize pageSize
| keySize < 8 && valueSize < 8 = Nothing
| otherwise = Just FileStoreConfig {
fileStoreConfigPageSize = pageSize
, fileStoreConfigMaxKeySize = keySize
, fileStoreConfigMaxValueSize = valueSize }
where
keySize = calculateMaxKeySize pageSize (encodedPageSize zeroHeight)
valueSize = calculateMaxValueSize pageSize keySize (encodedPageSize zeroHeight)
runFileStoreT :: Monad m
=> FileStoreT FilePath m a
-> FileStoreConfig
-> m a
runFileStoreT m config = evalStateT (runReaderT (fromFileStoreT m) config) M.empty
instance (Applicative m, Monad m, MonadIO m, MonadThrow m) =>
StoreM FilePath (FileStoreT FilePath m)
where
openHandle fp = do
alreadyOpen <- M.member fp <$> get
unless alreadyOpen $ do
liftIO $ createDirectoryIfMissing True (takeDirectory fp)
fh <- liftIO $ IO.openReadWrite fp
modify $ M.insert fp fh
lockHandle = void . liftIO . IO.obtainPrefixLock
releaseHandle = liftIO . IO.releasePrefixLock . IO.prefixLockFromPrefix
flushHandle fp = do
fh <- get >>= lookupHandle fp
liftIO $ IO.flush fh
closeHandle fp = do
fh <- get >>= lookupHandle fp
liftIO $ IO.flush fh
liftIO $ IO.close fh
modify (M.delete fp)
removeHandle fp =
liftIO $ removeFile fp `catchIOError` \e ->
unless (isDoesNotExistError e) (ioError e)
nodePageSize = return encodedPageSize
maxPageSize = asks fileStoreConfigPageSize
maxKeySize = asks fileStoreConfigMaxKeySize
maxValueSize = asks fileStoreConfigMaxValueSize
getNodePage fp height key val nid = do
h <- get >>= lookupHandle fp
size <- maxPageSize
let PageId pid = nodeIdToPageId nid
offset = fromIntegral $ pid * fromIntegral size
liftIO $ IO.seek h offset
bs <- liftIO $ readByteString h (fromIntegral size)
case viewHeight height of
UZero -> decodeM (leafNodePage height key val) bs >>= \case
LeafNodePage hgtSrc tree ->
justErrM WrongNodeTypeError $ castNode hgtSrc height tree
USucc _ -> decodeM (indexNodePage height key val) bs >>= \case
IndexNodePage hgtSrc tree ->
justErrM WrongNodeTypeError $ castNode hgtSrc height tree
putNodePage fp hgt nid node = do
h <- get >>= lookupHandle fp
size <- maxPageSize
let PageId pid = nodeIdToPageId nid
offset = fromIntegral $ pid * fromIntegral size
liftIO $ IO.seek h offset
bs <- justErrM PageOverflowError $ pg size
liftIO $ writeLazyByteString h bs
where
pg size = case viewHeight hgt of
UZero -> encodeAndPad size $ LeafNodePage hgt node
USucc _ -> encodeAndPad size $ IndexNodePage hgt node
getOverflow fp val = do
h <- get >>= lookupHandle fp
len <- liftIO $ IO.getFileSize h
liftIO $ IO.seek h 0
bs <- liftIO $ readByteString h (fromIntegral len)
n <- decodeM (overflowPage val) bs
case n of
OverflowPage v -> justErrM WrongOverflowValueError $ castValue v
putOverflow fp val = do
fh <- get >>= lookupHandle fp
liftIO $ IO.setFileSize fh (fromIntegral $ BL.length bs)
liftIO $ IO.seek fh 0
liftIO $ writeLazyByteString fh bs
where
bs = encode $ OverflowPage val
listOverflows dir = liftIO $ getDirectoryContents dir `catch` catch'
where catch' e | isDoesNotExistError e = return []
| otherwise = ioError e
instance (Applicative m, Monad m, MonadIO m, MonadCatch m) =>
ConcurrentMetaStoreM (FileStoreT FilePath m)
where
putConcurrentMeta fp meta = do
h <- get >>= lookupHandle fp
let page = ConcurrentMetaPage meta
bs = encode page
liftIO $ IO.setFileSize h (fromIntegral $ BL.length bs)
liftIO $ IO.seek h 0
liftIO $ writeLazyByteString h bs
readConcurrentMeta fp root = do
fh <- get >>= lookupHandle fp
len <- liftIO $ IO.getFileSize fh
liftIO $ IO.seek fh 0
bs <- liftIO $ readByteString fh (fromIntegral len)
handle handle' (Just <$> decodeM (concurrentMetaPage root) bs) >>= \case
Just (ConcurrentMetaPage meta) -> return $! cast meta
Nothing -> return Nothing
where
handle' (DecodeError _) = return Nothing
newtype FileNotFoundError hnd = FileNotFoundError hnd deriving (Show, Typeable)
instance (Typeable hnd, Show hnd) => Exception (FileNotFoundError hnd) where
data PageOverflowError = PageOverflowError deriving (Show, Typeable)
instance Exception PageOverflowError where
data WrongNodeTypeError = WrongNodeTypeError deriving (Show, Typeable)
instance Exception WrongNodeTypeError where
data WrongOverflowValueError = WrongOverflowValueError deriving (Show, Typeable)
instance Exception WrongOverflowValueError where