{-# 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 -> QueryExpr
QueryExpr -> RawSql
(QueryExpr -> RawSql)
-> (RawSql -> QueryExpr) -> SqlExpression QueryExpr
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: QueryExpr -> RawSql
toRawSql :: QueryExpr -> RawSql
$cunsafeFromRawSql :: RawSql -> QueryExpr
unsafeFromRawSql :: RawSql -> QueryExpr
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 :: SelectClause -> SelectList -> Maybe TableExpr -> QueryExpr
queryExpr SelectClause
querySelectClause SelectList
selectList Maybe TableExpr
maybeTableExpr =
  let
    maybeFromClause :: Maybe RawSql
maybeFromClause = do
      TableExpr
table <- Maybe TableExpr
maybeTableExpr
      RawSql -> Maybe RawSql
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (String -> RawSql
RawSql.fromString String
" FROM " RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> TableExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql TableExpr
table)
  in
    RawSql -> QueryExpr
QueryExpr (RawSql -> QueryExpr) -> RawSql -> QueryExpr
forall a b. (a -> b) -> a -> b
$
      [RawSql] -> RawSql
forall a. Monoid a => [a] -> a
mconcat
        [ SelectClause -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql SelectClause
querySelectClause
        , SelectList -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql SelectList
selectList
        , RawSql -> Maybe RawSql -> RawSql
forall a. a -> Maybe a -> a
fromMaybe (String -> RawSql
RawSql.fromString String
"") Maybe RawSql
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 -> SelectList
SelectList -> RawSql
(SelectList -> RawSql)
-> (RawSql -> SelectList) -> SqlExpression SelectList
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: SelectList -> RawSql
toRawSql :: SelectList -> RawSql
$cunsafeFromRawSql :: RawSql -> SelectList
unsafeFromRawSql :: RawSql -> SelectList
RawSql.SqlExpression)

{- |
  Constructs a 'SelectList' that will select all colums (i.e. the @*@ in
  @SELECT *@").

  @since 1.0.0.0
-}
selectStar :: SelectList
selectStar :: SelectList
selectStar =
  RawSql -> SelectList
SelectList (String -> RawSql
RawSql.fromString String
"*")

{- |
  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 :: [ColumnName] -> SelectList
selectColumns =
  [DerivedColumn] -> SelectList
selectDerivedColumns ([DerivedColumn] -> SelectList)
-> ([ColumnName] -> [DerivedColumn]) -> [ColumnName] -> SelectList
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ColumnName -> DerivedColumn) -> [ColumnName] -> [DerivedColumn]
forall a b. (a -> b) -> [a] -> [b]
map (ValueExpression -> DerivedColumn
deriveColumn (ValueExpression -> DerivedColumn)
-> (ColumnName -> ValueExpression) -> ColumnName -> DerivedColumn
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ColumnName -> ValueExpression
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 -> DerivedColumn
DerivedColumn -> RawSql
(DerivedColumn -> RawSql)
-> (RawSql -> DerivedColumn) -> SqlExpression DerivedColumn
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: DerivedColumn -> RawSql
toRawSql :: DerivedColumn -> RawSql
$cunsafeFromRawSql :: RawSql -> DerivedColumn
unsafeFromRawSql :: RawSql -> DerivedColumn
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 :: [DerivedColumn] -> SelectList
selectDerivedColumns =
  RawSql -> SelectList
SelectList (RawSql -> SelectList)
-> ([DerivedColumn] -> RawSql) -> [DerivedColumn] -> SelectList
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RawSql -> [DerivedColumn] -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
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 :: ValueExpression -> DerivedColumn
deriveColumn =
  RawSql -> DerivedColumn
DerivedColumn (RawSql -> DerivedColumn)
-> (ValueExpression -> RawSql) -> ValueExpression -> DerivedColumn
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ValueExpression -> RawSql
forall a. SqlExpression a => a -> RawSql
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 :: ValueExpression -> ColumnName -> DerivedColumn
deriveColumnAs ValueExpression
valueExpr ColumnName
asColumn =
  RawSql -> DerivedColumn
DerivedColumn
    ( ValueExpression -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql ValueExpression
valueExpr
        RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
" AS "
        RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> ColumnName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql ColumnName
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 -> TableExpr
TableExpr -> RawSql
(TableExpr -> RawSql)
-> (RawSql -> TableExpr) -> SqlExpression TableExpr
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: TableExpr -> RawSql
toRawSql :: TableExpr -> RawSql
$cunsafeFromRawSql :: RawSql -> TableExpr
unsafeFromRawSql :: RawSql -> TableExpr
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
-> Maybe WhereClause
-> Maybe GroupByClause
-> Maybe OrderByClause
-> Maybe LimitExpr
-> Maybe OffsetExpr
-> TableExpr
tableExpr
  TableReferenceList
tableReferenceList
  Maybe WhereClause
maybeWhereClause
  Maybe GroupByClause
maybeGroupByClause
  Maybe OrderByClause
maybeOrderByClause
  Maybe LimitExpr
maybeLimitExpr
  Maybe OffsetExpr
maybeOffsetExpr =
    RawSql -> TableExpr
TableExpr
      (RawSql -> TableExpr)
-> ([RawSql] -> RawSql) -> [RawSql] -> TableExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RawSql -> [RawSql] -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
RawSql.space
      ([RawSql] -> TableExpr) -> [RawSql] -> TableExpr
forall a b. (a -> b) -> a -> b
$ TableReferenceList -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql TableReferenceList
tableReferenceList
        RawSql -> [RawSql] -> [RawSql]
forall a. a -> [a] -> [a]
: [Maybe RawSql] -> [RawSql]
forall a. [Maybe a] -> [a]
catMaybes
          [ WhereClause -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql (WhereClause -> RawSql) -> Maybe WhereClause -> Maybe RawSql
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe WhereClause
maybeWhereClause
          , GroupByClause -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql (GroupByClause -> RawSql) -> Maybe GroupByClause -> Maybe RawSql
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe GroupByClause
maybeGroupByClause
          , OrderByClause -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql (OrderByClause -> RawSql) -> Maybe OrderByClause -> Maybe RawSql
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe OrderByClause
maybeOrderByClause
          , LimitExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql (LimitExpr -> RawSql) -> Maybe LimitExpr -> Maybe RawSql
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe LimitExpr
maybeLimitExpr
          , OffsetExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql (OffsetExpr -> RawSql) -> Maybe OffsetExpr -> Maybe RawSql
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe OffsetExpr
maybeOffsetExpr
          ]