Merge branch 'telser/windowing-expr-and-functions'

This commit is contained in:
Trevis Elser 2024-06-13 16:26:21 -04:00
commit 5f9291aeb8
20 changed files with 666 additions and 11 deletions

View File

@ -130,11 +130,16 @@ library
Orville.PostgreSQL.Expr.Internal.Name.SequenceName
Orville.PostgreSQL.Expr.Internal.Name.TableName
Orville.PostgreSQL.Expr.Internal.Name.TriggerName
Orville.PostgreSQL.Expr.Internal.Name.WindowName
Orville.PostgreSQL.Expr.Join
Orville.PostgreSQL.Expr.OrReplace
Orville.PostgreSQL.Expr.RowLocking
Orville.PostgreSQL.Expr.Trigger
Orville.PostgreSQL.Expr.Vacuum
Orville.PostgreSQL.Expr.Window
Orville.PostgreSQL.Expr.Window.WindowClause
Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr
Orville.PostgreSQL.Expr.Window.WindowFunction
Orville.PostgreSQL.Internal.Bracket
Orville.PostgreSQL.Internal.Extra.NonEmpty
Orville.PostgreSQL.Internal.FieldName
@ -217,6 +222,7 @@ test-suite spec
Test.Expr.Trigger
Test.Expr.Vacuum
Test.Expr.Where
Test.Expr.Window
Test.FieldDefinition
Test.MarshallError
Test.PgAssert

View File

@ -16,6 +16,7 @@ module Orville.PostgreSQL.Execution.SelectOptions
, selectLimitExpr
, selectOffsetExpr
, selectRowLockingClause
, selectWindowClause
, distinct
, where_
, orderBy
@ -23,6 +24,7 @@ module Orville.PostgreSQL.Execution.SelectOptions
, offset
, groupBy
, forRowLock
, window
, selectOptionsQueryExpr
)
where
@ -47,6 +49,7 @@ data SelectOptions = SelectOptions
, i_offsetExpr :: First Expr.OffsetExpr
, i_groupByExpr :: Maybe Expr.GroupByExpr
, i_rowLockingClause :: First Expr.RowLockingClause
, i_windowExpr :: Maybe Expr.NamedWindowDefinitionExpr
}
-- | @since 1.0.0.0
@ -72,6 +75,7 @@ emptySelectOptions =
, i_offsetExpr = mempty
, i_groupByExpr = mempty
, i_rowLockingClause = mempty
, i_windowExpr = Nothing
}
{- |
@ -91,6 +95,7 @@ appendSelectOptions left right =
(i_offsetExpr left <> i_offsetExpr right)
(i_groupByExpr left <> i_groupByExpr right)
(i_rowLockingClause left <> i_rowLockingClause right)
(i_windowExpr left <> i_windowExpr right)
unionMaybeWith :: (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
unionMaybeWith f mbLeft mbRight =
@ -185,6 +190,15 @@ selectRowLockingClause :: SelectOptions -> Maybe Expr.RowLockingClause
selectRowLockingClause =
getFirst . i_rowLockingClause
{- |
Builds an 'Expr.WindowClause' that will apply the windowing rules specified in the 'SelectOptions' (if any).
@since 1.1.0.0
-}
selectWindowClause :: SelectOptions -> Maybe Expr.WindowClause
selectWindowClause =
fmap Expr.windowClause . i_windowExpr
{- |
Constructs a 'SelectOptions' with just the given 'Expr.BooleanExpr'.
@ -251,6 +265,17 @@ forRowLock rowLockingClause =
{ i_rowLockingClause = First $ Just rowLockingClause
}
{- |
Constructs a 'SelectOptions' with just the given 'Expr.NamedWindowDefinitionExpr'.
@since 1.1.0.0
-}
window :: Expr.NamedWindowDefinitionExpr -> SelectOptions
window namedWindow =
emptySelectOptions
{ i_windowExpr = Just namedWindow
}
{- |
Builds a 'Expr.QueryExpr' that will use the specified 'Expr.SelectList' when
building the @SELECT@ statement to execute. It is up to the caller to make
@ -286,4 +311,5 @@ selectOptionsQueryExpr selectList tableReferenceList selectOptions =
(selectLimitExpr selectOptions)
(selectOffsetExpr selectOptions)
(selectRowLockingClause selectOptions)
(selectWindowClause selectOptions)
)

View File

@ -69,6 +69,7 @@ module Orville.PostgreSQL.Expr
, module Orville.PostgreSQL.Expr.Function
, module Orville.PostgreSQL.Expr.OrReplace
, module Orville.PostgreSQL.Expr.ConditionalExpr
, module Orville.PostgreSQL.Expr.Window
, module Orville.PostgreSQL.Expr.Vacuum
, module Orville.PostgreSQL.Expr.Extension
, module Orville.PostgreSQL.Expr.RowLocking
@ -115,3 +116,4 @@ import Orville.PostgreSQL.Expr.Update
import Orville.PostgreSQL.Expr.Vacuum
import Orville.PostgreSQL.Expr.ValueExpression
import Orville.PostgreSQL.Expr.WhereClause
import Orville.PostgreSQL.Expr.Window

View File

@ -0,0 +1,48 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{- |
Copyright : Flipstone Technology Partners 2024
License : MIT
Stability : Stable
@since 1.1.0.0
-}
module Orville.PostgreSQL.Expr.Internal.Name.WindowName
( WindowName
, windowName
)
where
import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier)
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
{- |
Type to represent a SQL window name. 'WindowName' values constructed
via the 'windowName' window will be properly escaped as part of the
generated SQL. E.G.
> "some_window_name"
'WindowName' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.
@since 1.1.0.0
-}
newtype WindowName
= WindowName Identifier
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
, -- | @since 1.1.0.0
IdentifierExpression
)
{- |
Construct a 'WindowName' from a 'String' with proper escaping as part of the generated SQL.
@since 1.1.0.0
-}
windowName :: String -> WindowName
windowName =
WindowName . identifier

View File

@ -26,3 +26,4 @@ import Orville.PostgreSQL.Expr.Internal.Name.SchemaName as Export
import Orville.PostgreSQL.Expr.Internal.Name.SequenceName as Export
import Orville.PostgreSQL.Expr.Internal.Name.TableName as Export
import Orville.PostgreSQL.Expr.Internal.Name.TriggerName as Export
import Orville.PostgreSQL.Expr.Internal.Name.WindowName as Export

View File

@ -46,6 +46,7 @@ import Orville.PostgreSQL.Expr.RowLocking (RowLockingClause)
import Orville.PostgreSQL.Expr.Select (SelectClause)
import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression, columnReference)
import Orville.PostgreSQL.Expr.WhereClause (BooleanExpr, InValuePredicate, WhereClause, inPredicate, notInPredicate)
import Orville.PostgreSQL.Expr.Window (WindowClause)
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
-- This is a rough model of "query specification" see https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#_7_16_query_specification for more detail than you probably want
@ -339,6 +340,8 @@ tableExpr ::
Maybe OffsetExpr ->
-- | An optional locking clause to apply to the result set.
Maybe RowLockingClause ->
-- | An optional @WINDOW@ clause to apply to the result set.
Maybe WindowClause ->
TableExpr
tableExpr
tableReferenceList
@ -347,13 +350,15 @@ tableExpr
maybeOrderByClause
maybeLimitExpr
maybeOffsetExpr
maybeRowLockingClause =
maybeRowLockingClause
maybeWindowClause =
TableExpr
. RawSql.intercalate RawSql.space
$ RawSql.toRawSql tableReferenceList
: catMaybes
[ RawSql.toRawSql <$> maybeWhereClause
, RawSql.toRawSql <$> maybeGroupByClause
, RawSql.toRawSql <$> maybeWindowClause
, RawSql.toRawSql <$> maybeOrderByClause
, RawSql.toRawSql <$> maybeLimitExpr
, RawSql.toRawSql <$> maybeOffsetExpr

View File

@ -0,0 +1,18 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# OPTIONS_GHC -Wno-missing-import-lists #-}
{- |
Copyright : Flipstone Technology Partners 2024
License : MIT
Stability : Stable
@since 1.1.0.0
-}
module Orville.PostgreSQL.Expr.Window
( module Export
)
where
import Orville.PostgreSQL.Expr.Window.WindowClause as Export
import Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr as Export
import Orville.PostgreSQL.Expr.Window.WindowFunction as Export

View File

@ -0,0 +1,92 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{- |
Copyright : Flipstone Technology Partners 2024
License : MIT
Stability : Stable
@since 1.1.0.0
-}
module Orville.PostgreSQL.Expr.Window.WindowClause
( WindowClause
, windowClause
, NamedWindowDefinitionExpr
, namedWindowDefinition
, appendNamedWindowDefinitionExpr
)
where
import Orville.PostgreSQL.Expr.Name (WindowName)
import Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr (WindowDefinitionExpr)
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
{- | Type to represent a named SQL window definition. This should only be used in a @WINDOW@ clause.
'NamedWindowDefinitionExpr' provides a 'RawSql.SqlExpression' instance.
See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.
@since 1.1.0.0
-}
newtype NamedWindowDefinitionExpr = NamedWindowDefinitionExpr RawSql.RawSql
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
)
{- |
@since 1.1.0.0
-}
instance Semigroup NamedWindowDefinitionExpr where
(<>) = appendNamedWindowDefinitionExpr
{- | Combines two 'NamedWindowDefinitionExpr's with a comma between them.
@since 1.1.0.0
-}
appendNamedWindowDefinitionExpr :: NamedWindowDefinitionExpr -> NamedWindowDefinitionExpr -> NamedWindowDefinitionExpr
appendNamedWindowDefinitionExpr (NamedWindowDefinitionExpr a) (NamedWindowDefinitionExpr b) =
NamedWindowDefinitionExpr (a <> RawSql.commaSpace <> b)
{- |
Builds a 'NamedWindowDefinitionExpr' with the given name and 'WindowDefinitionExpr'.
1
@since 1.1.0.0
-}
namedWindowDefinition :: WindowName -> WindowDefinitionExpr -> NamedWindowDefinitionExpr
namedWindowDefinition windowName expr =
NamedWindowDefinitionExpr $
RawSql.toRawSql windowName
<> RawSql.space
<> RawSql.fromString "AS"
<> RawSql.space
<> RawSql.parenthesized (RawSql.toRawSql expr)
{- |
Type to represent a SQL window clause. E.G.
> WINDOW foo , bar
'WindowClause' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.
@since 1.1.0.0
-}
newtype WindowClause
= WindowClause RawSql.RawSql
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
)
{- |
Builds a full 'WindowClause' with the given windowing described in the 'NamedWindowDefinitionExpr'
1
@since 1.1.0.0
-}
windowClause :: NamedWindowDefinitionExpr -> WindowClause
windowClause namedWindowDefinitionExpr =
WindowClause $
RawSql.fromString "WINDOW"
<> RawSql.space
<> RawSql.toRawSql namedWindowDefinitionExpr

View File

@ -0,0 +1,316 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{- |
Copyright : Flipstone Technology Partners 2024
License : MIT
Stability : Stable
@since 1.1.0.0
-}
module Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr
( WindowDefinitionExpr
, windowDefinition
, PartitionByExpr
, partitionBy
, FrameClause
, frameClause
, FrameModeExpr
, rangeFrameMode
, rowsFrameMode
, groupsFrameMode
, FrameStartExpr
, unboundedPrecedingFrameStart
, offsetPrecedingFrameStart
, currentRowFrameStart
, offsetFollowingFrameStart
, FrameEndExpr
, offsetPrecedingFrameEnd
, currentRowFrameEnd
, offsetFollowingFrameEnd
, unboundedFollowingFrameEnd
, FrameExclusionExpr
, currentRowFrameExclusion
, groupFrameExclusion
, tiesFrameExclusion
, noOthersFrameExclusion
)
where
import qualified Data.List.NonEmpty as NEL
import Data.Maybe (catMaybes)
import Orville.PostgreSQL.Expr.Name (WindowName)
import qualified Orville.PostgreSQL.Expr.OrderBy as OrderBy
import qualified Orville.PostgreSQL.Expr.ValueExpression as ValueExpression
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
{- | Type to represent a SQL window definition. This can be used in either a @WINDOW@ clause or in the
portion of a window function call after the @OVER@.
'WindowDefinitionExpr' provides a 'RawSql.SqlExpression' instance.
See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.
@since 1.1.0.0
-}
newtype WindowDefinitionExpr = WindowDefinitionExpr RawSql.RawSql
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
)
{- | Builds a 'WindowDefinitionExpr'. Note that it is up to the caller to ensure the options make
sense togther, including particular rules below.
N.B. If the 'WindowName' is used (so we are "copying" a previous definition), then a few rules apply:
* The 'ParititionByExpr' and 'OrderByClause' will be copied if any
* If either 'ParitionByExpr' and 'OrderByClause' exist then a 'PartitionByExpr' may not be specified.
* The 'OrderByClause' can _only_ be specified if the window copied does not have one.
* The copied window must not specify a 'FrameClause'.
@since 1.1.0.0
-}
windowDefinition ::
-- | An, optional, already existing named window that must be named in a prior @WINDOW@ entry.
Maybe WindowName ->
Maybe PartitionByExpr ->
Maybe OrderBy.OrderByClause ->
Maybe FrameClause ->
WindowDefinitionExpr
windowDefinition mbWindowName mbPartitionBy mbOrderBy mbFrame =
WindowDefinitionExpr
. RawSql.intercalate RawSql.space
$ catMaybes
[ fmap RawSql.toRawSql mbWindowName
, fmap RawSql.toRawSql mbPartitionBy
, fmap RawSql.toRawSql mbOrderBy
, fmap RawSql.toRawSql mbFrame
]
{- |
Type to represent the @PARTITION BY expression [, ...]@ portion of a SQL window definition.
'PartitionByExpr' provides a 'RawSql.SqlExpression' instance.
See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.
@since 1.1.0.0
-}
newtype PartitionByExpr = PartitionByExpr RawSql.RawSql
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
)
{- |
Builds a 'PartitionByExpr'.
@since 1.1.0.0
-}
partitionBy :: NEL.NonEmpty ValueExpression.ValueExpression -> PartitionByExpr
partitionBy exprs =
PartitionByExpr $
RawSql.fromString "PARTITION BY "
<> RawSql.intercalate RawSql.commaSpace exprs
{- |
Type to represent the framing clause of a window definition.
'FrameClause' provides a 'RawSql.SqlExpression' instance.
See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.
@since 1.1.0.0
-}
newtype FrameClause = FrameClause RawSql.RawSql
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
)
{- | Builds a 'FrameClause' from the given pieces.
@since 1.1.0.0
-}
frameClause :: Maybe FrameModeExpr -> FrameStartExpr -> Maybe FrameEndExpr -> Maybe FrameExclusionExpr -> FrameClause
frameClause mbFrameMode frameStart mbFrameEnd mbExclusion =
let
mode =
maybe mempty RawSql.toRawSql mbFrameMode
exclusion =
maybe mempty RawSql.toRawSql mbExclusion
in
FrameClause $
mode
<> RawSql.space
<> case mbFrameEnd of
Nothing -> RawSql.toRawSql frameStart
Just frameEnd ->
RawSql.fromString "BETWEEN "
<> RawSql.toRawSql frameStart
<> RawSql.fromString " AND "
<> RawSql.toRawSql frameEnd
<> RawSql.space
<> exclusion
{- |
Type to represent the mode of the framing in a window definition.
'FrameModeExpr' provides a 'RawSql.SqlExpression' instance.
See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.
@since 1.1.0.0
-}
newtype FrameModeExpr = FrameModeExpr RawSql.RawSql
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
)
{- | Value for the frame mode of @RANGE@
@since 1.1.0.0
-}
rangeFrameMode :: FrameModeExpr
rangeFrameMode = FrameModeExpr $ RawSql.fromString "RANGE"
{- | Value for the frame mode of @ROWS@
@since 1.1.0.0
-}
rowsFrameMode :: FrameModeExpr
rowsFrameMode = FrameModeExpr $ RawSql.fromString "ROWS"
{- | Value for the frame mode of @GROUPS@
@since 1.1.0.0
-}
groupsFrameMode :: FrameModeExpr
groupsFrameMode = FrameModeExpr $ RawSql.fromString "GROUPS"
{- |
Type to represent the beginning of the framing in a window definition.
'FrameStartExpr' provides a 'RawSql.SqlExpression' instance.
See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.
@since 1.1.0.0
-}
newtype FrameStartExpr = FrameStartExpr RawSql.RawSql
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
)
{- | Value for a frame start of @UNBOUNDED PRECEDING@
@since 1.1.0.0
-}
unboundedPrecedingFrameStart :: FrameStartExpr
unboundedPrecedingFrameStart = FrameStartExpr $ RawSql.fromString "UNBOUNDED PRECEDING"
{- | Value for a frame start of @expression PRECEDING@. Note that it is up to the caller to ensure
that the 'ValueExpression' is valid in this context.
@since 1.1.0.0
-}
offsetPrecedingFrameStart :: ValueExpression.ValueExpression -> FrameStartExpr
offsetPrecedingFrameStart val = FrameStartExpr $ RawSql.toRawSql val <> RawSql.fromString " PRECEDING"
{- | Value for a frame start of @CURRENT ROW@
@since 1.1.0.0
-}
currentRowFrameStart :: FrameStartExpr
currentRowFrameStart = FrameStartExpr $ RawSql.fromString "CURRENT ROW"
{- | Value for a frame start of @expression FOLLOWING@. Note that it is up to the caller to ensure
that the 'ValueExpression' is valid in this context.
@since 1.1.0.0
-}
offsetFollowingFrameStart :: ValueExpression.ValueExpression -> FrameStartExpr
offsetFollowingFrameStart val = FrameStartExpr $ RawSql.toRawSql val <> RawSql.fromString " FOLLOWING"
{- |
Type to represent the ending of the framing in a window definition.
'FrameEndExpr' provides a 'RawSql.SqlExpression' instance.
See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.
@since 1.1.0.0
-}
newtype FrameEndExpr = FrameEndExpr RawSql.RawSql
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
)
{- | Value for a frame end of @expression PRECEDING@. Note that it is up to the caller to ensure
that the 'ValueExpression' is valid in this context.
@since 1.1.0.0
-}
offsetPrecedingFrameEnd :: ValueExpression.ValueExpression -> FrameEndExpr
offsetPrecedingFrameEnd val = FrameEndExpr $ RawSql.toRawSql val <> RawSql.fromString " PRECEDING"
{- | Value for a frame end of @CURRENT ROW@
@since 1.1.0.0
-}
currentRowFrameEnd :: FrameEndExpr
currentRowFrameEnd = FrameEndExpr $ RawSql.fromString "CURRENT ROW"
{- | Value for a frame end of @expression FOLLOWING@. Note that it is up to the caller to ensure
that the 'ValueExpression' is valid in this context.
@since 1.1.0.0
-}
offsetFollowingFrameEnd :: ValueExpression.ValueExpression -> FrameEndExpr
offsetFollowingFrameEnd val = FrameEndExpr $ RawSql.toRawSql val <> RawSql.fromString " FOLLOWING"
{- | Value for a frame end of @UNBOUNDED FOLLOWING@
@since 1.1.0.0
-}
unboundedFollowingFrameEnd :: FrameEndExpr
unboundedFollowingFrameEnd = FrameEndExpr $ RawSql.fromString "UNBOUNDED FOLLOWING"
{- |
Type to represent the exclusion of results in the framing in a window definition.
'FrameExclusionExpr' provides a 'RawSql.SqlExpression' instance.
See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.
@since 1.1.0.0
-}
newtype FrameExclusionExpr = FrameExclusionExpr RawSql.RawSql
deriving
( -- | @since 1.1.0.0
RawSql.SqlExpression
)
{- | Value for a frame exclusion of @EXCLUDE CURRENT ROW@
@since 1.1.0.0
-}
currentRowFrameExclusion :: FrameExclusionExpr
currentRowFrameExclusion = FrameExclusionExpr $ RawSql.fromString "EXCLUDE CURRENT ROW"
{- | Value for a frame exclusion of @EXCLUDE GROUP@
@since 1.1.0.0
-}
groupFrameExclusion :: FrameExclusionExpr
groupFrameExclusion = FrameExclusionExpr $ RawSql.fromString "EXCLUDE GROUP"
{- | Value for a frame exclusion of @EXCLUDE TIES@
@since 1.1.0.0
-}
tiesFrameExclusion :: FrameExclusionExpr
tiesFrameExclusion = FrameExclusionExpr $ RawSql.fromString "EXCLUDE TIES"
{- | Value for a frame exclusion of @EXCLUDE NO OTHERS@
@since 1.1.0.0
-}
noOthersFrameExclusion :: FrameExclusionExpr
noOthersFrameExclusion = FrameExclusionExpr $ RawSql.fromString "EXCLUDE NO OTHERS"

View File

@ -0,0 +1,79 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{- |
Copyright : Flipstone Technology Partners 2024
License : MIT
Stability : Stable
@since 1.1.0.0
-}
module Orville.PostgreSQL.Expr.Window.WindowFunction
( rowNumber
, rank
, denseRank
, percentRank
, windowFunction
)
where
import Orville.PostgreSQL.Expr.Name (FunctionName, functionName)
import qualified Orville.PostgreSQL.Expr.ValueExpression as ValueExpression
import qualified Orville.PostgreSQL.Expr.WhereClause as WhereClause
import Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr (WindowDefinitionExpr)
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
{- |
The @row_number@ window function
@since 1.1.0.0
-}
rowNumber :: Maybe WhereClause.WhereClause -> WindowDefinitionExpr -> ValueExpression.ValueExpression
rowNumber = windowFunction (functionName "row_number") []
{- |
The @rank@ window function
@since 1.1.0.0
-}
rank :: Maybe WhereClause.WhereClause -> WindowDefinitionExpr -> ValueExpression.ValueExpression
rank = windowFunction (functionName "rank") []
{- |
The @dense_rank@ window function
@since 1.1.0.0
-}
denseRank :: Maybe WhereClause.WhereClause -> WindowDefinitionExpr -> ValueExpression.ValueExpression
denseRank = windowFunction (functionName "dense_rank") []
{- |
The @percent_rank@ window function
@since 1.1.0.0
-}
percentRank :: Maybe WhereClause.WhereClause -> WindowDefinitionExpr -> ValueExpression.ValueExpression
percentRank = windowFunction (functionName "percent_rank") []
{- | Build a 'ValueExpression' that represents a window function call. It is up to the caller to
ensure this makes sense as windowing functions are not allowed in all places.
@since 1.1.0.0
-}
windowFunction :: FunctionName -> [ValueExpression.ValueExpression] -> Maybe WhereClause.WhereClause -> WindowDefinitionExpr -> ValueExpression.ValueExpression
windowFunction function parameters mbWhereClause windowDef =
let
filtering =
case mbWhereClause of
Nothing -> mempty
Just whereClause ->
RawSql.fromString "FILTER"
<> RawSql.parenthesized (RawSql.toRawSql whereClause)
in
RawSql.unsafeFromRawSql $
RawSql.toRawSql (ValueExpression.functionCall function parameters)
<> RawSql.space
<> filtering
<> RawSql.space
<> RawSql.fromString "OVER"
<> RawSql.space
<> RawSql.toRawSql windowDef

View File

@ -32,6 +32,7 @@ import qualified Test.Expr.Time as ExprTime
import qualified Test.Expr.Trigger as ExprTrigger
import qualified Test.Expr.Vacuum as ExprVacuum
import qualified Test.Expr.Where as ExprWhere
import qualified Test.Expr.Window as ExprWindow
import qualified Test.FieldDefinition as FieldDefinition
import qualified Test.MarshallError as MarshallError
import qualified Test.PgCatalog as PgCatalog
@ -76,6 +77,7 @@ main = do
, ExprJoin.joinTests
, ExprFromItem.fromItemExprTests
, ExprConditional.conditionalTests
, ExprWindow.windowTests
, FieldDefinition.fieldDefinitionTests pool
, SqlMarshaller.sqlMarshallerTests
, MarshallError.marshallErrorTests pool

View File

@ -61,7 +61,7 @@ prop_countColumn =
(Expr.columnName "count")
]
)
(Just (Expr.tableExpr (Expr.tableFromItem $ Orville.tableName Foo.table) Nothing Nothing Nothing Nothing Nothing Nothing))
(Just (Expr.tableExpr (Expr.tableFromItem $ Orville.tableName Foo.table) Nothing Nothing Nothing Nothing Nothing Nothing Nothing))
marshaller =
Orville.annotateSqlMarshallerEmptyAnnotation $

View File

@ -97,7 +97,7 @@ groupByTest testName test =
Expr.queryExpr
(Expr.selectClause $ Expr.selectExpr Nothing)
(Expr.selectColumns [fooColumn, barColumn])
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing (groupByClause test) Nothing Nothing Nothing Nothing)
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing (groupByClause test) Nothing Nothing Nothing Nothing Nothing)
Execution.readRows result

View File

@ -87,7 +87,7 @@ groupByOrderByTest testName test =
Expr.queryExpr
(Expr.selectClause $ Expr.selectExpr Nothing)
(Expr.selectColumns [fooColumn, barColumn])
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing (groupByClause test) (orderByClause test) Nothing Nothing Nothing)
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing (groupByClause test) (orderByClause test) Nothing Nothing Nothing Nothing)
Execution.readRows result

View File

@ -127,7 +127,7 @@ orderByTest testName test =
Expr.queryExpr
(Expr.selectClause $ Expr.selectExpr Nothing)
(Expr.selectColumns [fooColumn, barColumn])
(Just $ Expr.tableExpr (Expr.tableFromItem fooBarTable) Nothing Nothing (orderByClause test) Nothing Nothing Nothing)
(Just $ Expr.tableExpr (Expr.tableFromItem fooBarTable) Nothing Nothing (orderByClause test) Nothing Nothing Nothing Nothing)
Execution.readRows result

View File

@ -83,7 +83,7 @@ findAllFooBarsInTable tableName =
Expr.queryExpr
(Expr.selectClause $ Expr.selectExpr Nothing)
(Expr.selectColumns [fooColumn, barColumnAliased])
(Just $ Expr.tableExpr tableRef Nothing Nothing (Just orderByFoo) Nothing Nothing Nothing)
(Just $ Expr.tableExpr tableRef Nothing Nothing (Just orderByFoo) Nothing Nothing Nothing Nothing)
encodeFooBar :: FooBar -> [(Maybe B8.ByteString, SqlValue.SqlValue)]
encodeFooBar fooBar =

View File

@ -252,7 +252,7 @@ whereConditionTest testName test =
Expr.queryExpr
(Expr.selectClause $ Expr.selectExpr Nothing)
(Expr.selectColumns [fooColumn, barColumn])
(Just $ Expr.tableExpr (Expr.tableFromItem fooBarTable) (whereClause test) Nothing Nothing Nothing Nothing Nothing)
(Just $ Expr.tableExpr (Expr.tableFromItem fooBarTable) (whereClause test) Nothing Nothing Nothing Nothing Nothing Nothing)
Execution.readRows result

View File

@ -0,0 +1,60 @@
module Test.Expr.Window
( windowTests
)
where
import qualified Data.ByteString.Char8 as B8
import GHC.Stack (HasCallStack, withFrozenCallStack)
import qualified Hedgehog as HH
import qualified Orville.PostgreSQL.Expr as Expr
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue
import Test.Expr.TestSchema (barColumn)
import qualified Test.Property as Property
windowTests :: Property.Group
windowTests =
Property.group
"Expr - Window"
[ prop_windowClauseSingleDef
, prop_windowClauseMultiDef
]
prop_windowClauseSingleDef :: Property.NamedProperty
prop_windowClauseSingleDef =
let
windowDef = Expr.windowDefinition Nothing (Just partitionBy) (Just orderBy) (Just frameClause)
windowDefName = Expr.fromIdentifier $ Expr.identifier "a"
partitionBy = Expr.partitionBy (pure . Expr.valueExpression $ SqlValue.fromInt32 1)
orderBy = Expr.orderByClause $ Expr.orderByColumnName barColumn Expr.ascendingOrder
frameClause = Expr.frameClause (Just Expr.rowsFrameMode) Expr.unboundedPrecedingFrameStart (Just Expr.unboundedFollowingFrameEnd) (Just Expr.tiesFrameExclusion)
in
Property.singletonNamedProperty "window clause with single window definition containing a partition by, an order by, and a frame clause generates expected sql."
. assertWindowEquals
"WINDOW \"a\" AS (PARTITION BY $1 ORDER BY \"bar\" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES)"
. Expr.windowClause
$ Expr.namedWindowDefinition windowDefName windowDef
prop_windowClauseMultiDef :: Property.NamedProperty
prop_windowClauseMultiDef =
let
windowDef1 = Expr.windowDefinition Nothing (Just partitionBy) Nothing Nothing
windowDef1Name = Expr.fromIdentifier $ Expr.identifier "a"
windowDef2 = Expr.windowDefinition (Just windowDef1Name) Nothing (Just orderBy) (Just frameClause)
windowDef2Name = Expr.fromIdentifier $ Expr.identifier "b"
partitionBy = Expr.partitionBy (pure . Expr.valueExpression $ SqlValue.fromInt32 1)
orderBy = Expr.orderByClause $ Expr.orderByColumnName barColumn Expr.ascendingOrder
frameClause = Expr.frameClause (Just Expr.rowsFrameMode) Expr.unboundedPrecedingFrameStart (Just Expr.unboundedFollowingFrameEnd) (Just Expr.tiesFrameExclusion)
in
Property.singletonNamedProperty "window clause with two window definitions, where the second \"copies\" the first, generates expected sql."
. assertWindowEquals
"WINDOW \"a\" AS (PARTITION BY $1), \"b\" AS (\"a\" ORDER BY \"bar\" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES)"
. Expr.windowClause
$ Expr.namedWindowDefinition windowDef1Name windowDef1 <> Expr.namedWindowDefinition windowDef2Name windowDef2
assertWindowEquals :: (HH.MonadTest m, HasCallStack) => String -> Expr.WindowClause -> m ()
assertWindowEquals windowClauseStr windowClause =
withFrozenCallStack $
RawSql.toExampleBytes windowClause HH.=== B8.pack windowClauseStr

View File

@ -257,7 +257,7 @@ runRoundTripTest pool testCase = do
Expr.queryExpr
(Expr.selectClause $ Expr.selectExpr Nothing)
(Expr.selectColumns [Marshall.fieldColumnName Nothing fieldDef])
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing Nothing Nothing Nothing Nothing Nothing)
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing Nothing Nothing Nothing Nothing Nothing Nothing)
Execution.readRows result
@ -302,7 +302,7 @@ runNullableRoundTripTest pool testCase = do
Expr.queryExpr
(Expr.selectClause $ Expr.selectExpr Nothing)
(Expr.selectColumns [Marshall.fieldColumnName Nothing fieldDef])
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing Nothing Nothing Nothing Nothing Nothing)
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing Nothing Nothing Nothing Nothing Nothing Nothing)
Execution.readRows result
@ -372,7 +372,7 @@ runDefaultValueFieldDefinitionTest pool testCase mkDefaultValue = do
Expr.queryExpr
(Expr.selectClause $ Expr.selectExpr Nothing)
(Expr.selectColumns [Marshall.fieldColumnName Nothing fieldDef])
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing Nothing Nothing Nothing Nothing Nothing)
(Just $ Expr.tableExpr (Expr.tableFromItem testTable) Nothing Nothing Nothing Nothing Nothing Nothing Nothing)
Execution.readRows result

View File

@ -545,7 +545,7 @@ runDecodingTest pool test =
Expr.queryExpr
(Expr.selectClause $ Expr.selectExpr Nothing)
Expr.selectStar
(Just $ Expr.tableExpr (Expr.tableFromItem tableName) Nothing Nothing Nothing Nothing Nothing Nothing)
(Just $ Expr.tableExpr (Expr.tableFromItem tableName) Nothing Nothing Nothing Nothing Nothing Nothing Nothing)
Execution.readRows result