packages feed

hasbolt-0.1.6.2: src/Database/Bolt/Value/Instances.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE BangPatterns      #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}

module Database.Bolt.Value.Instances where

import           Database.Bolt.Value.Helpers
import           Database.Bolt.Value.Type

import           Control.Monad                (forM, replicateM)
import           Control.Monad.Except         (MonadError (..))
import           Data.Binary                  (Binary (..), Put, decode, encode)
import           Data.Binary.Get
import           Data.Binary.IEEE754          (doubleToWord, wordToDouble)
import           Data.Binary.Put              (putByteString, putWord16be, putWord32be, putWord64be,
                                               putWord8)
import           Data.ByteString              (ByteString)
import qualified Data.ByteString              as B
import           Data.ByteString.Lazy         (fromStrict, toStrict)
import           Data.Int
import           Data.Map.Strict              (Map)
import qualified Data.Map.Strict              as M
import           Data.Text                    (Text)
import           Data.Text.Encoding           (decodeUtf8, encodeUtf8)
import           Data.Word

instance BoltValue () where
  pack () = putWord8 nullCode

  unpackT = getWord8 >>= unpackByMarker
    where unpackByMarker m | m == nullCode = pure ()
                           | otherwise     = fail "expected null"

instance BoltValue Bool where
  pack True  = putWord8 trueCode
  pack False = putWord8 falseCode

  unpackT = getWord8 >>= unpackByMarker
    where unpackByMarker m | m == trueCode  = pure True
                           | m == falseCode = pure False
                           | otherwise      = fail "expected bool"

instance BoltValue Int where
  pack int | isTinyInt int = putWord8 $ fromIntegral int
           | isIntX  8 int = putWord8 int8Code >> putWord8 (fromIntegral int)
           | isIntX 16 int = putWord8 int16Code >> putWord16be (fromIntegral int :: Word16)
           | isIntX 32 int = putWord8 int32Code >> putWord32be (fromIntegral int :: Word32)
           | isIntX 64 int = putWord8 int64Code >> putWord64be (fromIntegral int :: Word64)
           | otherwise     = error "Cannot pack so large integer"

  unpackT = getWord8 >>= unpackByMarker
    where unpackByMarker m | isTinyWord m   = pure . toInt $ (fromIntegral m :: Int8)
                           | m == int8Code  = toInt <$> getInt8
                           | m == int16Code = toInt <$> getInt16be
                           | m == int32Code = toInt <$> getInt32be
                           | m == int64Code = toInt <$> getInt64be
                           | otherwise      = fail "expected int"

instance BoltValue Double where
  pack dbl = putWord8 doubleCode >> putWord64be (doubleToWord dbl)

  unpackT = getWord8 >>= unpackByMarker
    where unpackByMarker m | m == doubleCode = wordToDouble <$> getWord64be
                           | otherwise       = fail "expected double"

instance BoltValue Text where
  pack txt = mkPackedCollection (B.length bs) pbs (textConst, text8Code, text16Code, text32Code)
    where bs = encodeUtf8 txt
          pbs = putByteString bs

  unpackT = getWord8 >>= unpackByMarker
    where unpackByMarker m | isTinyText m    = unpackTextBySize (getSize m)
                           | m == text8Code  = toInt <$> getInt8 >>= unpackTextBySize
                           | m == text16Code = toInt <$> getInt16be >>= unpackTextBySize
                           | m == text32Code = toInt <$> getInt32be >>= unpackTextBySize
                           | otherwise       = fail "expected text"
          unpackTextBySize size = do str <- getByteString size
                                     pure $! decodeUtf8 str

instance BoltValue a => BoltValue [a] where
  pack lst = mkPackedCollection (length lst) pbs (listConst, list8Code, list16Code, list32Code)
    where pbs = mapM_ pack lst

  unpackT = getWord8 >>= unpackByMarker
    where unpackByMarker m | isTinyList m    = unpackListBySize (getSize m)
                           | m == list8Code  = toInt <$> getInt8 >>= unpackListBySize
                           | m == list16Code = toInt <$> getInt16be >>= unpackListBySize
                           | m == list32Code = toInt <$> getInt32be >>= unpackListBySize
                           | otherwise       = fail "expected list"
          unpackListBySize size = forM [1..size] $ const unpackT

instance BoltValue a => BoltValue (Map Text a) where
  pack dict = mkPackedCollection (M.size dict) pbs (dictConst, dict8Code, dict16Code, dict32Code)
    where pbs = mapM_ mkPairPack $ M.assocs dict
          mkPairPack (key, val) = pack key >> pack val

  unpackT = getWord8 >>= unpackByMarker
    where unpackByMarker m | isTinyDict m    = unpackDictBySize (getSize m)
                           | m == dict8Code  = toInt <$> getInt16be >>= unpackDictBySize
                           | m == dict16Code = toInt <$> getInt16be >>= unpackDictBySize
                           | m == dict32Code = toInt <$> getInt32be >>= unpackDictBySize
                           | otherwise       = fail "expected dict"
          unpackDictBySize = (M.fromList <$>) . unpackPairsBySize
          unpackPairsBySize size = forM [1..size] $ const $ do
                                     !key <- unpackT
                                     !value <- unpackT
                                     pure (key, value)

instance BoltValue Structure where
  pack (Structure sig lst) | size < size4  = putWord8 (structConst + fromIntegral size) >> pData
                           | size < size8  = putWord8 struct8Code >> putWord8 (fromIntegral size) >> pData
                           | size < size16 = putWord8 struct16Code >> putWord16be size >> pData
                           | otherwise     = error "Cannot pack so large structure"
    where size = fromIntegral $ length lst :: Word16
          pData = putWord8 sig >> mapM_ pack lst

  unpackT = getWord8 >>= unpackByMarker
    where unpackByMarker m | isTinyStruct m    = unpackStructureBySize (getSize m)
                           | m == struct8Code  = toInt <$> getInt8 >>= unpackStructureBySize
                           | m == struct16Code = toInt <$> getInt16be >>= unpackStructureBySize
                           | otherwise         = fail "expected structure"
          unpackStructureBySize size = Structure <$> getWord8 <*> replicateM size unpackT

instance BoltValue Value where
  pack (N n) = pack n
  pack (B b) = pack b
  pack (I i) = pack i
  pack (F d) = pack d
  pack (T t) = pack t
  pack (L l) = pack l
  pack (M m) = pack m
  pack (S s) = pack s

  unpackT = lookAhead getWord8 >>= unpackByMarker
    where unpackByMarker m | isNull   m = N <$> unpackT
                           | isBool   m = B <$> unpackT
                           | isInt    m = I <$> unpackT
                           | isDouble m = F <$> unpackT
                           | isText   m = T <$> unpackT
                           | isList   m = L <$> unpackT
                           | isDict   m = M <$> unpackT
                           | isStruct m = S <$> unpackT
                           | otherwise  = fail "not value"

-- = Structure instances for Neo4j structures

instance FromStructure Node where
  fromStructure struct =
    case struct of
      (Structure sig [I nid, L vlbls, M prps]) | sig == sigNode -> flip (Node nid) prps <$> cnvT vlbls
      _                                                         -> throwError $ Not "Node"
    where
      cnvT []       = pure []
      cnvT (T x:xs) = (x:) <$> cnvT xs
      cnvT _        = throwError NotString

instance FromStructure Relationship where
  fromStructure struct =
    case struct of
      (Structure sig [I rid, I sni, I eni, T rt, M rp]) | sig == sigRel -> pure $ Relationship rid sni eni rt rp
      _                                                                 -> throwError $ Not "Relationship"

instance FromStructure URelationship where
  fromStructure struct =
    case struct of
      (Structure sig [I rid, T rt, M rp]) | sig == sigURel -> pure $ URelationship rid rt rp
      _                                                    -> throwError $ Not "URelationship"

instance FromStructure Path where
  fromStructure struct =
    case struct of
      (Structure sig [L vnp, L vrp, L vip]) | sig == sigPath -> Path <$> cnvN vnp <*> cnvR vrp <*> cnvI vip
      _                                                      -> throwError $ Not "Path"
    where
      cnvN []       = pure []
      cnvN (S x:xs) = (:) <$> fromStructure x <*> cnvN xs
      cnvN _        = throwError $ Not "Node"

      cnvR []       = pure []
      cnvR (S x:xs) = (:) <$> fromStructure x <*> cnvR xs
      cnvR _        = throwError NotStructure

      cnvI []       = pure []
      cnvI (I x:xs) = (x:) <$> cnvI xs
      cnvI _        = throwError NotInt


decodeStrict :: Binary a => ByteString -> a
decodeStrict = decode . fromStrict

encodeStrict :: Binary a => a -> ByteString
encodeStrict = toStrict . encode

-- |Pack collection using it's size and set of BOLT constants
mkPackedCollection :: Int -> Put -> (Word8, Word8, Word8, Word8) -> Put
mkPackedCollection size bst (wt, w8, w16, w32)
  | size < size4  = putWord8 (wt + fromIntegral size) >> bst
  | size < size8  = putWord8 w8 >> putWord8 (fromIntegral size) >> bst
  | size < size16 = putWord8 w16 >> putWord16be (fromIntegral size :: Word16) >> bst
  | size < size32 = putWord8 w32 >> putWord32be (fromIntegral size :: Word32) >> bst
  | otherwise  = error "Cannot pack so large collection"

size4,size8, size16,size32 :: Integral a => a
size4  = 2^(4  :: Int)
size8  = 2^(8  :: Int)
size16 = 2^(16 :: Int)
size32 = 2^(32 :: Int)