persistent-sql-lifted (empty) → 0.1.0.0
raw patch · 12 files changed
+1226/−0 lines, 12 filesdep +annotated-exceptiondep +basedep +conduit
Dependencies added: annotated-exception, base, conduit, containers, esqueleto, mtl, persistent, text, unliftio-core
Files
- CHANGELOG.md +9/−0
- LICENSE +21/−0
- README.md +30/−0
- library/Database/Persist/Sql/Lifted.hs +117/−0
- library/Database/Persist/Sql/Lifted/Core.hs +12/−0
- library/Database/Persist/Sql/Lifted/Esqueleto.hs +202/−0
- library/Database/Persist/Sql/Lifted/HasSqlBackend.hs +13/−0
- library/Database/Persist/Sql/Lifted/MonadSqlBackend.hs +25/−0
- library/Database/Persist/Sql/Lifted/MonadSqlTx.hs +15/−0
- library/Database/Persist/Sql/Lifted/Persistent.hs +643/−0
- package.yaml +68/−0
- persistent-sql-lifted.cabal +71/−0
+ CHANGELOG.md view
@@ -0,0 +1,9 @@+## [_Unreleased_](https://github.com/freckle/freckle-app/compare/persistent-sql-lifted-v0.1.0.0...main)++## [v0.0.0.1](https://github.com/freckle/freckle-app/compare/persistent-sql-lifted-v0.0.0.0...persistent-sql-lifted-v0.1.0.0)++Major expansion, adding query runners for Persistent and Esqueleto.++## [v0.0.0.0](https://github.com/freckle/freckle-app/tree/persistent-sql-lifted-v0.0.0.0/persistent-sql-lifted)++First release, sprouted from `freckle-app-1.20.3.0`.
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2023-2024 Renaissance Learning Inc++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,30 @@+# persistent-sql-lifted++This package introduces two classes:++- `MonadSqlBackend m`, for monadic contexts `m` in which a `SqlBackend` is available++- `MonadSqlBackend db m`, for monadic contexts `m` in which we can execute a SQL+ transaction of type `db a` and get a result `m a`. (The type `db` should have an+ instance of `MonadSqlBackend.)++Additionally, this package provides variants of query-running utilities from+[persistent] and [esqueleto] which are++1. Concretized to use `SqlBackend`;+2. Generalized to a `MonadSqlBackend m` constraint rather than `ReaderT backend m`;+3. Wrapped in [checkpointCallStack] so that exceptions will include call stacks.++How to migrate from vanilla persistent/esqueleto:++- Instead of [SqlPersistT], use a `MonadSqlBackend` constraint.+- Define an instance of `MonadSqlTx` for your application Monad that specifies how+ your application runs database transactions, e.g. by running [runSqlPool].+- Instead of calling `runSqlPool` directly from the rest of your application code,+ use the `runSqlTx` method from the `MonadSqlTx` class.++ [checkpointCallStack]: https://hackage.haskell.org/package/annotated-exception-0.3.0.2/docs/Control-Exception-Annotated-UnliftIO.html+ [esqueleto]: https://hackage.haskell.org/package/esqueleto+ [persistent]: https://hackage.haskell.org/package/persistent+ [runSqlPool]: https://hackage.haskell.org/package/persistent-2.14.6.3/docs/Database-Persist-Sql.html#v:runSqlPool+ [SqlPersistT]: https://hackage.haskell.org/package/persistent-2.14.6.3/docs/Database-Persist-Sql.html#t:SqlPersistT
+ library/Database/Persist/Sql/Lifted.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE CPP #-}++-- |+--+-- Re-exports from:+--+-- * "Database.Persist.Sql.Lifted.Core"+-- * "Database.Persist.Sql.Lifted.Persistent"+-- * "Database.Persist.Sql.Lifted.Esqueleto"+--+-- There are a few name conflicts between Persistent and Esqueleto. Where conflicts occur, this+-- module gives preference to Esqueleto. The following Persistent definitions are renamed:+--+-- * 'Database.Persist.Sql.Lifted.Persistent.delete' -> 'deleteKey'+-- * 'Database.Persist.Sql.Lifted.Persistent.update' -> 'update''+module Database.Persist.Sql.Lifted+ ( -- * Core concepts+ MonadSqlTx (..)+ , HasSqlBackend (..)+ , SqlBackend+ , MonadSqlBackend (..)+ , liftSql++ -- * Getting by key+ , get+ , getBy+ , getByValue+ , getEntity+ , getJust+ , getJustEntity+ , getMany++ -- * Selecting by filter+ , select+ , selectOne+ , selectFirst+ , selectKeys+ , selectKeysList+ , selectList++ -- * Selecting counts/existence+ , count+ , exists++ -- * Inserting+ , insertSelect+ , insertSelectCount+ , insert+ , insert_+ , insertBy+ , insertEntity+ , insertEntityMany+ , insertKey+ , insertMany+ , insertMany_+ , insertRecord+ , insertUnique+ , insertUniqueEntity++ -- * Updating+ , update+ , updateCount+ , update'+ , updateGet+ , updateWhere++ -- * Insert/update combinations+ , replace+ , replaceUnique+ , repsert+ , repsertMany+ , upsert+ , upsertBy+ , putMany++ -- * Working with unique constraints+ , checkUnique+ , checkUniqueUpdateable+ , onlyUnique++ -- * Deleting+ , delete+ , deleteKey+ , deleteBy+ , deleteWhere+ , deleteCount++ -- * Rendering queries to text+ , renderQueryDelete+ , renderQueryInsertInto+ , renderQuerySelect+ , renderQueryToText+ , renderQueryUpdate+ ) where++#if MIN_VERSION_base(4,17,0)+import Data.Type.Equality (type (~))+#endif+import Database.Persist (Key, PersistEntity (PersistEntityBackend), Update)+import Database.Persist.Sql.Lifted.Core+import Database.Persist.Sql.Lifted.Esqueleto+import Database.Persist.Sql.Lifted.Persistent hiding (delete, update)+import Database.Persist.Sql.Lifted.Persistent qualified as Persistent+import GHC.Stack (HasCallStack)++-- | Update individual fields on a specific record+update'+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => Key a+ -> [Update a]+ -> m ()+update' = Persistent.update
+ library/Database/Persist/Sql/Lifted/Core.hs view
@@ -0,0 +1,12 @@+module Database.Persist.Sql.Lifted.Core+ ( MonadSqlTx (..)+ , HasSqlBackend (..)+ , SqlBackend+ , MonadSqlBackend (..)+ , liftSql+ ) where++import Database.Persist.Sql (SqlBackend)+import Database.Persist.Sql.Lifted.HasSqlBackend+import Database.Persist.Sql.Lifted.MonadSqlBackend+import Database.Persist.Sql.Lifted.MonadSqlTx
+ library/Database/Persist/Sql/Lifted/Esqueleto.hs view
@@ -0,0 +1,202 @@+{-# LANGUAGE CPP #-}++-- | Wrappers that apply 'liftSql' to Esqueleto utilities of the same name.+module Database.Persist.Sql.Lifted.Esqueleto+ ( delete+ , deleteCount+ , deleteKey+ , insertSelect+ , insertSelectCount+ , renderQueryDelete+ , renderQueryInsertInto+ , renderQuerySelect+ , renderQueryToText+ , renderQueryUpdate+ , select+ , selectOne+ , update+ , updateCount+ ) where++import Data.Function (($))+import Data.Int (Int64)+import Data.Maybe (Maybe)+import Data.Text (Text)+#if MIN_VERSION_base(4,17,0)+import Data.Type.Equality (type (~))+#endif+import Database.Esqueleto.Experimental+ ( Entity+ , PersistEntity (Key, PersistEntityBackend)+ , PersistValue+ , SqlExpr+ , SqlQuery+ )+import Database.Esqueleto.Experimental qualified as E+import Database.Esqueleto.Internal.Internal (Insertion, Mode, SqlSelect)+import Database.Persist.Sql.Lifted.Core (MonadSqlBackend, SqlBackend, liftSql)+import GHC.Stack (HasCallStack)++-- | Execute an Esqueleto DELETE query+delete :: forall m. (MonadSqlBackend m, HasCallStack) => SqlQuery () -> m ()+delete q = liftSql $ E.delete q++-- | Execute an Esqueleto DELETE query+deleteCount+ :: forall m+ . (MonadSqlBackend m, HasCallStack)+ => SqlQuery ()+ -> m Int64+ -- ^ The number of rows affected+deleteCount q = liftSql $ E.deleteCount q++-- | Delete a specific record by identifier+--+-- Does nothing if record does not exist.+deleteKey+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => Key a+ -> m ()+deleteKey k = liftSql $ E.deleteKey k++-- | Insert a 'E.PersistField' for every selected value+insertSelect+ :: forall a m+ . ( PersistEntity a+ , MonadSqlBackend m+ , HasCallStack+ )+ => SqlQuery (SqlExpr (Insertion a))+ -> m ()+insertSelect q = liftSql $ E.insertSelect q++-- | Insert a 'PersistField' for every selected value, returning the count+insertSelectCount+ :: forall a m+ . ( PersistEntity a+ , MonadSqlBackend m+ , HasCallStack+ )+ => SqlQuery (SqlExpr (Insertion a))+ -> m Int64+ -- ^ The number of inserted rows+insertSelectCount q = liftSql $ E.insertSelectCount q++-- | Renders a 'SqlQuery' to 'Text' along with the list of 'PersistValue's+-- that would be supplied to the database for @?@ placeholders+renderQueryDelete+ :: forall a r m+ . ( SqlSelect a r+ , MonadSqlBackend m+ , HasCallStack+ )+ => SqlQuery a+ -- ^ SQL query to render+ -> m (Text, [PersistValue])+renderQueryDelete q = liftSql $ E.renderQueryDelete q++-- | Renders a 'SqlQuery' to 'Text' along with the list of 'PersistValue's+-- that would be supplied to the database for @?@ placeholders+renderQueryInsertInto+ :: forall a r m+ . ( SqlSelect a r+ , MonadSqlBackend m+ , HasCallStack+ )+ => SqlQuery a+ -- ^ SQL query to render+ -> m (Text, [PersistValue])+renderQueryInsertInto q = liftSql $ E.renderQueryInsertInto q++-- | Renders a 'SqlQuery' to 'Text' along with the list of 'PersistValue's+-- that would be supplied to the database for @?@ placeholders+renderQuerySelect+ :: forall a r m+ . ( SqlSelect a r+ , MonadSqlBackend m+ , HasCallStack+ )+ => SqlQuery a+ -- ^ SQL query to render+ -> m (Text, [PersistValue])+renderQuerySelect q = liftSql $ E.renderQuerySelect q++-- | Renders a 'SqlQuery' to 'Text' along with the list of 'PersistValue's+-- that would be supplied to the database for @?@ placeholders+renderQueryToText+ :: forall a r m+ . ( SqlSelect a r+ , MonadSqlBackend m+ , HasCallStack+ )+ => Mode+ -- ^ Whether to render as an SELECT, DELETE, etc.+ -- You must ensure that the Mode you pass to this function corresponds+ -- with the actual SqlQuery. If you pass a query that uses incompatible+ -- features (like an INSERT statement with a SELECT mode) then you'll+ -- get a weird result.+ -> SqlQuery a+ -- ^ SQL query to render+ -> m (Text, [PersistValue])+renderQueryToText m q = liftSql $ E.renderQueryToText m q++-- | Renders a 'SqlQuery' to 'Text' along with the list of 'PersistValue's+-- that would be supplied to the database for @?@ placeholders+renderQueryUpdate+ :: forall a r m+ . ( SqlSelect a r+ , MonadSqlBackend m+ , HasCallStack+ )+ => SqlQuery a+ -- ^ SQL query to render+ -> m (Text, [PersistValue])+renderQueryUpdate q = liftSql $ E.renderQueryUpdate q++-- | Execute an Esqueleto SELECT query+select+ :: forall a r m+ . (SqlSelect a r, MonadSqlBackend m, HasCallStack)+ => SqlQuery a+ -> m [r]+ -- ^ A list of rows+select q = liftSql $ E.select q++-- | Execute an Esqueleto SELECT query, getting only the first row+selectOne+ :: forall a r m+ . (SqlSelect a r, MonadSqlBackend m, HasCallStack)+ => SqlQuery a+ -> m (Maybe r)+ -- ^ The first row, or 'Nothing' if no rows are selected+selectOne q = liftSql $ E.selectOne q++-- | Execute an Esqueleto UPDATE query+update+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => (SqlExpr (Entity a) -> SqlQuery ())+ -> m ()+update q = liftSql $ E.update q++-- | Execute an Esqueleto UPDATE query, returning the count+updateCount+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => (SqlExpr (Entity a) -> SqlQuery ())+ -> m Int64+ -- ^ The number of inserted rows+updateCount q = liftSql $ E.updateCount q
+ library/Database/Persist/Sql/Lifted/HasSqlBackend.hs view
@@ -0,0 +1,13 @@+module Database.Persist.Sql.Lifted.HasSqlBackend+ ( HasSqlBackend (..)+ ) where++import Prelude++import Database.Persist.Sql (SqlBackend)++class HasSqlBackend a where+ getSqlBackend :: a -> SqlBackend++instance HasSqlBackend SqlBackend where+ getSqlBackend = id
+ library/Database/Persist/Sql/Lifted/MonadSqlBackend.hs view
@@ -0,0 +1,25 @@+module Database.Persist.Sql.Lifted.MonadSqlBackend+ ( MonadSqlBackend (..)+ , liftSql+ ) where++import Prelude++import Control.Exception.Annotated.UnliftIO (checkpointCallStack)+import Control.Monad.IO.Unlift (MonadUnliftIO)+import Control.Monad.Reader (ReaderT (..), asks)+import Database.Persist.Sql (SqlBackend)+import Database.Persist.Sql.Lifted.HasSqlBackend (HasSqlBackend, getSqlBackend)+import GHC.Stack (HasCallStack)++-- | A monadic context in which a SQL backend is available+-- for running database queries+class MonadUnliftIO m => MonadSqlBackend m where+ getSqlBackendM :: m SqlBackend++instance (HasSqlBackend r, MonadUnliftIO m) => MonadSqlBackend (ReaderT r m) where+ getSqlBackendM = asks getSqlBackend++-- | Generalize from 'SqlPersistT' to 'MonadSqlBackend'+liftSql :: (MonadSqlBackend m, HasCallStack) => ReaderT SqlBackend m a -> m a+liftSql (ReaderT f) = checkpointCallStack $ getSqlBackendM >>= f
+ library/Database/Persist/Sql/Lifted/MonadSqlTx.hs view
@@ -0,0 +1,15 @@+module Database.Persist.Sql.Lifted.MonadSqlTx+ ( MonadSqlTx (..)+ ) where++import Control.Monad.IO.Unlift (MonadUnliftIO)+import Database.Persist.Sql.Lifted.MonadSqlBackend (MonadSqlBackend)+import GHC.Stack (HasCallStack)++-- | The constraint @'MonadSqlTx' db m@ indicates that @m@ is a monadic+-- context that can run @db@ actions, usually as a SQL transaction.+-- Typically, this means that @db@ needs a connection and @m@ can+-- provide one, e.g. from a connection pool.+class (MonadSqlBackend db, MonadUnliftIO m) => MonadSqlTx db m | m -> db where+ -- | Runs the action in a SQL transaction+ runSqlTx :: HasCallStack => db a -> m a
+ library/Database/Persist/Sql/Lifted/Persistent.hs view
@@ -0,0 +1,643 @@+{-# 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+ , exists+ , get+ , getBy+ , getByValue+ , getEntity+ , getJust+ , getJustEntity+ , getMany+ , insert+ , insert_+ , insertBy+ , insertEntity+ , insertEntityMany+ , insertKey+ , insertMany+ , insertMany_+ , insertRecord+ , insertUnique+ , insertUniqueEntity+ , onlyUnique+ , putMany+ , replace+ , replaceUnique+ , repsert+ , repsertMany+ , selectFirst+ , selectKeys+ , selectKeysList+ , selectList+ , update+ , updateGet+ , updateWhere+ , upsert+ , upsertBy+ ) where++import Conduit (ConduitT, MonadResource, transPipe)+import Data.Bool (Bool)+import Data.Either (Either)+import Data.Eq (Eq)+import Data.Function (($))+import Data.Int (Int)+import Data.Map.Strict (Map)+import Data.Maybe (Maybe)+#if MIN_VERSION_base(4,17,0)+import Data.Type.Equality (type (~))+#endif+import Database.Persist+ ( AtLeastOneUniqueKey+ , Entity+ , Filter+ , OnlyOneUniqueKey+ , PersistEntity (..)+ , SelectOpt+ , Update+ )+import Database.Persist.Class qualified as P+import Database.Persist.Class.PersistEntity (SafeToInsert)+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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => Unique a+ -> m ()+deleteBy u = liftSql $ P.deleteBy u++-- | Delete all records matching the given criteria+deleteWhere+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [Filter a]+ -- ^ If you provide multiple values in the list, the conditions are ANDed together.+ -> m ()+deleteWhere fs = liftSql $ P.deleteWhere fs++-- | Check if there is at least one record fulfilling the given criteria+exists+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [Filter a]+ -- ^ If you provide multiple values in the list, the conditions are ANDed together.+ -> m Bool+exists fs = liftSql $ P.exists fs++-- | Get a record by identifier, if available+get+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntityBackend a ~ SqlBackend+ , AtLeastOneUniqueKey a+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => Key a+ -> m (Maybe (Entity a))+getEntity k = liftSql $ P.getEntity 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.+getJust+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => Key a+ -> m (Entity a)+getJustEntity k = liftSql $ P.getJustEntity k++-- | Get many records by their respective identifiers, if available+getMany+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [Key a]+ -> m (Map (Key a) a)+getMany ks = liftSql $ P.getMany ks++-- | Create a new record in the database+insert+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => a+ -> m ()+insert_ a = liftSql $ P.insert_ a++-- | Insert a value, checking for conflicts with any unique constraints+insertBy+ :: forall a m+ . ( PersistEntityBackend a ~ SqlBackend+ , AtLeastOneUniqueKey a+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => a+ -> m (Entity a)+insertEntity a = liftSql $ P.insertEntity a++-- | Create multiple records in the database, with specified keys+insertEntityMany+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [Entity a]+ -> m ()+insertEntityMany es = liftSql $ P.insertEntityMany es++-- | Create a new record in the database using the given key+insertKey+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => [a]+ -> m [Key a]+insertMany as = liftSql $ P.insertMany as++-- | Create multiple records in the database+insertMany_+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => [a]+ -> m ()+insertMany_ as = liftSql $ P.insertMany_ as++-- | Create a new record in the database+insertRecord+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+insertUniqueEntity+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntityBackend a ~ SqlBackend+ , OnlyOneUniqueKey a+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => [a]+ -- ^ A list of the records you want to insert or replace.+ -> m ()+putMany as = liftSql $ P.putMany as++-- | Replace the record in the database with the given key+--+-- The result is undefined if such record does not exist.+replace+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , Eq (Unique a)+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [(Key a, a)]+ -> m ()+repsertMany kas = liftSql $ P.repsertMany kas++-- | Get just the first record for the criteria+selectFirst+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [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+selectKeys+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , MonadResource m+ , HasCallStack+ )+ => [Filter a]+ -- ^ If you provide multiple values in the list, the conditions are ANDed together.+ -> [SelectOpt a]+ -> ConduitT () (Key a) m ()+ -- ^ Keys corresponding to the filters and options provided+selectKeys fs os = transPipe liftSql $ P.selectKeys fs os++-- | Get the 'Key's of all records matching the given criteria+selectKeysList+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [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++-- | Update individual fields on a specific record+update+ :: forall a m+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , MonadSqlBackend m+ , HasCallStack+ )+ => [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 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+ . ( PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , OnlyOneUniqueKey a+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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+ . ( PersistEntity a+ , PersistEntityBackend a ~ SqlBackend+ , SafeToInsert a+ , MonadSqlBackend m+ , HasCallStack+ )+ => 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
+ package.yaml view
@@ -0,0 +1,68 @@+name: persistent-sql-lifted+version: 0.1.0.0++maintainer: Freckle Education+category: Database+github: freckle/freckle-app+synopsis: Monad classes for running queries with Persistent and Esqueleto+description: |+ This package introduces two classes: MonadSqlBackend for monadic contexts in+ which a SqlBackend is available, and MonadSqlBackend for contexts in which we+ can execute a SQL transaction.++ Additionally, this package provides variants of query-running utilities from+ Persistent and Esqueleto which are concretized to use SqlBackend, generalized+ to a MonadSqlBackend m constraint rather than "ReaderT backend", and wrapped in+ checkpointCallStack so that exceptions will include call stacks.++extra-doc-files:+ - README.md+ - CHANGELOG.md++extra-source-files:+ - package.yaml++language: GHC2021++ghc-options:+ - -fignore-optim-changes+ - -fwrite-ide-info+ - -Weverything+ - -Wno-all-missed-specialisations+ - -Wno-missing-exported-signatures # re-enables missing-signatures+ - -Wno-missing-import-lists+ - -Wno-missing-kind-signatures+ - -Wno-missing-local-signatures+ - -Wno-missing-safe-haskell-mode+ - -Wno-monomorphism-restriction+ - -Wno-prepositive-qualified-module+ - -Wno-safe+ - -Wno-unsafe++when:+ - condition: "impl(ghc >= 9.8)"+ ghc-options:+ - -Wno-missing-role-annotations+ - -Wno-missing-poly-kind-signatures++dependencies:+ - base < 5++default-extensions:+ - ExplicitNamespaces+ - FunctionalDependencies+ - GADTs+ - NoImplicitPrelude+ - NoMonomorphismRestriction++library:+ source-dirs: library+ dependencies:+ - annotated-exception+ - conduit+ - containers+ - esqueleto+ - mtl+ - persistent+ - text+ - unliftio-core
+ persistent-sql-lifted.cabal view
@@ -0,0 +1,71 @@+cabal-version: 1.18+name: persistent-sql-lifted+version: 0.1.0.0+license: MIT+license-file: LICENSE+maintainer: Freckle Education+homepage: https://github.com/freckle/freckle-app#readme+bug-reports: https://github.com/freckle/freckle-app/issues+synopsis:+ Monad classes for running queries with Persistent and Esqueleto++description:+ This package introduces two classes: MonadSqlBackend for monadic contexts in+ which a SqlBackend is available, and MonadSqlBackend for contexts in which we+ can execute a SQL transaction.+ .+ Additionally, this package provides variants of query-running utilities from+ Persistent and Esqueleto which are concretized to use SqlBackend, generalized+ to a MonadSqlBackend m constraint rather than "ReaderT backend", and wrapped in+ checkpointCallStack so that exceptions will include call stacks.++category: Database+build-type: Simple+extra-source-files: package.yaml+extra-doc-files:+ README.md+ CHANGELOG.md++source-repository head+ type: git+ location: https://github.com/freckle/freckle-app++library+ exposed-modules:+ Database.Persist.Sql.Lifted+ Database.Persist.Sql.Lifted.Core+ Database.Persist.Sql.Lifted.Esqueleto+ Database.Persist.Sql.Lifted.HasSqlBackend+ Database.Persist.Sql.Lifted.MonadSqlBackend+ Database.Persist.Sql.Lifted.MonadSqlTx+ Database.Persist.Sql.Lifted.Persistent++ hs-source-dirs: library+ other-modules: Paths_persistent_sql_lifted+ default-language: GHC2021+ default-extensions:+ ExplicitNamespaces FunctionalDependencies GADTs NoImplicitPrelude+ NoMonomorphismRestriction++ ghc-options:+ -fignore-optim-changes -fwrite-ide-info -Weverything+ -Wno-all-missed-specialisations -Wno-missing-exported-signatures+ -Wno-missing-import-lists -Wno-missing-kind-signatures+ -Wno-missing-local-signatures -Wno-missing-safe-haskell-mode+ -Wno-monomorphism-restriction -Wno-prepositive-qualified-module+ -Wno-safe -Wno-unsafe++ build-depends:+ annotated-exception >=0.2.0.4,+ base >=4.16.4.0 && <5,+ conduit >=1.3.5,+ containers >=0.6.5.1,+ esqueleto >=3.5.10.0,+ mtl >=2.2.2,+ persistent >=2.14.0.1,+ text >=1.2.5.0,+ unliftio-core >=0.2.1.0++ if impl(ghc >=9.8)+ ghc-options:+ -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures