orville-postgresql-1.0.0.0: src/Orville/PostgreSQL/Expr/Query.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{- |
Copyright : Flipstone Technology Partners 2023
License : MIT
Stability : Stable
@since 1.0.0.0
-}
module Orville.PostgreSQL.Expr.Query
( QueryExpr
, queryExpr
, SelectList
, selectColumns
, DerivedColumn
, deriveColumn
, deriveColumnAs
, selectDerivedColumns
, selectStar
, TableExpr
, tableExpr
)
where
import Data.Maybe (catMaybes, fromMaybe)
import Orville.PostgreSQL.Expr.GroupBy (GroupByClause)
import Orville.PostgreSQL.Expr.LimitExpr (LimitExpr)
import Orville.PostgreSQL.Expr.Name (ColumnName)
import Orville.PostgreSQL.Expr.OffsetExpr (OffsetExpr)
import Orville.PostgreSQL.Expr.OrderBy (OrderByClause)
import Orville.PostgreSQL.Expr.Select (SelectClause)
import Orville.PostgreSQL.Expr.TableReferenceList (TableReferenceList)
import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression, columnReference)
import Orville.PostgreSQL.Expr.WhereClause (WhereClause)
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
{- |
Type to represent a SQL query, E.G.
> SELECT id FROM some_table
'QueryExpr' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.
@since 1.0.0.0
-}
newtype QueryExpr
= QueryExpr RawSql.RawSql
deriving (RawSql.SqlExpression)
{- |
Builds a 'QueryExpr' from the given 'SelectClause', 'SelectList' and
'TableExpr'. The resulting 'QueryExpr' is suitable for execution via the SQL
execution functions in "Orville.PostgreSQL.Execution" and
"Orville.PostgreSQL.Raw.RawSql".
@since 1.0.0.0
-}
queryExpr :: SelectClause -> SelectList -> Maybe TableExpr -> QueryExpr
queryExpr querySelectClause selectList maybeTableExpr =
let
maybeFromClause = do
table <- maybeTableExpr
pure (RawSql.fromString " FROM " <> RawSql.toRawSql table)
in
QueryExpr $
mconcat
[ RawSql.toRawSql querySelectClause
, RawSql.toRawSql selectList
, fromMaybe (RawSql.fromString "") maybeFromClause
]
{- |
Type to represent the list of items to be selected in a @SELECT@ clause.
E.G. the
> foo, bar, baz
in
> SELECT foo, bar, baz FROM some_table
'SelectList' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.
@since 1.0.0.0
-}
newtype SelectList = SelectList RawSql.RawSql
deriving (RawSql.SqlExpression)
{- |
Constructs a 'SelectList' that will select all colums (i.e. the @*@ in
@SELECT *@").
@since 1.0.0.0
-}
selectStar :: SelectList
selectStar =
SelectList (RawSql.fromString "*")
{- |
Constructs a 'SelectList' that will select the specified column names. This
is a special case of 'selectDerivedColumns' where all the items to be
selected are simple column references.
@since 1.0.0.0
-}
selectColumns :: [ColumnName] -> SelectList
selectColumns =
selectDerivedColumns . map (deriveColumn . columnReference)
{- |
Type to represent an individual item in a list of selected items. E.G.
> now() as current_time
'DerivedColumn' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.
@since 1.0.0.0
-}
newtype DerivedColumn = DerivedColumn RawSql.RawSql
deriving (RawSql.SqlExpression)
{- |
Constructs a 'SelectList' that will select the specified items, which may be
column references or other expressions as allowed by 'DerivedColumn'. See
also 'selectColumns' the simpler case of selecting a list of column names.
@since 1.0.0.0
-}
selectDerivedColumns :: [DerivedColumn] -> SelectList
selectDerivedColumns =
SelectList . RawSql.intercalate RawSql.comma
{- |
Constructs a 'DerivedColumn' that will select the given value. No name will
be given to the value in the result set. See 'deriveColumnAs' to give the
value a name in the result set.
@since 1.0.0.0
-}
deriveColumn :: ValueExpression -> DerivedColumn
deriveColumn =
DerivedColumn . RawSql.toRawSql
{- |
Constructs a 'DerivedColumn' that will select the given value and give it
the specified column name in the result set.
@since 1.0.0.0
-}
deriveColumnAs :: ValueExpression -> ColumnName -> DerivedColumn
deriveColumnAs valueExpr asColumn =
DerivedColumn
( RawSql.toRawSql valueExpr
<> RawSql.fromString " AS "
<> RawSql.toRawSql asColumn
)
{- |
Type to represent a table expression (including its associated options) in a
@SELECT@. This is the part that would appear *after* the word @FROM@. E.G.
> foo
> WHERE id > 100
> ORDER BY id
> LIMIT 1
> OFFSET 2
'TableExpr' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.
@since 1.0.0.0
-}
newtype TableExpr
= TableExpr RawSql.RawSql
deriving (RawSql.SqlExpression)
{- |
Constructs a 'TableExpr' with the given options.
@since 1.0.0.0
-}
tableExpr ::
-- | The list of tables to query from.
TableReferenceList ->
-- | An optional @WHERE@ clause to limit the results returned.
Maybe WhereClause ->
-- | An optional @GROUP BY@ clause to group the result set rows.
Maybe GroupByClause ->
-- | An optional @ORDER BY@ clause to order the result set rows.
Maybe OrderByClause ->
-- | An optional @LIMIT@ to apply to the result set.
Maybe LimitExpr ->
-- | An optional @OFFSET@ to apply to the result set.
Maybe OffsetExpr ->
TableExpr
tableExpr
tableReferenceList
maybeWhereClause
maybeGroupByClause
maybeOrderByClause
maybeLimitExpr
maybeOffsetExpr =
TableExpr
. RawSql.intercalate RawSql.space
$ RawSql.toRawSql tableReferenceList
: catMaybes
[ RawSql.toRawSql <$> maybeWhereClause
, RawSql.toRawSql <$> maybeGroupByClause
, RawSql.toRawSql <$> maybeOrderByClause
, RawSql.toRawSql <$> maybeLimitExpr
, RawSql.toRawSql <$> maybeOffsetExpr
]