persistent 1.3.3 → 2.0.0
raw patch · 21 files changed
+714/−743 lines, 21 filesdep +fast-loggerdep +mtldep +taggeddep ~basedep ~exceptionsdep ~resourcet
Dependencies added: fast-logger, mtl, tagged
Dependency ranges changed: base, exceptions, resourcet
Files
- Database/Persist/Class.hs +8/−0
- Database/Persist/Class/DeleteCascade.hs +12/−5
- Database/Persist/Class/PersistEntity.hs +51/−38
- Database/Persist/Class/PersistField.hs +7/−18
- Database/Persist/Class/PersistQuery.hs +60/−97
- Database/Persist/Class/PersistStore.hs +75/−75
- Database/Persist/Class/PersistUnique.hs +55/−68
- Database/Persist/Quasi.hs +51/−45
- Database/Persist/Sql.hs +8/−5
- Database/Persist/Sql/Class.hs +12/−60
- Database/Persist/Sql/Internal.hs +10/−20
- Database/Persist/Sql/Migration.hs +25/−24
- Database/Persist/Sql/Orphan/PersistQuery.hs +54/−72
- Database/Persist/Sql/Orphan/PersistStore.hs +100/−40
- Database/Persist/Sql/Orphan/PersistUnique.hs +15/−17
- Database/Persist/Sql/Raw.hs +40/−21
- Database/Persist/Sql/Run.hs +37/−17
- Database/Persist/Sql/Types.hs +22/−70
- Database/Persist/Types/Base.hs +65/−45
- persistent.cabal +7/−3
- test/main.hs +0/−3
Database/Persist/Class.hs view
@@ -11,9 +11,13 @@ , getByValue , insertBy , replaceUnique+ , checkUnique+ , onlyUnique -- * PersistQuery , PersistQuery (..)+ , selectSource+ , selectKeys , selectList , selectKeysList @@ -27,6 +31,10 @@ , PersistField (..) -- * PersistConfig , PersistConfig (..)++ -- * Lifting+ , HasPersistBackend (..)+ , liftPersist -- * JSON utilities , keyValueEntityToJSON, keyValueEntityFromJSON
Database/Persist/Class/DeleteCascade.hs view
@@ -11,10 +11,17 @@ import qualified Data.Conduit as C import qualified Data.Conduit.List as CL+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Reader (ReaderT, ask, runReaderT)+import Data.Acquire (with) -class (PersistStore m, PersistEntity a, PersistEntityBackend a ~ PersistMonadBackend m) => DeleteCascade a m where- deleteCascade :: Key a -> m ()+class (PersistStore backend, PersistEntity record, backend ~ PersistEntityBackend record)+ => DeleteCascade record backend where+ deleteCascade :: MonadIO m => Key record -> ReaderT backend m () -deleteCascadeWhere :: (DeleteCascade a m, PersistQuery m)- => [Filter a] -> m ()-deleteCascadeWhere filts = selectKeys filts [] C.$$ CL.mapM_ deleteCascade+deleteCascadeWhere :: (MonadIO m, DeleteCascade record backend, PersistQuery backend)+ => [Filter record] -> ReaderT backend m ()+deleteCascadeWhere filts = do+ srcRes <- selectKeysRes filts []+ conn <- ask+ liftIO $ with srcRes (C.$$ CL.mapM_ (flip runReaderT conn . deleteCascade))
Database/Persist/Class/PersistEntity.hs view
@@ -2,13 +2,14 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE FlexibleContexts, StandaloneDeriving, UndecidableInstances #-} module Database.Persist.Class.PersistEntity ( PersistEntity (..) , Update (..)+ , BackendSpecificUpdate , SelectOpt (..)- , BackendSpecificFilter , Filter (..)- , Key+ , BackendSpecificFilter , Entity (..) , keyValueEntityToJSON, keyValueEntityFromJSON@@ -36,35 +37,45 @@ -- -- Some advanced type system capabilities are used to make this process type-safe. -- Persistent users usually don't need to understand the class associated data and functions.-class PersistEntity record where- -- | An 'EntityField' is parameterised by the Haskell record it belongs to- -- and the additional type of that field- data EntityField record :: * -> *-- -- | return meta-data for a given 'EntityField'- persistFieldDef :: EntityField record typ -> FieldDef SqlType-- -- | Persistent allows multiple different backends+class ( PersistField (Key record), ToJSON (Key record), FromJSON (Key record)+ , Show (Key record), Read (Key record), Eq (Key record), Ord (Key record))+ => PersistEntity record where+ -- | Persistent allows multiple different backends (databases) type PersistEntityBackend record - -- | Unique keys besided the Key- data Unique record+ -- | By default, a backend will automatically generate the key+ -- Instead you can specify a Primary key made up of unique values.+ data Key record+ -- | a lower-level key operation+ keyToValues :: Key record -> [PersistValue]+ -- | a lower-level key operation+ keyFromValues :: [PersistValue] -> Either Text (Key record)+ -- | a meta-operation to retrieve the Key EntityField+ persistIdField :: EntityField record (Key record) -- | retrieve the EntityDef meta-data for the record- entityDef :: Monad m => m record -> EntityDef SqlType+ entityDef :: Monad m => m record -> EntityDef - -- | Get the database fields of a record+ -- | An 'EntityField' is parameterised by the Haskell record it belongs to+ -- and the additional type of that field+ data EntityField record :: * -> *+ -- | return meta-data for a given 'EntityField'+ persistFieldDef :: EntityField record typ -> FieldDef+ -- | A meta-operation to get the database fields of a record toPersistFields :: record -> [SomePersistField]-- -- | Convert from database values to a Haskell record+ -- | A lower-level operation to convert from database values to a Haskell record fromPersistValues :: [PersistValue] -> Either Text record + -- | Unique keys besided the Key+ data Unique record+ -- | A meta operation to retrieve all the Unique keys+ persistUniqueKeys :: record -> [Unique record]+ -- | A lower level operation persistUniqueToFieldNames :: Unique record -> [(HaskellName, DBName)]+ -- | A lower level operation persistUniqueToValues :: Unique record -> [PersistValue]- persistUniqueKeys :: record -> [Unique record] - persistIdField :: EntityField record (Key record)-+ -- | Use a PersistField as a lens fieldLens :: EntityField record field -> (forall f. Functor f => (field -> f field) -> Entity record -> f (Entity record)) @@ -108,11 +119,6 @@ | BackendFilter (BackendSpecificFilter (PersistEntityBackend record) record) --- | Helper wrapper, equivalent to @Key (PersistEntityBackend val) val@.------ Since 1.1.0-type Key record = KeyBackend (PersistEntityBackend record) record- -- | Datatype that represents an entity, with both its 'Key' and -- its Haskell record representation. --@@ -143,11 +149,15 @@ -- your query returns two entities (i.e. @(Entity backend a, -- Entity backend b)@), then you must you use @SELECT ??, ?? -- WHERE ...@, and so on.-data Entity entity =- Entity { entityKey :: Key entity- , entityVal :: entity }- deriving (Eq, Ord, Show, Read)+data PersistEntity record => Entity record =+ Entity { entityKey :: Key record+ , entityVal :: record } +deriving instance (PersistEntity record, Eq (Key record), Eq record) => Eq (Entity record)+deriving instance (PersistEntity record, Ord (Key record), Ord record) => Ord (Entity record)+deriving instance (PersistEntity record, Show (Key record), Show record) => Show (Entity record)+deriving instance (PersistEntity record, Read (Key record), Read record) => Read (Entity record)+ -- | Predefined @toJSON@. The resulting JSON looks like -- @{\"key\": 1, \"value\": {\"name\": ...}}@. --@@ -157,7 +167,8 @@ -- instance ToJSON User where -- toJSON = keyValueEntityToJSON -- @-keyValueEntityToJSON :: ToJSON e => Entity e -> Value+keyValueEntityToJSON :: (PersistEntity record, ToJSON record, ToJSON (Key record))+ => Entity record -> Value keyValueEntityToJSON (Entity key value) = object [ "key" .= key , "value" .= value@@ -172,7 +183,8 @@ -- instance FromJSON User where -- parseJSON = keyValueEntityFromJSON -- @-keyValueEntityFromJSON :: FromJSON e => Value -> Parser (Entity e)+keyValueEntityFromJSON :: (PersistEntity record, FromJSON record, FromJSON (Key record))+ => Value -> Parser (Entity record) keyValueEntityFromJSON (Object o) = Entity <$> o .: "key" <*> o .: "value"@@ -187,7 +199,7 @@ -- instance ToJSON User where -- toJSON = entityIdToJSON -- @-entityIdToJSON :: ToJSON e => Entity e -> Value+entityIdToJSON :: (PersistEntity record, ToJSON record, ToJSON (Key record)) => Entity record -> Value entityIdToJSON (Entity key value) = case toJSON value of Object o -> Object $ HM.insert "id" (toJSON key) o x -> x@@ -201,21 +213,22 @@ -- instance FromJSON User where -- parseJSON = entityIdFromJSON -- @-entityIdFromJSON :: FromJSON e => Value -> Parser (Entity e)+entityIdFromJSON :: (PersistEntity record, FromJSON record, FromJSON (Key record)) => Value -> Parser (Entity record) entityIdFromJSON value@(Object o) = Entity <$> o .: "id" <*> parseJSON value entityIdFromJSON _ = fail "entityIdFromJSON: not an object" -instance PersistField entity => PersistField (Entity entity) where+instance (PersistEntity record, PersistField record, PersistField (Key record))+ => PersistField (Entity record) where toPersistValue (Entity key value) = case toPersistValue value of (PersistMap alist) -> PersistMap ((idField, toPersistValue key) : alist) _ -> error $ T.unpack $ errMsg "expected PersistMap" fromPersistValue (PersistMap alist) = case after of [] -> Left $ errMsg $ "did not find " `mappend` idField `mappend` " field"- ("_id", k):afterRest ->- case fromPersistValue (PersistMap (before ++ afterRest)) of- Right record -> Right $ Entity (Key k) record- Left err -> Left err+ ("_id", kv):afterRest ->+ fromPersistValue (PersistMap (before ++ afterRest)) >>= \record ->+ keyFromValues [kv] >>= \k ->+ Right (Entity k record) _ -> Left $ errMsg $ "impossible id field: " `mappend` T.pack (show alist) where (before, after) = break ((== idField) . fst) alist
Database/Persist/Class/PersistField.hs view
@@ -19,7 +19,6 @@ #ifdef HIGH_PRECISION_DATE import Data.Time.Clock.POSIX (posixSecondsToUTCTime) #endif-import Data.Time.LocalTime (ZonedTime) import Data.ByteString.Char8 (ByteString, unpack, readInt) import Control.Applicative import Data.Int (Int8, Int16, Int32, Int64)@@ -47,6 +46,7 @@ import qualified Data.Map as M import qualified Data.Text.Encoding as TE+import qualified Data.Vector as V -- | A value which can be marshalled to and from a 'PersistValue'. class PersistField a where@@ -65,7 +65,6 @@ fromPersistValue (PersistDay d) = Right $ Prelude.show d fromPersistValue (PersistTimeOfDay d) = Right $ Prelude.show d fromPersistValue (PersistUTCTime d) = Right $ Prelude.show d- fromPersistValue (PersistZonedTime (ZT z)) = Right $ Prelude.show z fromPersistValue PersistNull = Left $ T.pack "Unexpected null" fromPersistValue (PersistBool b) = Right $ Prelude.show b fromPersistValue (PersistList _) = Left $ T.pack "Cannot convert PersistList to String"@@ -81,7 +80,7 @@ instance PersistField T.Text where toPersistValue = PersistText- fromPersistValue = either (Left . T.pack) Right . fromPersistValueText+ fromPersistValue = fromPersistValueText instance PersistField TL.Text where toPersistValue = toPersistValue . TL.toStrict@@ -230,19 +229,6 @@ fromPersistValue x = Left $ T.pack $ "Expected UTCTime, received: " ++ show x -instance PersistField ZonedTime where- toPersistValue = PersistZonedTime . ZT- fromPersistValue (PersistZonedTime (ZT z)) = Right z- fromPersistValue x@(PersistText t) =- case reads $ T.unpack t of- (z, _):_ -> Right z- _ -> Left $ T.pack $ "Expected ZonedTime, received " ++ show x- fromPersistValue x@(PersistByteString s) =- case reads $ unpack s of- (z, _):_ -> Right z- _ -> Left $ T.pack $ "Expected ZonedTime, received " ++ show x- fromPersistValue x = Left $ T.pack $ "Expected ZonedTime, received: " ++ show x- instance PersistField a => PersistField (Maybe a) where toPersistValue Nothing = PersistNull toPersistValue (Just a) = toPersistValue a@@ -260,6 +246,11 @@ fromPersistValue (PersistNull) = Right [] fromPersistValue x = Left $ T.pack $ "Expected PersistList, received: " ++ show x +instance PersistField a => PersistField (V.Vector a) where+ toPersistValue = toPersistValue . V.toList+ fromPersistValue = either (\e -> Left ("Vector: " `T.append` e))+ (Right . V.fromList) . fromPersistValue+ instance (Ord a, PersistField a) => PersistField (S.Set a) where toPersistValue = PersistList . map toPersistValue . S.toList fromPersistValue (PersistList list) =@@ -286,8 +277,6 @@ instance PersistField PersistValue where toPersistValue = id fromPersistValue = Right--deriving instance PersistField (KeyBackend backend entity) fromPersistList :: PersistField a => [PersistValue] -> Either T.Text [a] fromPersistList = mapM fromPersistValue
Database/Persist/Class/PersistQuery.hs view
@@ -2,136 +2,99 @@ {-# LANGUAGE TypeFamilies #-} module Database.Persist.Class.PersistQuery ( PersistQuery (..)+ , selectSource+ , selectKeys , selectList , selectKeysList ) where -import Control.Exception (throwIO) import Database.Persist.Types--import Control.Monad.Trans.Error (Error (..))--import Control.Monad.Trans.Class (lift) import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Monoid (Monoid)--import Data.Conduit.Internal (Pipe, ConduitM)-import Control.Monad.Logger (LoggingT)-import Control.Monad.Trans.Identity ( IdentityT)-import Control.Monad.Trans.List ( ListT )-import Control.Monad.Trans.Maybe ( MaybeT )-import Control.Monad.Trans.Error ( ErrorT )--#if MIN_VERSION_transformers(0,4,0)-import Control.Monad.Trans.Except ( ExceptT )-#endif--import Control.Monad.Trans.Reader ( ReaderT )-import Control.Monad.Trans.Cont ( ContT )-import Control.Monad.Trans.State ( StateT )-import Control.Monad.Trans.Writer ( WriterT )-import Control.Monad.Trans.RWS ( RWST )-import Control.Monad.Trans.Resource ( ResourceT)+import Control.Monad.Reader ( ReaderT, MonadReader ) -import qualified Control.Monad.Trans.RWS.Strict as Strict ( RWST )-import qualified Control.Monad.Trans.State.Strict as Strict ( StateT )-import qualified Control.Monad.Trans.Writer.Strict as Strict ( WriterT ) import qualified Data.Conduit as C import qualified Data.Conduit.List as CL import Database.Persist.Class.PersistStore import Database.Persist.Class.PersistEntity--class PersistStore m => PersistQuery m where- -- | Update individual fields on a specific record.- update :: (PersistEntity val, PersistEntityBackend val ~ PersistMonadBackend m)- => Key val -> [Update val] -> m ()-- -- | Update individual fields on a specific record, and retrieve the- -- updated value from the database.- --- -- Note that this function will throw an exception if the given key is not- -- found in the database.- updateGet :: (PersistEntity val, PersistMonadBackend m ~ PersistEntityBackend val)- => Key val -> [Update val] -> m val- updateGet key ups = do- update key ups- get key >>= maybe (liftIO $ throwIO $ KeyNotFound $ show key) return+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Resource (MonadResource, release)+import Data.Acquire (Acquire, mkAcquire, allocateAcquire, with) +class PersistStore backend => PersistQuery backend where -- | Update individual fields on any record matching the given criterion.- updateWhere :: (PersistEntity val, PersistEntityBackend val ~ PersistMonadBackend m)- => [Filter val] -> [Update val] -> m ()+ updateWhere :: (MonadIO m, PersistEntity val, backend ~ PersistEntityBackend val)+ => [Filter val] -> [Update val] -> ReaderT backend m () -- | Delete all records matching the given criterion.- deleteWhere :: (PersistEntity val, PersistEntityBackend val ~ PersistMonadBackend m)- => [Filter val] -> m ()+ deleteWhere :: (MonadIO m, PersistEntity val, backend ~ PersistEntityBackend val)+ => [Filter val] -> ReaderT backend m () -- | Get all records matching the given criterion in the specified order. -- Returns also the identifiers.- selectSource- :: (PersistEntity val, PersistEntityBackend val ~ PersistMonadBackend m)+ selectSourceRes+ :: (PersistEntity val, PersistEntityBackend val ~ backend, MonadIO m1, MonadIO m2) => [Filter val] -> [SelectOpt val]- -> C.Source m (Entity val)+ -> ReaderT backend m1 (Acquire (C.Source m2 (Entity val))) -- | get just the first record for the criterion- selectFirst :: (PersistEntity val, PersistEntityBackend val ~ PersistMonadBackend m)+ selectFirst :: (MonadIO m, PersistEntity val, backend ~ PersistEntityBackend val) => [Filter val] -> [SelectOpt val]- -> m (Maybe (Entity val))- selectFirst filts opts = selectSource filts ((LimitTo 1):opts) C.$$ CL.head-+ -> ReaderT backend m (Maybe (Entity val))+ selectFirst filts opts = do+ srcRes <- selectSourceRes filts ((LimitTo 1):opts)+ liftIO $ with srcRes (C.$$ CL.head) -- | Get the 'Key's of all records matching the given criterion.- selectKeys :: (PersistEntity val, PersistEntityBackend val ~ PersistMonadBackend m)- => [Filter val]- -> [SelectOpt val]- -> C.Source m (Key val)+ selectKeysRes+ :: (MonadIO m1, MonadIO m2, PersistEntity val, backend ~ PersistEntityBackend val)+ => [Filter val]+ -> [SelectOpt val]+ -> ReaderT backend m1 (Acquire (C.Source m2 (Key val))) -- | The total number of records fulfilling the given criterion.- count :: (PersistEntity val, PersistEntityBackend val ~ PersistMonadBackend m)- => [Filter val] -> m Int+ count :: (MonadIO m, PersistEntity val, backend ~ PersistEntityBackend val)+ => [Filter val] -> ReaderT backend m Int +-- | Get all records matching the given criterion in the specified order.+-- Returns also the identifiers.+selectSource+ :: (PersistQuery backend, MonadResource m, PersistEntity val, PersistEntityBackend val ~ backend, MonadReader env m, HasPersistBackend env backend)+ => [Filter val]+ -> [SelectOpt val]+ -> C.Source m (Entity val)+selectSource filts opts = do+ srcRes <- liftPersist $ selectSourceRes filts opts+ (releaseKey, src) <- allocateAcquire srcRes+ src+ release releaseKey++-- | Get the 'Key's of all records matching the given criterion.+selectKeys :: (PersistQuery backend, MonadResource m, PersistEntity val, backend ~ PersistEntityBackend val, MonadReader env m, HasPersistBackend env backend)+ => [Filter val]+ -> [SelectOpt val]+ -> C.Source m (Key val)+selectKeys filts opts = do+ srcRes <- liftPersist $ selectKeysRes filts opts+ (releaseKey, src) <- allocateAcquire srcRes+ src+ release releaseKey+ -- | Call 'selectSource' but return the result as a list.-selectList :: (PersistEntity val, PersistQuery m, PersistEntityBackend val ~ PersistMonadBackend m)+selectList :: (MonadIO m, PersistEntity val, PersistQuery backend, PersistEntityBackend val ~ backend) => [Filter val] -> [SelectOpt val]- -> m [Entity val]-selectList a b = selectSource a b C.$$ CL.consume+ -> ReaderT backend m [Entity val]+selectList filts opts = do+ srcRes <- selectSourceRes filts opts+ liftIO $ with srcRes (C.$$ CL.consume) -- | Call 'selectKeys' but return the result as a list.-selectKeysList :: (PersistEntity val, PersistQuery m, PersistEntityBackend val ~ PersistMonadBackend m)+selectKeysList :: (MonadIO m, PersistEntity val, PersistQuery backend, PersistEntityBackend val ~ backend) => [Filter val] -> [SelectOpt val]- -> m [Key val]-selectKeysList a b = selectKeys a b C.$$ CL.consume---#define DEF(T) { update k = lift . update k; updateGet k = lift . updateGet k; updateWhere f = lift . updateWhere f; deleteWhere = lift . deleteWhere; selectSource f = C.transPipe lift . selectSource f; selectFirst f = lift . selectFirst f; selectKeys f = C.transPipe lift . selectKeys f; count = lift . count }-#define GO(T) instance (PersistQuery m) => PersistQuery (T m) where DEF(T)-#define GOX(X, T) instance (X, PersistQuery m) => PersistQuery (T m) where DEF(T)--GO(LoggingT)-GO(IdentityT)-GO(ListT)-GO(MaybeT)-GOX(Error e, ErrorT e)--#if MIN_VERSION_transformers(0,4,0)-GO(ExceptT e)-#endif--GO(ReaderT r)-GO(ContT r)-GO(StateT s)-GO(ResourceT)-GO(Pipe l i o u)-GO(ConduitM i o)-GOX(Monoid w, WriterT w)-GOX(Monoid w, RWST r w s)-GOX(Monoid w, Strict.RWST r w s)-GO(Strict.StateT s)-GOX(Monoid w, Strict.WriterT w)--#undef DEF-#undef GO-#undef GOX+ -> ReaderT backend m [Key val]+selectKeysList filts opts = do+ srcRes <- selectKeysRes filts opts+ liftIO $ with srcRes (C.$$ CL.consume)
Database/Persist/Class/PersistStore.hs view
@@ -1,7 +1,12 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE TypeFamilies, FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-} module Database.Persist.Class.PersistStore- ( PersistStore (..)+ ( HasPersistBackend (..)+ , liftPersist+ , PersistStore (..) , getJust , belongsTo , belongsToJust@@ -13,85 +18,107 @@ import qualified Data.Text as T import Control.Monad.Trans.Error (Error (..))-import Control.Monad.Trans.Class (lift) import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Monoid (Monoid) import Control.Exception.Lifted (throwIO) import Data.Conduit.Internal (Pipe, ConduitM)-import Control.Monad.Logger (LoggingT)-import Control.Monad.Trans.Identity ( IdentityT)-import Control.Monad.Trans.List ( ListT )-import Control.Monad.Trans.Maybe ( MaybeT )-import Control.Monad.Trans.Error ( ErrorT ) -#if MIN_VERSION_transformers(0,4,0)-import Control.Monad.Trans.Except ( ExceptT )-#endif- import Control.Monad.Trans.Reader ( ReaderT )-import Control.Monad.Trans.Cont ( ContT )-import Control.Monad.Trans.State ( StateT )-import Control.Monad.Trans.Writer ( WriterT )-import Control.Monad.Trans.RWS ( RWST )-import Control.Monad.Trans.Resource ( ResourceT)+import Control.Monad.Reader (MonadReader (ask), runReaderT) -import qualified Control.Monad.Trans.RWS.Strict as Strict ( RWST )-import qualified Control.Monad.Trans.State.Strict as Strict ( StateT )-import qualified Control.Monad.Trans.Writer.Strict as Strict ( WriterT ) import Database.Persist.Class.PersistEntity+import Database.Persist.Class.PersistField import Database.Persist.Types+import qualified Data.Aeson as A -class MonadIO m => PersistStore m where- type PersistMonadBackend m+class HasPersistBackend env backend | env -> backend where+ persistBackend :: env -> backend +liftPersist :: (MonadReader env m, HasPersistBackend env backend, MonadIO m)+ => ReaderT backend IO a+ -> m a+liftPersist f = do+ env <- ask+ liftIO $ runReaderT f (persistBackend env)++class+ ( Show (BackendKey backend), Read (BackendKey backend)+ , Eq (BackendKey backend), Ord (BackendKey backend)+ , PersistField (BackendKey backend), A.ToJSON (BackendKey backend), A.FromJSON (BackendKey backend)+ ) => PersistStore backend where+ data BackendKey backend++ backendKeyToValues :: BackendKey backend -> [PersistValue]+ backendKeyFromValues :: [PersistValue] -> Either T.Text (BackendKey backend)+ -- | Get a record by identifier, if available.- get :: (PersistMonadBackend m ~ PersistEntityBackend val, PersistEntity val)- => Key val -> m (Maybe val)+ get :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val)+ => Key val -> ReaderT backend m (Maybe val) -- | Create a new record in the database, returning an automatically created -- key (in SQL an auto-increment id).- insert :: (PersistMonadBackend m ~ PersistEntityBackend val, PersistEntity val)- => val -> m (Key val)+ insert :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val)+ => val -> ReaderT backend m (Key val) -- | Same as 'insert', but doesn't return a @Key@.- insert_ :: (PersistMonadBackend m ~ PersistEntityBackend val, PersistEntity val)- => val -> m ()+ insert_ :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val)+ => val -> ReaderT backend m () insert_ val = insert val >> return () -- | Create multiple records in the database. -- SQL backends currently use the slow default implementation of -- @mapM insert@- insertMany :: (PersistMonadBackend m ~ PersistEntityBackend val, PersistEntity val)- => [val] -> m [Key val]+ insertMany :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val)+ => [val] -> ReaderT backend m [Key val] insertMany = mapM insert -- | Create a new record in the database using the given key.- insertKey :: (PersistMonadBackend m ~ PersistEntityBackend val, PersistEntity val)- => Key val -> val -> m ()+ insertKey :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val)+ => Key val -> val -> ReaderT backend m () -- | Put the record in the database with the given key. -- Unlike 'replace', if a record with the given key does not -- exist then a new record will be inserted.- repsert :: (PersistMonadBackend m ~ PersistEntityBackend val, PersistEntity val)- => Key val -> val -> m ()+ repsert :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val)+ => Key val -> val -> ReaderT backend m () -- | Replace the record in the database with the given -- key. Note that the result is undefined if such record does -- not exist, so you must use 'insertKey or 'repsert' in -- these cases.- replace :: (PersistMonadBackend m ~ PersistEntityBackend val, PersistEntity val)- => Key val -> val -> m ()+ replace :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val)+ => Key val -> val -> ReaderT backend m () -- | Delete a specific record by identifier. Does nothing if record does -- not exist.- delete :: (PersistMonadBackend m ~ PersistEntityBackend val, PersistEntity val)- => Key val -> m ()+ delete :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val)+ => Key val -> ReaderT backend m () + -- | Update individual fields on a specific record.+ update :: (MonadIO m, PersistEntity val, backend ~ PersistEntityBackend val)+ => Key val -> [Update val] -> ReaderT backend m ()++ -- | Update individual fields on a specific record, and retrieve the+ -- updated value from the database.+ --+ -- Note that this function will throw an exception if the given key is not+ -- found in the database.+ updateGet :: (MonadIO m, PersistEntity val, backend ~ PersistEntityBackend val)+ => Key val -> [Update val] -> ReaderT backend m val+ updateGet key ups = do+ update key ups+ get key >>= maybe (liftIO $ throwIO $ KeyNotFound $ Prelude.show key) return++ -- | Same as get, but for a non-null (not Maybe) foreign key -- Unsafe unless your database is enforcing that the foreign key is valid-getJust :: (PersistStore m, PersistEntity val, Show (Key val), PersistMonadBackend m ~ PersistEntityBackend val) => Key val -> m val+getJust :: ( PersistStore backend+ , PersistEntity val+ , Show (Key val)+ , backend ~ PersistEntityBackend val+ , MonadIO m+ ) => Key val -> ReaderT backend m val getJust key = get key >>= maybe (liftIO $ throwIO $ PersistForeignConstraintUnmet $ T.pack $ Prelude.show key) return@@ -99,50 +126,23 @@ -- | curry this to make a convenience function that loads an associated model -- > foreign = belongsTo foeignId belongsTo ::- (PersistStore m+ ( PersistStore backend , PersistEntity ent1 , PersistEntity ent2- , PersistMonadBackend m ~ PersistEntityBackend ent2- ) => (ent1 -> Maybe (Key ent2)) -> ent1 -> m (Maybe ent2)+ , backend ~ PersistEntityBackend ent2+ , MonadIO m+ ) => (ent1 -> Maybe (Key ent2)) -> ent1 -> ReaderT backend m (Maybe ent2) belongsTo foreignKeyField model = case foreignKeyField model of Nothing -> return Nothing Just f -> get f -- | same as belongsTo, but uses @getJust@ and therefore is similarly unsafe belongsToJust ::- (PersistStore m+ ( PersistStore backend , PersistEntity ent1 , PersistEntity ent2- , PersistMonadBackend m ~ PersistEntityBackend ent2)- => (ent1 -> Key ent2) -> ent1 -> m ent2+ , backend ~ PersistEntityBackend ent2+ , MonadIO m+ )+ => (ent1 -> Key ent2) -> ent1 -> ReaderT backend m ent2 belongsToJust getForeignKey model = getJust $ getForeignKey model--#define DEF(T) { type PersistMonadBackend (T m) = PersistMonadBackend m; insert = lift . insert; insertKey k = lift . insertKey k; repsert k = lift . repsert k; replace k = lift . replace k; delete = lift . delete; get = lift . get }-#define GO(T) instance (PersistStore m) => PersistStore (T m) where DEF(T)-#define GOX(X, T) instance (X, PersistStore m) => PersistStore (T m) where DEF(T)--GO(LoggingT)-GO(IdentityT)-GO(ListT)-GO(MaybeT)-GOX(Error e, ErrorT e)--#if MIN_VERSION_transformers(0,4,0)-GO(ExceptT e)-#endif--GO(ReaderT r)-GO(ContT r)-GO(StateT s)-GO(ResourceT)-GO(Pipe l i o u)-GO(ConduitM i o)-GOX(Monoid w, WriterT w)-GOX(Monoid w, RWST r w s)-GOX(Monoid w, Strict.RWST r w s)-GO(Strict.StateT s)-GOX(Monoid w, Strict.WriterT w)--#undef DEF-#undef GO-#undef GOX
Database/Persist/Class/PersistUnique.hs view
@@ -6,38 +6,21 @@ , insertBy , replaceUnique , checkUnique+ , onlyUnique ) where +import Database.Persist.Types import qualified Prelude-import Prelude hiding ((++), show)+import Prelude hiding ((++)) -import Control.Monad (liftM)-import Control.Monad.Trans.Error (Error (..))-import Control.Monad.Trans.Class (lift)-import Data.Monoid (Monoid)+import Control.Exception (throwIO)+import Control.Monad (liftM, when)+import Control.Monad.IO.Class (liftIO) import Data.List ((\\)) -import Data.Conduit.Internal (Pipe)-import Control.Monad.Logger (LoggingT)-import Control.Monad.Trans.Identity ( IdentityT)-import Control.Monad.Trans.List ( ListT )-import Control.Monad.Trans.Maybe ( MaybeT )-import Control.Monad.Trans.Error ( ErrorT )--#if MIN_VERSION_transformers(0,4,0)-import Control.Monad.Trans.Except ( ExceptT )-#endif- import Control.Monad.Trans.Reader ( ReaderT )-import Control.Monad.Trans.Cont ( ContT )-import Control.Monad.Trans.State ( StateT )-import Control.Monad.Trans.Writer ( WriterT )-import Control.Monad.Trans.RWS ( RWST )-import Control.Monad.Trans.Resource ( ResourceT)+import Control.Monad.IO.Class (MonadIO) -import qualified Control.Monad.Trans.RWS.Strict as Strict ( RWST )-import qualified Control.Monad.Trans.State.Strict as Strict ( StateT )-import qualified Control.Monad.Trans.Writer.Strict as Strict ( WriterT ) import Database.Persist.Class.PersistStore import Database.Persist.Class.PersistEntity @@ -53,41 +36,74 @@ -- * there is some fragility to trying to catch the correct exception and determing the column of failure. -- -- * an exception will automatically abort the current SQL transaction-class PersistStore m => PersistUnique m where+class PersistStore backend => PersistUnique backend where -- | Get a record by unique key, if available. Returns also the identifier.- getBy :: (PersistEntityBackend val ~ PersistMonadBackend m, PersistEntity val) => Unique val -> m (Maybe (Entity val))+ getBy :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val) => Unique val -> ReaderT backend m (Maybe (Entity val)) -- | Delete a specific record by unique key. Does nothing if no record -- matches.- deleteBy :: (PersistEntityBackend val ~ PersistMonadBackend m, PersistEntity val) => Unique val -> m ()+ deleteBy :: (MonadIO m, PersistEntityBackend val ~ backend, PersistEntity val) => Unique val -> ReaderT backend m () -- | Like 'insert', but returns 'Nothing' when the record -- couldn't be inserted because of a uniqueness constraint.- insertUnique :: (PersistEntityBackend val ~ PersistMonadBackend m, PersistEntity val) => val -> m (Maybe (Key val))+ insertUnique :: (MonadIO m, PersistEntityBackend val ~ backend, PersistEntity val) => val -> ReaderT backend m (Maybe (Key val)) insertUnique datum = do conflict <- checkUnique datum case conflict of Nothing -> Just `liftM` insert datum Just _ -> return Nothing + -- | update based on a uniquness constraint or insert+ --+ -- insert the new record if it does not exist+ -- update the existing record that matches the uniqueness contraint+ --+ -- Throws an exception if there is more than 1 uniqueness contraint+ upsert :: (MonadIO m, PersistEntityBackend val ~ backend, PersistEntity val)+ => val -- ^ new record to insert+ -> [Update val] -- ^ updates to perform if the record already exists.+ -- leaving this empty is the equivalent of performing a 'repsert' on a unique key.+ -> ReaderT backend m (Entity val) -- ^ the record in the database after the operation+ upsert record updates = do+ uniqueKey <- onlyUnique record + mExists <- getBy uniqueKey+ k <- case mExists of+ Just (Entity k _) -> do+ when (null updates) (replace k record)+ return k+ Nothing -> insert record+ Entity k `liftM` updateGet k updates++ -- | Insert a value, checking for conflicts with any unique constraints. If a -- duplicate exists in the database, it is returned as 'Left'. Otherwise, the -- new 'Key is returned as 'Right'.-insertBy :: (PersistEntity val, PersistUnique m, PersistEntityBackend val ~ PersistMonadBackend m)- => val -> m (Either (Entity val) (Key val))-+insertBy :: (MonadIO m, PersistEntity val, PersistUnique backend, PersistEntityBackend val ~ backend)+ => val -> ReaderT backend m (Either (Entity val) (Key val)) insertBy val = do res <- getByValue val case res of Nothing -> Right `liftM` insert val Just z -> return $ Left z +-- | Return the single unique key for a record+onlyUnique :: (MonadIO m, PersistEntity val, PersistUnique backend, PersistEntityBackend val ~ backend)+ => val -> ReaderT backend m (Unique val)+onlyUnique record = case onlyUniqueEither record of+ Right u -> return u+ Left us -> liftIO $ throwIO $ OnlyUniqueException $ show $ length us++onlyUniqueEither :: (PersistEntity val) => val -> Either [Unique val] (Unique val)+onlyUniqueEither record = case persistUniqueKeys record of+ (u:[]) -> Right u+ us -> Left us+ -- | A modification of 'getBy', which takes the 'PersistEntity' itself instead -- of a 'Unique' value. Returns a value matching /one/ of the unique keys. This -- function makes the most sense on entities with a single 'Unique' -- constructor.-getByValue :: (PersistEntity value, PersistUnique m, PersistEntityBackend value ~ PersistMonadBackend m)- => value -> m (Maybe (Entity value))+getByValue :: (MonadIO m, PersistEntity value, PersistUnique backend, PersistEntityBackend value ~ backend)+ => value -> ReaderT backend m (Maybe (Entity value)) getByValue = checkUniques . persistUniqueKeys where checkUniques [] = return Nothing@@ -104,8 +120,8 @@ -- If uniqueness is violated, return a 'Just' with the 'Unique' violation -- -- Since 1.2.2.0-replaceUnique :: (Eq record, Eq (Unique record), PersistEntityBackend record ~ PersistMonadBackend m, PersistEntity record, PersistStore m, PersistUnique m)- => Key record -> record -> m (Maybe (Unique record))+replaceUnique :: (MonadIO m, Eq record, Eq (Unique record), PersistEntityBackend record ~ backend, PersistEntity record, PersistUnique backend)+ => Key record -> record -> ReaderT backend m (Maybe (Unique record)) replaceUnique key datumNew = getJust key >>= replaceOriginal where uniqueKeysNew = persistUniqueKeys datumNew@@ -123,44 +139,15 @@ -- -- Returns 'Nothing' if the entity would be unique, and could thus safely be inserted. -- on a conflict returns the conflicting key-checkUnique :: (PersistEntityBackend record ~ PersistMonadBackend m, PersistEntity record, PersistUnique m)- => record -> m (Maybe (Unique record))+checkUnique :: (MonadIO m, PersistEntityBackend record ~ backend, PersistEntity record, PersistUnique backend)+ => record -> ReaderT backend m (Maybe (Unique record)) checkUnique = checkUniqueKeys . persistUniqueKeys -checkUniqueKeys :: (PersistEntity record, PersistUnique m, PersistEntityBackend record ~ PersistMonadBackend m)- => [Unique record] -> m (Maybe (Unique record))+checkUniqueKeys :: (MonadIO m, PersistEntity record, PersistUnique backend, PersistEntityBackend record ~ backend)+ => [Unique record] -> ReaderT backend m (Maybe (Unique record)) checkUniqueKeys [] = return Nothing checkUniqueKeys (x:xs) = do y <- getBy x case y of Nothing -> checkUniqueKeys xs Just _ -> return (Just x)--#define DEF(T) { getBy = lift . getBy; deleteBy = lift . deleteBy; insertUnique = lift . insertUnique }-#define GO(T) instance (PersistUnique m) => PersistUnique (T m) where DEF(T)-#define GOX(X, T) instance (X, PersistUnique m) => PersistUnique (T m) where DEF(T)--GO(LoggingT)-GO(IdentityT)-GO(ListT)-GO(MaybeT)-GOX(Error e, ErrorT e)--#if MIN_VERSION_transformers(0,4,0)-GO(ExceptT e)-#endif--GO(ReaderT r)-GO(ContT r)-GO(StateT s)-GO(ResourceT)-GO(Pipe l i o u)-GOX(Monoid w, WriterT w)-GOX(Monoid w, RWST r w s)-GOX(Monoid w, Strict.RWST r w s)-GO(Strict.StateT s)-GOX(Monoid w, Strict.WriterT w)--#undef DEF-#undef GO-#undef GOX
Database/Persist/Quasi.hs view
@@ -7,7 +7,6 @@ , PersistSettings (..) , upperCaseSettings , lowerCaseSettings- , stripId , nullable #if TEST , Token (..)@@ -100,7 +99,7 @@ } -- | Parses a quasi-quoted syntax into a list of entity definitions.-parse :: PersistSettings -> Text -> [EntityDef ()]+parse :: PersistSettings -> Text -> [EntityDef] parse ps = parseLines ps . removeSpaces . filter (not . empty)@@ -192,7 +191,7 @@ fromToken Spaces{} = Nothing -- | Divide lines into blocks and make entity definitions.-parseLines :: PersistSettings -> [Line] -> [EntityDef ()]+parseLines :: PersistSettings -> [Line] -> [EntityDef] parseLines ps lines = fixForeignKeysAll $ toEnts lines where@@ -202,34 +201,48 @@ toEnts (Line _ []:rest) = toEnts rest toEnts [] = [] -fixForeignKeysAll :: [EntityDef ()] -> [EntityDef ()]+fixForeignKeysAll :: [EntityDef] -> [EntityDef] fixForeignKeysAll ents = map fixForeignKeys ents- where fixForeignKeys :: EntityDef () -> EntityDef ()- fixForeignKeys ent = ent { entityForeigns = map (fixForeignKey ent) (entityForeigns ent) }- -- check the count and the sqltypes match and update the foreignFields with the names of the primary columns- chktypes :: [FieldDef ()] -> HaskellName -> [FieldDef ()] -> HaskellName -> Bool- chktypes fflds fkey pflds pkey = case (filter ((== fkey) . fieldHaskell) fflds, filter ((== pkey) . fieldHaskell) pflds) of- ([ffld],[pfld]) -> fieldType ffld == fieldType pfld- xs -> error $ "unexpected result "++ show xs- fixForeignKey :: EntityDef () -> ForeignDef -> ForeignDef- fixForeignKey fent fdef = - case find ((== foreignRefTableHaskell fdef) . entityHaskell) ents of- Just pent -> case entityPrimary pent of- Just pdef -> if length (foreignFields fdef) == length (primaryFields pdef) - then fdef { foreignFields = zipWith (\(a,b,_,_) (a',b') -> - if chktypes (entityFields fent) a (entityFields pent) a' - then (a,b,a',b') - else error ("type mismatch between foreign key and primary column" ++ show (a,a') ++ " primary ent="++show pent ++ " foreign ent="++show fent)) (foreignFields fdef) (primaryFields pdef) }- else error $ "found " ++ show (length (foreignFields fdef)) ++ " fkeys and " ++ show (length (primaryFields pdef)) ++ " pkeys: fdef=" ++ show fdef ++ " pdef=" ++ show pdef - Nothing -> error $ "no explicit primary key fdef="++show fdef++ " fent="++show fent- Nothing -> error $ "could not find table " ++ show (foreignRefTableHaskell fdef) ++ " fdef=" ++ show fdef ++ " allnames=" ++ show (map (unHaskellName . entityHaskell) ents) ++ "\n\nents=" ++ show ents+ where+ fixForeignKeys :: EntityDef -> EntityDef+ fixForeignKeys ent = ent { entityForeigns = map (fixForeignKey ent) (entityForeigns ent) }+ -- check the count and the sqltypes match and update the foreignFields with the names of the primary columns+ chktypes :: [FieldDef] -> HaskellName -> [FieldDef] -> HaskellName -> Bool+ chktypes fflds fkey pflds pkey = case (filter ((== fkey) . fieldHaskell) fflds, filter ((== pkey) . fieldHaskell) pflds) of+ ([ffld],[pfld]) -> fieldType ffld == fieldType pfld+ xs -> error $ "unexpected result "++ show xs+ fixForeignKey :: EntityDef -> ForeignDef -> ForeignDef+ fixForeignKey fent fdef =+ case find ((== foreignRefTableHaskell fdef) . entityHaskell) ents of+ Just pent -> case entityPrimary pent of+ Just pdef ->+ if length (foreignFields fdef) == length (primaryFields pdef)+ then+ fdef { foreignFields = zipWith (toForeignFields pent)+ (foreignFields fdef)+ (primaryFields pdef) }+ else+ error $ "found " ++ show (length (foreignFields fdef)) ++ " fkeys and " ++ show (length (primaryFields pdef)) ++ " pkeys: fdef=" ++ show fdef ++ " pdef=" ++ show pdef+ Nothing ->+ error $ "no explicit primary key fdef="++show fdef++ " fent="++show fent+ Nothing ->+ error $ "could not find table " ++ show (foreignRefTableHaskell fdef)+ ++ " fdef=" ++ show fdef ++ " allnames="+ ++ show (map (unHaskellName . entityHaskell) ents)+ ++ "\n\nents=" ++ show ents+ where+ toForeignFields pent (a,b,_,_) fd =+ let (a', b') = (fieldHaskell fd, fieldDB fd) in+ if chktypes (entityFields fent) a (entityFields pent) a'+ then (a,b,a',b')+ else error ("type mismatch between foreign key and primary column" ++ show (a,a') ++ " primary ent="++show pent ++ " foreign ent="++show fent) -- | Construct an entity definition. mkEntityDef :: PersistSettings -> Text -- ^ name -> [Attr] -- ^ entity attributes -> [Line] -- ^ indented lines- -> EntityDef ()+ -> EntityDef mkEntityDef ps name entattribs lines = EntityDef (HaskellName name')@@ -262,7 +275,7 @@ derives = concat $ mapMaybe takeDerives attribs - cols :: [FieldDef ()]+ cols :: [FieldDef] cols = mapMaybe (takeCols ps) attribs splitExtras :: [Line] -> ([[Text]], M.Map Text [[Text]])@@ -276,7 +289,7 @@ let (x, y) = splitExtras rest in (ts:x, y) -takeCols :: PersistSettings -> [Text] -> Maybe (FieldDef ())+takeCols :: PersistSettings -> [Text] -> Maybe FieldDef takeCols _ ("deriving":_) = Nothing takeCols ps (n':typ:rest) | not (T.null n) && isLower (T.head n) =@@ -286,10 +299,10 @@ { fieldHaskell = HaskellName n , fieldDB = DBName $ getDbName ps n rest , fieldType = ft- , fieldSqlType = ()+ , fieldSqlType = SqlOther $ "SqlType unset for " `mappend` n , fieldAttrs = rest , fieldStrict = fromMaybe (psStrictFields ps) mstrict- , fieldEmbedded = Nothing+ , fieldReference = NoReference } where (mstrict, n)@@ -300,14 +313,11 @@ getDbName :: PersistSettings -> Text -> [Text] -> Text getDbName ps n [] = psToDBName ps n-getDbName ps n (a:as) =- case T.stripPrefix "sql=" a of- Nothing -> getDbName ps n as- Just s -> s+getDbName ps n (a:as) = fromMaybe (getDbName ps n as) $ T.stripPrefix "sql=" a takeConstraint :: PersistSettings -> Text- -> [FieldDef a]+ -> [FieldDef] -> [Text] -> (Maybe PrimaryDef, Maybe UniqueDef, Maybe ForeignDef) takeConstraint ps tableName defs (n:rest) | not (T.null n) && isUpper (T.head n) = takeConstraint' @@ -318,25 +328,25 @@ | otherwise = (Nothing, Just $ takeUniq ps "" defs (n:rest), Nothing) -- retain compatibility with original unique constraint takeConstraint _ _ _ _ = (Nothing, Nothing, Nothing) -takePrimary :: [FieldDef a]+takePrimary :: [FieldDef] -> [Text] -> PrimaryDef takePrimary defs pkcols = PrimaryDef- (map (HaskellName &&& getDBName defs) pkcols)+ (map (getDef defs) pkcols) attrs where (_, attrs) = break ("!" `T.isPrefixOf`) pkcols- getDBName [] t = error $ "Unknown column in primary key constraint: " ++ show t- getDBName (d:ds) t+ getDef [] t = error $ "Unknown column in primary key constraint: " ++ show t+ getDef (d:ds) t | nullable (fieldAttrs d) /= NotNullable = error $ "primary key column cannot be nullable: " ++ show t- | fieldHaskell d == HaskellName t = fieldDB d- | otherwise = getDBName ds t+ | fieldHaskell d == HaskellName t = d+ | otherwise = getDef ds t -- Unique UppercaseConstraintName list of lowercasefields takeUniq :: PersistSettings -> Text- -> [FieldDef a]+ -> [FieldDef] -> [Text] -> UniqueDef takeUniq ps tableName defs (n:rest)@@ -356,7 +366,7 @@ takeForeign :: PersistSettings -> Text- -> [FieldDef a]+ -> [FieldDef] -> [Text] -> ForeignDef takeForeign ps tableName defs (refTableName:n:rest)@@ -380,10 +390,6 @@ takeDerives :: [Text] -> Maybe [Text] takeDerives ("deriving":rest) = Just rest takeDerives _ = Nothing--stripId :: FieldType -> Maybe Text-stripId (FTTypeCon Nothing t) = T.stripSuffix "Id" t-stripId _ = Nothing nullable :: [Text] -> IsNullable nullable s
Database/Persist/Sql.hs view
@@ -4,7 +4,9 @@ , module Database.Persist.Sql.Run , module Database.Persist.Sql.Migration , module Database.Persist+ , module Database.Persist.Sql.Orphan.PersistStore , rawQuery+ , rawQueryRes , rawExecute , rawExecuteCount , rawSql@@ -27,24 +29,25 @@ import Database.Persist.Sql.Internal import Database.Persist.Sql.Orphan.PersistQuery -import Database.Persist.Sql.Orphan.PersistStore ()+import Database.Persist.Sql.Orphan.PersistStore import Database.Persist.Sql.Orphan.PersistUnique () import Control.Monad.IO.Class+import Control.Monad.Trans.Reader (ReaderT, ask) -- | Commit the current transaction and begin a new one. -- -- Since 1.2.0-transactionSave :: MonadSqlPersist m => m ()+transactionSave :: MonadIO m => ReaderT Connection m () transactionSave = do- conn <- askSqlConn+ conn <- ask let getter = getStmtConn conn liftIO $ connCommit conn getter >> connBegin conn getter -- | Roll back the current transaction and begin a new one. -- -- Since 1.2.0-transactionUndo :: MonadSqlPersist m => m ()+transactionUndo :: MonadIO m => ReaderT Connection m () transactionUndo = do- conn <- askSqlConn+ conn <- ask let getter = getStmtConn conn liftIO $ connRollback conn getter >> connBegin conn getter
Database/Persist/Sql/Class.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeSynonymInstances #-}@@ -7,8 +8,7 @@ {-# LANGUAGE OverlappingInstances #-} #endif module Database.Persist.Sql.Class- ( MonadSqlPersist (..)- , RawSql (..)+ ( RawSql (..) , PersistFieldSql (..) ) where @@ -20,11 +20,8 @@ import Data.Text (Text, intercalate, pack) import Data.Maybe (fromMaybe) import Data.Fixed--import Data.Monoid (Monoid)-import Control.Monad.Trans.Class (lift)+import Data.Proxy (Proxy) -import Control.Monad.Logger (LoggingT) import Control.Monad.Trans.Identity ( IdentityT) import Control.Monad.Trans.List ( ListT ) import Control.Monad.Trans.Maybe ( MaybeT )@@ -34,66 +31,24 @@ import Control.Monad.Trans.Except ( ExceptT ) #endif -import Control.Monad.Trans.Cont ( ContT )-import Control.Monad.Trans.State ( StateT )-import Control.Monad.Trans.Writer ( WriterT )-import Control.Monad.Trans.RWS ( RWST ) import Control.Monad.Trans.Reader ( ReaderT, ask ) import Control.Monad.Trans.Resource ( ResourceT ) import Data.Conduit.Internal (Pipe, ConduitM) -import qualified Control.Monad.Trans.RWS.Strict as Strict ( RWST )-import qualified Control.Monad.Trans.State.Strict as Strict ( StateT )-import qualified Control.Monad.Trans.Writer.Strict as Strict ( WriterT ) import Control.Monad.IO.Class (MonadIO) import Control.Monad.Trans.Class (MonadTrans)-import Control.Monad.Logger (MonadLogger) import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Map as M import qualified Data.Set as S-import Data.Time (ZonedTime, UTCTime, TimeOfDay, Day)+import Data.Time (UTCTime, TimeOfDay, Day) import Data.Int import Data.Word import Data.ByteString (ByteString) import Text.Blaze.Html (Html) import Data.Bits (bitSize)--class (MonadIO m, MonadLogger m) => MonadSqlPersist m where- askSqlConn :: m Connection- default askSqlConn :: (MonadSqlPersist m, MonadTrans t, MonadLogger (t m))- => t m Connection- askSqlConn = lift askSqlConn--instance (MonadIO m, MonadLogger m) => MonadSqlPersist (SqlPersistT m) where- askSqlConn = SqlPersistT ask--#define GO(T) instance (MonadSqlPersist m) => MonadSqlPersist (T m)-#define GOX(X, T) instance (X, MonadSqlPersist m) => MonadSqlPersist (T m)-GO(LoggingT)-GO(IdentityT)-GO(ListT)-GO(MaybeT)-GOX(Error e, ErrorT e)--#if MIN_VERSION_transformers(0,4,0)-GO(ExceptT e)-#endif--GO(ReaderT r)-GO(ContT r)-GO(StateT s)-GO(ResourceT)-GO(Pipe l i o u)-GO(ConduitM i o)-GOX(Monoid w, WriterT w)-GOX(Monoid w, RWST r w s)-GOX(Monoid w, Strict.RWST r w s)-GO(Strict.StateT s)-GOX(Monoid w, Strict.WriterT w)-#undef GO-#undef GOX+import qualified Data.Vector as V -- | Class for data types that may be retrived from a 'rawSql' -- query.@@ -115,7 +70,7 @@ rawSqlProcessRow [pv] = Single <$> fromPersistValue pv rawSqlProcessRow _ = Left $ pack "RawSql (Single a): wrong number of columns." -instance PersistEntity a => RawSql (Entity a) where+instance (PersistEntity a, PersistEntityBackend a ~ SqlBackend) => RawSql (Entity a) where rawSqlCols escape = ((+1) . length . entityFields &&& process) . entityDef . Just . entityVal where process ed = (:[]) $@@ -251,7 +206,7 @@ extractMaybe = fromMaybe (error "Database.Persist.GenericSql.extractMaybe") class PersistField a => PersistFieldSql a where- sqlType :: Monad m => m a -> SqlType+ sqlType :: Proxy a -> SqlType #ifndef NO_OVERLAP instance PersistFieldSql String where@@ -298,10 +253,10 @@ sqlType _ = SqlTime instance PersistFieldSql UTCTime where sqlType _ = SqlDayTime-instance PersistFieldSql ZonedTime where- sqlType _ = SqlDayTimeZoned instance PersistFieldSql a => PersistFieldSql [a] where sqlType _ = SqlString+instance PersistFieldSql a => PersistFieldSql (V.Vector a) where+ sqlType _ = SqlString instance (Ord a, PersistFieldSql a) => PersistFieldSql (S.Set a) where sqlType _ = SqlString instance (PersistFieldSql a, PersistFieldSql b) => PersistFieldSql (a,b) where@@ -323,9 +278,6 @@ instance PersistFieldSql Rational where sqlType _ = SqlNumeric 32 20 -- need to make this field big enough to handle Rational to Mumber string conversion for ODBC --- perhaps a SQL user can figure this sqlType out?--- It is really intended for MongoDB though.-instance PersistField entity => PersistFieldSql (Entity entity) where- sqlType _ = SqlOther "embedded entity, hard to type"-instance PersistFieldSql (KeyBackend SqlBackend a) where- sqlType _ = SqlInt64+-- An embedded Entity+instance (PersistField record, PersistEntity record) => PersistFieldSql (Entity record) where+ sqlType _ = SqlString
Database/Persist/Sql/Internal.hs view
@@ -3,7 +3,6 @@ -- | Intended for creating new backends. module Database.Persist.Sql.Internal ( mkColumns- , convertKey ) where import Database.Persist.Types@@ -11,12 +10,11 @@ import Data.Char (isSpace) import Data.Text (Text) import qualified Data.Text as T-import Data.Monoid (Monoid, mappend, mconcat)-import Data.Maybe (mapMaybe, listToMaybe)+import Data.Monoid (mappend, mconcat) import Database.Persist.Sql.Types -- | Create the list of columns for the given entity.-mkColumns :: [EntityDef a] -> EntityDef SqlType -> ([Column], [UniqueDef], [ForeignDef])+mkColumns :: [EntityDef] -> EntityDef -> ([Column], [UniqueDef], [ForeignDef]) mkColumns allDefs t = (cols, entityUniques t, entityForeigns t) where@@ -26,19 +24,16 @@ tn :: DBName tn = entityDB t - go :: FieldDef SqlType -> Column+ go :: FieldDef -> Column go fd = Column (fieldDB fd) (nullable (fieldAttrs fd) /= NotNullable || entitySum t)- (maybe- (fieldSqlType fd)- SqlOther- (listToMaybe $ mapMaybe (T.stripPrefix "sqltype=") $ fieldAttrs fd))+ (fieldSqlType fd) (def $ fieldAttrs fd) Nothing (maxLen $ fieldAttrs fd)- (ref (fieldDB fd) (fieldType fd) (fieldAttrs fd))+ (ref (fieldDB fd) (fieldReference fd) (fieldAttrs fd)) def :: [Attr] -> Maybe Text def [] = Nothing@@ -57,12 +52,12 @@ | otherwise = maxLen as ref :: DBName- -> FieldType+ -> ReferenceDef -> [Attr] -> Maybe (DBName, DBName) -- table name, constraint name- ref c ft []- | Just f <- stripId ft =- Just (resolveTableName allDefs $ HaskellName f, refName tn c)+ ref c fe []+ | ForeignRef f <- fe =+ Just (resolveTableName allDefs f, refName tn c) | otherwise = Nothing ref _ _ ("noreference":_) = Nothing ref c _ (a:_)@@ -74,13 +69,8 @@ refName (DBName table) (DBName column) = DBName $ mconcat [table, "_", column, "_fkey"] -resolveTableName :: [EntityDef a] -> HaskellName -> DBName+resolveTableName :: [EntityDef] -> HaskellName -> DBName resolveTableName [] (HaskellName hn) = error $ "Table not found: " `mappend` T.unpack hn resolveTableName (e:es) hn | entityHaskell e == hn = entityDB e | otherwise = resolveTableName es hn--convertKey :: Bool -> KeyBackend t t1 -> [PersistValue]-convertKey True (Key (PersistList fks)) = fks-convertKey False (Key ret@(PersistInt64 _)) = [ret]-convertKey composite k = error $ "invalid key type " ++ show k ++ " composite=" ++ show composite
Database/Persist/Sql/Migration.hs view
@@ -16,6 +16,7 @@ import Control.Monad.Trans.Class (MonadTrans (..)) import Control.Monad.IO.Class import Control.Monad.Trans.Writer+import Control.Monad.Trans.Reader (ReaderT (..), ask) import Control.Monad (liftM, unless) import Data.Text (Text, unpack, snoc, isPrefixOf, pack) import qualified Data.Text.IO@@ -23,7 +24,6 @@ import System.IO.Silently (hSilence) import Control.Monad.Trans.Control (liftBaseOp_) import Database.Persist.Sql.Types-import Database.Persist.Sql.Class import Database.Persist.Sql.Raw import Database.Persist.Types @@ -34,48 +34,50 @@ safeSql :: CautiousMigration -> [Sql] safeSql = allSql . filter (not . fst) -parseMigration :: Monad m => Migration m -> m (Either [Text] CautiousMigration)+parseMigration :: MonadIO m => Migration -> ReaderT Connection m (Either [Text] CautiousMigration) parseMigration =- liftM go . runWriterT . execWriterT+ liftIOReader . liftM go . runWriterT . execWriterT where go ([], sql) = Right sql go (errs, _) = Left errs + liftIOReader (ReaderT m) = ReaderT $ liftIO . m+ -- like parseMigration, but call error or return the CautiousMigration-parseMigration' :: Monad m => Migration m -> m (CautiousMigration)+parseMigration' :: MonadIO m => Migration -> ReaderT Connection m (CautiousMigration) parseMigration' m = do x <- parseMigration m case x of Left errs -> error $ unlines $ map unpack errs Right sql -> return sql -printMigration :: MonadIO m => Migration m -> m ()+printMigration :: MonadIO m => Migration -> ReaderT Connection m () printMigration m = do mig <- parseMigration' m mapM_ (liftIO . Data.Text.IO.putStrLn . flip snoc ';') (allSql mig) -getMigration :: (MonadBaseControl IO m, MonadIO m) => Migration m -> m [Sql]+getMigration :: (MonadBaseControl IO m, MonadIO m) => Migration -> ReaderT Connection m [Sql] getMigration m = do mig <- parseMigration' m return $ allSql mig -runMigration :: MonadSqlPersist m- => Migration m- -> m ()+runMigration :: MonadIO m+ => Migration+ -> ReaderT Connection m () runMigration m = runMigration' m False >> return () -- | Same as 'runMigration', but returns a list of the SQL commands executed -- instead of printing them to stderr.-runMigrationSilent :: (MonadBaseControl IO m, MonadSqlPersist m)- => Migration m- -> m [Text]+runMigrationSilent :: (MonadBaseControl IO m, MonadIO m)+ => Migration+ -> ReaderT Connection m [Text] runMigrationSilent m = liftBaseOp_ (hSilence [stderr]) $ runMigration' m True runMigration'- :: MonadSqlPersist m- => Migration m+ :: MonadIO m+ => Migration -> Bool -- ^ is silent?- -> m [Text]+ -> ReaderT Connection m [Text] runMigration' m silent = do mig <- parseMigration' m case unsafeSql mig of@@ -86,14 +88,14 @@ , unlines $ map (\s -> " " ++ unpack s ++ ";") $ errs ] -runMigrationUnsafe :: MonadSqlPersist m- => Migration m- -> m ()+runMigrationUnsafe :: MonadIO m+ => Migration+ -> ReaderT Connection m () runMigrationUnsafe m = do mig <- parseMigration' m mapM_ (executeMigrate False) $ sortMigrations $ allSql mig -executeMigrate :: MonadSqlPersist m => Bool -> Text -> m Text+executeMigrate :: MonadIO m => Bool -> Text -> ReaderT Connection m Text executeMigrate silent s = do unless silent $ liftIO $ hPutStrLn stderr $ "Migrating: " ++ unpack s rawExecute s []@@ -109,11 +111,10 @@ -- choose to have this special sorting applied. isCreate t = pack "CREATe " `isPrefixOf` t -migrate :: MonadSqlPersist m- => [EntityDef SqlType]- -> EntityDef SqlType- -> Migration m+migrate :: [EntityDef]+ -> EntityDef+ -> Migration migrate allDefs val = do- conn <- askSqlConn+ conn <- lift $ lift ask res <- liftIO $ connMigrateSql conn allDefs (getStmtConn conn) val either tell (lift . tell) res
Database/Persist/Sql/Orphan/PersistQuery.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Database.Persist.Sql.Orphan.PersistQuery@@ -9,18 +10,14 @@ import Database.Persist import Database.Persist.Sql.Types-import Database.Persist.Sql.Class import Database.Persist.Sql.Raw-import Database.Persist.Sql.Orphan.PersistStore ()-import Database.Persist.Sql.Internal (convertKey)+import Database.Persist.Sql.Orphan.PersistStore (withRawQuery) import qualified Data.Text as T import Data.Text (Text) import Data.Monoid (Monoid (..), (<>)) import Data.Int (Int64)-import Control.Monad.Logger import Control.Monad.IO.Class-import Control.Monad.Trans.Class-import Control.Monad.Trans.Resource (MonadResource)+import Control.Monad.Trans.Reader (ReaderT, ask) import Control.Exception (throwIO) import qualified Data.Conduit.List as CL import Data.Conduit@@ -29,36 +26,9 @@ import Data.List (transpose, inits, find) -- orphaned instance for convenience of modularity-instance (MonadResource m, MonadLogger m) => PersistQuery (SqlPersistT m) where- update _ [] = return ()- update k upds = do- conn <- askSqlConn- let go'' n Assign = n <> "=?"- go'' n Add = T.concat [n, "=", n, "+?"]- go'' n Subtract = T.concat [n, "=", n, "-?"]- go'' n Multiply = T.concat [n, "=", n, "*?"]- go'' n Divide = T.concat [n, "=", n, "/?"]- let go' (x, pu) = go'' (connEscapeName conn x) pu- let composite = isJust $ entityPrimary t- let wher = case entityPrimary t of- Just pdef -> T.intercalate " AND " $ map (\fld -> connEscapeName conn (snd fld) <> "=? ") $ primaryFields pdef- Nothing -> connEscapeName conn (entityID t) <> "=?"- let sql = T.concat- [ "UPDATE "- , connEscapeName conn $ entityDB t- , " SET "- , T.intercalate "," $ map (go' . go) upds- , " WHERE "- , wher- ]- rawExecute sql $- map updatePersistValue upds `mappend` (convertKey composite k)- where- t = entityDef $ dummyFromKey k- go x = (fieldDB $ updateFieldDef x, updateUpdate x)-+instance PersistQuery Connection where count filts = do- conn <- askSqlConn+ conn <- ask let wher = if null filts then "" else filterClause False conn filts@@ -67,7 +37,7 @@ , connEscapeName conn $ entityDB t , wher ]- rawQuery sql (getFiltsValues conn filts) $$ do+ withRawQuery sql (getFiltsValues conn filts) $ do mm <- CL.head case mm of Just [PersistInt64 i] -> return $ fromIntegral i@@ -80,9 +50,10 @@ where t = entityDef $ dummyFromFilts filts - selectSource filts opts = do- conn <- lift askSqlConn- rawQuery (sql conn) (getFiltsValues conn filts) $= CL.mapM parse+ selectSourceRes filts opts = do+ conn <- ask+ srcRes <- rawQueryRes (sql conn) (getFiltsValues conn filts)+ return $ fmap ($= CL.mapM parse) srcRes where composite = isJust $ entityPrimary t (limit, offset, orders) = limitOffsetOrder opts@@ -90,7 +61,7 @@ parse vals = case entityPrimary t of Just pdef -> - let pks = map fst $ primaryFields pdef+ let pks = map fieldHaskell $ primaryFields pdef keyvals = map snd $ filter (\(a, _) -> let ret=isJust (find (== a) pks) in ret) $ zip (map fieldHaskell $ entityFields t) vals in case fromPersistValuesComposite' keyvals vals of Left s -> liftIO $ throwIO $ PersistMarshalError s@@ -101,21 +72,26 @@ Right row -> return row t = entityDef $ dummyFromFilts filts- fromPersistValues' (PersistInt64 x:xs) = - case fromPersistValues xs of- Left e -> Left e- Right xs' -> Right (Entity (Key $ PersistInt64 x) xs')- fromPersistValues' (PersistDouble x:xs) = -- oracle returns Double ++ fromPersistValues' (kpv:xs) = -- oracle returns Double case fromPersistValues xs of Left e -> Left e- Right xs' -> Right (Entity (Key $ PersistInt64 (truncate x)) xs') -- convert back to int64+ Right xs' ->+ case keyFromValues [kpv] of+ Left _ -> error $ "fromPersistValues': keyFromValues failed on " ++ show kpv+ Right k -> Right (Entity k xs')++ fromPersistValues' xs = Left $ T.pack ("error in fromPersistValues' xs=" ++ show xs) fromPersistValuesComposite' keyvals xs = case fromPersistValues xs of Left e -> Left e- Right xs' -> Right (Entity (Key $ PersistList keyvals) xs')+ Right xs' -> case keyFromValues keyvals of+ Left _ -> error "fromPersistValuesComposite': keyFromValues failed"+ Right key -> Right (Entity key xs') + wher conn = if null filts then "" else filterClause False conn filts@@ -135,13 +111,14 @@ , ord conn ] - selectKeys filts opts = do- conn <- lift askSqlConn- rawQuery (sql conn) (getFiltsValues conn filts) $= CL.mapM parse+ selectKeysRes filts opts = do+ conn <- ask+ srcRes <- rawQueryRes (sql conn) (getFiltsValues conn filts)+ return $ fmap ($= CL.mapM parse) srcRes where t = entityDef $ dummyFromFilts filts cols conn = case entityPrimary t of - Just pdef -> T.intercalate "," $ map (connEscapeName conn . snd) $ primaryFields pdef+ Just pdef -> T.intercalate "," $ map (connEscapeName conn . fieldDB) $ primaryFields pdef Nothing -> connEscapeName conn $ entityID t wher conn = if null filts@@ -163,17 +140,23 @@ [] -> "" ords -> " ORDER BY " <> T.intercalate "," ords - parse xs = case entityPrimary t of+ parse xs = do+ keyvals <- case entityPrimary t of Nothing -> case xs of- [PersistInt64 x] -> return $ Key $ PersistInt64 x- [PersistDouble x] -> return $ Key $ PersistInt64 (truncate x) -- oracle returns Double + [PersistInt64 x] -> return [PersistInt64 x]+ [PersistDouble x] -> return [PersistInt64 (truncate x)] -- oracle returns Double _ -> liftIO $ throwIO $ PersistMarshalError $ "Unexpected in selectKeys False: " <> T.pack (show xs) Just pdef -> - let pks = map fst $ primaryFields pdef+ let pks = map fieldHaskell $ primaryFields pdef keyvals = map snd $ filter (\(a, _) -> let ret=isJust (find (== a) pks) in ret) $ zip (map fieldHaskell $ entityFields t) xs- in return $ Key $ PersistList keyvals+ in return keyvals+ case keyFromValues keyvals of+ Right k -> return k+ Left _ -> error "selectKeysImpl: keyFromValues failed" ++ deleteWhere filts = do _ <- deleteWhereCount filts return ()@@ -185,11 +168,11 @@ -- | Same as 'deleteWhere', but returns the number of rows affected. -- -- Since 1.1.5-deleteWhereCount :: (PersistEntity val, MonadSqlPersist m)+deleteWhereCount :: (PersistEntity val, MonadIO m) => [Filter val]- -> m Int64+ -> ReaderT Connection m Int64 deleteWhereCount filts = do- conn <- askSqlConn+ conn <- ask let t = entityDef $ dummyFromFilts filts let wher = if null filts then ""@@ -204,13 +187,13 @@ -- | Same as 'updateWhere', but returns the number of rows affected. -- -- Since 1.1.5-updateWhereCount :: (PersistEntity val, MonadSqlPersist m)+updateWhereCount :: (PersistEntity val, MonadIO m, SqlBackend ~ PersistEntityBackend val) => [Filter val] -> [Update val]- -> m Int64+ -> ReaderT Connection m Int64 updateWhereCount _ [] = return 0 updateWhereCount filts upds = do- conn <- askSqlConn+ conn <- ask let wher = if null filts then "" else filterClause False conn filts@@ -234,8 +217,9 @@ go' conn (x, pu) = go'' (connEscapeName conn x) pu go x = (fieldDB $ updateFieldDef x, updateUpdate x) -updateFieldDef :: PersistEntity v => Update v -> FieldDef SqlType+updateFieldDef :: PersistEntity v => Update v -> FieldDef updateFieldDef (Update f _ _) = persistFieldDef f+updateFieldDef _ = error "BackendUpdate not implemented" dummyFromFilts :: [Filter v] -> Maybe v dummyFromFilts _ = Nothing@@ -281,23 +265,23 @@ else case (allVals, pfilter, isCompFilter pfilter) of ([PersistList xs], Eq, _) -> - let sqlcl=T.intercalate " and " (map (\a -> connEscapeName conn (snd a) <> showSqlFilter pfilter <> "? ") (primaryFields pdef))+ let sqlcl=T.intercalate " and " (map (\a -> connEscapeName conn (fieldDB a) <> showSqlFilter pfilter <> "? ") (primaryFields pdef)) in (wrapSql sqlcl,xs) ([PersistList xs], Ne, _) -> - let sqlcl=T.intercalate " or " (map (\a -> connEscapeName conn (snd a) <> showSqlFilter pfilter <> "? ") (primaryFields pdef))+ let sqlcl=T.intercalate " or " (map (\a -> connEscapeName conn (fieldDB a) <> showSqlFilter pfilter <> "? ") (primaryFields pdef)) in (wrapSql sqlcl,xs) (_, In, _) -> let xxs = transpose (map fromPersistList allVals)- sqls=map (\(a,xs) -> connEscapeName conn (snd a) <> showSqlFilter pfilter <> "(" <> T.intercalate "," (replicate (length xs) " ?") <> ") ") (zip (primaryFields pdef) xxs)+ sqls=map (\(a,xs) -> connEscapeName conn (fieldDB a) <> showSqlFilter pfilter <> "(" <> T.intercalate "," (replicate (length xs) " ?") <> ") ") (zip (primaryFields pdef) xxs) in (wrapSql (T.intercalate " and " (map wrapSql sqls)), concat xxs) (_, NotIn, _) -> let xxs = transpose (map fromPersistList allVals)- sqls=map (\(a,xs) -> connEscapeName conn (snd a) <> showSqlFilter pfilter <> "(" <> T.intercalate "," (replicate (length xs) " ?") <> ") ") (zip (primaryFields pdef) xxs)+ sqls=map (\(a,xs) -> connEscapeName conn (fieldDB a) <> showSqlFilter pfilter <> "(" <> T.intercalate "," (replicate (length xs) " ?") <> ") ") (zip (primaryFields pdef) xxs) in (wrapSql (T.intercalate " or " (map wrapSql sqls)), concat xxs) ([PersistList xs], _, True) -> let zs = tail (inits (primaryFields pdef)) sql1 = map (\b -> wrapSql (T.intercalate " and " (map (\(i,a) -> sql2 (i==length b) a) (zip [1..] b)))) zs- sql2 islast a = connEscapeName conn (snd a) <> (if islast then showSqlFilter pfilter else showSqlFilter Eq) <> "? "+ sql2 islast a = connEscapeName conn (fieldDB a) <> (if islast then showSqlFilter pfilter else showSqlFilter Eq) <> "? " sqlcl = T.intercalate " or " sql1 in (wrapSql sqlcl, concat (tail (inits xs))) (_, BackendSpecificFilter _, _) -> error "unhandled type BackendSpecificFilter for composite/non id primary keys"@@ -399,6 +383,7 @@ updatePersistValue :: Update v -> PersistValue updatePersistValue (Update _ v _) = toPersistValue v+updatePersistValue _ = error "BackendUpdate not implemented" filterClause :: PersistEntity val => Bool -- ^ include table name?@@ -416,7 +401,7 @@ case o of Asc x -> name $ persistFieldDef x Desc x -> name (persistFieldDef x) <> " DESC"- _ -> error $ "orderClause: expected Asc or Desc, not limit or offset"+ _ -> error "orderClause: expected Asc or Desc, not limit or offset" where dummyFromOrder :: SelectOpt a -> Maybe a dummyFromOrder _ = Nothing@@ -428,9 +413,6 @@ then ((tn <> ".") <>) else id) $ connEscapeName conn $ fieldDB x--dummyFromKey :: KeyBackend SqlBackend v -> Maybe v-dummyFromKey _ = Nothing -- | Generates sql for limit and offset for postgres, sqlite and mysql. decorateSQLWithLimitOffset::Text -> (Int,Int) -> Bool -> Text -> Text
Database/Persist/Sql/Orphan/PersistStore.hs view
@@ -1,58 +1,113 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# OPTIONS_GHC -fno-warn-orphans #-}-module Database.Persist.Sql.Orphan.PersistStore () where+module Database.Persist.Sql.Orphan.PersistStore (withRawQuery, BackendKey(..)) where import Database.Persist import Database.Persist.Sql.Types-import Database.Persist.Sql.Class import Database.Persist.Sql.Raw-import Database.Persist.Sql.Internal (convertKey) import qualified Data.Conduit as C import qualified Data.Conduit.List as CL-import Control.Monad.Logger import qualified Data.Text as T-import Data.Text (Text, unpack)+import Data.Text (Text, unpack, pack) import Data.Monoid (mappend, (<>)) import Control.Monad.IO.Class import Data.ByteString.Char8 (readInteger) import Data.Maybe (isJust) import Data.List (find)-import Control.Monad.Trans.Resource (MonadResource)+import Control.Monad.Trans.Reader (ReaderT, ask)+import Data.Acquire (with)+import Data.Int (Int64)+import Web.PathPieces (PathPiece)+import Database.Persist.Sql.Class (PersistFieldSql)+import qualified Data.Aeson as A -instance (MonadResource m, MonadLogger m) => PersistStore (SqlPersistT m) where- type PersistMonadBackend (SqlPersistT m) = SqlBackend+withRawQuery :: MonadIO m+ => Text+ -> [PersistValue]+ -> C.Sink [PersistValue] IO a+ -> ReaderT Connection m a+withRawQuery sql vals sink = do+ srcRes <- rawQueryRes sql vals+ liftIO $ with srcRes (C.$$ sink)++instance PersistStore Connection where+ newtype BackendKey SqlBackend = SqlBackendKey { unSqlBackendKey :: Int64 }+ deriving (Show, Read, Eq, Ord, Num, Integral, PersistField, PersistFieldSql, PathPiece, Real, Enum, Bounded, A.ToJSON, A.FromJSON)++ backendKeyToValues (SqlBackendKey i) = [PersistInt64 i]+ backendKeyFromValues [PersistInt64 i] = Right $ SqlBackendKey i+ backendKeyFromValues [PersistDouble i] = Right $ SqlBackendKey $ truncate i+ backendKeyFromValues s = Left $ pack $ show s++ update _ [] = return ()+ update k upds = do+ conn <- ask+ let go'' n Assign = n <> "=?"+ go'' n Add = T.concat [n, "=", n, "+?"]+ go'' n Subtract = T.concat [n, "=", n, "-?"]+ go'' n Multiply = T.concat [n, "=", n, "*?"]+ go'' n Divide = T.concat [n, "=", n, "/?"]+ let go' (x, pu) = go'' (connEscapeName conn x) pu+ let wher = case entityPrimary t of+ Just pdef -> T.intercalate " AND " $ map (\fld -> connEscapeName conn (fieldDB fld) <> "=? ") $ primaryFields pdef+ Nothing -> connEscapeName conn (entityID t) <> "=?"+ let sql = T.concat+ [ "UPDATE "+ , connEscapeName conn $ entityDB t+ , " SET "+ , T.intercalate "," $ map (go' . go) upds+ , " WHERE "+ , wher+ ]+ rawExecute sql $+ map updatePersistValue upds `mappend` keyToValues k+ where+ t = entityDef $ dummyFromKey k+ go x = (fieldDB $ updateFieldDef x, updateUpdate x)+ insert val = do- conn <- askSqlConn+ conn <- ask let esql = connInsertSql conn t vals key <- case esql of- ISRSingle sql -> rawQuery sql vals C.$$ do+ ISRSingle sql -> withRawQuery sql vals $ do x <- CL.head case x of- Just [PersistInt64 i] -> return $ Key $ PersistInt64 i+ Just [PersistInt64 i] -> case keyFromValues [PersistInt64 i] of+ Left err -> error $ "SQL insert: keyFromValues: PersistInt64 " `mappend` show i `mappend` " " `mappend` unpack err+ Right k -> return k Nothing -> error $ "SQL insert did not return a result giving the generated ID"- Just vals' -> error $ "Invalid result from a SQL insert, got: " ++ show vals'+ Just vals' -> case keyFromValues vals' of+ Left _ -> error $ "Invalid result from a SQL insert, got: " ++ show vals'+ Right k -> return k+ ISRInsertGet sql1 sql2 -> do rawExecute sql1 vals- rawQuery sql2 [] C.$$ do+ withRawQuery sql2 [] $ do mm <- CL.head- case mm of- Just [PersistInt64 i] -> return $ Key $ PersistInt64 i- Just [PersistDouble i] ->return $ Key $ PersistInt64 $ truncate i -- oracle need this!- Just [PersistByteString i] -> case readInteger i of -- mssql- Just (ret,"") -> return $ Key $ PersistInt64 $ fromIntegral ret- xs -> error $ "invalid number i["++show i++"] xs[" ++ show xs ++ "]"- Just xs -> error $ "invalid sql2 return xs["++show xs++"] sql2["++show sql2++"] sql1["++show sql1++"]"- Nothing -> error $ "invalid sql2 returned nothing sql2["++show sql2++"] sql1["++show sql1++"]"+ i <- case mm of+ Just [PersistInt64 i] -> return $ i+ Just [PersistDouble i] ->return $ truncate i -- oracle need this!+ Just [PersistByteString i] -> case readInteger i of -- mssql+ Just (ret,"") -> return $ fromIntegral ret+ xs -> error $ "invalid number i["++show i++"] xs[" ++ show xs ++ "]"+ Just xs -> error $ "invalid sql2 return xs["++show xs++"] sql2["++show sql2++"] sql1["++show sql1++"]"+ Nothing -> error $ "invalid sql2 returned nothing sql2["++show sql2++"] sql1["++show sql1++"]"+ case keyFromValues [PersistInt64 i] of+ Right k -> return k+ Left err -> error $ "ISRInsertGet: keyFromValues failed: " `mappend` unpack err ISRManyKeys sql fs -> do rawExecute sql vals case entityPrimary t of Nothing -> error $ "ISRManyKeys is used when Primary is defined " ++ show sql Just pdef -> - let pks = map fst $ primaryFields pdef+ let pks = map fieldHaskell $ primaryFields pdef keyvals = map snd $ filter (\(a, _) -> let ret=isJust (find (== a) pks) in ret) $ zip (map fieldHaskell $ entityFields t) fs- in return $ Key $ PersistList keyvals+ in case keyFromValues keyvals of+ Right k -> return k+ Left e -> error $ "ISRManyKeys: unexpected keyvals result: " `mappend` unpack e return key where@@ -60,7 +115,7 @@ vals = map toPersistValue $ toPersistFields val replace k val = do- conn <- askSqlConn+ conn <- ask let t = entityDef $ Just val let sql = T.concat [ "UPDATE "@@ -71,7 +126,7 @@ , connEscapeName conn $ entityID t , "=?" ]- vals = map toPersistValue (toPersistFields val) `mappend` [unKey k]+ vals = map toPersistValue (toPersistFields val) `mappend` keyToValues k rawExecute sql vals where go conn x = connEscapeName conn x `T.append` "=?"@@ -85,15 +140,14 @@ Just _ -> replace key value get k = do- conn <- askSqlConn+ conn <- ask let t = entityDef $ dummyFromKey k- let composite = isJust $ entityPrimary t let cols = T.intercalate "," $ map (connEscapeName conn . fieldDB) $ entityFields t noColumns :: Bool noColumns = null $ entityFields t let wher = case entityPrimary t of- Just pdef -> T.intercalate " AND " $ map (\fld -> connEscapeName conn (snd fld) <> "=? ") $ primaryFields pdef+ Just pdef -> T.intercalate " AND " $ map (\fld -> connEscapeName conn (fieldDB fld) <> "=? ") $ primaryFields pdef Nothing -> connEscapeName conn (entityID t) <> "=?" let sql = T.concat [ "SELECT "@@ -103,24 +157,23 @@ , " WHERE " , wher ]- rawQuery sql (convertKey composite k) C.$$ do+ withRawQuery sql (keyToValues k) $ do res <- CL.head case res of Nothing -> return Nothing Just vals -> case fromPersistValues $ if noColumns then [] else vals of- Left e -> error $ "get " ++ show (unKey k) ++ ": " ++ unpack e+ Left e -> error $ "get " ++ show k ++ ": " ++ unpack e Right v -> return $ Just v delete k = do- conn <- askSqlConn- rawExecute (sql conn) (convertKey composite k)+ conn <- ask+ rawExecute (sql conn) (keyToValues k) where t = entityDef $ dummyFromKey k- composite = isJust $ entityPrimary t wher conn = case entityPrimary t of- Just pdef -> T.intercalate " AND " $ map (\fld -> connEscapeName conn (snd fld) <> "=? ") $ primaryFields pdef+ Just pdef -> T.intercalate " AND " $ map (\fld -> connEscapeName conn (fieldDB fld) <> "=? ") $ primaryFields pdef Nothing -> connEscapeName conn (entityID t) <> "=?" sql conn = T.concat [ "DELETE FROM "@@ -129,16 +182,16 @@ , wher conn ] -dummyFromKey :: KeyBackend SqlBackend v -> Maybe v+dummyFromKey :: Key v -> Maybe v dummyFromKey _ = Nothing -insrepHelper :: (MonadIO m, PersistEntity val, MonadLogger m, MonadSqlPersist m)+insrepHelper :: (MonadIO m, PersistEntity val) => Text -> Key val -> val- -> m ()-insrepHelper command (Key k) val = do- conn <- askSqlConn+ -> ReaderT Connection m ()+insrepHelper command k val = do+ conn <- ask rawExecute (sql conn) vals where t = entityDef $ Just val@@ -154,4 +207,11 @@ , T.intercalate "," ("?" : map (const "?") (entityFields t)) , ")" ]- vals = k : map toPersistValue (toPersistFields val)+ vals = keyToValues k ++ map toPersistValue (toPersistFields val)++updateFieldDef :: PersistEntity v => Update v -> FieldDef+updateFieldDef (Update f _ _) = persistFieldDef f+updateFieldDef (BackendUpdate {}) = error "updateFieldDef did not expect BackendUpdate"++updatePersistValue :: Update v -> PersistValue+updatePersistValue (Update _ v _) = toPersistValue v
Database/Persist/Sql/Orphan/PersistUnique.hs view
@@ -4,26 +4,25 @@ import Database.Persist import Database.Persist.Sql.Types-import Database.Persist.Sql.Class import Database.Persist.Sql.Raw-import Database.Persist.Sql.Orphan.PersistStore ()+import Database.Persist.Sql.Orphan.PersistStore (withRawQuery) import qualified Data.Text as T-import Data.Monoid ((<>))+import Data.Monoid (mappend) import Control.Monad.Logger import qualified Data.Conduit.List as CL import Data.Conduit-import Control.Monad.Trans.Resource (MonadResource)+import Control.Monad.Trans.Reader (ask) -instance (MonadResource m, MonadLogger m) => PersistUnique (SqlPersistT m) where+instance PersistUnique Connection where deleteBy uniq = do- conn <- askSqlConn+ conn <- ask let sql' = sql conn vals = persistUniqueToValues uniq rawExecute sql' vals where t = entityDef $ dummyFromUnique uniq go = map snd . persistUniqueToFieldNames- go' conn x = connEscapeName conn x <> "=?"+ go' conn x = connEscapeName conn x `mappend` "=?" sql conn = T.concat [ "DELETE FROM " , connEscapeName conn $ entityDB t@@ -32,7 +31,7 @@ ] getBy uniq = do- conn <- askSqlConn+ conn <- ask let flds = map (connEscapeName conn . fieldDB) (entityFields t) let cols = case entityPrimary t of Just _ -> T.intercalate "," flds@@ -46,23 +45,22 @@ , sqlClause conn ] vals' = persistUniqueToValues uniq- rawQuery sql vals' $$ do+ withRawQuery sql vals' $ do row <- CL.head case row of Nothing -> return Nothing- Just (PersistInt64 k:vals) ->- case fromPersistValues vals of- Left s -> error $ T.unpack s- Right x -> return $ Just (Entity (Key $ PersistInt64 k) x)- Just (PersistDouble k:vals) -> -- oracle+ Just [] -> error "getBy: empty row"+ Just (kpv:vals) -> case fromPersistValues vals of Left s -> error $ T.unpack s- Right x -> return $ Just (Entity (Key $ PersistInt64 $ truncate k) x)- Just xs -> error $ "Database.Persist.GenericSql: Bad list in getBy xs="++show xs+ Right x ->+ case keyFromValues [kpv] of+ Right k -> return $ Just (Entity k x)+ Left _ -> error $ "getBy: keyFromValues failed: " `mappend` show kpv where sqlClause conn = T.intercalate " AND " $ map (go conn) $ toFieldNames' uniq- go conn x = connEscapeName conn x <> "=?"+ go conn x = connEscapeName conn x `mappend` "=?" t = entityDef $ dummyFromUnique uniq toFieldNames' = map snd . persistUniqueToFieldNames
Database/Persist/Sql/Raw.hs view
@@ -1,49 +1,67 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-} module Database.Persist.Sql.Raw where import Database.Persist import Database.Persist.Sql.Types import Database.Persist.Sql.Class import qualified Data.Map as Map-import Control.Monad.IO.Class (liftIO)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Monad.Reader (ReaderT, ask, MonadReader)+import Control.Monad.Trans.Resource (release)+import Data.Acquire (allocateAcquire, Acquire, mkAcquire, with) import Data.IORef (writeIORef, readIORef, newIORef) import Control.Exception (throwIO) import Control.Monad (when, liftM) import Data.Text (Text, pack)-import Control.Monad.Logger (logDebugS)+import Control.Monad.Logger (logDebugS, runLoggingT) import Data.Int (Int64)-import Control.Monad.Trans.Class (lift) import qualified Data.Text as T import Data.Conduit import Control.Monad.Trans.Resource (MonadResource) -rawQuery :: (MonadSqlPersist m, MonadResource m)+rawQuery :: (MonadResource m, MonadReader env m, HasPersistBackend env Connection) => Text -> [PersistValue] -> Source m [PersistValue] rawQuery sql vals = do- lift $ $logDebugS (pack "SQL") $ pack $ show sql ++ " " ++ show vals- conn <- lift askSqlConn- bracketP- (getStmtConn conn sql)- stmtReset- (flip stmtQuery vals)+ srcRes <- liftPersist $ rawQueryRes sql vals+ (releaseKey, src) <- allocateAcquire srcRes+ src+ release releaseKey -rawExecute :: MonadSqlPersist m => Text -> [PersistValue] -> m ()+rawQueryRes+ :: (MonadIO m1, MonadIO m2)+ => Text+ -> [PersistValue]+ -> ReaderT Connection m1 (Acquire (Source m2 [PersistValue]))+rawQueryRes sql vals = do+ conn <- ask+ let make = do+ runLoggingT ($logDebugS (pack "SQL") $ pack $ show sql ++ " " ++ show vals)+ (connLogFunc conn)+ getStmtConn conn sql+ return $ do+ stmt <- mkAcquire make stmtReset+ stmtQuery stmt vals++rawExecute :: MonadIO m => Text -> [PersistValue] -> ReaderT Connection m () rawExecute x y = liftM (const ()) $ rawExecuteCount x y -rawExecuteCount :: MonadSqlPersist m => Text -> [PersistValue] -> m Int64+rawExecuteCount :: MonadIO m => Text -> [PersistValue] -> ReaderT Connection m Int64 rawExecuteCount sql vals = do- $logDebugS (pack "SQL") $ pack $ show sql ++ " " ++ show vals+ conn <- ask+ runLoggingT ($logDebugS (pack "SQL") $ pack $ show sql ++ " " ++ show vals)+ (connLogFunc conn) stmt <- getStmt sql res <- liftIO $ stmtExecute stmt vals liftIO $ stmtReset stmt return res -getStmt :: MonadSqlPersist m => Text -> m Statement+getStmt :: MonadIO m => Text -> ReaderT Connection m Statement getStmt sql = do- conn <- askSqlConn+ conn <- ask liftIO $ getStmtConn conn sql getStmtConn :: Connection -> Text -> IO Statement@@ -101,10 +119,10 @@ -- However, most common problems are mitigated by using the -- entity selection placeholder @??@, and you shouldn't see any -- error at all if you're not using 'Single'.-rawSql :: (RawSql a, MonadSqlPersist m, MonadResource m)+rawSql :: (RawSql a, MonadIO m) => Text -- ^ SQL statement, possibly with placeholders. -> [PersistValue] -- ^ Values to fill the placeholders.- -> m [a]+ -> ReaderT Connection m [a] rawSql stmt = run where getType :: (x -> m [a]) -> a@@ -113,8 +131,9 @@ x = getType run process = rawSqlProcessRow - withStmt' colSubsts params = do- rawQuery sql params+ withStmt' colSubsts params sink = do+ srcRes <- rawQueryRes sql params+ liftIO $ with srcRes ($$ sink) where sql = T.concat $ makeSubsts colSubsts $ T.splitOn placeholder stmt placeholder = "??"@@ -131,9 +150,9 @@ ] run params = do- conn <- askSqlConn+ conn <- ask let (colCount, colSubsts) = rawSqlCols (connEscapeName conn) x- withStmt' colSubsts params $$ firstRow colCount+ withStmt' colSubsts params $ firstRow colCount firstRow colCount = do mrow <- await
Database/Persist/Sql/Run.hs view
@@ -3,8 +3,9 @@ import Database.Persist.Sql.Types import Database.Persist.Sql.Raw+import Control.Monad.Trans.Control import Data.Pool as P-import Control.Monad.Trans.Reader+import Control.Monad.Trans.Reader hiding (local) import Control.Monad.Trans.Resource import Control.Monad.Logger import Control.Monad.Base@@ -17,7 +18,9 @@ import Data.IORef (readIORef) import qualified Data.Map as Map import Control.Exception.Lifted (throwIO)+import Control.Exception (mask) import Control.Monad (liftM)+import System.Timeout (timeout) -- | Get a connection from the pool, run the given action, and then return the -- connection to the pool.@@ -26,26 +29,30 @@ mres <- withResourceTimeout 2000000 pconn $ runSqlConn r maybe (throwIO Couldn'tGetSQLConnection) return mres -withResourceTimeout- :: (MonadBaseControl IO m)+-- | Like 'withResource', but times out the operation if resource+-- allocation does not complete within the given timeout period.+--+-- Since 2.0.0+withResourceTimeout ::+ (MonadBaseControl IO m) => Int -- ^ Timeout period in microseconds- -> P.Pool a+ -> Pool a -> (a -> m b) -> m (Maybe b)-{-# SPECIALIZE withResourceTimeout :: Int -> P.Pool a -> (a -> IO b) -> IO (Maybe b) #-}+{-# SPECIALIZE withResourceTimeout :: Int -> Pool a -> (a -> IO b) -> IO (Maybe b) #-} withResourceTimeout ms pool act = control $ \runInIO -> mask $ \restore -> do- mres <- timeout ms $ P.takeResource pool+ mres <- timeout ms $ takeResource pool case mres of Nothing -> runInIO $ return Nothing Just (resource, local) -> do ret <- restore (runInIO (liftM Just $ act resource)) `onException`- P.destroyResource pool local resource- P.putResource local resource+ destroyResource pool local resource+ putResource local resource return ret {-# INLINABLE withResourceTimeout #-} runSqlConn :: MonadBaseControl IO m => SqlPersistT m a -> Connection -> m a-runSqlConn (SqlPersistT r) conn = do+runSqlConn r conn = do let getter = getStmtConn conn liftBase $ connBegin conn getter x <- onException@@ -60,8 +67,8 @@ runSqlPersistMPool :: SqlPersistM a -> Pool Connection -> IO a runSqlPersistMPool x pool = runResourceT $ runNoLoggingT $ runSqlPool x pool -withSqlPool :: MonadIO m- => IO Connection -- ^ create a new connection+withSqlPool :: (MonadIO m, MonadLogger m, MonadBaseControl IO m)+ => (LogFunc -> IO Connection) -- ^ create a new connection -> Int -- ^ connection count -> (Pool Connection -> m a) -> m a@@ -69,15 +76,28 @@ pool <- createSqlPool mkConn connCount f pool -createSqlPool :: MonadIO m- => IO Connection+createSqlPool :: (MonadIO m, MonadLogger m, MonadBaseControl IO m)+ => (LogFunc -> IO Connection) -> Int -> m (Pool Connection)-createSqlPool mkConn = liftIO . createPool mkConn close' 1 20+createSqlPool mkConn size = do+ logFunc <- askLogFunc+ liftIO $ createPool (mkConn logFunc) close' 1 20 size -withSqlConn :: (MonadIO m, MonadBaseControl IO m)- => IO Connection -> (Connection -> m a) -> m a-withSqlConn open = bracket (liftIO open) (liftIO . close')+-- NOTE: This function is a terrible, ugly hack. It would be much better to+-- just clean up monad-logger.+askLogFunc :: (MonadBaseControl IO m, MonadLogger m) => m LogFunc+askLogFunc = do+ runInBase <- control $ \run -> run $ return run+ return $ \a b c d -> do+ _ <- runInBase (monadLoggerLog a b c d)+ return ()++withSqlConn :: (MonadIO m, MonadBaseControl IO m, MonadLogger m)+ => (LogFunc -> IO Connection) -> (Connection -> m a) -> m a+withSqlConn open f = do+ logFunc <- askLogFunc+ bracket (liftIO $ open logFunc) (liftIO . close') f close' :: Connection -> IO () close' conn = do
Database/Persist/Sql/Types.hs view
@@ -10,32 +10,26 @@ module Database.Persist.Sql.Types where import Control.Exception (Exception)-import Control.Monad.Trans.Resource (MonadResource (..), MonadThrow (..), ResourceT)-#if MIN_VERSION_exceptions(0,6,0)-import Control.Monad.Catch (MonadCatch, MonadMask)-#endif-import Control.Monad.Logger (MonadLogger (..), NoLoggingT)+import Control.Monad.Trans.Resource (MonadResource (..), ResourceT)+import Data.Acquire (Acquire)+import Control.Monad.Logger (NoLoggingT) import Control.Monad.Trans.Control-import Control.Monad.Trans.Class (MonadTrans (..)) import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Trans.Reader (ReaderT (..))-import Control.Applicative (Applicative (..)) import Control.Monad.Trans.Writer (WriterT)-import Control.Monad.Base (MonadBase (..))-import Control.Monad (MonadPlus (..)) import Data.Typeable (Typeable) import Control.Monad (liftM) import Database.Persist.Types-import Data.Text (Text, pack)-import qualified Data.Text as T+import Database.Persist.Class (HasPersistBackend (..)) import Data.IORef (IORef) import Data.Map (Map) import Data.Int (Int64) import Data.Conduit (Source) import Data.Pool (Pool)-import Web.PathPieces-import Control.Exception (throw)-import qualified Data.Text.Read+import Language.Haskell.TH.Syntax (Loc)+import Control.Monad.Logger (LogSource, LogLevel)+import System.Log.FastLogger (LogStr)+import Data.Text (Text) data InsertSqlResult = ISRSingle Text | ISRInsertGet Text Text@@ -44,13 +38,13 @@ data Connection = Connection { connPrepare :: Text -> IO Statement -- | table name, column names, id name, either 1 or 2 statements to run- , connInsertSql :: EntityDef SqlType -> [PersistValue] -> InsertSqlResult+ , connInsertSql :: EntityDef -> [PersistValue] -> InsertSqlResult , connStmtMap :: IORef (Map Text Statement) , connClose :: IO () , connMigrateSql- :: [EntityDef SqlType]+ :: [EntityDef] -> (Text -> IO Statement)- -> EntityDef SqlType+ -> EntityDef -> IO (Either [Text] [(Bool, Text)]) , connBegin :: (Text -> IO Statement) -> IO () , connCommit :: (Text -> IO Statement) -> IO ()@@ -59,15 +53,21 @@ , connNoLimit :: Text , connRDBMS :: Text , connLimitOffset :: (Int,Int) -> Bool -> Text -> Text+ , connLogFunc :: LogFunc }+ deriving Typeable+instance HasPersistBackend Connection Connection where+ persistBackend = id +type LogFunc = Loc -> LogSource -> LogLevel -> LogStr -> IO ()+ data Statement = Statement { stmtFinalize :: IO () , stmtReset :: IO () , stmtExecute :: [PersistValue] -> IO Int64- , stmtQuery :: forall m. MonadResource m+ , stmtQuery :: forall m. MonadIO m => [PersistValue]- -> Source m [PersistValue]+ -> Acquire (Source m [PersistValue]) } data Column = Column@@ -86,71 +86,23 @@ deriving (Typeable, Show) instance Exception PersistentSqlException -data SqlBackend- deriving Typeable+type SqlBackend = Connection -- FIXME -newtype SqlPersistT m a = SqlPersistT { unSqlPersistT :: ReaderT Connection m a }- deriving (Monad, MonadIO, MonadTrans, Functor, Applicative, MonadPlus-#if MIN_VERSION_exceptions(0,6,0)-#if MIN_VERSION_resourcet(1,1,0)- , MonadThrow- , MonadCatch- , MonadMask-#endif-#endif- )+type SqlPersistT = ReaderT Connection -- FIXME rename connection type SqlPersist = SqlPersistT {-# DEPRECATED SqlPersist "Please use SqlPersistT instead" #-} type SqlPersistM = SqlPersistT (NoLoggingT (ResourceT IO)) -#if MIN_VERSION_resourcet(1,1,0)-#if !MIN_VERSION_exceptions(0,6,0)-instance MonadThrow m => MonadThrow (SqlPersistT m) where- throwM = lift . throwM-#endif--#else-instance MonadThrow m => MonadThrow (SqlPersistT m) where- monadThrow = lift . monadThrow-#endif--instance MonadBase backend m => MonadBase backend (SqlPersistT m) where- liftBase = lift . liftBase--instance MonadBaseControl backend m => MonadBaseControl backend (SqlPersistT m) where- newtype StM (SqlPersistT m) a = StMSP {unStMSP :: ComposeSt SqlPersistT m a}- liftBaseWith = defaultLiftBaseWith StMSP- restoreM = defaultRestoreM unStMSP-instance MonadTransControl SqlPersistT where- newtype StT SqlPersistT a = StReader {unStReader :: a}- liftWith f = SqlPersistT $ ReaderT $ \r -> f $ \t -> liftM StReader $ runReaderT (unSqlPersistT t) r- restoreT = SqlPersistT . ReaderT . const . liftM unStReader--instance MonadResource m => MonadResource (SqlPersistT m) where- liftResourceT = lift . liftResourceT--instance MonadLogger m => MonadLogger (SqlPersistT m) where- monadLoggerLog a b c = lift . monadLoggerLog a b c- type Sql = Text -- Bool indicates if the Sql is safe type CautiousMigration = [(Bool, Sql)] -type Migration m = WriterT [Text] (WriterT CautiousMigration m) ()+type Migration = WriterT [Text] (WriterT CautiousMigration (ReaderT Connection IO)) () type ConnectionPool = Pool Connection--instance PathPiece (KeyBackend SqlBackend entity) where- toPathPiece (Key (PersistInt64 i)) = toPathPiece i- toPathPiece k = throw $ PersistInvalidField $ pack $ "Invalid Key: " ++ show k- fromPathPiece t =- case Data.Text.Read.signed Data.Text.Read.decimal t of- Right (i, t') | T.null t' -> Just $ Key $ PersistInt64 i- _ -> Nothing- -- $rawSql --
Database/Persist/Types/Base.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-} module Database.Persist.Types.Base where import qualified Data.Aeson as A@@ -24,7 +25,6 @@ import Data.Bits (shiftL, shiftR) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS8-import Data.Time.LocalTime (ZonedTime, zonedTimeToUTC, zonedTimeToLocalTime, zonedTimeZone) import Data.Map (Map) import qualified Data.HashMap.Strict as HM import Data.Word (Word32)@@ -105,12 +105,12 @@ | ByNullableAttr deriving (Eq, Show) -data EntityDef sqlType = EntityDef+data EntityDef = EntityDef { entityHaskell :: !HaskellName , entityDB :: !DBName , entityID :: !DBName , entityAttrs :: ![Attr]- , entityFields :: ![FieldDef sqlType]+ , entityFields :: ![FieldDef] , entityPrimary :: Maybe PrimaryDef , entityUniques :: ![UniqueDef] , entityForeigns:: ![ForeignDef]@@ -118,7 +118,7 @@ , entityExtra :: !(Map Text [ExtraLine]) , entitySum :: !Bool }- deriving (Show, Eq, Read, Ord, Functor)+ deriving (Show, Eq, Read, Ord) type ExtraLine = [Text] @@ -136,17 +136,53 @@ | FTList FieldType deriving (Show, Eq, Read, Ord) -data FieldDef sqlType = FieldDef- { fieldHaskell :: !HaskellName -- ^ name of the field- , fieldDB :: !DBName- , fieldType :: !FieldType- , fieldSqlType :: !sqlType- , fieldAttrs :: ![Attr] -- ^ user annotations for a field- , fieldStrict :: !Bool -- ^ a strict field in the data type. Default: true- , fieldEmbedded :: Maybe (EntityDef ()) -- ^ indicates that the field uses an embedded entity+data FieldDef = FieldDef+ { fieldHaskell :: !HaskellName -- ^ name of the field+ , fieldDB :: !DBName+ , fieldType :: !FieldType+ , fieldSqlType :: !SqlType+ , fieldAttrs :: ![Attr] -- ^ user annotations for a field+ , fieldStrict :: !Bool -- ^ a strict field in the data type. Default: true+ , fieldReference :: !ReferenceDef }- deriving (Show, Eq, Read, Ord, Functor)+ deriving (Show, Eq, Read, Ord) +data ReferenceDef = ForeignRef !HaskellName+ | EmbedRef EmbedEntityDef+ | NoReference+ deriving (Show, Eq, Read, Ord)++-- | An EmbedEntityDef is the same as an EntityDef+-- But it is only used for fieldReference+-- so it only has data needed for embedding+data EmbedEntityDef = EmbedEntityDef+ { embeddedHaskell :: !HaskellName+ , embeddedFields :: ![EmbedFieldDef]+ } deriving (Show, Eq, Read, Ord)++-- | An EmbedFieldDef is the same as a FieldDef+-- But it is only used for embeddedFields+-- so it only has data needed for embedding+data EmbedFieldDef = EmbedFieldDef+ { emFieldDB :: !DBName+ , emFieldEmbed :: Maybe EmbedEntityDef+ }+ deriving (Show, Eq, Read, Ord)++toEmbedEntityDef :: EntityDef -> EmbedEntityDef+toEmbedEntityDef ent = EmbedEntityDef+ { embeddedHaskell = entityHaskell ent+ , embeddedFields = map toEmbedFieldDef $ entityFields ent+ }++toEmbedFieldDef :: FieldDef -> EmbedFieldDef+toEmbedFieldDef field =+ EmbedFieldDef { emFieldDB = fieldDB field+ , emFieldEmbed = case fieldReference field of+ EmbedRef em -> Just em+ _ -> Nothing+ }+ data UniqueDef = UniqueDef { uniqueHaskell :: !HaskellName , uniqueDBName :: !DBName@@ -156,7 +192,7 @@ deriving (Show, Eq, Read, Ord) data PrimaryDef = PrimaryDef- { primaryFields :: ![(HaskellName, DBName)]+ { primaryFields :: ![FieldDef] , primaryAttrs :: ![Attr] } deriving (Show, Eq, Read, Ord)@@ -184,14 +220,6 @@ instance Error PersistException where strMsg = PersistError . pack --- | Avoid orphan instances.-newtype ZT = ZT ZonedTime deriving (Show, Read, Typeable)--instance Eq ZT where- ZT a /= ZT b = zonedTimeToLocalTime a /= zonedTimeToLocalTime b || zonedTimeZone a /= zonedTimeZone b-instance Ord ZT where- ZT a `compare` ZT b = zonedTimeToUTC a `compare` zonedTimeToUTC b- -- | A raw value which can be stored in any backend and can be marshalled to -- and from a 'PersistField'. data PersistValue = PersistText Text@@ -203,7 +231,6 @@ | PersistDay Day | PersistTimeOfDay TimeOfDay | PersistUTCTime UTCTime- | PersistZonedTime ZT | PersistNull | PersistList [PersistValue] | PersistMap [(Text, PersistValue)]@@ -247,10 +274,10 @@ _ -> Just $ PersistText t toPathPiece x = case fromPersistValueText x of- Left e -> error e+ Left e -> error $ T.unpack e Right y -> y -fromPersistValueText :: PersistValue -> Either String Text+fromPersistValueText :: PersistValue -> Either Text Text fromPersistValueText (PersistText s) = Right s fromPersistValueText (PersistByteString bs) = Right $ TE.decodeUtf8With lenientDecode bs@@ -260,7 +287,6 @@ fromPersistValueText (PersistDay d) = Right $ T.pack $ show d fromPersistValueText (PersistTimeOfDay d) = Right $ T.pack $ show d fromPersistValueText (PersistUTCTime d) = Right $ T.pack $ show d-fromPersistValueText (PersistZonedTime (ZT z)) = Right $ T.pack $ show z fromPersistValueText PersistNull = Left "Unexpected null" fromPersistValueText (PersistBool b) = Right $ T.pack $ show b fromPersistValueText (PersistList _) = Left "Cannot convert PersistList to Text"@@ -283,7 +309,6 @@ toJSON (PersistBool b) = A.Bool b toJSON (PersistTimeOfDay t) = A.String $ T.pack $ 't' : show t toJSON (PersistUTCTime u) = A.String $ T.pack $ 'u' : show u- toJSON (PersistZonedTime z) = A.String $ T.pack $ 'z' : show z toJSON (PersistDay d) = A.String $ T.pack $ 'd' : show d toJSON PersistNull = A.Null toJSON (PersistList l) = A.Array $ V.fromList $ map A.toJSON l@@ -317,7 +342,6 @@ $ B64.decode $ TE.encodeUtf8 t Just ('t', t) -> fmap PersistTimeOfDay $ readMay t Just ('u', t) -> fmap PersistUTCTime $ readMay t- Just ('z', t) -> fmap PersistZonedTime $ readMay t Just ('d', t) -> fmap PersistDay $ readMay t Just ('r', t) -> fmap PersistRational $ readMay t Just ('o', t) -> maybe (fail "Invalid base64") (return . PersistObjectId) $@@ -367,33 +391,29 @@ | SqlBool | SqlDay | SqlTime- | SqlDayTime- | SqlDayTimeZoned+ | SqlDayTime -- ^ Always uses UTC timezone | SqlBlob | SqlOther T.Text -- ^ a backend-specific name deriving (Show, Read, Eq, Typeable, Ord) -newtype KeyBackend backend entity = Key { unKey :: PersistValue }- deriving (Show, Read, Eq, Ord)--type family KeyEntity key-type instance KeyEntity (KeyBackend backend entity) = entity--instance A.ToJSON (KeyBackend backend entity) where- toJSON (Key val) = A.toJSON val--instance A.FromJSON (KeyBackend backend entity) where- parseJSON = fmap Key . A.parseJSON- data PersistFilter = Eq | Ne | Gt | Lt | Ge | Le | In | NotIn | BackendSpecificFilter T.Text deriving (Read, Show) -data UpdateGetException = KeyNotFound String+data UpdateException = KeyNotFound String+ | UpsertError String deriving Typeable-instance Show UpdateGetException where+instance Show UpdateException where show (KeyNotFound key) = "Key not found during updateGet: " ++ key-instance Exception UpdateGetException+ show (UpsertError msg) = "Error during upsert: " ++ msg+instance Exception UpdateException++data OnlyUniqueException = OnlyUniqueException String deriving Typeable+instance Show OnlyUniqueException where+ show (OnlyUniqueException uniqueMsg) =+ "Expected only one unique key, got " ++ uniqueMsg+instance Exception OnlyUniqueException+ data PersistUpdate = Assign | Add | Subtract | Multiply | Divide -- FIXME need something else here deriving (Read, Show, Enum, Bounded)
persistent.cabal view
@@ -1,5 +1,5 @@ name: persistent-version: 1.3.3+version: 2.0.0 license: MIT license-file: LICENSE author: Michael Snoyman <michael@snoyman.com>@@ -28,10 +28,11 @@ , text >= 0.8 , containers >= 0.2 , conduit >= 1.0- , resourcet >= 0.4- , exceptions+ , resourcet >= 1.1+ , exceptions >= 0.6 , monad-control >= 0.3 , lifted-base >= 0.1+ , resource-pool , path-pieces >= 0.1 , aeson >= 0.5 , monad-logger >= 0.3@@ -44,8 +45,11 @@ , blaze-html >= 0.5 , blaze-markup >= 0.5.1 , silently+ , mtl+ , fast-logger >= 2.1 , scientific , resource-pool+ , tagged exposed-modules: Database.Persist Database.Persist.Quasi
test/main.hs view
@@ -101,6 +101,3 @@ baz = FTTypeCon Nothing "Baz" parseFieldType "Foo [Bar] Baz" `shouldBe` Just ( foo `FTApp` bars `FTApp` baz)- describe "stripId" $ do- it "works" $- (parseFieldType "FooId" >>= stripId) `shouldBe` Just "Foo"