{-# LANGUAGE GeneralizedNewtypeDeriving #-}

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

@since 1.0.0.0
-}
module Orville.PostgreSQL.Expr.WhereClause
  ( WhereClause
  , whereClause
  , BooleanExpr
  , literalBooleanExpr
  , andExpr
  , (.&&)
  , orExpr
  , (.||)
  , parenthesized
  , equals
  , notEquals
  , greaterThan
  , lessThan
  , greaterThanOrEqualTo
  , lessThanOrEqualTo
  , like
  , likeInsensitive
  , isNull
  , isNotNull
  , valueIn
  , valueNotIn
  , tupleIn
  , tupleNotIn
  , InValuePredicate
  , inPredicate
  , notInPredicate
  , inValueList
  )
where

import qualified Data.List.NonEmpty as NE

import Orville.PostgreSQL.Expr.BinaryOperator (andOp, binaryOpExpression, equalsOp, greaterThanOp, greaterThanOrEqualsOp, iLikeOp, lessThanOp, lessThanOrEqualsOp, likeOp, notEqualsOp, orOp)
import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression, rowValueConstructor)
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql

{- |
Type to represent a @WHERE@ clause restriction on a @SELECT@, @UPDATE@ or
@DELETE@ statement. E.G.

> WHERE (foo > 10)

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

{- |
Constructs a @WHERE@ clause from the given 'BooleanExpr'. E.G.

> WHERE <boolean expr>

@since 1.0.0.0
-}
whereClause :: BooleanExpr -> WhereClause
whereClause :: BooleanExpr -> WhereClause
whereClause BooleanExpr
booleanExpr =
  RawSql -> WhereClause
WhereClause (RawSql -> WhereClause) -> RawSql -> WhereClause
forall a b. (a -> b) -> a -> b
$
    String -> RawSql
RawSql.fromString String
"WHERE " RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> BooleanExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql BooleanExpr
booleanExpr

{- |
Type to represent a SQL value expression that evaluates to a boolean and therefore
can used with boolean logic functions. E.G.

> foo > 10

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

{- |
  Constructs a 'BooleanExpr' whose value is the SQL literal @TRUE@ or @FALSE@
  depending on the argument given.

  @since 1.0.0.0
-}
literalBooleanExpr :: Bool -> BooleanExpr
literalBooleanExpr :: Bool -> BooleanExpr
literalBooleanExpr Bool
bool =
  RawSql -> BooleanExpr
BooleanExpr (RawSql -> BooleanExpr)
-> (String -> RawSql) -> String -> BooleanExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> RawSql
RawSql.fromString (String -> BooleanExpr) -> String -> BooleanExpr
forall a b. (a -> b) -> a -> b
$
    case Bool
bool of
      Bool
False -> String
"FALSE"
      Bool
True -> String
"TRUE"

{- |
  Converts a 'BooleanExpr' to a 'ValueExpression' so that it can be used
  anywhere 'ValueExpression' is allowed.

  @since 1.0.0.0
-}
booleanValueExpression :: BooleanExpr -> ValueExpression
booleanValueExpression :: BooleanExpr -> ValueExpression
booleanValueExpression (BooleanExpr RawSql
rawSql) =
  RawSql -> ValueExpression
forall a. SqlExpression a => RawSql -> a
RawSql.unsafeFromRawSql RawSql
rawSql

{- |
  The SQL @OR@ operator. The arguments will be surrounded with parentheses
  to ensure that the associativity of expression in the resulting SQL matches
  the associativity implied by this Haskell function.

  @since 1.0.0.0
-}
orExpr :: BooleanExpr -> BooleanExpr -> BooleanExpr
orExpr :: BooleanExpr -> BooleanExpr -> BooleanExpr
orExpr BooleanExpr
left BooleanExpr
right =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression
    BinaryOperator
orOp
    (BooleanExpr -> ValueExpression
booleanValueExpression BooleanExpr
left)
    (BooleanExpr -> ValueExpression
booleanValueExpression BooleanExpr
right)

{- |
  The SQL @OR@ operator (alias for 'orExpr').

  @since 1.0.0.0
-}
(.||) :: BooleanExpr -> BooleanExpr -> BooleanExpr
.|| :: BooleanExpr -> BooleanExpr -> BooleanExpr
(.||) = BooleanExpr -> BooleanExpr -> BooleanExpr
orExpr

infixr 8 .||

{- |
  The SQL @AND@ operator. The arguments will be surrounded with parentheses
  to ensure that the associativity of expression in the resulting SQL matches
  the associativity implied by this Haskell function.

  @since 1.0.0.0
-}
andExpr :: BooleanExpr -> BooleanExpr -> BooleanExpr
andExpr :: BooleanExpr -> BooleanExpr -> BooleanExpr
andExpr BooleanExpr
left BooleanExpr
right =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression
    BinaryOperator
andOp
    (BooleanExpr -> ValueExpression
booleanValueExpression BooleanExpr
left)
    (BooleanExpr -> ValueExpression
booleanValueExpression BooleanExpr
right)

{- |
  The SQL @AND@ operator (alias for 'andExpr').

  @since 1.0.0.0
-}
(.&&) :: BooleanExpr -> BooleanExpr -> BooleanExpr
.&& :: BooleanExpr -> BooleanExpr -> BooleanExpr
(.&&) = BooleanExpr -> BooleanExpr -> BooleanExpr
andExpr

infixr 8 .&&

{- |
  The SQL @IN@ operator. The result will be @TRUE@ if the given value
  appears in the list of values given.

  @since 1.0.0.0
-}
valueIn :: ValueExpression -> NE.NonEmpty ValueExpression -> BooleanExpr
valueIn :: ValueExpression -> NonEmpty ValueExpression -> BooleanExpr
valueIn ValueExpression
needle NonEmpty ValueExpression
haystack =
  ValueExpression -> InValuePredicate -> BooleanExpr
inPredicate ValueExpression
needle (NonEmpty ValueExpression -> InValuePredicate
inValueList NonEmpty ValueExpression
haystack)

{- |
  The SQL @NOT IN@ operator. The result will be @TRUE@ if the given value
  does not appear in the list of values given.

  @since 1.0.0.0
-}
valueNotIn :: ValueExpression -> NE.NonEmpty ValueExpression -> BooleanExpr
valueNotIn :: ValueExpression -> NonEmpty ValueExpression -> BooleanExpr
valueNotIn ValueExpression
needle NonEmpty ValueExpression
haystack =
  ValueExpression -> InValuePredicate -> BooleanExpr
notInPredicate ValueExpression
needle (NonEmpty ValueExpression -> InValuePredicate
inValueList NonEmpty ValueExpression
haystack)

{- |
  The SQL @IN@ operator, like 'valueIn', but for when you want to construct a
  tuple in SQL and check if it is in a list of tuples. It is up to the caller
  to ensure that all the tuples given have the same arity.

  @since 1.0.0.0
-}
tupleIn :: NE.NonEmpty ValueExpression -> NE.NonEmpty (NE.NonEmpty ValueExpression) -> BooleanExpr
tupleIn :: NonEmpty ValueExpression
-> NonEmpty (NonEmpty ValueExpression) -> BooleanExpr
tupleIn NonEmpty ValueExpression
needle NonEmpty (NonEmpty ValueExpression)
haystack =
  ValueExpression -> InValuePredicate -> BooleanExpr
inPredicate
    (NonEmpty ValueExpression -> ValueExpression
rowValueConstructor NonEmpty ValueExpression
needle)
    (NonEmpty ValueExpression -> InValuePredicate
inValueList ((NonEmpty ValueExpression -> ValueExpression)
-> NonEmpty (NonEmpty ValueExpression) -> NonEmpty ValueExpression
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap NonEmpty ValueExpression -> ValueExpression
rowValueConstructor NonEmpty (NonEmpty ValueExpression)
haystack))

{- |
  The SQL @NOT IN@ operator, like 'valueNotIn', but for when you want to
  construct a tuple in SQL and check if it is not in a list of tuples. It is up
  to the caller to ensure that all the tuples given have the same arity.

  @since 1.0.0.0
-}
tupleNotIn :: NE.NonEmpty ValueExpression -> NE.NonEmpty (NE.NonEmpty ValueExpression) -> BooleanExpr
tupleNotIn :: NonEmpty ValueExpression
-> NonEmpty (NonEmpty ValueExpression) -> BooleanExpr
tupleNotIn NonEmpty ValueExpression
needle NonEmpty (NonEmpty ValueExpression)
haystack =
  ValueExpression -> InValuePredicate -> BooleanExpr
notInPredicate
    (NonEmpty ValueExpression -> ValueExpression
rowValueConstructor NonEmpty ValueExpression
needle)
    (NonEmpty ValueExpression -> InValuePredicate
inValueList ((NonEmpty ValueExpression -> ValueExpression)
-> NonEmpty (NonEmpty ValueExpression) -> NonEmpty ValueExpression
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap NonEmpty ValueExpression -> ValueExpression
rowValueConstructor NonEmpty (NonEmpty ValueExpression)
haystack))

{- |
  Lower-level access to the SQL @IN@ operator. This takes any 'ValueExpression'
  and 'InValuePredicate'. It is up to the caller to ensure the expressions
  given make sense together.

  @since 1.0.0.0
-}
inPredicate :: ValueExpression -> InValuePredicate -> BooleanExpr
inPredicate :: ValueExpression -> InValuePredicate -> BooleanExpr
inPredicate ValueExpression
predicand InValuePredicate
predicate =
  RawSql -> BooleanExpr
BooleanExpr (RawSql -> BooleanExpr) -> RawSql -> BooleanExpr
forall a b. (a -> b) -> a -> b
$
    ValueExpression -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.parenthesized ValueExpression
predicand
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
" IN "
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> InValuePredicate -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql InValuePredicate
predicate

{- |
  Lower-level access to the SQL @NOT IN@ operator. This takes any
  'ValueExpression' and 'InValuePredicate'. It is up to the caller to ensure
  the expressions given make sense together.

  @since 1.0.0.0
-}
notInPredicate :: ValueExpression -> InValuePredicate -> BooleanExpr
notInPredicate :: ValueExpression -> InValuePredicate -> BooleanExpr
notInPredicate ValueExpression
predicand InValuePredicate
predicate =
  RawSql -> BooleanExpr
BooleanExpr (RawSql -> BooleanExpr) -> RawSql -> BooleanExpr
forall a b. (a -> b) -> a -> b
$
    ValueExpression -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.parenthesized ValueExpression
predicand
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
" NOT IN "
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> InValuePredicate -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql InValuePredicate
predicate

{- |
Type to represent the right hand side of an @IN@ or @NOT IN@ expression.
E.G.

> (10,12,13)

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

{- |
  Constructs an 'InValuePredicate' from the given list of 'ValueExpression'.

  @since 1.0.0.0
-}
inValueList :: NE.NonEmpty ValueExpression -> InValuePredicate
inValueList :: NonEmpty ValueExpression -> InValuePredicate
inValueList NonEmpty ValueExpression
values =
  RawSql -> InValuePredicate
InValuePredicate (RawSql -> InValuePredicate) -> RawSql -> InValuePredicate
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.commaSpace NonEmpty ValueExpression
values
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.rightParen

{- |
  Surrounds the given 'BooleanExpr' with parentheses.

  @since 1.0.0.0
-}
parenthesized :: BooleanExpr -> BooleanExpr
parenthesized :: BooleanExpr -> BooleanExpr
parenthesized BooleanExpr
expr =
  RawSql -> BooleanExpr
BooleanExpr (RawSql -> BooleanExpr) -> RawSql -> BooleanExpr
forall a b. (a -> b) -> a -> b
$
    RawSql
RawSql.leftParen RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> BooleanExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql BooleanExpr
expr RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.rightParen

{- |
  The SQL @=@ operator.

  @since 1.0.0.0
-}
equals :: ValueExpression -> ValueExpression -> BooleanExpr
equals :: ValueExpression -> ValueExpression -> BooleanExpr
equals =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression BinaryOperator
equalsOp

{- |
  The SQL @<>@ operator.

  @since 1.0.0.0
-}
notEquals :: ValueExpression -> ValueExpression -> BooleanExpr
notEquals :: ValueExpression -> ValueExpression -> BooleanExpr
notEquals =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression BinaryOperator
notEqualsOp

{- |
  The SQL @>@ operator.

  @since 1.0.0.0
-}
greaterThan :: ValueExpression -> ValueExpression -> BooleanExpr
greaterThan :: ValueExpression -> ValueExpression -> BooleanExpr
greaterThan =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression BinaryOperator
greaterThanOp

{- |
  The SQL @<@ operator.

  @since 1.0.0.0
-}
lessThan :: ValueExpression -> ValueExpression -> BooleanExpr
lessThan :: ValueExpression -> ValueExpression -> BooleanExpr
lessThan =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression BinaryOperator
lessThanOp

{- |
  The SQL @>=@ operator.

  @since 1.0.0.0
-}
greaterThanOrEqualTo :: ValueExpression -> ValueExpression -> BooleanExpr
greaterThanOrEqualTo :: ValueExpression -> ValueExpression -> BooleanExpr
greaterThanOrEqualTo =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression BinaryOperator
greaterThanOrEqualsOp

{- |
  The SQL @<=@ operator.

  @since 1.0.0.0
-}
lessThanOrEqualTo :: ValueExpression -> ValueExpression -> BooleanExpr
lessThanOrEqualTo :: ValueExpression -> ValueExpression -> BooleanExpr
lessThanOrEqualTo =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression BinaryOperator
lessThanOrEqualsOp

{- |
  The SQL @LIKE@ operator.

  @since 1.0.0.0
-}
like :: ValueExpression -> ValueExpression -> BooleanExpr
like :: ValueExpression -> ValueExpression -> BooleanExpr
like =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression BinaryOperator
likeOp

{- |
  The SQL @ILIKE@ operator.

  @since 1.0.0.0
-}
likeInsensitive :: ValueExpression -> ValueExpression -> BooleanExpr
likeInsensitive :: ValueExpression -> ValueExpression -> BooleanExpr
likeInsensitive =
  BinaryOperator -> ValueExpression -> ValueExpression -> BooleanExpr
forall sql.
SqlExpression sql =>
BinaryOperator -> ValueExpression -> ValueExpression -> sql
binaryOpExpression BinaryOperator
iLikeOp

{- |
  The SQL @IS NULL@ condition.

  @since 1.0.0.0
-}
isNull :: ValueExpression -> BooleanExpr
isNull :: ValueExpression -> BooleanExpr
isNull ValueExpression
value =
  RawSql -> BooleanExpr
BooleanExpr (RawSql -> BooleanExpr) -> RawSql -> BooleanExpr
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
<> RawSql
RawSql.space
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
"IS NULL"

{- |
  The SQL @IS NOT NULL@ condition.

  @since 1.0.0.0
-}
isNotNull :: ValueExpression -> BooleanExpr
isNotNull :: ValueExpression -> BooleanExpr
isNotNull ValueExpression
value =
  RawSql -> BooleanExpr
BooleanExpr (RawSql -> BooleanExpr) -> RawSql -> BooleanExpr
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
<> RawSql
RawSql.space
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
"IS NOT NULL"