22{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
33{-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
44{-# LANGUAGE RecordWildCards, TemplateHaskell #-}
5+ {-# LANGUAGE MultiWayIf, DefaultSignatures #-}
6+ {-# LANGUAGE FlexibleContexts #-}
57
68{- |
79Module: Database.PostgreSQL.Simple.FromField
@@ -83,6 +85,7 @@ instances use 'typename' instead.
8385module Database.PostgreSQL.Simple.FromField
8486 (
8587 FromField (.. )
88+ , genericFromField
8689 , FieldParser
8790 , Conversion ()
8891
@@ -113,16 +116,19 @@ module Database.PostgreSQL.Simple.FromField
113116
114117#include "MachDeps.h"
115118
116- import Control.Applicative ( ( <|>) , (<$>) , pure , (*>) , (<*) )
119+ import Control.Applicative ( Alternative ( .. ), ( <|>) , (<$>) , pure , (*>) , (<*) , liftA2 )
117120import Control.Concurrent.MVar (MVar , newMVar )
118121import Control.Exception (Exception )
119122import qualified Data.Aeson as JSON
120123import qualified Data.Aeson.Parser as JSON (value' )
121124import Data.Attoparsec.ByteString.Char8 hiding (Result )
122125import Data.ByteString (ByteString )
126+ import Data.ByteString.Builder (Builder , toLazyByteString , byteString )
123127import qualified Data.ByteString.Char8 as B
128+ import Data.Char (toLower )
124129import Data.Int (Int16 , Int32 , Int64 )
125130import Data.IORef (IORef , newIORef )
131+ import Data.Proxy (Proxy (.. ))
126132import Data.Ratio (Ratio )
127133import Data.Time ( UTCTime , ZonedTime , LocalTime , Day , TimeOfDay )
128134import Data.Typeable (Typeable , typeOf )
@@ -150,6 +156,7 @@ import qualified Data.CaseInsensitive as CI
150156import Data.UUID.Types (UUID )
151157import qualified Data.UUID.Types as UUID
152158import Data.Scientific (Scientific )
159+ import GHC.Generics (Generic , Rep , M1 (.. ), K1 (.. ), D1 , C1 , S1 , Rec0 , Constructor , (:*:) (.. ), to , conName )
153160import GHC.Real (infinity , notANumber )
154161
155162-- | Exception thrown if conversion from a SQL value to a Haskell
@@ -188,6 +195,8 @@ type FieldParser a = Field -> Maybe ByteString -> Conversion a
188195-- | A type that may be converted from a SQL type.
189196class FromField a where
190197 fromField :: FieldParser a
198+ default fromField :: (Generic a , Typeable a , GFromField (Rep a )) => FieldParser a
199+ fromField = genericFromField (map toLower)
191200 -- ^ Convert a SQL value to a Haskell value.
192201 --
193202 -- Returns a list of exceptions if the conversion fails. In the case of
@@ -214,17 +223,18 @@ class FromField a where
214223-- postgresql-simple will check a per-connection cache, and then
215224-- finally query the database's meta-schema.
216225
217- typename :: Field -> Conversion ByteString
218- typename field = typname <$> typeInfo field
226+ typename :: Field -> Conversion ( Maybe ByteString )
227+ typename field = fmap typname <$> typeInfo field
219228
220- typeInfo :: Field -> Conversion TypeInfo
229+ typeInfo :: Field -> Conversion ( Maybe TypeInfo )
221230typeInfo Field {.. } = Conversion $ \ conn -> do
222- Ok <$> ( getTypeInfo conn typeOid)
231+ Ok <$> ( maybe ( return Nothing ) ( fmap Just . getTypeInfo conn) typeOid)
223232
224233typeInfoByOid :: PQ. Oid -> Conversion TypeInfo
225234typeInfoByOid oid = Conversion $ \ conn -> do
226235 Ok <$> (getTypeInfo conn oid)
227236
237+
228238-- | Returns the name of the column. This is often determined by a table
229239-- definition, but it can be set using an @as@ clause.
230240
@@ -262,7 +272,7 @@ format Field{..} = unsafeDupablePerformIO (PQ.fformat result column)
262272-- | void
263273instance FromField () where
264274 fromField f _bs
265- | typeOid f /= $ (inlineTypoid TI. void) = returnError Incompatible f " "
275+ | maybe False ( /= $ (inlineTypoid TI. void)) (typeOid f ) = returnError Incompatible f " "
266276 | otherwise = pure ()
267277
268278-- | For dealing with null values. Compatible with any postgresql type
@@ -292,7 +302,7 @@ instance FromField Null where
292302-- | bool
293303instance FromField Bool where
294304 fromField f bs
295- | typeOid f /= $ (inlineTypoid TI. bool) = returnError Incompatible f " "
305+ | maybe False ( /= $ (inlineTypoid TI. bool)) (typeOid f ) = returnError Incompatible f " "
296306 | bs == Nothing = returnError UnexpectedNull f " "
297307 | bs == Just " t" = pure True
298308 | bs == Just " f" = pure False
@@ -301,7 +311,7 @@ instance FromField Bool where
301311-- | \"char\"
302312instance FromField Char where
303313 fromField f bs =
304- if typeOid f /= $ (inlineTypoid TI. char)
314+ if maybe False ( /= $ (inlineTypoid TI. char)) (typeOid f )
305315 then returnError Incompatible f " "
306316 else case bs of
307317 Nothing -> returnError UnexpectedNull f " "
@@ -376,7 +386,7 @@ pg_rational
376386
377387-- | bytea, name, text, \"char\", bpchar, varchar, unknown
378388instance FromField SB. ByteString where
379- fromField f dat = if typeOid f == $ (inlineTypoid TI. bytea)
389+ fromField f dat = if maybe True ( == $ (inlineTypoid TI. bytea)) (typeOid f )
380390 then unBinary <$> fromField f dat
381391 else doFromField f okText' pure dat
382392
@@ -417,7 +427,7 @@ instance FromField LT.Text where
417427instance FromField (CI ST. Text ) where
418428 fromField f mdat = do
419429 typ <- typename f
420- if typ /= " citext"
430+ if maybe False ( /= " citext" ) typ
421431 then returnError Incompatible f " "
422432 else case mdat of
423433 Nothing -> returnError UnexpectedNull f " "
@@ -428,7 +438,7 @@ instance FromField (CI ST.Text) where
428438instance FromField (CI LT. Text ) where
429439 fromField f mdat = do
430440 typ <- typename f
431- if typ /= " citext"
441+ if maybe False ( /= " citext" ) typ
432442 then returnError Incompatible f " "
433443 else case mdat of
434444 Nothing -> returnError UnexpectedNull f " "
@@ -478,7 +488,7 @@ instance FromField Date where
478488ff :: PQ. Oid -> String -> (B8. ByteString -> Either String a )
479489 -> Field -> Maybe B8. ByteString -> Conversion a
480490ff compatOid hsType parse f mstr =
481- if typeOid f /= compatOid
491+ if maybe False ( /= compatOid) (typeOid f)
482492 then err Incompatible " "
483493 else case mstr of
484494 Nothing -> err UnexpectedNull " "
@@ -488,7 +498,7 @@ ff compatOid hsType parse f mstr =
488498 where
489499 err errC msg = do
490500 typnam <- typename f
491- left $ errC (B8. unpack typnam)
501+ left $ errC (maybe " " B8. unpack typnam)
492502 (tableOid f)
493503 (maybe " " B8. unpack (name f))
494504 hsType
@@ -507,23 +517,24 @@ instance (FromField a, Typeable a) => FromField (PGArray a) where
507517 fromField = pgArrayFieldParser fromField
508518
509519pgArrayFieldParser :: Typeable a => FieldParser a -> FieldParser (PGArray a )
510- pgArrayFieldParser fieldParser f mdat = do
511- info <- typeInfo f
512- case info of
513- TI. Array {} ->
514- case mdat of
515- Nothing -> returnError UnexpectedNull f " "
516- Just dat -> do
517- case parseOnly (fromArray fieldParser info f) dat of
518- Left err -> returnError ConversionFailed f err
519- Right conv -> PGArray <$> conv
520- _ -> returnError Incompatible f " "
520+ pgArrayFieldParser fieldParser f mdat = typeInfo f >>= maybe
521+ (returnError Incompatible f " arrays w/o typeinfo are not supported" )
522+ (\ info -> case info of
523+ TI. Array {} ->
524+ case mdat of
525+ Nothing -> returnError UnexpectedNull f " "
526+ Just dat -> do
527+ case parseOnly (fromArray fieldParser info f) dat of
528+ Left err -> returnError ConversionFailed f err
529+ Right conv -> PGArray <$> conv
530+ _ -> returnError Incompatible f " " )
531+
521532
522533fromArray :: FieldParser a -> TypeInfo -> Field -> Parser (Conversion [a ])
523534fromArray fieldParser typeInfo f = sequence . (parseIt <$> ) <$> array delim
524535 where
525536 delim = typdelim (typelem typeInfo)
526- fElem = f{ typeOid = typoid (typelem typeInfo) }
537+ fElem = f{ typeOid = Just $ typoid (typelem typeInfo) }
527538
528539 parseIt item =
529540 fieldParser f' $ if item == Arrays. Plain " NULL" then Nothing else Just item'
@@ -541,7 +552,7 @@ instance (FromField a, Typeable a) => FromField (IOVector a) where
541552-- | uuid
542553instance FromField UUID where
543554 fromField f mbs =
544- if typeOid f /= $ (inlineTypoid TI. uuid)
555+ if maybe False ( /= $ (inlineTypoid TI. uuid)) (typeOid f )
545556 then returnError Incompatible f " "
546557 else case mbs of
547558 Nothing -> returnError UnexpectedNull f " "
@@ -553,7 +564,8 @@ instance FromField UUID where
553564-- | json
554565instance FromField JSON. Value where
555566 fromField f mbs =
556- if typeOid f /= $ (inlineTypoid TI. json) && typeOid f /= $ (inlineTypoid TI. jsonb)
567+ if maybe False
568+ (\ t -> t /= $ (inlineTypoid TI. json) && t /= $ (inlineTypoid TI. jsonb)) (typeOid f)
557569 then returnError Incompatible f " "
558570 else case mbs of
559571 Nothing -> returnError UnexpectedNull f " "
@@ -621,8 +633,8 @@ okInt = ok64
621633doFromField :: forall a . (Typeable a )
622634 => Field -> Compat -> (ByteString -> Conversion a )
623635 -> Maybe ByteString -> Conversion a
624- doFromField f isCompat cvt (Just bs)
625- | isCompat ( typeOid f) = cvt bs
636+ doFromField f@ ( Field { .. }) isCompat cvt (Just bs)
637+ | maybe True isCompat typeOid = cvt bs
626638 | otherwise = returnError Incompatible f " types incompatible"
627639doFromField f _ _ _ = returnError UnexpectedNull f " "
628640
@@ -636,7 +648,7 @@ returnError :: forall a err . (Typeable a, Exception err)
636648 -> Field -> String -> Conversion a
637649returnError mkErr f msg = do
638650 typnam <- typename f
639- left $ mkErr (B. unpack typnam)
651+ left $ mkErr (maybe " " B. unpack typnam)
640652 (tableOid f)
641653 (maybe " " B. unpack (name f))
642654 (show (typeOf (undefined :: a )))
@@ -652,3 +664,86 @@ atto types p0 f dat = doFromField f types (go p0) dat
652664 case parseOnly p s of
653665 Left err -> returnError ConversionFailed f err
654666 Right v -> pure v
667+
668+
669+ -- | Type class for default implementation of FromField using generics.
670+ class GFromField f where
671+ gfromField :: (Typeable p )
672+ => Proxy p
673+ -> (String -> String )
674+ -> Field
675+ -> [Maybe ByteString ]
676+ -> Conversion (f p )
677+
678+ instance (GFromField f ) => GFromField (D1 i f ) where
679+ gfromField w t f v = M1 <$> gfromField w t f v
680+
681+ instance (GFromField f , Typeable f , Constructor i ) => GFromField (C1 i f ) where
682+ gfromField w t f (v: [] ) = let
683+ tname = B8. pack . t . conName $ (undefined :: (C1 i f t ))
684+ tcheck = maybe False (\ t -> t /= " record" && t /= tname)
685+ in tcheck <$> typename f >>= \ b -> M1 <$> case b of
686+ True -> returnError Incompatible f " "
687+ False -> maybe
688+ (returnError UnexpectedNull f " " )
689+ (either
690+ (returnError ConversionFailed f)
691+ (gfromField w t f)
692+ . (parseOnly record)) v
693+ gfromField _ _ f _ = M1 <$> returnError ConversionFailed f errUnexpectedArgs
694+
695+ instance (GFromField f , Typeable f , GFromField g ) => GFromField (f :*: g ) where
696+ gfromField _ _ f [] = liftA2 (:*:) (returnError ConversionFailed f errTooFewValues) empty
697+ gfromField w t f (v: vs) = liftA2 (:*:) (gfromField w t f [v]) (gfromField w t f vs)
698+
699+ instance (GFromField f , Typeable f ) => GFromField (S1 i f ) where
700+ gfromField _ _ f [] = M1 <$> returnError ConversionFailed f errTooFewValues
701+ gfromField w t f (v: [] ) = M1 <$> gfromField w t f [v]
702+ gfromField _ _ f _ = M1 <$> returnError ConversionFailed f errTooManyValues
703+
704+ instance (FromField f , Typeable f ) => GFromField (Rec0 f ) where
705+ gfromField _ _ f [v] = K1 <$> fromField (f {typeOid = Nothing }) v
706+ gfromField _ _ f _ = K1 <$> returnError ConversionFailed f errUnexpectedArgs
707+
708+
709+ -- | Common error messages for GFromField instances.
710+ errTooFewValues , errTooManyValues , errUnexpectedArgs :: String
711+ errTooFewValues = " too few values"
712+ errTooManyValues = " too many values"
713+ errUnexpectedArgs = " unexpected arguments"
714+
715+ -- | Parser of a postgresql record.
716+ record :: Parser [Maybe ByteString ]
717+ record = (char ' (' ) *> (recordField `sepBy` (char ' ,' )) <* (char ' )' )
718+
719+ -- | Parser of a postgresql record's field.
720+ recordField :: Parser (Maybe ByteString )
721+ recordField = (Just <$> quotedString) <|> (Just <$> unquotedString) <|> (pure Nothing ) where
722+ quotedString = unescape <$> (char ' "' *> scan False updateState) where
723+ updateState isBalanced c = if
724+ | c == ' "' -> Just . not $ isBalanced
725+ | not isBalanced -> Just False
726+ | c == ' ,' || c == ' )' -> Nothing
727+ | otherwise -> fail $ " unexpected symbol: " ++ [c]
728+
729+ unescape = unescape' ' \\ ' . unescape' ' "' . B8. init where
730+ unescape' c = halve c (byteString SB. empty) . groupByChar c
731+
732+ groupByChar c = B8. groupBy $ \ a b -> (a == c) == (b == c)
733+
734+ halve :: Char -> Builder -> [ByteString ] -> ByteString
735+ halve _ b [] = LB. toStrict . toLazyByteString $ b
736+ halve c b (s: ss) = halve c (b <> b') ss where
737+ b' = if
738+ | (/= c) . B8. head $ s -> byteString s
739+ | otherwise -> byteString . SB. take ((SB. length s) `div` 2 ) $ s
740+
741+ unquotedString = takeWhile1 (\ c -> c /= ' ,' && c /= ' )' )
742+
743+ -- | Function that creates fromField for a given type.
744+ genericFromField :: forall a . (Generic a , Typeable a , GFromField (Rep a ))
745+ => (String -> String ) -- ^ How to transform constructor's name to match
746+ -- postgresql type's name.
747+ -> FieldParser a
748+ genericFromField t f v = (to <$> (gfromField (Proxy :: Proxy a ) t f [v]))
749+
0 commit comments