{-|
Module: Squeal.PostgreSQL.Expression.Comparison
Description: comparison functions and operators
Copyright: (c) Eitan Chatav, 2019
Maintainer: eitan@morphism.tech
Stability: experimental

comparison functions and operators
-}

{-# LANGUAGE
    OverloadedStrings
  , RankNTypes
  , TypeInType
  , TypeOperators
#-}

module Squeal.PostgreSQL.Expression.Comparison
  ( -- * Comparison Operators
    (.==)
  , (./=)
  , (.>=)
  , (.<)
  , (.<=)
  , (.>)
    -- * Comparison Functions
  , greatest
  , least
    -- * Between
  , BetweenExpr
  , between
  , notBetween
  , betweenSymmetric
  , notBetweenSymmetric
    -- * Null Comparison
  , isDistinctFrom
  , isNotDistinctFrom
  , isTrue
  , isNotTrue
  , isFalse
  , isNotFalse
  , isUnknown
  , isNotUnknown
  ) where

import Data.ByteString

import Squeal.PostgreSQL.Expression
import Squeal.PostgreSQL.Expression.Logic
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Type.Schema

-- $setup
-- >>> import Squeal.PostgreSQL

-- | Comparison operations like `.==`, `./=`, `.>`, `.>=`, `.<` and `.<=`
-- will produce @NULL@s if one of their arguments is @NULL@.
--
-- >>> printSQL $ true .== null_
-- (TRUE = NULL)
(.==) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
.== :: Expression grp lat with db params from (null0 ty)
-> Expression grp lat with db params from (null1 ty)
-> Expression grp lat with db params from ('Null 'PGbool)
(.==) = ByteString -> Operator (null0 ty) (null1 ty) ('Null 'PGbool)
forall (ty0 :: NullType) (ty1 :: NullType) (ty2 :: NullType).
ByteString -> Operator ty0 ty1 ty2
unsafeBinaryOp ByteString
"="
infix 4 .==

-- | >>> printSQL $ true ./= null_
-- (TRUE <> NULL)
(./=) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
./= :: Expression grp lat with db params from (null0 ty)
-> Expression grp lat with db params from (null1 ty)
-> Expression grp lat with db params from ('Null 'PGbool)
(./=) = ByteString -> Operator (null0 ty) (null1 ty) ('Null 'PGbool)
forall (ty0 :: NullType) (ty1 :: NullType) (ty2 :: NullType).
ByteString -> Operator ty0 ty1 ty2
unsafeBinaryOp ByteString
"<>"
infix 4 ./=

-- | >>> printSQL $ true .>= null_
-- (TRUE >= NULL)
(.>=) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
.>= :: Expression grp lat with db params from (null0 ty)
-> Expression grp lat with db params from (null1 ty)
-> Expression grp lat with db params from ('Null 'PGbool)
(.>=) = ByteString -> Operator (null0 ty) (null1 ty) ('Null 'PGbool)
forall (ty0 :: NullType) (ty1 :: NullType) (ty2 :: NullType).
ByteString -> Operator ty0 ty1 ty2
unsafeBinaryOp ByteString
">="
infix 4 .>=

-- | >>> printSQL $ true .< null_
-- (TRUE < NULL)
(.<) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
.< :: Expression grp lat with db params from (null0 ty)
-> Expression grp lat with db params from (null1 ty)
-> Expression grp lat with db params from ('Null 'PGbool)
(.<) = ByteString -> Operator (null0 ty) (null1 ty) ('Null 'PGbool)
forall (ty0 :: NullType) (ty1 :: NullType) (ty2 :: NullType).
ByteString -> Operator ty0 ty1 ty2
unsafeBinaryOp ByteString
"<"
infix 4 .<

-- | >>> printSQL $ true .<= null_
-- (TRUE <= NULL)
(.<=) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
.<= :: Expression grp lat with db params from (null0 ty)
-> Expression grp lat with db params from (null1 ty)
-> Expression grp lat with db params from ('Null 'PGbool)
(.<=) = ByteString -> Operator (null0 ty) (null1 ty) ('Null 'PGbool)
forall (ty0 :: NullType) (ty1 :: NullType) (ty2 :: NullType).
ByteString -> Operator ty0 ty1 ty2
unsafeBinaryOp ByteString
"<="
infix 4 .<=

-- | >>> printSQL $ true .> null_
-- (TRUE > NULL)
(.>) :: Operator (null0 ty) (null1 ty) ('Null 'PGbool)
.> :: Expression grp lat with db params from (null0 ty)
-> Expression grp lat with db params from (null1 ty)
-> Expression grp lat with db params from ('Null 'PGbool)
(.>) = ByteString -> Operator (null0 ty) (null1 ty) ('Null 'PGbool)
forall (ty0 :: NullType) (ty1 :: NullType) (ty2 :: NullType).
ByteString -> Operator ty0 ty1 ty2
unsafeBinaryOp ByteString
">"
infix 4 .>

-- | >>> let expr = greatest [param @1] currentTimestamp
-- >>> printSQL expr
-- GREATEST(($1 :: timestamp with time zone), CURRENT_TIMESTAMP)
greatest :: FunctionVar ty ty ty
greatest :: [Expression grp lat with db params from ty]
-> Expression grp lat with db params from ty
-> Expression grp lat with db params from ty
greatest = ByteString -> FunctionVar ty ty ty
forall (x0 :: NullType) (x1 :: NullType) (y :: NullType).
ByteString -> FunctionVar x0 x1 y
unsafeFunctionVar ByteString
"GREATEST"

-- | >>> printSQL $ least [null_] currentTimestamp
-- LEAST(NULL, CURRENT_TIMESTAMP)
least :: FunctionVar ty ty ty
least :: [Expression grp lat with db params from ty]
-> Expression grp lat with db params from ty
-> Expression grp lat with db params from ty
least = ByteString -> FunctionVar ty ty ty
forall (x0 :: NullType) (x1 :: NullType) (y :: NullType).
ByteString -> FunctionVar x0 x1 y
unsafeFunctionVar ByteString
"LEAST"

{- |
A @RankNType@ for comparison expressions like `between`.
-}
type BetweenExpr
  =  forall grp lat with db params from ty
  .  Expression grp lat with db params from ty
  -> ( Expression grp lat with db params from ty
     , Expression grp lat with db params from ty ) -- ^ bounds
  -> Condition grp lat with db params from

unsafeBetweenExpr :: ByteString -> BetweenExpr
unsafeBetweenExpr :: ByteString -> BetweenExpr
unsafeBetweenExpr ByteString
fun Expression grp lat with db params from ty
a (Expression grp lat with db params from ty
x,Expression grp lat with db params from ty
y) = ByteString
-> Expression grp lat with db params from ('Null 'PGbool)
forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType)
       (ty :: NullType).
ByteString -> Expression grp lat with db params from ty
UnsafeExpression (ByteString
 -> Expression grp lat with db params from ('Null 'PGbool))
-> ByteString
-> Expression grp lat with db params from ('Null 'PGbool)
forall a b. (a -> b) -> a -> b
$
  Expression grp lat with db params from ty -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ty
a ByteString -> ByteString -> ByteString
<+> ByteString
fun ByteString -> ByteString -> ByteString
<+> Expression grp lat with db params from ty -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ty
x ByteString -> ByteString -> ByteString
<+> ByteString
"AND" ByteString -> ByteString -> ByteString
<+> Expression grp lat with db params from ty -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ty
y

{- | >>> printSQL $ true `between` (null_, false)
TRUE BETWEEN NULL AND FALSE
-}
between :: BetweenExpr
between :: Expression grp lat with db params from ty
-> (Expression grp lat with db params from ty,
    Expression grp lat with db params from ty)
-> Condition grp lat with db params from
between = ByteString -> BetweenExpr
unsafeBetweenExpr ByteString
"BETWEEN"

{- | >>> printSQL $ true `notBetween` (null_, false)
TRUE NOT BETWEEN NULL AND FALSE
-}
notBetween :: BetweenExpr
notBetween :: Expression grp lat with db params from ty
-> (Expression grp lat with db params from ty,
    Expression grp lat with db params from ty)
-> Condition grp lat with db params from
notBetween = ByteString -> BetweenExpr
unsafeBetweenExpr ByteString
"NOT BETWEEN"

{- | between, after sorting the comparison values

>>> printSQL $ true `betweenSymmetric` (null_, false)
TRUE BETWEEN SYMMETRIC NULL AND FALSE
-}
betweenSymmetric :: BetweenExpr
betweenSymmetric :: Expression grp lat with db params from ty
-> (Expression grp lat with db params from ty,
    Expression grp lat with db params from ty)
-> Condition grp lat with db params from
betweenSymmetric = ByteString -> BetweenExpr
unsafeBetweenExpr ByteString
"BETWEEN SYMMETRIC"

{- | not between, after sorting the comparison values

>>> printSQL $ true `notBetweenSymmetric` (null_, false)
TRUE NOT BETWEEN SYMMETRIC NULL AND FALSE
-}
notBetweenSymmetric :: BetweenExpr
notBetweenSymmetric :: Expression grp lat with db params from ty
-> (Expression grp lat with db params from ty,
    Expression grp lat with db params from ty)
-> Condition grp lat with db params from
notBetweenSymmetric = ByteString -> BetweenExpr
unsafeBetweenExpr ByteString
"NOT BETWEEN SYMMETRIC"

{- | not equal, treating null like an ordinary value

>>> printSQL $ true `isDistinctFrom` null_
(TRUE IS DISTINCT FROM NULL)
-}
isDistinctFrom :: Operator (null0 ty) (null1 ty) (null 'PGbool)
isDistinctFrom :: Expression grp lat with db params from (null0 ty)
-> Expression grp lat with db params from (null1 ty)
-> Expression grp lat with db params from (null 'PGbool)
isDistinctFrom = ByteString -> Operator (null0 ty) (null1 ty) (null 'PGbool)
forall (ty0 :: NullType) (ty1 :: NullType) (ty2 :: NullType).
ByteString -> Operator ty0 ty1 ty2
unsafeBinaryOp ByteString
"IS DISTINCT FROM"

{- | equal, treating null like an ordinary value

>>> printSQL $ true `isNotDistinctFrom` null_
(TRUE IS NOT DISTINCT FROM NULL)
-}
isNotDistinctFrom :: Operator (null0 ty) (null1 ty) (null 'PGbool)
isNotDistinctFrom :: Expression grp lat with db params from (null0 ty)
-> Expression grp lat with db params from (null1 ty)
-> Expression grp lat with db params from (null 'PGbool)
isNotDistinctFrom = ByteString -> Operator (null0 ty) (null1 ty) (null 'PGbool)
forall (ty0 :: NullType) (ty1 :: NullType) (ty2 :: NullType).
ByteString -> Operator ty0 ty1 ty2
unsafeBinaryOp ByteString
"IS NOT DISTINCT FROM"

{- | is true

>>> printSQL $ true & isTrue
(TRUE IS TRUE)
-}
isTrue :: null0 'PGbool --> null1 'PGbool
isTrue :: Expression grp lat with db params from (null0 'PGbool)
-> Expression grp lat with db params from (null1 'PGbool)
isTrue = ByteString -> null0 'PGbool --> null1 'PGbool
forall (x :: NullType) (y :: NullType). ByteString -> x --> y
unsafeRightOp ByteString
"IS TRUE"

{- | is false or unknown

>>> printSQL $ true & isNotTrue
(TRUE IS NOT TRUE)
-}
isNotTrue :: null0 'PGbool --> null1 'PGbool
isNotTrue :: Expression grp lat with db params from (null0 'PGbool)
-> Expression grp lat with db params from (null1 'PGbool)
isNotTrue = ByteString -> null0 'PGbool --> null1 'PGbool
forall (x :: NullType) (y :: NullType). ByteString -> x --> y
unsafeRightOp ByteString
"IS NOT TRUE"

{- | is false

>>> printSQL $ true & isFalse
(TRUE IS FALSE)
-}
isFalse :: null0 'PGbool --> null1 'PGbool
isFalse :: Expression grp lat with db params from (null0 'PGbool)
-> Expression grp lat with db params from (null1 'PGbool)
isFalse = ByteString -> null0 'PGbool --> null1 'PGbool
forall (x :: NullType) (y :: NullType). ByteString -> x --> y
unsafeRightOp ByteString
"IS FALSE"

{- | is true or unknown

>>> printSQL $ true & isNotFalse
(TRUE IS NOT FALSE)
-}
isNotFalse :: null0 'PGbool --> null1 'PGbool
isNotFalse :: Expression grp lat with db params from (null0 'PGbool)
-> Expression grp lat with db params from (null1 'PGbool)
isNotFalse = ByteString -> null0 'PGbool --> null1 'PGbool
forall (x :: NullType) (y :: NullType). ByteString -> x --> y
unsafeRightOp ByteString
"IS NOT FALSE"

{- | is unknown

>>> printSQL $ true & isUnknown
(TRUE IS UNKNOWN)
-}
isUnknown :: null0 'PGbool --> null1 'PGbool
isUnknown :: Expression grp lat with db params from (null0 'PGbool)
-> Expression grp lat with db params from (null1 'PGbool)
isUnknown = ByteString -> null0 'PGbool --> null1 'PGbool
forall (x :: NullType) (y :: NullType). ByteString -> x --> y
unsafeRightOp ByteString
"IS UNKNOWN"

{- | is true or false

>>> printSQL $ true & isNotUnknown
(TRUE IS NOT UNKNOWN)
-}
isNotUnknown :: null0 'PGbool --> null1 'PGbool
isNotUnknown :: Expression grp lat with db params from (null0 'PGbool)
-> Expression grp lat with db params from (null1 'PGbool)
isNotUnknown = ByteString -> null0 'PGbool --> null1 'PGbool
forall (x :: NullType) (y :: NullType). ByteString -> x --> y
unsafeRightOp ByteString
"IS NOT UNKNOWN"