{-|
Module: Squeal.PostgreSQL.Expression.Null
Description: null expressions and handlers
Copyright: (c) Eitan Chatav, 2019
Maintainer: eitan@morphism.tech
Stability: experimental

null expressions and handlers
-}

{-# LANGUAGE
    DataKinds
  , KindSignatures
  , OverloadedStrings
  , RankNTypes
  , TypeFamilies
  , TypeOperators
#-}

module Squeal.PostgreSQL.Expression.Null
  ( -- * Null
    null_
  , notNull
  , unsafeNotNull
  , monoNotNull
  , coalesce
  , fromNull
  , isNull
  , isNotNull
  , matchNull
  , nullIf
  , CombineNullity
  ) where

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

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

-- | analagous to `Nothing`
--
-- >>> printSQL null_
-- NULL
null_ :: Expr ('Null ty)
null_ :: Expression grp lat with db params from ('Null ty)
null_ = ByteString -> Expression grp lat with db params from ('Null ty)
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
"NULL"

-- | analagous to `Just`
--
-- >>> printSQL $ notNull true
-- TRUE
notNull :: 'NotNull ty --> 'Null ty
notNull :: Expression grp lat with db params from ('NotNull ty)
-> Expression grp lat with db params from ('Null ty)
notNull = ByteString -> Expression grp lat with db params from ('Null ty)
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 ty))
-> (Expression grp lat with db params from ('NotNull ty)
    -> ByteString)
-> Expression grp lat with db params from ('NotNull ty)
-> Expression grp lat with db params from ('Null ty)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Expression grp lat with db params from ('NotNull ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL

-- | Analagous to `Data.Maybe.fromJust` inverse to `notNull`,
-- useful when you know an `Expression` is `NotNull`,
-- because, for instance, you've filtered out @NULL@
-- values in a column.
unsafeNotNull :: 'Null ty --> 'NotNull ty
unsafeNotNull :: Expression grp lat with db params from ('Null ty)
-> Expression grp lat with db params from ('NotNull ty)
unsafeNotNull = ByteString -> Expression grp lat with db params from ('NotNull ty)
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 ('NotNull ty))
-> (Expression grp lat with db params from ('Null ty)
    -> ByteString)
-> Expression grp lat with db params from ('Null ty)
-> Expression grp lat with db params from ('NotNull ty)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Expression grp lat with db params from ('Null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL

-- | Some expressions are null polymorphic which may raise
-- inference issues. Use `monoNotNull` to fix their
-- nullity as `NotNull`.
monoNotNull
  :: (forall null. Expression grp lat with db params from (null ty))
  -- ^ null polymorphic
  -> Expression grp lat with db params from ('NotNull ty)
monoNotNull :: (forall (null :: PGType -> NullType).
 Expression grp lat with db params from (null ty))
-> Expression grp lat with db params from ('NotNull ty)
monoNotNull = (forall (null :: PGType -> NullType).
 Expression grp lat with db params from (null ty))
-> Expression grp lat with db params from ('NotNull ty)
forall a. a -> a
id

-- | return the leftmost value which is not NULL
--
-- >>> printSQL $ coalesce [null_, true] false
-- COALESCE(NULL, TRUE, FALSE)
coalesce :: FunctionVar ('Null ty) (null ty) (null ty)
coalesce :: [Expression grp lat with db params from ('Null ty)]
-> Expression grp lat with db params from (null ty)
-> Expression grp lat with db params from (null ty)
coalesce [Expression grp lat with db params from ('Null ty)]
nullxs Expression grp lat with db params from (null ty)
notNullx = ByteString -> Expression grp lat with db params from (null ty)
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 ty))
-> ByteString -> Expression grp lat with db params from (null ty)
forall a b. (a -> b) -> a -> b
$
  ByteString
"COALESCE" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteString
parenthesized ([ByteString] -> ByteString
commaSeparated
    ((Expression grp lat with db params from ('Null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL (Expression grp lat with db params from ('Null ty) -> ByteString)
-> [Expression grp lat with db params from ('Null ty)]
-> [ByteString]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Expression grp lat with db params from ('Null ty)]
nullxs) [ByteString] -> [ByteString] -> [ByteString]
forall a. Semigroup a => a -> a -> a
<> [Expression grp lat with db params from (null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from (null ty)
notNullx]))

-- | analagous to `Data.Maybe.fromMaybe` using @COALESCE@
--
-- >>> printSQL $ fromNull true null_
-- COALESCE(NULL, TRUE)
fromNull
  :: Expression grp lat with db params from ('NotNull ty)
  -- ^ what to convert @NULL@ to
  -> Expression grp lat with db params from ('Null ty)
  -> Expression grp lat with db params from ('NotNull ty)
fromNull :: Expression grp lat with db params from ('NotNull ty)
-> Expression grp lat with db params from ('Null ty)
-> Expression grp lat with db params from ('NotNull ty)
fromNull Expression grp lat with db params from ('NotNull ty)
notNullx Expression grp lat with db params from ('Null ty)
nullx = [Expression grp lat with db params from ('Null ty)]
-> Expression grp lat with db params from ('NotNull ty)
-> Expression grp lat with db params from ('NotNull ty)
forall (ty :: PGType) (null :: PGType -> NullType).
FunctionVar ('Null ty) (null ty) (null ty)
coalesce [Expression grp lat with db params from ('Null ty)
nullx] Expression grp lat with db params from ('NotNull ty)
notNullx

-- | >>> printSQL $ null_ & isNull
-- NULL IS NULL
isNull :: 'Null ty --> null 'PGbool
isNull :: Expression grp lat with db params from ('Null ty)
-> Expression grp lat with db params from (null 'PGbool)
isNull Expression grp lat with db params from ('Null ty)
x = 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 ('Null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ('Null ty)
x ByteString -> ByteString -> ByteString
<+> ByteString
"IS NULL"

-- | >>> printSQL $ null_ & isNotNull
-- NULL IS NOT NULL
isNotNull :: 'Null ty --> null 'PGbool
isNotNull :: Expression grp lat with db params from ('Null ty)
-> Expression grp lat with db params from (null 'PGbool)
isNotNull Expression grp lat with db params from ('Null ty)
x = 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 ('Null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ('Null ty)
x ByteString -> ByteString -> ByteString
<+> ByteString
"IS NOT NULL"

-- | analagous to `maybe` using @IS NULL@
--
-- >>> printSQL $ matchNull true not_ null_
-- CASE WHEN NULL IS NULL THEN TRUE ELSE (NOT NULL) END
matchNull
  :: Expression grp lat with db params from (nullty)
  -- ^ what to convert @NULL@ to
  -> ( Expression grp lat with db params from ('NotNull ty)
       -> Expression grp lat with db params from (nullty) )
  -- ^ function to perform when @NULL@ is absent
  -> Expression grp lat with db params from ('Null ty)
  -> Expression grp lat with db params from (nullty)
matchNull :: Expression grp lat with db params from nullty
-> (Expression grp lat with db params from ('NotNull ty)
    -> Expression grp lat with db params from nullty)
-> Expression grp lat with db params from ('Null ty)
-> Expression grp lat with db params from nullty
matchNull Expression grp lat with db params from nullty
y Expression grp lat with db params from ('NotNull ty)
-> Expression grp lat with db params from nullty
f Expression grp lat with db params from ('Null ty)
x = Condition grp lat with db params from
-> Expression grp lat with db params from nullty
-> Expression grp lat with db params from nullty
-> Expression grp lat with db params from nullty
forall (grp :: Grouping) (lat :: FromType) (with :: FromType)
       (db :: SchemasType) (params :: [NullType]) (from :: FromType)
       (ty :: NullType).
Condition grp lat with db params from
-> Expression grp lat with db params from ty
-> Expression grp lat with db params from ty
-> Expression grp lat with db params from ty
ifThenElse (Expression grp lat with db params from ('Null ty)
-> Condition grp lat with db params from
forall (ty :: PGType) (null :: PGType -> NullType).
'Null ty --> null 'PGbool
isNull Expression grp lat with db params from ('Null ty)
x) Expression grp lat with db params from nullty
y
  (Expression grp lat with db params from ('NotNull ty)
-> Expression grp lat with db params from nullty
f (ByteString -> Expression grp lat with db params from ('NotNull ty)
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 (Expression grp lat with db params from ('Null ty) -> ByteString
forall sql. RenderSQL sql => sql -> ByteString
renderSQL Expression grp lat with db params from ('Null ty)
x)))

{-| right inverse to `fromNull`, if its arguments are equal then
`nullIf` gives @NULL@.

>>> :set -XTypeApplications
>>> printSQL (nullIf (false *: param @1))
NULLIF(FALSE, ($1 :: bool))
-}
nullIf :: '[ 'NotNull ty, 'NotNull ty] ---> 'Null ty
nullIf :: NP
  (Expression grp lat with db params from)
  '[ 'NotNull ty, 'NotNull ty]
-> Expression grp lat with db params from ('Null ty)
nullIf = ByteString -> '[ 'NotNull ty, 'NotNull ty] ---> 'Null ty
forall (xs :: [NullType]) (y :: NullType).
SListI xs =>
ByteString -> xs ---> y
unsafeFunctionN ByteString
"NULLIF"

{-| Make the return type of the type family `NotNull` if both arguments are,
   or `Null` otherwise.
-}
type family CombineNullity
      (lhs :: PGType -> NullType) (rhs :: PGType -> NullType) :: PGType -> NullType where
  CombineNullity 'NotNull 'NotNull = 'NotNull
  CombineNullity _ _ = 'Null