packages feed

orville-postgresql-1.0.0.0: src/Orville/PostgreSQL/Expr/WhereClause.hs

{-# 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.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 $
    RawSql.fromString "WHERE " <> RawSql.toRawSql 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.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 . RawSql.fromString $
    case bool of
      False -> "FALSE"
      True -> "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 rawSql) =
  RawSql.unsafeFromRawSql 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 left right =
  binaryOpExpression
    orOp
    (booleanValueExpression left)
    (booleanValueExpression right)

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

  @since 1.0.0.0
-}
(.||) :: 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 left right =
  binaryOpExpression
    andOp
    (booleanValueExpression left)
    (booleanValueExpression right)

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

  @since 1.0.0.0
-}
(.&&) :: 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 needle haystack =
  inPredicate needle (inValueList 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 needle haystack =
  notInPredicate needle (inValueList 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 needle haystack =
  inPredicate
    (rowValueConstructor needle)
    (inValueList (fmap rowValueConstructor 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 needle haystack =
  notInPredicate
    (rowValueConstructor needle)
    (inValueList (fmap rowValueConstructor 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 predicand predicate =
  BooleanExpr $
    RawSql.parenthesized predicand
      <> RawSql.fromString " IN "
      <> RawSql.toRawSql 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 predicand predicate =
  BooleanExpr $
    RawSql.parenthesized predicand
      <> RawSql.fromString " NOT IN "
      <> RawSql.toRawSql 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.SqlExpression)

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

  @since 1.0.0.0
-}
inValueList :: NE.NonEmpty ValueExpression -> InValuePredicate
inValueList values =
  InValuePredicate $
    RawSql.leftParen
      <> RawSql.intercalate RawSql.commaSpace values
      <> RawSql.rightParen

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

  @since 1.0.0.0
-}
parenthesized :: BooleanExpr -> BooleanExpr
parenthesized expr =
  BooleanExpr $
    RawSql.leftParen <> RawSql.toRawSql expr <> RawSql.rightParen

{- |
  The SQL @=@ operator.

  @since 1.0.0.0
-}
equals :: ValueExpression -> ValueExpression -> BooleanExpr
equals =
  binaryOpExpression equalsOp

{- |
  The SQL @<>@ operator.

  @since 1.0.0.0
-}
notEquals :: ValueExpression -> ValueExpression -> BooleanExpr
notEquals =
  binaryOpExpression notEqualsOp

{- |
  The SQL @>@ operator.

  @since 1.0.0.0
-}
greaterThan :: ValueExpression -> ValueExpression -> BooleanExpr
greaterThan =
  binaryOpExpression greaterThanOp

{- |
  The SQL @<@ operator.

  @since 1.0.0.0
-}
lessThan :: ValueExpression -> ValueExpression -> BooleanExpr
lessThan =
  binaryOpExpression lessThanOp

{- |
  The SQL @>=@ operator.

  @since 1.0.0.0
-}
greaterThanOrEqualTo :: ValueExpression -> ValueExpression -> BooleanExpr
greaterThanOrEqualTo =
  binaryOpExpression greaterThanOrEqualsOp

{- |
  The SQL @<=@ operator.

  @since 1.0.0.0
-}
lessThanOrEqualTo :: ValueExpression -> ValueExpression -> BooleanExpr
lessThanOrEqualTo =
  binaryOpExpression lessThanOrEqualsOp

{- |
  The SQL @LIKE@ operator.

  @since 1.0.0.0
-}
like :: ValueExpression -> ValueExpression -> BooleanExpr
like =
  binaryOpExpression likeOp

{- |
  The SQL @ILIKE@ operator.

  @since 1.0.0.0
-}
likeInsensitive :: ValueExpression -> ValueExpression -> BooleanExpr
likeInsensitive =
  binaryOpExpression iLikeOp

{- |
  The SQL @IS NULL@ condition.

  @since 1.0.0.0
-}
isNull :: ValueExpression -> BooleanExpr
isNull value =
  BooleanExpr $
    RawSql.toRawSql value
      <> RawSql.space
      <> RawSql.fromString "IS NULL"

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

  @since 1.0.0.0
-}
isNotNull :: ValueExpression -> BooleanExpr
isNotNull value =
  BooleanExpr $
    RawSql.toRawSql value
      <> RawSql.space
      <> RawSql.fromString "IS NOT NULL"