|
2 | 2 | {-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-} |
3 | 3 | {-# LANGUAGE PatternGuards, ScopedTypeVariables #-} |
4 | 4 | {-# LANGUAGE RecordWildCards, TemplateHaskell #-} |
| 5 | +{-# LANGUAGE MultiWayIf, DefaultSignatures #-} |
| 6 | +{-# LANGUAGE FlexibleContexts #-} |
5 | 7 |
|
6 | 8 | {- | |
7 | 9 | Module: Database.PostgreSQL.Simple.FromField |
@@ -83,6 +85,7 @@ instances use 'typename' instead. |
83 | 85 | module Database.PostgreSQL.Simple.FromField |
84 | 86 | ( |
85 | 87 | FromField(..) |
| 88 | + , genericFromField |
86 | 89 | , FieldParser |
87 | 90 | , Conversion() |
88 | 91 |
|
@@ -113,16 +116,19 @@ module Database.PostgreSQL.Simple.FromField |
113 | 116 |
|
114 | 117 | #include "MachDeps.h" |
115 | 118 |
|
116 | | -import Control.Applicative ( (<|>), (<$>), pure, (*>), (<*) ) |
| 119 | +import Control.Applicative ( Alternative(..), (<|>), (<$>), pure, (*>), (<*), liftA2 ) |
117 | 120 | import Control.Concurrent.MVar (MVar, newMVar) |
118 | 121 | import Control.Exception (Exception) |
119 | 122 | import qualified Data.Aeson as JSON |
120 | 123 | import qualified Data.Aeson.Parser as JSON (value') |
121 | 124 | import Data.Attoparsec.ByteString.Char8 hiding (Result) |
122 | 125 | import Data.ByteString (ByteString) |
| 126 | +import Data.ByteString.Builder (Builder, toLazyByteString, byteString) |
123 | 127 | import qualified Data.ByteString.Char8 as B |
| 128 | +import Data.Char (toLower) |
124 | 129 | import Data.Int (Int16, Int32, Int64) |
125 | 130 | import Data.IORef (IORef, newIORef) |
| 131 | +import Data.Proxy (Proxy(..)) |
126 | 132 | import Data.Ratio (Ratio) |
127 | 133 | import Data.Time ( UTCTime, ZonedTime, LocalTime, Day, TimeOfDay ) |
128 | 134 | import Data.Typeable (Typeable, typeOf) |
@@ -150,6 +156,7 @@ import qualified Data.CaseInsensitive as CI |
150 | 156 | import Data.UUID.Types (UUID) |
151 | 157 | import qualified Data.UUID.Types as UUID |
152 | 158 | import Data.Scientific (Scientific) |
| 159 | +import GHC.Generics (Generic, Rep, M1(..), K1(..), D1, C1, S1, Rec0, Constructor, (:*:)(..), to, conName) |
153 | 160 | import GHC.Real (infinity, notANumber) |
154 | 161 |
|
155 | 162 | -- | Exception thrown if conversion from a SQL value to a Haskell |
@@ -188,6 +195,8 @@ type FieldParser a = Field -> Maybe ByteString -> Conversion a |
188 | 195 | -- | A type that may be converted from a SQL type. |
189 | 196 | class FromField a where |
190 | 197 | fromField :: FieldParser a |
| 198 | + default fromField :: (Generic a, Typeable a, GFromField (Rep a)) => FieldParser a |
| 199 | + fromField = genericFromField (map toLower) |
191 | 200 | -- ^ Convert a SQL value to a Haskell value. |
192 | 201 | -- |
193 | 202 | -- Returns a list of exceptions if the conversion fails. In the case of |
@@ -292,7 +301,8 @@ instance FromField Null where |
292 | 301 | -- | bool |
293 | 302 | instance FromField Bool where |
294 | 303 | fromField f bs |
295 | | - | typeOid f /= $(inlineTypoid TI.bool) = returnError Incompatible f "" |
| 304 | + | typeOid f /= $(inlineTypoid TI.bool) |
| 305 | + && typeOid f /= $(inlineTypoid TI.unknown) = returnError Incompatible f "" |
296 | 306 | | bs == Nothing = returnError UnexpectedNull f "" |
297 | 307 | | bs == Just "t" = pure True |
298 | 308 | | bs == Just "f" = pure False |
@@ -404,9 +414,9 @@ instance FromField (Binary SB.ByteString) where |
404 | 414 | instance FromField (Binary LB.ByteString) where |
405 | 415 | fromField f dat = Binary . LB.fromChunks . (:[]) . unBinary <$> fromField f dat |
406 | 416 |
|
407 | | --- | name, text, \"char\", bpchar, varchar |
| 417 | +-- | name, text, \"char\", bpchar, varchar, unknown |
408 | 418 | instance FromField ST.Text where |
409 | | - fromField f = doFromField f okText $ (either left pure . ST.decodeUtf8') |
| 419 | + fromField f = doFromField f okText' $ (either left pure . ST.decodeUtf8') |
410 | 420 | -- FIXME: check character encoding |
411 | 421 |
|
412 | 422 | -- | name, text, \"char\", bpchar, varchar |
@@ -645,10 +655,93 @@ returnError mkErr f msg = do |
645 | 655 | atto :: forall a. (Typeable a) |
646 | 656 | => Compat -> Parser a -> Field -> Maybe ByteString |
647 | 657 | -> Conversion a |
648 | | -atto types p0 f dat = doFromField f types (go p0) dat |
| 658 | +atto types p0 f dat = doFromField f (\t -> types t || (t == $(inlineTypoid TI.unknown))) (go p0) dat |
649 | 659 | where |
650 | 660 | go :: Parser a -> ByteString -> Conversion a |
651 | 661 | go p s = |
652 | 662 | case parseOnly p s of |
653 | 663 | Left err -> returnError ConversionFailed f err |
654 | 664 | Right v -> pure v |
| 665 | + |
| 666 | + |
| 667 | +-- | Type class for default implementation of FromField using generics. |
| 668 | +class GFromField f where |
| 669 | + gfromField :: (Typeable p) |
| 670 | + => Proxy p |
| 671 | + -> (String -> String) |
| 672 | + -> Field |
| 673 | + -> [Maybe ByteString] |
| 674 | + -> Conversion (f p) |
| 675 | + |
| 676 | +instance (GFromField f) => GFromField (D1 i f) where |
| 677 | + gfromField w t f v = M1 <$> gfromField w t f v |
| 678 | + |
| 679 | +instance (GFromField f, Typeable f, Constructor i) => GFromField (C1 i f) where |
| 680 | + gfromField w t f (v:[]) = let |
| 681 | + tname = B8.pack . t . conName $ (undefined::(C1 i f t)) |
| 682 | + tcheck = (\t -> t /= "record" && t /= tname) |
| 683 | + in tcheck <$> typename f >>= \b -> M1 <$> case b of |
| 684 | + True -> returnError Incompatible f "" |
| 685 | + False -> maybe |
| 686 | + (returnError UnexpectedNull f "") |
| 687 | + (either |
| 688 | + (returnError ConversionFailed f) |
| 689 | + (gfromField w t f) |
| 690 | + . (parseOnly record)) v |
| 691 | + gfromField _ _ f _ = M1 <$> returnError ConversionFailed f errUnexpectedArgs |
| 692 | + |
| 693 | +instance (GFromField f, Typeable f, GFromField g) => GFromField (f :*: g) where |
| 694 | + gfromField _ _ f [] = liftA2 (:*:) (returnError ConversionFailed f errTooFewValues) empty |
| 695 | + gfromField w t f (v:vs) = liftA2 (:*:) (gfromField w t f [v]) (gfromField w t f vs) |
| 696 | + |
| 697 | +instance (GFromField f, Typeable f) => GFromField (S1 i f) where |
| 698 | + gfromField _ _ f [] = M1 <$> returnError ConversionFailed f errTooFewValues |
| 699 | + gfromField w t f (v:[]) = M1 <$> gfromField w t f [v] |
| 700 | + gfromField _ _ f _ = M1 <$> returnError ConversionFailed f errTooManyValues |
| 701 | + |
| 702 | +instance (FromField f, Typeable f) => GFromField (Rec0 f) where |
| 703 | + gfromField _ _ f [v] = K1 <$> fromField (f {typeOid = typoid TI.unknown}) v |
| 704 | + gfromField _ _ f _ = K1 <$> returnError ConversionFailed f errUnexpectedArgs |
| 705 | + |
| 706 | + |
| 707 | +-- | Common error messages for GFromField instances. |
| 708 | +errTooFewValues, errTooManyValues, errUnexpectedArgs :: String |
| 709 | +errTooFewValues = "too few values" |
| 710 | +errTooManyValues = "too many values" |
| 711 | +errUnexpectedArgs = "unexpected arguments" |
| 712 | + |
| 713 | +-- | Parser of a postgresql record. |
| 714 | +record :: Parser [Maybe ByteString] |
| 715 | +record = (char '(') *> (recordField `sepBy` (char ',')) <* (char ')') |
| 716 | + |
| 717 | +-- | Parser of a postgresql record's field. |
| 718 | +recordField :: Parser (Maybe ByteString) |
| 719 | +recordField = (Just <$> quotedString) <|> (Just <$> unquotedString) <|> (pure Nothing) where |
| 720 | + quotedString = unescape <$> (char '"' *> scan False updateState) where |
| 721 | + updateState isBalanced c = if |
| 722 | + | c == '"' -> Just . not $ isBalanced |
| 723 | + | not isBalanced -> Just False |
| 724 | + | c == ',' || c == ')' -> Nothing |
| 725 | + | otherwise -> fail $ "unexpected symbol: " ++ [c] |
| 726 | + |
| 727 | + unescape = unescape' '\\' . unescape' '"' . B8.init where |
| 728 | + unescape' c = halve c (byteString SB.empty) . groupByChar c |
| 729 | + |
| 730 | + groupByChar c = B8.groupBy $ \a b -> (a == c) == (b == c) |
| 731 | + |
| 732 | + halve :: Char -> Builder -> [ByteString] -> ByteString |
| 733 | + halve _ b [] = LB.toStrict . toLazyByteString $ b |
| 734 | + halve c b (s:ss) = halve c (b <> b') ss where |
| 735 | + b' = if |
| 736 | + | (/= c) . B8.head $ s -> byteString s |
| 737 | + | otherwise -> byteString . SB.take ((SB.length s) `div` 2) $ s |
| 738 | + |
| 739 | + unquotedString = takeWhile1 (\c -> c /= ',' && c /= ')') |
| 740 | + |
| 741 | +-- | Function that creates fromField for a given type. |
| 742 | +genericFromField :: forall a. (Generic a, Typeable a, GFromField (Rep a)) |
| 743 | + => (String -> String) -- ^ How to transform constructor's name to match |
| 744 | + -- postgresql type's name. |
| 745 | + -> FieldParser a |
| 746 | +genericFromField t f v = (to <$> (gfromField (Proxy :: Proxy a) t f [v])) |
| 747 | + |
0 commit comments