karps-0.1.9.0: src/Spark/Core/Internal/RowGenericsFrom.hs
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
-- The generic implementation for the protocol that converts to
-- and from SQL cells.
-- Going through JSON is not recommended because of precision loss
-- for the numbers, and other issues related to numbers.
module Spark.Core.Internal.RowGenericsFrom(
FromSQL(_cellToValue),
TryS,
cellToValue,
) where
import GHC.Generics
import Data.Text(Text, pack)
import Control.Applicative(liftA2)
import Control.Monad.Except
import Formatting
import qualified Data.Vector as V
import Spark.Core.Internal.RowStructures
import Spark.Core.Internal.Utilities
import Spark.Core.Internal.TypesStructuresRepr(DataTypeRepr, DataTypeElementRepr)
-- Convert a cell to a value (if possible)
cellToValue :: (FromSQL a) => Cell -> Either Text a
cellToValue = _cellToValue
type TryS = Either Text
-- Because of the way the generic decoders work,
-- an array of cell needs special treatment when it is
-- decoded as the constructor of an object. Then it should
-- be interpreted as a stateful tape, for which we read a
-- few cells (number unknown) and return some value from the
-- cells that have been consumed.
data Decode2 =
-- A tape with some potentially remaining cells
D2Cons ![Cell]
-- Just a normal cell.
| D2Normal !Cell
deriving (Eq, Show)
-- All the types that can be converted to a SQL value.
class FromSQL a where
_cellToValue :: Cell -> TryS a
default _cellToValue :: (Generic a, GFromSQL (Rep a)) => Cell -> TryS a
_cellToValue cell = let
x = undefined :: a
x1r = _gFcell (from x) (D2Normal cell) :: InterResult (Decode2, Rep a a)
x2r = snd <$> x1r
x1t = to <$> x2r
in _toTry x1t
-- ******** Basic instance ********
instance FromSQL a => FromSQL (Maybe a) where
_cellToValue Empty = pure Nothing
_cellToValue x = pure <$> _cellToValue x
instance FromSQL Int where
_cellToValue (IntElement x) = pure x
_cellToValue x = throwError $ sformat ("FromSQL: Decoding an int from "%shown) x
instance FromSQL Double where
_cellToValue (DoubleElement x) = pure x
_cellToValue x = throwError $ sformat ("FromSQL: Decoding a double from "%shown) x
instance FromSQL Text where
_cellToValue (StringElement txt) = pure txt
_cellToValue x = throwError $ sformat ("FromSQL: Decoding a unicode text from "%shown) x
instance FromSQL Cell where
_cellToValue = pure
instance FromSQL Bool where
_cellToValue (BoolElement b) = pure b
_cellToValue x = throwError $ sformat ("FromSQL: Decoding a boolean from "%shown) x
instance FromSQL DataTypeRepr
instance FromSQL DataTypeElementRepr
instance FromSQL a => FromSQL [a] where
_cellToValue (RowArray xs) =
sequence (_cellToValue <$> V.toList xs)
_cellToValue x = throwError $ sformat ("FromSQL[]: Decoding array from "%shown) x
instance (FromSQL a1, FromSQL a2) => FromSQL (a1, a2) where
_cellToValue (RowArray xs) = case V.toList xs of
[x1, x2] ->
liftA2 (,) (_cellToValue x1) (_cellToValue x2)
l -> throwError $ sformat ("FromSQL: Expected 2 elements but got "%sh) l
_cellToValue x = throwError $ sformat ("FromSQL(,): Decoding array from "%shown) x
-- ******* GENERIC ********
-- A final message at the bottom
-- A path in the elements to get there
data FailureInfo = FailureInfo !Text ![Text] deriving (Eq, Show)
type InterResult a = Either FailureInfo a
class GFromSQL r where
-- An evidence about the type (in order to have info about the field names)
-- The current stuff that has been decoded
_gFcell :: r a -> Decode2 -> InterResult (Decode2, r a)
_toTry :: InterResult a -> TryS a
_toTry (Right x) = pure x
_toTry (Left (FailureInfo msg p)) = Left $ show' (reverse p) <> " : " <> msg
_fromTry :: TryS a -> InterResult a
_fromTry (Right x) = Right x
_fromTry (Left x) = Left $ FailureInfo x []
instance GFromSQL U1 where
_gFcell x = failure $ pack $ "GFromSQL UI called" ++ show x
instance (GFromSQL a, GFromSQL b) => GFromSQL (a :*: b) where
-- Switching to tape-reading mode
_gFcell ev (D2Normal (RowArray arr)) = _gFcell ev (D2Cons (V.toList arr))
-- Advancing into the reader
_gFcell ev (D2Cons l) = do
let (ev1 :*: ev2) = ev
(d1, x1) <- _gFcell ev1 (D2Cons l)
(d2, x2) <- _gFcell ev2 d1
return (d2, x1 :*: x2)
_gFcell _ x = failure $ pack ("GFromSQL (a :*: b) " ++ show x)
instance (GFromSQL a, GFromSQL b) => GFromSQL (a :+: b) where
_gFcell _ x = failure $ pack $ "GFromSQL (a :+: b)" ++ show x
instance (GFromSQL a, Constructor c) => GFromSQL (M1 C c a) where
_gFcell _ (D2Cons x) = failure $ pack ("GFromSQL (M1 C c a)" ++ " FAILED CONS: " ++ show x)
_gFcell ev (D2Normal cell) = do
let ev' = unM1 ev
(d, x) <- _withHint (pack (conName ev)) $ _gFcell ev' (D2Normal cell)
return (d, M1 x)
instance (GFromSQL a, Selector c) => GFromSQL (M1 S c a) where
_gFcell ev (D2Normal (RowArray arr)) = do
let ev' = unM1 ev
let l = V.toList arr
(d, x) <- _withHint ("(1)" <> pack (selName ev)) $ _gFcell ev' (D2Cons l)
return (d, M1 x)
_gFcell ev d = do
let ev' = unM1 ev
(d', x) <- _withHint ("(2)" <> pack (selName ev)) $ _gFcell ev' d
return (d', M1 x)
instance (GFromSQL a, Datatype c) => GFromSQL (M1 D c a) where
_gFcell ev (z @ (D2Normal (RowArray _))) = do
let ev' = unM1 ev
(d, x) <- _gFcell ev' z
return (d, M1 x)
_gFcell _ x = failure $ pack $ "FAIL GFromSQL (M1 D c a)" ++ show x
-- | Products: encode multiple arguments to constructors
instance (FromSQL a) => GFromSQL (K1 i a) where
-- It is just a normal cell.
-- Read one element and move on.
_gFcell _ (D2Cons (cell : r)) = do
x <- _fromTry $ _cellToValue cell
return (D2Cons r, K1 x)
-- Just reading a normal cell, return no tape.
_gFcell _ (D2Normal cell) = do
x <- _fromTry $ _cellToValue cell
return (D2Cons [], K1 x)
_gFcell _ x = failure $ pack ("GFromSQLK FAIL " ++ show x)
_withHint :: Text -> InterResult a -> InterResult a
_withHint extra (Left (FailureInfo msg l)) = Left (FailureInfo msg (extra : l))
_withHint _ (Right x) = Right x