on 137ebed help sign in
rel8/src/Rel8/Aggregate.hs
155 lines 4.6 kB history raw
1{-# language DataKinds #-}
2{-# language DerivingVia #-}
3{-# language FlexibleContexts #-}
4{-# language FlexibleInstances #-}
5{-# language GADTs #-}
6{-# language MultiParamTypeClasses #-}
7{-# language NamedFieldPuns #-}
8{-# language PolyKinds #-}
9{-# language StandaloneKindSignatures #-}
10{-# language TypeFamilies #-}
11{-# language UndecidableInstances #-}
12{-# language UndecidableSuperClasses #-}
14module Rel8.Aggregate
15 ( Aggregate(..), foldInputs, mapInputs
16 , Aggregator(..), unsafeMakeAggregate
17 , Aggregates
18 , Col( A, unA )
19 )
20where
22-- base
23import Data.Functor.Const ( Const( Const ), getConst )
24import Data.Functor.Identity ( Identity )
25import Data.Kind ( Constraint, Type )
26import Prelude
28-- opaleye
29import qualified Opaleye.Internal.Aggregate as Opaleye
30import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
31import qualified Opaleye.Internal.PackMap as Opaleye
33-- rel8
34import Rel8.Expr ( Expr )
35import Rel8.Schema.Context ( Interpretation(..) )
36import Rel8.Schema.Context.Label ( Labelable(..) )
37import Rel8.Schema.HTable.Identity ( HIdentity(..), HType )
38import Rel8.Schema.Name ( Name )
39import Rel8.Schema.Null ( Sql )
40import Rel8.Schema.Reify ( notReify )
41import Rel8.Schema.Result ( Result )
42import Rel8.Schema.Spec ( Spec( Spec ) )
43import Rel8.Table
44 ( Table, Columns, Context, fromColumns, toColumns
45 , reify, unreify
46 )
47import Rel8.Table.Recontextualize ( Recontextualize )
48import Rel8.Type ( DBType )
51-- | An @Aggregate a@ describes how to aggregate @Table@s of type @a@. You can
52-- unpack an @Aggregate@ back to @a@ by running it with 'Rel8.aggregate'. As
53-- @Aggregate@ is almost an 'Applicative' functor - but there is no 'pure'
54-- operation. This means 'Aggregate' is an instance of 'Apply', and you can
55-- combine @Aggregate@s using the @<.>@ combinator.
56type Aggregate :: k -> Type
57data Aggregate a where
58 Aggregate :: !(Opaleye.Aggregator () (Expr a)) -> Aggregate a
61instance Interpretation Aggregate where
62 data Col Aggregate _spec where
63 A :: ()
64 => { unA :: !(Aggregate a) }
65 -> Col Aggregate ('Spec labels a)
68instance Sql DBType a => Table Aggregate (Aggregate a) where
69 type Columns (Aggregate a) = HType a
70 type Context (Aggregate a) = Aggregate
72 toColumns = HIdentity . A
73 fromColumns (HIdentity (A a)) = a
75 reify = notReify
76 unreify = notReify
79instance Sql DBType a =>
80 Recontextualize Aggregate Aggregate (Aggregate a) (Aggregate a)
83instance Sql DBType a =>
84 Recontextualize Aggregate Expr (Aggregate a) (Expr a)
87instance Sql DBType a =>
88 Recontextualize Aggregate Result (Aggregate a) (Identity a)
91instance Sql DBType a =>
92 Recontextualize Aggregate Name (Aggregate a) (Name a)
95instance Sql DBType a =>
96 Recontextualize Expr Aggregate (Expr a) (Aggregate a)
99instance Sql DBType a =>
100 Recontextualize Result Aggregate (Identity a) (Aggregate a)
103instance Sql DBType a =>
104 Recontextualize Name Aggregate (Name a) (Aggregate a)
107instance Labelable Aggregate where
108 labeler (A aggregate) = A aggregate
109 unlabeler (A aggregate) = A aggregate
112-- | @Aggregates a b@ means that the columns in @a@ are all 'Aggregate' 'Expr's
113-- for the columns in @b@.
114type Aggregates :: Type -> Type -> Constraint
115class Recontextualize Aggregate Expr aggregates exprs => Aggregates aggregates exprs
116instance Recontextualize Aggregate Expr aggregates exprs => Aggregates aggregates exprs
119foldInputs :: Monoid b
120 => (Maybe Aggregator -> Opaleye.PrimExpr -> b) -> Aggregate a -> b
121foldInputs f (Aggregate (Opaleye.Aggregator (Opaleye.PackMap agg))) =
122 getConst $ flip agg () $ \(aggregator, a) ->
123 Const $ f (detuplize <$> aggregator) a
124 where
125 detuplize (operation, ordering, distinction) =
126 Aggregator {operation, ordering, distinction}
129mapInputs :: ()
130 => (Opaleye.PrimExpr -> Opaleye.PrimExpr) -> Aggregate a -> Aggregate a
131mapInputs transform (Aggregate (Opaleye.Aggregator (Opaleye.PackMap agg))) =
132 Aggregate $ Opaleye.Aggregator $ Opaleye.PackMap $ agg . \f input ->
133 f (fmap transform input)
136type Aggregator :: Type
137data Aggregator = Aggregator
138 { operation :: Opaleye.AggrOp
139 , ordering :: [Opaleye.OrderExpr]
140 , distinction :: Opaleye.AggrDistinct
141 }
144unsafeMakeAggregate :: ()
145 => (Expr input -> Opaleye.PrimExpr)
146 -> (Opaleye.PrimExpr -> Expr output)
147 -> Maybe Aggregator
148 -> Expr input
149 -> Aggregate output
150unsafeMakeAggregate input output aggregator expr =
151 Aggregate $ Opaleye.Aggregator $ Opaleye.PackMap $ \f _ ->
152 output <$> f (tuplize <$> aggregator, input expr)
153 where
154 tuplize Aggregator {operation, ordering, distinction} =
155 (operation, ordering, distinction)