on 04fdf27 help sign in
rel8/src/Rel8/Expr/Aggregate.hs
187 lines 5.4 kB history raw
1{-# language DataKinds #-}
2{-# language FlexibleContexts #-}
3{-# language ScopedTypeVariables #-}
4{-# language TypeFamilies #-}
6{-# options_ghc -fno-warn-redundant-constraints #-}
8module Rel8.Expr.Aggregate
9 ( count, countDistinct, countStar, countWhere
10 , and, or
11 , min, max
12 , sum, sumWhere
13 , stringAgg
14 , groupByExpr
15 , array1DAggExpr, listAggExpr, nonEmptyAggExpr
16 )
17where
19-- base
20import Data.Int ( Int64 )
21import Data.List.NonEmpty ( NonEmpty )
22import Prelude hiding ( and, max, min, null, or, sum )
24-- opaleye
25import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
27-- rel8
28import Rel8.Aggregate ( Aggregate, Aggregator(..), unsafeMakeAggregate )
29import Rel8.Expr ( Expr )
30import Rel8.Expr.Bool ( caseExpr )
31import Rel8.Expr.Opaleye
32 ( castExpr
33 , fromPrimExpr
34 , fromPrimExpr
35 , toPrimExpr
36 )
37import Rel8.Expr.Null ( null )
38import Rel8.Expr.Serialize ( litExpr )
39import Rel8.Schema.Null ( Sql )
40import Rel8.Type.Array ( fromPrimArray )
41import Rel8.Type.Array1D ( Array1D, NotArray )
42import Rel8.Type.Eq ( DBEq )
43import Rel8.Type.Num ( DBNum )
44import Rel8.Type.Ord ( DBMax, DBMin )
45import Rel8.Type.String ( DBString )
46import Rel8.Type.Sum ( DBSum )
49-- | Count the occurances of a single column. Corresponds to @COUNT(a)@
50count :: Expr a -> Aggregate (Expr Int64)
51count = unsafeMakeAggregate toPrimExpr fromPrimExpr $
52 Just Aggregator
53 { operation = Opaleye.AggrCount
54 , ordering = []
55 , distinction = Opaleye.AggrAll
56 }
59-- | Count the number of distinct occurances of a single column. Corresponds to
60-- @COUNT(DISTINCT a)@
61countDistinct :: Sql DBEq a => Expr a -> Aggregate (Expr Int64)
62countDistinct = unsafeMakeAggregate toPrimExpr fromPrimExpr $
63 Just Aggregator
64 { operation = Opaleye.AggrCount
65 , ordering = []
66 , distinction = Opaleye.AggrDistinct
67 }
70-- | Corresponds to @COUNT(*)@.
71countStar :: Aggregate (Expr Int64)
72countStar = count (litExpr True)
75-- | A count of the number of times a given expression is @true@.
76countWhere :: Expr Bool -> Aggregate (Expr Int64)
77countWhere condition = count (caseExpr [(condition, litExpr (Just True))] null)
80-- | Corresponds to @bool_and@.
81and :: Expr Bool -> Aggregate (Expr Bool)
82and = unsafeMakeAggregate toPrimExpr fromPrimExpr $
83 Just Aggregator
84 { operation = Opaleye.AggrBoolAnd
85 , ordering = []
86 , distinction = Opaleye.AggrAll
87 }
90-- | Corresponds to @bool_or@.
91or :: Expr Bool -> Aggregate (Expr Bool)
92or = unsafeMakeAggregate toPrimExpr fromPrimExpr $
93 Just Aggregator
94 { operation = Opaleye.AggrBoolOr
95 , ordering = []
96 , distinction = Opaleye.AggrAll
97 }
100-- | Produce an aggregation for @Expr a@ using the @max@ function.
101max :: Sql DBMax a => Expr a -> Aggregate (Expr a)
102max = unsafeMakeAggregate toPrimExpr fromPrimExpr $
103 Just Aggregator
104 { operation = Opaleye.AggrMax
105 , ordering = []
106 , distinction = Opaleye.AggrAll
107 }
110-- | Produce an aggregation for @Expr a@ using the @max@ function.
111min :: Sql DBMin a => Expr a -> Aggregate (Expr a)
112min = unsafeMakeAggregate toPrimExpr fromPrimExpr $
113 Just Aggregator
114 { operation = Opaleye.AggrMin
115 , ordering = []
116 , distinction = Opaleye.AggrAll
117 }
119-- | Corresponds to @sum@. Note that in SQL, @sum@ is type changing - for
120-- example the @sum@ of @integer@ returns a @bigint@. Rel8 doesn't support
121-- this, and will add explicit cast back to the original input type. This can
122-- lead to overflows, and if you anticipate very large sums, you should upcast
123-- your input.
124sum :: Sql DBSum a => Expr a -> Aggregate (Expr a)
125sum = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) $
126 Just Aggregator
127 { operation = Opaleye.AggrSum
128 , ordering = []
129 , distinction = Opaleye.AggrAll
130 }
133-- | Take the sum of all expressions that satisfy a predicate.
134sumWhere :: (Sql DBNum a, Sql DBSum a)
135 => Expr Bool -> Expr a -> Aggregate (Expr a)
136sumWhere condition a = sum (caseExpr [(condition, a)] 0)
139-- | Corresponds to @string_agg()@.
140stringAgg :: Sql DBString a
141 => Expr db -> Expr a -> Aggregate (Expr a)
142stringAgg delimiter =
143 unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) $
144 Just Aggregator
145 { operation = Opaleye.AggrStringAggr (toPrimExpr delimiter)
146 , ordering = []
147 , distinction = Opaleye.AggrAll
148 }
151-- | Aggregate a value by grouping by it.
152groupByExpr :: Sql DBEq a => Expr a -> Aggregate (Expr a)
153groupByExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr Nothing
156-- | Collect expressions values as an array.
157array1DAggExpr :: NotArray a => Expr a -> Aggregate (Expr (Array1D a))
158array1DAggExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr $ Just
159 Aggregator
160 { operation = Opaleye.AggrArr
161 , ordering = []
162 , distinction = Opaleye.AggrAll
163 }
166-- | Collect expressions values as a list.
167listAggExpr :: Expr a -> Aggregate (Expr [a])
168listAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
169 Aggregator
170 { operation = Opaleye.AggrArr
171 , ordering = []
172 , distinction = Opaleye.AggrAll
173 }
174 where
175 from = fromPrimExpr . fromPrimArray
178-- | Collect expressions values as a non-empty list.
179nonEmptyAggExpr :: Expr a -> Aggregate (Expr (NonEmpty a))
180nonEmptyAggExpr = unsafeMakeAggregate toPrimExpr from $ Just
181 Aggregator
182 { operation = Opaleye.AggrArr
183 , ordering = []
184 , distinction = Opaleye.AggrAll
185 }
186 where
187 from = fromPrimExpr . fromPrimArray