diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2014, Nikita Volkov
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/competition/Main.hs b/competition/Main.hs
new file mode 100644
--- /dev/null
+++ b/competition/Main.hs
@@ -0,0 +1,163 @@
+import BasePrelude
+import MTLPrelude
+import Data.Text (Text)
+import Data.Vector (Vector)
+import Data.Time
+import CriterionPlus
+import qualified Hasql as H
+import qualified Hasql.Postgres as H
+import qualified ListT
+import qualified Database.PostgreSQL.Simple as P
+import qualified Database.PostgreSQL.Simple.SqlQQ as P
+import qualified Database.PostgreSQL.Simple.FromField as P
+import qualified Database.PostgreSQL.Simple.ToField as P
+import qualified Database.PostgreSQL.Simple.FromRow as P
+import qualified Database.PostgreSQL.Simple.ToRow as P
+import qualified Database.PostgreSQL.Simple.Transaction as P
+import qualified Test.QuickCheck.Gen as Q
+import qualified Test.QuickCheck.Arbitrary as Q
+import qualified Test.QuickCheck.Instances
+
+
+main =
+  benchmark $ do
+
+    standoff "Results parsing" $ do
+
+      rows :: [(Text, Day)] <- 
+        liftIO $ replicateM 100 $ 
+          (,) <$> Q.generate Q.arbitrary <*> Q.generate Q.arbitrary
+
+      subject "hasql" $ do
+        pause
+        H.session (H.Postgres host port user password db) (fromJust $ H.sessionSettings 1 30) $ do
+          H.tx Nothing $ do
+            H.unit [H.q|DROP TABLE IF EXISTS a|]
+            H.unit [H.q|CREATE TABLE a (id SERIAL NOT NULL, 
+                                        name VARCHAR NOT NULL, 
+                                        birthday DATE,
+                                        PRIMARY KEY (id))|]
+            forM_ rows $ \(name, birthday) -> do
+              H.unit $ [H.q|INSERT INTO a (name, birthday) VALUES (?, ?)|] name birthday
+          lift $ continue
+          replicateM_ 100 $ do
+            H.tx Nothing $ do
+              H.list $ [H.q|SELECT * FROM a|] :: H.Tx H.Postgres s [(Int, Text, Day)]
+          lift $ pause
+
+      subject "postgresql-simple" $ do
+        pause
+        c <- liftIO $ P.connect $ P.ConnectInfo host port user password db
+        liftIO $ P.execute_ c "SET client_min_messages TO WARNING"
+        liftIO $ P.execute_ c "DROP TABLE IF EXISTS a"
+        liftIO $ P.execute_ c [P.sql|CREATE TABLE a (id SERIAL NOT NULL, 
+                                                     name VARCHAR NOT NULL, 
+                                                     birthday DATE, 
+                                                     PRIMARY KEY (id))|]
+        forM_ rows $ \(name, birthday) -> do
+          liftIO $ P.execute c "INSERT INTO a (name, birthday) VALUES (?, ?)" (name, birthday)
+        continue
+        liftIO $ replicateM_ 100 $ do
+          P.query_ c "SELECT * FROM a" :: IO [(Int, Text, Day)]
+        pause
+        liftIO $ P.close c
+
+    standoff "Templates and rendering" $ do
+
+      rows :: [(Text, Day)] <- 
+        liftIO $ replicateM 100 $ 
+          (,) <$> Q.generate Q.arbitrary <*> Q.generate Q.arbitrary
+
+      subject "hasql" $ do
+        pause
+        H.session (H.Postgres host port user password db) (fromJust $ H.sessionSettings 1 30) $ do
+          H.tx Nothing $ do
+            H.unit [H.q|DROP TABLE IF EXISTS a|]
+            H.unit [H.q|CREATE TABLE a (id SERIAL NOT NULL, 
+                                        name VARCHAR NOT NULL, 
+                                        birthday DATE,
+                                        PRIMARY KEY (id))|]
+          lift $ continue
+          replicateM_ 1000 $ do
+            H.tx Nothing $ do
+              H.list $ 
+                [H.q|SELECT * FROM a WHERE id > ? AND id < ? AND birthday != ?|] 
+                  (1000 :: Int)
+                  (0 :: Int) 
+                  (read "2014-10-26" :: Day)
+                :: H.Tx H.Postgres s [(Int, Text, Day)]
+          lift $ pause
+
+      subject "postgresql-simple" $ do
+        pause
+        c <- liftIO $ P.connect $ P.ConnectInfo host port user password db
+        liftIO $ P.execute_ c "SET client_min_messages TO WARNING"
+        liftIO $ P.execute_ c "DROP TABLE IF EXISTS a"
+        liftIO $ P.execute_ c [P.sql|CREATE TABLE a (id SERIAL NOT NULL, 
+                                                     name VARCHAR NOT NULL, 
+                                                     birthday DATE, 
+                                                     PRIMARY KEY (id))|]
+        continue
+        liftIO $ replicateM_ 1000 $ do
+          P.query c "SELECT * FROM a WHERE id > ? AND id < ? AND birthday != ?" 
+            (1000 :: Int, 0 :: Int, read "2014-10-26" :: Day) 
+            :: IO [(Int, Text, Day)]
+        pause
+        liftIO $ P.close c
+
+    standoff "Writing transaction" $ do
+      
+      let users = 20 :: Int
+          transfers = do
+            a <- [1..users]
+            b <- [1..users]
+            if a /= b then return (a, b) else mzero
+          amount = 3 :: Int64
+
+      subject "hasql" $ do
+        pause
+        H.session (H.Postgres host port user password db) (fromJust $ H.sessionSettings 1 30) $ do
+          H.tx Nothing $ do
+            H.unit [H.q|DROP TABLE IF EXISTS a|]
+            H.unit [H.q|CREATE TABLE a (id SERIAL NOT NULL, balance INT8, PRIMARY KEY (id))|]
+            replicateM_ users $ do
+              H.unit [H.q|INSERT INTO a (balance) VALUES (0)|]
+          lift $ continue
+          forM_ transfers $ \(id1, id2) -> do
+            H.tx (Just (H.Serializable, True)) $ do
+              runMaybeT $ do
+                do
+                  Identity balance <- MaybeT $ H.single $ [H.q|SELECT balance FROM a WHERE id=?|] id1
+                  lift $ H.unit $ [H.q|UPDATE a SET balance=? WHERE id=?|] (balance - amount) id1
+                do
+                  Identity balance <- MaybeT $ H.single $ [H.q|SELECT balance FROM a WHERE id=?|] id2
+                  lift $ H.unit $ [H.q|UPDATE a SET balance=? WHERE id=?|] (balance + amount) id2
+          lift $ pause
+
+      subject "postgresql-simple" $ do
+        pause
+        c <- liftIO $ P.connect $ P.ConnectInfo host port user password db
+        liftIO $ P.execute_ c "SET client_min_messages TO WARNING"
+        liftIO $ P.execute_ c "DROP TABLE IF EXISTS a"
+        liftIO $ P.execute_ c "CREATE TABLE a (id SERIAL NOT NULL, balance INT8, PRIMARY KEY (id))"
+        replicateM_ users $ do
+          liftIO $ P.execute_ c "INSERT INTO a (balance) VALUES (0)"
+        continue
+        forM_ transfers $ \(id1, id2) -> do
+          liftIO $ P.withTransactionMode (P.TransactionMode P.Serializable P.ReadWrite) c $ do
+            do
+              [P.Only balance] <- P.query c "SELECT balance FROM a WHERE id=?" (P.Only id1)
+              P.execute c "UPDATE a SET balance=? WHERE id=?" ((balance - amount), id1)
+            do
+              [P.Only balance] <- P.query c "SELECT balance FROM a WHERE id=?" (P.Only id2)
+              P.execute c "UPDATE a SET balance=? WHERE id=?" ((balance + amount), id1)
+        pause
+        liftIO $ P.close c
+
+
+
+host = "localhost"
+port = 5432
+user = "postgres"
+password = ""
+db = "postgres"
diff --git a/hasql-postgres.cabal b/hasql-postgres.cabal
new file mode 100644
--- /dev/null
+++ b/hasql-postgres.cabal
@@ -0,0 +1,255 @@
+name:
+  hasql-postgres
+version:
+  0.1.0
+synopsis:
+  A "PostgreSQL" driver for the "hasql" library
+description:
+category:
+  Database
+homepage:
+  https://github.com/nikita-volkov/hasql-postgres 
+bug-reports:
+  https://github.com/nikita-volkov/hasql-postgres/issues 
+author:
+  Nikita Volkov <nikita.y.volkov@mail.ru>
+maintainer:
+  Nikita Volkov <nikita.y.volkov@mail.ru>
+copyright:
+  (c) 2014, Nikita Volkov
+license:
+  MIT
+license-file:
+  LICENSE
+build-type:
+  Simple
+cabal-version:
+  >=1.10
+
+
+source-repository head
+  type:
+    git
+  location:
+    git://github.com/nikita-volkov/hasql-postgres.git
+
+
+library
+  hs-source-dirs:
+    library
+  ghc-options:
+    -funbox-strict-fields
+  default-extensions:
+    Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language:
+    Haskell2010
+  other-modules:
+    Hasql.Postgres.Prelude
+    Hasql.Postgres.Renderer
+    Hasql.Postgres.ErrorCode
+    Hasql.Postgres.OID
+    Hasql.Postgres.Statement
+    Hasql.Postgres.Parser
+    Hasql.Postgres.StatementPreparer
+    Hasql.Postgres.TemplateConverter
+    Hasql.Postgres.TemplateConverter.Parser
+    Hasql.Postgres.ResultHandler
+    Hasql.Postgres.Connector
+    Hasql.Postgres.ResultParser
+  exposed-modules:
+    Hasql.Postgres
+  build-depends:
+    -- parsers:
+    attoparsec == 0.12.*,
+    -- database:
+    hasql-backend == 0.1.*,
+    postgresql-libpq == 0.9.*,
+    -- data:
+    vector == 0.10.*,
+    old-locale == 1.0.*,
+    time == 1.4.*,
+    hashtables == 1.1.*,
+    Decimal == 0.4.*,
+    scientific == 0.3.*,
+    text >= 1 && < 1.3,
+    bytestring >= 0.10.4.0 && < 0.11,
+    hashable == 1.2.*,
+    -- control:
+    mmorph == 1.0.*,
+    list-t >= 0.2.3 && < 0.3,
+    -- errors:
+    loch-th == 0.2.*,
+    placeholders == 0.1.*,
+    -- general:
+    mtl-prelude == 2.0.*,
+    base-prelude >= 0.1.3 && < 0.2,
+    base >= 4.5 && < 4.8
+
+
+test-suite library-tests
+  type:             
+    exitcode-stdio-1.0
+  hs-source-dirs:   
+    library-tests
+    library
+  main-is:          
+    Main.hs
+  ghc-options:
+    -threaded
+    "-with-rtsopts=-N"
+    -funbox-strict-fields
+  default-extensions:
+    Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language:
+    Haskell2010
+  build-depends:
+    -- testing:
+    HTF == 0.12.*,
+    quickcheck-instances == 0.3.*,
+    QuickCheck == 2.7.*,
+    HUnit == 1.2.*,
+    -- parsers:
+    attoparsec == 0.12.*,
+    -- database:
+    hasql == 0.1.*,
+    postgresql-libpq == 0.9.*,
+    -- data:
+    vector == 0.10.*,
+    utf8-string >= 0.3.8 && < 0.4,
+    old-locale == 1.0.*,
+    time == 1.4.*,
+    hashtables == 1.1.*,
+    Decimal == 0.4.*,
+    scientific == 0.3.*,
+    text >= 1 && < 1.3,
+    bytestring >= 0.10.4.0 && < 0.11,
+    hashable == 1.2.*,
+    -- control:
+    mmorph == 1.0.*,
+    list-t >= 0.2.3 && < 0.3,
+    -- errors:
+    loch-th == 0.2.*,
+    placeholders == 0.1.*,
+    -- general:
+    mtl-prelude == 2.0.*,
+    base-prelude >= 0.1.3 && < 0.2,
+    base >= 4.5 && < 4.8
+
+
+test-suite tests
+  type:             
+    exitcode-stdio-1.0
+  hs-source-dirs:   
+    tests
+  main-is:          
+    Main.hs
+  ghc-options:
+    -threaded
+    "-with-rtsopts=-N"
+    -funbox-strict-fields
+  default-extensions:
+    Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language:
+    Haskell2010
+  build-depends:
+    hasql-postgres,
+    hasql-backend,
+    hasql == 0.1.*,
+    -- testing:
+    HTF == 0.12.*,
+    quickcheck-instances == 0.3.*,
+    QuickCheck == 2.7.*,
+    HUnit == 1.2.*,
+    -- concurrency:
+    SafeSemaphore == 0.10.*,
+    slave-thread == 0.1.*,
+    -- data:
+    old-locale == 1.0.*,
+    time == 1.4.*,
+    scientific == 0.3.*,
+    text >= 1 && < 1.3,
+    bytestring >= 0.10.4.0 && < 0.11,
+    hashable == 1.2.*,
+    -- general:
+    list-t == 0.2.*,
+    mtl-prelude == 2.0.*,
+    base-prelude >= 0.1.3 && < 0.2,
+    base >= 4.5 && < 4.8
+
+
+benchmark competition
+  type: 
+    exitcode-stdio-1.0
+  hs-source-dirs:
+    competition
+  main-is:
+    Main.hs
+  ghc-options:
+    -O2
+    -threaded
+    "-with-rtsopts=-N"
+    -funbox-strict-fields
+  default-extensions:
+    Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language:
+    Haskell2010
+  build-depends:
+    postgresql-simple == 0.4.*,
+    hasql-postgres,
+    hasql-backend,
+    hasql == 0.1.*,
+    -- random:
+    QuickCheck == 2.7.*,
+    quickcheck-instances == 0.3.*,
+    -- benchmarking:
+    criterion-plus == 0.1.*,
+    -- data:
+    vector == 0.10.*,
+    time == 1.4.*,
+    text >= 1 && < 1.3,
+    scientific == 0.3.*,
+    -- general:
+    list-t == 0.2.*,
+    mtl-prelude == 2.0.*,
+    base-prelude >= 0.1.3 && < 0.2,
+    base >= 4.5 && < 4.8
+
+
+benchmark profiling
+  type: 
+    exitcode-stdio-1.0
+  hs-source-dirs:
+    profiling
+  main-is:
+    Main.hs
+  ghc-options:
+    -O2
+    -threaded
+    "-with-rtsopts=-N"
+    -funbox-strict-fields
+    -fprof-auto
+    "-with-rtsopts=-N -p -s -h -i0.1"
+  default-extensions:
+    Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFunctor, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, ImpredicativeTypes, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language:
+    Haskell2010
+  build-depends:
+    postgresql-simple == 0.4.*,
+    hasql-postgres,
+    hasql-backend,
+    hasql == 0.1.*,
+    -- random:
+    QuickCheck == 2.7.*,
+    quickcheck-instances == 0.3.*,
+    -- data:
+    vector == 0.10.*,
+    time == 1.4.*,
+    text >= 1 && < 1.3,
+    scientific == 0.3.*,
+    -- general:
+    list-t == 0.2.*,
+    mtl-prelude == 2.0.*,
+    base-prelude >= 0.1.3 && < 0.2,
+    base >= 4.5 && < 4.8
+
+
diff --git a/library-tests/Main.hs b/library-tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/library-tests/Main.hs
@@ -0,0 +1,11 @@
+{-# OPTIONS_GHC -F -pgmF htfpp #-}
+module Main where
+
+import Test.Framework
+import Hasql.Postgres.Prelude
+
+import {-@ HTF_TESTS @-} Hasql.Postgres.ParserTests
+import {-@ HTF_TESTS @-} Hasql.Postgres.TemplateConverterTests
+
+main = 
+  htfMain $ htf_thisModulesTests : htf_importedTests
diff --git a/library/Hasql/Postgres.hs b/library/Hasql/Postgres.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres.hs
@@ -0,0 +1,237 @@
+module Hasql.Postgres (Postgres(..)) where
+
+import Hasql.Postgres.Prelude
+import qualified Database.PostgreSQL.LibPQ as PQ
+import qualified Hasql.Backend as Backend
+import qualified Hasql.Postgres.Connector as Connector
+import qualified Hasql.Postgres.ResultParser as ResultParser
+import qualified Hasql.Postgres.ResultHandler as ResultHandler
+import qualified Hasql.Postgres.Statement as Statement
+import qualified Hasql.Postgres.StatementPreparer as StatementPreparer
+import qualified Hasql.Postgres.TemplateConverter as TemplateConverter
+import qualified Hasql.Postgres.Parser as Parser
+import qualified Hasql.Postgres.Renderer as Renderer
+import qualified Hasql.Postgres.OID as OID
+import qualified Data.Text.Encoding as Text
+import qualified ListT
+
+
+-- |
+-- Settings of a Postgres backend.
+data Postgres =
+  Postgres {
+    host :: ByteString,
+    port :: Word16,
+    user :: Text,
+    password :: Text,
+    database :: Text
+  }
+
+instance Backend.Backend Postgres where
+  newtype StatementArgument Postgres = 
+    StatementArgument {unpackStatementArgument :: (PQ.Oid, Maybe (ByteString, PQ.Format))}
+  newtype Result Postgres = 
+    Result {unpackResult :: (Maybe ByteString)}
+  data Connection Postgres = 
+    Connection {
+      connection :: !PQ.Connection, 
+      preparer :: !StatementPreparer.StatementPreparer,
+      transactionState :: !(IORef (Maybe Word))
+    }
+  connect p =
+    do
+      r <- runExceptT $ Connector.open settings
+      case r of
+        Left e -> 
+          throwIO $ Backend.CantConnect $ fromString $ show e
+        Right c ->
+          Connection <$> pure c <*> StatementPreparer.new c <*> newIORef Nothing
+    where
+      settings =
+        Connector.Settings (host p) (port p) (user p) (password p) (database p)
+  disconnect c =
+    PQ.finish (connection c)
+  execute s c = 
+    ResultHandler.unit =<< execute (liftStatement s) c
+  executeAndGetMatrix s c =
+    unsafeCoerce . ResultHandler.rowsVector =<< execute (liftStatement s) c
+  executeAndStream s c =
+    do
+      name <- declareCursor
+      return $ unsafeCoerce $
+        let loop = do
+              chunk <- lift $ fetchFromCursor name
+              null <- lift $ ListT.null chunk
+              guard $ not null
+              chunk <> loop
+            in loop
+    where
+      nextName = 
+        do
+          counterM <- readIORef (transactionState c)
+          counter <- maybe (throwIO Backend.NotInTransaction) return counterM
+          writeIORef (transactionState c) (Just (succ counter))
+          return $ Renderer.run counter $ \n -> Renderer.char 'v' <> Renderer.word n
+      declareCursor =
+        do
+          name <- nextName
+          ResultHandler.unit =<< execute (Statement.declareCursor name (liftStatement s)) c
+          return name
+      fetchFromCursor name =
+        ResultHandler.rowsStream =<< execute (Statement.fetchFromCursor name) c
+      closeCursor name =
+        ResultHandler.unit =<< execute (Statement.closeCursor name) c
+  executeAndCountEffects s c =
+    do
+      b <- ResultHandler.rowsAffected =<< execute (liftStatement s) c
+      case Parser.run b Parser.unsignedIntegral of
+        Left m -> 
+          throwIO $ Backend.UnexpectedResult m
+        Right r ->
+          return r
+  beginTransaction (isolation, write) c = 
+    do
+      writeIORef (transactionState c) (Just 0)
+      ResultHandler.unit =<< execute (Statement.beginTransaction (statementIsolation, write)) c
+    where
+      statementIsolation =
+        case isolation of
+          Backend.Serializable    -> Statement.Serializable
+          Backend.RepeatableReads -> Statement.RepeatableRead
+          Backend.ReadCommitted   -> Statement.ReadCommitted
+          Backend.ReadUncommitted -> Statement.ReadCommitted
+  finishTransaction commit c =
+    do
+      ResultHandler.unit =<< execute (bool Statement.abortTransaction Statement.commitTransaction commit) c
+      writeIORef (transactionState c) Nothing
+
+liftStatement :: Backend.Statement Postgres -> Statement.Statement
+liftStatement (template, values) =
+  (template, map unpackStatementArgument values, True)
+
+execute :: Statement.Statement -> Backend.Connection Postgres -> IO ResultParser.Result
+execute s c =
+  ResultParser.parse (connection c) =<< do
+    let (template, params, preparable) = s
+    convertedTemplate <- convertTemplate template
+    case preparable of
+      True -> do
+        let (tl, vl) = unzip params
+        key <- StatementPreparer.prepare convertedTemplate tl (preparer c)
+        PQ.execPrepared (connection c) key vl PQ.Text
+      False -> do
+        let params' = map (\(t, v) -> (\(vb, vf) -> (t, vb, vf)) <$> v) params
+        PQ.execParams (connection c) convertedTemplate params' PQ.Text
+
+convertTemplate :: ByteString -> IO ByteString
+convertTemplate t =
+  case TemplateConverter.convert t of
+    Left m -> 
+      throwIO $ Backend.UnparsableTemplate $ 
+        "Template: " <> Text.decodeLatin1 t <> ". " <>
+        "Error: " <> m <> "."
+    Right r ->
+      return r
+
+
+
+-- * Mappings
+-------------------------
+
+instance Backend.Mapping Postgres a => Backend.Mapping Postgres (Maybe a) where
+  renderValue =
+    \case
+      Nothing -> 
+        case Backend.renderValue (undefined :: a) of
+          StatementArgument (oid, _) -> StatementArgument (oid, Nothing)
+      Just v ->
+        Backend.renderValue v
+  parseResult = traverse (Backend.parseResult . Result . Just) . unpackResult
+
+instance Backend.Mapping Postgres Bool where
+  renderValue = mkRenderValue OID.bool Renderer.bool
+  parseResult = mkParseResult Parser.bool
+
+instance Backend.Mapping Postgres Char where
+  renderValue = mkRenderValue OID.varchar Renderer.char
+  parseResult = mkParseResult Parser.utf8Char
+
+instance Backend.Mapping Postgres Text where
+  renderValue = mkRenderValue OID.text Renderer.text
+  parseResult = mkParseResult Parser.utf8Text
+
+instance Backend.Mapping Postgres Int where
+  renderValue = mkRenderValue OID.int8 Renderer.int
+  parseResult = mkParseResult Parser.integral
+
+instance Backend.Mapping Postgres Int8 where
+  renderValue = mkRenderValue OID.int2 Renderer.int8
+  parseResult = mkParseResult Parser.integral
+
+instance Backend.Mapping Postgres Int16 where
+  renderValue = mkRenderValue OID.int2 Renderer.int16
+  parseResult = mkParseResult Parser.integral
+
+instance Backend.Mapping Postgres Int32 where
+  renderValue = mkRenderValue OID.int4 Renderer.int32
+  parseResult = mkParseResult Parser.integral
+
+instance Backend.Mapping Postgres Int64 where
+  renderValue = mkRenderValue OID.int8 Renderer.int64
+  parseResult = mkParseResult Parser.integral
+
+instance Backend.Mapping Postgres Word where
+  renderValue = mkRenderValue OID.int8 Renderer.word
+  parseResult = mkParseResult Parser.unsignedIntegral
+
+instance Backend.Mapping Postgres Word8 where
+  renderValue = mkRenderValue OID.int2 Renderer.word8
+  parseResult = mkParseResult Parser.unsignedIntegral
+
+instance Backend.Mapping Postgres Word16 where
+  renderValue = mkRenderValue OID.int4 Renderer.word16
+  parseResult = mkParseResult Parser.unsignedIntegral
+
+instance Backend.Mapping Postgres Word32 where
+  renderValue = mkRenderValue OID.int8 Renderer.word32
+  parseResult = mkParseResult Parser.unsignedIntegral
+
+instance Backend.Mapping Postgres Word64 where
+  renderValue = mkRenderValue OID.int8 Renderer.word64
+  parseResult = mkParseResult Parser.unsignedIntegral
+
+instance Backend.Mapping Postgres Day where
+  renderValue = mkRenderValue OID.date Renderer.day
+  parseResult = mkParseResult Parser.day
+
+instance Backend.Mapping Postgres TimeOfDay where
+  renderValue = mkRenderValue OID.time Renderer.timeOfDay
+  parseResult = mkParseResult Parser.timeOfDay
+
+instance Backend.Mapping Postgres LocalTime where
+  renderValue = mkRenderValue OID.timestamp Renderer.localTime
+  parseResult = mkParseResult Parser.localTime
+
+instance Backend.Mapping Postgres ZonedTime where
+  renderValue = mkRenderValue OID.timestamptz Renderer.zonedTime 
+  parseResult = mkParseResult Parser.zonedTime 
+
+instance Backend.Mapping Postgres UTCTime where
+  renderValue = mkRenderValue OID.timestamp Renderer.utcTime
+  parseResult = mkParseResult Parser.utcTime
+
+
+-- |
+-- Make a 'renderValue' function with the 'Text' format.
+{-# INLINE mkRenderValue #-}
+mkRenderValue :: PQ.Oid -> Renderer.R a -> (a -> Backend.StatementArgument Postgres)
+mkRenderValue o r a =
+  StatementArgument (o, Just (Renderer.run a r, PQ.Text))
+
+{-# INLINE mkParseResult #-}
+mkParseResult :: Parser.P a -> (Backend.Result Postgres -> Either Text a)
+mkParseResult p (Result r) =
+  do
+    r' <- maybe (Left "Null result") Right r
+    left (\t -> "Input: " <> Text.decodeLatin1 r' <> ". Error: " <> t) $ 
+      Parser.run r' p
diff --git a/library/Hasql/Postgres/Connector.hs b/library/Hasql/Postgres/Connector.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/Connector.hs
@@ -0,0 +1,67 @@
+-- |
+-- Mid-level abstractions over gritty details of \"lib-pq\".
+module Hasql.Postgres.Connector where
+
+import Hasql.Postgres.Prelude hiding (Error)
+import qualified Database.PostgreSQL.LibPQ as L
+import qualified Hasql.Postgres.Renderer as Renderer
+
+
+data Settings =
+  Settings {
+    host :: ByteString,
+    port :: Word16,
+    user :: Text,
+    password :: Text,
+    database :: Text
+  }
+
+
+-- |
+-- Default settings.
+settings :: Settings
+settings =
+  Settings "127.0.0.1" 5432 "postgres" "" ""
+
+
+data Error =
+  BadStatus (Maybe ByteString) |
+  UnsupportedVersion Int
+  deriving (Show, Typeable)
+
+
+-- |
+-- Establish and initialize a connection.
+open :: Settings -> ExceptT Error IO L.Connection
+open s =
+  do
+    c <- lift $ L.connectdb (Renderer.run s settingsRenderer)
+    do
+      s <- lift $ L.status c
+      when (s /= L.ConnectionOk) $ 
+        do
+          m <- lift $ L.errorMessage c
+          throwError $ BadStatus m
+    do
+      v <- lift $ L.serverVersion c
+      when (v < 80200) $ throwError $ UnsupportedVersion v
+    lift $ L.exec c $ mconcat $ map (<> ";") $ 
+      [ "SET standard_conforming_strings TO on",
+        "SET datestyle TO ISO",
+        "SET client_encoding = 'UTF8'",
+        "SET client_min_messages TO WARNING" ]
+    return c
+
+
+settingsRenderer :: Renderer.R Settings
+settingsRenderer s =
+  mconcat $ intersperse " " args
+  where
+    args =
+      [
+        "host="     <> Renderer.byteString (host s),
+        "port="     <> Renderer.word16 (port s),
+        "user="     <> Renderer.text (user s),
+        "password=" <> Renderer.text (password s),
+        "dbname="   <> Renderer.text (database s)
+      ]
diff --git a/library/Hasql/Postgres/ErrorCode.hs b/library/Hasql/Postgres/ErrorCode.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/ErrorCode.hs
@@ -0,0 +1,409 @@
+-- |
+-- Error codes.
+-- See <http://www.postgresql.org/docs/current/interactive/errcodes-appendix.html>
+module Hasql.Postgres.ErrorCode where
+
+import qualified Data.ByteString
+
+type ErrorCode = 
+  Data.ByteString.ByteString
+
+-- * Class 00 — Successful Completion
+-------------------------
+
+successful_completion                                :: ErrorCode = "00000"
+
+-- * Class 01 — Warning
+-------------------------
+
+warning                                              :: ErrorCode = "01000"
+dynamic_result_sets_returned                         :: ErrorCode = "0100C"
+implicit_zero_bit_padding                            :: ErrorCode = "01008"
+null_value_eliminated_in_set_function                :: ErrorCode = "01003"
+privilege_not_granted                                :: ErrorCode = "01007"
+privilege_not_revoked                                :: ErrorCode = "01006"
+string_data_right_truncation                         :: ErrorCode = "01004"
+deprecated_feature                                   :: ErrorCode = "01P01"
+
+-- * Class 02 — No Data (this is also a warning class per the SQL standard)
+-------------------------
+
+no_data                                              :: ErrorCode = "02000"
+no_additional_dynamic_result_sets_returned           :: ErrorCode = "02001"
+
+-- * Class 03 — SQL Statement Not Yet Complete
+-------------------------
+
+sql_statement_not_yet_complete                       :: ErrorCode = "03000"
+
+-- * Class 08 — Connection Exception
+-------------------------
+
+connection_exception                                 :: ErrorCode = "08000"
+connection_does_not_exist                            :: ErrorCode = "08003"
+connection_failure                                   :: ErrorCode = "08006"
+sqlclient_unable_to_establish_sqlconnection          :: ErrorCode = "08001"
+sqlserver_rejected_establishment_of_sqlconnection    :: ErrorCode = "08004"
+transaction_resolution_unknown                       :: ErrorCode = "08007"
+protocol_violation                                   :: ErrorCode = "08P01"
+
+-- * Class 09 — Triggered Action Exception
+-------------------------
+
+triggered_action_exception                           :: ErrorCode = "09000"
+
+-- * Class 0A — Feature Not Supported
+-------------------------
+
+feature_not_supported                                :: ErrorCode = "0A000"
+
+-- * Class 0B — Invalid Transaction Initiation
+-------------------------
+
+invalid_transaction_initiation                       :: ErrorCode = "0B000"
+
+-- * Class 0F — Locator Exception
+-------------------------
+
+locator_exception                                    :: ErrorCode = "0F000"
+invalid_locator_specification                        :: ErrorCode = "0F001"
+
+-- * Class 0L — Invalid Grantor
+-------------------------
+
+invalid_grantor                                      :: ErrorCode = "0L000"
+invalid_grant_operation                              :: ErrorCode = "0LP01"
+
+-- * Class 0P — Invalid Role Specification
+-------------------------
+
+invalid_role_specification                           :: ErrorCode = "0P000"
+
+-- * Class 0Z — Diagnostics Exception
+-------------------------
+
+diagnostics_exception                                :: ErrorCode = "0Z000"
+stacked_diagnostics_accessed_without_active_handler  :: ErrorCode = "0Z002"
+
+-- * Class 20 — Case Not Found
+-------------------------
+
+case_not_found                                       :: ErrorCode = "20000"
+
+-- * Class 21 — Cardinality Violation
+-------------------------
+
+cardinality_violation                                :: ErrorCode = "21000"
+
+-- * Class 22 — Data Exception
+-------------------------
+
+data_exception                                       :: ErrorCode = "22000"
+array_subscript_error                                :: ErrorCode = "2202E"
+character_not_in_repertoire                          :: ErrorCode = "22021"
+datetime_field_overflow                              :: ErrorCode = "22008"
+division_by_zero                                     :: ErrorCode = "22012"
+error_in_assignment                                  :: ErrorCode = "22005"
+escape_character_conflict                            :: ErrorCode = "2200B"
+indicator_overflow                                   :: ErrorCode = "22022"
+interval_field_overflow                              :: ErrorCode = "22015"
+invalid_argument_for_logarithm                       :: ErrorCode = "2201E"
+invalid_argument_for_ntile_function                  :: ErrorCode = "22014"
+invalid_argument_for_nth_value_function              :: ErrorCode = "22016"
+invalid_argument_for_power_function                  :: ErrorCode = "2201F"
+invalid_argument_for_width_bucket_function           :: ErrorCode = "2201G"
+invalid_character_value_for_cast                     :: ErrorCode = "22018"
+invalid_datetime_format                              :: ErrorCode = "22007"
+invalid_escape_character                             :: ErrorCode = "22019"
+invalid_escape_octet                                 :: ErrorCode = "2200D"
+invalid_escape_sequence                              :: ErrorCode = "22025"
+nonstandard_use_of_escape_character                  :: ErrorCode = "22P06"
+invalid_indicator_parameter_value                    :: ErrorCode = "22010"
+invalid_parameter_value                              :: ErrorCode = "22023"
+invalid_regular_expression                           :: ErrorCode = "2201B"
+invalid_row_count_in_limit_clause                    :: ErrorCode = "2201W"
+invalid_row_count_in_result_offset_clause            :: ErrorCode = "2201X"
+invalid_time_zone_displacement_value                 :: ErrorCode = "22009"
+invalid_use_of_escape_character                      :: ErrorCode = "2200C"
+most_specific_type_mismatch                          :: ErrorCode = "2200G"
+null_value_not_allowed                               :: ErrorCode = "22004"
+null_value_no_indicator_parameter                    :: ErrorCode = "22002"
+numeric_value_out_of_range                           :: ErrorCode = "22003"
+string_data_length_mismatch                          :: ErrorCode = "22026"
+string_data_right_truncation'                        :: ErrorCode = "22001"
+substring_error                                      :: ErrorCode = "22011"
+trim_error                                           :: ErrorCode = "22027"
+unterminated_c_string                                :: ErrorCode = "22024"
+zero_length_character_string                         :: ErrorCode = "2200F"
+floating_point_exception                             :: ErrorCode = "22P01"
+invalid_text_representation                          :: ErrorCode = "22P02"
+invalid_binary_representation                        :: ErrorCode = "22P03"
+bad_copy_file_format                                 :: ErrorCode = "22P04"
+untranslatable_character                             :: ErrorCode = "22P05"
+not_an_xml_document                                  :: ErrorCode = "2200L"
+invalid_xml_document                                 :: ErrorCode = "2200M"
+invalid_xml_content                                  :: ErrorCode = "2200N"
+invalid_xml_comment                                  :: ErrorCode = "2200S"
+invalid_xml_processing_instruction                   :: ErrorCode = "2200T"
+
+-- * Class 23 — Integrity Constraint Violation
+-------------------------
+
+integrity_constraint_violation                       :: ErrorCode = "23000"
+restrict_violation                                   :: ErrorCode = "23001"
+not_null_violation                                   :: ErrorCode = "23502"
+foreign_key_violation                                :: ErrorCode = "23503"
+unique_violation                                     :: ErrorCode = "23505"
+check_violation                                      :: ErrorCode = "23514"
+exclusion_violation                                  :: ErrorCode = "23P01"
+
+-- * Class 24 — Invalid Cursor State
+-------------------------
+
+invalid_cursor_state                                 :: ErrorCode = "24000"
+
+-- * Class 25 — Invalid Transaction State
+-------------------------
+
+invalid_transaction_state                            :: ErrorCode = "25000"
+active_sql_transaction                               :: ErrorCode = "25001"
+branch_transaction_already_active                    :: ErrorCode = "25002"
+held_cursor_requires_same_isolation_level            :: ErrorCode = "25008"
+inappropriate_access_mode_for_branch_transaction     :: ErrorCode = "25003"
+inappropriate_isolation_level_for_branch_transaction :: ErrorCode = "25004"
+no_active_sql_transaction_for_branch_transaction     :: ErrorCode = "25005"
+read_only_sql_transaction                            :: ErrorCode = "25006"
+schema_and_data_statement_mixing_not_supported       :: ErrorCode = "25007"
+no_active_sql_transaction                            :: ErrorCode = "25P01"
+in_failed_sql_transaction                            :: ErrorCode = "25P02"
+
+-- * Class 26 — Invalid SQL Statement Name
+-------------------------
+
+invalid_sql_statement_name                           :: ErrorCode = "26000"
+
+-- * Class 27 — Triggered Data Change Violation
+-------------------------
+
+triggered_data_change_violation                      :: ErrorCode = "27000"
+
+-- * Class 28 — Invalid Authorization Specification
+-------------------------
+
+invalid_authorization_specification                  :: ErrorCode = "28000"
+invalid_password                                     :: ErrorCode = "28P01"
+
+-- * Class 2B — Dependent Privilege Descriptors Still Exist
+-------------------------
+
+dependent_privilege_descriptors_still_exist          :: ErrorCode = "2B000"
+dependent_objects_still_exist                        :: ErrorCode = "2BP01"
+
+-- * Class 2D — Invalid Transaction Termination
+-------------------------
+
+invalid_transaction_termination                      :: ErrorCode = "2D000"
+
+-- * Class 2F — SQL Routine Exception
+-------------------------
+
+sql_routine_exception                                :: ErrorCode = "2F000"
+function_executed_no_return_statement                :: ErrorCode = "2F005"
+modifying_sql_data_not_permitted                     :: ErrorCode = "2F002"
+prohibited_sql_statement_attempted                   :: ErrorCode = "2F003"
+reading_sql_data_not_permitted                       :: ErrorCode = "2F004"
+
+-- * Class 34 — Invalid Cursor Name
+-------------------------
+
+invalid_cursor_name                                  :: ErrorCode = "34000"
+
+-- * Class 38 — External Routine Exception
+-------------------------
+
+external_routine_exception                           :: ErrorCode = "38000"
+containing_sql_not_permitted                         :: ErrorCode = "38001"
+modifying_sql_data_not_permitted'                    :: ErrorCode = "38002"
+prohibited_sql_statement_attempted'                  :: ErrorCode = "38003"
+reading_sql_data_not_permitted'                      :: ErrorCode = "38004"
+
+-- * Class 39 — External Routine Invocation Exception
+-------------------------
+
+external_routine_invocation_exception                :: ErrorCode = "39000"
+invalid_sqlstate_returned                            :: ErrorCode = "39001"
+null_value_not_allowed'                              :: ErrorCode = "39004"
+trigger_protocol_violated                            :: ErrorCode = "39P01"
+srf_protocol_violated                                :: ErrorCode = "39P02"
+
+-- * Class 3B — Savepoint Exception
+-------------------------
+
+savepoint_exception                                  :: ErrorCode = "3B000"
+invalid_savepoint_specification                      :: ErrorCode = "3B001"
+
+-- * Class 3D — Invalid Catalog Name
+-------------------------
+
+invalid_catalog_name                                 :: ErrorCode = "3D000"
+
+-- * Class 3F — Invalid Schema Name
+-------------------------
+
+invalid_schema_name                                  :: ErrorCode = "3F000"
+
+-- * Class 40 — Transaction Rollback
+-------------------------
+
+transaction_rollback                                 :: ErrorCode = "40000"
+transaction_integrity_constraint_violation           :: ErrorCode = "40002"
+serialization_failure                                :: ErrorCode = "40001"
+statement_completion_unknown                         :: ErrorCode = "40003"
+deadlock_detected                                    :: ErrorCode = "40P01"
+
+-- * Class 42 — Syntax Error or Access Rule Violation
+-------------------------
+
+syntax_error_or_access_rule_violation                :: ErrorCode = "42000"
+syntax_error                                         :: ErrorCode = "42601"
+insufficient_privilege                               :: ErrorCode = "42501"
+cannot_coerce                                        :: ErrorCode = "42846"
+grouping_error                                       :: ErrorCode = "42803"
+windowing_error                                      :: ErrorCode = "42P20"
+invalid_recursion                                    :: ErrorCode = "42P19"
+invalid_foreign_key                                  :: ErrorCode = "42830"
+invalid_name                                         :: ErrorCode = "42602"
+name_too_long                                        :: ErrorCode = "42622"
+reserved_name                                        :: ErrorCode = "42939"
+datatype_mismatch                                    :: ErrorCode = "42804"
+indeterminate_datatype                               :: ErrorCode = "42P18"
+collation_mismatch                                   :: ErrorCode = "42P21"
+indeterminate_collation                              :: ErrorCode = "42P22"
+wrong_object_type                                    :: ErrorCode = "42809"
+undefined_column                                     :: ErrorCode = "42703"
+undefined_function                                   :: ErrorCode = "42883"
+undefined_table                                      :: ErrorCode = "42P01"
+undefined_parameter                                  :: ErrorCode = "42P02"
+undefined_object                                     :: ErrorCode = "42704"
+duplicate_column                                     :: ErrorCode = "42701"
+duplicate_cursor                                     :: ErrorCode = "42P03"
+duplicate_database                                   :: ErrorCode = "42P04"
+duplicate_function                                   :: ErrorCode = "42723"
+duplicate_prepared_statement                         :: ErrorCode = "42P05"
+duplicate_schema                                     :: ErrorCode = "42P06"
+duplicate_table                                      :: ErrorCode = "42P07"
+duplicate_alias                                      :: ErrorCode = "42712"
+duplicate_object                                     :: ErrorCode = "42710"
+ambiguous_column                                     :: ErrorCode = "42702"
+ambiguous_function                                   :: ErrorCode = "42725"
+ambiguous_parameter                                  :: ErrorCode = "42P08"
+ambiguous_alias                                      :: ErrorCode = "42P09"
+invalid_column_reference                             :: ErrorCode = "42P10"
+invalid_column_definition                            :: ErrorCode = "42611"
+invalid_cursor_definition                            :: ErrorCode = "42P11"
+invalid_database_definition                          :: ErrorCode = "42P12"
+invalid_function_definition                          :: ErrorCode = "42P13"
+invalid_prepared_statement_definition                :: ErrorCode = "42P14"
+invalid_schema_definition                            :: ErrorCode = "42P15"
+invalid_table_definition                             :: ErrorCode = "42P16"
+invalid_object_definition                            :: ErrorCode = "42P17"
+
+-- * Class 44 — WITH CHECK OPTION Violation
+-------------------------
+
+with_check_option_violation                          :: ErrorCode = "44000"
+
+-- * Class 53 — Insufficient Resources
+-------------------------
+
+insufficient_resources                               :: ErrorCode = "53000"
+disk_full                                            :: ErrorCode = "53100"
+out_of_memory                                        :: ErrorCode = "53200"
+too_many_connections                                 :: ErrorCode = "53300"
+configuration_limit_exceeded                         :: ErrorCode = "53400"
+
+-- * Class 54 — Program Limit Exceeded
+-------------------------
+
+program_limit_exceeded                               :: ErrorCode = "54000"
+statement_too_complex                                :: ErrorCode = "54001"
+too_many_columns                                     :: ErrorCode = "54011"
+too_many_arguments                                   :: ErrorCode = "54023"
+
+-- * Class 55 — Object Not In Prerequisite State
+-------------------------
+
+object_not_in_prerequisite_state                     :: ErrorCode = "55000"
+object_in_use                                        :: ErrorCode = "55006"
+cant_change_runtime_param                            :: ErrorCode = "55P02"
+lock_not_available                                   :: ErrorCode = "55P03"
+
+-- * Class 57 — Operator Intervention
+-------------------------
+
+operator_intervention                                :: ErrorCode = "57000"
+query_canceled                                       :: ErrorCode = "57014"
+admin_shutdown                                       :: ErrorCode = "57P01"
+crash_shutdown                                       :: ErrorCode = "57P02"
+cannot_connect_now                                   :: ErrorCode = "57P03"
+database_dropped                                     :: ErrorCode = "57P04"
+
+-- * Class 58 — System Error (errors external to PostgreSQL itself)
+-------------------------
+
+system_error                                         :: ErrorCode = "58000"
+io_error                                             :: ErrorCode = "58030"
+undefined_file                                       :: ErrorCode = "58P01"
+duplicate_file                                       :: ErrorCode = "58P02"
+
+-- * Class F0 — Configuration File Error
+-------------------------
+
+config_file_error                                    :: ErrorCode = "F0000"
+lock_file_exists                                     :: ErrorCode = "F0001"
+
+-- * Class HV — Foreign Data Wrapper Error (SQL/MED)
+-------------------------
+
+fdw_error                                            :: ErrorCode = "HV000"
+fdw_column_name_not_found                            :: ErrorCode = "HV005"
+fdw_dynamic_parameter_value_needed                   :: ErrorCode = "HV002"
+fdw_function_sequence_error                          :: ErrorCode = "HV010"
+fdw_inconsistent_descriptor_information              :: ErrorCode = "HV021"
+fdw_invalid_attribute_value                          :: ErrorCode = "HV024"
+fdw_invalid_column_name                              :: ErrorCode = "HV007"
+fdw_invalid_column_number                            :: ErrorCode = "HV008"
+fdw_invalid_data_type                                :: ErrorCode = "HV004"
+fdw_invalid_data_type_descriptors                    :: ErrorCode = "HV006"
+fdw_invalid_descriptor_field_identifier              :: ErrorCode = "HV091"
+fdw_invalid_handle                                   :: ErrorCode = "HV00B"
+fdw_invalid_option_index                             :: ErrorCode = "HV00C"
+fdw_invalid_option_name                              :: ErrorCode = "HV00D"
+fdw_invalid_string_length_or_buffer_length           :: ErrorCode = "HV090"
+fdw_invalid_string_format                            :: ErrorCode = "HV00A"
+fdw_invalid_use_of_null_pointer                      :: ErrorCode = "HV009"
+fdw_too_many_handles                                 :: ErrorCode = "HV014"
+fdw_out_of_memory                                    :: ErrorCode = "HV001"
+fdw_no_schemas                                       :: ErrorCode = "HV00P"
+fdw_option_name_not_found                            :: ErrorCode = "HV00J"
+fdw_reply_handle                                     :: ErrorCode = "HV00K"
+fdw_schema_not_found                                 :: ErrorCode = "HV00Q"
+fdw_table_not_found                                  :: ErrorCode = "HV00R"
+fdw_unable_to_create_execution                       :: ErrorCode = "HV00L"
+fdw_unable_to_create_reply                           :: ErrorCode = "HV00M"
+fdw_unable_to_establish_connection                   :: ErrorCode = "HV00N"
+
+-- * Class P0 — PL/pgSQL Error
+-------------------------
+
+plpgsql_error                                        :: ErrorCode = "P0000"
+raise_exception                                      :: ErrorCode = "P0001"
+no_data_found                                        :: ErrorCode = "P0002"
+too_many_rows                                        :: ErrorCode = "P0003"
+
+-- * Class XX — Internal Error
+-------------------------
+
+internal_error                                       :: ErrorCode = "XX000"
+data_corrupted                                       :: ErrorCode = "XX001"
+index_corrupted                                      :: ErrorCode = "XX002"
diff --git a/library/Hasql/Postgres/OID.hs b/library/Hasql/Postgres/OID.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/OID.hs
@@ -0,0 +1,54 @@
+module Hasql.Postgres.OID where
+
+import Database.PostgreSQL.LibPQ (Oid(..))
+
+
+type OID = Oid
+
+abstime     = Oid 702
+bit         = Oid 1560
+bool        = Oid 16
+box         = Oid 603
+bpchar      = Oid 1042
+bytea       = Oid 17
+char        = Oid 18
+cid         = Oid 29
+cidr        = Oid 650
+circle      = Oid 718
+date        = Oid 1082
+float4      = Oid 700
+float8      = Oid 701
+inet        = Oid 869
+int2        = Oid 21
+int4        = Oid 23
+int8        = Oid 20
+interval    = Oid 1186
+json        = Oid 114
+line        = Oid 628
+lseg        = Oid 601
+macaddr     = Oid 829
+money       = Oid 790
+name        = Oid 19
+numeric     = Oid 1700
+oid         = Oid 26
+path        = Oid 602
+point       = Oid 600
+polygon     = Oid 604
+record      = Oid 2249
+refcursor   = Oid 1790
+regproc     = Oid 24
+reltime     = Oid 703
+text        = Oid 25
+tid         = Oid 27
+time        = Oid 1083
+timestamp   = Oid 1114
+timestamptz = Oid 1184
+timetz      = Oid 1266
+tinterval   = Oid 704
+unknown     = Oid 705
+uuid        = Oid 2950
+varbit      = Oid 1562
+varchar     = Oid 1043
+void        = Oid 2278
+xid         = Oid 28
+xml         = Oid 142
diff --git a/library/Hasql/Postgres/Parser.hs b/library/Hasql/Postgres/Parser.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/Parser.hs
@@ -0,0 +1,144 @@
+module Hasql.Postgres.Parser where
+
+import Hasql.Postgres.Prelude hiding (take)
+import Data.Attoparsec.ByteString
+import Data.Attoparsec.ByteString.Char8
+import qualified Data.ByteString
+import qualified Data.Text
+import qualified Data.Text.Encoding
+import qualified Data.Text.Lazy
+import qualified Data.Text.Lazy.Encoding
+
+
+type P = Parser
+
+run :: ByteString -> P a -> Either Text a
+run input parser =
+  left fromString $ parseOnly (parser <* endOfInput) input
+
+
+-- ** Parser
+-------------------------
+
+labeling :: String -> Parser a -> Parser a
+labeling n p = 
+  p <?> n
+
+bool :: P Bool
+bool =
+  labeling "bool" $
+    ((string "true" <|> string "t" <|> string "True" <|> string "1") *> pure True) <|>
+    ((string "false" <|> string "f" <|> string "False" <|> string "0") *> pure False)
+
+utf8Char :: P Char
+utf8Char =
+  labeling "utf8Char" $
+    asum $ map byLength [1..4]
+  where
+    byLength l =
+      do
+        b <- take l
+        t <- either (const empty) return $ Data.Text.Encoding.decodeUtf8' b
+        (c, _) <- maybe empty return $ Data.Text.uncons t
+        return c
+
+utf8LazyText :: P Data.Text.Lazy.Text
+utf8LazyText =
+  labeling "utf8LazyText" $ do
+    b <- takeLazyByteString
+    either (const empty) return $ Data.Text.Lazy.Encoding.decodeUtf8' b
+
+utf8Text :: P Text
+utf8Text =
+  Data.Text.Lazy.toStrict <$> utf8LazyText
+
+charUnit :: Char -> P ()
+charUnit c = 
+  skip ((==) (fromIntegral (ord c)))
+
+-- | A signed integral value from a sequence of characters.
+{-# INLINE integral #-}
+integral :: (Integral a, Num a) => P a
+integral =
+  signed decimal
+  
+-- | An unsigned integral value from a sequence of characters.
+{-# INLINE unsignedIntegral #-}
+unsignedIntegral :: (Integral a, Num a) => P a
+unsignedIntegral =
+  decimal
+
+-- | An integral value from a single character.
+{-# INLINE integralDigit #-}
+integralDigit :: Integral a => P a
+integralDigit = 
+  satisfyWith (subtract 48 . fromIntegral) (\n -> n < 10 && n >= 0)
+
+day :: P Day
+day =
+  do
+    y <- unsignedIntegral
+    charUnit '-'
+    m <- unsignedIntegral
+    charUnit '-'
+    d <- unsignedIntegral
+    maybe empty return (fromGregorianValid y m d)
+
+timeOfDay :: P TimeOfDay
+timeOfDay =
+  do
+    h <- unsignedIntegral
+    charUnit ':'
+    m <- unsignedIntegral
+    charUnit ':'
+    s <- unsignedIntegral
+    p <- (charUnit '.' *> decimals) <|> pure 0
+    maybe empty return 
+      (makeTimeOfDayValid h m (fromIntegral s + p))
+  where
+    decimals = do
+      (b, i) <- match unsignedIntegral
+      return $ fromIntegral i / (10 ^ Data.ByteString.length b)
+
+localTime :: P LocalTime
+localTime = 
+  LocalTime <$> day <*> (charUnit ' ' *> timeOfDay)
+
+timeZoneTuple :: P (Bool, Int, Int, Int)
+timeZoneTuple =
+  do
+    p <- (charUnit '+' *> pure True) <|> (charUnit '-' *> pure False)
+    h <- unsignedIntegral
+    m <- (charUnit ':' *> unsignedIntegral) <|> pure 0
+    s <- (charUnit ':' *> unsignedIntegral) <|> pure 0
+    return $! (p, h, m, s)
+
+timeZone :: P TimeZone
+timeZone =
+  do
+    (p, h, m, s) <- timeZoneTuple
+    return $!
+      minutesToTimeZone ((Hasql.Postgres.Prelude.bool negate id p) (60 * h + m))
+
+-- |
+-- Takes seconds in timezone into account.
+zonedTime :: P ZonedTime
+zonedTime = 
+  do
+    LocalTime d t <- localTime
+    (zp, zh, zm, zs) <- timeZoneTuple
+    return $ ZonedTime (LocalTime d (timeOfDayDiffSecs zs t)) (composeTimezone zp zh zm)
+  where
+    timeOfDayDiffSecs s =
+      if s /= 0
+        then \t -> timeToTimeOfDay $ timeOfDayToTime t - fromIntegral s
+        else id
+    composeTimezone p h m =
+      minutesToTimeZone ((Hasql.Postgres.Prelude.bool negate id p) (60 * h + m))
+
+utcTime :: P UTCTime
+utcTime =
+  UTCTime <$> day <*> (charUnit ' ' *> diffTime)
+
+diffTime :: P DiffTime
+diffTime = timeOfDayToTime <$> timeOfDay
diff --git a/library/Hasql/Postgres/Prelude.hs b/library/Hasql/Postgres/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/Prelude.hs
@@ -0,0 +1,76 @@
+module Hasql.Postgres.Prelude
+( 
+  module Exports,
+  bug,
+  bottom,
+  partial,
+)
+where
+
+
+-- base-prelude
+-------------------------
+import BasePrelude as Exports
+
+-- mtl-prelude
+-------------------------
+import MTLPrelude as Exports hiding (shift)
+
+-- mmorph
+-------------------------
+import Control.Monad.Morph as Exports
+
+-- list-t
+-------------------------
+import ListT as Exports (ListT)
+
+-- hashable
+-------------------------
+import Data.Hashable as Exports (Hashable(..))
+
+-- text
+-------------------------
+import Data.Text as Exports (Text)
+
+-- bytestring
+-------------------------
+import Data.ByteString as Exports (ByteString)
+
+-- decimal
+-------------------------
+import Data.Decimal as Exports (Decimal, DecimalRaw)
+
+-- scientific
+-------------------------
+import Data.Scientific as Exports (Scientific)
+
+-- time
+-------------------------
+import Data.Time as Exports
+
+-- old-locale
+-------------------------
+import System.Locale as Exports
+
+-- vector
+-------------------------
+import Data.Vector as Exports (Vector)
+
+-- placeholders
+-------------------------
+import Development.Placeholders as Exports
+
+-- custom
+-------------------------
+import qualified Debug.Trace.LocationTH
+
+
+bug = [e| $(Debug.Trace.LocationTH.failure) . (msg <>) |]
+  where
+    msg = "A \"hasql-postgres\" package bug: " :: String
+
+bottom = [e| $bug "Bottom evaluated" |]
+
+partial :: Alternative f => (a -> Bool) -> a -> f a
+partial p x = 
+  if p x then pure x else empty
diff --git a/library/Hasql/Postgres/Renderer.hs b/library/Hasql/Postgres/Renderer.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/Renderer.hs
@@ -0,0 +1,162 @@
+-- |
+-- Useful info:
+-- https://github.com/hdbc/hdbc/blob/7ed3dfad534773cbfe2811ea241d245009e2961b/Database/HDBC/SqlValue.hs#L252
+module Hasql.Postgres.Renderer where
+
+import Hasql.Postgres.Prelude
+import qualified Data.ByteString.Builder as B
+import qualified Data.ByteString.Lazy as L
+import qualified Data.Text.Encoding as T
+import qualified Data.ByteString.Builder.Scientific
+
+
+-- | A renderer of @a@.
+type R a =
+  a -> B.Builder
+
+run :: a -> R a -> ByteString
+run a f =
+  (L.toStrict . B.toLazyByteString . f) a
+
+
+-- ** Renderer
+-------------------------
+
+ascii :: Show a => R a
+ascii =
+  B.string7 . show
+
+
+-- *** strings
+-------------------------
+
+char7 :: R Char = 
+  B.char7
+
+char :: R Char = 
+  B.charUtf8
+
+string7 :: R String = 
+  B.string7
+
+string :: R String = 
+  B.string8
+
+byteString :: R ByteString = 
+  B.byteString
+
+text :: R Text =
+  byteString . T.encodeUtf8
+
+
+-- *** enumerations
+-------------------------
+
+bool :: R Bool =
+  \b -> if b then word8 1 else word8 0
+
+word8 :: R Word8 =
+  B.word8Dec
+
+word16 :: R Word16 =
+  B.word16Dec
+
+word32 :: R Word32 =
+  B.word32Dec
+
+word64 :: R Word64 =
+  B.word64Dec
+
+word :: R Word =
+  B.wordDec
+
+int8 :: R Int8 =
+  B.int8Dec
+
+int16 :: R Int16 =
+  B.int16Dec
+
+int32 :: R Int32 =
+  B.int32Dec
+
+int64 :: R Int64 =
+  B.int64Dec
+
+int :: R Int =
+  B.intDec
+
+integer :: R Integer =
+  B.integerDec
+
+paddedInt :: Int -> R Int
+paddedInt padding n =
+  if padding <= width
+    then int n
+    else mconcat (replicate (padding - width) (B.char7 '0')) <> int n
+  where
+    width = fromIntegral (succ (floor (logBase 10 (fromIntegral n))) :: Integer)
+    
+
+-- *** fractionals
+-------------------------
+
+float :: R Float =
+  B.floatDec
+  
+double :: R Double =
+  B.doubleDec
+
+decimalRawInt32 :: R (DecimalRaw Int32) =
+  ascii
+
+decimalRawInt64 :: R (DecimalRaw Int64) =
+  ascii
+
+decimalRawWord32 :: R (DecimalRaw Word32) =
+  ascii
+
+decimalRawWord64 :: R (DecimalRaw Word64) =
+  ascii
+
+decimal :: R Decimal =
+  ascii
+
+pico :: R Pico =
+  B.string7 . showFixed True
+
+scientific :: R Scientific =
+  Data.ByteString.Builder.Scientific.scientificBuilder
+
+-- *** time
+-------------------------
+
+day :: R Day = 
+  B.string7 . formatTime defaultTimeLocale (iso8601DateFormat Nothing)
+
+timeOfDay :: R TimeOfDay = 
+  B.string7 . formatTime defaultTimeLocale "%T%Q"
+
+localTime :: R LocalTime = 
+  B.string7 . formatTime defaultTimeLocale (iso8601DateFormat (Just "%T%Q"))
+
+timeZone :: R TimeZone =
+  \(TimeZone t _ _) ->
+    if t < 0
+      then B.char7 '-' <> uncurry hm (divMod (negate t) 60)
+      else B.char7 '+' <> uncurry hm (divMod t 60)
+  where
+    hm h m = 
+      paddedInt 2 h <> B.char7 ':' <> paddedInt 2 m 
+
+zonedTime :: R ZonedTime = 
+  \(ZonedTime lt tz) ->
+    localTime lt <> timeZone tz
+
+utcTime :: R UTCTime = 
+  B.string7 . formatTime defaultTimeLocale (iso8601DateFormat (Just "%T%Q"))
+
+diffTime :: R DiffTime =
+  pico . fromRational . toRational
+
+nominalDiffTime :: R NominalDiffTime =
+  pico . fromRational . toRational
diff --git a/library/Hasql/Postgres/ResultHandler.hs b/library/Hasql/Postgres/ResultHandler.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/ResultHandler.hs
@@ -0,0 +1,75 @@
+-- |
+-- Backend-aware parsed results' handlers.
+module Hasql.Postgres.ResultHandler where
+
+import Hasql.Postgres.Prelude
+import qualified Hasql.Backend as Backend
+import qualified Hasql.Postgres.ResultParser as Result
+import qualified Hasql.Postgres.ErrorCode as ErrorCode
+
+
+type ResultHandler a =
+  Result.Result -> IO a
+
+{-# INLINE unit #-}
+unit :: ResultHandler ()
+unit =
+  resultHandler $ \case
+    Result.CommandOK _ -> Right $ return ()
+    _ -> Left "Not a unit"
+
+{-# INLINE rowsStream #-}
+rowsStream :: ResultHandler Result.RowsStream
+rowsStream =
+  resultHandler $ \case
+    Result.Rows s _ _ -> Right s
+    _ -> Left "Not a rows result"
+
+{-# INLINE rowsVector #-}
+rowsVector :: ResultHandler Result.RowsVector
+rowsVector =
+  resultHandler $ \case
+    Result.Rows _ v _ -> Right v
+    _ -> Left "Not a rows result"
+
+{-# INLINE rowsList #-}
+rowsList :: ResultHandler Result.RowsList
+rowsList =
+  resultHandler $ \case
+    Result.Rows _ _ l -> Right l
+    _ -> Left "Not a rows result"
+
+{-# INLINE rowsAffected #-}
+rowsAffected :: ResultHandler ByteString
+rowsAffected =
+  resultHandler $ \case
+    Result.CommandOK (Just v) -> Right $ return v
+    _ -> Left "Not a number of affected rows"
+
+{-# INLINE resultHandler #-}
+resultHandler :: (Result.Result -> Either Text (IO a)) -> ResultHandler a
+resultHandler partial result =
+  case partial result of
+    Right io -> 
+      io
+    Left text -> 
+      -- Handle erroneous results with unexpected result as a fallback.
+      case result of
+        Result.StatusError _ c _ _ _ | elem c codes ->
+          throwIO Backend.TransactionConflict
+          where
+            codes =
+              [
+                ErrorCode.transaction_rollback,
+                ErrorCode.transaction_integrity_constraint_violation,
+                ErrorCode.serialization_failure,
+                ErrorCode.statement_completion_unknown,
+                ErrorCode.deadlock_detected
+              ]
+        _ ->
+          maybe
+            (throwIO $ Backend.UnexpectedResult text)
+            (throwIO . Backend.ErroneousResult)
+            (Result.erroneousResultText result)
+
+
diff --git a/library/Hasql/Postgres/ResultParser.hs b/library/Hasql/Postgres/ResultParser.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/ResultParser.hs
@@ -0,0 +1,185 @@
+module Hasql.Postgres.ResultParser
+( 
+  Result(..), 
+  StatusErrorStatus(..),
+  RowsStream(..),
+  RowsVector(..),
+  RowsList(..),
+  parse,
+  erroneousResultText,
+)
+where
+
+import Hasql.Postgres.Prelude
+import qualified Database.PostgreSQL.LibPQ as L
+import qualified Data.Vector as Vector
+import qualified Data.Vector.Mutable as MVector
+import qualified ListT
+import qualified Data.Text.Encoding as Text
+import qualified Data.Text as Text
+
+
+data Result =
+  -- |
+  -- Out-of-memory conditions or serious errors such as inability to send the command to the server.
+  -- May contain some description.
+  NoResult (Maybe ByteString) |
+  -- |
+  -- A failure with comprehensive description.
+  -- 
+  -- The fields are: status, code, message, detail, hint.
+  StatusError StatusErrorStatus ByteString (Maybe ByteString) (Maybe ByteString) (Maybe ByteString) |
+  -- |
+  -- Command executed fine.
+  -- 
+  -- The fields are: a number of affected rows.
+  CommandOK (Maybe ByteString) |
+  -- |
+  -- Command executed fine and returns rows.
+  -- 
+  -- The fields are generators of respective rows representations.
+  Rows (IO RowsStream) (IO RowsVector) (IO RowsList)
+
+data StatusErrorStatus =
+  BadResponse | NonfatalError | FatalError
+  deriving (Show, Typeable, Eq, Ord, Enum, Bounded)
+
+
+parse :: L.Connection -> Maybe L.Result -> IO Result
+parse c =
+  \case
+    Nothing ->
+      NoResult <$> L.errorMessage c
+    Just r ->
+      L.resultStatus r >>=
+        \case
+          L.CommandOk ->
+            CommandOK <$> L.cmdTuples r
+          L.TuplesOk ->
+            return $ Rows <$> getRowsStream <*> getRowsVector <*> getRowsList $ r
+          L.BadResponse ->
+            statusError BadResponse
+          L.NonfatalError ->
+            statusError NonfatalError
+          L.FatalError ->
+            statusError FatalError
+          r ->
+            $bug $ "Unsupported result status: " <> show r
+      where
+        statusError s =
+          StatusError s <$> state <*> message <*> detail <*> hint
+          where
+            state   = fromJust <$> L.resultErrorField r L.DiagSqlstate
+            message = L.resultErrorField r L.DiagMessagePrimary
+            detail  = L.resultErrorField r L.DiagMessageDetail
+            hint    = L.resultErrorField r L.DiagMessageHint
+
+
+{-# INLINE erroneousResultText #-}
+erroneousResultText :: Result -> Maybe Text
+erroneousResultText =
+  \case
+    NoResult (Just bs) -> 
+      Just $ "Inable to send command to the server due to: " <> Text.decodeLatin1 bs
+    NoResult Nothing -> 
+      Just $ "Inable to send command to the server"
+    StatusError status code message details hint ->
+      Just $ 
+        "A status error. " <> formatFields fields
+      where
+        formatFields = 
+          formatList . map formatField . catMaybes
+          where
+            formatList items =
+              Text.intercalate "; " items <> "."
+            formatField (n, v) =
+              n <> ": \"" <> v <> "\""
+        fields =
+          [
+            Just ("Status", fromString $ show status),
+            Just ("Code", Text.decodeLatin1 code),
+            fmap (("Message",) . Text.decodeLatin1) $ message,
+            fmap (("Details",) . Text.decodeLatin1) $ details,
+            fmap (("Hint",) . Text.decodeLatin1) $ hint
+          ]
+    _ -> 
+      Nothing
+
+
+
+-- * Rows processing
+-------------------------
+
+type Row =
+  Vector (Maybe ByteString)
+
+
+type RowsStream =
+  ListT IO Row
+
+getRowsStream :: L.Result -> IO RowsStream
+getRowsStream r =
+  do
+    nr <- L.ntuples r
+    nc <- L.nfields r
+    return $ 
+      let
+        loop ir = 
+          if ir < nr
+            then do 
+              row <- 
+                liftIO $ do
+                  mv <- MVector.new (colInt nc)
+                  forM_ [0..pred nc] $ \ic ->
+                    MVector.write mv (colInt ic) =<< L.getvalue r ir ic
+                  Vector.unsafeFreeze mv
+              ListT.cons row (loop (succ ir))
+            else mzero
+        in 
+          loop 0
+
+
+type RowsVector =
+  Vector Row
+
+getRowsVector :: L.Result -> IO RowsVector
+getRowsVector r =
+  do
+    nr <- L.ntuples r
+    nc <- L.nfields r
+    mvx <- MVector.new (rowInt nr)
+    forM_ [0..pred nr] $ \ir -> do
+      mvy <- MVector.new (colInt nc)
+      forM_ [0..pred nc] $ \ic -> do
+        MVector.write mvy (colInt ic) =<< L.getvalue r ir ic
+      vy <- Vector.unsafeFreeze mvy
+      MVector.write mvx (rowInt ir) vy
+    Vector.unsafeFreeze mvx
+
+
+type RowsList =
+  [Row]
+
+getRowsList :: L.Result -> IO RowsList
+getRowsList r =
+  do
+    nr <- L.ntuples r
+    nc <- L.nfields r
+    mvx <- MVector.new (rowInt nr)
+    forM [0..pred nr] $ \ir -> do
+      mvy <- MVector.new (colInt nc)
+      forM_ [0..pred nc] $ \ic -> do
+        MVector.write mvy (colInt ic) =<< L.getvalue r ir ic
+      Vector.unsafeFreeze mvy
+
+
+-- * Utils
+-------------------------
+
+{-# INLINE colInt #-}
+colInt :: L.Column -> Int
+colInt (L.Col n) = fromIntegral n
+
+{-# INLINE rowInt #-}
+rowInt :: L.Row -> Int
+rowInt (L.Row n) = fromIntegral n
diff --git a/library/Hasql/Postgres/Statement.hs b/library/Hasql/Postgres/Statement.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/Statement.hs
@@ -0,0 +1,90 @@
+module Hasql.Postgres.Statement where
+
+import Hasql.Postgres.Prelude
+import qualified Database.PostgreSQL.LibPQ as L
+import qualified Hasql.Postgres.OID as OID
+import qualified Hasql.Postgres.Renderer as Renderer
+
+
+type Statement =
+  (ByteString, [(ValueType, Value)], Preparable)
+
+-- |
+-- Maybe a rendered value with its serialization format.
+-- 'Nothing' implies @NULL@.
+type Value = 
+  Maybe (ByteString, L.Format)
+
+type ValueType =
+  L.Oid
+
+type Preparable =
+  Bool  
+
+
+-- * Transaction types
+-------------------------
+
+type Cursor =
+  ByteString
+
+data Isolation =
+  ReadCommitted |
+  RepeatableRead |
+  Serializable 
+
+type TransactionMode =
+  (Isolation, Bool)
+
+
+declareCursor :: Cursor -> Statement -> Statement
+declareCursor cursor (template, values, preparable) =
+  let
+    template' =
+      "DECLARE " <> cursor <> " NO SCROLL CURSOR FOR " <> template
+    in (template', values, preparable)
+
+closeCursor :: Cursor -> Statement
+closeCursor cursor =
+  (template, [], False)
+  where
+    template =
+      Renderer.run cursor $ \c -> Renderer.string7 "CLOSE " <> Renderer.byteString c
+
+fetchFromCursor :: Cursor -> Statement
+fetchFromCursor cursor =
+  (template, [], False)
+  where
+    template =
+      Renderer.run cursor $ \c -> 
+        Renderer.string7 "FETCH FORWARD 256 FROM " <> Renderer.byteString c
+
+beginTransaction :: TransactionMode -> Statement
+beginTransaction mode =
+  (template, [], True)
+  where
+    template =
+      Renderer.run mode $ \(i, w) ->
+        mconcat $ intersperse (Renderer.char7 ' ') $
+          [
+            Renderer.string7 "BEGIN"
+            ,
+            case i of
+              ReadCommitted  -> Renderer.string7 "ISOLATION LEVEL READ COMMITTED"
+              RepeatableRead -> Renderer.string7 "ISOLATION LEVEL REPEATABLE READ"
+              Serializable   -> Renderer.string7 "ISOLATION LEVEL SERIALIZABLE"
+            ,
+            case w of
+              True  -> "READ WRITE"
+              False -> "READ ONLY"
+          ]
+
+commitTransaction :: Statement
+commitTransaction =
+  ("COMMIT", [], True)
+
+abortTransaction :: Statement
+abortTransaction =
+  ("ABORT", [], True)
+
+
diff --git a/library/Hasql/Postgres/StatementPreparer.hs b/library/Hasql/Postgres/StatementPreparer.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/StatementPreparer.hs
@@ -0,0 +1,55 @@
+-- |
+-- A backend-aware component, which prepares statements.
+module Hasql.Postgres.StatementPreparer where
+
+import Hasql.Postgres.Prelude
+import qualified Data.HashTable.IO as Hashtables
+import qualified Database.PostgreSQL.LibPQ as PQ
+import qualified Hasql.Postgres.Renderer as Renderer
+import qualified Hasql.Postgres.ResultParser as Result
+import qualified Hasql.Postgres.ResultHandler as ResultHandler
+
+
+
+type StatementPreparer =
+  (PQ.Connection, IORef Word16, Hashtables.BasicHashTable LocalKey RemoteKey)
+
+
+-- |
+-- Local statement key.
+data LocalKey =
+  LocalKey !ByteString ![PQ.Oid]
+  deriving (Show, Eq)
+
+-- |
+-- Optimized by ignoring the OIDs.
+instance Hashable LocalKey where
+  hashWithSalt s (LocalKey b _) = hashWithSalt s b
+
+
+-- |
+-- Remote statement key.
+type RemoteKey =
+  ByteString
+
+
+new :: PQ.Connection -> IO StatementPreparer
+new connection =
+  (,,) <$> pure connection <*> newIORef 0 <*> Hashtables.new
+
+prepare :: ByteString -> [PQ.Oid] -> StatementPreparer -> IO RemoteKey
+prepare s tl (c, counter, table) =
+  do
+    let k = LocalKey s tl
+    r <- Hashtables.lookup table k
+    case r of
+      Just r -> 
+        return r
+      Nothing ->
+        do
+          w <- readIORef counter
+          n <- return (Renderer.run w Renderer.word16)
+          ResultHandler.unit =<< Result.parse c =<< PQ.prepare c n s (partial (not . null) tl)
+          Hashtables.insert table k n
+          writeIORef counter (succ w)
+          return n
diff --git a/library/Hasql/Postgres/TemplateConverter.hs b/library/Hasql/Postgres/TemplateConverter.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/TemplateConverter.hs
@@ -0,0 +1,22 @@
+module Hasql.Postgres.TemplateConverter where
+
+import Hasql.Postgres.Prelude
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Builder as BB
+import qualified Hasql.Postgres.TemplateConverter.Parser as Parser
+
+
+convert :: ByteString -> Either Text ByteString
+convert t =
+  do
+    parts <- Parser.run t Parser.parts
+    return $
+      BL.toStrict $ BB.toLazyByteString $ mconcat $ ($ 1) $ evalState $ do
+        forM parts $ \case
+          Parser.Chunk c -> do
+            return c
+          Parser.Placeholder -> do
+            i <- get
+            put $ succ i
+            return $ BB.char8 '$' <> BB.wordDec i
+
diff --git a/library/Hasql/Postgres/TemplateConverter/Parser.hs b/library/Hasql/Postgres/TemplateConverter/Parser.hs
new file mode 100644
--- /dev/null
+++ b/library/Hasql/Postgres/TemplateConverter/Parser.hs
@@ -0,0 +1,39 @@
+module Hasql.Postgres.TemplateConverter.Parser where
+
+import Hasql.Postgres.Prelude
+import Data.Attoparsec.ByteString.Char8
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Builder as BB
+
+
+data Part =
+  Chunk BB.Builder |
+  Placeholder
+
+run :: ByteString -> Parser a -> Either Text a
+run input parser =
+  left fromString $ parseOnly (parser <* endOfInput) input
+
+parts :: Parser [Part]
+parts =
+  many (chunk <|> placeholder)
+  where
+    chunk = 
+      fmap Chunk $ fmap mconcat $ many1 $ stringLit <|> (BB.char8 <$> notChar '?')
+    placeholder = 
+      char '?' *> pure Placeholder
+
+stringLit :: Parser BB.Builder
+stringLit =
+  do
+    quote <- 
+      char '"' <|> char '\''
+    contentBuilders <- 
+      many $ 
+        (BB.byteString <$> string "\\\\") <|> 
+        (BB.byteString <$> string (fromString ['\\', quote])) <|> 
+        (BB.char8 <$> notChar quote)
+    char quote
+    return $
+      BB.char7 quote <> mconcat contentBuilders <> BB.char7 quote
+
diff --git a/profiling/Main.hs b/profiling/Main.hs
new file mode 100644
--- /dev/null
+++ b/profiling/Main.hs
@@ -0,0 +1,36 @@
+import BasePrelude
+import MTLPrelude
+import Data.Text (Text)
+import Data.Vector (Vector)
+import Data.Time
+import qualified Hasql as H
+import qualified Hasql.Postgres as H
+import qualified ListT
+import qualified Test.QuickCheck.Gen as Q
+import qualified Test.QuickCheck.Arbitrary as Q
+import qualified Test.QuickCheck.Instances
+
+
+main = do
+  rows :: [(Text, Day)] <- 
+    liftIO $ replicateM 100 $ 
+      (,) <$> Q.generate Q.arbitrary <*> Q.generate Q.arbitrary
+  H.session (H.Postgres host port user password db) (fromJust $ H.sessionSettings 1 30) $ do
+    H.tx Nothing $ do
+      H.unit [H.q|DROP TABLE IF EXISTS a|]
+      H.unit [H.q|CREATE TABLE a (id SERIAL NOT NULL, 
+                                  name VARCHAR NOT NULL, 
+                                  birthday DATE, 
+                                  PRIMARY KEY (id))|]
+      forM_ rows $ \(name, birthday) -> do
+        H.unit $ [H.q|INSERT INTO a (name, birthday) VALUES (?, ?)|] name birthday
+    replicateM_ 2000 $ do
+      H.tx Nothing $ do
+        H.list $ [H.q|SELECT * FROM a|] :: H.Tx H.Postgres s [(Int, Text, Day)]
+
+
+host = "localhost"
+port = 5432
+user = "postgres"
+password = ""
+db = "postgres"
diff --git a/tests/Main.hs b/tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/Main.hs
@@ -0,0 +1,212 @@
+{-# OPTIONS_GHC -F -pgmF htfpp #-}
+module Main where
+
+import BasePrelude hiding (assert)
+import MTLPrelude
+import Test.Framework
+import Test.QuickCheck.Instances
+import Hasql
+import Hasql.Postgres (Postgres(..))
+import Data.Text (Text)
+import Data.Time
+import qualified Data.Text
+import qualified ListT
+import qualified SlaveThread
+import qualified Control.Concurrent.SSem as SSem
+import qualified Hasql.Backend as Backend
+import qualified Hasql as H
+import qualified Hasql.Postgres as H
+
+
+main = 
+  htfMain $ htf_thisModulesTests
+
+
+test_rendering =
+  assertEqual (Just $ head rows) =<< do 
+    session1 $ do
+      H.tx Nothing $ do
+        H.unit [H.q|DROP TABLE IF EXISTS a|]
+        H.unit [H.q|CREATE TABLE a (id SERIAL NOT NULL, 
+                                    name VARCHAR NOT NULL, 
+                                    birthday INT8,
+                                    PRIMARY KEY (id))|]
+        forM_ rows $ \(name, birthday) -> do
+          H.unit $ [H.q|INSERT INTO a (name, birthday) VALUES (?, ?)|] name birthday
+      H.tx Nothing $ do
+        H.single $ [H.q|SELECT name, birthday FROM a WHERE id = ? |] (1 :: Int)
+  where
+    rows = [("A", 34525), ("B", 324987)] :: [(Text, Int)]
+
+test_countEffects =
+  unitTestPending ""
+
+test_autoIncrement =
+  assertEqual (Just (1 :: Word64), Just (2 :: Word64)) =<< do
+    session1 $ tx Nothing $ do
+      unit [q|DROP TABLE IF EXISTS a|]
+      unit [q|CREATE TABLE a (id SERIAL NOT NULL, v INT8, PRIMARY KEY (id))|]
+      id1 <- (fmap . fmap) runIdentity $ single $ [q|INSERT INTO a (v) VALUES (1) RETURNING id|]
+      id2 <- (fmap . fmap) runIdentity $ single $ [q|INSERT INTO a (v) VALUES (2) RETURNING id|]
+      return (id1, id2)
+
+test_transactionConflictResolution =
+  do
+    session1 $ tx Nothing $ do
+      unit [q|DROP TABLE IF EXISTS a|]
+      unit [q|CREATE TABLE a ("id" int8, "v" int8, PRIMARY KEY ("id"))|]
+      unit [q|INSERT INTO a (id, v) VALUES ('7', '0')|]
+    semaphore <- SSem.new (-1)
+    SlaveThread.fork $ session >> SSem.signal semaphore
+    SlaveThread.fork $ session >> SSem.signal semaphore
+    SSem.wait semaphore
+    r <- session1 $ tx Nothing $ single [q|SELECT v FROM a WHERE id='7'|]
+    assertEqual (Just (Identity (200 :: Int))) r
+  where
+    session =
+      session1 $ do
+        replicateM 100 $ tx (Just (Serializable, True)) $ do
+          Just (Identity (v :: Int)) <- single [q|SELECT v FROM a WHERE id='7'|]
+          unit $ [q|UPDATE a SET v=? WHERE id='7'|] (succ v)
+
+test_transaction =
+  unitTestPending ""
+
+test_cursorResultsOrder =
+  session1 $ do
+    r :: [Word] <-
+      tx (Just (ReadCommitted, False)) $ do
+        ListT.toList $ fmap runIdentity $ stream $ 
+          [q|select oid from pg_type ORDER BY oid|]
+    liftIO $ assertEqual (sort r) r
+
+test_cursor =
+  session1 $ do
+    r :: [(Word, Text)] <-
+      tx (Just (ReadCommitted, False)) $ do
+        ListT.toList $ stream $
+          [q|select oid, typname from pg_type|]
+    r' :: [(Word, Text)] <-
+      tx (Just (ReadCommitted, False)) $ do
+        list $ [q|select oid, typname from pg_type|]
+    liftIO $ assertEqual r' r
+
+test_select =
+  session1 $ do
+    r :: [(Word, Text)] <-
+      tx Nothing $ do
+        list $ [q|select oid, typname from pg_type|]
+    liftIO $ assertNotEqual [] r
+
+test_mappingOfMaybe =
+  session1 $ do
+    validMappingSession (Just '!')
+    validMappingSession (Nothing :: Maybe Bool)
+
+test_mappingOfBool =
+  session1 $ do
+    validMappingSession True
+    validMappingSession False
+
+test_mappingOfUTF8Char =
+  session1 $ do
+    validMappingSession 'Й'
+
+-- Postgres does not allow the '/NUL' character in text data
+prop_mappingOfChar (v :: Char) =
+  (v /= '\NUL') ==>
+    Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+-- Postgres does not allow the '/NUL' character in text data
+prop_mappingOfText (v :: Text) =
+  (isNothing $ Data.Text.find (== '\NUL') v) ==>
+    Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfInt (v :: Int) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfInt8 (v :: Int8) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfInt16 (v :: Int16) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfInt32 (v :: Int32) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfInt64 (v :: Int64) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfWord (v :: Word) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfWord8 (v :: Word8) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfWord16 (v :: Word16) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfWord32 (v :: Word32) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfWord64 (v :: Word64) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfDay (v :: Day) =
+  Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfTimeOfDay (v :: TimeOfDay) =
+  forAll microsTimeOfDayGen $ \v -> 
+    Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfLocalTime =
+  forAll microsLocalTimeGen $ \v -> 
+    Just v === do unsafePerformIO $ session1 $ selectSelf v
+
+prop_mappingOfZonedTime =
+  forAll gen $ \v -> 
+    eq v $ fromJust $ do unsafePerformIO $ session1 $ selectSelf v
+  where
+    eq a b = zonedTimeToUTC a === zonedTimeToUTC b
+    gen =
+      do
+        t <- microsLocalTimeGen
+        z <- minutesToTimeZone <$> choose (negate (11 * 60 + 59), 11 * 60 + 59)
+        return $ ZonedTime t z
+
+prop_mappingOfUTCTime =
+  forAll gen $ \v ->
+    Just v === do unsafePerformIO $ session1 $ selectSelf v
+  where
+    gen = UTCTime <$> arbitrary <*> microsDiffTimeGen
+
+microsTimeOfDayGen :: Gen TimeOfDay
+microsTimeOfDayGen =
+  timeToTimeOfDay <$> microsDiffTimeGen
+
+microsLocalTimeGen :: Gen LocalTime
+microsLocalTimeGen = 
+  LocalTime <$> arbitrary <*> microsTimeOfDayGen
+
+microsDiffTimeGen :: Gen DiffTime
+microsDiffTimeGen = do
+  fmap picosecondsToDiffTime $ fmap (* (10^6)) $ choose (0, (10^6)*24*60*60)
+
+selectSelf :: 
+  Backend.Mapping Postgres a => Typeable a => 
+  a -> Session Postgres IO (Maybe a)
+selectSelf v =
+  tx Nothing $ (fmap . fmap) runIdentity $ single $ [q| SELECT ? |] v
+
+validMappingSession :: 
+  Backend.Mapping Postgres a => Typeable a => Show a => Eq a => 
+  a -> Session Postgres IO ()
+validMappingSession v =
+  selectSelf v >>= liftIO . assertEqual (Just v)
+
+session1 :: Session Postgres IO r -> IO r
+session1 =
+  session backendSettings poolSettings
+  where
+    backendSettings = Postgres "localhost" 5432 "postgres" "" "postgres"
+    poolSettings = fromJust $ sessionSettings 6 30
