packages feed

sqel-0.0.1.0: lib/Sqel/Codec/Sum.hs

module Sqel.Codec.Sum where

import Data.Functor.Contravariant.Divisible (choose)
import Data.Functor.Invariant (Invariant (invmap))
import Exon (exon)
import Generics.SOP (
  All2,
  HIndex (hindex),
  I,
  NP (Nil, (:*)),
  NS (S, Z),
  SListI,
  SListI2,
  SOP (SOP),
  Top,
  hcfoldMap,
  hctraverse_,
  hmap,
  hsequence,
  unSOP,
  )
import Generics.SOP.GGP (gfrom, gto)
import Hasql.Decoders (Row)
import qualified Hasql.Encoders as Encoders
import qualified Hasql.Encoders as Encoder
import Hasql.Encoders (Params)
import Lens.Micro ((^.))
import Lens.Micro.Extras (view)
import qualified Sqel.Data.Codec as Codec
import Sqel.Data.Codec (Codec (Codec), Decoder (Decoder), Encoder (Encoder), FullCodec)
import Sqel.Data.Dd (ConCol (ConCol, unConCol))
import Sqel.SOP.Constraint (ConstructSOP, ReifySOP)

import Sqel.Codec.Product (prodParams)

unconsNS ::
  NS (NP I) (ds : dss) ->
  Either (NP I ds) (NS (NP I) dss)
unconsNS = \case
  Z x -> Left x
  S x -> Right x

newtype ConB b as =
  ConB { unConB :: b (NP I as) }

readNull ::
  ∀ as .
  Decoder (NP I as) ->
  Row ()
readNull rs =
  rs ^. #decodeNulls

readNulls ::
  ∀ ass .
  SListI2 ass =>
  NP (ConB Decoder) ass ->
  Row ()
readNulls cons =
  hctraverse_ (Proxy @SListI) (readNull . unConB) cons

sumRows ::
  All2 Top ass =>
  NP (ConB Decoder) ass ->
  Int64 ->
  Row (NS (NP I) ass)
sumRows (ConB con :* cons) 0 =
  Z <$> (con ^. #decodeValue) <* readNulls cons
sumRows (ConB con :* cons) index = do
  readNull con
  S <$> sumRows cons (index - 1)
sumRows Nil index =
  fail [exon|invalid index into sum type in database: #{show index}|]

ignoreEncoder :: Encoder.Value a -> Params b
ignoreEncoder v =
  const Nothing >$< Encoders.param (Encoders.nullable v)

writeNull ::
  ∀ a as .
  ConB Encoder as ->
  Params a
writeNull (ConB enc) =
  contramap unit (enc ^. #encodeNulls)

writeNulls ::
  ∀ a ass .
  SListI2 ass =>
  NP (ConB Encoder) ass ->
  Params a
writeNulls =
  hcfoldMap (Proxy @SListI) writeNull

sumParams ::
  All2 Top ass =>
  NP (ConB Encoder) ass ->
  Params (NS (NP I) ass)
sumParams = \case
  con :* cons ->
    choose unconsNS inhabited uninhabited
    where
      inhabited = (unConB con) ^. #encodeValue <> writeNulls cons
      uninhabited = writeNull con <> sumParams cons
  Nil ->
    mempty

type WrapConB :: (Type -> Type) -> [[Type]] -> [Type] -> Constraint
class WrapConB b ass as where
  wrapConB :: NP b as -> NP (ConB b) ass

instance WrapConB b '[] '[] where
  wrapConB Nil = Nil

instance (
    Invariant b,
    WrapConB b ass as
  ) => WrapConB b (as' : ass) (ConCol name record fields as' : as) where
    wrapConB (b :* bs) =
      ConB (invmap unConCol ConCol b) :* wrapConB bs

encodeValue ::
  ConstructSOP a ass =>
  Encoder Int64 ->
  NP (ConB Encoder) ass ->
  Params a
encodeValue (Encoder indexParams _) wrapped =
  unSOP . gfrom >$< (indexEncoder <> sumParams wrapped)
  where
    indexEncoder = (fromIntegral . hindex) >$< indexParams

type SumCodec :: (Type -> Type) -> Type -> [Type] -> Constraint
class SumCodec b a as where
  sumCodec :: NP b as -> b a

-- TODO add null builders
instance (
    ReifySOP a ass,
    ConstructSOP a ass,
    WrapConB FullCodec ass as
  ) => SumCodec FullCodec a (Int64 : as) where
    sumCodec (Codec index (Decoder indexRow _) :* conCodecs) =
      Codec {
        decoder = Decoder decodeValue unit,
        encoder = Encoder (encodeValue index (hmap (ConB . view #encoder . unConB) wrapped)) mempty
      }
      where
        decodeValue =
          gto . SOP <$> (sumRows decs =<< indexRow)
        decs =
          hmap (ConB . view #decoder . unConB) wrapped
        wrapped =
          wrapConB conCodecs

instance (
    ConstructSOP a ass,
    WrapConB Encoder ass as
  ) => SumCodec Encoder a (Int64 : as) where
    sumCodec (index :* conCodecs) =
      Encoder (encodeValue index wrapped) mempty
      where
        wrapped = wrapConB conCodecs

type ConCodec :: (Type -> Type) -> [Type] -> Constraint
class ConCodec b as where
  conCodec :: NP b as -> b (ConCol name record fields as)

instance SListI as => ConCodec FullCodec as where
  conCodec np =
    Codec {
      decoder = ConCol <$> hsequence (hmap (view #decoder) np),
      encoder = unConCol >$< prodParams (hmap (view #encoder) np)
    }

instance SListI as => ConCodec Encoder as where
    conCodec np = unConCol >$< prodParams np