packages feed

persistent-redis-0.2.0: Database/Persist/Redis/Internal.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Database.Persist.Redis.Internal
	( toInsertFields
    , toKeyId
    , toEntityName
    , toKeyText
    , toB
    , mkEntity
	) where

import Data.Fixed
import Data.Time
import Data.Int (Int64)
import Data.Word (Word8)
import Control.Monad (liftM, liftM2, liftM3)
import Data.Binary (Binary(..), encode, getWord8, Get)
import qualified Data.Binary as Q
import Data.Text (Text, unpack)
import qualified Data.Text as T
import Database.Persist.Types
import Database.Persist.Class
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.UTF8 as U

toLabel :: FieldDef a -> B.ByteString
toLabel = U.fromString . unpack . unDBName . fieldDB

toEntityString :: PersistEntity val => val -> Text
toEntityString = unDBName . entityDB . entityDef . Just

toEntityName :: EntityDef a -> B.ByteString
toEntityName = U.fromString . unpack . unDBName . entityDB

instance Binary Text where
    put = put . U.fromString . unpack
    get = do 
        str <- Q.get
        return $ (T.pack . U.toString) str

instance HasResolution a => Binary (Fixed a) where
    put = put . toRational
    get = do
        x <- Q.get :: Get Rational
        return $ fromRational x

instance Binary DiffTime where
    put = put . toRational
    get = do
        x <- Q.get :: Get Rational
        return $ fromRational x

instance Binary Day where
    put (ModifiedJulianDay x) = put x
    get = do
        x <- Q.get :: Get Integer
        return $ ModifiedJulianDay x

instance Binary TimeOfDay where
    put (TimeOfDay h m s) = do
        put h
        put m
        put s
    get = liftM3 TimeOfDay (Q.get :: Get Int) (Q.get :: Get Int) (Q.get :: Get Pico)

instance Binary ZT where
    put (ZT (ZonedTime (LocalTime day timeOfDay) (TimeZone mins summer name))) = do
        put day
        put timeOfDay
        put mins
        put summer
        put name

    get = do
        day <- Q.get :: Get Day
        timeOfDay <- Q.get :: Get TimeOfDay
        mins <- Q.get :: Get Int
        summer <- Q.get :: Get Bool
        name <- Q.get :: Get String
        return $ ZT (ZonedTime (LocalTime day timeOfDay) (TimeZone mins summer name))

instance Binary PersistValue where
    put (PersistText x) = do
        put (1 :: Word8)
        put $ (U.fromString . unpack) x

    put (PersistByteString x) = do
        put (2 :: Word8)
        put x

    put (PersistInt64 x) = do
        put (3 :: Word8)
        put x

    put (PersistDouble x) = do
        put (4 :: Word8)
        put x

    put (PersistBool x) = do 
        put (5 :: Word8)
        put x

    put (PersistDay day) = do
        put (6 :: Word8)
        put day

    put (PersistTimeOfDay tod) = do
        put (7 :: Word8)
        put tod

    put (PersistUTCTime (UTCTime day pc)) = do
        put (8 :: Word8)
        put day
        put pc

    put (PersistNull) = put (9 :: Word8)
    put (PersistList x) = do 
        put (10 :: Word8)
        put x

    put (PersistMap x) = do
        put (11 :: Word8)
        put x

    put (PersistRational x) = do
        put (12 :: Word8)
        put x

    put (PersistZonedTime x) = do
        put (13 :: Word8)
        put x

    put (PersistDbSpecific _) = undefined
    put (PersistObjectId _) = error "PersistObjectId is not supported."

    get = do
        tag <- getWord8
        case tag of
            1 -> liftM PersistText (Q.get :: Get Text)
            2 -> liftM PersistByteString (Q.get :: Get B.ByteString)
            3 -> liftM PersistInt64 (Q.get :: Get Int64)
            4 -> liftM PersistDouble (Q.get :: Get Double)
            5 -> liftM PersistBool (Q.get :: Get Bool)
            6 -> liftM PersistDay (Q.get :: Get Day)
            7 -> liftM PersistTimeOfDay (Q.get :: Get TimeOfDay)
            8 -> liftM PersistUTCTime $ liftM2 UTCTime (Q.get :: Get Day) (Q.get :: Get DiffTime)
            9 -> return PersistNull
            10-> liftM PersistList (Q.get :: Get [PersistValue])
            11-> liftM PersistMap (Q.get :: Get [(Text, PersistValue)])
            12-> liftM PersistRational (Q.get :: Get Rational)
            13-> liftM PersistZonedTime (Q.get :: Get ZT)
            _ -> fail "Incorrect tag came to Binary deserialization"

toValue :: PersistValue -> B.ByteString
toValue = L.toStrict . encode

castOne :: B.ByteString -> PersistValue
castOne = Q.decode . L.fromStrict

redisToPerisistValues :: [(B.ByteString, B.ByteString)] -> [PersistValue]
redisToPerisistValues = map (castOne . snd)

mkEntity :: (Monad m, PersistEntity val) => Key val -> [(B.ByteString, B.ByteString)] -> m (Entity val)
mkEntity key fields = do
    let values = redisToPerisistValues fields
    let v = fromPersistValues values
    case v of
        Right body -> return $ Entity key body
        Left a -> fail (unpack a)


zipAndConvert :: PersistField t => [FieldDef a] -> [t] -> [(B.ByteString, B.ByteString)]
zipAndConvert [] _ = []
zipAndConvert _ [] = []
zipAndConvert (e:efields) (p:pfields) = 
    let pv = toPersistValue p
    in
        if pv == PersistNull then zipAndConvert efields pfields
            else (toLabel e, toValue pv) : zipAndConvert efields pfields

-- | Create a list for create/update in Redis store
toInsertFields :: PersistEntity val => val -> [(B.ByteString, B.ByteString)]
toInsertFields record = zipAndConvert entity fields
    where
        entity = entityFields $ entityDef $ Just record
        fields = toPersistFields record

underscoreBs :: B.ByteString
underscoreBs = U.fromString "_"

-- | Make a key for given entity and id
toKeyText :: PersistEntity val => val -> Integer -> Text
toKeyText val k = T.append (T.append (toEntityString val) "_") (T.pack $ show k)

toB :: Text -> B.ByteString
toB = U.fromString . unpack

-- | Create a string key for given entity
toObjectPrefix :: PersistEntity val => val -> B.ByteString
toObjectPrefix val = B.append (toEntityName $ entityDef $ Just val) underscoreBs

idBs :: B.ByteString
idBs = U.fromString "id"

-- | Construct an id key, that is incremented for access
toKeyId :: PersistEntity val => val -> B.ByteString
toKeyId val = B.append (toObjectPrefix val) idBs