continuum-client-0.1.0.0: src/Continuum/Common/Serialization.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Continuum.Common.Serialization where
import Foreign
import Continuum.Common.Types
import Data.Serialize as S
import qualified Data.ByteString as B
import qualified Data.Map as Map
import Control.Applicative ( (<$>) )
import Data.List ( elemIndex )
import Data.Maybe ( isJust, fromJust, catMaybes )
import Control.Monad.Except ( forM_, throwError )
import Control.Monad ( join )
-- import Debug.Trace
data Success = Success
-- validate :: DbSchema -> DbRecord -> Either String Success
-- validate = error "Not Implemented"
-- |
-- | ENCODING
-- |
encodeRecord :: DbSchema -> DbRecord -> Integer -> (B.ByteString, B.ByteString)
encodeRecord schema (DbRecord timestamp vals) sid = (encodeKey, encodeValue)
where encodeKey = B.concat [(packWord64 timestamp), (packWord64 sid)]
encodedParts = fmap fastEncodeValue $ catMaybes $ (\x -> Map.lookup x vals) <$> (fields schema)
lengths = B.length <$> encodedParts
encodeValue = runPut $ do
-- Change to B.Pack
forM_ lengths (putWord8 . fromIntegral)
forM_ encodedParts putByteString
-- encode . catMaybes $ fmap (\x -> Map.lookup x vals) (fields schema)
encodeBeginTimestamp :: Integer -> B.ByteString
encodeBeginTimestamp timestamp =
B.concat [(packWord64 timestamp), (packWord64 0)]
encodeEndTimestamp :: Integer -> B.ByteString
encodeEndTimestamp timestamp =
B.concat [(packWord64 timestamp), (packWord64 999999)]
encodeSchema :: DbSchema -> B.ByteString
encodeSchema = encode
decodeSchema :: (DbName, B.ByteString)
-> DbErrorMonad DbResult
decodeSchema (dbName, encodedSchema) =
case (decode encodedSchema) of
(Left err) -> throwError $ SchemaDecodingError err
(Right schema) -> return $ DbSchemaResult (dbName, schema)
decodeQuery :: B.ByteString
-> DbErrorMonad SelectQuery
decodeQuery encodedQuery =
case (decode encodedQuery) of
(Left err) -> throwError $ SchemaDecodingError err
(Right query) -> return query
decodeDbResult :: B.ByteString
-> DbErrorMonad DbResult
decodeDbResult encodedDbResult =
case (decode encodedDbResult) of
(Left err) -> throwError $ SchemaDecodingError err
(Right query) -> join $ return query
-- |
-- | DECODING
-- |
decodeRecord :: Decoding
-> DbSchema
-> Decoder
decodeRecord (Field field) schema !(k, bs) = do
decodedK <- decodeKey k
decodedVal <- if isJust idx
then decodeFieldByIndex schema indices (fromJust idx) bs
else throwError FieldNotFoundError
return $! FieldRes (decodedK, decodedVal)
where idx = elemIndex field (fields schema)
indices = decodeIndexes schema bs
decodeRecord Record schema !(k, bs) = do
timestamp <- decodeKey k
decodedValue <- decodeValues schema bs
return $! RecordRes $ DbRecord timestamp (Map.fromList $ zip (fields schema) decodedValue)
decodeRecord (Fields flds) schema (k, bs) = do
decodedK <- decodeKey k
decodedVal <- if isJust idxs
then mapM (\idx -> decodeFieldByIndex schema (decodeIndexes schema bs) idx bs) (fromJust idxs)
else throwError FieldNotFoundError
return $! FieldsRes (decodedK, decodedVal)
where
{-# INLINE idxs #-}
idxs = mapM (`elemIndex` (fields schema)) flds
{-# INLINE decodeRecord #-}
-- |
-- | DECODING Utility Functions
-- |
decodeKey :: B.ByteString -> DbErrorMonad Integer
decodeKey x = return $ unpackWord64 (B.take 8 x)
{-# INLINE decodeKey #-}
decodeChunkKey :: (B.ByteString, B.ByteString) -> DbErrorMonad DbResult
decodeChunkKey (x, _) = return $ KeyRes (unpackWord64 (B.take 8 x))
{-# INLINE decodeChunkKey #-}
decodeIndexes :: DbSchema -> B.ByteString -> [Int]
decodeIndexes schema bs =
map fromIntegral $ B.unpack $ B.take (length (fields schema)) bs
{-# INLINE decodeIndexes #-}
slide :: [a] -> [(a, a)]
slide (f:s:xs) = (f,s) : slide (s:xs)
slide _ = []
decodeValues :: DbSchema -> B.ByteString -> DbErrorMonad [DbValue]
decodeValues schema bs = mapM (\(t, s) -> fastDecodeValue t s) (zip (schemaTypes schema) bytestrings)
where
indices = decodeIndexes schema bs
bytestrings = snd (foldl step (B.drop (length indices) bs, []) indices)
step (remaining', acc) n = (B.drop n remaining', acc ++ [B.take n remaining'])
-- | Decodes field by index
decodeFieldByIndex :: DbSchema
-> [Int]
-> Int
-> B.ByteString
-> DbErrorMonad DbValue
decodeFieldByIndex schema indices idx bs = fastDecodeValue ((schemaTypes schema) !! idx) bytestring
where
{-# INLINE bytestring #-}
bytestring = B.take (indices !! idx) $ B.drop startFrom bs
{-# INLINE startFrom #-}
startFrom = (length indices) + (sum $ take idx indices)
{-# INLINE decodeFieldByIndex #-}
-- |
-- | "FAST" CUSTOM SERIALIZATION
-- |
fastEncodeValue :: DbValue -> B.ByteString
fastEncodeValue (DbInt value) = packWord64 value
fastEncodeValue (DbString value) = value
fastDecodeValue :: DbType -> B.ByteString -> DbErrorMonad DbValue
fastDecodeValue DbtInt bs = return $ DbInt (unpackWord64 bs)
fastDecodeValue DbtString bs = return $ DbString bs
packWord64 :: Integer -> B.ByteString
packWord64 i =
let w = (fromIntegral i :: Word64)
in B.pack [ (fromIntegral (w `shiftR` 56) :: Word8)
, (fromIntegral (w `shiftR` 48) :: Word8)
, (fromIntegral (w `shiftR` 40) :: Word8)
, (fromIntegral (w `shiftR` 32) :: Word8)
, (fromIntegral (w `shiftR` 24) :: Word8)
, (fromIntegral (w `shiftR` 16) :: Word8)
, (fromIntegral (w `shiftR` 8) :: Word8)
, (fromIntegral (w) :: Word8)]
{-# INLINE packWord64 #-}
unpackWord64 :: B.ByteString -> Integer
unpackWord64 s =
(fromIntegral (s `B.index` 0) `shiftL` 56) .|.
(fromIntegral (s `B.index` 1) `shiftL` 48) .|.
(fromIntegral (s `B.index` 2) `shiftL` 40) .|.
(fromIntegral (s `B.index` 3) `shiftL` 32) .|.
(fromIntegral (s `B.index` 4) `shiftL` 24) .|.
(fromIntegral (s `B.index` 5) `shiftL` 16) .|.
(fromIntegral (s `B.index` 6) `shiftL` 8) .|.
(fromIntegral (s `B.index` 7) )
{-# INLINE unpackWord64 #-}
-- Group Queries
byField :: B.ByteString -> DbRecord -> DbValue
byField f (DbRecord _ m) = fromJust $ Map.lookup f m
byFieldMaybe :: B.ByteString -> DbRecord -> Maybe DbValue
byFieldMaybe f (DbRecord _ m) = Map.lookup f m
byTime :: Integer -> DbRecord -> Integer
byTime interval (DbRecord t _) = interval * (t `quot` interval)
unpackString :: DbValue -> B.ByteString
unpackString (DbString i) = i
unpackString _ = error "Can't unpack Int"
unpackInt :: DbValue -> Integer
unpackInt (DbInt i) = i
unpackInt _ = error "Can't unpack Int"
unpackFloat :: DbValue -> Float
unpackFloat (DbFloat i) = i
unpackFloat _ = error "Can't unpack Float"
unpackDouble :: DbValue -> Double
unpackDouble (DbDouble i) = i
unpackDouble _ = error "Can't unpack Double"