{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeFamilies, FlexibleContexts, ConstraintKinds #-}
module Database.Persist.Class.PersistUnique
(PersistUniqueRead(..)
,PersistUniqueWrite(..)
,getByValue
,insertBy
,insertUniqueEntity
,replaceUnique
,checkUnique
,onlyUnique
,defaultPutMany
,persistUniqueKeyValues
)
where
import Database.Persist.Types
import Control.Exception (throwIO)
import Control.Monad (liftM)
import Control.Monad.IO.Class (liftIO, MonadIO)
import Data.List ((\\), deleteFirstsBy, nubBy)
import Data.Function (on)
import Control.Monad.Trans.Reader (ReaderT)
import Database.Persist.Class.PersistStore
import Database.Persist.Class.PersistEntity
import Data.Monoid (mappend)
import Data.Text (unpack, Text)
import Data.Maybe (catMaybes)
class (PersistCore backend, PersistStoreRead backend) =>
PersistUniqueRead backend where
getBy
:: (MonadIO m, PersistRecordBackend record backend)
=> Unique record -> ReaderT backend m (Maybe (Entity record))
class (PersistUniqueRead backend, PersistStoreWrite backend) =>
PersistUniqueWrite backend where
deleteBy
:: (MonadIO m, PersistRecordBackend record backend)
=> Unique record -> ReaderT backend m ()
insertUnique
:: (MonadIO m, PersistRecordBackend record backend)
=> record -> ReaderT backend m (Maybe (Key record))
insertUnique datum = do
conflict <- checkUnique datum
case conflict of
Nothing -> Just `liftM` insert datum
Just _ -> return Nothing
upsert
:: (MonadIO m, PersistRecordBackend record backend)
=> record
-> [Update record]
-> ReaderT backend m (Entity record)
upsert record updates = do
uniqueKey <- onlyUnique record
upsertBy uniqueKey record updates
upsertBy
:: (MonadIO m, PersistRecordBackend record backend)
=> Unique record
-> record
-> [Update record]
-> ReaderT backend m (Entity record)
upsertBy uniqueKey record updates = do
mrecord <- getBy uniqueKey
maybe (insertEntity record) (`updateGetEntity` updates) mrecord
where
updateGetEntity (Entity k _) upds =
(Entity k) `liftM` (updateGet k upds)
putMany
:: (MonadIO m, PersistRecordBackend record backend)
=> [record]
-> ReaderT backend m ()
putMany = defaultPutMany
insertBy
:: (MonadIO m
,PersistUniqueWrite backend
,PersistRecordBackend record backend)
=> record -> ReaderT backend m (Either (Entity record) (Key record))
insertBy val = do
res <- getByValue val
case res of
Nothing -> Right `liftM` insert val
Just z -> return $ Left z
_insertOrGet :: (MonadIO m, PersistUniqueWrite backend, PersistRecordBackend record backend)
=> record -> ReaderT backend m (Key record)
_insertOrGet val = do
res <- getByValue val
case res of
Nothing -> insert val
Just (Entity key _) -> return key
insertUniqueEntity
:: (MonadIO m
,PersistRecordBackend record backend
,PersistUniqueWrite backend)
=> record -> ReaderT backend m (Maybe (Entity record))
insertUniqueEntity datum =
fmap (\key -> Entity key datum) `liftM` insertUnique datum
onlyUnique
:: (MonadIO m
,PersistUniqueWrite backend
,PersistRecordBackend record backend)
=> record -> ReaderT backend m (Unique record)
onlyUnique record =
case onlyUniqueEither record of
Right u -> return u
Left us ->
requireUniques record us >>=
liftIO . throwIO . OnlyUniqueException . show . length
onlyUniqueEither
:: (PersistEntity record)
=> record -> Either [Unique record] (Unique record)
onlyUniqueEither record =
case persistUniqueKeys record of
[u] -> Right u
us -> Left us
getByValue
:: (MonadIO m
,PersistUniqueRead backend
,PersistRecordBackend record backend)
=> record -> ReaderT backend m (Maybe (Entity record))
getByValue record =
checkUniques =<< requireUniques record (persistUniqueKeys record)
where
checkUniques [] = return Nothing
checkUniques (x:xs) = do
y <- getBy x
case y of
Nothing -> checkUniques xs
Just z -> return $ Just z
requireUniques
:: (MonadIO m, PersistEntity record)
=> record -> [Unique record] -> m [Unique record]
requireUniques record [] = liftIO $ throwIO $ userError errorMsg
where
errorMsg = "getByValue: " `Data.Monoid.mappend` unpack (recordName record) `mappend` " does not have any Unique"
requireUniques _ xs = return xs
recordName
:: (PersistEntity record)
=> record -> Text
recordName = unHaskellName . entityHaskell . entityDef . Just
replaceUnique
:: (MonadIO m
,Eq record
,Eq (Unique record)
,PersistRecordBackend record backend
,PersistUniqueWrite backend)
=> Key record -> record -> ReaderT backend m (Maybe (Unique record))
replaceUnique key datumNew = getJust key >>= replaceOriginal
where
uniqueKeysNew = persistUniqueKeys datumNew
replaceOriginal original = do
conflict <- checkUniqueKeys changedKeys
case conflict of
Nothing -> replace key datumNew >> return Nothing
(Just conflictingKey) -> return $ Just conflictingKey
where
changedKeys = uniqueKeysNew \\ uniqueKeysOriginal
uniqueKeysOriginal = persistUniqueKeys original
checkUnique
:: (MonadIO m
,PersistRecordBackend record backend
,PersistUniqueRead backend)
=> record -> ReaderT backend m (Maybe (Unique record))
checkUnique = checkUniqueKeys . persistUniqueKeys
checkUniqueKeys
:: (MonadIO m
,PersistEntity record
,PersistUniqueRead backend
,PersistRecordBackend 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)
defaultPutMany
::( PersistEntityBackend record ~ BaseBackend backend
, PersistEntity record
, MonadIO m
, PersistStoreWrite backend
, PersistUniqueRead backend
)
=> [record]
-> ReaderT backend m ()
defaultPutMany [] = return ()
defaultPutMany rsD = do
let rs = nubBy ((==) `on` persistUniqueKeyValues) (reverse rsD)
mEsOld <- mapM getByValue rs
let merge (Just x) y = Just (x, y)
merge _ _ = Nothing
let mEsOldAndRs = zipWith merge mEsOld rs
let esOldAndRs = catMaybes mEsOldAndRs
let esOld = fmap fst esOldAndRs
let rsOld = fmap entityVal esOld
let rsNew = deleteFirstsBy ((==) `on` persistUniqueKeyValues) rs rsOld
let rsUpd = fmap snd esOldAndRs
let ksOld = fmap entityKey esOld
let krs = zip ksOld rsUpd
insertMany_ rsNew
mapM_ (uncurry replace) krs
persistUniqueKeyValues :: PersistEntity record => record -> [PersistValue]
persistUniqueKeyValues r = concat $ map persistUniqueToValues $ persistUniqueKeys r