content: {-|
  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_
    , just_
    , unsafeNotNull
    , monoNotNull
    , coalesce
    , fromNull
    , isNull
    , isNotNull
    , matchNull
    , nullIf
    , CombineNullity
      -- deprecated
    , notNull
    ) 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_ = UnsafeExpression "NULL"
  
  -- | analagous to `Just`
  --
  -- >>> printSQL $ just_ true
  -- TRUE
  just_ :: 'NotNull ty --> 'Null ty
  just_ = UnsafeExpression . renderSQL
  
  -- | analagous to `Just`
  {-# DEPRECATED notNull "use just_ instead" #-}
  notNull :: 'NotNull ty --> 'Null ty
  notNull = UnsafeExpression . 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 = UnsafeExpression . 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 x = x
  
  -- | 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 nullxs notNullx = UnsafeExpression $
    "COALESCE" <> parenthesized (commaSeparated
      ((renderSQL <$> nullxs) <> [renderSQL 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 notNullx nullx = coalesce [nullx] notNullx
  
  -- | >>> printSQL $ null_ & isNull
  -- NULL IS NULL
  isNull :: 'Null ty --> null 'PGbool
  isNull x = UnsafeExpression $ renderSQL x <+> "IS NULL"
  
  -- | >>> printSQL $ null_ & isNotNull
  -- NULL IS NOT NULL
  isNotNull :: 'Null ty --> null 'PGbool
  isNotNull x = UnsafeExpression $ renderSQL x <+> "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 y f x = ifThenElse (isNull x) y
    (f (UnsafeExpression (renderSQL 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 = unsafeFunctionN "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
  