packages feed

rel8-1.0.0.0: src/Rel8/Schema/Context/Nullify.hs

{-# language DataKinds #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language InstanceSigs #-}
{-# language MultiParamTypeClasses #-}
{-# language NamedFieldPuns #-}
{-# language ScopedTypeVariables #-}
{-# language StandaloneKindSignatures #-}
{-# language TypeFamilies #-}

module Rel8.Schema.Context.Nullify
  ( Nullifiable( ConstrainTag, encodeTag, decodeTag, nullifier, unnullifier )
  , HNullifiable( HConstrainTag, hencodeTag, hdecodeTag, hnullifier, hunnullifier )
  , runTag, unnull
  )
where

-- base
import Data.Kind ( Constraint, Type )
import GHC.TypeLits ( KnownSymbol )
import Prelude hiding ( null )

-- opaleye
import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye

-- rel8
import Rel8.Aggregate
  ( Aggregate( Aggregate ), Col( A )
  , mapInputs
  , unsafeMakeAggregate
  )
import Rel8.Expr ( Expr, Col( E ) )
import Rel8.Expr.Bool ( boolExpr )
import Rel8.Expr.Null ( nullify, unsafeUnnullify )
import Rel8.Expr.Opaleye ( fromPrimExpr, toPrimExpr )
import Rel8.Schema.Context ( Interpretation )
import qualified Rel8.Schema.Kind as K
import Rel8.Schema.Name ( Name( Name ), Col( N ) )
import Rel8.Schema.Null ( Nullify, Nullity( Null, NotNull ), Sql )
import Rel8.Schema.Dict ( Dict( Dict ) )
import Rel8.Schema.Spec ( Spec( Spec ), SSpec(..) )
import Rel8.Schema.Spec.ConstrainDBType ( ConstrainDBType )
import qualified Rel8.Schema.Spec.ConstrainDBType as ConstrainDBType
import Rel8.Table.Tag ( Tag(..), Taggable, fromAggregate, fromExpr, fromName )


type Nullifiable :: K.Context -> Constraint
class Interpretation context => Nullifiable context where
  type ConstrainTag context :: Type -> Constraint
  type ConstrainTag _context = DefaultConstrainTag

  encodeTag ::
    ( Sql (ConstrainTag context) a
    , KnownSymbol label
    , Taggable a
    )
    => Tag label a
    -> Col context ('Spec labels a)

  decodeTag ::
    ( Sql (ConstrainTag context) a
    , KnownSymbol label
    , Taggable a
    )
    => Col context ('Spec labels a)
    -> Tag label a

  nullifier :: ()
    => Tag label a
    -> (Expr a -> Expr Bool)
    -> SSpec ('Spec labels x)
    -> Col context ('Spec labels x)
    -> Col context ('Spec labels (Nullify x))

  unnullifier :: ()
    => SSpec ('Spec labels x)
    -> Col context ('Spec labels (Nullify x))
    -> Col context ('Spec labels x)


instance Nullifiable Aggregate where
  encodeTag Tag {aggregator, expr} =
    A $ unsafeMakeAggregate toPrimExpr fromPrimExpr aggregator expr

  decodeTag (A aggregate) = fromAggregate aggregate

  nullifier Tag {expr} test SSpec {nullity} (A (Aggregate a)) =
    A $
    mapInputs (toPrimExpr . runTag nullity condition . fromPrimExpr) $
    Aggregate $
    runTag nullity condition <$> a
    where
      condition = test expr

  unnullifier SSpec {nullity} (A (Aggregate a)) =
    A (Aggregate (unnull nullity <$> a))

  {-# INLINABLE encodeTag #-}
  {-# INLINABLE decodeTag #-}
  {-# INLINABLE nullifier #-}
  {-# INLINABLE unnullifier #-}


instance Nullifiable Expr where
  encodeTag Tag {expr} = E expr
  decodeTag (E a) = fromExpr a
  nullifier Tag {expr} test SSpec {nullity} (E a) =
    E $ runTag nullity (test expr) a
  unnullifier SSpec {nullity} (E a) = E $ unnull nullity a

  {-# INLINABLE encodeTag #-}
  {-# INLINABLE decodeTag #-}
  {-# INLINABLE nullifier #-}
  {-# INLINABLE unnullifier #-}


instance Nullifiable Name where
  encodeTag Tag {name} = N name
  decodeTag (N name) = fromName name
  nullifier _ _ _ (N (Name name)) = N (Name name)
  unnullifier _ (N (Name name)) = N (Name name)

  {-# INLINABLE encodeTag #-}
  {-# INLINABLE decodeTag #-}
  {-# INLINABLE nullifier #-}
  {-# INLINABLE unnullifier #-}


runTag :: Nullity a -> Expr Bool -> Expr a -> Expr (Nullify a)
runTag nullity tag a = case nullity of
  Null -> boolExpr null a tag
  NotNull -> boolExpr null (nullify a) tag
  where
    null = fromPrimExpr $ Opaleye.ConstExpr Opaleye.NullLit


unnull :: Nullity a -> Expr (Nullify a) -> Expr a
unnull nullity a = case nullity of
  Null -> a
  NotNull -> unsafeUnnullify a


type HNullifiable :: K.HContext -> Constraint
class HNullifiable context where
  type HConstrainTag context :: Type -> Constraint
  type HConstrainTag _context = DefaultConstrainTag

  hencodeTag :: (Sql (HConstrainTag context) a, KnownSymbol label, Taggable a)
    => Tag label a
    -> context ('Spec labels a)

  hdecodeTag :: (Sql (HConstrainTag context) a, KnownSymbol label, Taggable a)
    => context ('Spec labels a)
    -> Tag label a

  hnullifier :: ()
    => Tag label a
    -> (Expr a -> Expr Bool)
    -> SSpec ('Spec labels x)
    -> context ('Spec labels x)
    -> context ('Spec labels (Nullify x))

  hunnullifier :: ()
    => SSpec ('Spec labels x)
    -> context ('Spec labels (Nullify x))
    -> context ('Spec labels x)


instance Nullifiable context => HNullifiable (Col context) where
  type HConstrainTag (Col context) = ConstrainTag context
  hencodeTag = encodeTag
  hdecodeTag = decodeTag
  hnullifier = nullifier
  hunnullifier = unnullifier


instance HNullifiable (Dict (ConstrainDBType constraint)) where
  type HConstrainTag (Dict (ConstrainDBType constraint)) = constraint

  hencodeTag _ = Dict
  hdecodeTag = mempty
  hnullifier _ _ = ConstrainDBType.nullifier
  hunnullifier = ConstrainDBType.unnullifier


type DefaultConstrainTag :: Type -> Constraint
class DefaultConstrainTag a
instance DefaultConstrainTag a