1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
{-# LANGUAGE ExplicitForAll #-}
module Database.Persist.Class.PersistQuery
( selectList
, PersistQueryRead (..)
, PersistQueryWrite (..)
, selectSource
, selectKeys
, selectKeysList
) where
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader (ReaderT, MonadReader)
import Control.Monad.Trans.Resource (MonadResource, release)
import Data.Acquire (Acquire, allocateAcquire, with)
import Data.Conduit (ConduitM, (.|), await, runConduit)
import qualified Data.Conduit.List as CL
import Database.Persist.Class.PersistStore
import Database.Persist.Class.PersistEntity
-- | Backends supporting conditional read operations.
class (PersistCore backend, PersistStoreRead backend) => PersistQueryRead backend where
-- | Get all records matching the given criterion in the specified order.
-- Returns also the identifiers.
--
-- NOTE: This function returns an 'Acquire' and a 'ConduitM', which implies
-- that it streams from the database. It does not. Please use 'selectList'
-- to simplify the code. If you want streaming behavior, consider
-- @persistent-pagination@ which efficiently chunks a query into ranges, or
-- investigate a backend-specific streaming solution.
selectSourceRes
:: (PersistRecordBackend record backend, MonadIO m1, MonadIO m2)
=> [Filter record]
-> [SelectOpt record]
-> ReaderT backend m1 (Acquire (ConduitM () (Entity record) m2 ()))
-- | Get just the first record for the criterion.
selectFirst :: (MonadIO m, PersistRecordBackend record backend)
=> [Filter record]
-> [SelectOpt record]
-> ReaderT backend m (Maybe (Entity record))
selectFirst filts opts = do
srcRes <- selectSourceRes filts (LimitTo 1 : opts)
liftIO $ with srcRes (\src -> runConduit $ src .| await)
-- | Get the 'Key's of all records matching the given criterion.
selectKeysRes
:: (MonadIO m1, MonadIO m2, PersistRecordBackend record backend)
=> [Filter record]
-> [SelectOpt record]
-> ReaderT backend m1 (Acquire (ConduitM () (Key record) m2 ()))
-- | The total number of records fulfilling the given criterion.
count :: (MonadIO m, PersistRecordBackend record backend)
=> [Filter record] -> ReaderT backend m Int
-- | Check if there is at least one record fulfilling the given criterion.
--
-- @since 2.11
exists :: (MonadIO m, PersistRecordBackend record backend)
=> [Filter record] -> ReaderT backend m Bool
-- | Backends supporting conditional write operations
class (PersistQueryRead backend, PersistStoreWrite backend) => PersistQueryWrite backend where
-- | Update individual fields on any record matching the given criterion.
updateWhere :: (MonadIO m, PersistRecordBackend record backend)
=> [Filter record] -> [Update record] -> ReaderT backend m ()
-- | Delete all records matching the given criterion.
deleteWhere :: (MonadIO m, PersistRecordBackend record backend)
=> [Filter record] -> ReaderT backend m ()
-- | Get all records matching the given criterion in the specified order.
-- Returns also the identifiers.
--
-- WARNING: This function returns a 'ConduitM', which suggests that it streams
-- the results. It does not stream results on most backends. If you need
-- streaming, see @persistent-pagination@ for a means of chunking results based
-- on indexed ranges.
selectSource
:: forall record backend m. (PersistQueryRead backend, MonadResource m, PersistRecordBackend record backend, MonadReader backend m)
=> [Filter record]
-> [SelectOpt record]
-> ConduitM () (Entity record) m ()
selectSource filts opts = do
srcRes <- liftPersist $ selectSourceRes filts opts
(releaseKey, src) <- allocateAcquire srcRes
src
release releaseKey
-- | Get the 'Key's of all records matching the given criterion.
--
-- For an example, see 'selectList'.
selectKeys :: forall record backend m. (PersistQueryRead backend, MonadResource m, PersistRecordBackend record backend, MonadReader backend m)
=> [Filter record]
-> [SelectOpt record]
-> ConduitM () (Key record) m ()
selectKeys filts opts = do
srcRes <- liftPersist $ selectKeysRes filts opts
(releaseKey, src) <- allocateAcquire srcRes
src
release releaseKey
-- | Returns a @['Entity' record]@ corresponding to the filters and options
-- provided.
--
-- Filters are constructed using the operators defined in "Database.Persist"
-- (and re-exported from "Database.Persist.Sql"). Let's look at some examples:
--
-- @
-- usersWithAgeOver40 :: 'SqlPersistT' 'IO' ['Entity' User]
-- usersWithAgeOver40 =
-- 'selectList' [UserAge 'Database.Persist.>=.' 40] []
-- @
--
-- If you provide multiple values in the list, the conditions are @AND@ed
-- together.
--
-- @
-- usersWithAgeBetween30And50 :: 'SqlPersistT' 'IO' ['Entity' User]
-- usersWithAgeBetween30And50 =
-- 'selectList'
-- [ UserAge 'Database.Persist.>=.' 30
-- , UserAge 'Database.Persist.<=.' 50
-- ]
-- []
-- @
--
-- The second list contains the 'SelectOpt' for a record. We can select the
-- first ten records with 'LimitTo'
--
-- @
-- firstTenUsers =
-- 'selectList' [] ['LimitTo' 10]
-- @
--
-- And we can select the second ten users with 'OffsetBy'.
--
-- @
-- secondTenUsers =
-- 'selectList' [] ['LimitTo' 10, 'OffsetBy' 10]
-- @
--
-- <https://use-the-index-luke.com/sql/partial-results/fetch-next-page Warning that LIMIT/OFFSET is bad for pagination!>
--
-- The type of record can usually be infered from the types of the provided filters
-- and select options. In the previous two examples, though, you'll notice that the
-- select options are polymorphic, applying to any record type. In order to help
-- type inference in such situations, or simply as an enhancement to readability,
-- you might find type application useful, illustrated below.
--
-- @
-- {-# LANGUAGE TypeApplications #-}
-- ...
--
-- firstTenUsers =
-- 'selectList' @User [] ['LimitTo' 10]
--
-- secondTenUsers =
-- 'selectList' @User [] ['LimitTo' 10, 'OffsetBy' 10]
-- @
--
-- With 'Asc' and 'Desc', we can provide the field we want to sort on. We can
-- provide multiple sort orders - later ones are used to sort records that are
-- equal on the first field.
--
-- @
-- newestUsers =
-- selectList [] ['Desc' UserCreatedAt, 'LimitTo' 10]
--
-- oldestUsers =
-- selectList [] ['Asc' UserCreatedAt, 'LimitTo' 10]
-- @
selectList
:: forall record backend m. (MonadIO m, PersistQueryRead backend, PersistRecordBackend record backend)
=> [Filter record]
-> [SelectOpt record]
-> ReaderT backend m [Entity record]
selectList filts opts = do
srcRes <- selectSourceRes filts opts
liftIO $ with srcRes (\src -> runConduit $ src .| CL.consume)
-- | Call 'selectKeys' but return the result as a list.
selectKeysList :: forall record backend m. (MonadIO m, PersistQueryRead backend, PersistRecordBackend record backend)
=> [Filter record]
-> [SelectOpt record]
-> ReaderT backend m [Key record]
selectKeysList filts opts = do
srcRes <- selectKeysRes filts opts
liftIO $ with srcRes (\src -> runConduit $ src .| CL.consume)
|