diff --git a/changelog.d/20240527_022306_shane.obrien_aggregateJustTable.md b/changelog.d/20240527_022306_shane.obrien_aggregateJustTable.md new file mode 100644 index 00000000..2a10e12b --- /dev/null +++ b/changelog.d/20240527_022306_shane.obrien_aggregateJustTable.md @@ -0,0 +1,5 @@ +### Added + +- Add `aggregateJustTable`, `aggregateJustTable` aggregator functions. These provide another way to do aggregation of `MaybeTable`s than the existing `aggregateMaybeTable` function. +- Add `aggregateLeftTable`, `aggregateLeftTable1`, `aggregateRightTable` and `aggregateRightTable1` aggregator functions. These provide another way to do aggregation of `EitherTable`s than the existing `aggregateEitherTable` function. +- Add `aggregateThisTable`, `aggregateThisTable1`, `aggregateThatTable`, `aggregateThatTable1`, `aggregateThoseTable`, `aggregateThoseTable1`, `aggregateHereTable`, `aggregateHereTable1`, `aggregateThereTable` and `aggregateThereTable1` aggregation functions. These provide another way to do aggregation of `TheseTable`s than the existing `aggregateTheseTable` function. diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 433077b3..a30ecf98 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -357,7 +357,6 @@ focus on the /type/ of that query:: author <- each authorSchema project <- projectsForAuthor author return (author, project) - where >>> :t select conn authorsAndProjects select conn authorsAndProjects diff --git a/rel8.cabal b/rel8.cabal index 01a35052..731adba3 100644 --- a/rel8.cabal +++ b/rel8.cabal @@ -187,6 +187,7 @@ library Rel8.Table Rel8.Table.ADT Rel8.Table.Aggregate + Rel8.Table.Aggregate.Maybe Rel8.Table.Alternative Rel8.Table.Bool Rel8.Table.Cols diff --git a/src/Rel8.hs b/src/Rel8.hs index 20921c48..0d7f87d0 100644 --- a/src/Rel8.hs +++ b/src/Rel8.hs @@ -66,6 +66,7 @@ module Rel8 , optional , catMaybeTable , traverseMaybeTable + , aggregateJustTable, aggregateJustTable1 , aggregateMaybeTable , nameMaybeTable @@ -76,6 +77,8 @@ module Rel8 , keepLeftTable , keepRightTable , bitraverseEitherTable + , aggregateLeftTable, aggregateLeftTable1 + , aggregateRightTable, aggregateRightTable1 , aggregateEitherTable , nameEitherTable @@ -93,12 +96,17 @@ module Rel8 , keepThatTable, loseThatTable , keepThoseTable, loseThoseTable , bitraverseTheseTable + , aggregateThisTable, aggregateThisTable1 + , aggregateThatTable, aggregateThatTable1 + , aggregateThoseTable, aggregateThoseTable1 + , aggregateHereTable, aggregateHereTable1 + , aggregateThereTable, aggregateThereTable1 , aggregateTheseTable , nameTheseTable -- ** @ListTable@ , ListTable - , listTable, ($*) + , listOf, listTable, ($*) , nameListTable , many , manyExpr @@ -107,7 +115,7 @@ module Rel8 -- ** @NonEmptyTable@ , NonEmptyTable - , nonEmptyTable, ($+) + , nonEmptyOf, nonEmptyTable, ($+) , nameNonEmptyTable , some , someExpr @@ -396,6 +404,7 @@ import Rel8.Column.Null import Rel8.Column.These import Rel8.Expr import Rel8.Expr.Aggregate +import Rel8.Expr.Array import Rel8.Expr.Bool import Rel8.Expr.Default import Rel8.Expr.Eq @@ -453,6 +462,7 @@ import Rel8.Statement.View import Rel8.Table import Rel8.Table.ADT import Rel8.Table.Aggregate +import Rel8.Table.Aggregate.Maybe import Rel8.Table.Alternative import Rel8.Table.Bool import Rel8.Table.Either diff --git a/src/Rel8/Query/Materialize.hs b/src/Rel8/Query/Materialize.hs index 64502711..a091c2f2 100644 --- a/src/Rel8/Query/Materialize.hs +++ b/src/Rel8/Query/Materialize.hs @@ -15,7 +15,6 @@ import Opaleye.With ( withMaterializedExplicit ) import Rel8.Expr ( Expr ) import Rel8.Query ( Query ) import Rel8.Query.Opaleye ( fromOpaleye, toOpaleye ) -import Rel8.Query.Rebind ( rebind ) import Rel8.Table ( Table ) import Rel8.Table.Opaleye ( unpackspec ) @@ -31,10 +30,9 @@ import Rel8.Table.Opaleye ( unpackspec ) -- specifically the @WITH _ AS MATERIALIZED (_)@ form introduced in PostgreSQL -- 12. This means that 'materialize' can only be used with PostgreSQL 12 or -- newer. -materialize :: (Table Expr a, Table Expr b) - => Query a -> (Query a -> Query b) -> Query b +materialize :: Table Expr a => Query a -> (Query a -> Query b) -> Query b materialize query f = - (>>= rebind "with") . fromOpaleye $ + fromOpaleye $ withMaterializedExplicit unpackspec (toOpaleye query') (toOpaleye . f . fromOpaleye) diff --git a/src/Rel8/Statement.hs b/src/Rel8/Statement.hs index 91032953..2ce69c9e 100644 --- a/src/Rel8/Statement.hs +++ b/src/Rel8/Statement.hs @@ -132,7 +132,7 @@ data Returning where -- delete Delete -- { from = fooSchema -- , using = pure () --- , deleteWhere = \_ -> predicate +-- , deleteWhere = \\_ -> predicate -- , returning = Returning id -- } -- insert Insert diff --git a/src/Rel8/Table/Aggregate.hs b/src/Rel8/Table/Aggregate.hs index 8437d69a..a9bc14af 100644 --- a/src/Rel8/Table/Aggregate.hs +++ b/src/Rel8/Table/Aggregate.hs @@ -9,9 +9,8 @@ module Rel8.Table.Aggregate ( groupBy, groupByOn , listAgg, listAggOn, nonEmptyAgg, nonEmptyAggOn , listCat, listCatOn, nonEmptyCat, nonEmptyCatOn - , filterWhere, filterWhereOptional + , filterWhere , orderAggregateBy - , optionalAggregate ) where @@ -25,11 +24,7 @@ import qualified Opaleye.Internal.Aggregate as Opaleye import Data.Profunctor (dimap, lmap) -- rel8 -import Rel8.Aggregate - ( Aggregator, Aggregator' (Aggregator), Aggregator1 - , toAggregator - ) -import Rel8.Aggregate.Fold (Fallback (Fallback)) +import Rel8.Aggregate (Aggregator, Aggregator' (Aggregator), Aggregator1) import Rel8.Expr ( Expr ) import Rel8.Expr.Aggregate ( filterWhereExplicit @@ -39,7 +34,6 @@ import Rel8.Expr.Aggregate , snonEmptyAggExpr , snonEmptyCatExpr ) -import Rel8.Expr.Opaleye (toColumn, toPrimExpr) import Rel8.Order (Order (Order)) import Rel8.Schema.Dict ( Dict( Dict ) ) import Rel8.Schema.HTable (HTable, hfield, hspecs, htabulateA) @@ -49,7 +43,6 @@ import Rel8.Schema.Spec ( Spec( Spec, info ) ) import Rel8.Table (Table, toColumns, fromColumns) import Rel8.Table.Eq ( EqTable, eqTable ) import Rel8.Table.List ( ListTable ) -import Rel8.Table.Maybe (MaybeTable, makeMaybeTable, justTable, nothingTable) import Rel8.Table.NonEmpty ( NonEmptyTable ) import Rel8.Table.Opaleye (ifPP) import Rel8.Type.Eq ( DBEq ) @@ -91,22 +84,13 @@ hgroupBy eqs = htabulateA $ \field -> case hfield eqs field of -- predicate supplied to 'filterWhere' could return 'Rel8.false' for every -- row, 'filterWhere' needs an 'Aggregator' as opposed to an 'Aggregator1', so -- that it can return a default value in such a case. For a variant of --- 'filterWhere' that can work with 'Aggregator1's, see 'filterWhereOptional'. +-- 'filterWhere' that can work with 'Aggregator1's, see +-- 'Rel8.filterWhereOptional'. filterWhere :: Table Expr a => (i -> Expr Bool) -> Aggregator i a -> Aggregator' fold i a filterWhere = filterWhereExplicit ifPP --- | 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 - - -- | Aggregate rows into a single row containing an array of all aggregated -- rows. This can be used to associate multiple rows with a single row, without -- changing the over cardinality of the query. This allows you to essentially @@ -184,11 +168,3 @@ nonEmptyCatOn f = lmap f nonEmptyCat orderAggregateBy :: Order i -> Aggregator' fold i a -> Aggregator' fold i a orderAggregateBy (Order order) (Aggregator fallback aggregator) = Aggregator fallback $ Opaleye.orderAggregate order 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 diff --git a/src/Rel8/Table/Aggregate/Maybe.hs b/src/Rel8/Table/Aggregate/Maybe.hs new file mode 100644 index 00000000..aa2a2cc6 --- /dev/null +++ b/src/Rel8/Table/Aggregate/Maybe.hs @@ -0,0 +1,89 @@ +{-# 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)) diff --git a/src/Rel8/Table/Either.hs b/src/Rel8/Table/Either.hs index 3e5c24c2..88f8476f 100644 --- a/src/Rel8/Table/Either.hs +++ b/src/Rel8/Table/Either.hs @@ -1,3 +1,4 @@ +{-# language ApplicativeDo #-} {-# language DataKinds #-} {-# language DeriveFunctor #-} {-# language DerivingStrategies #-} @@ -19,6 +20,8 @@ module Rel8.Table.Either ( EitherTable(..) , eitherTable, leftTable, rightTable , isLeftTable, isRightTable + , aggregateLeftTable, aggregateLeftTable1 + , aggregateRightTable, aggregateRightTable1 , aggregateEitherTable , nameEitherTable ) @@ -37,7 +40,7 @@ import Control.Comonad ( extract ) import Data.Profunctor (lmap) -- rel8 -import Rel8.Aggregate (Aggregator', Aggregator1, toAggregator1) +import Rel8.Aggregate (Aggregator, Aggregator', Aggregator1, toAggregator1) import Rel8.Expr ( Expr ) import Rel8.Expr.Aggregate (groupByExprOn) import Rel8.Expr.Serialize ( litExpr ) @@ -54,9 +57,14 @@ import Rel8.Table , FromExprs, fromResult, toResult , Transpose ) +import Rel8.Table.Aggregate (filterWhere) +import Rel8.Table.Aggregate.Maybe (filterWhereOptional) import Rel8.Table.Bool ( bool ) import Rel8.Table.Eq ( EqTable, eqTable ) -import Rel8.Table.Nullify ( Nullify, aggregateNullify, guard ) +import Rel8.Table.Maybe (MaybeTable) +import Rel8.Table.Nullify + ( Nullify, aggregateNullify, guard, unsafeUnnullifyTable + ) import Rel8.Table.Ord ( OrdTable, ordTable ) import Rel8.Table.Projection ( Biprojectable, Projectable, biproject, project ) import Rel8.Table.Serialize ( ToExprs ) @@ -218,6 +226,50 @@ rightTable :: Table Expr a => b -> EitherTable Expr a b rightTable = EitherTable (litExpr IsRight) undefined . pure +-- | Lift an 'Aggregator' to operate on an 'EitherTable'. If the input query has +-- @'leftTable' a@s, they are folded into a single @c@ by the given aggregator +-- — in the case where the input query is all 'rightTable's, the +-- 'Aggregator'\'s fallback @c@ is returned. +aggregateLeftTable :: Table Expr c + => Aggregator a c + -> Aggregator' fold (EitherTable Expr a b) c +aggregateLeftTable = + filterWhere isLeftTable . lmap (unsafeUnnullifyTable . left) + + +-- | Lift an 'Aggregator1' to operate on an 'EitherTable'. If the input query +-- has @'leftTable' a@s, they are folded into a single @'Rel8.justTable' c@ +-- by the given aggregator — in the case where the input query is all +-- 'rightTable's, a single 'nothingTable' row is returned. +aggregateLeftTable1 :: Table Expr c + => Aggregator' fold a c + -> Aggregator' fold' (EitherTable Expr a b) (MaybeTable Expr c) +aggregateLeftTable1 = + filterWhereOptional isLeftTable . lmap (unsafeUnnullifyTable . left) + + +-- | Lift an 'Aggregator' to operate on an 'EitherTable'. If the input query has +-- @'rightTable' b@s, they are folded into a single @c@ by the given aggregator +-- — in the case where the input query is all 'rightTable's, the +-- 'Aggregator'\'s fallback @c@ is returned. +aggregateRightTable :: Table Expr c + => Aggregator b c + -> Aggregator' fold (EitherTable Expr a b) c +aggregateRightTable = + filterWhere isRightTable . lmap (unsafeUnnullifyTable . right) + + +-- | Lift an 'Aggregator1' to operate on an 'EitherTable'. If the input query +-- has @'rightTable' b@s, they are folded into a single @'Rel8.justTable' c@ +-- by the given aggregator — in the case where the input query is all +-- 'leftTable's, a single 'nothingTable' row is returned. +aggregateRightTable1 :: Table Expr c + => Aggregator' fold a c + -> Aggregator' fold' (EitherTable Expr a b) (MaybeTable Expr c) +aggregateRightTable1 = + filterWhereOptional isLeftTable . lmap (unsafeUnnullifyTable . left) + + -- | Lift a pair aggregators to operate on an 'EitherTable'. @leftTable@s and -- @rightTable@s are grouped separately. aggregateEitherTable :: () diff --git a/src/Rel8/Table/Maybe.hs b/src/Rel8/Table/Maybe.hs index 90f6e862..2b344289 100644 --- a/src/Rel8/Table/Maybe.hs +++ b/src/Rel8/Table/Maybe.hs @@ -18,9 +18,9 @@ module Rel8.Table.Maybe , isNothingTable, isJustTable , fromMaybeTable , ($?) - , aggregateMaybeTable , nameMaybeTable , makeMaybeTable + , unsafeFromJustTable ) where @@ -38,13 +38,8 @@ import Control.Comonad ( extract ) import qualified Opaleye.Field as Opaleye import qualified Opaleye.SqlTypes as Opaleye --- profunctors -import Data.Profunctor (lmap) - -- rel8 -import Rel8.Aggregate (Aggregator', Aggregator1, toAggregator1) import Rel8.Expr ( Expr ) -import Rel8.Expr.Aggregate (groupByExprOn) import Rel8.Expr.Bool ( boolExpr ) import Rel8.Expr.Null ( isNull, isNonNull, null, nullify ) import Rel8.Expr.Opaleye (fromColumn, fromPrimExpr) @@ -70,7 +65,7 @@ import Rel8.Table.Bool ( bool ) import Rel8.Table.Eq ( EqTable, eqTable ) import Rel8.Table.Ord ( OrdTable, ordTable ) import Rel8.Table.Projection ( Projectable, project ) -import Rel8.Table.Nullify ( Nullify, aggregateNullify, guard ) +import Rel8.Table.Nullify (Nullify, guard, unsafeUnnullifyTable) import Rel8.Table.Serialize ( ToExprs ) import Rel8.Table.Undefined ( undefined ) import Rel8.Type ( DBType ) @@ -222,6 +217,10 @@ fromMaybeTable :: Table Expr a => a -> MaybeTable Expr a -> a fromMaybeTable fallback = maybeTable fallback id +unsafeFromJustTable :: MaybeTable Expr a -> a +unsafeFromJustTable (MaybeTable _ just) = unsafeUnnullifyTable just + + -- | Project a single expression out of a 'MaybeTable'. You can think of this -- operator like the '$' operator, but it also has the ability to return -- @null@. @@ -233,17 +232,6 @@ f $? ma@(MaybeTable _ a) = case nullable @b of infixl 4 $? --- | 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)) - - -- | Construct a 'MaybeTable' in the 'Name' context. This can be useful if you -- have a 'MaybeTable' that you are storing in a table and need to construct a -- 'TableSchema'. diff --git a/src/Rel8/Table/Nullify.hs b/src/Rel8/Table/Nullify.hs index c86738d1..799bec0d 100644 --- a/src/Rel8/Table/Nullify.hs +++ b/src/Rel8/Table/Nullify.hs @@ -16,6 +16,7 @@ module Rel8.Table.Nullify , aggregateNullify , guard , isNull + , unsafeUnnullifyTable ) where @@ -182,9 +183,7 @@ aggregateNullify :: () -> Aggregator' fold (Nullify Expr i) (Nullify Expr a) aggregateNullify = dimap from to where - from = \case - Table _ a -> a - Fields notNullifiable _ -> absurd NExpr notNullifiable + from = unsafeUnnullifyTable to = Table NExpr @@ -208,6 +207,12 @@ isNull = toColumns +unsafeUnnullifyTable :: Nullify Expr a -> a +unsafeUnnullifyTable = \case + Table _ a -> a + Fields notNullifiable _ -> absurd NExpr notNullifiable + + newtype Any = Any { getAny :: Expr Bool } diff --git a/src/Rel8/Table/These.hs b/src/Rel8/Table/These.hs index 4c2bd2bd..9809f2b8 100644 --- a/src/Rel8/Table/These.hs +++ b/src/Rel8/Table/These.hs @@ -22,12 +22,18 @@ module Rel8.Table.These , hasHereTable, hasThereTable , justHereTable, justThereTable , alignMaybeTable + , aggregateThisTable, aggregateThisTable1 + , aggregateThatTable, aggregateThatTable1 + , aggregateThoseTable, aggregateThoseTable1 + , aggregateHereTable, aggregateHereTable1 + , aggregateThereTable, aggregateThereTable1 , aggregateTheseTable , nameTheseTable ) where -- base +import Control.Arrow ((&&&)) import Data.Bifunctor ( Bifunctor, bimap ) import Data.Kind ( Type ) import Data.Maybe ( isJust ) @@ -37,7 +43,7 @@ import Prelude hiding ( null, undefined ) import Data.Profunctor (lmap) -- rel8 -import Rel8.Aggregate (Aggregator', Aggregator1) +import Rel8.Aggregate (Aggregator, Aggregator', Aggregator1) import Rel8.Expr ( Expr ) import Rel8.Expr.Bool ( (&&.), (||.), boolExpr, not_ ) import Rel8.Expr.Null ( null, isNonNull ) @@ -55,13 +61,19 @@ import Rel8.Table , FromExprs, fromResult, toResult , Transpose ) +import Rel8.Table.Aggregate (filterWhere) +import Rel8.Table.Aggregate.Maybe + ( aggregateJustTable, aggregateJustTable1 + , aggregateMaybeTable + , filterWhereOptional + ) import Rel8.Table.Eq ( EqTable, eqTable ) import Rel8.Table.Maybe ( MaybeTable(..) , maybeTable, justTable, nothingTable , isJustTable - , aggregateMaybeTable , nameMaybeTable + , unsafeFromJustTable ) import Rel8.Table.Nullify ( Nullify, guard ) import Rel8.Table.Ord ( OrdTable, ordTable ) @@ -328,8 +340,118 @@ theseTable f g h TheseTable {here, there} = there --- | Lift a pair aggregators to operate on a 'TheseTable'. @thisTable@s, --- @thatTable@s are @thoseTable@s are grouped separately. +-- | Lift an 'Aggregator' to operate on a 'TheseTable'. If the input query has +-- @'thisTable' a@s, they are folded into a single @c@ by the given aggregator +-- — in the case where the input query is all 'thatTable's or 'thoseTable's, +-- the 'Aggregator'\'s fallback @c@ is returned. +aggregateThisTable :: Table Expr c + => Aggregator a c + -> Aggregator' fold (TheseTable Expr a b) c +aggregateThisTable = + filterWhere isThisTable . lmap (unsafeFromJustTable . here) + + +-- | Lift an 'Aggregator1' to operate on a 'TheseTable'. If the input query +-- has @'thisTable' a@s, they are folded into a single @'Rel8.justTable' c@ +-- by the given aggregator — in the case where the input query is all +-- 'thatTable's or 'thoseTable's, a single 'nothingTable' row is returned. +aggregateThisTable1 :: Table Expr c + => Aggregator' fold a c + -> Aggregator' fold' (TheseTable Expr a b) (MaybeTable Expr c) +aggregateThisTable1 = + filterWhereOptional isThisTable . lmap (unsafeFromJustTable . here) + + +-- | Lift an 'Aggregator' to operate on a 'TheseTable'. If the input query has +-- @'thatTable' b@s, they are folded into a single @c@ by the given aggregator +-- — in the case where the input query is all 'thisTable's or 'thoseTable's, +-- the 'Aggregator'\'s fallback @c@ is returned. +aggregateThatTable :: Table Expr c + => Aggregator b c + -> Aggregator' fold (TheseTable Expr a b) c +aggregateThatTable = + filterWhere isThatTable . lmap (unsafeFromJustTable . there) + + +-- | Lift an 'Aggregator1' to operate on a 'TheseTable'. If the input query +-- has @'thatTable' b@s, they are folded into a single @'Rel8.justTable' c@ +-- by the given aggregator — in the case where the input query is all +-- 'thisTable's or 'thoseTable's, a single 'nothingTable' row is returned. +aggregateThatTable1 :: Table Expr c + => Aggregator' fold b c + -> Aggregator' fold' (TheseTable Expr a b) (MaybeTable Expr c) +aggregateThatTable1 = + filterWhereOptional isThatTable . lmap (unsafeFromJustTable . there) + + +-- | Lift an 'Aggregator' to operate on a 'ThoseTable'. If the input query has +-- @'thoseTable' a b@s, they are folded into a single @c@ by the given +-- aggregator — in the case where the input query is all 'thisTable's or +-- 'thatTable's, the 'Aggregator'\'s fallback @c@ is returned. +aggregateThoseTable :: Table Expr c + => Aggregator (a, b) c + -> Aggregator' fold (TheseTable Expr a b) c +aggregateThoseTable = + filterWhere isThoseTable + . lmap (unsafeFromJustTable . here &&& unsafeFromJustTable . there) + + +-- | Lift an 'Aggregator1' to operate on a 'TheseTable'. If the input query +-- has @'thoseTable' a b@s, they are folded into a single @'Rel8.justTable' c@ +-- by the given aggregator — in the case where the input query is all +-- 'thisTable's or 'thatTable's, a single 'nothingTable' row is returned. +aggregateThoseTable1 :: Table Expr c + => Aggregator' fold (a, b) c + -> Aggregator' fold' (TheseTable Expr a b) (MaybeTable Expr c) +aggregateThoseTable1 = + filterWhereOptional isThoseTable + . lmap (unsafeFromJustTable . here &&& unsafeFromJustTable . there) + + +-- | Lift an 'Aggregator' to operate on a 'TheseTable'. If the input query has +-- @'thisTable' a@s or @'thoseTable' a _@s, the @a@s are folded into a single +-- @c@ by the given aggregator — in the case where the input query is all +-- 'thatTable's, the 'Aggregator'\'s fallback @c@ is returned. +aggregateHereTable :: Table Expr c + => Aggregator a c + -> Aggregator' fold (TheseTable Expr a b) c +aggregateHereTable = lmap here . aggregateJustTable + + +-- | Lift an 'Aggregator1' to operate on an 'TheseTable'. If the input query +-- has @'thisTable' a@s or @'thoseTable' a _@s, the @a@s are folded into a +-- single @'Rel8.justTable' c@ by the given aggregator — in the case where +-- the input query is all 'thatTable's, a single 'nothingTable' row is +-- returned. +aggregateHereTable1 :: Table Expr c + => Aggregator' fold a c + -> Aggregator' fold' (TheseTable Expr a b) (MaybeTable Expr c) +aggregateHereTable1 = lmap here . aggregateJustTable1 + + +-- | Lift an 'Aggregator' to operate on a 'TheseTable'. If the input query has +-- @'thatTable' b@s or @'thoseTable' _ b@s, the @b@s are folded into a single +-- @c@ by the given aggregator — in the case where the input query is all +-- 'thisTable's, the 'Aggregator'\'s fallback @c@ is returned. +aggregateThereTable :: Table Expr c + => Aggregator b c + -> Aggregator' fold (TheseTable Expr a b) c +aggregateThereTable = lmap there . aggregateJustTable + + +-- | Lift an 'Aggregator1' to operate on an 'TheseTable'. If the input query +-- has @'thatTable' b@s or @'thoseTable' _ b@s, the @b@s are folded into a +-- single @'Rel8.justTable' c@ by the given aggregator — in the case where +-- the input query is all 'thisTable's, a single 'nothingTable' row is +-- returned. +aggregateThereTable1 :: Table Expr c + => Aggregator' fold b c + -> Aggregator' fold' (TheseTable Expr a b) (MaybeTable Expr c) +aggregateThereTable1 = lmap there . aggregateJustTable1 + + +-- | Lift a pair aggregators to operate on a 'TheseTable'. 'thisTable's, +-- 'thatTable's are 'thoseTable's are grouped separately. aggregateTheseTable :: () => Aggregator' fold i a -> Aggregator' fold' i' b diff --git a/src/Rel8/Tabulate.hs b/src/Rel8/Tabulate.hs index 9b93e33d..8ecc0fd5 100644 --- a/src/Rel8/Tabulate.hs +++ b/src/Rel8/Tabulate.hs @@ -527,7 +527,7 @@ alignWith f (Tabulation as) (Tabulation bs) = Tabulation $ \p -> do -- -- Note that you can achieve the same effect with 'optional' and the -- 'Applicative' instance for 'Tabulation', i.e., this is just --- @\left right -> liftA2 (,) left (optional right). You can also +-- @\\left right -> liftA2 (,) left (optional right)@. You can also -- use @do@-notation. leftAlign :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k (a, MaybeTable Expr b) @@ -540,7 +540,7 @@ leftAlign = leftAlignWith (,) -- -- Note that you can achieve the same effect with 'optional' and the -- 'Applicative' instance for 'Tabulation', i.e., this is just --- @\f left right -> liftA2 f left (optional right). You can also +-- @\\f left right -> liftA2 f left (optional right)@. You can also -- use @do@-notation. leftAlignWith :: EqTable k => (a -> MaybeTable Expr b -> c) @@ -554,7 +554,7 @@ leftAlignWith f left right = liftA2 f left (optional right) -- -- Note that you can achieve the same effect with 'optional' and the -- 'Applicative' instance for 'Tabulation', i.e., this is just --- @\left right -> liftA2 (flip (,)) right (optional left). You can +-- @\\left right -> liftA2 (flip (,)) right (optional left)@. You can -- also use @do@-notation. rightAlign :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k (MaybeTable Expr a, b) @@ -567,7 +567,7 @@ rightAlign = rightAlignWith (,) -- -- Note that you can achieve the same effect with 'optional' and the -- 'Applicative' instance for 'Tabulation', i.e., this is just --- @\f left right -> liftA2 (flip f) right (optional left). You can +-- @\\f left right -> liftA2 (flip f) right (optional left)@. You can -- also use @do@-notation. rightAlignWith :: EqTable k => (MaybeTable Expr a -> b -> c) @@ -607,7 +607,7 @@ zipWith = liftA2 -- -- Note that you can achieve a similar effect with 'present' and the -- 'Applicative' instance of 'Tabulation', i.e., this is just --- @\left right -> left <* present right@. You can also use +-- @\\left right -> left <* present right@. You can also use -- @do@-notation. similarity :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a similarity a b = a <* present b @@ -621,14 +621,14 @@ similarity a b = a <* present b -- -- Note that you can achieve a similar effect with 'absent' and the -- 'Applicative' instance of 'Tabulation', i.e., this is just --- @\left right -> left <* absent right@. You can also use +-- @\\left right -> left <* absent right@. You can also use -- @do@-notation. difference :: EqTable k => Tabulation k a -> Tabulation k b -> Tabulation k a difference a b = a <* absent b -- | 'Q.materialize' for 'Tabulation's. -materialize :: (Table Expr k, Table Expr a, Table Expr b) +materialize :: (Table Expr k, Table Expr a) => Tabulation k a -> (Tabulation k a -> Query b) -> Query b materialize tabulation f = case peek tabulation of Tabulation query -> do diff --git a/src/Rel8/Type/Num.hs b/src/Rel8/Type/Num.hs index 98c04e89..a1b58532 100644 --- a/src/Rel8/Type/Num.hs +++ b/src/Rel8/Type/Num.hs @@ -41,7 +41,7 @@ instance DBNum Scientific -- | The class of database types that can be coerced to from integral -- expressions. This is a Rel8 concept, and allows us to provide --- 'fromIntegral'. +-- 'Rel8.Expr.Num.fromIntegral'. type DBIntegral :: Type -> Constraint class (DBNum a, DBOrd a) => DBIntegral a instance DBIntegral Int16 diff --git a/src/Rel8/Window.hs b/src/Rel8/Window.hs index 45ea2af6..143f7bd8 100644 --- a/src/Rel8/Window.hs +++ b/src/Rel8/Window.hs @@ -72,9 +72,9 @@ newtype Partition a = Partition (Opaleye.Window a) -- | 'over' adds a 'Partition' to a 'Window' expression. -- --- @@@ --- 'Rel8.Table.Window.cumulative' ('Rel8.Expr.Aggregate.sum' . salary) `over` 'partitionBy' department <> 'orderPartitionBy' (salary >$< 'Rel8.desc') --- @@@ +-- @ +-- 'Rel8.Table.Window.cumulative' ('Rel8.Expr.Aggregate.sum' . salary) `over` 'partitionBy' department <> 'orderPartitionBy' (salary >$< 'Rel8.desc') +-- @ over :: Window a b -> Partition a -> Window a b over (Window (Opaleye.Windows (Opaleye.PackMap w))) (Partition p) = Window $ Opaleye.Windows $ Opaleye.PackMap $ \f ->