diff --git a/Database/SQLite/Simple.hs b/Database/SQLite/Simple.hs
--- a/Database/SQLite/Simple.hs
+++ b/Database/SQLite/Simple.hs
@@ -84,6 +84,7 @@
   , withTransaction
   , withImmediateTransaction
   , withExclusiveTransaction
+  , withSavepoint
     -- * Low-level statement API for stream access and prepared statements
   , openStatement
   , closeStatement
@@ -107,6 +108,7 @@
 import           Control.Monad.Trans.Reader
 import           Control.Monad.Trans.State.Strict
 import           Data.Int (Int64)
+import           Data.IORef
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as TE
 import           Data.Typeable (Typeable)
@@ -132,7 +134,11 @@
 data NamedParam where
     (:=) :: (ToField v) => T.Text -> v -> NamedParam
 
-data TransactionType = Deferred | Immediate | Exclusive
+data TransactionType
+  = Deferred
+  | Immediate
+  | Exclusive
+  | Savepoint T.Text
 
 infixr 3 :=
 
@@ -160,11 +166,11 @@
 -- connection.  This database will vanish when you close the
 -- connection.
 open :: String -> IO Connection
-open fname = Connection <$> Base.open (T.pack fname)
+open fname = Connection <$> Base.open (T.pack fname) <*> newIORef 0
 
 -- | Close a database connection.
 close :: Connection -> IO ()
-close (Connection c) = Base.close c
+close = Base.close . connectionHandle
 
 -- | Opens a database connection, executes an action using this connection, and
 -- closes the connection, even in the presence of exceptions.
@@ -182,8 +188,8 @@
 -- Warning: If the logger callback throws an exception, your whole
 -- program may crash.  Enable only for debugging!
 setTrace :: Connection -> Maybe (T.Text -> IO ()) -> IO ()
-setTrace (Connection db) logger =
-  BaseD.setTrace db (fmap (\lf -> lf . unUtf8) logger)
+setTrace conn logger =
+  BaseD.setTrace (connectionHandle conn) (fmap (\lf -> lf . unUtf8) logger)
 
 -- | Binds parameters to a prepared statement. Once 'nextRow' returns 'Nothing',
 -- the statement must be reset with the 'reset' function before it can be
@@ -238,7 +244,7 @@
 reset :: Statement -> IO ()
 reset (Statement stmt) = Base.reset stmt
 
--- | Return the name of a a particular column in the result set of a
+-- | Return the name of a particular column in the result set of a
 -- 'Statement'.  Throws an 'ArrayException' if the colum index is out
 -- of bounds.
 --
@@ -273,8 +279,8 @@
 -- 'Nothing', you need to invoke 'reset' before reexecuting the statement again
 -- with 'nextRow'.
 openStatement :: Connection -> Query -> IO Statement
-openStatement (Connection c) (Query t) = do
-  stmt <- Base.prepare c t
+openStatement conn (Query t) = do
+  stmt <- Base.prepare (connectionHandle conn) t
   return $ Statement stmt
 
 -- | Closes a prepared statement.
@@ -498,12 +504,17 @@
     commit
     return r
   where
-    begin    = case ttype of
-                 Deferred  -> execute_ conn "BEGIN TRANSACTION"
-                 Immediate -> execute_ conn "BEGIN IMMEDIATE TRANSACTION"
-                 Exclusive -> execute_ conn "BEGIN EXCLUSIVE TRANSACTION"
-    commit   = execute_ conn "COMMIT TRANSACTION"
-    rollback = execute_ conn "ROLLBACK TRANSACTION"
+    begin    = execute_ conn $ case ttype of
+                 Deferred  -> "BEGIN TRANSACTION"
+                 Immediate -> "BEGIN IMMEDIATE TRANSACTION"
+                 Exclusive -> "BEGIN EXCLUSIVE TRANSACTION"
+                 Savepoint name -> Query $ "SAVEPOINT '" <> name <> "'"
+    commit   = execute_ conn $ case ttype of
+                 Savepoint name -> Query $ "RELEASE '" <> name <> "'"
+                 _ -> "COMMIT TRANSACTION"
+    rollback = execute_ conn $ case ttype of
+                 Savepoint name -> Query $ "ROLLBACK TO '" <> name <> "'"
+                 _ -> "ROLLBACK TRANSACTION"
 
 
 -- | Run an IO action inside a SQL transaction started with @BEGIN IMMEDIATE
@@ -532,21 +543,21 @@
 --
 -- See also <http://www.sqlite.org/c3ref/last_insert_rowid.html>.
 lastInsertRowId :: Connection -> IO Int64
-lastInsertRowId (Connection c) = BaseD.lastInsertRowId c
+lastInsertRowId = BaseD.lastInsertRowId . connectionHandle
 
 -- | <http://www.sqlite.org/c3ref/changes.html>
 --
 -- Return the number of rows that were changed, inserted, or deleted
 -- by the most recent @INSERT@, @DELETE@, or @UPDATE@ statement.
 changes :: Connection -> IO Int
-changes (Connection c) = BaseD.changes c
+changes = BaseD.changes . connectionHandle
 
 -- | <http://www.sqlite.org/c3ref/total_changes.html>
 --
 -- Return the total number of row changes caused by @INSERT@, @DELETE@,
 -- or @UPDATE@ statements since the 'Database' was opened.
 totalChanges :: Connection -> IO Int
-totalChanges (Connection c) = BaseD.totalChanges c
+totalChanges = BaseD.totalChanges . connectionHandle
 
 -- | Run an IO action inside a SQL transaction started with @BEGIN
 -- TRANSACTION@.  If the action throws any kind of an exception, the
@@ -556,6 +567,19 @@
 withTransaction conn action =
   withTransactionPrivate conn action Deferred
 
+-- | Run an IO action inside an SQLite @SAVEPOINT@. If the action throws any
+-- kind of an exception, the transaction will be rolled back to the savepoint
+-- with @ROLLBACK TO@. Otherwise the results are released to the outer
+-- transaction if any with @RELEASE@.
+--
+-- See <https://sqlite.org/lang_savepoint.html> for a full description of
+-- savepoint semantics.
+withSavepoint :: Connection -> IO a -> IO a
+withSavepoint conn action = do
+  n <- atomicModifyIORef' (connectionTempNameCounter conn) $ \n -> (n + 1, n)
+  withTransactionPrivate conn action $
+    Savepoint $ "sqlite_simple_savepoint_" <> T.pack (show n)
+
 fmtError :: Show v => String -> Query -> [v] -> a
 fmtError msg q xs =
   throw FormatError {
@@ -624,13 +648,13 @@
 -- To most easily construct a query, enable GHC's @OverloadedStrings@
 -- language extension and write your query as a normal literal string.
 --
--- > {-# LANGUAGE OverloadedStrings #-}
+-- > {-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
 -- >
 -- > import Database.SQLite.Simple
 -- >
 -- > hello = do
 -- >   conn <- open "test.db"
--- >   [[x]] <- query_ conn "select 2 + 2"
+-- >   [[x :: Int]] <- query_ conn "select 2 + 2"
 -- >   print x
 --
 -- A 'Query' value does not represent the actual query that will be
@@ -782,7 +806,7 @@
 --
 -- Although SQL can accommodate @NULL@ as a value for any of these
 -- types, Haskell cannot. If your result contains columns that may be
--- @NULL@, be sure that you use 'Maybe' in those positions of of your
+-- @NULL@, be sure that you use 'Maybe' in those positions of your
 -- tuple.
 --
 -- > (Text, Maybe Int, Int)
diff --git a/Database/SQLite/Simple/FromField.hs b/Database/SQLite/Simple/FromField.hs
--- a/Database/SQLite/Simple/FromField.hs
+++ b/Database/SQLite/Simple/FromField.hs
@@ -1,6 +1,7 @@
-{-# LANGUAGE CPP, DeriveDataTypeable, DeriveFunctor  #-}
-{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
-{-# LANGUAGE ScopedTypeVariables      #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 
 ------------------------------------------------------------------------------
 -- |
diff --git a/Database/SQLite/Simple/FromRow.hs b/Database/SQLite/Simple/FromRow.hs
--- a/Database/SQLite/Simple/FromRow.hs
+++ b/Database/SQLite/Simple/FromRow.hs
@@ -1,5 +1,4 @@
-{-# LANGUAGE RecordWildCards, DefaultSignatures, FlexibleContexts,
-  StandaloneDeriving #-}
+{-# LANGUAGE DefaultSignatures, FlexibleContexts #-}
 
 ------------------------------------------------------------------------------
 -- |
diff --git a/Database/SQLite/Simple/Function.hs b/Database/SQLite/Simple/Function.hs
--- a/Database/SQLite/Simple/Function.hs
+++ b/Database/SQLite/Simple/Function.hs
@@ -55,8 +55,8 @@
       Errors ex -> throw $ ManyErrors ex
 
 createFunction :: forall f . Function f => Connection -> T.Text -> f -> IO (Either Base.Error ())
-createFunction (Connection db) fn f = Base.createFunction
-  db
+createFunction conn fn f = Base.createFunction
+  (connectionHandle conn)
   (Base.Utf8 $ TE.encodeUtf8 fn)
   (Just $ Base.ArgCount $ argCount (Proxy :: Proxy f))
   (deterministicFn (Proxy :: Proxy f))
@@ -65,7 +65,7 @@
     ((const :: IO () -> SomeException -> IO ()) $ Base.funcResultNull ctx))
 
 deleteFunction :: Connection -> T.Text -> IO (Either Base.Error ())
-deleteFunction (Connection db) fn = Base.deleteFunction
-  db
+deleteFunction conn fn = Base.deleteFunction
+  (connectionHandle conn)
   (Base.Utf8 $ TE.encodeUtf8 fn)
   Nothing
diff --git a/Database/SQLite/Simple/Internal.hs b/Database/SQLite/Simple/Internal.hs
--- a/Database/SQLite/Simple/Internal.hs
+++ b/Database/SQLite/Simple/Internal.hs
@@ -24,7 +24,9 @@
 import           Control.Applicative
 import           Data.ByteString (ByteString)
 import           Data.ByteString.Char8()
+import           Data.IORef
 import           Data.Typeable (Typeable)
+import           Data.Word
 import           Control.Monad.Trans.State.Strict
 import           Control.Monad.Trans.Reader
 
@@ -39,7 +41,10 @@
 -- functionality that's not exposed in the sqlite-simple API.  This
 -- should be a safe thing to do although mixing both APIs is
 -- discouraged.
-newtype Connection = Connection { connectionHandle :: Base.Database }
+data Connection = Connection
+  { connectionHandle :: {-# UNPACK #-} !Base.Database
+  , connectionTempNameCounter :: {-# UNPACK #-} !(IORef Word64)
+  }
 
 data ColumnOutOfBounds = ColumnOutOfBounds { errorColumnIndex :: !Int }
                       deriving (Eq, Show, Typeable)
diff --git a/Database/SQLite/Simple/Ok.hs b/Database/SQLite/Simple/Ok.hs
--- a/Database/SQLite/Simple/Ok.hs
+++ b/Database/SQLite/Simple/Ok.hs
@@ -19,7 +19,7 @@
 -- commonly-used type and typeclass included in @base@.
 --
 -- Extending the failure case to a list of 'SomeException's enables a
--- more sensible 'Alternative' instance definitions:   '<|>' concatinates
+-- more sensible 'Alternative' instance definitions:   '<|>' concatenates
 -- the list of exceptions when both cases fail,  and 'empty' is defined as
 -- 'Errors []'.   Though '<|>' one could pick one of two exceptions, and
 -- throw away the other,  and have 'empty' provide a generic exception,
@@ -33,6 +33,7 @@
 import Control.Applicative
 import Control.Exception
 import Control.Monad (MonadPlus(..))
+import Control.Monad.Catch (MonadThrow, throwM)
 import Data.Typeable
 
 #if !MIN_VERSION_base(4,13,0) && MIN_VERSION_base(4,9,0)
@@ -80,6 +81,9 @@
 #if MIN_VERSION_base(4,9,0)
 instance MonadFail Ok where
     fail str = Errors [SomeException (ErrorCall str)]
+
+instance MonadThrow Ok where
+    throwM = fail . show
 #endif
 
 -- | a way to reify a list of exceptions into a single exception
diff --git a/Database/SQLite/Simple/QQ.hs b/Database/SQLite/Simple/QQ.hs
--- a/Database/SQLite/Simple/QQ.hs
+++ b/Database/SQLite/Simple/QQ.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TemplateHaskellQuotes #-}
 
 ------------------------------------------------------------------------------
 -- |
@@ -27,7 +27,7 @@
 One should consider turning on the @-XQuasiQuotes@ pragma in that module:
 
 @
-{-# LANGUAGE QuasiQuoter #-}
+{-# LANGUAGE QuasiQuotes #-}
 
 myQuery = query conn [sql|
     SELECT
diff --git a/Database/SQLite/Simple/ToField.hs b/Database/SQLite/Simple/ToField.hs
--- a/Database/SQLite/Simple/ToField.hs
+++ b/Database/SQLite/Simple/ToField.hs
@@ -1,5 +1,4 @@
-{-# LANGUAGE DeriveDataTypeable, DeriveFunctor       #-}
-{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleInstances #-}
 
 ------------------------------------------------------------------------------
 -- |
diff --git a/Database/SQLite/Simple/Types.hs b/Database/SQLite/Simple/Types.hs
--- a/Database/SQLite/Simple/Types.hs
+++ b/Database/SQLite/Simple/Types.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE DeriveDataTypeable, DeriveFunctor, GeneralizedNewtypeDeriving, CPP #-}
+{-# LANGUAGE DeriveDataTypeable, CPP #-}
 
 ------------------------------------------------------------------------------
 -- |
diff --git a/README.markdown b/README.markdown
--- a/README.markdown
+++ b/README.markdown
@@ -1,7 +1,7 @@
 sqlite-simple: mid-level bindings to the sqlite database
 ========================================================
 
-[![Build Status](https://secure.travis-ci.org/nurpax/sqlite-simple.png)](http://travis-ci.org/nurpax/sqlite-simple)
+[![Build Status](https://github.com/nurpax/sqlite-simple/actions/workflows/haskell-ci.yml/badge.svg)](https://github.com/nurpax/sqlite-simple/actions/workflows/haskell-ci.yml) [![Hackage](https://img.shields.io/hackage/v/sqlite-simple.svg)](https://hackage.haskell.org/package/sqlite-simple)
 
 This library is a mid-level Haskell binding to the SQLite database.
 
@@ -95,7 +95,8 @@
 ## Credits
 
 - [Janne Hellsten](https://github.com/nurpax) author, long-term maintainer
-- [Sergey Bushnyak](https://github.com/sigrlami) current maintainer
+- [Sergey Bushnyak](https://github.com/sigrlami) long-term maintainer
+- [Joshua Chia](https://github.com/jchia) current maintainer
 
 A lot of the code is directly borrowed from
 [mysql-simple](http://github.com/bos/mysql-simple) by Bryan O'Sullivan
diff --git a/changelog b/changelog
--- a/changelog
+++ b/changelog
@@ -1,6 +1,3 @@
-0.4.18.2
-	* Depend on semigroups only for old GHC versions
-
 0.4.18.1
 	* Add generic implementation of 'FromRow' and 'ToRow'.
 
diff --git a/sqlite-simple.cabal b/sqlite-simple.cabal
--- a/sqlite-simple.cabal
+++ b/sqlite-simple.cabal
@@ -1,5 +1,5 @@
 Name:                sqlite-simple
-Version:             0.4.18.2
+Version:             0.4.19.0
 Synopsis:            Mid-Level SQLite client library
 Description:
     Mid-level SQLite client library, based on postgresql-simple.
@@ -23,7 +23,7 @@
 Build-type:          Simple
 
 Cabal-version:       >= 1.10
-
+tested-with:         GHC == 8.8.4 || == 8.10.7 || == 9.0.2 || == 9.2.8 || == 9.4.8 || == 9.6.3 || == 9.8.1
 extra-source-files:  README.markdown
                      changelog
 
@@ -51,12 +51,13 @@
     bytestring >= 0.9,
     containers,
     direct-sqlite >= 2.3.13 && < 2.4,
+    exceptions >= 0.4,
     template-haskell,
     text >= 0.11,
     time,
     transformers,
     Only >= 0.1 && < 0.1.1
-    
+
   if impl(ghc < 8.0)
     Build-depends: semigroups >= 0.18 && < 0.20
 
diff --git a/test/DirectSqlite.hs b/test/DirectSqlite.hs
--- a/test/DirectSqlite.hs
+++ b/test/DirectSqlite.hs
@@ -14,7 +14,7 @@
 testDirectSqlite TestEnv{..} = TestCase $ do
   let dsConn = connectionHandle conn
   bracket (DS.prepare dsConn "SELECT 1+1") DS.finalize testDirect
-  [Only (res :: Int)] <- query_ (Connection dsConn) "SELECT 1+2"
+  [Only (res :: Int)] <- query_ conn "SELECT 1+2"
   assertEqual "1+2" 3 res
   where
     testDirect stmt = do
diff --git a/test/Errors.hs b/test/Errors.hs
--- a/test/Errors.hs
+++ b/test/Errors.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE ScopedTypeVariables, BangPatterns #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 
 module Errors (
     testErrorsColumns
@@ -9,6 +9,7 @@
   , testErrorsTransaction
   , testErrorsImmediateTransaction
   , testErrorsExclusiveTransaction
+  , testErrorsSavepoint
   ) where
 
 import           Control.Exception
@@ -22,15 +23,11 @@
 import           Common
 import           Database.SQLite.Simple.Types (Null)
 
--- The "length (show e) `seq` .." trickery below is to force evaluate
--- the contents of error messages.  Another option would be to log
--- them (would be useful), but I don't know if HUnit has any logging
--- mechanisms.  Just printing them as is will look like the tests are
--- hitting errors and would be confusing.
-assertResultErrorCaught :: IO a -> Assertion
-assertResultErrorCaught action = do
-  catch (action >> return False) (\(e :: ResultError) -> length (show e) `seq` return True) >>=
-    assertBool "assertResultError exc"
+assertResultErrorThrown :: IO a -> ResultError -> Assertion
+assertResultErrorThrown action expectedError =
+  catch
+    (action >> assertFailure ("Expected error: " ++ show expectedError ++ ", but nothing was thrown"))
+    (\(e :: ResultError) -> assertEqual "assertResultErrorThrown" expectedError e)
 
 assertFormatErrorCaught :: IO a -> Assertion
 assertFormatErrorCaught action = do
@@ -55,54 +52,85 @@
   assertEqual "row count" 1 (length rows)
   assertEqual "string" (Only "test string") (head rows)
   -- Mismatched number of output columns (selects two, dest type has 1 field)
-  assertResultErrorCaught (query_ conn "SELECT id,t FROM cols" :: IO [Only Int])
+  assertResultErrorThrown (query_ conn "SELECT id,t FROM cols" :: IO [Only Int])
+    ConversionFailed
+      { errSQLType = "2 values: [(\"INTEGER\",\"SQLInteger 1\"),(\"TEXT\",\"SQLText \\\"test s[...]\")]"
+      , errHaskellType = "at least 1 slots in target type"
+      , errMessage = "mismatch between number of columns to convert and number in target type"
+      }
   -- Same as above but the other way round (select 1, dst has two)
-  assertResultErrorCaught (query_ conn "SELECT id FROM cols" :: IO [(Int, String)])
-  -- Mismatching types (source int,text doesn't match dst string,int
-  assertResultErrorCaught (query_ conn "SELECT id, t FROM cols" :: IO [(String, Int)])
-  -- Mismatching types (source string doesn't match dst integer
-  assertResultErrorCaught (query_ conn "SELECT 'foo'" :: IO [Only Integer])
+  assertResultErrorThrown (query_ conn "SELECT id FROM cols" :: IO [(Int, String)])
+    ConversionFailed
+      { errSQLType = "1 values: [(\"INTEGER\",\"SQLInteger 1\")]"
+      , errHaskellType = "at least 2 slots in target type"
+      , errMessage = "mismatch between number of columns to convert and number in target type"
+      }
+  -- Mismatching types (source int,text doesn't match dst string,int)
+  assertResultErrorThrown (query_ conn "SELECT id, t FROM cols" :: IO [(String, Int)])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "[Char]", errMessage = "expecting SQLText column type"}
+  -- Mismatching types (source string doesn't match dst integer)
+  assertResultErrorThrown (query_ conn "SELECT 'foo'" :: IO [Only Integer])
+    ConversionFailed {errSQLType = "TEXT", errHaskellType = "Integer", errMessage = "need an int"}
   -- Mismatching types (sources don't match destination float/double type)
-  assertResultErrorCaught (query_ conn "SELECT 1" :: IO [Only Double])
-  assertResultErrorCaught (query_ conn "SELECT 'foo'" :: IO [Only Double])
-  assertResultErrorCaught (query_ conn "SELECT 1" :: IO [Only Float])
-  assertResultErrorCaught (query_ conn "SELECT 'foo'" :: IO [Only Float])
+  assertResultErrorThrown (query_ conn "SELECT 1" :: IO [Only Double])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "Double", errMessage = "expecting an SQLFloat column type"}
+  assertResultErrorThrown (query_ conn "SELECT 'foo'" :: IO [Only Double])
+    ConversionFailed {errSQLType = "TEXT", errHaskellType = "Double", errMessage = "expecting an SQLFloat column type"}
+  assertResultErrorThrown (query_ conn "SELECT 1" :: IO [Only Float])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "Float", errMessage = "expecting an SQLFloat column type"}
+  assertResultErrorThrown (query_ conn "SELECT 'foo'" :: IO [Only Float])
+    ConversionFailed {errSQLType = "TEXT", errHaskellType = "Float", errMessage = "expecting an SQLFloat column type"}
   -- Mismatching types (sources don't match destination bool type, or is out of bounds)
-  assertResultErrorCaught (query_ conn "SELECT 'true'" :: IO [Only Bool])
-  assertResultErrorCaught (query_ conn "SELECT 2" :: IO [Only Bool])
+  assertResultErrorThrown (query_ conn "SELECT 'true'" :: IO [Only Bool])
+    ConversionFailed {errSQLType = "TEXT", errHaskellType = "Bool", errMessage = "expecting an SQLInteger column type"}
+  assertResultErrorThrown (query_ conn "SELECT 2" :: IO [Only Bool])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "Bool", errMessage = "bool must be 0 or 1, got 2"}
   -- Mismatching types (sources don't match destination string types (text, string)
-  assertResultErrorCaught (query_ conn "SELECT 1" :: IO [Only T.Text])
-  assertResultErrorCaught (query_ conn "SELECT 1" :: IO [Only LT.Text])
-  assertResultErrorCaught (query_ conn "SELECT 1.0" :: IO [Only T.Text])
-  assertResultErrorCaught (query_ conn "SELECT 1.0" :: IO [Only LT.Text])
+  assertResultErrorThrown (query_ conn "SELECT 1" :: IO [Only T.Text])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "Text", errMessage = "need a text"}
+  assertResultErrorThrown (query_ conn "SELECT 1" :: IO [Only LT.Text])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "Text", errMessage = "need a text"}
+  assertResultErrorThrown (query_ conn "SELECT 1.0" :: IO [Only T.Text])
+    ConversionFailed {errSQLType = "FLOAT", errHaskellType = "Text", errMessage = "need a text"}
+  assertResultErrorThrown (query_ conn "SELECT 1.0" :: IO [Only LT.Text])
+    ConversionFailed {errSQLType = "FLOAT", errHaskellType = "Text", errMessage = "need a text"}
   -- Mismatching types (sources don't match destination string types (time/date)
-  assertResultErrorCaught (query_ conn "SELECT 1" :: IO [Only UTCTime])
-  assertResultErrorCaught (query_ conn "SELECT 1" :: IO [Only Day])
+  assertResultErrorThrown (query_ conn "SELECT 1" :: IO [Only UTCTime])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "UTCTime", errMessage = "expecting SQLText column type"}
+  assertResultErrorThrown (query_ conn "SELECT 1" :: IO [Only Day])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "Day", errMessage = "expecting SQLText column type"}
   -- Mismatching types (sources don't match destination bytestring)
   [Only (_ :: B.ByteString)] <-  query_ conn "SELECT X'3177'"
-  assertResultErrorCaught (query_ conn "SELECT 1" :: IO [Only B.ByteString])
-  assertResultErrorCaught (query_ conn "SELECT 1" :: IO [Only LB.ByteString])
-  assertResultErrorCaught (query_ conn "SELECT 'foo'" :: IO [Only B.ByteString])
-  assertResultErrorCaught (query_ conn "SELECT 'foo'" :: IO [Only LB.ByteString])
+  assertResultErrorThrown (query_ conn "SELECT 1" :: IO [Only B.ByteString])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "ByteString", errMessage = "expecting SQLBlob column type"}
+  assertResultErrorThrown (query_ conn "SELECT 1" :: IO [Only LB.ByteString])
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "ByteString", errMessage = "expecting SQLBlob column type"}
+  assertResultErrorThrown (query_ conn "SELECT 'foo'" :: IO [Only B.ByteString])
+    ConversionFailed {errSQLType = "TEXT", errHaskellType = "ByteString", errMessage = "expecting SQLBlob column type"}
+  assertResultErrorThrown (query_ conn "SELECT 'foo'" :: IO [Only LB.ByteString])
+    ConversionFailed {errSQLType = "TEXT", errHaskellType = "ByteString", errMessage = "expecting SQLBlob column type"}
   -- Trying to get a blob into a string
   let d = B.pack ([0..127] :: [Word8])
   execute_ conn "CREATE TABLE cols_blobs (id INTEGER, b BLOB)"
   execute conn "INSERT INTO cols_blobs (id, b) VALUES (?,?)" (1 :: Int, d)
-  assertResultErrorCaught
+  assertResultErrorThrown
     (do [Only _t1] <- query conn "SELECT b FROM cols_blobs WHERE id = ?" (Only (1 :: Int)) :: IO [Only String]
         return ())
+    ConversionFailed {errSQLType = "BLOB", errHaskellType = "[Char]", errMessage = "expecting SQLText column type"}
   execute_ conn "CREATE TABLE cols_bools (id INTEGER PRIMARY KEY, b BOOLEAN)"
   -- 3 = invalid value for bool, must be 0 or 1
   execute_ conn "INSERT INTO cols_bools (b) VALUES (3)"
-  assertResultErrorCaught
+  assertResultErrorThrown
     (do [Only _t1] <- query_ conn "SELECT b FROM cols_bools" :: IO [Only Bool]
         return ())
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "Bool", errMessage = "bool must be 0 or 1, got 3"}
   [Only (nullVal :: Null)] <- query_ conn "SELECT NULL"
   False @=? nullVal == nullVal
   False @=? nullVal /= nullVal
-  assertResultErrorCaught
+  assertResultErrorThrown
     (do [Only (_t1 :: Null)] <- query_ conn "SELECT 1" :: IO [Only Null]
         return ())
+    ConversionFailed {errSQLType = "INTEGER", errHaskellType = "Null", errMessage = "data is not null"}
 
 testErrorsInvalidParams :: TestEnv -> Test
 testErrorsInvalidParams TestEnv{..} = TestCase $ do
@@ -238,6 +266,40 @@
   where
     rowExists = do
       rows <- query_ conn "SELECT t FROM etrans" :: IO [Only String]
+      case rows of
+        [Only txt] -> do
+          "foo" @=? txt
+          return True
+        [] ->
+          return False
+        _ -> error "should have only one row"
+
+testErrorsSavepoint :: TestEnv -> Test
+testErrorsSavepoint TestEnv{..} = TestCase $ do
+  execute_ conn "CREATE TABLE strans (id INTEGER PRIMARY KEY, t TEXT)"
+  v <- withSavepoint conn $ do
+    executeNamed conn "INSERT INTO strans (t) VALUES (:txt)" [":txt" := ("foo" :: String)]
+    [Only r] <- query_ conn "SELECT t FROM strans" :: IO [Only String]
+    return r
+  v @=? "foo"
+  e <- rowExists
+  True @=? e
+  execute_ conn "DELETE FROM strans"
+  e <- rowExists
+  False @=? e
+  assertFormatErrorCaught
+    (withSavepoint conn $ do
+        -- this execute should be automatically rolled back on error
+        executeNamed conn
+          "INSERT INTO strans (t) VALUES (:txt)" [":txt" := ("foo" :: String)]
+        -- intentional mistake here to hit an error & cause rollback of txn
+        executeNamed conn
+          "INSERT INTO strans (t) VALUES (:txt)" [":missing" := ("foo" :: String)])
+  e <- rowExists
+  False @=? e
+  where
+    rowExists = do
+      rows <- query_ conn "SELECT t FROM strans" :: IO [Only String]
       case rows of
         [Only txt] -> do
           "foo" @=? txt
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -52,6 +52,7 @@
     , TestLabel "Errors"    . testErrorsTransaction
     , TestLabel "Errors"    . testErrorsImmediateTransaction
     , TestLabel "Errors"    . testErrorsExclusiveTransaction
+    , TestLabel "Errors"    . testErrorsSavepoint
     , TestLabel "Utf8"      . testUtf8Simplest
     , TestLabel "Utf8"      . testBlobs
     , TestLabel "Instances" . testUserFromField
diff --git a/test/Simple.hs b/test/Simple.hs
--- a/test/Simple.hs
+++ b/test/Simple.hs
@@ -174,7 +174,7 @@
   [d] <- query conn "SELECT ?" (Only (T.append zulu "Z"))
   matchDates (zulu, d)
   where
-    matchDates (str,(Only date)) = do
+    matchDates (str, Only date) = do
       -- Remove 'T' when reading in to Haskell
       let t = read (makeReadable str) :: UTCTime
       t @=? date
@@ -198,9 +198,9 @@
   execute_ conn "CREATE TABLE utctimestz (t TIMESTAMP)"
   mapM_ (\t -> execute conn "INSERT INTO utctimestz (t) VALUES (?)" (Only t)) timestrs
   dates <- query_ conn "SELECT t from utctimestz" :: IO [Only UTCTime]
-  mapM_ matchDates (zip (timestrs) dates)
+  mapM_ matchDates (zip timestrs dates)
   where
-    matchDates (str,(Only date)) = do
+    matchDates (str, Only date) = do
       -- Remove 'T' when reading in to Haskell
       let t = read . T.unpack $ str :: UTCTime
       t @=? date
@@ -221,7 +221,7 @@
       assertEqual "UTCTime" tstr t
 
 testSimpleQueryCov :: TestEnv -> Test
-testSimpleQueryCov TestEnv{..} = TestCase $ do
+testSimpleQueryCov _ = TestCase $ do
   let str = "SELECT 1+1" :: T.Text
       q   = "SELECT 1+1" :: Query
   fromQuery q @=? str
