packages feed

opaleye 0.9.5.1 → 0.9.6.0

raw patch · 12 files changed

+393/−2 lines, 12 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Opaleye.Internal.HaskellDB.PrimQuery: Partition :: [PrimExpr] -> [OrderExpr] -> Partition
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwAggregate :: AggrOp -> PrimExpr -> WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwCumeDist :: WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwDenseRank :: WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwExpr :: WndwOp -> Partition -> PrimExpr
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwFirstValue :: PrimExpr -> WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwLag :: PrimExpr -> PrimExpr -> PrimExpr -> WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwLastValue :: PrimExpr -> WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwLead :: PrimExpr -> PrimExpr -> PrimExpr -> WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwNthValue :: PrimExpr -> PrimExpr -> WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwNtile :: PrimExpr -> WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwPercentRank :: WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwRank :: WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: WndwRowNumber :: WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: [orderBy] :: Partition -> [OrderExpr]
+ Opaleye.Internal.HaskellDB.PrimQuery: [partitionBy] :: Partition -> [PrimExpr]
+ Opaleye.Internal.HaskellDB.PrimQuery: data Partition
+ Opaleye.Internal.HaskellDB.PrimQuery: data WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: instance GHC.Read.Read Opaleye.Internal.HaskellDB.PrimQuery.Partition
+ Opaleye.Internal.HaskellDB.PrimQuery: instance GHC.Read.Read Opaleye.Internal.HaskellDB.PrimQuery.WndwOp
+ Opaleye.Internal.HaskellDB.PrimQuery: instance GHC.Show.Show Opaleye.Internal.HaskellDB.PrimQuery.Partition
+ Opaleye.Internal.HaskellDB.PrimQuery: instance GHC.Show.Show Opaleye.Internal.HaskellDB.PrimQuery.WndwOp
+ Opaleye.Internal.HaskellDB.Sql: SqlPartition :: Maybe (NonEmpty SqlExpr) -> Maybe (NonEmpty (SqlExpr, SqlOrder)) -> SqlPartition
+ Opaleye.Internal.HaskellDB.Sql: WndwFunSqlExpr :: String -> [SqlExpr] -> SqlPartition -> SqlExpr
+ Opaleye.Internal.HaskellDB.Sql: [sqlOrderBy] :: SqlPartition -> Maybe (NonEmpty (SqlExpr, SqlOrder))
+ Opaleye.Internal.HaskellDB.Sql: [sqlPartitionBy] :: SqlPartition -> Maybe (NonEmpty SqlExpr)
+ Opaleye.Internal.HaskellDB.Sql: data SqlPartition
+ Opaleye.Internal.HaskellDB.Sql: instance GHC.Show.Show Opaleye.Internal.HaskellDB.Sql.SqlPartition
+ Opaleye.Internal.HaskellDB.Sql.Default: showWndwOp :: SqlGenerator -> WndwOp -> (String, [SqlExpr])
+ Opaleye.Internal.HaskellDB.Sql.Default: toSqlPartition :: SqlGenerator -> Partition -> SqlPartition
+ Opaleye.Internal.PrimQuery: Window :: Bindings (WndwOp, Partition) -> PrimQuery' a -> PrimQuery' a
+ Opaleye.Internal.PrimQuery: [window] :: PrimQueryFoldP a p p' -> Bindings (WndwOp, Partition) -> p -> p'
+ Opaleye.Internal.Sql: window :: Bindings (WndwOp, Partition) -> Select -> Select
+ Opaleye.Internal.Window: Window :: (a -> [PrimExpr]) -> Order a -> Window a
+ Opaleye.Internal.Window: WindowFunction :: PackMap WndwOp PrimExpr a b -> WindowFunction a b
+ Opaleye.Internal.Window: Windows :: PackMap (WndwOp, Window a) PrimExpr a b -> Windows a b
+ Opaleye.Internal.Window: aggregatorWindowFunction :: Aggregator a b -> (a' -> a) -> WindowFunction a' b
+ Opaleye.Internal.Window: data Window a
+ Opaleye.Internal.Window: extractWindowFields :: Tag -> a -> (WndwOp, Window a) -> PM (Bindings (WndwOp, Partition)) PrimExpr
+ Opaleye.Internal.Window: instance Data.Functor.Contravariant.Contravariant Opaleye.Internal.Window.Window
+ Opaleye.Internal.Window: instance Data.Profunctor.Unsafe.Profunctor Opaleye.Internal.Window.WindowFunction
+ Opaleye.Internal.Window: instance Data.Profunctor.Unsafe.Profunctor Opaleye.Internal.Window.Windows
+ Opaleye.Internal.Window: instance GHC.Base.Applicative (Opaleye.Internal.Window.WindowFunction a)
+ Opaleye.Internal.Window: instance GHC.Base.Applicative (Opaleye.Internal.Window.Windows a)
+ Opaleye.Internal.Window: instance GHC.Base.Functor (Opaleye.Internal.Window.WindowFunction a)
+ Opaleye.Internal.Window: instance GHC.Base.Functor (Opaleye.Internal.Window.Windows a)
+ Opaleye.Internal.Window: instance GHC.Base.Monoid (Opaleye.Internal.Window.Window a)
+ Opaleye.Internal.Window: instance GHC.Base.Semigroup (Opaleye.Internal.Window.Window a)
+ Opaleye.Internal.Window: makeWndw :: WindowFunction WndwOp (Field_ n a)
+ Opaleye.Internal.Window: makeWndwAny :: WndwOp -> WindowFunction a (Field_ n b)
+ Opaleye.Internal.Window: makeWndwField :: (PrimExpr -> WndwOp) -> WindowFunction (Field_ n a) (Field_ n' a')
+ Opaleye.Internal.Window: newtype WindowFunction a b
+ Opaleye.Internal.Window: newtype Windows a b
+ Opaleye.Internal.Window: noWindowFunction :: (a -> b) -> WindowFunction a b
+ Opaleye.Internal.Window: orderPartitionBy :: Order a -> Window a
+ Opaleye.Internal.Window: over :: WindowFunction a b -> Window a -> Order a -> Windows a b
+ Opaleye.Internal.Window: partitionBy :: (a -> Field_ n b) -> Window a
+ Opaleye.Internal.Window: runWindows :: Windows a b -> Select a -> Select b
+ Opaleye.Internal.Window: runWindows' :: Applicative f => Windows a b -> ((WndwOp, Window a) -> f PrimExpr) -> a -> f b
+ Opaleye.Internal.Window: windowsApply :: Windows (Windows a b, a) b
+ Opaleye.Window: aggregatorWindowFunction :: Aggregator a b -> (a' -> a) -> WindowFunction a' b
+ Opaleye.Window: cumeDist :: WindowFunction a (Field SqlFloat8)
+ Opaleye.Window: data Window a
+ Opaleye.Window: data WindowFunction a b
+ Opaleye.Window: data Windows a b
+ Opaleye.Window: denseRank :: WindowFunction a (Field SqlInt8)
+ Opaleye.Window: firstValue :: WindowFunction (Field_ n a) (Field_ n a)
+ Opaleye.Window: lag :: Field SqlInt4 -> Field_ n a -> WindowFunction (Field_ n a) (Field_ n a)
+ Opaleye.Window: lastValue :: WindowFunction (Field_ n a) (Field_ n a)
+ Opaleye.Window: lead :: Field SqlInt4 -> Field_ n a -> WindowFunction (Field_ n a) (Field_ n a)
+ Opaleye.Window: noWindowFunction :: (a -> b) -> WindowFunction a b
+ Opaleye.Window: nthValue :: Field SqlInt4 -> WindowFunction (Field_ n a) (FieldNullable a)
+ Opaleye.Window: ntile :: Field SqlInt4 -> WindowFunction a (Field SqlInt4)
+ Opaleye.Window: over :: WindowFunction a b -> Window a -> Order a -> Windows a b
+ Opaleye.Window: partitionBy :: (a -> Field_ n b) -> Window a
+ Opaleye.Window: percentRank :: WindowFunction a (Field SqlFloat8)
+ Opaleye.Window: rank :: WindowFunction a (Field SqlInt8)
+ Opaleye.Window: rowNumber :: WindowFunction a (Field SqlInt8)
+ Opaleye.Window: runWindows :: Windows a b -> Select a -> Select b
- Opaleye.Internal.PrimQuery: PrimQueryFold :: p' -> (a -> p') -> (TableIdentifier -> Bindings PrimExpr -> p') -> (NonEmpty (Lateral, p) -> [PrimExpr] -> p') -> (Bindings (Maybe (AggrOp, [OrderExpr], AggrDistinct), Symbol) -> p -> p') -> (Maybe (NonEmpty PrimExpr) -> [OrderExpr] -> p -> p') -> (LimitOp -> p -> p') -> (JoinType -> PrimExpr -> (Lateral, p) -> (Lateral, p) -> p') -> (SemijoinType -> p -> p -> p') -> (Symbol -> p -> p') -> ([Symbol] -> NonEmpty [PrimExpr] -> p') -> (BinOp -> (p, p) -> p') -> (String -> p -> p') -> (PrimExpr -> Bindings PrimExpr -> p') -> (Bool -> Bindings PrimExpr -> p -> p') -> (p -> p') -> (Recursive -> Symbol -> [Symbol] -> p -> p -> p') -> PrimQueryFoldP a p p'
+ Opaleye.Internal.PrimQuery: PrimQueryFold :: p' -> (a -> p') -> (TableIdentifier -> Bindings PrimExpr -> p') -> (NonEmpty (Lateral, p) -> [PrimExpr] -> p') -> (Bindings (Maybe (AggrOp, [OrderExpr], AggrDistinct), Symbol) -> p -> p') -> (Bindings (WndwOp, Partition) -> p -> p') -> (Maybe (NonEmpty PrimExpr) -> [OrderExpr] -> p -> p') -> (LimitOp -> p -> p') -> (JoinType -> PrimExpr -> (Lateral, p) -> (Lateral, p) -> p') -> (SemijoinType -> p -> p -> p') -> (Symbol -> p -> p') -> ([Symbol] -> NonEmpty [PrimExpr] -> p') -> (BinOp -> (p, p) -> p') -> (String -> p -> p') -> (PrimExpr -> Bindings PrimExpr -> p') -> (Bool -> Bindings PrimExpr -> p -> p') -> (p -> p') -> (Recursive -> Symbol -> [Symbol] -> p -> p -> p') -> PrimQueryFoldP a p p'

Files

CHANGELOG.md view
@@ -1,3 +1,8 @@+## 0.9.6.0++* Add `Opaleye.Window` to support window functions.  Thanks to Shane+  O'Brien.+ ## 0.9.5.1  * Actually expose `arrayAgg_`
opaleye.cabal view
@@ -1,6 +1,6 @@ name:            opaleye copyright:       Copyright (c) 2014-2018 Purely Agile Limited; 2019-2022 Tom Ellis-version:         0.9.5.1+version:         0.9.6.0 synopsis:        An SQL-generating DSL targeting PostgreSQL description:     An SQL-generating DSL targeting PostgreSQL.  Allows                  Postgres queries to be written within Haskell in a@@ -70,6 +70,7 @@                    Opaleye.TypeFamilies,                    Opaleye.Values,                    Opaleye.With,+                   Opaleye.Window,                    Opaleye.Internal.Aggregate,                    Opaleye.Internal.Binary,                    Opaleye.Internal.Constant,@@ -103,6 +104,7 @@                    Opaleye.Internal.TypeFamilies,                    Opaleye.Internal.Unpackspec,                    Opaleye.Internal.Values,+                   Opaleye.Internal.Window,                    Opaleye.Internal.HaskellDB.PrimQuery,                    Opaleye.Internal.HaskellDB.Sql,                    Opaleye.Internal.HaskellDB.Sql.Default,
src/Opaleye.hs view
@@ -37,6 +37,7 @@                , module Opaleye.ToFields                , module Opaleye.Values                , module Opaleye.With+               , module Opaleye.Window                ) where  import Opaleye.Adaptors@@ -62,4 +63,5 @@ import Opaleye.Table import Opaleye.ToFields import Opaleye.Values+import Opaleye.Window import Opaleye.With
src/Opaleye/Internal/HaskellDB/PrimQuery.hs view
@@ -23,6 +23,7 @@                 | BinExpr   BinOp PrimExpr PrimExpr                 | UnExpr    UnOp PrimExpr                 | AggrExpr  AggrDistinct AggrOp PrimExpr [OrderExpr]+                | WndwExpr  WndwOp Partition                 | ConstExpr Literal                 | CaseExpr [(PrimExpr,PrimExpr)] PrimExpr                 | ListExpr (NEL.NonEmpty PrimExpr)@@ -101,3 +102,24 @@  data BoundExpr = Inclusive PrimExpr | Exclusive PrimExpr | PosInfinity | NegInfinity                  deriving (Show,Read)++data WndwOp+  = WndwRowNumber+  | WndwRank+  | WndwDenseRank+  | WndwPercentRank+  | WndwCumeDist+  | WndwNtile PrimExpr+  | WndwLag PrimExpr PrimExpr PrimExpr+  | WndwLead PrimExpr PrimExpr PrimExpr+  | WndwFirstValue PrimExpr+  | WndwLastValue PrimExpr+  | WndwNthValue PrimExpr PrimExpr+  | WndwAggregate AggrOp PrimExpr+  deriving (Show,Read)++data Partition = Partition+  { partitionBy :: [PrimExpr]+  , orderBy :: [OrderExpr]+  }+  deriving (Read, Show)
src/Opaleye/Internal/HaskellDB/Sql.hs view
@@ -31,6 +31,13 @@                          , sqlOrderNulls     :: SqlOrderNulls }   deriving Show ++data SqlPartition = SqlPartition+  { sqlPartitionBy :: Maybe (NEL.NonEmpty SqlExpr)+  , sqlOrderBy :: Maybe (NEL.NonEmpty (SqlExpr, SqlOrder))+  }+  deriving Show+ data SqlRangeBound = Inclusive SqlExpr | Exclusive SqlExpr | PosInfinity | NegInfinity                    deriving Show @@ -46,6 +53,7 @@              | PostfixSqlExpr String SqlExpr              | FunSqlExpr     String [SqlExpr]              | AggrFunSqlExpr String [SqlExpr] [(SqlExpr, SqlOrder)] SqlDistinct -- ^ Aggregate functions separate from normal functions.+             | WndwFunSqlExpr String [SqlExpr] SqlPartition              | ConstSqlExpr   String              | CaseSqlExpr    (NEL.NonEmpty (SqlExpr,SqlExpr)) SqlExpr              | ListSqlExpr    (NEL.NonEmpty SqlExpr)
src/Opaleye/Internal/HaskellDB/Sql/Default.hs view
@@ -50,6 +50,13 @@             PQ.NullsLast  -> Sql.SqlNullsLast  +toSqlPartition :: SqlGenerator -> Partition -> SqlPartition+toSqlPartition gen (Partition partition order) = SqlPartition+  { sqlPartitionBy = NEL.nonEmpty (map (sqlExpr gen) partition)+  , sqlOrderBy = NEL.nonEmpty (map (toSqlOrder gen) order)+  }++ toSqlColumn :: Attribute -> SqlColumn toSqlColumn = SqlColumn @@ -135,6 +142,9 @@                                                       AggrDistinct -> SqlDistinct                                                       AggrAll      -> SqlNotDistinct                                      in AggrFunSqlExpr op' e' ord' distinct'+      WndwExpr op window  -> let (op', e') = showWndwOp gen op+                                 window' = toSqlPartition gen window+                              in WndwFunSqlExpr op' e' window'       ConstExpr l      -> ConstSqlExpr (sqlLiteral gen l)       CaseExpr cs e    -> let cs' = [(sqlExpr gen c, sqlExpr gen x)| (c,x) <- cs]                               e'  = sqlExpr gen e@@ -227,6 +237,22 @@   JsonArr -> ("JSON_AGG", [sqlExpr gen arg])   AggrStringAggr sep -> ("STRING_AGG", [sqlExpr gen arg, sqlExpr gen sep])   AggrOther s -> (s, [sqlExpr gen arg])+++showWndwOp :: SqlGenerator -> WndwOp -> (String, [SqlExpr])+showWndwOp gen op = case op of+  WndwRowNumber -> ("ROW_NUMBER", [])+  WndwRank -> ("RANK", [])+  WndwDenseRank -> ("DENSE_RANK", [])+  WndwPercentRank -> ("PERCENT_RANK", [])+  WndwCumeDist -> ("CUME_DIST", [])+  WndwNtile e -> ("NTILE", [sqlExpr gen e])+  WndwLag e offset def -> ("LAG", map (sqlExpr gen) [e, offset, def])+  WndwLead e offset def -> ("LEAD", map (sqlExpr gen) [e, offset, def])+  WndwFirstValue e -> ("FIRST_VALUE", [sqlExpr gen e])+  WndwLastValue e -> ("LAST_VALUE", [sqlExpr gen e])+  WndwNthValue e n -> ("NTH_VALUE", map (sqlExpr gen) [e, n])+  WndwAggregate op' arg -> showAggrOp gen op' arg   defaultSqlLiteral :: SqlGenerator -> Literal -> String
src/Opaleye/Internal/HaskellDB/Sql/Print.hs view
@@ -24,7 +24,7 @@ import Opaleye.Internal.HaskellDB.Sql (SqlColumn(..), SqlDelete(..),                                SqlExpr(..), SqlOrder(..), SqlInsert(..),                                SqlUpdate(..), SqlTable(..), SqlRangeBound(..),-                               OnConflict(..))+                               SqlPartition(..), OnConflict(..)) import qualified Opaleye.Internal.HaskellDB.Sql as Sql  import Data.List (intersperse)@@ -55,6 +55,22 @@     ppGroupAttrs :: [SqlExpr] -> Doc     ppGroupAttrs = commaV (ppSqlExpr . deliteral) +ppWindowExpr :: String -> [SqlExpr] -> SqlPartition -> Doc+ppWindowExpr f es partition = text f <> parens (commaH ppSqlExpr es) <+> text "OVER" <+> parens (ppSqlPartition partition)++ppSqlPartition :: SqlPartition -> Doc+ppSqlPartition partition =+  ppPartitionBy (sqlPartitionBy partition) <+>+  ppOrderBy (maybe [] NEL.toList (sqlOrderBy partition))++ppPartitionBy :: Maybe (NEL.NonEmpty SqlExpr) -> Doc+ppPartitionBy Nothing = empty+ppPartitionBy (Just es) =+  text "PARTITION BY" <+> ppPartitionAttrs (NEL.toList es)+  where+    ppPartitionAttrs :: [SqlExpr] -> Doc+    ppPartitionAttrs = commaH (ppSqlExpr . deliteral)+ ppOrderBy :: [(SqlExpr,SqlOrder)] -> Doc ppOrderBy [] = empty ppOrderBy ord = text "ORDER BY" <+> commaV ppOrd ord@@ -177,6 +193,7 @@       ArraySqlExpr es        -> text "ARRAY" <> brackets (commaH ppSqlExpr es)       RangeSqlExpr t s e     -> ppRange t s e       AggrFunSqlExpr f es ord distinct -> text f <> parens (ppSqlDistinct distinct <+> commaH ppSqlExpr es <+> ppOrderBy ord)+      WndwFunSqlExpr f es window -> ppWindowExpr f es window       CaseSqlExpr cs el   -> text "CASE" <+> vcat (toList (fmap ppWhen cs))                              <+> text "ELSE" <+> ppSqlExpr el <+> text "END"           where ppWhen (w,t) = text "WHEN" <+> ppSqlExpr w
src/Opaleye/Internal/Optimize.hs view
@@ -53,6 +53,7 @@                    \x y -> PQ.Product <$> sequenceOf (traverse._2) x                                       <*> pure y   , PQ.aggregate = fmap . PQ.Aggregate+  , PQ.window    = fmap . PQ.Window   , PQ.distinctOnOrderBy = \mDistinctOns -> fmap . PQ.DistinctOnOrderBy mDistinctOns   , PQ.limit     = fmap . PQ.Limit   , PQ.join      = \jt pe pq1 pq2 -> PQ.Join jt pe <$> sequence pq1 <*> sequence pq2
src/Opaleye/Internal/PrimQuery.hs view
@@ -133,6 +133,7 @@                                                 HPQ.AggrDistinct),                                           HPQ.Symbol))                               (PrimQuery' a)+                  | Window (Bindings (HPQ.WndwOp, HPQ.Partition)) (PrimQuery' a)                   -- | Represents both @DISTINCT ON@ and @ORDER BY@                   --   clauses. In order to represent valid SQL only,                   --   @DISTINCT ON@ expressions are always@@ -180,6 +181,7 @@                                    HPQ.Symbol)                       -> p                       -> p'+  , window            :: Bindings (HPQ.WndwOp, HPQ.Partition) -> p -> p'   , distinctOnOrderBy :: Maybe (NEL.NonEmpty HPQ.PrimExpr)                       -> [HPQ.OrderExpr]                       -> p@@ -212,6 +214,7 @@   , baseTable         = BaseTable   , product           = Product   , aggregate         = Aggregate+  , window            = Window   , distinctOnOrderBy = DistinctOnOrderBy   , limit             = Limit   , join              = Join@@ -236,6 +239,7 @@   , baseTable = \ti bs -> g (baseTable f ti bs)   , product = \ps conds -> g (product f ((fmap . fmap) self ps) conds)   , aggregate = \b p -> g (aggregate f b (self p))+  , window = \b p -> g (window f b (self p))   , distinctOnOrderBy = \m os p -> g (distinctOnOrderBy f m os (self p))   , limit = \l p -> g (limit f l (self p))   , join = \j pe lp lp' -> g (join f j pe (fmap self lp) (fmap self lp'))@@ -258,6 +262,7 @@   BaseTable ti syms -> baseTable f ti syms   Product qs pes -> product f qs pes   Aggregate aggrs q -> aggregate f aggrs q+  Window wndws q -> window f wndws q   DistinctOnOrderBy dxs oxs q -> distinctOnOrderBy f dxs oxs q   Limit op q -> limit f op q   Join j cond q1 q2 -> join f j cond q1 q2
src/Opaleye/Internal/Sql.hs view
@@ -111,6 +111,7 @@   , PQ.baseTable         = baseTable   , PQ.product           = product   , PQ.aggregate         = aggregate+  , PQ.window            = window   , PQ.distinctOnOrderBy = distinctOnOrderBy   , PQ.limit             = limit_   , PQ.join              = join@@ -204,6 +205,12 @@  aggrExpr :: Maybe (HPQ.AggrOp, [HPQ.OrderExpr], HPQ.AggrDistinct) -> HPQ.PrimExpr -> HPQ.PrimExpr aggrExpr = maybe id (\(op, ord, distinct) e -> HPQ.AggrExpr distinct op e ord)++window :: PQ.Bindings (HPQ.WndwOp, HPQ.Partition) -> Select -> Select+window wndws' s = SelectFrom $ newSelect+  { attrs = SelectAttrsStar (ensureColumns (map (sqlBinding . fmap (uncurry HPQ.WndwExpr)) wndws'))+  , tables = oneTable s+  }  distinctOnOrderBy :: Maybe (NEL.NonEmpty HPQ.PrimExpr) -> [HPQ.OrderExpr] -> Select -> Select distinctOnOrderBy distinctExprs orderExprs s = SelectFrom $ newSelect
+ src/Opaleye/Internal/Window.hs view
@@ -0,0 +1,182 @@+-- https://www.postgresql.org/docs/current/tutorial-window.html#id-1.4.5.6.9.5+-- talks about partitions and window frames.  The window frame is the+-- way the elements of a partition are ordered for processing the+-- result row of each element of the partition.+--+-- So neither of these terms is suitable for the _whole thing_.+-- Perhaps the answer should be "Window"?  This is also attested by+-- the WINDOW declaration in a SELECT.++module Opaleye.Internal.Window where++import           Control.Applicative (Applicative, pure, (<*>), liftA2)+import           Data.Profunctor (lmap, Profunctor, dimap)+import           Data.Semigroup (Semigroup, (<>))++import qualified Opaleye.Internal.Aggregate as A+import qualified Opaleye.Internal.PackMap as PM+import qualified Opaleye.Internal.PrimQuery as PQ+import qualified Opaleye.Internal.QueryArr as Q+import qualified Opaleye.Internal.Tag as T+import qualified Opaleye.Internal.Column as C+import qualified Opaleye.Internal.Order as O++import qualified Opaleye.Internal.HaskellDB.PrimQuery as HPQ+import Data.Functor.Contravariant (contramap, Contravariant)+import Control.Arrow (second)+++-- | 'WindowFunction' represents expressions that contain [window+-- functions](https://www.postgresql.org/docs/current/tutorial-window.html).+-- You can choose a 'WindowFunction' from the options below, and+-- combine and manipulate them using the @Applicative@ and+-- 'Data.Profunctor.Profunctor' operations.+newtype WindowFunction a b =+  WindowFunction (PM.PackMap HPQ.WndwOp HPQ.PrimExpr a b)++instance Functor (WindowFunction a) where+  fmap f (WindowFunction w) = WindowFunction (fmap f w)++instance Applicative (WindowFunction a) where+  pure = WindowFunction . pure+  WindowFunction f <*> WindowFunction x = WindowFunction ((<*>) f x)++instance Profunctor WindowFunction where+  dimap f g (WindowFunction w) =  WindowFunction (dimap f g w)++-- | You can create @Windows@ using 'over', and combine and manipulate+-- them using the @Applicative@ and 'Data.Profunctor.Profunctor'+-- operations.+newtype Windows a b =+  Windows (PM.PackMap (HPQ.WndwOp, Window a) HPQ.PrimExpr a b)++instance Functor (Windows a) where+  fmap f (Windows w) = Windows (fmap f w)++instance Applicative (Windows a) where+  pure = Windows . pure+  Windows f <*> Windows x = Windows ((<*>) f x)++instance Profunctor Windows where+  dimap f g (Windows (PM.PackMap pm)) =+    Windows $ PM.PackMap $ \h a ->+      fmap g (pm (\(op, w) -> h (op, contramap f w)) (f a))++runWindows' :: Applicative f+  => Windows a b -> ((HPQ.WndwOp, Window a) -> f HPQ.PrimExpr) -> a -> f b+runWindows' (Windows a) = PM.traversePM a+++extractWindowFields+  :: T.Tag+  -> a+  -> (HPQ.WndwOp, Window a)+  -> PM.PM (PQ.Bindings (HPQ.WndwOp, HPQ.Partition)) HPQ.PrimExpr+extractWindowFields tag a (op, Window ps os) = do+  i <- PM.new+  let symbol = HPQ.Symbol ("window" ++ i) tag+  PM.write (symbol, (op, HPQ.Partition (ps a) (O.orderExprs a os)))+  pure (HPQ.AttrExpr symbol)+++-- | A 'WindowFunction' that doesn't actually contain any window+-- function.+noWindowFunction :: (a -> b) -> WindowFunction a b+noWindowFunction f = fmap f (WindowFunction (PM.PackMap (const pure)))+++-- | @runWindows@ runs a query composed of expressions containing+-- [window+-- functions](https://www.postgresql.org/docs/current/tutorial-window.html).+-- @runWindows@ is similar to 'Opaleye.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 per group. In Haskell+-- terminology, 'Opaleye.aggregate' is to 'foldl' as @runWindows@ is+-- to 'scanl'.+runWindows :: Windows a b -> Q.Select a -> Q.Select b+runWindows wndw q = Q.productQueryArr $ do+  (a, primQ) <- Q.runSimpleSelect q+  tag <- T.fresh+  let+    (b, bindings) = PM.run (runWindows' wndw (extractWindowFields tag a) a)+  pure (b, PQ.Window bindings primQ)+++windowsApply :: Windows (Windows a b, a) b+windowsApply = Windows $ PM.PackMap $ \f (agg, a) ->+  case agg of+    Windows (PM.PackMap inner) -> inner (f . second (contramap snd)) a+++makeWndw :: WindowFunction HPQ.WndwOp (C.Field_ n a)+makeWndw = WindowFunction (PM.PackMap (\f op -> C.Column <$> f op))+++makeWndwField :: (HPQ.PrimExpr -> HPQ.WndwOp)+              -> WindowFunction (C.Field_ n a) (C.Field_ n' a')+makeWndwField f = lmap (f . C.unColumn) makeWndw+++makeWndwAny :: HPQ.WndwOp -> WindowFunction a (C.Field_ n b)+makeWndwAny op = lmap (const op) makeWndw++-- | 'aggregatorWindowFunction' allows the use of 'A.Aggregator's in+-- 'WindowFunction's. In particular, @'aggregatorWindowFunction'+-- 'Opaleye.sum'@ gives a running total (when combined with an order+-- argument to 'over').+aggregatorWindowFunction :: A.Aggregator a b -> (a' -> a) -> WindowFunction a' b+aggregatorWindowFunction agg g = WindowFunction $ PM.PackMap $ \f a ->+  pm (\(mop, expr) -> case mop of+         Nothing -> pure expr+         Just (op, _, _) -> f (HPQ.WndwAggregate op expr)) a+  where A.Aggregator (PM.PackMap pm) = lmap g agg+++-- | 'over' applies a 'WindowFunction' on a particular 'Window'.  For+-- example,+--+-- @+-- over ('aggregatorWindowFunction' 'Opaleye.sum' salary) ('partitionBy' department) ('Opaleye.desc' salary)+-- @+--+-- If you want to use a 'Window' that consists of the entire @SELECT@+-- then supply 'mempty' for the @'Window' a@ argument.  If you don't+-- want to order the 'Window' then supply 'mempty' for the @'O.Order'+-- a@ argument.+over :: WindowFunction a b -> Window a -> O.Order a -> Windows a b+over (WindowFunction windowFunction) partition order =+  let PM.PackMap pm = windowFunction+      orderPartitionBy' = orderPartitionBy order+  in Windows $ PM.PackMap $ \f -> pm (\op ->+    f (op, partition <> orderPartitionBy'))++-- | In PostgreSQL, window functions must specify the \"window\" over+-- which they operate. The syntax for this looks like: @SUM(salary)+-- OVER (PARTITION BY department)@. The Opaleye type 'Window'+-- represents the segment consisting of the @PARTIION BY@.+--+-- You can create a @Window@ using 'partitionBy' and combine two+-- @Windows@ in a single one which combines the partition of both by+-- using '<>'.+data Window a = Window (a -> [HPQ.PrimExpr]) (O.Order a)++instance Semigroup (Window a) where+  Window p1 o1 <> Window p2 o2 = Window (p1 <> p2) (o1 <> o2)++instance Monoid (Window a) where+  mempty = Window mempty mempty+  mappend = (<>)++instance Contravariant Window where+  contramap f (Window p o) = Window (lmap f p) (contramap f o)++-- | The window where each partition shares the same value for the+-- given 'Field'.+partitionBy :: (a -> C.Field_ n b) -> Window a+partitionBy f = Window (\a -> [C.unColumn (f a)]) mempty++-- | Controls the order in which rows are processed by window functions. This+-- does not need to match the ordering of the overall query.+orderPartitionBy :: O.Order a -> Window a+orderPartitionBy = Window mempty
+ src/Opaleye/Window.hs view
@@ -0,0 +1,114 @@+-- | Support for [PostgreSQL window+-- functions](https://www.postgresql.org/docs/current/tutorial-window.html)++module Opaleye.Window+       (+         -- * Run window functions on a @Select@+         W.runWindows++         -- * Create @Windows@+       , W.Windows+       , W.over++         -- * Create a @Window@+       , W.Window+       , W.partitionBy++         -- * Create a @WindowFunction@+       , W.WindowFunction++         -- * Window functions++         -- | You might like to also refer to [the Postgres+         -- documentation page that describes its window+         -- functions](https://www.postgresql.org/docs/devel/functions-window.html).++       , W.noWindowFunction+       , W.aggregatorWindowFunction+       , rowNumber+       , rank+       , denseRank+       , percentRank+       , cumeDist+       , ntile+       , lag+       , lead+       , firstValue+       , lastValue+       , nthValue+       ) where++import qualified Opaleye.Internal.Column as IC+import qualified Opaleye.Internal.HaskellDB.PrimQuery as HPQ+import qualified Opaleye.Internal.Window as W++import qualified Opaleye.Field as F+import qualified Opaleye.SqlTypes as T++-- | [@row_number()@](https://www.postgresql.org/docs/current/functions-window.html)+rowNumber :: W.WindowFunction a (F.Field T.SqlInt8)+rowNumber = W.makeWndwAny HPQ.WndwRowNumber+++-- | [@rank()@](https://www.postgresql.org/docs/current/functions-window.html)+rank :: W.WindowFunction a (F.Field T.SqlInt8)+rank = W.makeWndwAny HPQ.WndwRank+++-- | [@dense_rank()@](https://www.postgresql.org/docs/current/functions-window.html)+denseRank :: W.WindowFunction a (F.Field T.SqlInt8)+denseRank = W.makeWndwAny HPQ.WndwDenseRank+++-- | [@percent_rank()@](https://www.postgresql.org/docs/current/functions-window.html)+percentRank :: W.WindowFunction a (F.Field T.SqlFloat8)+percentRank = W.makeWndwAny HPQ.WndwPercentRank+++-- | [@cume_dist()@](https://www.postgresql.org/docs/current/functions-window.html)+cumeDist :: W.WindowFunction a (F.Field T.SqlFloat8)+cumeDist = W.makeWndwAny HPQ.WndwCumeDist+++-- | [@ntile(num_buckets)@](https://www.postgresql.org/docs/current/functions-window.html)+ntile :: F.Field T.SqlInt4+      -- ^ num_buckets+      -> W.WindowFunction a (F.Field T.SqlInt4)+ntile (IC.Column buckets) = W.makeWndwAny $ HPQ.WndwNtile buckets+++-- | [@lag(value, offset, default)@](https://www.postgresql.org/docs/current/functions-window.html)+lag :: F.Field T.SqlInt4+    -- ^ offset+    -> F.Field_ n a+    -- ^ default+    -> W.WindowFunction (F.Field_ n a) (F.Field_ n a)+lag (IC.Column offset) (IC.Column def) =+  W.makeWndwField $ \a -> HPQ.WndwLag a offset def+++-- | [@lead(value, offset, default)@](https://www.postgresql.org/docs/current/functions-window.html)+lead :: F.Field T.SqlInt4+     -- ^ offset+     -> F.Field_ n a+     -- ^ default+     -> W.WindowFunction (F.Field_ n a) (F.Field_ n a)+lead (IC.Column offset) (IC.Column def) =+  W.makeWndwField $ \a -> HPQ.WndwLead a offset def+++-- | [@first_value(value)@](https://www.postgresql.org/docs/current/functions-window.html)+firstValue :: W.WindowFunction (F.Field_ n a) (F.Field_ n a)+firstValue = W.makeWndwField HPQ.WndwFirstValue+++-- | [@last_value(value)@](https://www.postgresql.org/docs/current/functions-window.html)+lastValue :: W.WindowFunction (F.Field_ n a) (F.Field_ n a)+lastValue = W.makeWndwField HPQ.WndwLastValue+++-- | [@nth_value(value, n)@](https://www.postgresql.org/docs/current/functions-window.html)+nthValue :: F.Field T.SqlInt4+         -- ^ n+         -> W.WindowFunction (F.Field_ n a) (F.FieldNullable a)+nthValue (IC.Column n) = W.makeWndwField $ \a -> HPQ.WndwNthValue a n