on da76712 help sign in
rel8/src/Rel8/Expr/Aggregate.hs
150 lines 5.0 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 , avg
14 , stringAgg
15 , groupByExpr
16 , listAggExpr, nonEmptyAggExpr
17 , slistAggExpr, snonEmptyAggExpr
18 )
19where
21-- base
22import Data.Int ( Int64 )
23import Data.List.NonEmpty ( NonEmpty )
24import Prelude hiding ( and, max, min, null, or, sum )
26-- opaleye
27import qualified Opaleye.Internal.Aggregate as Opaleye
28import Opaleye.Internal.Column ( Field_( Column ) )
29import qualified Opaleye.Aggregate as Opaleye
31-- rel8
32import Rel8.Aggregate ( Aggregate, unsafeMakeAggregate )
33import Rel8.Expr ( Expr )
34import Rel8.Expr.Bool ( caseExpr )
35import Rel8.Expr.Opaleye
36 ( castExpr
37 , fromPrimExpr
38 , fromPrimExpr
39 , toPrimExpr
40 )
41import Rel8.Expr.Null ( null )
42import Rel8.Expr.Serialize ( litExpr )
43import Rel8.Schema.Null ( Sql, Unnullify )
44import Rel8.Type ( DBType, typeInformation )
45import Rel8.Type.Array ( encodeArrayElement )
46import Rel8.Type.Eq ( DBEq )
47import Rel8.Type.Information ( TypeInformation )
48import Rel8.Type.Num ( DBNum )
49import Rel8.Type.Ord ( DBMax, DBMin )
50import Rel8.Type.String ( DBString )
51import Rel8.Type.Sum ( DBSum )
54-- | Count the occurances of a single column. Corresponds to @COUNT(a)@
55count :: Expr a -> Aggregate Int64
56count = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.count
58-- | Count the number of distinct occurances of a single column. Corresponds to
59-- @COUNT(DISTINCT a)@
60countDistinct :: Sql DBEq a => Expr a -> Aggregate Int64
61countDistinct = unsafeMakeAggregate toPrimExpr fromPrimExpr $
62 Opaleye.distinctAggregator Opaleye.count
65-- | Corresponds to @COUNT(*)@.
66countStar :: Aggregate Int64
67countStar = count (litExpr True)
70-- | A count of the number of times a given expression is @true@.
71countWhere :: Expr Bool -> Aggregate Int64
72countWhere condition = count (caseExpr [(condition, litExpr (Just True))] null)
75-- | Corresponds to @bool_and@.
76and :: Expr Bool -> Aggregate Bool
77and = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.boolAnd
80-- | Corresponds to @bool_or@.
81or :: Expr Bool -> Aggregate Bool
82or = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.boolOr
85-- | Produce an aggregation for @Expr a@ using the @max@ function.
86max :: Sql DBMax a => Expr a -> Aggregate a
87max = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.unsafeMax
90-- | Produce an aggregation for @Expr a@ using the @max@ function.
91min :: Sql DBMin a => Expr a -> Aggregate a
92min = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.unsafeMin
94-- | Corresponds to @sum@. Note that in SQL, @sum@ is type changing - for
95-- example the @sum@ of @integer@ returns a @bigint@. Rel8 doesn't support
96-- this, and will add explicit casts back to the original input type. This can
97-- lead to overflows, and if you anticipate very large sums, you should upcast
98-- your input.
99sum :: Sql DBSum a => Expr a -> Aggregate a
100sum = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) Opaleye.unsafeSum
103-- | Corresponds to @avg@. Note that in SQL, @avg@ is type changing - for
104-- example, the @avg@ of @integer@ returns a @numeric@. Rel8 doesn't support
105-- this, and will add explicit casts back to the original input type. If you
106-- need a fractional result on an integral column, you should cast your input
107-- to 'Double' or 'Data.Scientific.Scientific' before calling 'avg'.
108avg :: Sql DBSum a => Expr a -> Aggregate a
109avg = unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) Opaleye.unsafeAvg
111-- | Take the sum of all expressions that satisfy a predicate.
112sumWhere :: (Sql DBNum a, Sql DBSum a)
113 => Expr Bool -> Expr a -> Aggregate a
114sumWhere condition a = sum (caseExpr [(condition, a)] 0)
117-- | Corresponds to @string_agg()@.
118stringAgg :: Sql DBString a
119 => Expr db -> Expr a -> Aggregate a
120stringAgg delimiter =
121 unsafeMakeAggregate toPrimExpr (castExpr . fromPrimExpr) (Opaleye.stringAgg (Column (toPrimExpr delimiter)))
124-- | Aggregate a value by grouping by it.
125groupByExpr :: Sql DBEq a => Expr a -> Aggregate a
126groupByExpr = unsafeMakeAggregate toPrimExpr fromPrimExpr Opaleye.groupBy
129-- | Collect expressions values as a list.
130listAggExpr :: Sql DBType a => Expr a -> Aggregate [a]
131listAggExpr = slistAggExpr typeInformation
134-- | Collect expressions values as a non-empty list.
135nonEmptyAggExpr :: Sql DBType a => Expr a -> Aggregate (NonEmpty a)
136nonEmptyAggExpr = snonEmptyAggExpr typeInformation
139slistAggExpr :: ()
140 => TypeInformation (Unnullify a) -> Expr a -> Aggregate [a]
141slistAggExpr info = unsafeMakeAggregate to fromPrimExpr Opaleye.arrayAgg
142 where
143 to = encodeArrayElement info . toPrimExpr
146snonEmptyAggExpr :: ()
147 => TypeInformation (Unnullify a) -> Expr a -> Aggregate (NonEmpty a)
148snonEmptyAggExpr info = unsafeMakeAggregate to fromPrimExpr Opaleye.arrayAgg
149 where
150 to = encodeArrayElement info . toPrimExpr