packages feed

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
          ]