diff --git a/benchmark/Main.hs b/benchmark/Main.hs
--- a/benchmark/Main.hs
+++ b/benchmark/Main.hs
@@ -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
 
 
diff --git a/benchmark/Main/Queries.hs b/benchmark/Main/Queries.hs
--- a/benchmark/Main/Queries.hs
+++ b/benchmark/Main/Queries.hs
@@ -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)
diff --git a/hasql.cabal b/hasql.cabal
--- a/hasql.cabal
+++ b/hasql.cabal
@@ -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
diff --git a/library/Hasql/Commands.hs b/library/Hasql/Commands.hs
--- a/library/Hasql/Commands.hs
+++ b/library/Hasql/Commands.hs
@@ -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
diff --git a/library/Hasql/Decoders.hs b/library/Hasql/Decoders.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Decoders.hs
@@ -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))
+
diff --git a/library/Hasql/Decoders/Array.hs b/library/Hasql/Decoders/Array.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Decoders/Array.hs
@@ -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'
+
diff --git a/library/Hasql/Decoders/Composite.hs b/library/Hasql/Decoders/Composite.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Decoders/Composite.hs
@@ -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'
+
diff --git a/library/Hasql/Decoders/Result.hs b/library/Hasql/Decoders/Result.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Decoders/Result.hs
@@ -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
diff --git a/library/Hasql/Decoders/Results.hs b/library/Hasql/Decoders/Results.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Decoders/Results.hs
@@ -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)
diff --git a/library/Hasql/Decoders/Row.hs b/library/Hasql/Decoders/Row.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Decoders/Row.hs
@@ -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
diff --git a/library/Hasql/Decoders/Value.hs b/library/Hasql/Decoders/Value.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Decoders/Value.hs
@@ -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
+
diff --git a/library/Hasql/Decoding.hs b/library/Hasql/Decoding.hs
deleted file mode 100644
--- a/library/Hasql/Decoding.hs
+++ /dev/null
@@ -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))
-
diff --git a/library/Hasql/Decoding/Array.hs b/library/Hasql/Decoding/Array.hs
deleted file mode 100644
--- a/library/Hasql/Decoding/Array.hs
+++ /dev/null
@@ -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'
-
diff --git a/library/Hasql/Decoding/Composite.hs b/library/Hasql/Decoding/Composite.hs
deleted file mode 100644
--- a/library/Hasql/Decoding/Composite.hs
+++ /dev/null
@@ -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'
-
diff --git a/library/Hasql/Decoding/Result.hs b/library/Hasql/Decoding/Result.hs
deleted file mode 100644
--- a/library/Hasql/Decoding/Result.hs
+++ /dev/null
@@ -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
diff --git a/library/Hasql/Decoding/Results.hs b/library/Hasql/Decoding/Results.hs
deleted file mode 100644
--- a/library/Hasql/Decoding/Results.hs
+++ /dev/null
@@ -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)
diff --git a/library/Hasql/Decoding/Row.hs b/library/Hasql/Decoding/Row.hs
deleted file mode 100644
--- a/library/Hasql/Decoding/Row.hs
+++ /dev/null
@@ -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
diff --git a/library/Hasql/Decoding/Value.hs b/library/Hasql/Decoding/Value.hs
deleted file mode 100644
--- a/library/Hasql/Decoding/Value.hs
+++ /dev/null
@@ -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
-
diff --git a/library/Hasql/Encoders.hs b/library/Hasql/Encoders.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Encoders.hs
@@ -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)
+
diff --git a/library/Hasql/Encoders/Array.hs b/library/Hasql/Encoders/Array.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Encoders/Array.hs
@@ -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')
+
diff --git a/library/Hasql/Encoders/Params.hs b/library/Hasql/Encoders/Params.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Encoders/Params.hs
@@ -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)
diff --git a/library/Hasql/Encoders/Value.hs b/library/Hasql/Encoders/Value.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Encoders/Value.hs
@@ -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'
+
+
diff --git a/library/Hasql/Encoding.hs b/library/Hasql/Encoding.hs
deleted file mode 100644
--- a/library/Hasql/Encoding.hs
+++ /dev/null
@@ -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)
-
diff --git a/library/Hasql/Encoding/Array.hs b/library/Hasql/Encoding/Array.hs
deleted file mode 100644
--- a/library/Hasql/Encoding/Array.hs
+++ /dev/null
@@ -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')
-
diff --git a/library/Hasql/Encoding/Params.hs b/library/Hasql/Encoding/Params.hs
deleted file mode 100644
--- a/library/Hasql/Encoding/Params.hs
+++ /dev/null
@@ -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)
diff --git a/library/Hasql/Encoding/Value.hs b/library/Hasql/Encoding/Value.hs
deleted file mode 100644
--- a/library/Hasql/Encoding/Value.hs
+++ /dev/null
@@ -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'
-
-
diff --git a/library/Hasql/IO.hs b/library/Hasql/IO.hs
--- a/library/Hasql/IO.hs
+++ b/library/Hasql/IO.hs
@@ -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
diff --git a/library/Hasql/Query.hs b/library/Hasql/Query.hs
--- a/library/Hasql/Query.hs
+++ b/library/Hasql/Query.hs
@@ -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
diff --git a/tasty/Main.hs b/tasty/Main.hs
--- a/tasty/Main.hs
+++ b/tasty/Main.hs
@@ -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
   ]
 
diff --git a/tasty/Main/DSL.hs b/tasty/Main/DSL.hs
--- a/tasty/Main/DSL.hs
+++ b/tasty/Main/DSL.hs
@@ -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 =
diff --git a/tasty/Main/Queries.hs b/tasty/Main/Queries.hs
--- a/tasty/Main/Queries.hs
+++ b/tasty/Main/Queries.hs
@@ -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
 
 
