packages feed

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 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