packages feed

rel8-1.6.0.0: src/Rel8/Table/Aggregate/Maybe.hs

{-# language FlexibleContexts #-}

module Rel8.Table.Aggregate.Maybe
  ( filterWhereOptional
  , optionalAggregate
  , aggregateJustTable
  , aggregateJustTable1
  , aggregateMaybeTable
  )
where

-- base
import Prelude

-- opaleye
import qualified Opaleye.Internal.Aggregate as Opaleye

-- profunctors
import Data.Profunctor (lmap)

-- rel8
import Rel8.Aggregate
  ( Aggregator' (Aggregator)
  , Aggregator, toAggregator
  , Aggregator1, toAggregator1
  )
import Rel8.Aggregate.Fold (Fallback (Fallback))
import Rel8.Expr (Expr)
import Rel8.Expr.Aggregate (groupByExprOn)
import Rel8.Expr.Opaleye (toColumn, toPrimExpr)
import Rel8.Table (Table)
import Rel8.Table.Aggregate (filterWhere)
import Rel8.Table.Maybe
  ( MaybeTable (MaybeTable, just, tag), justTable, nothingTable
  , isJustTable
  , makeMaybeTable
  )
import Rel8.Table.Nullify (aggregateNullify, unsafeUnnullifyTable)


-- | A variant of 'filterWhere' that can be used with an 'Aggregator1'
-- (upgrading it to an 'Aggregator' in the process). It returns
-- 'nothingTable' in the case where the predicate matches zero rows.
filterWhereOptional :: Table Expr a
  => (i -> Expr Bool) -> Aggregator' fold i a -> Aggregator' fold' i (MaybeTable Expr a)
filterWhereOptional f (Aggregator _ aggregator) =
  Aggregator (Fallback nothingTable) $
    Opaleye.filterWhereInternal makeMaybeTable (toColumn . toPrimExpr . f) aggregator


-- | 'optionalAggregate' upgrades an 'Aggregator1' into an 'Aggregator' by
-- having it return 'nothingTable' when aggregating over an empty collection
-- of rows.
optionalAggregate :: Table Expr a
  => Aggregator' fold i a -> Aggregator' fold' i (MaybeTable Expr a)
optionalAggregate = toAggregator nothingTable . fmap justTable


-- | Lift an 'Aggregator' to operate on a 'MaybeTable'. If the input query has
-- @'justTable' i@s, they are folded into a single @a@ by the given aggregator
-- — in the case where the input query is all 'nothingTable's, the
-- 'Aggregator'\'s fallback @a@ is returned.
aggregateJustTable :: Table Expr a
  => Aggregator i a
  -> Aggregator' fold (MaybeTable Expr i) a
aggregateJustTable =
  filterWhere isJustTable . lmap (unsafeUnnullifyTable . just)


-- | Lift an 'Aggregator1' to operate on a 'MaybeTable'. If the input query
-- has @'justTable' i@s, they are folded into a single @'justTable' a@ by the
-- given aggregator — in the case where the input query is all
-- 'nothingTable's, a single 'nothingTable' row is returned.
aggregateJustTable1 :: Table Expr a
  => Aggregator' fold i a
  -> Aggregator' fold' (MaybeTable Expr i) (MaybeTable Expr a)
aggregateJustTable1 =
  filterWhereOptional isJustTable . lmap (unsafeUnnullifyTable . just)


-- | Lift an aggregator to operate on a 'MaybeTable'. @nothingTable@s and
-- @justTable@s are grouped separately.
aggregateMaybeTable :: ()
  => Aggregator' fold i a
  -> Aggregator1 (MaybeTable Expr i) (MaybeTable Expr a)
aggregateMaybeTable aggregator =
  MaybeTable
    <$> groupByExprOn tag
    <*> lmap just (toAggregator1 (aggregateNullify aggregator))