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