{-# 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