module Database.Haskey.Store.Page where
import Codec.Compression.LZ4
import Control.Applicative ((<$>))
import Control.Monad.Catch
import Data.Binary (Binary(..), Put, Get)
import Data.Binary.Get (runGetOrFail)
import Data.Binary.Put (runPut)
import Data.Bits ((.&.), (.|.))
import Data.ByteString (ByteString)
import Data.ByteString.Lazy (fromStrict, toStrict)
import Data.Digest.XXHash.FFI (xxh64)
import Data.Maybe (fromMaybe)
import Data.Proxy
import Data.Typeable (Typeable)
import Data.Word (Word8, Word64)
import qualified Data.Binary as B
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Numeric (showHex)
import Data.BTree.Impure
import Data.BTree.Impure.Structures (putLeafNode, getLeafNode, putIndexNode, getIndexNode)
import Data.BTree.Primitives
import Database.Haskey.Alloc.Concurrent
data PageType = TypeEmpty
| TypeConcurrentMeta
| TypeOverflow
| TypeLeafNode
| TypeIndexNode
deriving (Eq, Show)
data SPageType t where
STypeEmpty :: SPageType 'TypeEmpty
STypeConcurrentMeta :: SPageType 'TypeConcurrentMeta
STypeOverflow :: SPageType 'TypeOverflow
STypeLeafNode :: SPageType 'TypeLeafNode
STypeIndexNode :: SPageType 'TypeIndexNode
instance Binary PageType where
put TypeEmpty = put (0x00 :: Word8)
put TypeConcurrentMeta = put (0x20 :: Word8)
put TypeOverflow = put (0x40 :: Word8)
put TypeLeafNode = put (0x60 :: Word8)
put TypeIndexNode = put (0x80 :: Word8)
get = (get :: Get Word8) >>= \case
0x00 -> return TypeEmpty
0x20 -> return TypeConcurrentMeta
0x40 -> return TypeOverflow
0x60 -> return TypeLeafNode
0x80 -> return TypeIndexNode
t -> fail $ "unknown page type: " ++ showHex t ""
data Page (t :: PageType) where
EmptyPage :: Page 'TypeEmpty
ConcurrentMetaPage :: Root root
=> ConcurrentMeta root
-> Page 'TypeConcurrentMeta
OverflowPage :: (Value v)
=> v
-> Page 'TypeOverflow
LeafNodePage :: (Key k, Value v)
=> Height 'Z
-> Node 'Z k v
-> Page 'TypeLeafNode
IndexNodePage :: (Key k, Value v)
=> Height ('S h)
-> Node ('S h) k v
-> Page 'TypeIndexNode
data SGet t = SGet (SPageType t) (Get (Page t))
pageType :: SPageType t -> PageType
pageType STypeEmpty = TypeEmpty
pageType STypeConcurrentMeta = TypeConcurrentMeta
pageType STypeOverflow = TypeOverflow
pageType STypeLeafNode = TypeLeafNode
pageType STypeIndexNode = TypeIndexNode
encodeZeroChecksum :: Page t -> BL.ByteString
encodeZeroChecksum p = zero `BL.append` encodeNoChecksum p
where zero = "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL"
encode :: Page t -> BL.ByteString
encode = prependChecksum . encodeNoChecksum
prependChecksum :: BL.ByteString -> BL.ByteString
prependChecksum bs = B.encode (xxh64 bs checksumSeed :: Word64) `BL.append` bs
encodeNoChecksum :: Page t -> BL.ByteString
encodeNoChecksum = runPut . putPage
where
_tryCompress bs = do
(t, body) <- BL.uncons bs
c <- compress (toStrict body)
if BS.length c < fromIntegral (BL.length bs)
then Just $ maskCompressed t `BL.cons` fromStrict c
else Nothing
maskCompressed t = t .|. 0x01
encodedPageSize :: (Key k, Value v) => Height h -> Node h k v -> PageSize
encodedPageSize h = case viewHeight h of
UZero -> fromIntegral . BL.length . encodeZeroChecksum . LeafNodePage h
USucc _ -> fromIntegral . BL.length . encodeZeroChecksum . IndexNodePage h
decode :: SGet t -> ByteString -> Either String (Page t)
decode g@(SGet t _) bs = do
let (cksumBs, body) = BS.splitAt 8 bs
cksum <- if BS.length cksumBs < 8
then Left $ "could not decode " ++ show (pageType t) ++ ": "
++ "not enough checksum bytes"
else Right $ B.decode (fromStrict cksumBs)
let cksum' = xxh64 body checksumSeed
if cksum' /= cksum
then Left $ "could not decode " ++ show (pageType t) ++ ": "
++ "expected checksum " ++ show cksum' ++ " but checksum "
++ "field contains " ++ show cksum
else decodeNoChecksum g body
decodeNoChecksum :: SGet t -> ByteString -> Either String (Page t)
decodeNoChecksum (SGet t g) bs = case runGetOrFail g (fromStrict bs) of
Left err -> Left $ err' err
Right (_, _, v) -> Right v
where
err' (bs', offset, err) =
"could not decode " ++ show (pageType t) ++ ": " ++ err ++
"at pos " ++ show offset ++ ", remaining bytes: " ++ show bs' ++
", full body: " ++ show bs
_decompressed = fromMaybe (fromStrict bs) $ do
(tb, body) <- BS.uncons bs
if isCompressed tb
then do
c <- decompress body
Just $ unmaskCompressed tb `BL.cons` fromStrict c
else Nothing
isCompressed b = b .&. 0x01 == 0x01
unmaskCompressed b = b .&. 0xFE
decodeM :: MonadThrow m => SGet t -> ByteString -> m (Page t)
decodeM g bs = case decode g bs of
Left err -> throwM $ DecodeError err
Right v -> return v
putPage :: Page t -> Put
putPage EmptyPage = put TypeEmpty
putPage (ConcurrentMetaPage m) = put TypeConcurrentMeta >> put m
putPage (OverflowPage v) = put TypeOverflow >> put v
putPage (LeafNodePage _ n) = put TypeLeafNode >> putLeafNode n
putPage (IndexNodePage h n) = put TypeIndexNode >> put h >> putIndexNode n
emptyPage :: SGet 'TypeEmpty
emptyPage = SGet STypeEmpty $ get >>= \case
TypeEmpty -> return EmptyPage
x -> fail $ "unexpected " ++ show x ++ " while decoding TypeEmpty"
leafNodePage :: (Key k, Value v)
=> Height 'Z
-> Proxy k
-> Proxy v
-> SGet 'TypeLeafNode
leafNodePage h k v = SGet STypeLeafNode $ get >>= \case
TypeLeafNode -> LeafNodePage h <$> get' h k v
x -> fail $ "unexpected " ++ show x ++ " while decoding TypeLeafNode"
where
get' :: (Key k, Value v)
=> Height 'Z -> Proxy k -> Proxy v -> Get (Node 'Z k v)
get' h' _ _ = getLeafNode h'
indexNodePage :: (Key k, Value v)
=> Height ('S n)
-> Proxy k
-> Proxy v
-> SGet 'TypeIndexNode
indexNodePage h k v = SGet STypeIndexNode $ get >>= \case
TypeIndexNode -> do
h' <- get
if fromHeight h == fromHeight h'
then IndexNodePage h <$> get' h k v
else fail $ "expected height " ++ show h ++ " but got "
++ show h' ++ " while decoding TypeNode"
x -> fail $ "unexpected " ++ show x ++ " while decoding TypeIndexNode"
where
get' :: (Key k, Value v)
=> Height ('S n) -> Proxy k -> Proxy v -> Get (Node ('S n) k v)
get' h' _ _ = getIndexNode h'
overflowPage :: (Value v) => Proxy v -> SGet 'TypeOverflow
overflowPage v = SGet STypeOverflow $ get >>= \case
TypeOverflow -> OverflowPage <$> get' v
x -> fail $ "unexpected " ++ show x ++ " while decoding TypeOverflow"
where
get' :: (Value v) => Proxy v -> Get v
get' _ = get
concurrentMetaPage :: Root root
=> Proxy root
-> SGet 'TypeConcurrentMeta
concurrentMetaPage root = SGet STypeConcurrentMeta $ get >>= \ case
TypeConcurrentMeta -> ConcurrentMetaPage <$> get' root
x -> fail $ "unexpected " ++ show x ++ " while decoding TypeConcurrentMeta"
where
get' :: Root root => Proxy root -> Get (ConcurrentMeta root)
get' _ = get
newtype DecodeError = DecodeError String deriving (Show, Typeable)
instance Exception DecodeError where
checksumSeed :: Word64
checksumSeed = 0