packages feed

persistent-sql-lifted-0.4.3.0: library/Database/Persist/Sql/Lifted/Persistent.hs

{-# LANGUAGE CPP #-}

-- | Wrappers that apply 'liftSql' to Persistent utilities of the same name.
module Database.Persist.Sql.Lifted.Persistent
  ( checkUnique
  , checkUniqueUpdateable
  , count
  , delete
  , deleteBy
  , deleteWhere
  , deleteWhereCount
  , exists
  , existsBy
  , get
  , getBy
  , getByValue
  , getEntity
  , getFieldName
  , getJust
  , getJustEntity
  , getMany
  , getTableName
  , insert
  , insert_
  , insertBy
  , insertEntity
  , insertEntityMany
  , insertKey
  , insertMany
  , insertMany_
  , insertRecord
  , insertUnique
  , insertUnique_
  , insertUniqueEntity
  , onlyUnique
  , putMany
  , rawExecute
  , rawExecuteCount
  , rawSql
  , replace
  , replaceUnique
  , repsert
  , repsertMany
  , selectFirst
  , selectKeysList
  , selectList
  , transactionSave
  , transactionSaveWithIsolation
  , transactionUndo
  , transactionUndoWithIsolation
  , update
  , updateGet
  , updateWhere
  , updateWhereCount
  , upsert
  , upsertBy
  ) where

import Data.Bool (Bool)
import Data.Either (Either)
import Data.Eq (Eq)
import Data.Function (($))
import Data.Int (Int, Int64)
import Data.Map.Strict (Map)
import Data.Maybe (Maybe)
#if MIN_VERSION_base(4,17,0)
import Data.Type.Equality (type (~))
#endif
import Data.Text (Text)
import Database.Persist
  ( AtLeastOneUniqueKey
  , Entity
  , Filter
  , OnlyOneUniqueKey
  , PersistEntity (..)
  , PersistValue
  , SelectOpt
  , Update
  )
import Database.Persist.Class qualified as P
import Database.Persist.Class.PersistEntity (SafeToInsert)
import Database.Persist.Sql (IsolationLevel, RawSql)
import Database.Persist.Sql qualified as P
import Database.Persist.Sql.Lifted.Core (MonadSqlBackend, SqlBackend, liftSql)
import GHC.Stack (HasCallStack)

-- | Check whether there are any conflicts for unique keys with this entity
--   and existing entities in the database
checkUnique
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => a
  -> m (Maybe (Unique a))
  -- ^ 'Nothing' if the entity would be unique, and could thus safely
  --   be inserted. On a conflict, 'Just' the conflicting key.
checkUnique a = liftSql $ P.checkUnique a

-- | Check whether there are any conflicts for unique keys with this entity and existing entities in the database
--
-- This is useful for updating because it ignores conflicts when the particular entity already exists.
checkUniqueUpdateable
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Entity a
  -> m (Maybe (Unique a))
  -- ^ 'Nothing' if the entity would stay unique, and could thus safely be updated.
  --   On a conflict, 'Just' the conflicting key.
checkUniqueUpdateable e = liftSql $ P.checkUniqueUpdateable e

-- | The total number of records fulfilling the given criteria
count
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Filter a]
  -- ^ If you provide multiple values in the list, the conditions are ANDed together.
  -> m Int
count fs = liftSql $ P.count fs

-- | Delete a specific record by identifier
--
-- Does nothing if record does not exist.
delete
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> m ()
delete k = liftSql $ P.delete k

-- | Delete a specific record by unique key
--
-- Does nothing if no record matches.
deleteBy
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Unique a
  -> m ()
deleteBy u = liftSql $ P.deleteBy u

-- | Delete all records matching the given criteria
deleteWhere
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Filter a]
  -- ^ If you provide multiple values in the list, the conditions are ANDed together.
  -> m ()
deleteWhere fs = liftSql $ P.deleteWhere fs

-- | Delete all records matching the given criteria
deleteWhereCount
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Filter a]
  -- ^ If you provide multiple values in the list, the conditions are ANDed together.
  -> m Int64
  -- ^ The number of rows affected
deleteWhereCount fs = liftSql $ P.deleteWhereCount fs

-- | Check if there is at least one record fulfilling the given criteria
exists
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Filter a]
  -- ^ If you provide multiple values in the list, the conditions are ANDed together.
  -> m Bool
exists fs = liftSql $ P.exists fs

-- | Check if a record with this unique key exists
existsBy
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Unique a
  -> m Bool
existsBy u = liftSql $ P.existsBy u

-- | Get a record by identifier, if available
get
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> m (Maybe a)
get k = liftSql $ P.get k

-- | Get a record by unique key, if available, returning both the identifier and the record
getBy
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Unique a
  -> m (Maybe (Entity a))
getBy u = liftSql $ P.getBy u

-- Get a record by unique key, if available, returning both the identifier and the record
--
-- This function makes the most sense on entities with a single 'Unique' constructor.
getByValue
  :: forall a m
   . ( AtLeastOneUniqueKey a
     , HasCallStack
     , MonadSqlBackend m
     , PersistEntityBackend a ~ SqlBackend
     )
  => a
  -> m (Maybe (Entity a))
  -- ^ A record matching one of the unique keys.
getByValue a = liftSql $ P.getByValue a

-- | Get a record by identifier, if available
getEntity
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> m (Maybe (Entity a))
getEntity k = liftSql $ P.getEntity k

-- | Get the SQL string for the field that an 'EntityField' represents
getFieldName
  :: forall a t m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => EntityField a t
  -> m Text
getFieldName f = liftSql $ P.getFieldName f

-- | Get a record by identifier, if available, for a non-null (not 'Maybe') foreign key
--
-- Unsafe unless your database is enforcing that the foreign key is valid.
getJust
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> m a
getJust k = liftSql $ P.getJust k

-- | Get a record by identifier, if available, for a non-null (not 'Maybe') foreign key
--
-- Unsafe unless your database is enforcing that the foreign key is valid.
getJustEntity
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> m (Entity a)
getJustEntity k = liftSql $ P.getJustEntity k

-- | Get many records by their respective identifiers, if available
getMany
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Key a]
  -> m (Map (Key a) a)
getMany ks = liftSql $ P.getMany ks

-- | Get the SQL string for the table that a 'PersistEntity' represents
getTableName
  :: forall a m. (HasCallStack, MonadSqlBackend m, PersistEntity a) => a -> m Text
getTableName x = liftSql $ P.getTableName x

-- | Create a new record in the database
insert
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => a
  -> m (Key a)
  -- ^ The auto-increment ID that was generated
insert a = liftSql $ P.insert a

-- | Create a new record in the database
insert_
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => a
  -> m ()
insert_ a = liftSql $ P.insert_ a

-- | Insert a value, checking for conflicts with any unique constraints
insertBy
  :: forall a m
   . ( AtLeastOneUniqueKey a
     , HasCallStack
     , MonadSqlBackend m
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => a
  -> m (Either (Entity a) (Key a))
  -- ^ If a duplicate exists in the database, it is returned as 'Left'.
  --   Otherwise, the new 'Key' is returned as 'Right'.
insertBy a = liftSql $ P.insertBy a

-- | Create a new record in the database, returning an auto-increment ID and the inserted record
insertEntity
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => a
  -> m (Entity a)
insertEntity a = liftSql $ P.insertEntity a

-- | Create multiple records in the database, with specified keys
insertEntityMany
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Entity a]
  -> m ()
insertEntityMany es = liftSql $ P.insertEntityMany es

-- | Create a new record in the database using the given key
insertKey
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> a
  -> m ()
insertKey k a = liftSql $ P.insertKey k a

-- | Create multiple records in the database and return their 'Key's
insertMany
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => [a]
  -> m [Key a]
insertMany as = liftSql $ P.insertMany as

-- | Create multiple records in the database
insertMany_
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => [a]
  -> m ()
insertMany_ as = liftSql $ P.insertMany_ as

-- | Create a new record in the database
insertRecord
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => a
  -> m a
  -- ^ The record that was inserted
insertRecord a = liftSql $ P.insertRecord a

-- | Create a new record in the database
insertUnique
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => a
  -> m (Maybe (Key a))
  -- ^ An auto-increment ID, or 'Nothing' when the record couldn't be
  --   inserted because of a uniqueness constraint
insertUnique a = liftSql $ P.insertUnique a

-- | Create a new record in the database
insertUnique_
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => a
  -> m (Maybe ())
  -- ^ (), or 'Nothing' when the record couldn't be inserted because of a
  --   uniqueness constraint
insertUnique_ a = liftSql $ P.insertUnique_ a

-- | Create a new record in the database
insertUniqueEntity
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => a
  -> m (Maybe (Entity a))
  -- ^ An auto-increment ID and the inserted record, or 'Nothing' when the record
  --   couldn't be inserted because of a uniqueness constraint.
insertUniqueEntity a = liftSql $ P.insertUniqueEntity a

-- | Return the single unique key for a record
onlyUnique
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , OnlyOneUniqueKey a
     , PersistEntityBackend a ~ SqlBackend
     )
  => a
  -> m (Unique a)
onlyUnique a = liftSql $ P.onlyUnique a

-- | Put many records into the database
--
-- * Insert new records that do not exist (or violate any unique constraints);
-- * Replace existing records (matching any unique constraint).
putMany
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => [a]
  -- ^ A list of the records you want to insert or replace.
  -> m ()
putMany as = liftSql $ P.putMany as

-- | Execute a raw SQL statement
rawExecute
  :: forall m
   . (HasCallStack, MonadSqlBackend m)
  => Text
  -- ^ SQL statement, possibly with placeholders
  -> [PersistValue]
  -- ^ Values to fill the placeholders
  -> m ()
rawExecute t vs = liftSql $ P.rawExecute t vs

-- | Execute a raw SQL statement
rawExecuteCount
  :: forall m
   . (HasCallStack, MonadSqlBackend m)
  => Text
  -- ^ SQL statement, possibly with placeholders
  -> [PersistValue]
  -- ^ Values to fill the placeholders
  -> m Int64
  -- ^ The number of rows modified
rawExecuteCount t vs = liftSql $ P.rawExecuteCount t vs

rawSql
  :: forall a m
   . (HasCallStack, MonadSqlBackend m, RawSql a)
  => Text
  -- ^ SQL statement, possibly with placeholders
  -> [PersistValue]
  -- ^ Values to fill the placeholders
  -> m [a]
rawSql sql vals = liftSql $ P.rawSql sql vals

-- | Replace the record in the database with the given key
--
-- The result is undefined if such record does not exist.
replace
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> a
  -> m ()
replace k a = liftSql $ P.replace k a

-- | Attempt to replace the record of the given key with the given new record
--
-- First query the unique fields to make sure the replacement maintains uniqueness constraints.
replaceUnique
  :: forall a m
   . ( Eq (Unique a)
     , HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> a
  -> m (Maybe (Unique a))
  -- ^ 'Nothing' if the replacement was made. If uniqueness is violated,
  --   'Just' the 'Unique' violation.
replaceUnique k a = liftSql $ P.replaceUnique k a

-- | Put the record in the database with the given key
--
-- If a record with the given key does not exist then a new record will be inserted.
repsert
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> a
  -> m ()
repsert k a = liftSql $ P.repsert k a

-- | Put many entities into the database
--
-- For each item, if a record with the given key does not exist then a new record will be inserted.
repsertMany
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [(Key a, a)]
  -> m ()
repsertMany kas = liftSql $ P.repsertMany kas

-- | Get just the first record for the criteria
selectFirst
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Filter a]
  -- ^ If you provide multiple values in the list, the conditions are ANDed together.
  -> [SelectOpt a]
  -> m (Maybe (Entity a))
selectFirst fs os = liftSql $ P.selectFirst fs os

-- | Get the 'Key's of all records matching the given criteria
selectKeysList
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Filter a]
  -- ^ If you provide multiple values in the list, the conditions are ANDed together.
  -> [SelectOpt a]
  -> m [Key a]
selectKeysList fs os = liftSql $ P.selectKeysList fs os

selectList
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Filter a]
  -- ^ If you provide multiple values in the list, the conditions are ANDed together.
  -> [SelectOpt a]
  -> m [Entity a]
  -- ^ Entities corresponding to the filters and options provided
selectList fs os = liftSql $ P.selectList fs os

-- | Commit the current transaction and begin a new one
transactionSave :: forall m. (HasCallStack, MonadSqlBackend m) => m ()
transactionSave = liftSql P.transactionSave

-- | Commit the current transaction and begin a new one
transactionSaveWithIsolation
  :: forall m
   . (HasCallStack, MonadSqlBackend m)
  => IsolationLevel
  -- ^ Isolation level
  -> m ()
transactionSaveWithIsolation il = liftSql $ transactionSaveWithIsolation il

-- | Roll back the current transaction and begin a new one
transactionUndo :: forall m. (HasCallStack, MonadSqlBackend m) => m ()
transactionUndo = liftSql transactionUndo

-- | Roll back the current transaction and begin a new one
transactionUndoWithIsolation
  :: forall m
   . (HasCallStack, MonadSqlBackend m)
  => IsolationLevel
  -- ^ Isolation level
  -> m ()
transactionUndoWithIsolation il = liftSql $ transactionUndoWithIsolation il

-- | Update individual fields on a specific record
update
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> [Update a]
  -> m ()
update k us = liftSql $ P.update k us

-- | Update individual fields on a specific record, and retrieve the updated value from the database
--
-- This function will throw an exception if the given key is not found in the database.
updateGet
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => Key a
  -> [Update a]
  -> m a
updateGet k us = liftSql $ P.updateGet k us

-- | Update individual fields on any record matching the given criteria
updateWhere
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Filter a]
  -- ^ If you provide multiple values in the list, the conditions are ANDed together.
  -> [Update a]
  -> m ()
updateWhere fs us = liftSql $ P.updateWhere fs us

-- | Update individual fields on any record matching the given criteria
updateWhereCount
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     )
  => [Filter a]
  -- ^ If you provide multiple values in the list, the conditions are ANDed together.
  -> [Update a]
  -> m Int64
  -- ^ The number of rows affected
updateWhereCount fs us = liftSql $ P.updateWhereCount fs us

-- | Update based on a uniqueness constraint or insert:
--
-- * Unsert the new record if it does not exist;
-- * If the record exists (matched via it's uniqueness constraint), then update the
--   existing record with the parameters which is passed on as list to the function.
upsert
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , OnlyOneUniqueKey a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => a
  -- ^ New record to insert
  -> [Update a]
  -- ^ Updates to perform if the record already exists
  -> m (Entity a)
  -- ^ The record in the database after the operation
upsert a us = liftSql $ P.upsert a us

-- | Update based on a given uniqueness constraint or insert:
--
-- * Insert the new record if it does not exist;
-- * Update the existing record that matches the given uniqueness constraint.
upsertBy
  :: forall a m
   . ( HasCallStack
     , MonadSqlBackend m
     , PersistEntity a
     , PersistEntityBackend a ~ SqlBackend
     , SafeToInsert a
     )
  => Unique a
  -- ^ Uniqueness constraint to find by
  -> a
  -- ^ New record to insert
  -> [Update a]
  -- ^ Updates to perform if the record already exists
  -> m (Entity a)
  -- ^ The record in the database after the operation
upsertBy u a us = liftSql $ P.upsertBy u a us