{-# language DataKinds #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language MultiParamTypeClasses #-}
{-# language NamedFieldPuns #-}
{-# language RankNTypes #-}
{-# language StandaloneKindSignatures #-}
{-# language TypeFamilies #-}
{-# language UndecidableInstances #-}

module Rel8.Aggregate
  ( Aggregate(..), foldInputs, mapInputs
  , Aggregator(..), unsafeMakeAggregate
  , Aggregates
  )
where

-- base
import Data.Functor.Const ( Const( Const ), getConst )
import Data.Functor.Identity ( Identity( Identity ) )
import Data.Kind ( Constraint, Type )
import Prelude

-- opaleye
import qualified Opaleye.Internal.Aggregate as Opaleye
import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
import qualified Opaleye.Internal.PackMap as Opaleye

-- rel8
import Rel8.Expr ( Expr )
import Rel8.Schema.HTable.Identity ( HIdentity(..) )
import qualified Rel8.Schema.Kind as K
import Rel8.Schema.Null ( Sql )
import Rel8.Table
  ( Table, Columns, Context, fromColumns, toColumns
  , FromExprs, fromResult, toResult
  , Transpose
  )
import Rel8.Table.Transpose ( Transposes )
import Rel8.Type ( DBType )


-- | An @Aggregate a@ describes how to aggregate @Table@s of type @a@. You can
-- unpack an @Aggregate@ back to @a@ by running it with 'Rel8.aggregate'. As
-- @Aggregate@ is almost an 'Applicative' functor - but there is no 'pure'
-- operation. This means 'Aggregate' is an instance of 'Apply', and you can
-- combine @Aggregate@s using the @<.>@ combinator.
type Aggregate :: K.Context
newtype Aggregate a = Aggregate (Opaleye.Aggregator () (Expr a))


instance Sql DBType a => Table Aggregate (Aggregate a) where
  type Columns (Aggregate a) = HIdentity a
  type Context (Aggregate a) = Aggregate
  type FromExprs (Aggregate a) = a
  type Transpose to (Aggregate a) = to a

  toColumns :: Aggregate a -> Columns (Aggregate a) Aggregate
toColumns = Aggregate a -> Columns (Aggregate a) Aggregate
forall a (context :: Context). context a -> HIdentity a context
HIdentity
  fromColumns :: Columns (Aggregate a) Aggregate -> Aggregate a
fromColumns (HIdentity a) = Aggregate a
a
  toResult :: FromExprs (Aggregate a) -> Columns (Aggregate a) Result
toResult = Identity a -> HIdentity a Result
forall a (context :: Context). context a -> HIdentity a context
HIdentity (Identity a -> HIdentity a Result)
-> (a -> Identity a) -> a -> HIdentity a Result
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Identity a
forall a. a -> Identity a
Identity
  fromResult :: Columns (Aggregate a) Result -> FromExprs (Aggregate a)
fromResult (HIdentity (Identity a)) = a
FromExprs (Aggregate a)
a


-- | @Aggregates a b@ means that the columns in @a@ are all 'Aggregate' 'Expr's
-- for the columns in @b@.
type Aggregates :: Type -> Type -> Constraint
class Transposes Aggregate Expr aggregates exprs => Aggregates aggregates exprs
instance Transposes Aggregate Expr aggregates exprs => Aggregates aggregates exprs


foldInputs :: forall (a :: Type) (b :: Type). Monoid b
  => (Maybe Aggregator -> Opaleye.PrimExpr -> b) -> Aggregate a -> b
foldInputs :: (Maybe Aggregator -> PrimExpr -> b) -> Aggregate a -> b
foldInputs Maybe Aggregator -> PrimExpr -> b
f (Aggregate (Opaleye.Aggregator (Opaleye.PackMap forall (f :: Context).
Applicative f =>
((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
 -> f PrimExpr)
-> () -> f (Expr a)
agg))) =
  Const b (Expr a) -> b
forall a k (b :: k). Const a b -> a
getConst (Const b (Expr a) -> b) -> Const b (Expr a) -> b
forall a b. (a -> b) -> a -> b
$ (((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
  -> Const b PrimExpr)
 -> () -> Const b (Expr a))
-> ()
-> ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
    -> Const b PrimExpr)
-> Const b (Expr a)
forall a b c. (a -> b -> c) -> b -> a -> c
flip ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
 -> Const b PrimExpr)
-> () -> Const b (Expr a)
forall (f :: Context).
Applicative f =>
((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
 -> f PrimExpr)
-> () -> f (Expr a)
agg () (((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
  -> Const b PrimExpr)
 -> Const b (Expr a))
-> ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
    -> Const b PrimExpr)
-> Const b (Expr a)
forall a b. (a -> b) -> a -> b
$ \(Maybe (AggrOp, [OrderExpr], AggrDistinct)
aggregator, PrimExpr
a) ->
    b -> Const b PrimExpr
forall k a (b :: k). a -> Const a b
Const (b -> Const b PrimExpr) -> b -> Const b PrimExpr
forall a b. (a -> b) -> a -> b
$ Maybe Aggregator -> PrimExpr -> b
f ((AggrOp, [OrderExpr], AggrDistinct) -> Aggregator
detuplize ((AggrOp, [OrderExpr], AggrDistinct) -> Aggregator)
-> Maybe (AggrOp, [OrderExpr], AggrDistinct) -> Maybe Aggregator
forall (f :: Context) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe (AggrOp, [OrderExpr], AggrDistinct)
aggregator) PrimExpr
a
  where
    detuplize :: (AggrOp, [OrderExpr], AggrDistinct) -> Aggregator
detuplize (AggrOp
operation, [OrderExpr]
ordering, AggrDistinct
distinction) =
      Aggregator :: AggrOp -> [OrderExpr] -> AggrDistinct -> Aggregator
Aggregator {AggrOp
operation :: AggrOp
operation :: AggrOp
operation, [OrderExpr]
ordering :: [OrderExpr]
ordering :: [OrderExpr]
ordering, AggrDistinct
distinction :: AggrDistinct
distinction :: AggrDistinct
distinction}


mapInputs :: forall (a :: Type). ()
  => (Opaleye.PrimExpr -> Opaleye.PrimExpr) -> Aggregate a -> Aggregate a
mapInputs :: (PrimExpr -> PrimExpr) -> Aggregate a -> Aggregate a
mapInputs PrimExpr -> PrimExpr
transform (Aggregate (Opaleye.Aggregator (Opaleye.PackMap forall (f :: Context).
Applicative f =>
((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
 -> f PrimExpr)
-> () -> f (Expr a)
agg))) =
  Aggregator () (Expr a) -> Aggregate a
forall a. Aggregator () (Expr a) -> Aggregate a
Aggregate (Aggregator () (Expr a) -> Aggregate a)
-> Aggregator () (Expr a) -> Aggregate a
forall a b. (a -> b) -> a -> b
$ PackMap
  (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
  PrimExpr
  ()
  (Expr a)
-> Aggregator () (Expr a)
forall a b.
PackMap
  (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr) PrimExpr a b
-> Aggregator a b
Opaleye.Aggregator (PackMap
   (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
   PrimExpr
   ()
   (Expr a)
 -> Aggregator () (Expr a))
-> PackMap
     (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
     PrimExpr
     ()
     (Expr a)
-> Aggregator () (Expr a)
forall a b. (a -> b) -> a -> b
$ (forall (f :: Context).
 Applicative f =>
 ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
  -> f PrimExpr)
 -> () -> f (Expr a))
-> PackMap
     (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
     PrimExpr
     ()
     (Expr a)
forall a b s t.
(forall (f :: Context). Applicative f => (a -> f b) -> s -> f t)
-> PackMap a b s t
Opaleye.PackMap ((forall (f :: Context).
  Applicative f =>
  ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
   -> f PrimExpr)
  -> () -> f (Expr a))
 -> PackMap
      (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
      PrimExpr
      ()
      (Expr a))
-> (forall (f :: Context).
    Applicative f =>
    ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
     -> f PrimExpr)
    -> () -> f (Expr a))
-> PackMap
     (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
     PrimExpr
     ()
     (Expr a)
forall a b. (a -> b) -> a -> b
$ ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
 -> f PrimExpr)
-> () -> f (Expr a)
forall (f :: Context).
Applicative f =>
((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
 -> f PrimExpr)
-> () -> f (Expr a)
agg (((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
  -> f PrimExpr)
 -> () -> f (Expr a))
-> (((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
     -> f PrimExpr)
    -> (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
    -> f PrimExpr)
-> ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
    -> f PrimExpr)
-> ()
-> f (Expr a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. \(Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr) -> f PrimExpr
f (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
input ->
    (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr) -> f PrimExpr
f ((PrimExpr -> PrimExpr)
-> (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
-> (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
forall (f :: Context) a b. Functor f => (a -> b) -> f a -> f b
fmap PrimExpr -> PrimExpr
transform (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
input)


type Aggregator :: Type
data Aggregator = Aggregator
  { Aggregator -> AggrOp
operation :: Opaleye.AggrOp
  , Aggregator -> [OrderExpr]
ordering :: [Opaleye.OrderExpr]
  , Aggregator -> AggrDistinct
distinction :: Opaleye.AggrDistinct
  }


unsafeMakeAggregate :: forall (input :: Type) (output :: Type). ()
  => (Expr input -> Opaleye.PrimExpr)
  -> (Opaleye.PrimExpr -> Expr output)
  -> Maybe Aggregator
  -> Expr input
  -> Aggregate output
unsafeMakeAggregate :: (Expr input -> PrimExpr)
-> (PrimExpr -> Expr output)
-> Maybe Aggregator
-> Expr input
-> Aggregate output
unsafeMakeAggregate Expr input -> PrimExpr
input PrimExpr -> Expr output
output Maybe Aggregator
aggregator Expr input
expr =
  Aggregator () (Expr output) -> Aggregate output
forall a. Aggregator () (Expr a) -> Aggregate a
Aggregate (Aggregator () (Expr output) -> Aggregate output)
-> Aggregator () (Expr output) -> Aggregate output
forall a b. (a -> b) -> a -> b
$ PackMap
  (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
  PrimExpr
  ()
  (Expr output)
-> Aggregator () (Expr output)
forall a b.
PackMap
  (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr) PrimExpr a b
-> Aggregator a b
Opaleye.Aggregator (PackMap
   (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
   PrimExpr
   ()
   (Expr output)
 -> Aggregator () (Expr output))
-> PackMap
     (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
     PrimExpr
     ()
     (Expr output)
-> Aggregator () (Expr output)
forall a b. (a -> b) -> a -> b
$ (forall (f :: Context).
 Applicative f =>
 ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
  -> f PrimExpr)
 -> () -> f (Expr output))
-> PackMap
     (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
     PrimExpr
     ()
     (Expr output)
forall a b s t.
(forall (f :: Context). Applicative f => (a -> f b) -> s -> f t)
-> PackMap a b s t
Opaleye.PackMap ((forall (f :: Context).
  Applicative f =>
  ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
   -> f PrimExpr)
  -> () -> f (Expr output))
 -> PackMap
      (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
      PrimExpr
      ()
      (Expr output))
-> (forall (f :: Context).
    Applicative f =>
    ((Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
     -> f PrimExpr)
    -> () -> f (Expr output))
-> PackMap
     (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr)
     PrimExpr
     ()
     (Expr output)
forall a b. (a -> b) -> a -> b
$ \(Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr) -> f PrimExpr
f ()
_ ->
    PrimExpr -> Expr output
output (PrimExpr -> Expr output) -> f PrimExpr -> f (Expr output)
forall (f :: Context) a b. Functor f => (a -> b) -> f a -> f b
<$> (Maybe (AggrOp, [OrderExpr], AggrDistinct), PrimExpr) -> f PrimExpr
f (Aggregator -> (AggrOp, [OrderExpr], AggrDistinct)
tuplize (Aggregator -> (AggrOp, [OrderExpr], AggrDistinct))
-> Maybe Aggregator -> Maybe (AggrOp, [OrderExpr], AggrDistinct)
forall (f :: Context) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe Aggregator
aggregator, Expr input -> PrimExpr
input Expr input
expr)
  where
    tuplize :: Aggregator -> (AggrOp, [OrderExpr], AggrDistinct)
tuplize Aggregator {AggrOp
operation :: AggrOp
operation :: Aggregator -> AggrOp
operation, [OrderExpr]
ordering :: [OrderExpr]
ordering :: Aggregator -> [OrderExpr]
ordering, AggrDistinct
distinction :: AggrDistinct
distinction :: Aggregator -> AggrDistinct
distinction} =
      (AggrOp
operation, [OrderExpr]
ordering, AggrDistinct
distinction)