-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
df1a271
commit b0039f6
Showing
8 changed files
with
263 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
module Rel8.Expr.Window | ||
( cumulative | ||
, rowNumber | ||
, rank | ||
, denseRank | ||
, percentRank | ||
, cumeDist | ||
, ntile | ||
, lag | ||
, lead | ||
, firstValue | ||
, lastValue | ||
, nthValue | ||
) | ||
where | ||
|
||
-- base | ||
import Data.Int ( Int32, Int64 ) | ||
import Prelude | ||
|
||
-- opaleye | ||
import qualified Opaleye.Window as Opaleye | ||
|
||
-- rel8 | ||
import Rel8.Aggregate ( Aggregate( Aggregate ) ) | ||
import Rel8.Expr ( Expr ) | ||
import Rel8.Expr.Opaleye ( fromColumn, fromPrimExpr, toColumn, toPrimExpr ) | ||
import Rel8.Schema.Null ( Nullify ) | ||
import Rel8.Window ( Window( Window ) ) | ||
|
||
|
||
cumulative :: Aggregate a -> Window (Expr a) | ||
cumulative (Aggregate aggregator) = Window $ Opaleye.cumulative aggregator () | ||
|
||
|
||
-- | [@row_number()@](https://www.postgresql.org/docs/current/functions-window.html) | ||
rowNumber :: Window (Expr Int64) | ||
rowNumber = Window $ fromPrimExpr . fromColumn <$> Opaleye.rowNumber | ||
|
||
|
||
-- | [@rank()@](https://www.postgresql.org/docs/current/functions-window.html) | ||
rank :: Window (Expr Int64) | ||
rank = Window $ fromPrimExpr . fromColumn <$> Opaleye.rank | ||
|
||
|
||
-- | [@dense_rank()@](https://www.postgresql.org/docs/current/functions-window.html) | ||
denseRank :: Window (Expr Int64) | ||
denseRank = Window $ fromPrimExpr . fromColumn <$> Opaleye.denseRank | ||
|
||
|
||
-- | [@percent_rank()@](https://www.postgresql.org/docs/current/functions-window.html) | ||
percentRank :: Window (Expr Double) | ||
percentRank = Window $ fromPrimExpr . fromColumn <$> Opaleye.percentRank | ||
|
||
|
||
-- | [@cume_dist()@](https://www.postgresql.org/docs/current/functions-window.html) | ||
cumeDist :: Window (Expr Double) | ||
cumeDist = Window $ fromPrimExpr . fromColumn <$> Opaleye.cumeDist | ||
|
||
|
||
-- | [@ntile(num_buckets)@](https://www.postgresql.org/docs/current/functions-window.html) | ||
ntile :: Expr Int32 -> Window (Expr Int32) | ||
ntile buckets = Window $ fromPrimExpr . fromColumn <$> | ||
Opaleye.ntile (toColumn (toPrimExpr buckets)) | ||
|
||
|
||
-- | [@lag(value, offset, default)@](https://www.postgresql.org/docs/current/functions-window.html) | ||
lag :: Expr a -> Expr Int32 -> Expr a -> Window (Expr a) | ||
lag a offset def = Window $ fromPrimExpr . fromColumn <$> | ||
Opaleye.lag (toColumn (toPrimExpr a)) (toColumn (toPrimExpr offset)) | ||
(toColumn (toPrimExpr def)) | ||
|
||
|
||
-- | [@lead(value, offset, default)@](https://www.postgresql.org/docs/current/functions-window.html) | ||
lead :: Expr a -> Expr Int32 -> Expr a -> Window (Expr a) | ||
lead a offset def = Window $ fromPrimExpr . fromColumn <$> | ||
Opaleye.lead (toColumn (toPrimExpr a)) (toColumn (toPrimExpr offset)) | ||
(toColumn (toPrimExpr def)) | ||
|
||
|
||
-- | [@first_value(value)@](https://www.postgresql.org/docs/current/functions-window.html) | ||
firstValue :: Expr a -> Window (Expr a) | ||
firstValue a = Window $ fromPrimExpr . fromColumn <$> | ||
Opaleye.firstValue (toColumn (toPrimExpr a)) | ||
|
||
|
||
-- | [@last_value(value)@](https://www.postgresql.org/docs/current/functions-window.html) | ||
lastValue :: Expr a -> Window (Expr a) | ||
lastValue a = Window $ fromPrimExpr . fromColumn <$> | ||
Opaleye.lastValue (toColumn (toPrimExpr a)) | ||
|
||
|
||
-- | [@nth_value(value, n)@](https://www.postgresql.org/docs/current/functions-window.html) | ||
nthValue :: Expr a -> Expr Int32 -> Window (Expr (Nullify a)) | ||
nthValue a n = Window $ fromPrimExpr . fromColumn <$> | ||
Opaleye.nthValue (toColumn (toPrimExpr a)) (toColumn (toPrimExpr n)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module Rel8.Query.Window | ||
( window | ||
) | ||
where | ||
|
||
-- base | ||
import Prelude | ||
|
||
-- opaleye | ||
import qualified Opaleye.Window as Opaleye | ||
|
||
-- rel8 | ||
import Rel8.Query ( Query ) | ||
import Rel8.Query.Opaleye ( mapOpaleye ) | ||
import Rel8.Window ( Window( Window ) ) | ||
|
||
|
||
-- | 'window' runs a query composed of expressions containing | ||
-- [window functions](https://www.postgresql.org/docs/current/tutorial-window.html). | ||
-- 'window' is similar to 'Rel8.aggregate', with the main difference being | ||
-- that in a window query, each input row corresponds to one output row, | ||
-- whereas aggregation queries fold the entire input query down into a single | ||
-- row. To put this into a Haskell context, 'Rel8.aggregate' is to 'foldl' as | ||
-- 'window' is to 'scanl'. | ||
window :: Query (Window a) -> Query a | ||
window = mapOpaleye (Opaleye.window . fmap (\(Window a) -> a)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{-# language MonoLocalBinds #-} | ||
|
||
module Rel8.Table.Window | ||
( cumulative | ||
) | ||
where | ||
|
||
-- base | ||
import Prelude | ||
|
||
-- rel8 | ||
import Rel8.Aggregate ( Aggregates ) | ||
import qualified Rel8.Expr.Window as Expr | ||
import Rel8.Schema.HTable ( htraverse ) | ||
import Rel8.Table ( fromColumns, toColumns ) | ||
import Rel8.Window ( Window ) | ||
|
||
|
||
-- | 'cumulative' allows the use of aggregation functions in 'Window' | ||
-- expressions. In particular, @'cumulative' . 'Rel8.sum'@ | ||
-- (when combined with 'Rel8.Window.orderPartitionBy') gives a running total, | ||
-- also known as a \"cumulative sum\", hence the name @cumulative@. | ||
cumulative :: Aggregates aggregates exprs => aggregates -> Window exprs | ||
cumulative = fmap fromColumns . htraverse Expr.cumulative . toColumns |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
{-# language DerivingVia #-} | ||
{-# language FlexibleContexts #-} | ||
{-# language GeneralizedNewtypeDeriving #-} | ||
{-# language StandaloneKindSignatures #-} | ||
|
||
module Rel8.Window | ||
( Window(..) | ||
, Partition | ||
, over | ||
, partitionBy | ||
, orderPartitionBy | ||
) | ||
where | ||
|
||
-- base | ||
import Data.Kind ( Type ) | ||
import Prelude | ||
|
||
-- opaleye | ||
import qualified Opaleye.Internal.Window as Opaleye | ||
import qualified Opaleye.Internal.PackMap as Opaleye | ||
|
||
-- rel8 | ||
import Rel8.Expr ( Expr ) | ||
import Rel8.Expr.Opaleye ( toColumn, toPrimExpr ) | ||
import Rel8.Order( Order( Order ) ) | ||
import Rel8.Schema.HTable ( hfoldMap ) | ||
import Rel8.Table ( Table, toColumns ) | ||
|
||
-- semigroupoids | ||
import Data.Functor.Apply ( Apply, WrappedApplicative(..) ) | ||
|
||
|
||
-- | 'Window' is an applicative functor that represents expressions that | ||
-- contain | ||
-- [window functions](https://www.postgresql.org/docs/current/tutorial-window.html). | ||
-- 'Rel8.Query.Window.window' can be used to | ||
-- evaluate these expressions over a particular query. | ||
type Window :: Type -> Type | ||
newtype Window a = Window (Opaleye.Window a) | ||
deriving newtype (Functor, Applicative) | ||
deriving (Apply) via (WrappedApplicative Window) | ||
|
||
|
||
-- | In PostgreSQL, window functions must specify the \"window\" or | ||
-- \"partition\" over which they operate. The syntax for this looks like: | ||
-- @SUM(salary) OVER (PARTITION BY department)@. The Rel8 type 'Partition' | ||
-- represents everything that comes after @OVER@. | ||
-- | ||
-- 'Partition' is a 'Monoid', so 'Partition's created with 'partitionBy' and | ||
-- 'orderPartitionBy' can be combined using '<>'. | ||
type Partition :: Type | ||
newtype Partition = Partition Opaleye.Partition | ||
deriving newtype (Semigroup, Monoid) | ||
|
||
|
||
-- | 'over' adds a 'Partition' to a 'Window' expression. | ||
-- | ||
-- @@@ | ||
-- 'Rel8.Table.Window.cumulative' ('Rel8.Expr.Aggregate.sum' salary) `over` 'partitionBy' department <> 'orderPartitionBy' salary 'Rel8.desc' | ||
-- @@@ | ||
over :: Window a -> Partition -> Window a | ||
over (Window w) (Partition p) = Window (w `Opaleye.over` p) | ||
infixl 1 `over` | ||
|
||
|
||
-- | Restricts a window function to operate only the group of rows that share | ||
-- the same value(s) for the given expression(s). | ||
partitionBy :: Table Expr a => a -> Partition | ||
partitionBy = Partition . hfoldMap opartitionBy . toColumns | ||
where | ||
opartitionBy = Opaleye.partitionBy . toColumn . toPrimExpr | ||
|
||
|
||
-- | Controls the order in which rows are processed by window functions. This | ||
-- does not need to match the ordering of the overall query. | ||
orderPartitionBy :: a -> Order a -> Partition | ||
orderPartitionBy a (Order ordering) = Partition $ Opaleye.orderPartitionBy a ordering |