diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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_`
diff --git a/opaleye.cabal b/opaleye.cabal
--- a/opaleye.cabal
+++ b/opaleye.cabal
@@ -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,
diff --git a/src/Opaleye.hs b/src/Opaleye.hs
--- a/src/Opaleye.hs
+++ b/src/Opaleye.hs
@@ -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
diff --git a/src/Opaleye/Internal/HaskellDB/PrimQuery.hs b/src/Opaleye/Internal/HaskellDB/PrimQuery.hs
--- a/src/Opaleye/Internal/HaskellDB/PrimQuery.hs
+++ b/src/Opaleye/Internal/HaskellDB/PrimQuery.hs
@@ -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)
diff --git a/src/Opaleye/Internal/HaskellDB/Sql.hs b/src/Opaleye/Internal/HaskellDB/Sql.hs
--- a/src/Opaleye/Internal/HaskellDB/Sql.hs
+++ b/src/Opaleye/Internal/HaskellDB/Sql.hs
@@ -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)
diff --git a/src/Opaleye/Internal/HaskellDB/Sql/Default.hs b/src/Opaleye/Internal/HaskellDB/Sql/Default.hs
--- a/src/Opaleye/Internal/HaskellDB/Sql/Default.hs
+++ b/src/Opaleye/Internal/HaskellDB/Sql/Default.hs
@@ -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
diff --git a/src/Opaleye/Internal/HaskellDB/Sql/Print.hs b/src/Opaleye/Internal/HaskellDB/Sql/Print.hs
--- a/src/Opaleye/Internal/HaskellDB/Sql/Print.hs
+++ b/src/Opaleye/Internal/HaskellDB/Sql/Print.hs
@@ -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
diff --git a/src/Opaleye/Internal/Optimize.hs b/src/Opaleye/Internal/Optimize.hs
--- a/src/Opaleye/Internal/Optimize.hs
+++ b/src/Opaleye/Internal/Optimize.hs
@@ -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
diff --git a/src/Opaleye/Internal/PrimQuery.hs b/src/Opaleye/Internal/PrimQuery.hs
--- a/src/Opaleye/Internal/PrimQuery.hs
+++ b/src/Opaleye/Internal/PrimQuery.hs
@@ -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
diff --git a/src/Opaleye/Internal/Sql.hs b/src/Opaleye/Internal/Sql.hs
--- a/src/Opaleye/Internal/Sql.hs
+++ b/src/Opaleye/Internal/Sql.hs
@@ -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
diff --git a/src/Opaleye/Internal/Window.hs b/src/Opaleye/Internal/Window.hs
new file mode 100644
--- /dev/null
+++ b/src/Opaleye/Internal/Window.hs
@@ -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
diff --git a/src/Opaleye/Window.hs b/src/Opaleye/Window.hs
new file mode 100644
--- /dev/null
+++ b/src/Opaleye/Window.hs
@@ -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
