{-# LANGUAGE GeneralizedNewtypeDeriving #-}

{- |
Copyright : Flipstone Technology Partners 2023
License   : MIT
Stability : Stable

@since 1.0.0.0
-}
module Orville.PostgreSQL.Expr.ValueExpression
  ( ValueExpression
  , cast
  , ParameterName
  , columnReference
  , valueExpression
  , rowValueConstructor
  , functionCall
  , functionCallNamedParams
  )
where

import qualified Data.List.NonEmpty as NE

import Orville.PostgreSQL.Expr.DataType (DataType)
import Orville.PostgreSQL.Expr.Name (ColumnName, FunctionName)
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
import Orville.PostgreSQL.Raw.SqlValue (SqlValue)

{- |
Type to represent an arbitrary value in a SQL expression. This could be a
constant value, a column reference or any arbitrary calculated expression.
E.G.

> (foo + bar) > 20

'ValueExpression' 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 ValueExpression = ValueExpression RawSql.RawSql
  deriving (RawSql -> ValueExpression
ValueExpression -> RawSql
(ValueExpression -> RawSql)
-> (RawSql -> ValueExpression) -> SqlExpression ValueExpression
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: ValueExpression -> RawSql
toRawSql :: ValueExpression -> RawSql
$cunsafeFromRawSql :: RawSql -> ValueExpression
unsafeFromRawSql :: RawSql -> ValueExpression
RawSql.SqlExpression)

{- |
Performs a SQL type cast to the specified type on the given 'ValueExpression'.
E.G.

> foo :: integer

@since 1.0.0.0
-}
cast :: ValueExpression -> DataType -> ValueExpression
cast :: ValueExpression -> DataType -> ValueExpression
cast ValueExpression
value DataType
dataType =
  RawSql -> ValueExpression
ValueExpression (RawSql -> ValueExpression) -> RawSql -> ValueExpression
forall a b. (a -> b) -> a -> b
$
    ValueExpression -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql ValueExpression
value
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
"::"
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> DataType -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql DataType
dataType

{- |
Uses a 'ColumnName' to reference a column as a 'ValueExpression'. This
is the equivalent of simply writing the column name as the expression. E.G.

> foo

@since 1.0.0.0
-}
columnReference :: ColumnName -> ValueExpression
columnReference :: ColumnName -> ValueExpression
columnReference = RawSql -> ValueExpression
ValueExpression (RawSql -> ValueExpression)
-> (ColumnName -> RawSql) -> ColumnName -> ValueExpression
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ColumnName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql

{- |
  Uses the given 'SqlValue' as a constant expression. The value will be passed
  as a statement parameter, not as a literal expression, so there is not need
  to worry about escaping. However, there are a few places (usually in DDL)
  where PostgreSQL does not support values passed as parameters where this
  cannot be used.

  @since 1.0.0.0
-}
valueExpression :: SqlValue -> ValueExpression
valueExpression :: SqlValue -> ValueExpression
valueExpression = RawSql -> ValueExpression
ValueExpression (RawSql -> ValueExpression)
-> (SqlValue -> RawSql) -> SqlValue -> ValueExpression
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SqlValue -> RawSql
RawSql.parameter

{- |
Constructs a PostgreSQL row value expression from the given list of
expressions. E.G.

> (foo, bar, now())

@since 1.0.0.0
-}
rowValueConstructor :: NE.NonEmpty ValueExpression -> ValueExpression
rowValueConstructor :: NonEmpty ValueExpression -> ValueExpression
rowValueConstructor NonEmpty ValueExpression
elements =
  RawSql -> ValueExpression
ValueExpression (RawSql -> ValueExpression) -> RawSql -> ValueExpression
forall a b. (a -> b) -> a -> b
$
    RawSql
RawSql.leftParen
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql -> NonEmpty ValueExpression -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
RawSql.comma NonEmpty ValueExpression
elements
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.rightParen

{- |
Constructs a 'ValueExpression' that will call the specified PostgreSQL
function with the given arguments passed as position parameters. E.G.

> nextval(sequence_name)

@since 1.0.0.0
-}
functionCall :: FunctionName -> [ValueExpression] -> ValueExpression
functionCall :: FunctionName -> [ValueExpression] -> ValueExpression
functionCall FunctionName
functionName [ValueExpression]
parameters =
  RawSql -> ValueExpression
ValueExpression (RawSql -> ValueExpression) -> RawSql -> ValueExpression
forall a b. (a -> b) -> a -> b
$
    FunctionName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql FunctionName
functionName
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.leftParen
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql -> [ValueExpression] -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
RawSql.comma [ValueExpression]
parameters
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.rightParen

{- |
Type to represent the name of a name parameter in a PostgreSQL function call.
E.G.

> foo

in

> some_func(foo => 1)

'ParameterName' 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 ParameterName = ParameterName RawSql.RawSql
  deriving (RawSql -> ParameterName
ParameterName -> RawSql
(ParameterName -> RawSql)
-> (RawSql -> ParameterName) -> SqlExpression ParameterName
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: ParameterName -> RawSql
toRawSql :: ParameterName -> RawSql
$cunsafeFromRawSql :: RawSql -> ParameterName
unsafeFromRawSql :: RawSql -> ParameterName
RawSql.SqlExpression)

{- |
Constructs a 'ValueExpression' that will call the specified PostgreSQL
function with the given arguments passed as named parameters. E.G.

> make_interval(years => 1)

@since 1.0.0.0
-}
functionCallNamedParams :: FunctionName -> [(ParameterName, ValueExpression)] -> ValueExpression
functionCallNamedParams :: FunctionName
-> [(ParameterName, ValueExpression)] -> ValueExpression
functionCallNamedParams FunctionName
functionName [(ParameterName, ValueExpression)]
parameters =
  RawSql -> ValueExpression
ValueExpression (RawSql -> ValueExpression) -> RawSql -> ValueExpression
forall a b. (a -> b) -> a -> b
$
    FunctionName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql FunctionName
functionName
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.leftParen
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql -> [RawSql] -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
RawSql.comma (((ParameterName, ValueExpression) -> RawSql)
-> [(ParameterName, ValueExpression)] -> [RawSql]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((ParameterName -> ValueExpression -> RawSql)
-> (ParameterName, ValueExpression) -> RawSql
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry ParameterName -> ValueExpression -> RawSql
namedParameterArgument) [(ParameterName, ValueExpression)]
parameters)
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.rightParen

{- |
  Constructs a sql fragment that will pass the given named argument with the
  specified value.

  @since 1.0.0.0
-}
namedParameterArgument :: ParameterName -> ValueExpression -> RawSql.RawSql
namedParameterArgument :: ParameterName -> ValueExpression -> RawSql
namedParameterArgument ParameterName
name ValueExpression
value =
  ParameterName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql ParameterName
name
    RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.space
    RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
"=>"
    RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.space
    RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> ValueExpression -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql ValueExpression
value