on 97d89cf help sign in
rel8/src/Rel8/Aggregate.hs
131 lines 4.2 kB history raw
1{-# language DataKinds #-}
2{-# language FlexibleContexts #-}
3{-# language FlexibleInstances #-}
4{-# language GADTs #-}
5{-# language MultiParamTypeClasses #-}
6{-# language NamedFieldPuns #-}
7{-# language PolyKinds #-}
8{-# language RankNTypes #-}
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 )
19where
21-- base
22import Data.Functor.Const ( Const( Const ), getConst )
23import Data.Kind ( Constraint, Type )
24import Prelude
26-- opaleye
27import qualified Opaleye.Internal.Aggregate as Opaleye
28import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
29import qualified Opaleye.Internal.PackMap as Opaleye
31-- rel8
32import Rel8.Expr ( Expr )
33import Rel8.Schema.HTable.Identity ( HIdentity(..), HType )
34import Rel8.Schema.Name ( Name )
35import Rel8.Schema.Null ( Sql )
36import Rel8.Schema.Result ( Result( R ) )
37import Rel8.Schema.Spec ( Spec( Spec ) )
38import Rel8.Table
39 ( Table, Columns, Context, fromColumns, toColumns
40 , FromExprs, fromResult, toResult
41 )
42import Rel8.Table.Recontextualize ( Recontextualize )
43import Rel8.Type ( DBType )
46-- | An @Aggregate a@ describes how to aggregate @Table@s of type @a@. You can
47-- unpack an @Aggregate@ back to @a@ by running it with 'Rel8.aggregate'. As
48-- @Aggregate@ is almost an 'Applicative' functor - but there is no 'pure'
49-- operation. This means 'Aggregate' is an instance of 'Apply', and you can
50-- combine @Aggregate@s using the @<.>@ combinator.
51type Aggregate :: k -> Type
52data Aggregate a where
53 Aggregate :: forall (a :: Type). !(Opaleye.Aggregator () (Expr a)) -> Aggregate a
54 A :: { unA :: !(Aggregate a) } -> Aggregate ('Spec a)
57instance Sql DBType a => Table Aggregate (Aggregate a) where
58 type Columns (Aggregate a) = HType a
59 type Context (Aggregate a) = Aggregate
60 type FromExprs (Aggregate a) = a
62 toColumns = HIdentity . A
63 fromColumns (HIdentity (A a)) = a
64 toResult = HIdentity . R
65 fromResult (HIdentity (R a)) = a
68instance Sql DBType a =>
69 Recontextualize Aggregate Aggregate (Aggregate a) (Aggregate a)
72instance Sql DBType a =>
73 Recontextualize Aggregate Expr (Aggregate a) (Expr a)
76instance Sql DBType a =>
77 Recontextualize Aggregate Name (Aggregate a) (Name a)
80instance Sql DBType a =>
81 Recontextualize Expr Aggregate (Expr a) (Aggregate a)
84instance Sql DBType a =>
85 Recontextualize Name Aggregate (Name a) (Aggregate a)
88-- | @Aggregates a b@ means that the columns in @a@ are all 'Aggregate' 'Expr's
89-- for the columns in @b@.
90type Aggregates :: Type -> Type -> Constraint
91class Recontextualize Aggregate Expr aggregates exprs => Aggregates aggregates exprs
92instance Recontextualize Aggregate Expr aggregates exprs => Aggregates aggregates exprs
95foldInputs :: forall (a :: Type) (b :: Type). Monoid b
96 => (Maybe Aggregator -> Opaleye.PrimExpr -> b) -> Aggregate a -> b
97foldInputs f (Aggregate (Opaleye.Aggregator (Opaleye.PackMap agg))) =
98 getConst $ flip agg () $ \(aggregator, a) ->
99 Const $ f (detuplize <$> aggregator) a
100 where
101 detuplize (operation, ordering, distinction) =
102 Aggregator {operation, ordering, distinction}
105mapInputs :: forall (a :: Type). ()
106 => (Opaleye.PrimExpr -> Opaleye.PrimExpr) -> Aggregate a -> Aggregate a
107mapInputs transform (Aggregate (Opaleye.Aggregator (Opaleye.PackMap agg))) =
108 Aggregate $ Opaleye.Aggregator $ Opaleye.PackMap $ agg . \f input ->
109 f (fmap transform input)
112type Aggregator :: Type
113data Aggregator = Aggregator
114 { operation :: Opaleye.AggrOp
115 , ordering :: [Opaleye.OrderExpr]
116 , distinction :: Opaleye.AggrDistinct
117 }
120unsafeMakeAggregate :: forall (input :: Type) (output :: Type). ()
121 => (Expr input -> Opaleye.PrimExpr)
122 -> (Opaleye.PrimExpr -> Expr output)
123 -> Maybe Aggregator
124 -> Expr input
125 -> Aggregate output
126unsafeMakeAggregate input output aggregator expr =
127 Aggregate $ Opaleye.Aggregator $ Opaleye.PackMap $ \f _ ->
128 output <$> f (tuplize <$> aggregator, input expr)
129 where
130 tuplize Aggregator {operation, ordering, distinction} =
131 (operation, ordering, distinction)