module Database.Persist.Base
( PersistValue (..)
, SqlType (..)
, PersistField (..)
, PersistEntity (..)
, PersistBackend (..)
, PersistFilter (..)
, PersistUpdate (..)
, SelectOpt (..)
, SomePersistField (..)
, selectList
, insertBy
, getByValue
, getJust
, belongsTo
, belongsToJust
, checkUnique
, DeleteCascade (..)
, deleteCascadeWhere
, PersistException (..)
, Update (..)
, updateFieldName
, Filter (..)
, Key (..)
, EntityDef (..)
, ColumnName
, ColumnType
, ColumnDef (..)
, UniqueDef (..)
, fst3, snd3, third3
, limitOffsetOrder
, PersistConfig (..)
) where
import Data.Time (Day, TimeOfDay, UTCTime)
import Data.ByteString.Char8 (ByteString, unpack)
import Control.Applicative
import Data.Typeable (Typeable)
import Data.Int (Int8, Int16, Int32, Int64)
import Data.Word (Word8, Word16, Word32, Word64)
import Text.Blaze (Html, unsafeByteString)
import Text.Blaze.Renderer.Utf8 (renderHtml)
import qualified Data.Text as T
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import Data.Enumerator hiding (consume, map)
import Data.Enumerator.List (consume)
import qualified Data.Enumerator.List as EL
import qualified Control.Monad.IO.Class as Trans
import qualified Control.Exception as E
import Control.Monad.Trans.Error (Error (..))
import Data.Bits (bitSize)
import Control.Monad (liftM)
import qualified Data.Text.Encoding as T
import qualified Data.Text.Encoding.Error as T
import Web.PathPieces (SinglePiece (..))
import qualified Data.Text.Read
import Control.Monad.IO.Control (MonadControlIO)
import Data.Object (TextObject)
fst3 :: forall t t1 t2. (t, t1, t2) -> t
fst3 (x, _, _) = x
snd3 :: forall t t1 t2. (t, t1, t2) -> t1
snd3 (_, x, _) = x
third3 :: forall t t1 t2. (t, t1, t2) -> t2
third3 (_, _, x) = x
data PersistException
= PersistError String
| PersistMarshalError String
| PersistInvalidField String
| PersistForeignConstraintUnmet String
| PersistMongoDBError String
| PersistMongoDBUnsupported String
deriving (Show, Typeable)
instance E.Exception PersistException
instance Error PersistException where strMsg msg = PersistError msg
data PersistValue = PersistText T.Text
| PersistByteString ByteString
| PersistInt64 Int64
| PersistDouble Double
| PersistBool Bool
| PersistDay Day
| PersistTimeOfDay TimeOfDay
| PersistUTCTime UTCTime
| PersistNull
| PersistList [PersistValue]
| PersistMap [(T.Text, PersistValue)]
| PersistObjectId ByteString
deriving (Show, Read, Eq, Typeable, Ord)
instance SinglePiece PersistValue where
fromSinglePiece t =
case Data.Text.Read.signed Data.Text.Read.decimal t of
Right (i, t')
| T.null t' -> Just $ PersistInt64 i
_ -> Just $ PersistText t
toSinglePiece x =
case fromPersistValue x of
Left e -> error e
Right y -> y
data SqlType = SqlString
| SqlInt32
| SqlInteger
| SqlReal
| SqlBool
| SqlDay
| SqlTime
| SqlDayTime
| SqlBlob
deriving (Show, Read, Eq, Typeable)
class PersistField a where
toPersistValue :: a -> PersistValue
fromPersistValue :: PersistValue -> Either String a
sqlType :: a -> SqlType
isNullable :: a -> Bool
isNullable _ = False
instance PersistField String where
toPersistValue = PersistText . T.pack
fromPersistValue (PersistText s) = Right $ T.unpack s
fromPersistValue (PersistByteString bs) =
Right $ T.unpack $ T.decodeUtf8With T.lenientDecode bs
fromPersistValue (PersistInt64 i) = Right $ show i
fromPersistValue (PersistDouble d) = Right $ show d
fromPersistValue (PersistDay d) = Right $ show d
fromPersistValue (PersistTimeOfDay d) = Right $ show d
fromPersistValue (PersistUTCTime d) = Right $ show d
fromPersistValue PersistNull = Left "Unexpected null"
fromPersistValue (PersistBool b) = Right $ show b
fromPersistValue (PersistList _) = Left "Cannot convert PersistList to String"
fromPersistValue (PersistMap _) = Left "Cannot convert PersistMap to String"
fromPersistValue (PersistObjectId _) = Left "Cannot convert PersistObjectId to String"
sqlType _ = SqlString
instance PersistField ByteString where
toPersistValue = PersistByteString
fromPersistValue (PersistByteString bs) = Right bs
fromPersistValue x = T.encodeUtf8 <$> fromPersistValue x
sqlType _ = SqlBlob
instance PersistField T.Text where
toPersistValue = PersistText
fromPersistValue (PersistText s) = Right s
fromPersistValue (PersistByteString bs) =
Right $ T.decodeUtf8With T.lenientDecode bs
fromPersistValue (PersistInt64 i) = Right $ T.pack $ show i
fromPersistValue (PersistDouble d) = Right $ T.pack $ show d
fromPersistValue (PersistDay d) = Right $ T.pack $ show d
fromPersistValue (PersistTimeOfDay d) = Right $ T.pack $ show d
fromPersistValue (PersistUTCTime d) = Right $ T.pack $ show d
fromPersistValue PersistNull = Left "Unexpected null"
fromPersistValue (PersistBool b) = Right $ T.pack $ show b
fromPersistValue (PersistList _) = Left "Cannot convert PersistList to Text"
fromPersistValue (PersistMap _) = Left "Cannot convert PersistMap to Text"
fromPersistValue (PersistObjectId _) = Left "Cannot convert PersistObjectId to Text"
sqlType _ = SqlString
instance PersistField Html where
toPersistValue = PersistByteString . S.concat . L.toChunks . renderHtml
fromPersistValue = fmap unsafeByteString . fromPersistValue
sqlType _ = SqlString
instance PersistField Int where
toPersistValue = PersistInt64 . fromIntegral
fromPersistValue (PersistInt64 i) = Right $ fromIntegral i
fromPersistValue x = Left $ "Expected Integer, received: " ++ show x
sqlType x = case bitSize x of
32 -> SqlInt32
_ -> SqlInteger
instance PersistField Int8 where
toPersistValue = PersistInt64 . fromIntegral
fromPersistValue (PersistInt64 i) = Right $ fromIntegral i
fromPersistValue x = Left $ "Expected Integer, received: " ++ show x
sqlType _ = SqlInt32
instance PersistField Int16 where
toPersistValue = PersistInt64 . fromIntegral
fromPersistValue (PersistInt64 i) = Right $ fromIntegral i
fromPersistValue x = Left $ "Expected Integer, received: " ++ show x
sqlType _ = SqlInt32
instance PersistField Int32 where
toPersistValue = PersistInt64 . fromIntegral
fromPersistValue (PersistInt64 i) = Right $ fromIntegral i
fromPersistValue x = Left $ "Expected Integer, received: " ++ show x
sqlType _ = SqlInt32
instance PersistField Int64 where
toPersistValue = PersistInt64 . fromIntegral
fromPersistValue (PersistInt64 i) = Right $ fromIntegral i
fromPersistValue x = Left $ "Expected Integer, received: " ++ show x
sqlType _ = SqlInteger
instance PersistField Word8 where
toPersistValue = PersistInt64 . fromIntegral
fromPersistValue (PersistInt64 i) = Right $ fromIntegral i
fromPersistValue x = Left $ "Expected Wordeger, received: " ++ show x
sqlType _ = SqlInt32
instance PersistField Word16 where
toPersistValue = PersistInt64 . fromIntegral
fromPersistValue (PersistInt64 i) = Right $ fromIntegral i
fromPersistValue x = Left $ "Expected Wordeger, received: " ++ show x
sqlType _ = SqlInt32
instance PersistField Word32 where
toPersistValue = PersistInt64 . fromIntegral
fromPersistValue (PersistInt64 i) = Right $ fromIntegral i
fromPersistValue x = Left $ "Expected Wordeger, received: " ++ show x
sqlType _ = SqlInteger
instance PersistField Word64 where
toPersistValue = PersistInt64 . fromIntegral
fromPersistValue (PersistInt64 i) = Right $ fromIntegral i
fromPersistValue x = Left $ "Expected Wordeger, received: " ++ show x
sqlType _ = SqlInteger
instance PersistField Double where
toPersistValue = PersistDouble
fromPersistValue (PersistDouble d) = Right d
fromPersistValue x = Left $ "Expected Double, received: " ++ show x
sqlType _ = SqlReal
instance PersistField Bool where
toPersistValue = PersistBool
fromPersistValue (PersistBool b) = Right b
fromPersistValue (PersistInt64 i) = Right $ i /= 0
fromPersistValue x = Left $ "Expected Bool, received: " ++ show x
sqlType _ = SqlBool
instance PersistField Day where
toPersistValue = PersistDay
fromPersistValue (PersistDay d) = Right d
fromPersistValue x@(PersistText t) =
case reads $ T.unpack t of
(d, _):_ -> Right d
_ -> Left $ "Expected Day, received " ++ show x
fromPersistValue x@(PersistByteString s) =
case reads $ unpack s of
(d, _):_ -> Right d
_ -> Left $ "Expected Day, received " ++ show x
fromPersistValue x = Left $ "Expected Day, received: " ++ show x
sqlType _ = SqlDay
instance PersistField TimeOfDay where
toPersistValue = PersistTimeOfDay
fromPersistValue (PersistTimeOfDay d) = Right d
fromPersistValue x@(PersistText t) =
case reads $ T.unpack t of
(d, _):_ -> Right d
_ -> Left $ "Expected TimeOfDay, received " ++ show x
fromPersistValue x@(PersistByteString s) =
case reads $ unpack s of
(d, _):_ -> Right d
_ -> Left $ "Expected TimeOfDay, received " ++ show x
fromPersistValue x = Left $ "Expected TimeOfDay, received: " ++ show x
sqlType _ = SqlTime
instance PersistField UTCTime where
toPersistValue = PersistUTCTime
fromPersistValue (PersistUTCTime d) = Right d
fromPersistValue x@(PersistText t) =
case reads $ T.unpack t of
(d, _):_ -> Right d
_ -> Left $ "Expected UTCTime, received " ++ show x
fromPersistValue x@(PersistByteString s) =
case reads $ unpack s of
(d, _):_ -> Right d
_ -> Left $ "Expected UTCTime, received " ++ show x
fromPersistValue x = Left $ "Expected UTCTime, received: " ++ show x
sqlType _ = SqlDayTime
instance PersistField a => PersistField (Maybe a) where
toPersistValue Nothing = PersistNull
toPersistValue (Just a) = toPersistValue a
fromPersistValue PersistNull = Right Nothing
fromPersistValue x = fmap Just $ fromPersistValue x
sqlType _ = sqlType (error "this is the problem" :: a)
isNullable _ = True
data Update v = forall typ. PersistField typ => Update
{ updateField :: EntityField v typ
, updateValue :: typ
, updateUpdate :: PersistUpdate
}
updateFieldName :: PersistEntity v => Update v -> String
updateFieldName (Update f _ _) = columnName $ persistColumnDef f
data SelectOpt v = forall typ. Asc (EntityField v typ)
| forall typ. Desc (EntityField v typ)
| OffsetBy Int
| LimitTo Int
data Filter v = forall typ. PersistField typ => Filter
{ filterField :: EntityField v typ
, filterValue :: Either typ [typ]
, filterFilter :: PersistFilter
}
| FilterAnd [Filter v]
| FilterOr [Filter v]
class PersistEntity val where
data EntityField val :: * -> *
persistColumnDef :: EntityField val typ -> ColumnDef
data Unique val :: ((* -> *) -> * -> *) -> *
entityDef :: val -> EntityDef
toPersistFields :: val -> [SomePersistField]
fromPersistValues :: [PersistValue] -> Either String val
halfDefined :: val
persistUniqueToFieldNames :: Unique val backend -> [String]
persistUniqueToValues :: Unique val backend -> [PersistValue]
persistUniqueKeys :: val -> [Unique val backend]
data SomePersistField = forall a. PersistField a => SomePersistField a
instance PersistField SomePersistField where
toPersistValue (SomePersistField a) = toPersistValue a
fromPersistValue x = fmap SomePersistField (fromPersistValue x :: Either String String)
sqlType (SomePersistField a) = sqlType a
newtype Key backend entity = Key { unKey :: PersistValue }
deriving (Show, Read, Eq, Ord, PersistField)
class (Trans.MonadIO (b m), Trans.MonadIO m, Monad (b m), Monad m) => PersistBackend b m where
insert :: PersistEntity val => val -> b m (Key b val)
replace :: PersistEntity val => Key b val -> val -> b m ()
update :: PersistEntity val => Key b val -> [Update val] -> b m ()
updateWhere :: PersistEntity val => [Filter val] -> [Update val] -> b m ()
delete :: PersistEntity val => Key b val -> b m ()
deleteBy :: PersistEntity val => Unique val b -> b m ()
deleteWhere :: PersistEntity val => [Filter val] -> b m ()
get :: PersistEntity val => Key b val -> b m (Maybe val)
getBy :: PersistEntity val => Unique val b -> b m (Maybe (Key b val, val))
selectEnum
:: PersistEntity val
=> [Filter val]
-> [SelectOpt val]
-> Enumerator (Key b val, val) (b m) a
selectFirst :: PersistEntity val
=> [Filter val]
-> [SelectOpt val]
-> b m (Maybe (Key b val, val))
selectFirst filts opts = run_ $ selectEnum filts ((LimitTo 1):opts) ==<< EL.head
selectKeys :: PersistEntity val
=> [Filter val]
-> Enumerator (Key b val) (b m) a
count :: PersistEntity val => [Filter val] -> b m Int
insertBy :: (PersistEntity v, PersistBackend b m)
=> v -> b m (Either (Key b v, v) (Key b v))
insertBy val =
go $ persistUniqueKeys val
where
go [] = Right `liftM` insert val
go (x:xs) = do
y <- getBy x
case y of
Nothing -> go xs
Just z -> return $ Left z
getByValue :: (PersistEntity v, PersistBackend b m)
=> v -> b m (Maybe (Key b v, v))
getByValue val =
go $ persistUniqueKeys val
where
go [] = return Nothing
go (x:xs) = do
y <- getBy x
case y of
Nothing -> go xs
Just z -> return $ Just z
belongsTo ::
(PersistBackend b m
, PersistEntity ent1
, PersistEntity ent2) => (ent1 -> Maybe (Key b ent2)) -> ent1 -> b m (Maybe ent2)
belongsTo foreignKeyField model = case foreignKeyField model of
Nothing -> return Nothing
Just f -> get f
belongsToJust ::
(PersistBackend b m
, PersistEntity ent1
, PersistEntity ent2) => (ent1 -> Key b ent2) -> ent1 -> b m ent2
belongsToJust getForeignKey model = getJust $ getForeignKey model
getJust :: (PersistBackend b m, PersistEntity val, Show (Key b val)) => Key b val -> b m val
getJust key = get key >>= maybe
(Trans.liftIO . E.throwIO $ PersistForeignConstraintUnmet (show key))
return
checkUnique :: (PersistEntity val, PersistBackend b m) => val -> b m Bool
checkUnique val =
go $ persistUniqueKeys val
where
go [] = return True
go (x:xs) = do
y <- getBy x
case y of
Nothing -> go xs
Just _ -> return False
limitOffsetOrder :: PersistEntity val => [SelectOpt val] -> (Int, Int, [SelectOpt val])
limitOffsetOrder opts =
foldr go (0, 0, []) opts
where
go (LimitTo l) (_, b, c) = (l, b ,c)
go (OffsetBy o) (a, _, c) = (a, o, c)
go x (a, b, c) = (a, b, x : c)
selectList :: (PersistEntity val, PersistBackend b m)
=> [Filter val]
-> [SelectOpt val]
-> b m [(Key b val, val)]
selectList a b = do
res <- run $ selectEnum a b ==<< consume
case res of
Left e -> Trans.liftIO . E.throwIO $ PersistError $ show e
Right x -> return x
data EntityDef = EntityDef
{ entityName :: String
, entityAttribs :: [String]
, entityColumns :: [ColumnDef]
, entityUniques :: [UniqueDef]
, entityDerives :: [String]
}
deriving Show
type ColumnName = String
type ColumnType = String
data ColumnDef = ColumnDef
{ columnName :: ColumnName
, columnType :: ColumnType
, columnAttribs :: [String]
}
deriving Show
data UniqueDef = UniqueDef
{ uniqueName :: String
, uniqueColumns :: [ColumnName]
}
deriving Show
data PersistFilter = Eq | Ne | Gt | Lt | Ge | Le | In | NotIn
| BackendSpecificFilter String
deriving (Read, Show)
class PersistEntity a => DeleteCascade a b where
deleteCascade :: PersistBackend b m => Key b a -> b m ()
deleteCascadeWhere :: (DeleteCascade a b, PersistBackend b m)
=> [Filter a] -> b m ()
deleteCascadeWhere filts = do
res <- run $ selectKeys filts $ Continue iter
case res of
Left e -> Trans.liftIO . E.throwIO $ PersistError $ show e
Right () -> return ()
where
iter EOF = Iteratee $ return $ Yield () EOF
iter (Chunks keys) = Iteratee $ do
mapM_ deleteCascade keys
return $ Continue iter
data PersistUpdate = Assign | Add | Subtract | Multiply | Divide
deriving (Read, Show, Enum, Bounded)
instance PersistField PersistValue where
toPersistValue = id
fromPersistValue = Right
sqlType _ = SqlInteger
class PersistConfig c where
type PersistConfigBackend c :: (* -> *) -> * -> *
type PersistConfigPool c
loadConfig :: TextObject -> Either String c
withPool :: (Applicative m, MonadControlIO m) => c -> (PersistConfigPool c -> m a) -> m a
runPool :: MonadControlIO m => c -> PersistConfigBackend c m a
-> PersistConfigPool c
-> m a