hasql 0.14.0.3 → 0.15.0.2
raw patch · 31 files changed
+1815/−1818 lines, 31 files
Files
- benchmark/Main.hs +2/−2
- benchmark/Main/Queries.hs +2/−2
- hasql.cabal +12/−14
- library/Hasql/Commands.hs +3/−3
- library/Hasql/Decoders.hs +669/−0
- library/Hasql/Decoders/Array.hs +31/−0
- library/Hasql/Decoders/Composite.hs +26/−0
- library/Hasql/Decoders/Result.hs +232/−0
- library/Hasql/Decoders/Results.hs +90/−0
- library/Hasql/Decoders/Row.hs +65/−0
- library/Hasql/Decoders/Value.hs +23/−0
- library/Hasql/Decoding.hs +0/−669
- library/Hasql/Decoding/Array.hs +0/−31
- library/Hasql/Decoding/Composite.hs +0/−26
- library/Hasql/Decoding/Result.hs +0/−232
- library/Hasql/Decoding/Results.hs +0/−90
- library/Hasql/Decoding/Row.hs +0/−65
- library/Hasql/Decoding/Value.hs +0/−23
- library/Hasql/Encoders.hs +483/−0
- library/Hasql/Encoders/Array.hs +31/−0
- library/Hasql/Encoders/Params.hs +49/−0
- library/Hasql/Encoders/Value.hs +27/−0
- library/Hasql/Encoding.hs +0/−483
- library/Hasql/Encoding/Array.hs +0/−31
- library/Hasql/Encoding/Params.hs +0/−49
- library/Hasql/Encoding/Value.hs +0/−27
- library/Hasql/IO.hs +18/−18
- library/Hasql/Query.hs +13/−13
- tasty/Main.hs +35/−36
- tasty/Main/DSL.hs +2/−2
- tasty/Main/Queries.hs +2/−2
benchmark/Main.hs view
@@ -5,8 +5,8 @@ import qualified Hasql.Connection as HC import qualified Hasql.Settings as HS import qualified Hasql.Query as HQ-import qualified Hasql.Encoding as HE-import qualified Hasql.Decoding as HD+import qualified Hasql.Encoders as HE+import qualified Hasql.Decoders as HD import qualified Main.Queries as Q
benchmark/Main/Queries.hs view
@@ -2,8 +2,8 @@ import Main.Prelude import qualified Hasql.Query as HQ-import qualified Hasql.Encoding as HE-import qualified Hasql.Decoding as HD+import qualified Hasql.Encoders as HE+import qualified Hasql.Decoders as HD select1 :: Int -> HQ.Query () (Vector Int64)
hasql.cabal view
@@ -1,7 +1,7 @@ name: hasql version:- 0.14.0.3+ 0.15.0.2 category: Hasql, Database, PostgreSQL synopsis:@@ -50,20 +50,20 @@ Hasql.PTI Hasql.IO Hasql.Commands- Hasql.Decoding.Array- Hasql.Decoding.Composite- Hasql.Decoding.Value- Hasql.Decoding.Row- Hasql.Decoding.Result- Hasql.Decoding.Results- Hasql.Encoding.Array- Hasql.Encoding.Value- Hasql.Encoding.Params+ Hasql.Decoders.Array+ Hasql.Decoders.Composite+ Hasql.Decoders.Value+ Hasql.Decoders.Row+ Hasql.Decoders.Result+ Hasql.Decoders.Results+ Hasql.Encoders.Array+ Hasql.Encoders.Value+ Hasql.Encoders.Params Hasql.PreparedStatementRegistry Hasql.Connection.Impl exposed-modules:- Hasql.Decoding- Hasql.Encoding+ Hasql.Decoders+ Hasql.Encoders Hasql.Settings Hasql.Connection Hasql.Query@@ -99,8 +99,6 @@ base >= 4.6 && < 5 --- Note that this test-suite is supposed to be single-threaded.--- Hence the absense of the appropriate GHC flags. test-suite tasty type: exitcode-stdio-1.0
library/Hasql/Commands.hs view
@@ -2,7 +2,7 @@ ( Commands, asBytes,- setEncodingToUTF8,+ setEncodersToUTF8, setMinClientMessagesToWarning, ) where@@ -22,8 +22,8 @@ asBytes (Commands list) = BL.toStrict $ BB.toLazyByteString $ foldMap (<> BB.char7 ';') $ list -setEncodingToUTF8 :: Commands-setEncodingToUTF8 =+setEncodersToUTF8 :: Commands+setEncodersToUTF8 = Commands (pure "SET client_encoding = 'UTF8'") setMinClientMessagesToWarning :: Commands
+ library/Hasql/Decoders.hs view
@@ -0,0 +1,669 @@+-- |+-- A DSL for declaration of result decoders.+module Hasql.Decoders+(+ -- * Result+ Result,+ unit,+ rowsAffected,+ singleRow,+ -- ** Specialized multi-row results+ maybeRow,+ rowsVector,+ rowsList,+ -- ** Multi-row traversers+ foldlRows,+ foldrRows,+ -- * Row+ Row,+ value,+ nullableValue,+ -- * Value+ Value,+ bool,+ int2,+ int4,+ int8,+ float4,+ float8,+ numeric,+ char,+ text,+ bytea,+ date,+ timestamp,+ timestamptz,+ time,+ timetz,+ interval,+ uuid,+ json,+ array,+ composite,+ hstore,+ enum,+ -- * Array+ Array,+ arrayDimension,+ arrayValue,+ arrayNullableValue,+ -- * Composite+ Composite,+ compositeValue,+ compositeNullableValue,+)+where++import Hasql.Prelude hiding (maybe, bool)+import qualified Data.Aeson as Aeson+import qualified Data.Vector as Vector+import qualified PostgreSQL.Binary.Decoder as Decoder+import qualified Hasql.Decoders.Results as Results+import qualified Hasql.Decoders.Result as Result+import qualified Hasql.Decoders.Row as Row+import qualified Hasql.Decoders.Value as Value+import qualified Hasql.Decoders.Array as Array+import qualified Hasql.Decoders.Composite as Composite+import qualified Hasql.Prelude as Prelude+++-- * Result+-------------------------++-- |+-- Decoder of a query result.+-- +newtype Result a =+ Result (Results.Results a)+ deriving (Functor)++-- |+-- Decode no value from the result.+-- +-- Useful for statements like @INSERT@ or @CREATE@.+-- +{-# INLINABLE unit #-}+unit :: Result ()+unit =+ Result (Results.single Result.unit)++-- |+-- Get the amount of rows affected by such statements as+-- @UPDATE@ or @DELETE@.+-- +{-# INLINABLE rowsAffected #-}+rowsAffected :: Result Int64+rowsAffected =+ Result (Results.single Result.rowsAffected)++-- |+-- Exactly one row.+-- Will raise the 'Hasql.Query.UnexpectedAmountOfRows' error if it's any other.+-- +{-# INLINABLE singleRow #-}+singleRow :: Row a -> Result a+singleRow (Row row) =+ Result (Results.single (Result.single row))++-- ** Multi-row traversers+-------------------------++-- |+-- Foldl multiple rows.+-- +{-# INLINABLE foldlRows #-}+foldlRows :: (a -> b -> a) -> a -> Row b -> Result a+foldlRows step init (Row row) =+ Result (Results.single (Result.foldl step init row))++-- |+-- Foldr multiple rows.+-- +{-# INLINABLE foldrRows #-}+foldrRows :: (b -> a -> a) -> a -> Row b -> Result a+foldrRows step init (Row row) =+ Result (Results.single (Result.foldr step init row))++-- ** Specialized multi-row results+-------------------------++-- |+-- Maybe one row or none.+-- +{-# INLINABLE maybeRow #-}+maybeRow :: Row a -> Result (Maybe a)+maybeRow (Row row) =+ Result (Results.single (Result.maybe row))++-- |+-- Zero or more rows packed into the vector.+-- +-- It's recommended to prefer this function to 'rowsList',+-- since it performs notably better.+-- +{-# INLINABLE rowsVector #-}+rowsVector :: Row a -> Result (Vector a)+rowsVector (Row row) =+ Result (Results.single (Result.vector row))++-- |+-- Zero or more rows packed into the list.+-- +{-# INLINABLE rowsList #-}+rowsList :: Row a -> Result [a]+rowsList =+ foldrRows strictCons []+++-- ** Instances+-------------------------++-- | Maps to 'unit'.+instance Default (Result ()) where+ {-# INLINE def #-}+ def =+ unit++-- | Maps to 'rowsAffected'.+instance Default (Result Int64) where+ {-# INLINE def #-}+ def =+ rowsAffected++-- | Maps to @('maybeRow' def)@.+instance Default (Row a) => Default (Result (Maybe a)) where+ {-# INLINE def #-}+ def =+ maybeRow def++-- | Maps to @('rowsVector' def)@.+instance Default (Row a) => Default (Result (Vector a)) where+ {-# INLINE def #-}+ def =+ rowsVector def++-- | Maps to @('rowsList' def)@.+instance Default (Row a) => Default (Result ([] a)) where+ {-# INLINE def #-}+ def =+ rowsList def++-- | Maps to @(fmap Identity ('singleRow' def)@.+instance Default (Row a) => Default (Result (Identity a)) where+ {-# INLINE def #-}+ def =+ fmap Identity (singleRow def)+++-- * Row+-------------------------++-- |+-- Decoder of an individual row,+-- which gets composed of column value decoders.+-- +-- E.g.:+-- +-- >x :: Row (Maybe Int64, Text, TimeOfDay)+-- >x =+-- > (,,) <$> nullableValue int8 <*> value text <*> value time+-- +newtype Row a =+ Row (Row.Row a)+ deriving (Functor, Applicative, Monad)++-- |+-- Lift an individual non-nullable value decoder to a composable row decoder.+-- +{-# INLINABLE value #-}+value :: Value a -> Row a+value (Value imp) =+ Row (Row.nonNullValue imp)++-- |+-- Lift an individual nullable value decoder to a composable row decoder.+-- +{-# INLINABLE nullableValue #-}+nullableValue :: Value a -> Row (Maybe a)+nullableValue (Value imp) =+ Row (Row.value imp)+++-- ** Instances+-------------------------++instance Default (Value a) => Default (Row (Identity a)) where+ {-# INLINE def #-}+ def =+ fmap Identity (value def)++instance Default (Value a) => Default (Row (Maybe a)) where+ {-# INLINE def #-}+ def =+ nullableValue def++instance (Default (Value a1), Default (Value a2)) => Default (Row (a1, a2)) where+ {-# INLINE def #-}+ def =+ ap (fmap (,) (value def)) (value def)+++-- * Value+-------------------------++-- |+-- Decoder of an individual value.+-- +newtype Value a =+ Value (Value.Value a)+ deriving (Functor)+++-- ** Plain value decoders+-------------------------++-- |+-- Decoder of the @BOOL@ values.+-- +{-# INLINABLE bool #-}+bool :: Value Bool+bool =+ Value (Value.decoder (const Decoder.bool))++-- |+-- Decoder of the @INT2@ values.+-- +{-# INLINABLE int2 #-}+int2 :: Value Int16+int2 =+ Value (Value.decoder (const Decoder.int))++-- |+-- Decoder of the @INT4@ values.+-- +{-# INLINABLE int4 #-}+int4 :: Value Int32+int4 =+ Value (Value.decoder (const Decoder.int))++-- |+-- Decoder of the @INT8@ values.+-- +{-# INLINABLE int8 #-}+int8 :: Value Int64+int8 =+ {-# SCC "int8" #-} + Value (Value.decoder (const ({-# SCC "int8.int" #-} Decoder.int)))++-- |+-- Decoder of the @FLOAT4@ values.+-- +{-# INLINABLE float4 #-}+float4 :: Value Float+float4 =+ Value (Value.decoder (const Decoder.float4))++-- |+-- Decoder of the @FLOAT8@ values.+-- +{-# INLINABLE float8 #-}+float8 :: Value Double+float8 =+ Value (Value.decoder (const Decoder.float8))++-- |+-- Decoder of the @NUMERIC@ values.+-- +{-# INLINABLE numeric #-}+numeric :: Value Scientific+numeric =+ Value (Value.decoder (const Decoder.numeric))++-- |+-- Decoder of the @CHAR@ values.+-- Note that it supports UTF-8 values.+{-# INLINABLE char #-}+char :: Value Char+char =+ Value (Value.decoder (const Decoder.char))++-- |+-- Decoder of the @TEXT@ values.+-- +{-# INLINABLE text #-}+text :: Value Text+text =+ Value (Value.decoder (const Decoder.text_strict))++-- |+-- Decoder of the @BYTEA@ values.+-- +{-# INLINABLE bytea #-}+bytea :: Value ByteString+bytea =+ Value (Value.decoder (const Decoder.bytea_strict))++-- |+-- Decoder of the @DATE@ values.+-- +{-# INLINABLE date #-}+date :: Value Day+date =+ Value (Value.decoder (const Decoder.date))++-- |+-- Decoder of the @TIMESTAMP@ values.+-- +{-# INLINABLE timestamp #-}+timestamp :: Value LocalTime+timestamp =+ Value (Value.decoder (Prelude.bool Decoder.timestamp_float Decoder.timestamp_int))++-- |+-- Decoder of the @TIMESTAMPTZ@ values.+-- +-- /NOTICE/+-- +-- Postgres does not store the timezone information of @TIMESTAMPTZ@.+-- Instead it stores a UTC value and performs silent conversions+-- to the currently set timezone, when dealt with in the text format.+-- However this library bypasses the silent conversions+-- and communicates with Postgres using the UTC values directly.+{-# INLINABLE timestamptz #-}+timestamptz :: Value UTCTime+timestamptz =+ Value (Value.decoder (Prelude.bool Decoder.timestamptz_float Decoder.timestamptz_int))++-- |+-- Decoder of the @TIME@ values.+-- +{-# INLINABLE time #-}+time :: Value TimeOfDay+time =+ Value (Value.decoder (Prelude.bool Decoder.time_float Decoder.time_int))++-- |+-- Decoder of the @TIMETZ@ values.+-- +-- Unlike in case of @TIMESTAMPTZ@, +-- Postgres does store the timezone information for @TIMETZ@.+-- However the Haskell's \"time\" library does not contain any composite type,+-- that fits the task, so we use a pair of 'TimeOfDay' and 'TimeZone'+-- to represent a value on the Haskell's side.+{-# INLINABLE timetz #-}+timetz :: Value (TimeOfDay, TimeZone)+timetz =+ Value (Value.decoder (Prelude.bool Decoder.timetz_float Decoder.timetz_int))++-- |+-- Decoder of the @INTERVAL@ values.+-- +{-# INLINABLE interval #-}+interval :: Value DiffTime+interval =+ Value (Value.decoder (Prelude.bool Decoder.interval_float Decoder.interval_int))++-- |+-- Decoder of the @UUID@ values.+-- +{-# INLINABLE uuid #-}+uuid :: Value UUID+uuid =+ Value (Value.decoder (const Decoder.uuid))++-- |+-- Decoder of the @JSON@ values.+-- +{-# INLINABLE json #-}+json :: Value Aeson.Value+json =+ Value (Value.decoder (const Decoder.json))+++-- ** Composite value decoders+-------------------------++-- |+-- Lifts the 'Array' decoder to the 'Value' decoder.+-- +{-# INLINABLE array #-}+array :: Array a -> Value a+array (Array imp) =+ Value (Value.decoder (Array.run imp))++-- |+-- Lifts the 'Composite' decoder to the 'Value' decoder.+-- +{-# INLINABLE composite #-}+composite :: Composite a -> Value a+composite (Composite imp) =+ Value (Value.decoder (Composite.run imp))++-- |+-- A generic decoder of @HSTORE@ values.+-- +-- Here's how you can use it to construct a specific value:+-- +-- @+-- x :: Value [(Text, Maybe Text)]+-- x =+-- hstore 'replicateM'+-- @+-- +{-# INLINABLE hstore #-}+hstore :: (forall m. Monad m => Int -> m (Text, Maybe Text) -> m a) -> Value a+hstore replicateM =+ Value (Value.decoder (const (Decoder.hstore replicateM Decoder.text_strict Decoder.text_strict)))++-- |+-- Given a partial mapping from text to value,+-- produces a decoder of that value.+enum :: (Text -> Maybe a) -> Value a+enum mapping =+ Value (Value.decoder (const (Decoder.enum mapping)))+++-- ** Instances+-------------------------++-- |+-- Maps to 'bool'.+instance Default (Value Bool) where+ {-# INLINE def #-}+ def =+ bool++-- |+-- Maps to 'int2'.+instance Default (Value Int16) where+ {-# INLINE def #-}+ def =+ int2++-- |+-- Maps to 'int4'.+instance Default (Value Int32) where+ {-# INLINE def #-}+ def =+ int4++-- |+-- Maps to 'int8'.+instance Default (Value Int64) where+ {-# INLINE def #-}+ def =+ int8++-- |+-- Maps to 'float4'.+instance Default (Value Float) where+ {-# INLINE def #-}+ def =+ float4++-- |+-- Maps to 'float8'.+instance Default (Value Double) where+ {-# INLINE def #-}+ def =+ float8++-- |+-- Maps to 'numeric'.+instance Default (Value Scientific) where+ {-# INLINE def #-}+ def =+ numeric++-- |+-- Maps to 'char'.+instance Default (Value Char) where+ {-# INLINE def #-}+ def =+ char++-- |+-- Maps to 'text'.+instance Default (Value Text) where+ {-# INLINE def #-}+ def =+ text++-- |+-- Maps to 'bytea'.+instance Default (Value ByteString) where+ {-# INLINE def #-}+ def =+ bytea++-- |+-- Maps to 'date'.+instance Default (Value Day) where+ {-# INLINE def #-}+ def =+ date++-- |+-- Maps to 'timestamp'.+instance Default (Value LocalTime) where+ {-# INLINE def #-}+ def =+ timestamp++-- |+-- Maps to 'timestamptz'.+instance Default (Value UTCTime) where+ {-# INLINE def #-}+ def =+ timestamptz++-- |+-- Maps to 'time'.+instance Default (Value TimeOfDay) where+ {-# INLINE def #-}+ def =+ time++-- |+-- Maps to 'timetz'.+instance Default (Value (TimeOfDay, TimeZone)) where+ {-# INLINE def #-}+ def =+ timetz++-- |+-- Maps to 'interval'.+instance Default (Value DiffTime) where+ {-# INLINE def #-}+ def =+ interval++-- |+-- Maps to 'uuid'.+instance Default (Value UUID) where+ {-# INLINE def #-}+ def =+ uuid++-- |+-- Maps to 'json'.+instance Default (Value Aeson.Value) where+ {-# INLINE def #-}+ def =+ json+++-- * Array decoders+-------------------------++-- |+-- A generic array decoder.+-- +-- Here's how you can use it to produce a specific array value decoder:+-- +-- @+-- x :: Value [[Text]]+-- x =+-- array (arrayDimension 'replicateM' (arrayDimension 'replicateM' (arrayValue text)))+-- @+-- +newtype Array a =+ Array (Array.Array a)+ deriving (Functor)++-- |+-- A function for parsing a dimension of an array.+-- Provides support for multi-dimensional arrays.+-- +-- Accepts:+-- +-- * An implementation of the @replicateM@ function+-- (@Control.Monad.'Control.Monad.replicateM'@, @Data.Vector.'Data.Vector.replicateM'@),+-- which determines the output value.+-- +-- * A decoder of its components, which can be either another 'arrayDimension',+-- 'arrayValue' or 'arrayNullableValue'.+-- +{-# INLINABLE arrayDimension #-}+arrayDimension :: (forall m. Monad m => Int -> m a -> m b) -> Array a -> Array b+arrayDimension replicateM (Array imp) =+ Array (Array.dimension replicateM imp)++-- |+-- Lift a 'Value' decoder into an 'Array' decoder for parsing of non-nullable leaf values.+{-# INLINABLE arrayValue #-}+arrayValue :: Value a -> Array a+arrayValue (Value imp) =+ Array (Array.nonNullValue (Value.run imp))++-- |+-- Lift a 'Value' decoder into an 'Array' decoder for parsing of nullable leaf values.+{-# INLINABLE arrayNullableValue #-}+arrayNullableValue :: Value a -> Array (Maybe a)+arrayNullableValue (Value imp) =+ Array (Array.value (Value.run imp))+++-- * Composite decoders+-------------------------++-- |+-- Composable decoder of composite values (rows, records).+newtype Composite a =+ Composite (Composite.Composite a)+ deriving (Functor, Applicative, Monad)++-- |+-- Lift a 'Value' decoder into a 'Composite' decoder for parsing of non-nullable leaf values.+{-# INLINABLE compositeValue #-}+compositeValue :: Value a -> Composite a+compositeValue (Value imp) =+ Composite (Composite.nonNullValue (Value.run imp))++-- |+-- Lift a 'Value' decoder into a 'Composite' decoder for parsing of nullable leaf values.+{-# INLINABLE compositeNullableValue #-}+compositeNullableValue :: Value a -> Composite (Maybe a)+compositeNullableValue (Value imp) =+ Composite (Composite.value (Value.run imp))+
+ library/Hasql/Decoders/Array.hs view
@@ -0,0 +1,31 @@+module Hasql.Decoders.Array where++import Hasql.Prelude+import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified PostgreSQL.Binary.Decoder as Decoder+++newtype Array a =+ Array (ReaderT Bool Decoder.ArrayDecoder a)+ deriving (Functor)++{-# INLINE run #-}+run :: Array a -> Bool -> Decoder.Decoder a+run (Array imp) env =+ Decoder.array (runReaderT imp env)++{-# INLINE dimension #-}+dimension :: (forall m. Monad m => Int -> m a -> m b) -> Array a -> Array b+dimension replicateM (Array imp) =+ Array $ ReaderT $ \env -> Decoder.arrayDimension replicateM (runReaderT imp env)++{-# INLINE value #-}+value :: (Bool -> Decoder.Decoder a) -> Array (Maybe a)+value decoder' =+ Array $ ReaderT $ Decoder.arrayValue . decoder'++{-# INLINE nonNullValue #-}+nonNullValue :: (Bool -> Decoder.Decoder a) -> Array a+nonNullValue decoder' =+ Array $ ReaderT $ Decoder.arrayNonNullValue . decoder'+
+ library/Hasql/Decoders/Composite.hs view
@@ -0,0 +1,26 @@+module Hasql.Decoders.Composite where++import Hasql.Prelude+import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified PostgreSQL.Binary.Decoder as Decoder+++newtype Composite a =+ Composite (ReaderT Bool Decoder.CompositeDecoder a)+ deriving (Functor, Applicative, Monad)++{-# INLINE run #-}+run :: Composite a -> Bool -> Decoder.Decoder a+run (Composite imp) env =+ Decoder.composite (runReaderT imp env)++{-# INLINE value #-}+value :: (Bool -> Decoder.Decoder a) -> Composite (Maybe a)+value decoder' =+ Composite $ ReaderT $ Decoder.compositeValue . decoder'++{-# INLINE nonNullValue #-}+nonNullValue :: (Bool -> Decoder.Decoder a) -> Composite a+nonNullValue decoder' =+ Composite $ ReaderT $ Decoder.compositeNonNullValue . decoder'+
+ library/Hasql/Decoders/Result.hs view
@@ -0,0 +1,232 @@+module Hasql.Decoders.Result where++import Hasql.Prelude hiding (maybe, many)+import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified Hasql.Decoders.Row as Row+import qualified Data.Attoparsec.ByteString.Char8 as Attoparsec+import qualified Data.ByteString as ByteString+import qualified Hasql.Prelude as Prelude+import qualified Data.Vector as Vector+import qualified Data.Vector.Mutable as MutableVector+++newtype Result a =+ Result (ReaderT (Bool, LibPQ.Result) (EitherT Error IO) a)+ deriving (Functor, Applicative, Monad)++data Error =+ -- | + -- An error reported by the DB. Code, message, details, hint.+ -- + -- * The SQLSTATE code for the error. The SQLSTATE code identifies the type of error that has occurred; + -- it can be used by front-end applications to perform specific operations (such as error handling) + -- in response to a particular database error. + -- For a list of the possible SQLSTATE codes, see Appendix A.+ -- This field is not localizable, and is always present.+ -- + -- * The primary human-readable error message (typically one line). Always present.+ -- + -- * Detail: an optional secondary error message carrying more detail about the problem. + -- Might run to multiple lines.+ -- + -- * Hint: an optional suggestion what to do about the problem. + -- This is intended to differ from detail in that it offers advice (potentially inappropriate) + -- rather than hard facts. Might run to multiple lines.+ ServerError !ByteString !ByteString !(Maybe ByteString) !(Maybe ByteString) |+ -- |+ -- The database returned an unexpected result.+ -- Indicates an improper statement or a schema mismatch.+ UnexpectedResult !Text |+ -- |+ -- An error of the row reader, preceded by the index of the row.+ RowError !Int !Row.Error |+ -- |+ -- An unexpected amount of rows.+ UnexpectedAmountOfRows !Int+ deriving (Show)++{-# INLINE run #-}+run :: Result a -> (Bool, LibPQ.Result) -> IO (Either Error a)+run (Result reader) env =+ runEitherT (runReaderT reader env)++{-# INLINE unit #-}+unit :: Result ()+unit =+ checkExecStatus $ \case+ LibPQ.CommandOk -> True+ LibPQ.TuplesOk -> True+ _ -> False++{-# INLINE rowsAffected #-}+rowsAffected :: Result Int64+rowsAffected =+ do+ checkExecStatus $ \case+ LibPQ.CommandOk -> True+ _ -> False+ Result $ ReaderT $ \(_, result) -> EitherT $+ LibPQ.cmdTuples result & fmap cmdTuplesReader+ where+ cmdTuplesReader =+ notNothing >=> notEmpty >=> decimal+ where+ notNothing =+ Prelude.maybe (Left (UnexpectedResult "No bytes")) Right+ notEmpty bytes =+ if ByteString.null bytes+ then Left (UnexpectedResult "Empty bytes")+ else Right bytes+ decimal bytes =+ mapLeft (\m -> UnexpectedResult ("Decimal parsing failure: " <> fromString m)) $+ Attoparsec.parseOnly (Attoparsec.decimal <* Attoparsec.endOfInput) bytes++{-# INLINE checkExecStatus #-}+checkExecStatus :: (LibPQ.ExecStatus -> Bool) -> Result ()+checkExecStatus predicate =+ {-# SCC "checkExecStatus" #-} + do+ status <- Result $ ReaderT $ \(_, result) -> lift $ LibPQ.resultStatus result+ unless (predicate status) $ do+ case status of+ LibPQ.BadResponse -> serverError+ LibPQ.NonfatalError -> serverError+ LibPQ.FatalError -> serverError+ _ -> Result $ lift $ EitherT $ pure $ Left $ UnexpectedResult $ "Unexpected result status: " <> (fromString $ show status)++{-# INLINE serverError #-}+serverError :: Result ()+serverError =+ Result $ ReaderT $ \(_, result) -> EitherT $ do+ code <- + fmap (fromMaybe ($bug "No code")) $+ LibPQ.resultErrorField result LibPQ.DiagSqlstate+ message <- + fmap (fromMaybe ($bug "No message")) $+ LibPQ.resultErrorField result LibPQ.DiagMessagePrimary+ detail <- + LibPQ.resultErrorField result LibPQ.DiagMessageDetail+ hint <- + LibPQ.resultErrorField result LibPQ.DiagMessageHint+ pure $ Left $ ServerError code message detail hint++{-# INLINE maybe #-}+maybe :: Row.Row a -> Result (Maybe a)+maybe rowDec =+ do+ checkExecStatus $ \case+ LibPQ.TuplesOk -> True+ _ -> False+ Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ do+ maxRows <- LibPQ.ntuples result+ case maxRows of+ 0 -> return (Right Nothing)+ 1 -> do+ maxCols <- LibPQ.nfields result+ fmap (fmap Just . mapLeft (RowError 0)) $ Row.run rowDec (result, 0, maxCols, integerDatetimes)+ _ -> return (Left (UnexpectedAmountOfRows (rowToInt maxRows)))+ where+ rowToInt (LibPQ.Row n) =+ fromIntegral n+ intToRow =+ LibPQ.Row . fromIntegral++{-# INLINE single #-}+single :: Row.Row a -> Result a+single rowDec =+ do+ checkExecStatus $ \case+ LibPQ.TuplesOk -> True+ _ -> False+ Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ do+ maxRows <- LibPQ.ntuples result+ case maxRows of+ 1 -> do+ maxCols <- LibPQ.nfields result+ fmap (mapLeft (RowError 0)) $ Row.run rowDec (result, 0, maxCols, integerDatetimes)+ _ -> return (Left (UnexpectedAmountOfRows (rowToInt maxRows)))+ where+ rowToInt (LibPQ.Row n) =+ fromIntegral n+ intToRow =+ LibPQ.Row . fromIntegral++{-# INLINE vector #-}+vector :: Row.Row a -> Result (Vector a)+vector rowDec =+ do+ checkExecStatus $ \case+ LibPQ.TuplesOk -> True+ _ -> False+ Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ do+ maxRows <- LibPQ.ntuples result+ maxCols <- LibPQ.nfields result+ mvector <- MutableVector.unsafeNew (rowToInt maxRows)+ failureRef <- newIORef Nothing+ forMFromZero_ (rowToInt maxRows) $ \rowIndex -> do+ rowResult <- Row.run rowDec (result, intToRow rowIndex, maxCols, integerDatetimes)+ case rowResult of+ Left !x -> writeIORef failureRef (Just (RowError rowIndex x))+ Right !x -> MutableVector.unsafeWrite mvector rowIndex x+ readIORef failureRef >>= \case+ Nothing -> Right <$> Vector.unsafeFreeze mvector+ Just x -> pure (Left x)+ where+ rowToInt (LibPQ.Row n) =+ fromIntegral n+ intToRow =+ LibPQ.Row . fromIntegral++{-# INLINE foldl #-}+foldl :: (a -> b -> a) -> a -> Row.Row b -> Result a+foldl step init rowDec =+ {-# SCC "foldl" #-} + do+ checkExecStatus $ \case+ LibPQ.TuplesOk -> True+ _ -> False+ Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ {-# SCC "traversal" #-} do+ maxRows <- LibPQ.ntuples result+ maxCols <- LibPQ.nfields result+ accRef <- newIORef init+ failureRef <- newIORef Nothing+ forMFromZero_ (rowToInt maxRows) $ \rowIndex -> do+ rowResult <- Row.run rowDec (result, intToRow rowIndex, maxCols, integerDatetimes)+ case rowResult of+ Left !x -> writeIORef failureRef (Just (RowError rowIndex x))+ Right !x -> modifyIORef accRef (\acc -> step acc x)+ readIORef failureRef >>= \case+ Nothing -> Right <$> readIORef accRef+ Just x -> pure (Left x)+ where+ rowToInt (LibPQ.Row n) =+ fromIntegral n+ intToRow =+ LibPQ.Row . fromIntegral++{-# INLINE foldr #-}+foldr :: (b -> a -> a) -> a -> Row.Row b -> Result a+foldr step init rowDec =+ {-# SCC "foldr" #-} + do+ checkExecStatus $ \case+ LibPQ.TuplesOk -> True+ _ -> False+ Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ do+ maxRows <- LibPQ.ntuples result+ maxCols <- LibPQ.nfields result+ accRef <- newIORef init+ failureRef <- newIORef Nothing+ forMToZero_ (rowToInt maxRows) $ \rowIndex -> do+ rowResult <- Row.run rowDec (result, intToRow rowIndex, maxCols, integerDatetimes)+ case rowResult of+ Left !x -> writeIORef failureRef (Just (RowError rowIndex x))+ Right !x -> modifyIORef accRef (\acc -> step x acc)+ readIORef failureRef >>= \case+ Nothing -> Right <$> readIORef accRef+ Just x -> pure (Left x)+ where+ rowToInt (LibPQ.Row n) =+ fromIntegral n+ intToRow =+ LibPQ.Row . fromIntegral
+ library/Hasql/Decoders/Results.hs view
@@ -0,0 +1,90 @@+-- |+-- An API for retrieval of multiple results.+-- Can be used to handle:+-- +-- * A single result,+-- +-- * Individual results of a multi-statement query+-- with the help of "Applicative" and "Monad",+-- +-- * Row-by-row fetching.+-- +module Hasql.Decoders.Results where++import Hasql.Prelude hiding (maybe, many)+import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified Hasql.Prelude as Prelude+import qualified Hasql.Decoders.Result as Result+import qualified Hasql.Decoders.Row as Row+++newtype Results a =+ Results (ReaderT (Bool, LibPQ.Connection) (EitherT Error IO) a)+ deriving (Functor, Applicative, Monad)++data Error =+ -- |+ -- An error on the client-side,+ -- with a message generated by the \"libpq\" library.+ -- Usually indicates problems with the connection.+ ClientError !(Maybe ByteString) |+ ResultError !Result.Error++{-# INLINE run #-}+run :: Results a -> (Bool, LibPQ.Connection) -> IO (Either Error a)+run (Results stack) env =+ runEitherT (runReaderT stack env)++{-# INLINE clientError #-}+clientError :: Results a+clientError =+ Results $ ReaderT $ \(_, connection) -> EitherT $+ fmap (Left . ClientError) (LibPQ.errorMessage connection)++-- |+-- Parse a single result.+{-# INLINE single #-}+single :: Result.Result a -> Results a+single resultDec =+ Results $ ReaderT $ \(integerDatetimes, connection) -> EitherT $ do+ resultMaybe <- LibPQ.getResult connection+ case resultMaybe of+ Just result ->+ mapLeft ResultError <$> Result.run resultDec (integerDatetimes, result) + Nothing ->+ fmap (Left . ClientError) (LibPQ.errorMessage connection)++-- |+-- Fetch a single result.+{-# INLINE getResult #-}+getResult :: Results LibPQ.Result+getResult =+ Results $ ReaderT $ \(_, connection) -> EitherT $ do+ resultMaybe <- LibPQ.getResult connection+ case resultMaybe of+ Just result -> pure (Right result)+ Nothing -> fmap (Left . ClientError) (LibPQ.errorMessage connection)++-- |+-- Fetch a single result.+{-# INLINE getResultMaybe #-}+getResultMaybe :: Results (Maybe LibPQ.Result)+getResultMaybe =+ Results $ ReaderT $ \(_, connection) -> lift $ LibPQ.getResult connection++{-# INLINE dropRemainders #-}+dropRemainders :: Results ()+dropRemainders =+ {-# SCC "dropRemainders" #-} + Results $ ReaderT $ \(integerDatetimes, connection) -> loop integerDatetimes connection+ where+ loop integerDatetimes connection =+ getResultMaybe >>= Prelude.maybe (pure ()) onResult+ where+ getResultMaybe =+ lift $ LibPQ.getResult connection+ onResult result =+ checkErrors *> loop integerDatetimes connection+ where+ checkErrors =+ EitherT $ fmap (mapLeft ResultError) $ Result.run Result.unit (integerDatetimes, result)
+ library/Hasql/Decoders/Row.hs view
@@ -0,0 +1,65 @@+module Hasql.Decoders.Row where++import Hasql.Prelude+import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified PostgreSQL.Binary.Decoder as Decoder+import qualified Hasql.Decoders.Value as Value+++newtype Row a =+ Row (ReaderT Env (EitherT Error IO) a)+ deriving (Functor, Applicative, Monad)++data Env =+ Env !LibPQ.Result !LibPQ.Row !LibPQ.Column !Bool !(IORef LibPQ.Column)++data Error =+ EndOfInput |+ UnexpectedNull |+ ValueError !Text+ deriving (Show)+++-- * Functions+-------------------------++{-# INLINE run #-}+run :: Row a -> (LibPQ.Result, LibPQ.Row, LibPQ.Column, Bool) -> IO (Either Error a)+run (Row impl) (result, row, columnsAmount, integerDatetimes) =+ do+ columnRef <- newIORef 0+ runEitherT (runReaderT impl (Env result row columnsAmount integerDatetimes columnRef))++{-# INLINE error #-}+error :: Error -> Row a+error x =+ Row (ReaderT (const (EitherT (pure (Left x)))))++-- |+-- Next value, decoded using the provided value decoder.+{-# INLINE value #-}+value :: Value.Value a -> Row (Maybe a)+value valueDec =+ {-# SCC "value" #-} + Row $ ReaderT $ \(Env result row columnsAmount integerDatetimes columnRef) -> EitherT $ do+ col <- readIORef columnRef+ writeIORef columnRef (succ col)+ if col < columnsAmount+ then do+ valueMaybe <- {-# SCC "getvalue'" #-} LibPQ.getvalue' result row col+ pure $ + case valueMaybe of+ Nothing ->+ Right Nothing+ Just value ->+ fmap Just $ mapLeft ValueError $+ {-# SCC "decode" #-} Decoder.run (Value.run valueDec integerDatetimes) value+ else pure (Left EndOfInput)++-- |+-- Next value, decoded using the provided value decoder.+{-# INLINE nonNullValue #-}+nonNullValue :: Value.Value a -> Row a+nonNullValue valueDec =+ {-# SCC "nonNullValue" #-} + value valueDec >>= maybe (error UnexpectedNull) pure
+ library/Hasql/Decoders/Value.hs view
@@ -0,0 +1,23 @@+module Hasql.Decoders.Value where++import Hasql.Prelude+import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified PostgreSQL.Binary.Decoder as Decoder+++newtype Value a =+ Value (ReaderT Bool Decoder.Decoder a)+ deriving (Functor)+++{-# INLINE run #-}+run :: Value a -> Bool -> Decoder.Decoder a+run (Value imp) integerDatetimes =+ runReaderT imp integerDatetimes++{-# INLINE decoder #-}+decoder :: (Bool -> Decoder.Decoder a) -> Value a+decoder =+ {-# SCC "decoder" #-} + Value . ReaderT+
− library/Hasql/Decoding.hs
@@ -1,669 +0,0 @@--- |--- A DSL for declaration of result decoders.-module Hasql.Decoding-(- -- * Result- Result,- unit,- rowsAffected,- singleRow,- -- ** Specialized multi-row results- maybeRow,- rowsVector,- rowsList,- -- ** Multi-row traversers- foldlRows,- foldrRows,- -- * Row- Row,- value,- nullableValue,- -- * Value- Value,- bool,- int2,- int4,- int8,- float4,- float8,- numeric,- char,- text,- bytea,- date,- timestamp,- timestamptz,- time,- timetz,- interval,- uuid,- json,- array,- composite,- hstore,- enum,- -- * Array- Array,- arrayDimension,- arrayValue,- arrayNullableValue,- -- * Composite- Composite,- compositeValue,- compositeNullableValue,-)-where--import Hasql.Prelude hiding (maybe, bool)-import qualified Data.Aeson as Aeson-import qualified Data.Vector as Vector-import qualified PostgreSQL.Binary.Decoder as Decoder-import qualified Hasql.Decoding.Results as Results-import qualified Hasql.Decoding.Result as Result-import qualified Hasql.Decoding.Row as Row-import qualified Hasql.Decoding.Value as Value-import qualified Hasql.Decoding.Array as Array-import qualified Hasql.Decoding.Composite as Composite-import qualified Hasql.Prelude as Prelude----- * Result------------------------------ |--- Decoder of a query result.--- -newtype Result a =- Result (Results.Results a)- deriving (Functor)---- |--- Decode no value from the result.--- --- Useful for statements like @INSERT@ or @CREATE@.--- -{-# INLINABLE unit #-}-unit :: Result ()-unit =- Result (Results.single Result.unit)---- |--- Get the amount of rows affected by such statements as--- @UPDATE@ or @DELETE@.--- -{-# INLINABLE rowsAffected #-}-rowsAffected :: Result Int64-rowsAffected =- Result (Results.single Result.rowsAffected)---- |--- Exactly one row.--- Will raise the 'Hasql.Query.UnexpectedAmountOfRows' error if it's any other.--- -{-# INLINABLE singleRow #-}-singleRow :: Row a -> Result a-singleRow (Row row) =- Result (Results.single (Result.single row))---- ** Multi-row traversers------------------------------ |--- Foldl multiple rows.--- -{-# INLINABLE foldlRows #-}-foldlRows :: (a -> b -> a) -> a -> Row b -> Result a-foldlRows step init (Row row) =- Result (Results.single (Result.foldl step init row))---- |--- Foldr multiple rows.--- -{-# INLINABLE foldrRows #-}-foldrRows :: (b -> a -> a) -> a -> Row b -> Result a-foldrRows step init (Row row) =- Result (Results.single (Result.foldr step init row))---- ** Specialized multi-row results------------------------------ |--- Maybe one row or none.--- -{-# INLINABLE maybeRow #-}-maybeRow :: Row a -> Result (Maybe a)-maybeRow (Row row) =- Result (Results.single (Result.maybe row))---- |--- Zero or more rows packed into the vector.--- --- It's recommended to prefer this function to 'rowsList',--- since it performs notably better.--- -{-# INLINABLE rowsVector #-}-rowsVector :: Row a -> Result (Vector a)-rowsVector (Row row) =- Result (Results.single (Result.vector row))---- |--- Zero or more rows packed into the list.--- -{-# INLINABLE rowsList #-}-rowsList :: Row a -> Result [a]-rowsList =- foldrRows strictCons []----- ** Instances------------------------------ | Maps to 'unit'.-instance Default (Result ()) where- {-# INLINE def #-}- def =- unit---- | Maps to 'rowsAffected'.-instance Default (Result Int64) where- {-# INLINE def #-}- def =- rowsAffected---- | Maps to @('maybeRow' def)@.-instance Default (Row a) => Default (Result (Maybe a)) where- {-# INLINE def #-}- def =- maybeRow def---- | Maps to @('rowsVector' def)@.-instance Default (Row a) => Default (Result (Vector a)) where- {-# INLINE def #-}- def =- rowsVector def---- | Maps to @('rowsList' def)@.-instance Default (Row a) => Default (Result ([] a)) where- {-# INLINE def #-}- def =- rowsList def---- | Maps to @(fmap Identity ('singleRow' def)@.-instance Default (Row a) => Default (Result (Identity a)) where- {-# INLINE def #-}- def =- fmap Identity (singleRow def)----- * Row------------------------------ |--- Decoder of an individual row,--- which gets composed of column value decoders.--- --- E.g.:--- --- >x :: Row (Maybe Int64, Text, TimeOfDay)--- >x =--- > (,,) <$> nullableValue int8 <*> value text <*> value time--- -newtype Row a =- Row (Row.Row a)- deriving (Functor, Applicative, Monad)---- |--- Lift an individual non-nullable value decoder to a composable row decoder.--- -{-# INLINABLE value #-}-value :: Value a -> Row a-value (Value imp) =- Row (Row.nonNullValue imp)---- |--- Lift an individual nullable value decoder to a composable row decoder.--- -{-# INLINABLE nullableValue #-}-nullableValue :: Value a -> Row (Maybe a)-nullableValue (Value imp) =- Row (Row.value imp)----- ** Instances----------------------------instance Default (Value a) => Default (Row (Identity a)) where- {-# INLINE def #-}- def =- fmap Identity (value def)--instance Default (Value a) => Default (Row (Maybe a)) where- {-# INLINE def #-}- def =- nullableValue def--instance (Default (Value a1), Default (Value a2)) => Default (Row (a1, a2)) where- {-# INLINE def #-}- def =- ap (fmap (,) (value def)) (value def)----- * Value------------------------------ |--- Decoder of an individual value.--- -newtype Value a =- Value (Value.Value a)- deriving (Functor)----- ** Plain value decoders------------------------------ |--- Decoder of the @BOOL@ values.--- -{-# INLINABLE bool #-}-bool :: Value Bool-bool =- Value (Value.decoder (const Decoder.bool))---- |--- Decoder of the @INT2@ values.--- -{-# INLINABLE int2 #-}-int2 :: Value Int16-int2 =- Value (Value.decoder (const Decoder.int))---- |--- Decoder of the @INT4@ values.--- -{-# INLINABLE int4 #-}-int4 :: Value Int32-int4 =- Value (Value.decoder (const Decoder.int))---- |--- Decoder of the @INT8@ values.--- -{-# INLINABLE int8 #-}-int8 :: Value Int64-int8 =- {-# SCC "int8" #-} - Value (Value.decoder (const ({-# SCC "int8.int" #-} Decoder.int)))---- |--- Decoder of the @FLOAT4@ values.--- -{-# INLINABLE float4 #-}-float4 :: Value Float-float4 =- Value (Value.decoder (const Decoder.float4))---- |--- Decoder of the @FLOAT8@ values.--- -{-# INLINABLE float8 #-}-float8 :: Value Double-float8 =- Value (Value.decoder (const Decoder.float8))---- |--- Decoder of the @NUMERIC@ values.--- -{-# INLINABLE numeric #-}-numeric :: Value Scientific-numeric =- Value (Value.decoder (const Decoder.numeric))---- |--- Decoder of the @CHAR@ values.--- Note that it supports UTF-8 values.-{-# INLINABLE char #-}-char :: Value Char-char =- Value (Value.decoder (const Decoder.char))---- |--- Decoder of the @TEXT@ values.--- -{-# INLINABLE text #-}-text :: Value Text-text =- Value (Value.decoder (const Decoder.text_strict))---- |--- Decoder of the @BYTEA@ values.--- -{-# INLINABLE bytea #-}-bytea :: Value ByteString-bytea =- Value (Value.decoder (const Decoder.bytea_strict))---- |--- Decoder of the @DATE@ values.--- -{-# INLINABLE date #-}-date :: Value Day-date =- Value (Value.decoder (const Decoder.date))---- |--- Decoder of the @TIMESTAMP@ values.--- -{-# INLINABLE timestamp #-}-timestamp :: Value LocalTime-timestamp =- Value (Value.decoder (Prelude.bool Decoder.timestamp_float Decoder.timestamp_int))---- |--- Decoder of the @TIMESTAMPTZ@ values.--- --- /NOTICE/--- --- Postgres does not store the timezone information of @TIMESTAMPTZ@.--- Instead it stores a UTC value and performs silent conversions--- to the currently set timezone, when dealt with in the text format.--- However this library bypasses the silent conversions--- and communicates with Postgres using the UTC values directly.-{-# INLINABLE timestamptz #-}-timestamptz :: Value UTCTime-timestamptz =- Value (Value.decoder (Prelude.bool Decoder.timestamptz_float Decoder.timestamptz_int))---- |--- Decoder of the @TIME@ values.--- -{-# INLINABLE time #-}-time :: Value TimeOfDay-time =- Value (Value.decoder (Prelude.bool Decoder.time_float Decoder.time_int))---- |--- Decoder of the @TIMETZ@ values.--- --- Unlike in case of @TIMESTAMPTZ@, --- Postgres does store the timezone information for @TIMETZ@.--- However the Haskell's \"time\" library does not contain any composite type,--- that fits the task, so we use a pair of 'TimeOfDay' and 'TimeZone'--- to represent a value on the Haskell's side.-{-# INLINABLE timetz #-}-timetz :: Value (TimeOfDay, TimeZone)-timetz =- Value (Value.decoder (Prelude.bool Decoder.timetz_float Decoder.timetz_int))---- |--- Decoder of the @INTERVAL@ values.--- -{-# INLINABLE interval #-}-interval :: Value DiffTime-interval =- Value (Value.decoder (Prelude.bool Decoder.interval_float Decoder.interval_int))---- |--- Decoder of the @UUID@ values.--- -{-# INLINABLE uuid #-}-uuid :: Value UUID-uuid =- Value (Value.decoder (const Decoder.uuid))---- |--- Decoder of the @JSON@ values.--- -{-# INLINABLE json #-}-json :: Value Aeson.Value-json =- Value (Value.decoder (const Decoder.json))----- ** Composite value decoders------------------------------ |--- Lifts the 'Array' decoder to the 'Value' decoder.--- -{-# INLINABLE array #-}-array :: Array a -> Value a-array (Array imp) =- Value (Value.decoder (Array.run imp))---- |--- Lifts the 'Composite' decoder to the 'Value' decoder.--- -{-# INLINABLE composite #-}-composite :: Composite a -> Value a-composite (Composite imp) =- Value (Value.decoder (Composite.run imp))---- |--- A generic decoder of @HSTORE@ values.--- --- Here's how you can use it to construct a specific value:--- --- @--- x :: Value [(Text, Maybe Text)]--- x =--- hstore 'replicateM'--- @--- -{-# INLINABLE hstore #-}-hstore :: (forall m. Monad m => Int -> m (Text, Maybe Text) -> m a) -> Value a-hstore replicateM =- Value (Value.decoder (const (Decoder.hstore replicateM Decoder.text_strict Decoder.text_strict)))---- |--- Given a partial mapping from text to value,--- produces a decoder of that value.-enum :: (Text -> Maybe a) -> Value a-enum mapping =- Value (Value.decoder (const (Decoder.enum mapping)))----- ** Instances------------------------------ |--- Maps to 'bool'.-instance Default (Value Bool) where- {-# INLINE def #-}- def =- bool---- |--- Maps to 'int2'.-instance Default (Value Int16) where- {-# INLINE def #-}- def =- int2---- |--- Maps to 'int4'.-instance Default (Value Int32) where- {-# INLINE def #-}- def =- int4---- |--- Maps to 'int8'.-instance Default (Value Int64) where- {-# INLINE def #-}- def =- int8---- |--- Maps to 'float4'.-instance Default (Value Float) where- {-# INLINE def #-}- def =- float4---- |--- Maps to 'float8'.-instance Default (Value Double) where- {-# INLINE def #-}- def =- float8---- |--- Maps to 'numeric'.-instance Default (Value Scientific) where- {-# INLINE def #-}- def =- numeric---- |--- Maps to 'char'.-instance Default (Value Char) where- {-# INLINE def #-}- def =- char---- |--- Maps to 'text'.-instance Default (Value Text) where- {-# INLINE def #-}- def =- text---- |--- Maps to 'bytea'.-instance Default (Value ByteString) where- {-# INLINE def #-}- def =- bytea---- |--- Maps to 'date'.-instance Default (Value Day) where- {-# INLINE def #-}- def =- date---- |--- Maps to 'timestamp'.-instance Default (Value LocalTime) where- {-# INLINE def #-}- def =- timestamp---- |--- Maps to 'timestamptz'.-instance Default (Value UTCTime) where- {-# INLINE def #-}- def =- timestamptz---- |--- Maps to 'time'.-instance Default (Value TimeOfDay) where- {-# INLINE def #-}- def =- time---- |--- Maps to 'timetz'.-instance Default (Value (TimeOfDay, TimeZone)) where- {-# INLINE def #-}- def =- timetz---- |--- Maps to 'interval'.-instance Default (Value DiffTime) where- {-# INLINE def #-}- def =- interval---- |--- Maps to 'uuid'.-instance Default (Value UUID) where- {-# INLINE def #-}- def =- uuid---- |--- Maps to 'json'.-instance Default (Value Aeson.Value) where- {-# INLINE def #-}- def =- json----- * Array decoders------------------------------ |--- A generic array decoder.--- --- Here's how you can use it to produce a specific array value decoder:--- --- @--- x :: Value [[Text]]--- x =--- array (arrayDimension 'replicateM' (arrayDimension 'replicateM' (arrayValue text)))--- @--- -newtype Array a =- Array (Array.Array a)- deriving (Functor)---- |--- A function for parsing a dimension of an array.--- Provides support for multi-dimensional arrays.--- --- Accepts:--- --- * An implementation of the @replicateM@ function--- (@Control.Monad.'Control.Monad.replicateM'@, @Data.Vector.'Data.Vector.replicateM'@),--- which determines the output value.--- --- * A decoder of its components, which can be either another 'arrayDimension',--- 'arrayValue' or 'arrayNullableValue'.--- -{-# INLINABLE arrayDimension #-}-arrayDimension :: (forall m. Monad m => Int -> m a -> m b) -> Array a -> Array b-arrayDimension replicateM (Array imp) =- Array (Array.dimension replicateM imp)---- |--- Lift a 'Value' decoder into an 'Array' decoder for parsing of non-nullable leaf values.-{-# INLINABLE arrayValue #-}-arrayValue :: Value a -> Array a-arrayValue (Value imp) =- Array (Array.nonNullValue (Value.run imp))---- |--- Lift a 'Value' decoder into an 'Array' decoder for parsing of nullable leaf values.-{-# INLINABLE arrayNullableValue #-}-arrayNullableValue :: Value a -> Array (Maybe a)-arrayNullableValue (Value imp) =- Array (Array.value (Value.run imp))----- * Composite decoders------------------------------ |--- Composable decoder of composite values (rows, records).-newtype Composite a =- Composite (Composite.Composite a)- deriving (Functor, Applicative, Monad)---- |--- Lift a 'Value' decoder into a 'Composite' decoder for parsing of non-nullable leaf values.-{-# INLINABLE compositeValue #-}-compositeValue :: Value a -> Composite a-compositeValue (Value imp) =- Composite (Composite.nonNullValue (Value.run imp))---- |--- Lift a 'Value' decoder into a 'Composite' decoder for parsing of nullable leaf values.-{-# INLINABLE compositeNullableValue #-}-compositeNullableValue :: Value a -> Composite (Maybe a)-compositeNullableValue (Value imp) =- Composite (Composite.value (Value.run imp))-
− library/Hasql/Decoding/Array.hs
@@ -1,31 +0,0 @@-module Hasql.Decoding.Array where--import Hasql.Prelude-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified PostgreSQL.Binary.Decoder as Decoder---newtype Array a =- Array (ReaderT Bool Decoder.ArrayDecoder a)- deriving (Functor)--{-# INLINE run #-}-run :: Array a -> Bool -> Decoder.Decoder a-run (Array imp) env =- Decoder.array (runReaderT imp env)--{-# INLINE dimension #-}-dimension :: (forall m. Monad m => Int -> m a -> m b) -> Array a -> Array b-dimension replicateM (Array imp) =- Array $ ReaderT $ \env -> Decoder.arrayDimension replicateM (runReaderT imp env)--{-# INLINE value #-}-value :: (Bool -> Decoder.Decoder a) -> Array (Maybe a)-value decoder' =- Array $ ReaderT $ Decoder.arrayValue . decoder'--{-# INLINE nonNullValue #-}-nonNullValue :: (Bool -> Decoder.Decoder a) -> Array a-nonNullValue decoder' =- Array $ ReaderT $ Decoder.arrayNonNullValue . decoder'-
− library/Hasql/Decoding/Composite.hs
@@ -1,26 +0,0 @@-module Hasql.Decoding.Composite where--import Hasql.Prelude-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified PostgreSQL.Binary.Decoder as Decoder---newtype Composite a =- Composite (ReaderT Bool Decoder.CompositeDecoder a)- deriving (Functor, Applicative, Monad)--{-# INLINE run #-}-run :: Composite a -> Bool -> Decoder.Decoder a-run (Composite imp) env =- Decoder.composite (runReaderT imp env)--{-# INLINE value #-}-value :: (Bool -> Decoder.Decoder a) -> Composite (Maybe a)-value decoder' =- Composite $ ReaderT $ Decoder.compositeValue . decoder'--{-# INLINE nonNullValue #-}-nonNullValue :: (Bool -> Decoder.Decoder a) -> Composite a-nonNullValue decoder' =- Composite $ ReaderT $ Decoder.compositeNonNullValue . decoder'-
− library/Hasql/Decoding/Result.hs
@@ -1,232 +0,0 @@-module Hasql.Decoding.Result where--import Hasql.Prelude hiding (maybe, many)-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified Hasql.Decoding.Row as Row-import qualified Data.Attoparsec.ByteString.Char8 as Attoparsec-import qualified Data.ByteString as ByteString-import qualified Hasql.Prelude as Prelude-import qualified Data.Vector as Vector-import qualified Data.Vector.Mutable as MutableVector---newtype Result a =- Result (ReaderT (Bool, LibPQ.Result) (EitherT Error IO) a)- deriving (Functor, Applicative, Monad)--data Error =- -- | - -- An error reported by the DB. Code, message, details, hint.- -- - -- * The SQLSTATE code for the error. The SQLSTATE code identifies the type of error that has occurred; - -- it can be used by front-end applications to perform specific operations (such as error handling) - -- in response to a particular database error. - -- For a list of the possible SQLSTATE codes, see Appendix A.- -- This field is not localizable, and is always present.- -- - -- * The primary human-readable error message (typically one line). Always present.- -- - -- * Detail: an optional secondary error message carrying more detail about the problem. - -- Might run to multiple lines.- -- - -- * Hint: an optional suggestion what to do about the problem. - -- This is intended to differ from detail in that it offers advice (potentially inappropriate) - -- rather than hard facts. Might run to multiple lines.- ServerError !ByteString !ByteString !(Maybe ByteString) !(Maybe ByteString) |- -- |- -- The database returned an unexpected result.- -- Indicates an improper statement or a schema mismatch.- UnexpectedResult !Text |- -- |- -- An error of the row reader, preceded by the index of the row.- RowError !Int !Row.Error |- -- |- -- An unexpected amount of rows.- UnexpectedAmountOfRows !Int- deriving (Show)--{-# INLINE run #-}-run :: Result a -> (Bool, LibPQ.Result) -> IO (Either Error a)-run (Result reader) env =- runEitherT (runReaderT reader env)--{-# INLINE unit #-}-unit :: Result ()-unit =- checkExecStatus $ \case- LibPQ.CommandOk -> True- LibPQ.TuplesOk -> True- _ -> False--{-# INLINE rowsAffected #-}-rowsAffected :: Result Int64-rowsAffected =- do- checkExecStatus $ \case- LibPQ.CommandOk -> True- _ -> False- Result $ ReaderT $ \(_, result) -> EitherT $- LibPQ.cmdTuples result & fmap cmdTuplesReader- where- cmdTuplesReader =- notNothing >=> notEmpty >=> decimal- where- notNothing =- Prelude.maybe (Left (UnexpectedResult "No bytes")) Right- notEmpty bytes =- if ByteString.null bytes- then Left (UnexpectedResult "Empty bytes")- else Right bytes- decimal bytes =- mapLeft (\m -> UnexpectedResult ("Decimal parsing failure: " <> fromString m)) $- Attoparsec.parseOnly (Attoparsec.decimal <* Attoparsec.endOfInput) bytes--{-# INLINE checkExecStatus #-}-checkExecStatus :: (LibPQ.ExecStatus -> Bool) -> Result ()-checkExecStatus predicate =- {-# SCC "checkExecStatus" #-} - do- status <- Result $ ReaderT $ \(_, result) -> lift $ LibPQ.resultStatus result- unless (predicate status) $ do- case status of- LibPQ.BadResponse -> serverError- LibPQ.NonfatalError -> serverError- LibPQ.FatalError -> serverError- _ -> Result $ lift $ EitherT $ pure $ Left $ UnexpectedResult $ "Unexpected result status: " <> (fromString $ show status)--{-# INLINE serverError #-}-serverError :: Result ()-serverError =- Result $ ReaderT $ \(_, result) -> EitherT $ do- code <- - fmap (fromMaybe ($bug "No code")) $- LibPQ.resultErrorField result LibPQ.DiagSqlstate- message <- - fmap (fromMaybe ($bug "No message")) $- LibPQ.resultErrorField result LibPQ.DiagMessagePrimary- detail <- - LibPQ.resultErrorField result LibPQ.DiagMessageDetail- hint <- - LibPQ.resultErrorField result LibPQ.DiagMessageHint- pure $ Left $ ServerError code message detail hint--{-# INLINE maybe #-}-maybe :: Row.Row a -> Result (Maybe a)-maybe rowDec =- do- checkExecStatus $ \case- LibPQ.TuplesOk -> True- _ -> False- Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ do- maxRows <- LibPQ.ntuples result- case maxRows of- 0 -> return (Right Nothing)- 1 -> do- maxCols <- LibPQ.nfields result- fmap (fmap Just . mapLeft (RowError 0)) $ Row.run rowDec (result, 0, maxCols, integerDatetimes)- _ -> return (Left (UnexpectedAmountOfRows (rowToInt maxRows)))- where- rowToInt (LibPQ.Row n) =- fromIntegral n- intToRow =- LibPQ.Row . fromIntegral--{-# INLINE single #-}-single :: Row.Row a -> Result a-single rowDec =- do- checkExecStatus $ \case- LibPQ.TuplesOk -> True- _ -> False- Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ do- maxRows <- LibPQ.ntuples result- case maxRows of- 1 -> do- maxCols <- LibPQ.nfields result- fmap (mapLeft (RowError 0)) $ Row.run rowDec (result, 0, maxCols, integerDatetimes)- _ -> return (Left (UnexpectedAmountOfRows (rowToInt maxRows)))- where- rowToInt (LibPQ.Row n) =- fromIntegral n- intToRow =- LibPQ.Row . fromIntegral--{-# INLINE vector #-}-vector :: Row.Row a -> Result (Vector a)-vector rowDec =- do- checkExecStatus $ \case- LibPQ.TuplesOk -> True- _ -> False- Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ do- maxRows <- LibPQ.ntuples result- maxCols <- LibPQ.nfields result- mvector <- MutableVector.unsafeNew (rowToInt maxRows)- failureRef <- newIORef Nothing- forMFromZero_ (rowToInt maxRows) $ \rowIndex -> do- rowResult <- Row.run rowDec (result, intToRow rowIndex, maxCols, integerDatetimes)- case rowResult of- Left !x -> writeIORef failureRef (Just (RowError rowIndex x))- Right !x -> MutableVector.unsafeWrite mvector rowIndex x- readIORef failureRef >>= \case- Nothing -> Right <$> Vector.unsafeFreeze mvector- Just x -> pure (Left x)- where- rowToInt (LibPQ.Row n) =- fromIntegral n- intToRow =- LibPQ.Row . fromIntegral--{-# INLINE foldl #-}-foldl :: (a -> b -> a) -> a -> Row.Row b -> Result a-foldl step init rowDec =- {-# SCC "foldl" #-} - do- checkExecStatus $ \case- LibPQ.TuplesOk -> True- _ -> False- Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ {-# SCC "traversal" #-} do- maxRows <- LibPQ.ntuples result- maxCols <- LibPQ.nfields result- accRef <- newIORef init- failureRef <- newIORef Nothing- forMFromZero_ (rowToInt maxRows) $ \rowIndex -> do- rowResult <- Row.run rowDec (result, intToRow rowIndex, maxCols, integerDatetimes)- case rowResult of- Left !x -> writeIORef failureRef (Just (RowError rowIndex x))- Right !x -> modifyIORef accRef (\acc -> step acc x)- readIORef failureRef >>= \case- Nothing -> Right <$> readIORef accRef- Just x -> pure (Left x)- where- rowToInt (LibPQ.Row n) =- fromIntegral n- intToRow =- LibPQ.Row . fromIntegral--{-# INLINE foldr #-}-foldr :: (b -> a -> a) -> a -> Row.Row b -> Result a-foldr step init rowDec =- {-# SCC "foldr" #-} - do- checkExecStatus $ \case- LibPQ.TuplesOk -> True- _ -> False- Result $ ReaderT $ \(integerDatetimes, result) -> EitherT $ do- maxRows <- LibPQ.ntuples result- maxCols <- LibPQ.nfields result- accRef <- newIORef init- failureRef <- newIORef Nothing- forMToZero_ (rowToInt maxRows) $ \rowIndex -> do- rowResult <- Row.run rowDec (result, intToRow rowIndex, maxCols, integerDatetimes)- case rowResult of- Left !x -> writeIORef failureRef (Just (RowError rowIndex x))- Right !x -> modifyIORef accRef (\acc -> step x acc)- readIORef failureRef >>= \case- Nothing -> Right <$> readIORef accRef- Just x -> pure (Left x)- where- rowToInt (LibPQ.Row n) =- fromIntegral n- intToRow =- LibPQ.Row . fromIntegral
− library/Hasql/Decoding/Results.hs
@@ -1,90 +0,0 @@--- |--- An API for retrieval of multiple results.--- Can be used to handle:--- --- * A single result,--- --- * Individual results of a multi-statement query--- with the help of "Applicative" and "Monad",--- --- * Row-by-row fetching.--- -module Hasql.Decoding.Results where--import Hasql.Prelude hiding (maybe, many)-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified Hasql.Prelude as Prelude-import qualified Hasql.Decoding.Result as Result-import qualified Hasql.Decoding.Row as Row---newtype Results a =- Results (ReaderT (Bool, LibPQ.Connection) (EitherT Error IO) a)- deriving (Functor, Applicative, Monad)--data Error =- -- |- -- An error on the client-side,- -- with a message generated by the \"libpq\" library.- -- Usually indicates problems with the connection.- ClientError !(Maybe ByteString) |- ResultError !Result.Error--{-# INLINE run #-}-run :: Results a -> (Bool, LibPQ.Connection) -> IO (Either Error a)-run (Results stack) env =- runEitherT (runReaderT stack env)--{-# INLINE clientError #-}-clientError :: Results a-clientError =- Results $ ReaderT $ \(_, connection) -> EitherT $- fmap (Left . ClientError) (LibPQ.errorMessage connection)---- |--- Parse a single result.-{-# INLINE single #-}-single :: Result.Result a -> Results a-single resultDec =- Results $ ReaderT $ \(integerDatetimes, connection) -> EitherT $ do- resultMaybe <- LibPQ.getResult connection- case resultMaybe of- Just result ->- mapLeft ResultError <$> Result.run resultDec (integerDatetimes, result) - Nothing ->- fmap (Left . ClientError) (LibPQ.errorMessage connection)---- |--- Fetch a single result.-{-# INLINE getResult #-}-getResult :: Results LibPQ.Result-getResult =- Results $ ReaderT $ \(_, connection) -> EitherT $ do- resultMaybe <- LibPQ.getResult connection- case resultMaybe of- Just result -> pure (Right result)- Nothing -> fmap (Left . ClientError) (LibPQ.errorMessage connection)---- |--- Fetch a single result.-{-# INLINE getResultMaybe #-}-getResultMaybe :: Results (Maybe LibPQ.Result)-getResultMaybe =- Results $ ReaderT $ \(_, connection) -> lift $ LibPQ.getResult connection--{-# INLINE dropRemainders #-}-dropRemainders :: Results ()-dropRemainders =- {-# SCC "dropRemainders" #-} - Results $ ReaderT $ \(integerDatetimes, connection) -> loop integerDatetimes connection- where- loop integerDatetimes connection =- getResultMaybe >>= Prelude.maybe (pure ()) onResult- where- getResultMaybe =- lift $ LibPQ.getResult connection- onResult result =- checkErrors *> loop integerDatetimes connection- where- checkErrors =- EitherT $ fmap (mapLeft ResultError) $ Result.run Result.unit (integerDatetimes, result)
− library/Hasql/Decoding/Row.hs
@@ -1,65 +0,0 @@-module Hasql.Decoding.Row where--import Hasql.Prelude-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified PostgreSQL.Binary.Decoder as Decoder-import qualified Hasql.Decoding.Value as Value---newtype Row a =- Row (ReaderT Env (EitherT Error IO) a)- deriving (Functor, Applicative, Monad)--data Env =- Env !LibPQ.Result !LibPQ.Row !LibPQ.Column !Bool !(IORef LibPQ.Column)--data Error =- EndOfInput |- UnexpectedNull |- ValueError !Text- deriving (Show)----- * Functions----------------------------{-# INLINE run #-}-run :: Row a -> (LibPQ.Result, LibPQ.Row, LibPQ.Column, Bool) -> IO (Either Error a)-run (Row impl) (result, row, columnsAmount, integerDatetimes) =- do- columnRef <- newIORef 0- runEitherT (runReaderT impl (Env result row columnsAmount integerDatetimes columnRef))--{-# INLINE error #-}-error :: Error -> Row a-error x =- Row (ReaderT (const (EitherT (pure (Left x)))))---- |--- Next value, decoded using the provided value decoder.-{-# INLINE value #-}-value :: Value.Value a -> Row (Maybe a)-value valueDec =- {-# SCC "value" #-} - Row $ ReaderT $ \(Env result row columnsAmount integerDatetimes columnRef) -> EitherT $ do- col <- readIORef columnRef- writeIORef columnRef (succ col)- if col < columnsAmount- then do- valueMaybe <- {-# SCC "getvalue'" #-} LibPQ.getvalue' result row col- pure $ - case valueMaybe of- Nothing ->- Right Nothing- Just value ->- fmap Just $ mapLeft ValueError $- {-# SCC "decode" #-} Decoder.run (Value.run valueDec integerDatetimes) value- else pure (Left EndOfInput)---- |--- Next value, decoded using the provided value decoder.-{-# INLINE nonNullValue #-}-nonNullValue :: Value.Value a -> Row a-nonNullValue valueDec =- {-# SCC "nonNullValue" #-} - value valueDec >>= maybe (error UnexpectedNull) pure
− library/Hasql/Decoding/Value.hs
@@ -1,23 +0,0 @@-module Hasql.Decoding.Value where--import Hasql.Prelude-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified PostgreSQL.Binary.Decoder as Decoder---newtype Value a =- Value (ReaderT Bool Decoder.Decoder a)- deriving (Functor)---{-# INLINE run #-}-run :: Value a -> Bool -> Decoder.Decoder a-run (Value imp) integerDatetimes =- runReaderT imp integerDatetimes--{-# INLINE decoder #-}-decoder :: (Bool -> Decoder.Decoder a) -> Value a-decoder =- {-# SCC "decoder" #-} - Value . ReaderT-
+ library/Hasql/Encoders.hs view
@@ -0,0 +1,483 @@+-- |+-- A DSL for declaration of query parameter encoders.+module Hasql.Encoders+(+ -- * Params+ Params,+ unit,+ value,+ nullableValue,+ -- * Value+ Value,+ bool,+ int2,+ int4,+ int8,+ float4,+ float8,+ numeric,+ char,+ text,+ bytea,+ date,+ timestamp,+ timestamptz,+ time,+ timetz,+ interval,+ uuid,+ json,+ array,+ enum,+ -- * Array+ Array,+ arrayValue,+ arrayNullableValue,+ arrayDimension,+)+where++import Hasql.Prelude hiding (bool)+import qualified PostgreSQL.Binary.Encoder as Encoder+import qualified Data.Aeson as Aeson+import qualified Hasql.Encoders.Params as Params+import qualified Hasql.Encoders.Value as Value+import qualified Hasql.Encoders.Array as Array+import qualified Hasql.PTI as PTI+import qualified Hasql.Prelude as Prelude+++-- * Parameters Product Encoder+-------------------------++-- |+-- Encoder of some representation of the parameters product.+-- +-- Has instances of 'Contravariant', 'Divisible' and 'Monoid',+-- which you can use to compose multiple parameters together.+-- E.g.,+-- +-- @+-- someParamsEncoder :: 'Params' (Int64, Maybe Text)+-- someParamsEncoder =+-- 'contramap' 'fst' ('value' 'int8') '<>'+-- 'contramap' 'snd' ('nullableValue' 'text')+-- @+-- +-- As a general solution for tuples of any arity, instead of 'fst' and 'snd',+-- consider the functions of the @contrazip@ family+-- from the \"contravariant-extras\" package.+-- E.g., here's how you can achieve the same as the above:+-- +-- @+-- someParamsEncoder :: 'Params' (Int64, Maybe Text)+-- someParamsEncoder =+-- 'contrazip2' ('value' 'int8') ('nullableValue' 'text')+-- @+-- +-- Here's how you can implement encoders for custom composite types:+-- +-- @+-- data Person =+-- Person { name :: Text, gender :: Gender, age :: Int }+-- +-- data Gender =+-- Male | Female+-- +-- personParams :: 'Params' Person+-- personParams =+-- 'contramap' name ('value' 'text') '<>'+-- 'contramap' gender ('value' genderValue) '<>'+-- 'contramap' (fromIntegral . age) ('value' 'int8')+-- +-- genderValue :: 'Value' Gender+-- genderValue =+-- 'contramap' genderText 'text'+-- where+-- genderText gender =+-- case gender of+-- Male -> "male"+-- Female -> "female"+-- @+-- +newtype Params a =+ Params (Params.Params a)+ deriving (Contravariant, Divisible, Monoid)++-- |+-- Encode no parameters.+-- +{-# INLINABLE unit #-}+unit :: Params ()+unit =+ Params mempty++-- |+-- Lift an individual value encoder to a parameters encoder.+-- +{-# INLINABLE value #-}+value :: Value a -> Params a+value (Value x) =+ Params (Params.value x)++-- |+-- Lift an individual nullable value encoder to a parameters encoder.+-- +{-# INLINABLE nullableValue #-}+nullableValue :: Value a -> Params (Maybe a)+nullableValue (Value x) =+ Params (Params.nullableValue x)+++-- ** Instances+-------------------------++-- |+-- Maps to 'unit'.+instance Default (Params ()) where+ {-# INLINE def #-}+ def =+ unit++instance Default (Value a) => Default (Params (Identity a)) where+ {-# INLINE def #-}+ def =+ contramap runIdentity (value def)++instance (Default (Value a1), Default (Value a2)) => Default (Params (a1, a2)) where+ {-# INLINE def #-}+ def =+ contrazip2 (value def) (value def)++instance (Default (Value a1), Default (Value a2), Default (Value a3)) => Default (Params (a1, a2, a3)) where+ {-# INLINE def #-}+ def =+ contrazip3 (value def) (value def) (value def)++instance (Default (Value a1), Default (Value a2), Default (Value a3), Default (Value a4)) => Default (Params (a1, a2, a3, a4)) where+ {-# INLINE def #-}+ def =+ contrazip4 (value def) (value def) (value def) (value def)++instance (Default (Value a1), Default (Value a2), Default (Value a3), Default (Value a4), Default (Value a5)) => Default (Params (a1, a2, a3, a4, a5)) where+ {-# INLINE def #-}+ def =+ contrazip5 (value def) (value def) (value def) (value def) (value def)+++-- * Value Encoder+-------------------------++-- |+-- An individual value encoder.+-- Will be mapped to a single placeholder in the query.+-- +newtype Value a =+ Value (Value.Value a)+ deriving (Contravariant)++-- |+-- Encoder of @BOOL@ values.+{-# INLINABLE bool #-}+bool :: Value Bool+bool =+ Value (Value.unsafePTI PTI.bool (const Encoder.bool))++-- |+-- Encoder of @INT2@ values.+{-# INLINABLE int2 #-}+int2 :: Value Int16+int2 =+ Value (Value.unsafePTI PTI.int2 (const Encoder.int2_int16))++-- |+-- Encoder of @INT4@ values.+{-# INLINABLE int4 #-}+int4 :: Value Int32+int4 =+ Value (Value.unsafePTI PTI.int4 (const Encoder.int4_int32))++-- |+-- Encoder of @INT8@ values.+{-# INLINABLE int8 #-}+int8 :: Value Int64+int8 =+ Value (Value.unsafePTI PTI.int8 (const Encoder.int8_int64))++-- |+-- Encoder of @FLOAT4@ values.+{-# INLINABLE float4 #-}+float4 :: Value Float+float4 =+ Value (Value.unsafePTI PTI.float4 (const Encoder.float4))++-- |+-- Encoder of @FLOAT8@ values.+{-# INLINABLE float8 #-}+float8 :: Value Double+float8 =+ Value (Value.unsafePTI PTI.float8 (const Encoder.float8))++-- |+-- Encoder of @NUMERIC@ values.+{-# INLINABLE numeric #-}+numeric :: Value Scientific+numeric =+ Value (Value.unsafePTI PTI.numeric (const Encoder.numeric))++-- |+-- Encoder of @CHAR@ values.+-- Note that it supports UTF-8 values and+-- identifies itself under the @TEXT@ OID because of that.+{-# INLINABLE char #-}+char :: Value Char+char =+ Value (Value.unsafePTI PTI.text (const Encoder.char))++-- |+-- Encoder of @TEXT@ values.+{-# INLINABLE text #-}+text :: Value Text+text =+ Value (Value.unsafePTI PTI.text (const Encoder.text_strict))++-- |+-- Encoder of @BYTEA@ values.+{-# INLINABLE bytea #-}+bytea :: Value ByteString+bytea =+ Value (Value.unsafePTI PTI.bytea (const Encoder.bytea_strict))++-- |+-- Encoder of @DATE@ values.+{-# INLINABLE date #-}+date :: Value Day+date =+ Value (Value.unsafePTI PTI.date (const Encoder.date))++-- |+-- Encoder of @TIMESTAMP@ values.+{-# INLINABLE timestamp #-}+timestamp :: Value LocalTime+timestamp =+ Value (Value.unsafePTI PTI.timestamp (Prelude.bool Encoder.timestamp_int Encoder.timestamp_float))++-- |+-- Encoder of @TIMESTAMPTZ@ values.+{-# INLINABLE timestamptz #-}+timestamptz :: Value UTCTime+timestamptz =+ Value (Value.unsafePTI PTI.timestamptz (Prelude.bool Encoder.timestamptz_int Encoder.timestamptz_float))++-- |+-- Encoder of @TIME@ values.+{-# INLINABLE time #-}+time :: Value TimeOfDay+time =+ Value (Value.unsafePTI PTI.time (Prelude.bool Encoder.time_int Encoder.time_float))++-- |+-- Encoder of @TIMETZ@ values.+{-# INLINABLE timetz #-}+timetz :: Value (TimeOfDay, TimeZone)+timetz =+ Value (Value.unsafePTI PTI.timetz (Prelude.bool Encoder.timetz_int Encoder.timetz_float))++-- |+-- Encoder of @INTERVAL@ values.+{-# INLINABLE interval #-}+interval :: Value DiffTime+interval =+ Value (Value.unsafePTI PTI.interval (Prelude.bool Encoder.interval_int Encoder.interval_float))++-- |+-- Encoder of @UUID@ values.+{-# INLINABLE uuid #-}+uuid :: Value UUID+uuid =+ Value (Value.unsafePTI PTI.uuid (const Encoder.uuid))++-- |+-- Encoder of @JSON@ values.+{-# INLINABLE json #-}+json :: Value Aeson.Value+json =+ Value (Value.unsafePTI PTI.json (const Encoder.json))++-- |+-- Unlifts the 'Array' encoder to the plain 'Value' encoder.+{-# INLINABLE array #-}+array :: Array a -> Value a+array (Array imp) =+ Array.run imp & \(arrayOID, encoder') ->+ Value (Value.Value arrayOID arrayOID encoder')++-- |+-- Given a function,+-- which maps the value into the textual enum label from the DB side,+-- produces a encoder of that value.+{-# INLINABLE enum #-}+enum :: (a -> Text) -> Value a+enum mapping =+ Value (Value.unsafePTI PTI.text (const (Encoder.enum mapping)))+++-- ** Instances+-------------------------++-- | Maps to 'bool'.+instance Default (Value Bool) where+ {-# INLINE def #-}+ def =+ bool++-- | Maps to 'int2'.+instance Default (Value Int16) where+ {-# INLINE def #-}+ def =+ int2++-- | Maps to 'int4'.+instance Default (Value Int32) where+ {-# INLINE def #-}+ def =+ int4++-- | Maps to 'int8'.+instance Default (Value Int64) where+ {-# INLINE def #-}+ def =+ int8++-- | Maps to 'float4'.+instance Default (Value Float) where+ {-# INLINE def #-}+ def =+ float4++-- | Maps to 'float8'.+instance Default (Value Double) where+ {-# INLINE def #-}+ def =+ float8++-- | Maps to 'numeric'.+instance Default (Value Scientific) where+ {-# INLINE def #-}+ def =+ numeric++-- | Maps to 'char'.+instance Default (Value Char) where+ {-# INLINE def #-}+ def =+ char++-- | Maps to 'text'.+instance Default (Value Text) where+ {-# INLINE def #-}+ def =+ text++-- | Maps to 'bytea'.+instance Default (Value ByteString) where+ {-# INLINE def #-}+ def =+ bytea++-- | Maps to 'date'.+instance Default (Value Day) where+ {-# INLINE def #-}+ def =+ date++-- | Maps to 'timestamp'.+instance Default (Value LocalTime) where+ {-# INLINE def #-}+ def =+ timestamp++-- | Maps to 'timestamptz'.+instance Default (Value UTCTime) where+ {-# INLINE def #-}+ def =+ timestamptz++-- | Maps to 'time'.+instance Default (Value TimeOfDay) where+ {-# INLINE def #-}+ def =+ time++-- | Maps to 'timetz'.+instance Default (Value (TimeOfDay, TimeZone)) where+ {-# INLINE def #-}+ def =+ timetz++-- | Maps to 'interval'.+instance Default (Value DiffTime) where+ {-# INLINE def #-}+ def =+ interval++-- | Maps to 'uuid'.+instance Default (Value UUID) where+ {-# INLINE def #-}+ def =+ uuid++-- | Maps to 'json'.+instance Default (Value Aeson.Value) where+ {-# INLINE def #-}+ def =+ json+++-- * Array+-------------------------++-- |+-- A generic array encoder.+-- +-- Here's an example of its usage:+-- +-- >x :: Value [[Int64]]+-- >x =+-- > array (arrayDimension foldl' (arrayDimension foldl' (arrayValue int8)))+-- +newtype Array a =+ Array (Array.Array a)++-- |+-- Lifts the 'Value' encoder into the 'Array' encoder of a non-nullable value.+{-# INLINABLE arrayValue #-}+arrayValue :: Value a -> Array a+arrayValue (Value (Value.Value elementOID arrayOID encoder')) =+ Array (Array.value elementOID arrayOID encoder')++-- |+-- Lifts the 'Value' encoder into the 'Array' encoder of a nullable value.+{-# INLINABLE arrayNullableValue #-}+arrayNullableValue :: Value a -> Array (Maybe a)+arrayNullableValue (Value (Value.Value elementOID arrayOID encoder')) =+ Array (Array.nullableValue elementOID arrayOID encoder')++-- |+-- An encoder of an array dimension,+-- which thus provides support for multidimensional arrays.+-- +-- Accepts:+-- +-- * An implementation of the left-fold operation,+-- such as @Data.Foldable.'foldl''@,+-- which determines the input value.+-- +-- * A component encoder, which can be either another 'arrayDimension',+-- 'arrayValue' or 'arrayNullableValue'.+-- +{-# INLINABLE arrayDimension #-}+arrayDimension :: (forall a. (a -> b -> a) -> a -> c -> a) -> Array b -> Array c+arrayDimension foldl (Array imp) =+ Array (Array.dimension foldl imp)+
+ library/Hasql/Encoders/Array.hs view
@@ -0,0 +1,31 @@+module Hasql.Encoders.Array where++import Hasql.Prelude+import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified PostgreSQL.Binary.Encoder as Encoder+import qualified Hasql.PTI as PTI+++data Array a =+ Array PTI.OID PTI.OID (Bool -> Encoder.ArrayEncoder a)++{-# INLINE run #-}+run :: Array a -> (PTI.OID, Bool -> Encoder.Encoder a)+run (Array valueOID arrayOID encoder') =+ (arrayOID, \env -> Encoder.array (PTI.oidWord32 valueOID) (encoder' env))++{-# INLINE value #-}+value :: PTI.OID -> PTI.OID -> (Bool -> Encoder.Encoder a) -> Array a+value valueOID arrayOID encoder' =+ Array valueOID arrayOID (Encoder.arrayValue . encoder')++{-# INLINE nullableValue #-}+nullableValue :: PTI.OID -> PTI.OID -> (Bool -> Encoder.Encoder a) -> Array (Maybe a)+nullableValue valueOID arrayOID encoder' =+ Array valueOID arrayOID (Encoder.arrayNullableValue . encoder')++{-# INLINE dimension #-}+dimension :: (forall a. (a -> b -> a) -> a -> c -> a) -> Array b -> Array c+dimension foldl (Array valueOID arrayOID encoder') =+ Array valueOID arrayOID (Encoder.arrayDimension foldl . encoder')+
+ library/Hasql/Encoders/Params.hs view
@@ -0,0 +1,49 @@+module Hasql.Encoders.Params where++import Hasql.Prelude+import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified PostgreSQL.Binary.Encoder as Encoder+import qualified Hasql.Encoders.Value as Value+import qualified Hasql.PTI as PTI+++-- |+-- Encoder of some representation of a parameters product.+newtype Params a =+ Params (Op (DList (LibPQ.Oid, Bool -> Maybe ByteString)) a)+ deriving (Contravariant, Divisible, Monoid)++run :: Params a -> a -> DList (LibPQ.Oid, Bool -> Maybe ByteString)+run (Params (Op op)) params =+ {-# SCC "run" #-} + op params++run' :: Params a -> a -> Bool -> ([LibPQ.Oid], [Maybe (ByteString, LibPQ.Format)])+run' (Params (Op op)) params integerDatetimes =+ {-# SCC "run'" #-} + foldr step ([], []) (op params)+ where+ step (oid, bytesGetter) ~(oidList, bytesAndFormatList) =+ (,)+ (oid : oidList)+ (fmap (\bytes -> (bytes, LibPQ.Binary)) (bytesGetter integerDatetimes) : bytesAndFormatList)++run'' :: Params a -> a -> Bool -> [Maybe (LibPQ.Oid, ByteString, LibPQ.Format)]+run'' (Params (Op op)) params integerDatetimes =+ {-# SCC "run''" #-} + foldr step [] (op params)+ where+ step a b =+ mapping a : b+ where+ mapping (oid, bytesGetter) =+ (,,) <$> pure oid <*> bytesGetter integerDatetimes <*> pure LibPQ.Binary++value :: Value.Value a -> Params a+value =+ contramap Just . nullableValue++nullableValue :: Value.Value a -> Params (Maybe a)+nullableValue (Value.Value valueOID arrayOID encoder') =+ Params $ Op $ \input -> + pure (PTI.oidPQ valueOID, \env -> fmap (Encoder.run (encoder' env)) input)
+ library/Hasql/Encoders/Value.hs view
@@ -0,0 +1,27 @@+module Hasql.Encoders.Value where++import Hasql.Prelude+import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified PostgreSQL.Binary.Encoder as Encoder+import qualified Hasql.PTI as PTI+++data Value a =+ Value PTI.OID PTI.OID (Bool -> Encoder.Encoder a)++instance Contravariant Value where+ {-# INLINE contramap #-}+ contramap f (Value valueOID arrayOID encoder) =+ Value valueOID arrayOID (\integerDatetimes input -> encoder integerDatetimes (f input))++{-# INLINE run #-}+run :: Value a -> (PTI.OID, PTI.OID, Bool -> Encoder.Encoder a)+run (Value valueOID arrayOID encoder') =+ (valueOID, arrayOID, encoder')++{-# INLINE unsafePTI #-}+unsafePTI :: PTI.PTI -> (Bool -> Encoder.Encoder a) -> Value a+unsafePTI pti encoder' =+ Value (PTI.ptiOID pti) (fromMaybe ($bug "No array OID") (PTI.ptiArrayOID pti)) encoder'++
− library/Hasql/Encoding.hs
@@ -1,483 +0,0 @@--- |--- A DSL for declaration of query parameter encoders.-module Hasql.Encoding-(- -- * Params- Params,- unit,- value,- nullableValue,- -- * Value- Value,- bool,- int2,- int4,- int8,- float4,- float8,- numeric,- char,- text,- bytea,- date,- timestamp,- timestamptz,- time,- timetz,- interval,- uuid,- json,- array,- enum,- -- * Array- Array,- arrayValue,- arrayNullableValue,- arrayDimension,-)-where--import Hasql.Prelude hiding (bool)-import qualified PostgreSQL.Binary.Encoder as Encoder-import qualified Data.Aeson as Aeson-import qualified Hasql.Encoding.Params as Params-import qualified Hasql.Encoding.Value as Value-import qualified Hasql.Encoding.Array as Array-import qualified Hasql.PTI as PTI-import qualified Hasql.Prelude as Prelude----- * Parameters Product Encoder------------------------------ |--- Encoder of some representation of the parameters product.--- --- Has instances of 'Contravariant', 'Divisible' and 'Monoid',--- which you can use to compose multiple parameters together.--- E.g.,--- --- @--- someParamsEncoder :: 'Params' (Int64, Maybe Text)--- someParamsEncoder =--- 'contramap' 'fst' ('value' 'int8') '<>'--- 'contramap' 'snd' ('nullableValue' 'text')--- @--- --- As a general solution for tuples of any arity, instead of 'fst' and 'snd',--- consider the functions of the @contrazip@ family--- from the \"contravariant-extras\" package.--- E.g., here's how you can achieve the same as the above:--- --- @--- someParamsEncoder :: 'Params' (Int64, Maybe Text)--- someParamsEncoder =--- 'contrazip2' ('value' 'int8') ('nullableValue' 'text')--- @--- --- Here's how you can implement encoders for custom composite types:--- --- @--- data Person =--- Person { name :: Text, gender :: Gender, age :: Int }--- --- data Gender =--- Male | Female--- --- personParams :: 'Params' Person--- personParams =--- 'contramap' name ('value' 'text') '<>'--- 'contramap' gender ('value' genderValue) '<>'--- 'contramap' (fromIntegral . age) ('value' 'int8')--- --- genderValue :: 'Value' Gender--- genderValue =--- 'contramap' genderText 'text'--- where--- genderText gender =--- case gender of--- Male -> "male"--- Female -> "female"--- @--- -newtype Params a =- Params (Params.Params a)- deriving (Contravariant, Divisible, Monoid)---- |--- Encode no parameters.--- -{-# INLINABLE unit #-}-unit :: Params ()-unit =- Params mempty---- |--- Lift an individual value encoder to a parameters encoder.--- -{-# INLINABLE value #-}-value :: Value a -> Params a-value (Value x) =- Params (Params.value x)---- |--- Lift an individual nullable value encoder to a parameters encoder.--- -{-# INLINABLE nullableValue #-}-nullableValue :: Value a -> Params (Maybe a)-nullableValue (Value x) =- Params (Params.nullableValue x)----- ** Instances------------------------------ |--- Maps to 'unit'.-instance Default (Params ()) where- {-# INLINE def #-}- def =- unit--instance Default (Value a) => Default (Params (Identity a)) where- {-# INLINE def #-}- def =- contramap runIdentity (value def)--instance (Default (Value a1), Default (Value a2)) => Default (Params (a1, a2)) where- {-# INLINE def #-}- def =- contrazip2 (value def) (value def)--instance (Default (Value a1), Default (Value a2), Default (Value a3)) => Default (Params (a1, a2, a3)) where- {-# INLINE def #-}- def =- contrazip3 (value def) (value def) (value def)--instance (Default (Value a1), Default (Value a2), Default (Value a3), Default (Value a4)) => Default (Params (a1, a2, a3, a4)) where- {-# INLINE def #-}- def =- contrazip4 (value def) (value def) (value def) (value def)--instance (Default (Value a1), Default (Value a2), Default (Value a3), Default (Value a4), Default (Value a5)) => Default (Params (a1, a2, a3, a4, a5)) where- {-# INLINE def #-}- def =- contrazip5 (value def) (value def) (value def) (value def) (value def)----- * Value Encoder------------------------------ |--- An individual value encoder.--- Will be mapped to a single placeholder in the query.--- -newtype Value a =- Value (Value.Value a)- deriving (Contravariant)---- |--- Encoder of @BOOL@ values.-{-# INLINABLE bool #-}-bool :: Value Bool-bool =- Value (Value.unsafePTI PTI.bool (const Encoder.bool))---- |--- Encoder of @INT2@ values.-{-# INLINABLE int2 #-}-int2 :: Value Int16-int2 =- Value (Value.unsafePTI PTI.int2 (const Encoder.int2_int16))---- |--- Encoder of @INT4@ values.-{-# INLINABLE int4 #-}-int4 :: Value Int32-int4 =- Value (Value.unsafePTI PTI.int4 (const Encoder.int4_int32))---- |--- Encoder of @INT8@ values.-{-# INLINABLE int8 #-}-int8 :: Value Int64-int8 =- Value (Value.unsafePTI PTI.int8 (const Encoder.int8_int64))---- |--- Encoder of @FLOAT4@ values.-{-# INLINABLE float4 #-}-float4 :: Value Float-float4 =- Value (Value.unsafePTI PTI.float4 (const Encoder.float4))---- |--- Encoder of @FLOAT8@ values.-{-# INLINABLE float8 #-}-float8 :: Value Double-float8 =- Value (Value.unsafePTI PTI.float8 (const Encoder.float8))---- |--- Encoder of @NUMERIC@ values.-{-# INLINABLE numeric #-}-numeric :: Value Scientific-numeric =- Value (Value.unsafePTI PTI.numeric (const Encoder.numeric))---- |--- Encoder of @CHAR@ values.--- Note that it supports UTF-8 values and--- identifies itself under the @TEXT@ OID because of that.-{-# INLINABLE char #-}-char :: Value Char-char =- Value (Value.unsafePTI PTI.text (const Encoder.char))---- |--- Encoder of @TEXT@ values.-{-# INLINABLE text #-}-text :: Value Text-text =- Value (Value.unsafePTI PTI.text (const Encoder.text_strict))---- |--- Encoder of @BYTEA@ values.-{-# INLINABLE bytea #-}-bytea :: Value ByteString-bytea =- Value (Value.unsafePTI PTI.bytea (const Encoder.bytea_strict))---- |--- Encoder of @DATE@ values.-{-# INLINABLE date #-}-date :: Value Day-date =- Value (Value.unsafePTI PTI.date (const Encoder.date))---- |--- Encoder of @TIMESTAMP@ values.-{-# INLINABLE timestamp #-}-timestamp :: Value LocalTime-timestamp =- Value (Value.unsafePTI PTI.timestamp (Prelude.bool Encoder.timestamp_int Encoder.timestamp_float))---- |--- Encoder of @TIMESTAMPTZ@ values.-{-# INLINABLE timestamptz #-}-timestamptz :: Value UTCTime-timestamptz =- Value (Value.unsafePTI PTI.timestamptz (Prelude.bool Encoder.timestamptz_int Encoder.timestamptz_float))---- |--- Encoder of @TIME@ values.-{-# INLINABLE time #-}-time :: Value TimeOfDay-time =- Value (Value.unsafePTI PTI.time (Prelude.bool Encoder.time_int Encoder.time_float))---- |--- Encoder of @TIMETZ@ values.-{-# INLINABLE timetz #-}-timetz :: Value (TimeOfDay, TimeZone)-timetz =- Value (Value.unsafePTI PTI.timetz (Prelude.bool Encoder.timetz_int Encoder.timetz_float))---- |--- Encoder of @INTERVAL@ values.-{-# INLINABLE interval #-}-interval :: Value DiffTime-interval =- Value (Value.unsafePTI PTI.interval (Prelude.bool Encoder.interval_int Encoder.interval_float))---- |--- Encoder of @UUID@ values.-{-# INLINABLE uuid #-}-uuid :: Value UUID-uuid =- Value (Value.unsafePTI PTI.uuid (const Encoder.uuid))---- |--- Encoder of @JSON@ values.-{-# INLINABLE json #-}-json :: Value Aeson.Value-json =- Value (Value.unsafePTI PTI.json (const Encoder.json))---- |--- Unlifts the 'Array' encoder to the plain 'Value' encoder.-{-# INLINABLE array #-}-array :: Array a -> Value a-array (Array imp) =- Array.run imp & \(arrayOID, encoder') ->- Value (Value.Value arrayOID arrayOID encoder')---- |--- Given a function,--- which maps the value into the textual enum label from the DB side,--- produces a encoder of that value.-{-# INLINABLE enum #-}-enum :: (a -> Text) -> Value a-enum mapping =- Value (Value.unsafePTI PTI.text (const (Encoder.enum mapping)))----- ** Instances------------------------------ | Maps to 'bool'.-instance Default (Value Bool) where- {-# INLINE def #-}- def =- bool---- | Maps to 'int2'.-instance Default (Value Int16) where- {-# INLINE def #-}- def =- int2---- | Maps to 'int4'.-instance Default (Value Int32) where- {-# INLINE def #-}- def =- int4---- | Maps to 'int8'.-instance Default (Value Int64) where- {-# INLINE def #-}- def =- int8---- | Maps to 'float4'.-instance Default (Value Float) where- {-# INLINE def #-}- def =- float4---- | Maps to 'float8'.-instance Default (Value Double) where- {-# INLINE def #-}- def =- float8---- | Maps to 'numeric'.-instance Default (Value Scientific) where- {-# INLINE def #-}- def =- numeric---- | Maps to 'char'.-instance Default (Value Char) where- {-# INLINE def #-}- def =- char---- | Maps to 'text'.-instance Default (Value Text) where- {-# INLINE def #-}- def =- text---- | Maps to 'bytea'.-instance Default (Value ByteString) where- {-# INLINE def #-}- def =- bytea---- | Maps to 'date'.-instance Default (Value Day) where- {-# INLINE def #-}- def =- date---- | Maps to 'timestamp'.-instance Default (Value LocalTime) where- {-# INLINE def #-}- def =- timestamp---- | Maps to 'timestamptz'.-instance Default (Value UTCTime) where- {-# INLINE def #-}- def =- timestamptz---- | Maps to 'time'.-instance Default (Value TimeOfDay) where- {-# INLINE def #-}- def =- time---- | Maps to 'timetz'.-instance Default (Value (TimeOfDay, TimeZone)) where- {-# INLINE def #-}- def =- timetz---- | Maps to 'interval'.-instance Default (Value DiffTime) where- {-# INLINE def #-}- def =- interval---- | Maps to 'uuid'.-instance Default (Value UUID) where- {-# INLINE def #-}- def =- uuid---- | Maps to 'json'.-instance Default (Value Aeson.Value) where- {-# INLINE def #-}- def =- json----- * Array------------------------------ |--- A generic array encoder.--- --- Here's an example of its usage:--- --- >x :: Value [[Int64]]--- >x =--- > array (arrayDimension foldl' (arrayDimension foldl' (arrayValue int8)))--- -newtype Array a =- Array (Array.Array a)---- |--- Lifts the 'Value' encoder into the 'Array' encoder of a non-nullable value.-{-# INLINABLE arrayValue #-}-arrayValue :: Value a -> Array a-arrayValue (Value (Value.Value elementOID arrayOID encoder')) =- Array (Array.value elementOID arrayOID encoder')---- |--- Lifts the 'Value' encoder into the 'Array' encoder of a nullable value.-{-# INLINABLE arrayNullableValue #-}-arrayNullableValue :: Value a -> Array (Maybe a)-arrayNullableValue (Value (Value.Value elementOID arrayOID encoder')) =- Array (Array.nullableValue elementOID arrayOID encoder')---- |--- An encoder of an array dimension,--- which thus provides support for multidimensional arrays.--- --- Accepts:--- --- * An implementation of the left-fold operation,--- such as @Data.Foldable.'foldl''@,--- which determines the input value.--- --- * A component encoder, which can be either another 'arrayDimension',--- 'arrayValue' or 'arrayNullableValue'.--- -{-# INLINABLE arrayDimension #-}-arrayDimension :: (forall a. (a -> b -> a) -> a -> c -> a) -> Array b -> Array c-arrayDimension foldl (Array imp) =- Array (Array.dimension foldl imp)-
− library/Hasql/Encoding/Array.hs
@@ -1,31 +0,0 @@-module Hasql.Encoding.Array where--import Hasql.Prelude-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified PostgreSQL.Binary.Encoder as Encoder-import qualified Hasql.PTI as PTI---data Array a =- Array PTI.OID PTI.OID (Bool -> Encoder.ArrayEncoder a)--{-# INLINE run #-}-run :: Array a -> (PTI.OID, Bool -> Encoder.Encoder a)-run (Array valueOID arrayOID encoder') =- (arrayOID, \env -> Encoder.array (PTI.oidWord32 valueOID) (encoder' env))--{-# INLINE value #-}-value :: PTI.OID -> PTI.OID -> (Bool -> Encoder.Encoder a) -> Array a-value valueOID arrayOID encoder' =- Array valueOID arrayOID (Encoder.arrayValue . encoder')--{-# INLINE nullableValue #-}-nullableValue :: PTI.OID -> PTI.OID -> (Bool -> Encoder.Encoder a) -> Array (Maybe a)-nullableValue valueOID arrayOID encoder' =- Array valueOID arrayOID (Encoder.arrayNullableValue . encoder')--{-# INLINE dimension #-}-dimension :: (forall a. (a -> b -> a) -> a -> c -> a) -> Array b -> Array c-dimension foldl (Array valueOID arrayOID encoder') =- Array valueOID arrayOID (Encoder.arrayDimension foldl . encoder')-
− library/Hasql/Encoding/Params.hs
@@ -1,49 +0,0 @@-module Hasql.Encoding.Params where--import Hasql.Prelude-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified PostgreSQL.Binary.Encoder as Encoder-import qualified Hasql.Encoding.Value as Value-import qualified Hasql.PTI as PTI----- |--- Encoder of some representation of a parameters product.-newtype Params a =- Params (Op (DList (LibPQ.Oid, Bool -> Maybe ByteString)) a)- deriving (Contravariant, Divisible, Monoid)--run :: Params a -> a -> DList (LibPQ.Oid, Bool -> Maybe ByteString)-run (Params (Op op)) params =- {-# SCC "run" #-} - op params--run' :: Params a -> a -> Bool -> ([LibPQ.Oid], [Maybe (ByteString, LibPQ.Format)])-run' (Params (Op op)) params integerDatetimes =- {-# SCC "run'" #-} - foldr step ([], []) (op params)- where- step (oid, bytesGetter) ~(oidList, bytesAndFormatList) =- (,)- (oid : oidList)- (fmap (\bytes -> (bytes, LibPQ.Binary)) (bytesGetter integerDatetimes) : bytesAndFormatList)--run'' :: Params a -> a -> Bool -> [Maybe (LibPQ.Oid, ByteString, LibPQ.Format)]-run'' (Params (Op op)) params integerDatetimes =- {-# SCC "run''" #-} - foldr step [] (op params)- where- step a b =- mapping a : b- where- mapping (oid, bytesGetter) =- (,,) <$> pure oid <*> bytesGetter integerDatetimes <*> pure LibPQ.Binary--value :: Value.Value a -> Params a-value =- contramap Just . nullableValue--nullableValue :: Value.Value a -> Params (Maybe a)-nullableValue (Value.Value valueOID arrayOID encoder') =- Params $ Op $ \input -> - pure (PTI.oidPQ valueOID, \env -> fmap (Encoder.run (encoder' env)) input)
− library/Hasql/Encoding/Value.hs
@@ -1,27 +0,0 @@-module Hasql.Encoding.Value where--import Hasql.Prelude-import qualified Database.PostgreSQL.LibPQ as LibPQ-import qualified PostgreSQL.Binary.Encoder as Encoder-import qualified Hasql.PTI as PTI---data Value a =- Value PTI.OID PTI.OID (Bool -> Encoder.Encoder a)--instance Contravariant Value where- {-# INLINE contramap #-}- contramap f (Value valueOID arrayOID encoder) =- Value valueOID arrayOID (\integerDatetimes input -> encoder integerDatetimes (f input))--{-# INLINE run #-}-run :: Value a -> (PTI.OID, PTI.OID, Bool -> Encoder.Encoder a)-run (Value valueOID arrayOID encoder') =- (valueOID, arrayOID, encoder')--{-# INLINE unsafePTI #-}-unsafePTI :: PTI.PTI -> (Bool -> Encoder.Encoder a) -> Value a-unsafePTI pti encoder' =- Value (PTI.ptiOID pti) (fromMaybe ($bug "No array OID") (PTI.ptiArrayOID pti)) encoder'--
library/Hasql/IO.hs view
@@ -7,9 +7,9 @@ import qualified Database.PostgreSQL.LibPQ as LibPQ import qualified Hasql.Commands as Commands import qualified Hasql.PreparedStatementRegistry as PreparedStatementRegistry-import qualified Hasql.Decoding.Result as ResultDecoding-import qualified Hasql.Decoding.Results as ResultsDecoding-import qualified Hasql.Encoding.Params as ParamsEncoding+import qualified Hasql.Decoders.Result as ResultDecoders+import qualified Hasql.Decoders.Results as ResultsDecoders+import qualified Hasql.Encoders.Params as ParamsEncoders import qualified Data.DList as DList @@ -55,19 +55,19 @@ {-# INLINE initConnection #-} initConnection :: LibPQ.Connection -> IO () initConnection c =- void $ LibPQ.exec c (Commands.asBytes (Commands.setEncodingToUTF8 <> Commands.setMinClientMessagesToWarning))+ void $ LibPQ.exec c (Commands.asBytes (Commands.setEncodersToUTF8 <> Commands.setMinClientMessagesToWarning)) {-# INLINE getResults #-}-getResults :: LibPQ.Connection -> Bool -> ResultsDecoding.Results a -> IO (Either ResultsDecoding.Error a)+getResults :: LibPQ.Connection -> Bool -> ResultsDecoders.Results a -> IO (Either ResultsDecoders.Error a) getResults connection integerDatetimes des = {-# SCC "getResults" #-} - ResultsDecoding.run (des <* ResultsDecoding.dropRemainders) (integerDatetimes, connection)+ ResultsDecoders.run (des <* ResultsDecoders.dropRemainders) (integerDatetimes, connection) {-# INLINE getPreparedStatementKey #-} getPreparedStatementKey :: LibPQ.Connection -> PreparedStatementRegistry.PreparedStatementRegistry -> ByteString -> [LibPQ.Oid] ->- IO (Either ResultsDecoding.Error ByteString)+ IO (Either ResultsDecoders.Error ByteString) getPreparedStatementKey connection registry template oidList = {-# SCC "getPreparedStatementKey" #-} do@@ -81,8 +81,8 @@ sent <- LibPQ.sendPrepare connection key template (mfilter (not . null) (Just oidList)) let resultsDecoder = if sent- then ResultsDecoding.single ResultDecoding.unit- else ResultsDecoding.clientError+ then ResultsDecoders.single ResultDecoders.unit+ else ResultsDecoders.clientError runEitherT $ do EitherT $ getResults connection undefined resultsDecoder pure key@@ -91,10 +91,10 @@ map (\(LibPQ.Oid x) -> fromIntegral x) oidList {-# INLINE checkedSend #-}-checkedSend :: LibPQ.Connection -> IO Bool -> IO (Either ResultsDecoding.Error ())+checkedSend :: LibPQ.Connection -> IO Bool -> IO (Either ResultsDecoders.Error ()) checkedSend connection send = send >>= \case- False -> fmap (Left . ResultsDecoding.ClientError) $ LibPQ.errorMessage connection+ False -> fmap (Left . ResultsDecoders.ClientError) $ LibPQ.errorMessage connection True -> pure (Right ()) {-# INLINE sendPreparedParametricQuery #-}@@ -104,7 +104,7 @@ ByteString -> [LibPQ.Oid] -> [Maybe (ByteString, LibPQ.Format)] ->- IO (Either ResultsDecoding.Error ())+ IO (Either ResultsDecoders.Error ()) sendPreparedParametricQuery connection registry template oidList valueAndFormatList = runEitherT $ do key <- EitherT $ getPreparedStatementKey connection registry template oidList@@ -115,7 +115,7 @@ LibPQ.Connection -> ByteString -> [Maybe (LibPQ.Oid, ByteString, LibPQ.Format)] ->- IO (Either ResultsDecoding.Error ())+ IO (Either ResultsDecoders.Error ()) sendUnpreparedParametricQuery connection template paramList = checkedSend connection $ LibPQ.sendQueryParams connection template paramList LibPQ.Binary @@ -125,27 +125,27 @@ Bool -> PreparedStatementRegistry.PreparedStatementRegistry -> ByteString ->- ParamsEncoding.Params a ->+ ParamsEncoders.Params a -> Bool -> a ->- IO (Either ResultsDecoding.Error ())+ IO (Either ResultsDecoders.Error ()) sendParametricQuery connection integerDatetimes registry template encoder prepared params = {-# SCC "sendParametricQuery" #-} if prepared then let (oidList, valueAndFormatList) =- ParamsEncoding.run' encoder params integerDatetimes+ ParamsEncoders.run' encoder params integerDatetimes in sendPreparedParametricQuery connection registry template oidList valueAndFormatList else let paramList =- ParamsEncoding.run'' encoder params integerDatetimes+ ParamsEncoders.run'' encoder params integerDatetimes in sendUnpreparedParametricQuery connection template paramList {-# INLINE sendNonparametricQuery #-}-sendNonparametricQuery :: LibPQ.Connection -> ByteString -> IO (Either ResultsDecoding.Error ())+sendNonparametricQuery :: LibPQ.Connection -> ByteString -> IO (Either ResultsDecoders.Error ()) sendNonparametricQuery connection sql = checkedSend connection $ LibPQ.sendQuery connection sql
library/Hasql/Query.hs view
@@ -12,10 +12,10 @@ import Hasql.Prelude import qualified Database.PostgreSQL.LibPQ as LibPQ import qualified Hasql.PreparedStatementRegistry as PreparedStatementRegistry-import qualified Hasql.Decoding.Results as ResultsDecoding-import qualified Hasql.Decoding as Decoding-import qualified Hasql.Encoding.Params as ParamsEncoding-import qualified Hasql.Encoding as Encoding+import qualified Hasql.Decoders.Results as ResultsDecoders+import qualified Hasql.Decoders as Decoders+import qualified Hasql.Encoders.Params as ParamsEncoders+import qualified Hasql.Encoders as Encoders import qualified Hasql.Settings as Settings import qualified Hasql.IO as IO import qualified Hasql.Connection.Impl as Connection@@ -103,7 +103,7 @@ -- According to the format, -- parameters must be referred to using the positional notation, as in the following: -- @$1@, @$2@, @$3@ and etc.--- Those references must be used to refer to the values of the 'Encoding.Params' encoder.+-- Those references must be used to refer to the values of the 'Encoders.Params' encoder. -- -- Following is an example of the declaration of a prepared statement with its associated codecs. -- @@ -115,17 +115,17 @@ -- sql = -- "select ($1 + $2)" -- encoder =--- 'contramap' 'fst' (Hasql.Encoding.'Hasql.Encoding.value' Hasql.Encoding.'Hasql.Encoding.int8') '<>'--- 'contramap' 'snd' (Hasql.Encoding.'Hasql.Encoding.value' Hasql.Encoding.'Hasql.Encoding.int8')+-- 'contramap' 'fst' (Hasql.Encoders.'Hasql.Encoders.value' Hasql.Encoders.'Hasql.Encoders.int8') '<>'+-- 'contramap' 'snd' (Hasql.Encoders.'Hasql.Encoders.value' Hasql.Encoders.'Hasql.Encoders.int8') -- decoder =--- Hasql.Decoding.'Hasql.Decoding.singleRow' (Hasql.Decoding.'Hasql.Decoding.value' Hasql.Decoding.'Hasql.Decoding.int8')+-- Hasql.Decoders.'Hasql.Decoders.singleRow' (Hasql.Decoders.'Hasql.Decoders.value' Hasql.Decoders.'Hasql.Decoders.int8') -- @ -- -- The statement above accepts a product of two parameters of type 'Int64' -- and produces a single result of type 'Int64'. -- data Query a b =- Query !ByteString !(Encoding.Params a) !(Decoding.Result b) !Bool+ Query !ByteString !(Encoders.Params a) !(Decoders.Result b) !Bool deriving (Functor) instance Profunctor Query where@@ -150,16 +150,16 @@ -- | -- WARNING: We need to take special care that the structure of--- the "ResultsDecoding.Error" type in the public API is an exact copy of+-- the "ResultsDecoders.Error" type in the public API is an exact copy of -- "Error", since we're using coercion.-coerceResultsError :: ResultsDecoding.Error -> ResultsError+coerceResultsError :: ResultsDecoders.Error -> ResultsError coerceResultsError = unsafeCoerce -coerceDecoder :: Decoding.Result a -> ResultsDecoding.Results a+coerceDecoder :: Decoders.Result a -> ResultsDecoders.Results a coerceDecoder = unsafeCoerce -coerceEncoder :: Encoding.Params a -> ParamsEncoding.Params a+coerceEncoder :: Encoders.Params a -> ParamsEncoders.Params a coerceEncoder = unsafeCoerce
tasty/Main.hs view
@@ -3,38 +3,40 @@ import Main.Prelude hiding (assert, isRight, isLeft) import Test.QuickCheck.Instances import Test.Tasty-import qualified Main.Queries as Queries-import qualified Test.Tasty.HUnit as HUnit-import qualified Test.Tasty.SmallCheck as SmallCheck-import qualified Test.Tasty.QuickCheck as QuickCheck+import Test.Tasty.Runners+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck import qualified Test.QuickCheck as QuickCheck+import qualified Main.Queries as Queries import qualified Main.DSL as DSL import qualified Hasql.Query as Query-import qualified Hasql.Encoding as Encoding-import qualified Hasql.Decoding as Decoding+import qualified Hasql.Encoders as Encoders+import qualified Hasql.Decoders as Decoders main = defaultMain tree tree =+ localOption (NumThreads 1) $ testGroup "All tests" [- HUnit.testCase "Enum" $- HUnit.assertEqual "" (Right "ok") $+ testCase "Executing the same query twice" $+ pure ()+ ,+ testCase "Enum" $ let- actual =- unsafePerformIO $+ actualIO = DSL.session $ do let query =- Query.Query sql mempty Decoding.unit True+ Query.Query sql mempty Decoders.unit True where sql = "drop type if exists mood" in DSL.query () query let query =- Query.Query sql mempty Decoding.unit True+ Query.Query sql mempty Decoders.unit True where sql = "create type mood as enum ('sad', 'ok', 'happy')"@@ -46,17 +48,15 @@ sql = "select ($1 :: mood)" decoder =- (Decoding.singleRow (Decoding.value (Decoding.enum (Just . id))))+ (Decoders.singleRow (Decoders.value (Decoders.enum (Just . id)))) encoder =- Encoding.value (Encoding.enum id)+ Encoders.value (Encoders.enum id) in DSL.query "ok" query- in actual+ in actualIO >>= assertEqual "" (Right "ok") ,- HUnit.testCase "The same prepared statement used on different types" $ - HUnit.assertEqual "" (Right ("ok", 1)) $+ testCase "The same prepared statement used on different types" $ let- actual =- unsafePerformIO $+ actualIO = DSL.session $ do let effect1 =@@ -68,9 +68,9 @@ sql = "select $1" encoder =- Encoding.value Encoding.text+ Encoders.value Encoders.text decoder =- (Decoding.singleRow (Decoding.value (Decoding.text)))+ (Decoders.singleRow (Decoders.value (Decoders.text))) effect2 = DSL.query 1 query where@@ -80,17 +80,16 @@ sql = "select $1" encoder =- Encoding.value Encoding.int8+ Encoders.value Encoders.int8 decoder =- (Decoding.singleRow (Decoding.value Decoding.int8))+ (Decoders.singleRow (Decoders.value Decoders.int8)) in (,) <$> effect1 <*> effect2- in actual+ in actualIO >>= assertEqual "" (Right ("ok", 1)) ,- HUnit.testCase "Affected rows counting" $- HUnit.assertEqual "" (Right 100) $+ testCase "Affected rows counting" $+ replicateM_ 13 $ let- actual =- unsafePerformIO $+ actualIO = DSL.session $ do dropTable createTable@@ -112,25 +111,25 @@ sql = "delete from a" decoder =- Decoding.rowsAffected- in actual+ Decoders.rowsAffected+ in actualIO >>= assertEqual "" (Right 100) ,- HUnit.testCase "Result of an auto-incremented column" $+ testCase "Result of an auto-incremented column" $ let actualIO = DSL.session $ do DSL.query () $ Queries.plain $ "drop table if exists a" DSL.query () $ Queries.plain $ "create table a (id serial not null, v char not null, primary key (id))"- id1 <- DSL.query () $ Query.Query "insert into a (v) values ('a') returning id" def (Decoding.singleRow (Decoding.value Decoding.int4)) False- id2 <- DSL.query () $ Query.Query "insert into a (v) values ('b') returning id" def (Decoding.singleRow (Decoding.value Decoding.int4)) False+ id1 <- DSL.query () $ Query.Query "insert into a (v) values ('a') returning id" def (Decoders.singleRow (Decoders.value Decoders.int4)) False+ id2 <- DSL.query () $ Query.Query "insert into a (v) values ('b') returning id" def (Decoders.singleRow (Decoders.value Decoders.int4)) False DSL.query () $ Queries.plain $ "drop table if exists a" pure (id1, id2)- in HUnit.assertEqual "" (Right (1, 2)) =<< actualIO+ in assertEqual "" (Right (1, 2)) =<< actualIO ,- HUnit.testCase "List decoding" $+ testCase "List decoding" $ let actualIO = DSL.session $ DSL.query () $ Queries.selectList- in HUnit.assertEqual "" (Right [(1, 2), (3, 4), (5, 6)]) =<< actualIO+ in assertEqual "" (Right [(1, 2), (3, 4), (5, 6)]) =<< actualIO ]
tasty/Main/DSL.hs view
@@ -4,8 +4,8 @@ import qualified Hasql.Connection as HC import qualified Hasql.Query as HQ import qualified Hasql.Settings as HS-import qualified Hasql.Encoding as HE-import qualified Hasql.Decoding as HD+import qualified Hasql.Encoders as HE+import qualified Hasql.Decoders as HD newtype Session a =
tasty/Main/Queries.hs view
@@ -2,8 +2,8 @@ import Main.Prelude hiding (def) import qualified Hasql.Query as HQ-import qualified Hasql.Encoding as HE-import qualified Hasql.Decoding as HD+import qualified Hasql.Encoders as HE+import qualified Hasql.Decoders as HD import qualified Main.Prelude as Prelude