File: PersistQuery.hs

package info (click to toggle)
haskell-persistent 2.17.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,196 kB
  • sloc: haskell: 14,076; makefile: 3
file content (190 lines) | stat: -rw-r--r-- 7,397 bytes parent folder | download | duplicates (2)
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)