{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} -- | A storage back-end manages physical storage of pages. module Database.Haskey.Store.Class ( -- * Class StoreM(..) -- * Helpers , arbitrarySearch , calculateMaxKeySize , calculateMaxValueSize , ZeroEncoded(..) ) where import Prelude hiding (max, min, pred) import Control.Applicative (Applicative) import Control.Monad.Trans import Control.Monad.Trans.Reader (ReaderT) import Control.Monad.Trans.State (StateT) import Data.Binary (Binary(..), Get) import Data.Proxy import Data.Typeable (Typeable) import Data.Word (Word8, Word64) import qualified Data.Map as M import Data.BTree.Impure import Data.BTree.Impure.Structures import Data.BTree.Primitives -------------------------------------------------------------------------------- -- | A storage back-end that can store and fetch physical pages. class (Applicative m, Monad m) => StoreM hnd m | m -> hnd where -- | Open a database handle for reading and writing. openHandle :: hnd -> m () -- | Flush the contents of a handle to disk (or other storage). flushHandle :: hnd -> m () -- | Close a database handle. closeHandle :: hnd -> m () -- | Remove a handle from the storage back-end. removeHandle :: hnd -> m () -- | A function that calculates the hypothetical size of a node, if it were -- to be written to a page (regardless of the maximum page size). nodePageSize :: (Key key, Value val) => m (Height height -> Node height key val -> PageSize) -- | The maximum page size the allocator can handle. maxPageSize :: m PageSize -- | Get the maximum key size -- -- The default implementation will repeatedly call 'calculateMaxKeySize'. -- You might want to cache this value in your own implementation. maxKeySize :: m Word64 maxKeySize = do f <- nodePageSize fmax <- maxPageSize return $ calculateMaxKeySize fmax (f zeroHeight) -- | Get the maximum value size -- -- The default implementation will repeatedly call 'calculateMaxValueSize'. -- You might want to cache this value in your own implementation. maxValueSize :: m Word64 maxValueSize = do f <- nodePageSize key <- maxKeySize fmax <- maxPageSize return $ calculateMaxValueSize fmax key (f zeroHeight) -- | Read a page and return the actual node and the transaction id when the -- node was written. getNodePage :: (Key key, Value val) => hnd -> Height height -> Proxy key -> Proxy val -> NodeId height key val -> m (Node height key val) -- | Write a node to a physical page. putNodePage :: (Key key, Value val) => hnd -> Height height -> NodeId height key val -> Node height key val -> m () -- | Read a value from an overflow page getOverflow :: (Value val) => hnd -> Proxy val -> m val -- | Write a value to an overflow page putOverflow :: (Value val) => hnd -> val -> m () -- | List overflow pages in the specific overflow directory. -- -- The result should include **AT LEAST** the handles in the specified -- directory, but it may contain more handles, even handles that do not -- belong to an overflow page. listOverflows :: hnd -> m [hnd] instance StoreM hnd m => StoreM hnd (StateT s m) where openHandle = lift. openHandle flushHandle = lift. flushHandle closeHandle = lift. closeHandle removeHandle = lift. closeHandle nodePageSize = lift nodePageSize maxPageSize = lift maxPageSize maxKeySize = lift maxKeySize maxValueSize = lift maxValueSize getNodePage = ((((lift.).).).). getNodePage putNodePage = (((lift.).).). putNodePage getOverflow = (lift.). getOverflow putOverflow = (lift.). putOverflow listOverflows = lift. listOverflows instance StoreM hnd m => StoreM hnd (ReaderT s m) where openHandle = lift. openHandle flushHandle = lift. flushHandle closeHandle = lift. closeHandle removeHandle = lift. closeHandle nodePageSize = lift nodePageSize maxPageSize = lift maxPageSize maxKeySize = lift maxKeySize maxValueSize = lift maxValueSize getNodePage = ((((lift.).).).). getNodePage putNodePage = (((lift.).).). putNodePage getOverflow = (lift.). getOverflow putOverflow = (lift.). putOverflow listOverflows = lift. listOverflows -------------------------------------------------------------------------------- -- | Calculate the maximum key size. -- -- Return the size for which at least 4 key-value pairs with keys and values of -- that size can fit in a leaf node. calculateMaxKeySize :: PageSize -- ^ Maximum pages size -> (Node 'Z ZeroEncoded ZeroEncoded -> PageSize) -- ^ Function that calculates the page size of a node -> Word64 -- ^ Maximum key size calculateMaxKeySize fmax f = arbitrarySearch 2 pred fmax where pred n = f (Leaf $ kvs n) kvs n = M.fromList [(ZeroEncoded n i, RawValue $ ZeroEncoded n i) | i <- [1..4]] -- | Calculate the maximum value size. -- -- Return the size for which at least 4 key-value pairs of the specified -- maximum key size and values of the returned size can fit in a leaf node. -- that size can fit in a leaf node. calculateMaxValueSize :: PageSize -- ^ Maximum page size -> Word64 -- ^ Maximum key size -> (Node 'Z ZeroEncoded ZeroEncoded -> PageSize) -- ^ Function that calculates the page size of a node -> Word64 -- ^ Maximum value size calculateMaxValueSize fmax keySize f = arbitrarySearch 2 pred fmax where pred n = f (Leaf $ kvs n) kvs n = M.fromList [(ZeroEncoded keySize i, RawValue $ ZeroEncoded n i) | i <- [1..4]] -- | Search an arbitrary number, less than a limit, greater than a starting -- value. arbitrarySearch :: (Ord v, Integral n) => n -> (n -> v) -> v -> n arbitrarySearch start f fmax = go start where go n = let s = f n in if s == fmax then n else if s > fmax then search (n `quot` 2) n else go (n*2) search min max | max - min == 1 = min | otherwise = let c = min + ((max - min) `quot` 2) s = f c in if s == fmax then c else if s > fmax then search min c else search c max -- | Data type which encodes the integer using a variable amount of NULL or ONE -- bytes. data ZeroEncoded = ZeroEncoded { getZeroEncoded :: Word64 , getZeroEncodedValue :: Word64 } deriving (Eq, Ord, Show, Typeable) instance Binary ZeroEncoded where put (ZeroEncoded 0 _) = error "must be >0" put (ZeroEncoded 1 0) = put (255 :: Word8) put (ZeroEncoded 1 _) = error "value too large" put (ZeroEncoded n v) = put byte >> put (ZeroEncoded (n-1) v') where byte = fromIntegral $ v `rem` 255 :: Word8 v' = v `quot` 255 get = do byte <- get :: Get Word8 case byte of 0 -> return (ZeroEncoded 1 0) _ -> do next <- get return $ ZeroEncoded (getZeroEncoded next + 1) 0 instance Key ZeroEncoded where instance Value ZeroEncoded where --------------------------------------------------------------------------------