@@ -2,6 +2,7 @@
22{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
33{-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
44{-# LANGUAGE RecordWildCards #-}
5+{-# LANGUAGE PolyKinds #-}
56
67{- |
78Module: Database.PostgreSQL.Simple.FromField
@@ -106,6 +107,7 @@ module Database.PostgreSQL.Simple.FromField
106107 , PQ.Oid(..)
107108 , PQ.Format(..)
108109 , pgArrayFieldParser
110+ , attoFieldParser
109111
110112 , optionalField
111113 , fromJSONField
@@ -114,19 +116,18 @@ module Database.PostgreSQL.Simple.FromField
114116
115117#include "MachDeps.h"
116118
117−import Control.Applicative ( (<|>), (<$>), pure, (*>), (<*) )
119+import Control.Applicative ( Const(Const), (<|>), (<$>), pure, (*>), (<*) )
118120import Control.Concurrent.MVar (MVar, newMVar)
119−import Control.Exception (Exception)
121+import Control.Exception (Exception (toException, fromException))
120122import qualified Data.Aeson as JSON
121−import qualified Data.Aeson.Internal as JSON
122−import qualified Data.Aeson.Parser as JSON (value')
123123import Data.Attoparsec.ByteString.Char8 hiding (Result)
124124import Data.ByteString (ByteString)
125125import qualified Data.ByteString.Char8 as B
126+import Data.Functor.Identity (Identity(Identity))
126127import Data.Int (Int16, Int32, Int64)
127128import Data.IORef (IORef, newIORef)
128129import Data.Ratio (Ratio)
129−import Data.Time ( UTCTime, ZonedTime, LocalTime, Day, TimeOfDay )
130+import Data.Time.Compat ( UTCTime, ZonedTime, LocalTime, Day, TimeOfDay, CalendarDiffTime )
130131import Data.Typeable (Typeable, typeOf)
131132import Data.Vector (Vector)
132133import Data.Vector.Mutable (IOVector)
@@ -153,6 +154,8 @@ import qualified Data.UUID.Types as UUID
153154import Data.Scientific (Scientific)
154155import GHC.Real (infinity, notANumber)
155156
157+import qualified Data.Aeson.Types as JSON
158+
156159-- | Exception thrown if conversion from a SQL value to a Haskell
157160-- value fails.
158161data ResultError = Incompatible { errSQLType :: String
@@ -179,7 +182,9 @@ data ResultError = Incompatible { errSQLType :: String
179182 -- between metadata and actual data in a row).
180183 deriving (Eq, Show, Typeable)
181184
182−instance Exception ResultError
185+instance Exception ResultError where
186+ toException = postgresqlExceptionToException
187+ fromException = postgresqlExceptionFromException
183188
184189left :: Exception a => a -> Conversion b
185190left = conversionError
@@ -268,6 +273,12 @@ instance FromField () where
268273 | typeOid f /= TI.voidOid = returnError Incompatible f ""
269274 | otherwise = pure ()
270275
276+instance (FromField a) => FromField (Const a b) where
277+ fromField f bs = Const <$> fromField f bs
278+
279+instance (FromField a) => FromField (Identity a) where
280+ fromField f bs = Identity <$> fromField f bs
281+
271282-- | For dealing with null values. Compatible with any postgresql type
272283-- compatible with type @a@. Note that the type is not checked if
273284-- the value is null, although it is inadvisable to rely on this
@@ -314,11 +325,11 @@ instance FromField Char where
314325
315326-- | int2
316327instance FromField Int16 where
317− fromField = atto ok16 $ signed decimal
328+ fromField = attoFieldParser ok16 $ signed decimal
318329
319330-- | int2, int4
320331instance FromField Int32 where
321− fromField = atto ok32 $ signed decimal
332+ fromField = attoFieldParser ok32 $ signed decimal
322333
323334#if WORD_SIZE_IN_BITS < 64
324335-- | int2, int4, and if compiled as 64-bit code, int8 as well.
@@ -328,36 +339,36 @@ instance FromField Int32 where
328339-- This library was compiled as 64-bit code.
329340#endif
330341instance FromField Int where
331− fromField = atto okInt $ signed decimal
342+ fromField = attoFieldParser okInt $ signed decimal
332343
333344-- | int2, int4, int8
334345instance FromField Int64 where
335− fromField = atto ok64 $ signed decimal
346+ fromField = attoFieldParser ok64 $ signed decimal
336347
337348-- | int2, int4, int8
338349instance FromField Integer where
339− fromField = atto ok64 $ signed decimal
350+ fromField = attoFieldParser ok64 $ signed decimal
340351
341352-- | int2, float4 (Uses attoparsec's 'double' routine, for
342353-- better accuracy convert to 'Scientific' or 'Rational' first)
343354instance FromField Float where
344− fromField = atto ok (realToFrac <$> pg_double)
355+ fromField = attoFieldParser ok (realToFrac <$> pg_double)
345356 where ok = eq TI.float4Oid \/ eq TI.int2Oid
346357
347358-- | int2, int4, float4, float8 (Uses attoparsec's 'double' routine, for
348359-- better accuracy convert to 'Scientific' or 'Rational' first)
349360instance FromField Double where
350− fromField = atto ok pg_double
361+ fromField = attoFieldParser ok pg_double
351362 where ok = eq TI.float4Oid \/ eq TI.float8Oid \/ eq TI.int2Oid \/ eq TI.int4Oid
352363
353364-- | int2, int4, int8, float4, float8, numeric
354365instance FromField (Ratio Integer) where
355− fromField = atto ok pg_rational
366+ fromField = attoFieldParser ok pg_rational
356367 where ok = eq TI.float4Oid \/ eq TI.float8Oid \/ eq TI.int2Oid \/ eq TI.int4Oid \/ eq TI.int8Oid \/ eq TI.numericOid
357368
358369-- | int2, int4, int8, float4, float8, numeric
359370instance FromField Scientific where
360− fromField = atto ok rational
371+ fromField = attoFieldParser ok rational
361372 where ok = eq TI.float4Oid \/ eq TI.float8Oid \/ eq TI.int2Oid \/ eq TI.int4Oid \/ eq TI.int8Oid \/ eq TI.numericOid
362373
363374unBinary :: Binary t -> t
@@ -385,7 +396,7 @@ instance FromField SB.ByteString where
385396
386397-- | oid
387398instance FromField PQ.Oid where
388− fromField f dat = PQ.Oid <$> atto (== TI.oidOid) decimal f dat
399+ fromField f dat = PQ.Oid <$> attoFieldParser (== TI.oidOid) decimal f dat
389400
390401-- | bytea, name, text, \"char\", bpchar, varchar, unknown
391402instance FromField LB.ByteString where
@@ -478,6 +489,15 @@ instance FromField LocalTimestamp where
478489instance FromField Date where
479490 fromField = ff TI.dateOid "Date" parseDate
480491
492+-- | interval. Requires you to configure intervalstyle as @iso_8601@.
493+--
494+-- You can configure intervalstyle on every connection with a @SET@ command,
495+-- but for better performance you may want to configure it permanently in the
496+-- file found with @SHOW config_file;@ .
497+--
498+instance FromField CalendarDiffTime where
499+ fromField = ff TI.intervalOid "CalendarDiffTime" parseCalendarDiffTime
500+
481501ff :: PQ.Oid -> String -> (B8.ByteString -> Either String a)
482502 -> Field -> Maybe B8.ByteString -> Conversion a
483503ff compatOid hsType parseBS f mstr =
@@ -556,7 +576,7 @@ instance FromField UUID where
556576-- | json, jsonb
557577instance FromField JSON.Value where
558578 fromField f mbs = parseBS =<< fromFieldJSONByteString f mbs
559− where parseBS bs = case parseOnly (JSON.value' <* endOfInput) bs of
579+ where parseBS bs = case JSON.eitherDecodeStrict' bs of
560580 Left err -> returnError ConversionFailed f err
561581 Right val -> pure val
562582
@@ -664,10 +684,22 @@ returnError mkErr f msg = do
664684 (show (typeOf (undefined :: a)))
665685 msg
666686
667−atto :: forall a. (Typeable a)
668− => Compat -> Parser a -> Field -> Maybe ByteString
669− -> Conversion a
670−atto types p0 f dat = doFromField f types (go p0) dat
687+-- | Construct a field parser from an attoparsec parser. An 'Incompatible' error is thrown if the
688+-- PostgreSQL oid does not match the specified predicate.
689+--
690+-- @
691+-- instance FromField Int16 where
692+-- fromField = attoFieldParser ok16 (signed decimal)
693+-- @
694+--
695+-- @since 0.6.3
696+attoFieldParser :: forall a. (Typeable a)
697+ => (PQ.Oid -> Bool)
698+ -- ^ Predicate for whether the postgresql type oid is compatible with this parser
699+ -> Parser a
700+ -- ^ An attoparsec parser.
701+ -> FieldParser a
702+attoFieldParser types p0 f dat = doFromField f types (go p0) dat
671703 where
672704 go :: Parser a -> ByteString -> Conversion a
673705 go p s =