diff --git a/Database/SQLite/Simple.hs b/Database/SQLite/Simple.hs
--- a/Database/SQLite/Simple.hs
+++ b/Database/SQLite/Simple.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, GeneralizedNewtypeDeriving, ScopedTypeVariables, GADTs #-}
 
 ------------------------------------------------------------------------------
 -- |
@@ -22,6 +22,12 @@
     -- ** Parameter substitution
     -- $subst
 
+    -- *** Positional parameters
+    -- $substpos
+
+    -- *** Named parameters
+    -- $substnamed
+
     -- *** Type inference
     -- $inference
 
@@ -49,6 +55,7 @@
   , Base.SQLData(..)
   , Statement
   , ColumnIndex(..)
+  , NamedParam(..)
     -- * Connections
   , open
   , close
@@ -57,13 +64,16 @@
     -- * Queries that return results
   , query
   , query_
+  , queryNamed
   , lastInsertRowId
     -- * Queries that stream results
   , fold
   , fold_
+  , foldNamed
     -- * Statements that do not return results
   , execute
   , execute_
+  , executeNamed
   , field
     -- * Low-level statement API for stream access and prepared statements
   , openStatement
@@ -97,6 +107,7 @@
 import           Database.SQLite.Simple.Internal
 import           Database.SQLite.Simple.Ok
 import           Database.SQLite.Simple.ToRow (ToRow(..))
+import           Database.SQLite.Simple.ToField (ToField(..))
 import           Database.SQLite.Simple.FromRow
 
 -- | An SQLite prepared statement.
@@ -106,13 +117,21 @@
 newtype ColumnIndex = ColumnIndex BaseD.ColumnIndex
     deriving (Eq, Ord, Enum, Num, Real, Integral)
 
+data NamedParam where
+    (:=) :: (ToField v) => T.Text -> v -> NamedParam
+
+infixr 3 :=
+
+instance Show NamedParam where
+  show (k := v) = show (k, toField v)
+
 -- | Exception thrown if a 'Query' was malformed.
 -- This may occur if the number of \'@?@\' characters in the query
 -- string does not match the number of parameters provided.
 data FormatError = FormatError {
-      fmtMessage :: String
-    , fmtQuery :: Query
-    , fmtParams :: [String]
+      fmtMessage :: !String
+    , fmtQuery   :: !Query
+    , fmtParams  :: ![String]
     } deriving (Eq, Show, Typeable)
 
 instance Exception FormatError
@@ -176,6 +195,30 @@
                     templ qp
         Nothing -> return ()
 
+-- | Binds named parameters to a prepared statement.
+bindNamed :: Statement -> [NamedParam] -> IO ()
+bindNamed (Statement stmt) params = do
+  stmtParamCount <- Base.bindParameterCount stmt
+  when (length params /= fromIntegral stmtParamCount) $ throwColumnMismatch stmtParamCount
+  bind stmt params
+  where
+    bind stmt params =
+      mapM_ (\(n := v) -> do
+              idx <- BaseD.bindParameterIndex stmt (BaseD.Utf8 . TE.encodeUtf8 $ n)
+              case idx of
+                Just i ->
+                  Base.bindSQLData stmt i (toField v)
+                Nothing -> do
+                  templ <- getQuery stmt
+                  fmtError ("Unknown named parameter '" ++ T.unpack n ++ "'")
+                    templ params)
+            params
+
+    throwColumnMismatch nParams = do
+      templ <- getQuery stmt
+      fmtError ("SQL query contains " ++ show nParams ++ " params, but " ++
+                show (length params) ++ " arguments given") templ params
+
 -- | Resets a statement. This does not reset bound parameters, if any, but
 -- allows the statement to be reexecuted again by invoking 'nextRow'.
 reset :: Statement -> IO ()
@@ -226,20 +269,34 @@
 withStatement conn query = bracket (openStatement conn query) closeStatement
 
 -- A version of 'withStatement' which binds parameters.
-withStatementP :: (ToRow params) => Connection -> Query -> params -> (Statement -> IO a) -> IO a
-withStatementP conn template params action =
+withStatementParams :: (ToRow params)
+                       => Connection
+                       -> Query
+                       -> params
+                       -> (Statement -> IO a)
+                       -> IO a
+withStatementParams conn template params action =
   withStatement conn template $ \stmt ->
     -- Don't use withBind here, there is no need to reset the parameters since
     -- we're destroying the statement
     bind stmt (toRow params) >> action stmt
 
+-- A version of 'withStatement' which binds named parameters.
+withStatementNamedParams :: Connection
+                         -> Query
+                         -> [NamedParam]
+                         -> (Statement -> IO a)
+                         -> IO a
+withStatementNamedParams conn template namedParams action =
+  withStatement conn template $ \stmt -> bindNamed stmt namedParams >> action stmt
+
 -- | Execute an @INSERT@, @UPDATE@, or other SQL query that is not
 -- expected to return results.
 --
 -- Throws 'FormatError' if the query could not be formatted correctly.
 execute :: (ToRow q) => Connection -> Query -> q -> IO ()
 execute conn template qs =
-  withStatementP conn template qs $ \(Statement stmt) ->
+  withStatementParams conn template qs $ \(Statement stmt) ->
     void . Base.step $ stmt
 
 
@@ -262,7 +319,7 @@
 query :: (ToRow q, FromRow r)
          => Connection -> Query -> q -> IO [r]
 query conn templ qs =
-  withStatementP conn templ qs $ \stmt ->
+  withStatementParams conn templ qs $ \stmt ->
     doFoldToList stmt
 
 -- | A version of 'query' that does not perform query substitution.
@@ -270,12 +327,25 @@
 query_ conn query =
   withStatement conn query doFoldToList
 
+-- | A version of 'query' where the query parameters (placeholders)
+-- are named.
+queryNamed :: (FromRow r) => Connection -> Query -> [NamedParam] -> IO [r]
+queryNamed conn templ params =
+  withStatementNamedParams conn templ params $ \stmt -> doFoldToList stmt
+
 -- | A version of 'execute' that does not perform query substitution.
 execute_ :: Connection -> Query -> IO ()
 execute_ conn template =
   withStatement conn template $ \(Statement stmt) ->
     void $ Base.step stmt
 
+-- | A version of 'execute' where the query parameters (placeholders)
+-- are named.
+executeNamed :: Connection -> Query -> [NamedParam] -> IO ()
+executeNamed conn template params =
+  withStatementNamedParams conn template params $ \(Statement stmt) ->
+    void $ Base.step stmt
+
 -- | Perform a @SELECT@ or other SQL query that is expected to return results.
 -- Results are converted and fed into the 'action' callback as they are being
 -- retrieved from the database.
@@ -296,7 +366,7 @@
         -> (a -> row -> IO a)
         -> IO a
 fold conn query params initalState action =
-  withStatementP conn query params $ \stmt ->
+  withStatementParams conn query params $ \stmt ->
     doFold stmt initalState action
 
 -- | A version of 'fold' which does not perform parameter substitution.
@@ -310,6 +380,19 @@
   withStatement conn query $ \stmt ->
     doFold stmt initalState action
 
+-- | A version of 'fold' where the query parameters (placeholders) are
+-- named.
+foldNamed :: ( FromRow row )
+          => Connection
+          -> Query
+          -> [NamedParam]
+          -> a
+          -> (a -> row -> IO a)
+          -> IO a
+foldNamed conn query params initalState action =
+  withStatementNamedParams conn query params $ \stmt ->
+    doFold stmt initalState action
+
 doFold :: (FromRow row) => Statement ->  a -> (a -> row -> IO a) -> IO a
 doFold stmt initState action =
   loop initState
@@ -369,12 +452,13 @@
 lastInsertRowId :: Connection -> IO Int64
 lastInsertRowId (Connection c) = BaseD.lastInsertRowId c
 
-fmtError :: String -> Query -> [Base.SQLData] -> a
-fmtError msg q xs = throw FormatError {
-                      fmtMessage = msg
-                    , fmtQuery = q
-                    , fmtParams = map show xs
-                    }
+fmtError :: Show v => String -> Query -> [v] -> a
+fmtError msg q xs =
+  throw FormatError {
+      fmtMessage  = msg
+    , fmtQuery    = q
+    , fmtParams   = map show xs
+    }
 
 getQuery :: Base.Statement -> IO Query
 getQuery stmt =
@@ -448,16 +532,28 @@
 -- parameters that change, this library uses SQLite's parameter
 -- binding query substitution capability.
 --
--- The 'Query' template accepted by 'query' and 'execute' can contain
--- any number of \"@?@\" characters.  Both 'query' and 'execute'
--- accept a third argument, typically a tuple. When constructing the
--- real query to execute, these functions replace the first \"@?@\" in
--- the template with the first element of the tuple, the second
--- \"@?@\" with the second element, and so on. If necessary, each
--- tuple element will be quoted and escaped prior to substitution;
--- this defeats the single most common injection vector for malicious
--- data.
+-- This library restricts parameter substitution to work only with
+-- named parameters and positional arguments with the \"@?@\" syntax.
+-- The API does not support for mixing these two types of bindings.
+-- Unsupported parameters will be rejected and a 'FormatError' will be
+-- thrown.
 --
+-- You should always use parameter substitution instead of inlining
+-- your dynamic parameters into your queries with messy string
+-- concatenation.  SQLite will automatically quote and escape your
+-- data into these placeholder parameters; this defeats the single
+-- most common injection vector for malicious data.
+
+-- $substpos
+--
+-- The 'Query' template accepted by 'query', 'execute' and 'fold' can
+-- contain any number of \"@?@\" characters.  Both 'query' and
+-- 'execute' accept a third argument, typically a tuple. When the
+-- query executes, the first \"@?@\" in the template will be replaced
+-- with the first element of the tuple, the second \"@?@\" with the
+-- second element, and so on.  This substitution happens inside the
+-- native SQLite implementation.
+--
 -- For example, given the following 'Query' template:
 --
 -- > select * from user where first_name = ? and age > ?
@@ -478,11 +574,34 @@
 -- validate your query. It's up to you to write syntactically valid
 -- SQL, and to ensure that each \"@?@\" in your query template is
 -- matched with the right tuple element.
+
+-- $substnamed
 --
--- This library restricts parameter substitution to work only with
--- \"@?@\" characters.  SQLite natively supports several other
--- syntaxes for binding query parameters.  Unsupported parameters will
--- be rejected and a 'FormatError' will be thrown.
+-- Named parameters are accepted by 'queryNamed', 'executeNamed' and
+-- 'foldNamed'.  These functions take a list of 'NamedParam's which
+-- are key-value pairs binding a value to an argument name.  As is the
+-- case with \"@?@\" parameters, named parameters are automatically
+-- escaped by the SQLite library.  The parameter names are prefixed
+-- with either @:@ or @\@@, e.g. @:foo@ or @\@foo@.
+--
+-- Example:
+--
+-- @
+-- r \<- 'queryNamed' c \"SELECT id,text FROM posts WHERE id = :id AND date >= :date\" [\":id\" ':=' postId, \":date\" := afterDate]
+-- @
+--
+-- Note that you can mix different value types in the same list.
+-- E.g., the following is perfectly legal:
+--
+-- @
+-- [\":id\" := (3 :: Int), \":str\" := (\"foo\" :: String)]
+-- @
+--
+-- The parameter name (or key) in the 'NamedParam' must match exactly
+-- the name written in the SQL query.  E.g., if you used @:foo@ in
+-- your SQL statement, you need to use @\":foo\"@ as the parameter
+-- key, not @\"foo\"@.  Some libraries like Python's sqlite3
+-- automatically drop the @:@ character from the name.
 
 -- $inference
 --
@@ -516,6 +635,8 @@
 --
 -- > execute conn "insert into users (first_name) values (?)"
 -- >              ["Nuala"]
+--
+-- Or you can use named parameters which do not have this restriction.
 
 -- $result
 --
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
@@ -39,11 +39,12 @@
 import           Data.ByteString (ByteString)
 import qualified Data.ByteString.Char8 as B
 import qualified Data.ByteString.Lazy as LB
-import           Data.Int (Int16, Int32, Int64)
+import           Data.Int (Int8, Int16, Int32, Int64)
 import           Data.Time (UTCTime, Day)
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as LT
 import           Data.Typeable (Typeable, typeOf)
+import           Data.Word (Word, Word8, Word16, Word32, Word64)
 import           GHC.Float (double2Float)
 
 import           Database.SQLite3 as Base
@@ -54,18 +55,18 @@
 
 -- | Exception thrown if conversion from a SQL value to a Haskell
 -- value fails.
-data ResultError = Incompatible { errSQLType :: String
-                                , errHaskellType :: String
-                                , errMessage :: String }
+data ResultError = Incompatible { errSQLType :: !String
+                                , errHaskellType :: !String
+                                , errMessage :: !String }
                  -- ^ The SQL and Haskell types are not compatible.
-                 | UnexpectedNull { errSQLType :: String
-                                  , errHaskellType :: String
-                                  , errMessage :: String }
+                 | UnexpectedNull { errSQLType :: !String
+                                  , errHaskellType :: !String
+                                  , errMessage :: !String }
                  -- ^ A SQL @NULL@ was encountered when the Haskell
                  -- type did not permit it.
-                 | ConversionFailed { errSQLType :: String
-                                    , errHaskellType :: String
-                                    , errMessage :: String }
+                 | ConversionFailed { errSQLType :: !String
+                                    , errHaskellType :: !String
+                                    , errMessage :: !String }
                  -- ^ The SQL value could not be parsed, or could not
                  -- be represented as a valid Haskell value, or an
                  -- unexpected low-level error occurred (e.g. mismatch
@@ -109,6 +110,9 @@
 takeInt (Field (SQLInteger i) _) = Ok . fromIntegral $ i
 takeInt f                        = returnError ConversionFailed f "need an int"
 
+instance FromField Int8 where
+    fromField = takeInt
+
 instance FromField Int16 where
     fromField = takeInt
 
@@ -124,20 +128,35 @@
 instance FromField Integer where
     fromField = takeInt
 
+instance FromField Word8 where
+    fromField = takeInt
+
+instance FromField Word16 where
+    fromField = takeInt
+
+instance FromField Word32 where
+    fromField = takeInt
+
+instance FromField Word64 where
+    fromField = takeInt
+
+instance FromField Word where
+    fromField = takeInt
+
 instance FromField Double where
     fromField (Field (SQLFloat flt) _) = Ok flt
-    fromField f                        = returnError ConversionFailed f "need a float"
+    fromField f                        = returnError ConversionFailed f "expecting an SQLFloat column type"
 
 instance FromField Float where
     fromField (Field (SQLFloat flt) _) = Ok . double2Float $ flt
-    fromField f                        = returnError ConversionFailed f "need a float"
+    fromField f                        = returnError ConversionFailed f "expecting an SQLFloat column type"
 
 instance FromField Bool where
     fromField f@(Field (SQLInteger b) _)
       | (b == 0) || (b == 1) = Ok (b /= 0)
       | otherwise = returnError ConversionFailed f ("bool must be 0 or 1, got " ++ show b)
 
-    fromField f = returnError ConversionFailed f "need a float"
+    fromField f = returnError ConversionFailed f "expecting an SQLInteger column type"
 
 instance FromField T.Text where
     fromField (Field (SQLText txt) _) = Ok txt
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
@@ -43,7 +43,7 @@
 -- discouraged.
 newtype Connection = Connection { connectionHandle :: Base.Database }
 
-data ColumnOutOfBounds = ColumnOutOfBounds { errorColumnIndex :: Int }
+data ColumnOutOfBounds = ColumnOutOfBounds { errorColumnIndex :: !Int }
                       deriving (Eq, Show, Typeable)
 
 instance Exception ColumnOutOfBounds
diff --git a/README.markdown b/README.markdown
--- a/README.markdown
+++ b/README.markdown
@@ -23,6 +23,8 @@
 
 [![Build Status](https://secure.travis-ci.org/nurpax/sqlite-simple.png)](http://travis-ci.org/nurpax/sqlite-simple)
 
+[![Coverage Status](https://coveralls.io/repos/nurpax/sqlite-simple/badge.png?branch=master)](https://coveralls.io/r/nurpax/sqlite-simple?branch=master)
+
 Installation
 ------------
 
diff --git a/changelog b/changelog
--- a/changelog
+++ b/changelog
@@ -1,3 +1,12 @@
+0.4.6.0
+	* Add "named parameters" variants of query & al.  Named params
+	  allow queries like:
+	  res <- queryNamed conn "SELECT * FROM posts WHERE id = :id" [":id" := postId]
+	* Add FromField instances for Int8, Word, Word8, Word16, Word32
+	  and Word64.
+	* Fix typos in some type conversion error messages.
+	* Improved test coverage.
+
 0.4.5.2
 	* Build fix for GHC 7.4
 
diff --git a/sqlite-simple.cabal b/sqlite-simple.cabal
--- a/sqlite-simple.cabal
+++ b/sqlite-simple.cabal
@@ -1,13 +1,13 @@
 Name:                sqlite-simple
-Version:             0.4.5.2
+Version:             0.4.6.0
 Synopsis:            Mid-Level SQLite client library
 Description:
     Mid-level SQLite client library, based on postgresql-simple.
     .
-    Main documentation: <docs/Database-SQLite-Simple.html Database.SQLite.Simple>
+    Main documentation (with examples): <docs/Database-SQLite-Simple.html Database.SQLite.Simple>
     .
-    For more info, browse to <http://github.com/nurpax/sqlite-simple>
-    for examples & more information.
+    You can view the project page at <http://github.com/nurpax/sqlite-simple>
+    for more information.
 
 License:             BSD3
 License-file:        LICENSE
@@ -48,7 +48,7 @@
     blaze-textual,
     bytestring >= 0.9,
     containers,
-    direct-sqlite >= 2.3.4 && < 2.4,
+    direct-sqlite >= 2.3.13 && < 2.4,
     text >= 0.11,
     time,
     transformers
diff --git a/test/Common.hs b/test/Common.hs
--- a/test/Common.hs
+++ b/test/Common.hs
@@ -1,18 +1,18 @@
 
 module Common (
+    -- Note: Do not add more exports for SQLite.Simple here.  This is
+    -- so that we trap we by default export enough out of
+    -- Database.SQLite.Simple to make it useful as a single import.
     module Database.SQLite.Simple
   , module Test.HUnit
   , TestEnv(..)
 ) where
 
 import Test.HUnit
-
 import Database.SQLite.Simple
 
 data TestEnv
     = TestEnv
         { conn     :: Connection
             -- ^ Connection shared by all the tests
-        , withConn :: forall a. (Connection -> IO a) -> IO a
-            -- ^ Bracket for spawning additional connections
         }
diff --git a/test/Errors.hs b/test/Errors.hs
--- a/test/Errors.hs
+++ b/test/Errors.hs
@@ -1,8 +1,9 @@
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ScopedTypeVariables, BangPatterns #-}
 
 module Errors (
     testErrorsColumns
   , testErrorsInvalidParams
+  , testErrorsInvalidNamedParams
   , testErrorsWithStatement
   , testErrorsColumnName
   ) where
@@ -17,22 +18,22 @@
 
 assertResultErrorCaught :: IO a -> Assertion
 assertResultErrorCaught action = do
-  catch (action >> return False) (\(_ :: ResultError) -> return True) >>=
+  catch (action >> return False) (\((!_) :: ResultError) -> return True) >>=
     assertBool "assertResultError exc"
 
 assertFormatErrorCaught :: IO a -> Assertion
 assertFormatErrorCaught action = do
-  catch (action >> return False) (\(_ :: FormatError) -> return True) >>=
+  catch (action >> return False) (\((!_) :: FormatError) -> return True) >>=
     assertBool "assertFormatError exc"
 
 assertSQLErrorCaught :: IO a -> Assertion
 assertSQLErrorCaught action = do
-  catch (action >> return False) (\(_ :: SQLError) -> return True) >>=
+  catch (action >> return False) (\((!_) :: SQLError) -> return True) >>=
     assertBool "assertSQLError exc"
 
 assertOOBCaught :: IO a -> Assertion
 assertOOBCaught action = do
-  catch (action >> return False) (\(_ :: ArrayException) -> return True) >>=
+  catch (action >> return False) (\((!_) :: ArrayException) -> return True) >>=
     assertBool "assertOOBCaught exc"
 
 testErrorsColumns :: TestEnv -> Test
@@ -62,7 +63,6 @@
     (do [Only _t1] <- query_ conn "SELECT b FROM cols_bools" :: IO [Only Bool]
         return ())
 
-
 testErrorsInvalidParams :: TestEnv -> Test
 testErrorsInvalidParams TestEnv{..} = TestCase $ do
   execute_ conn "CREATE TABLE invparams (id INTEGER PRIMARY KEY, t TEXT)"
@@ -75,6 +75,16 @@
   -- execute.  This should cause an error.
   assertFormatErrorCaught
     (execute conn "INSERT INTO invparams (id, t) VALUES (?, ?)" (Only (3::Int)))
+
+testErrorsInvalidNamedParams :: TestEnv -> Test
+testErrorsInvalidNamedParams TestEnv{..} = TestCase $ do
+  -- Test that only unnamed params are accepted
+  assertFormatErrorCaught
+    (queryNamed conn "SELECT :foo" [":foox" := (1 :: Int)] :: IO [Only Int])
+  -- In this case, we have two bound params but only one given to
+  -- execute.  This should cause an error.
+  assertFormatErrorCaught
+    (queryNamed conn "SELECT :foo + :bar" [":foo" := (1 :: Int)] :: IO [Only Int])
 
 testErrorsWithStatement :: TestEnv -> Test
 testErrorsWithStatement TestEnv{..} = TestCase $ do
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -15,6 +15,7 @@
 import Statement
 import Debug
 import DirectSqlite
+import TestImports
 
 tests :: [TestEnv -> Test]
 tests =
@@ -27,12 +28,22 @@
     , TestLabel "Simple"    . testSimpleUTCTime
     , TestLabel "Simple"    . testSimpleUTCTimeTZ
     , TestLabel "Simple"    . testSimpleUTCTimeParams
+    , TestLabel "Simple"    . testSimpleQueryCov
+    , TestLabel "Simple"    . testSimpleStrings
+    , TestLabel "ParamConv" . testParamConvNull
     , TestLabel "ParamConv" . testParamConvInt
+    , TestLabel "ParamConv" . testParamConvIntWidths
+    , TestLabel "ParamConv" . testParamConvIntWidthsFromField
     , TestLabel "ParamConv" . testParamConvFloat
     , TestLabel "ParamConv" . testParamConvDateTime
     , TestLabel "ParamConv" . testParamConvBools
+    , TestLabel "ParamConv" . testParamConvToRow
+    , TestLabel "ParamConv" . testParamConvFromRow
+    , TestLabel "ParamConv" . testParamConvComposite
+    , TestLabel "ParamConv" . testParamNamed
     , TestLabel "Errors"    . testErrorsColumns
     , TestLabel "Errors"    . testErrorsInvalidParams
+    , TestLabel "Errors"    . testErrorsInvalidNamedParams
     , TestLabel "Errors"    . testErrorsWithStatement
     , TestLabel "Errors"    . testErrorsColumnName
     , TestLabel "Utf8"      . testUtf8Simplest
@@ -44,6 +55,7 @@
     , TestLabel "Statement" . testPreparedStatements
     , TestLabel "Debug"     . testDebugTracing
     , TestLabel "Direct"    . testDirectSqlite
+    , TestLabel "Imports"   . testImports
     ]
 
 -- | Action for connecting to the database that will be used for testing.
@@ -55,11 +67,7 @@
 
 withTestEnv :: (TestEnv -> IO a) -> IO a
 withTestEnv cb =
-    withConn $ \conn ->
-        cb TestEnv
-            { conn     = conn
-            , withConn = withConn
-            }
+  withConn $ \conn -> cb TestEnv { conn = conn }
   where
     withConn = bracket testConnect close
 
diff --git a/test/ParamConv.hs b/test/ParamConv.hs
--- a/test/ParamConv.hs
+++ b/test/ParamConv.hs
@@ -1,12 +1,24 @@
+{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-}
 
 module ParamConv (
-    testParamConvInt
+    testParamConvNull
+  , testParamConvInt
+  , testParamConvIntWidths
+  , testParamConvIntWidthsFromField
   , testParamConvFloat
   , testParamConvBools
-  , testParamConvDateTime) where
+  , testParamConvDateTime
+  , testParamConvFromRow
+  , testParamConvToRow
+  , testParamConvComposite
+  , testParamNamed) where
 
-import Data.Int
-import Data.Time
+import           Control.Applicative
+import           Data.Int
+import           Data.Word
+import           Data.Time
+import qualified Data.Text as T
+import           Database.SQLite.Simple.Types (Null(..))
 
 import Common
 
@@ -15,16 +27,31 @@
 two   = 2
 three = 3
 
+testParamConvNull :: TestEnv -> Test
+testParamConvNull TestEnv{..} = TestCase $ do
+  execute_ conn "CREATE TABLE nulltype (id INTEGER PRIMARY KEY, t1 TEXT)"
+  [Only r] <- (query_ conn "SELECT NULL") :: IO [Only Null]
+  execute conn "INSERT INTO nulltype (id, t1) VALUES (?,?)" (one, r)
+  [Only mr1] <- query_ conn "SELECT t1 FROM nulltype WHERE id = 1" :: IO [Only (Maybe String)]
+  assertEqual "nulls" Nothing mr1
+  execute conn "INSERT INTO nulltype (id, t1) VALUES (?,?)" (two, "foo" :: String)
+  [mr2] <- query_ conn "SELECT t1 FROM nulltype WHERE id = 2" :: IO [Only (Maybe String)]
+  assertEqual "nulls" (Just "foo") (fromOnly mr2)
+
 testParamConvInt :: TestEnv -> Test
 testParamConvInt TestEnv{..} = TestCase $ do
   [Only r] <- (query conn "SELECT ?" (Only one)) :: IO [Only Int]
   assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only one)) :: IO [Only Integer]
+  assertEqual "value" 1 r
   [Only r] <- (query conn "SELECT ?+?" (one, two)) :: IO [Only Int]
   assertEqual "value" 3 r
   [Only r] <- (query conn "SELECT ?+?" (one, 15 :: Int64)) :: IO [Only Int]
   assertEqual "value" 16 r
   [Only r] <- (query conn "SELECT ?+?" (two, 14 :: Int32)) :: IO [Only Int]
   assertEqual "value" 16 r
+  [Only r] <- (query conn "SELECT ?+?" (two, 14 :: Integer)) :: IO [Only Int]
+  assertEqual "value" 16 r
   -- This overflows 32-bit ints, verify that we get more than 32-bits out
   [Only r] <- (query conn "SELECT 255*?" (Only (0x7FFFFFFF :: Int32))) :: IO [Only Int64]
   assertEqual "> 32-bit result"
@@ -41,6 +68,56 @@
   [Only r] <- (query conn "SELECT ?") (Only (Just three :: Maybe Int)) :: IO [Only (Maybe Int)]
   assertEqual "should see 4" (Just 3) r
 
+testParamConvIntWidths :: TestEnv -> Test
+testParamConvIntWidths TestEnv{..} = TestCase $ do
+  [Only r] <- (query conn "SELECT ?" (Only (1 :: Int8))) :: IO [Only Int]
+  assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only (257 :: Int8))) :: IO [Only Int] -- wrap around
+  assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only (257 :: Int16))) :: IO [Only Int]
+  assertEqual "value" 257 r
+  [Only r] <- (query conn "SELECT ?" (Only (258 :: Int32))) :: IO [Only Int]
+  assertEqual "value" 258 r
+  [Only r] <- (query conn "SELECT ?" (Only (1 :: Word8))) :: IO [Only Int]
+  assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only (257 :: Word8))) :: IO [Only Int] -- wrap around
+  assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only (257 :: Word16))) :: IO [Only Int]
+  assertEqual "value" 257 r
+  [Only r] <- (query conn "SELECT ?" (Only (257 :: Word32))) :: IO [Only Int]
+  assertEqual "value" 257 r
+  [Only r] <- (query conn "SELECT ?" (Only (0x100000000 :: Word64))) :: IO [Only Int]
+  assertEqual "value" 0x100000000 r
+  [Only r] <- (query conn "SELECT ?" (Only (1 :: Integer))) :: IO [Only Int]
+  assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only (1 :: Word))) :: IO [Only Int]
+  assertEqual "value" 1 r
+
+testParamConvIntWidthsFromField :: TestEnv -> Test
+testParamConvIntWidthsFromField TestEnv{..} = TestCase $ do
+  [Only r] <- (query conn "SELECT ?" (Only (1 :: Int))) :: IO [Only Int8]
+  assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only (257 :: Int))) :: IO [Only Int8] -- wrap around
+  assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only (65536 :: Int))) :: IO [Only Int16] -- wrap around
+  assertEqual "value" 0 r
+  [Only r] <- (query conn "SELECT ?" (Only (65536 :: Int))) :: IO [Only Int32] -- wrap around
+  assertEqual "value" 65536 r
+  [Only r] <- (query conn "SELECT ?" (Only (258 :: Int))) :: IO [Only Int32]
+  assertEqual "value" 258 r
+  [Only r] <- (query conn "SELECT ?" (Only (1 :: Int))) :: IO [Only Word8]
+  assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only (257 :: Int))) :: IO [Only Word8] -- wrap around
+  assertEqual "value" 1 r
+  [Only r] <- (query conn "SELECT ?" (Only (257 :: Int))) :: IO [Only Word16]
+  assertEqual "value" 257 r
+  [Only r] <- (query conn "SELECT ?" (Only (257 :: Int))) :: IO [Only Word32]
+  assertEqual "value" 257 r
+  [Only r] <- (query conn "SELECT ?" (Only (0x100000000 :: Int64))) :: IO [Only Word64]
+  assertEqual "value" 0x100000000 r
+  [Only r] <- (query conn "SELECT ?" (Only (1 :: Int))) :: IO [Only Word]
+  assertEqual "value" 1 r
+
 testParamConvFloat :: TestEnv -> Test
 testParamConvFloat TestEnv{..} = TestCase $ do
   [Only r] <- query conn "SELECT ?" (Only (1.0 :: Double)) :: IO [Only Double]
@@ -79,3 +156,89 @@
   assertEqual "bool" True r3
   assertEqual "bool" False r4
   assertEqual "bool" False r5
+
+testParamConvFromRow :: TestEnv -> Test
+testParamConvFromRow TestEnv{..} = TestCase $ do
+  [(1,2)] <- query_ conn "SELECT 1,2" :: IO [(Int,Int)]
+  [(1,2,3)] <- query_ conn "SELECT 1,2,3" :: IO [(Int,Int,Int)]
+  [(1,2,3,4)] <- query_ conn "SELECT 1,2,3,4" :: IO [(Int,Int,Int,Int)]
+  [(1,2,3,4,5)] <- query_ conn "SELECT 1,2,3,4,5" :: IO [(Int,Int,Int,Int,Int)]
+  [(1,2,3,4,5,6)] <- query_ conn "SELECT 1,2,3,4,5,6" :: IO [(Int,Int,Int,Int,Int,Int)]
+  [(1,2,3,4,5,6,7)] <- query_ conn "SELECT 1,2,3,4,5,6,7" :: IO [(Int,Int,Int,Int,Int,Int,Int)]
+  [(1,2,3,4,5,6,7,8)] <- query_ conn "SELECT 1,2,3,4,5,6,7,8" :: IO [(Int,Int,Int,Int,Int,Int,Int,Int)]
+  [(1,2,3,4,5,6,7,8,9)] <- query_ conn "SELECT 1,2,3,4,5,6,7,8,9" :: IO [(Int,Int,Int,Int,Int,Int,Int,Int,Int)]
+  [(1,2,3,4,5,6,7,8,9,10)] <- query_ conn "SELECT 1,2,3,4,5,6,7,8,9,10" :: IO [(Int,Int,Int,Int,Int,Int,Int,Int,Int,Int)]
+  [[1,2,3]] <- query_ conn "SELECT 1,2,3" :: IO [[Int]]
+  return ()
+
+testParamConvToRow :: TestEnv -> Test
+testParamConvToRow TestEnv{..} = TestCase $ do
+  [Only (s :: Int)] <- query conn "SELECT 13" ()
+  13 @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?" (Only one)
+  1 @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?+?" (one, two)
+  (1+2) @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?+?+?" (one, two, three)
+  (1+2+3) @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?+?+?+?" (one, two, three, 4 :: Int)
+  (1+2+3+4) @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?+?+?+?+?" (one, two, three, 4 :: Int, 5 :: Int)
+  (1+2+3+4+5) @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?+?+?+?+?+?" (one, two, three, 4 :: Int, 5 :: Int, 6 :: Int)
+  (1+2+3+4+5+6) @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?+?+?+?+?+?+?"
+                         (one, two, three, 4 :: Int, 5 :: Int, 6 :: Int, 7 :: Int)
+  (1+2+3+4+5+6+7) @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?+?+?+?+?+?+?+?"
+                         (one, two, three, 4 :: Int, 5 :: Int, 6 :: Int, 7 :: Int, 8 :: Int)
+  (1+2+3+4+5+6+7+8) @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?+?+?+?+?+?+?+?+?"
+                         (one, two, three, 4 :: Int, 5 :: Int, 6 :: Int, 7 :: Int, 8 :: Int, 9 :: Int)
+  (1+2+3+4+5+6+7+8+9) @=? s
+  [Only (s :: Int)] <- query conn "SELECT ?+?+?+?+?+?+?+?+?+?"
+                         (one, two, three, 4 :: Int, 5 :: Int, 6 :: Int, 7 :: Int, 8 :: Int, 9 :: Int, 10 :: Int)
+  (1+2+3+4+5+6+7+8+9+10) @=? s
+
+data TestTuple  = TestTuple  Int64 Int64 deriving (Eq, Show)
+data TestTuple2 = TestTuple2 T.Text T.Text deriving (Eq, Show)
+
+instance FromRow TestTuple where
+  fromRow = TestTuple <$> field <*> field
+
+instance FromRow TestTuple2 where
+  fromRow = TestTuple2 <$> field <*> field
+
+instance ToRow TestTuple where
+  toRow (TestTuple a b) = [SQLInteger a, SQLInteger b]
+
+instance ToRow TestTuple2 where
+  toRow (TestTuple2 a b) = [SQLText a, SQLText b]
+
+testParamConvComposite :: TestEnv -> Test
+testParamConvComposite TestEnv{..} = TestCase $ do
+  [t1] <- query_ conn "SELECT 1,2"
+  TestTuple 1 2 @=? t1
+  [t2] <- query_ conn "SELECT 'foo','bar'"
+  TestTuple2 "foo" "bar" @=? t2
+  [a :. b] <- query_ conn "SELECT 4,5,'baz','xyzz'"
+  TestTuple 4 5 :. TestTuple2 "baz" "xyzz" @=? a :. b
+  [TestTuple x y :. TestTuple2 z w] <- query conn "SELECT ?,?,?,?" (a :. b)
+  x @=? 4
+  y @=? 5
+  z @=? "baz"
+  w @=? "xyzz"
+
+testParamNamed :: TestEnv -> Test
+testParamNamed TestEnv{..} = TestCase $ do
+  [Only t1] <- queryNamed conn "SELECT :foo / :bar" [":foo" := two, ":bar" := one]
+  t1 @=? (2 :: Int)
+  [(t1,t2)] <- queryNamed conn "SELECT :foo,:bar" [":foo" := ("foo" :: T.Text), ":bar" := one]
+  t1 @=? ("foo" :: T.Text)
+  t2 @=? one
+  execute_ conn "CREATE TABLE np (id INTEGER PRIMARY KEY, b BOOLEAN)"
+  executeNamed conn "INSERT INTO np (b) VALUES (:b)" [":b" := True]
+  [Only t1] <- query_ conn "SELECT b FROM np"
+  True @=? t1
+
+
diff --git a/test/Simple.hs b/test/Simple.hs
--- a/test/Simple.hs
+++ b/test/Simple.hs
@@ -10,12 +10,18 @@
   , testSimpleUTCTime
   , testSimpleUTCTimeTZ
   , testSimpleUTCTimeParams
+  , testSimpleQueryCov
+  , testSimpleStrings
   ) where
 
 import qualified Data.Text as T
-import Data.Time (UTCTime, Day)
-import Common
+import qualified Data.Text.Lazy as LT
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as LBS
+import           Data.Time (UTCTime, Day)
 
+import           Common
+
 -- Simplest SELECT
 testSimpleOnePlusOne :: TestEnv -> Test
 testSimpleOnePlusOne TestEnv{..} = TestCase $ do
@@ -195,3 +201,30 @@
       let utct = read . T.unpack $ tstr :: UTCTime
       [Only t] <- query conn "SELECT ?" (Only utct) :: IO [Only T.Text]
       assertEqual "UTCTime" tstr t
+
+testSimpleQueryCov :: TestEnv -> Test
+testSimpleQueryCov TestEnv{..} = TestCase $ do
+  let str = "SELECT 1+1" :: T.Text
+      q   = "SELECT 1+1" :: Query
+  fromQuery q @=? str
+  q @=? q
+  True @=? q <= q
+
+testSimpleStrings :: TestEnv -> Test
+testSimpleStrings TestEnv{..} = TestCase $ do
+  [Only s] <- query_ conn "SELECT 'str1'"  :: IO [Only T.Text]
+  s @=? "str1"
+  [Only s] <- query_ conn "SELECT 'strLazy'"  :: IO [Only LT.Text]
+  s @=? "strLazy"
+  [Only s] <- query conn "SELECT ?" (Only ("strP" :: T.Text)) :: IO [Only T.Text]
+  s @=? "strP"
+  [Only s] <- query conn "SELECT ?" (Only ("strPLazy" :: LT.Text)) :: IO [Only T.Text]
+  s @=? "strPLazy"
+  -- ByteStrings are blobs in sqlite storage, so use ByteString for
+  -- both input and output
+  [Only s] <- query conn "SELECT ?" (Only ("strBsP" :: BS.ByteString)) :: IO [Only BS.ByteString]
+  s @=? "strBsP"
+  [Only s] <- query conn "SELECT ?" (Only ("strBsPLazy" :: LBS.ByteString)) :: IO [Only BS.ByteString]
+  s @=? "strBsPLazy"
+  [Only s] <- query conn "SELECT ?" (Only ("strBsPLazy2" :: BS.ByteString)) :: IO [Only LBS.ByteString]
+  s @=? "strBsPLazy2"
diff --git a/test/TestImports.hs b/test/TestImports.hs
--- a/test/TestImports.hs
+++ b/test/TestImports.hs
@@ -1,30 +1,45 @@
 
-module TestImports where
+module TestImports (
+    testImports
+  ) where
 
 -- Test file to test that we can do most things with a single import
 import           Control.Applicative
 import qualified Data.Text as T
-import           Database.SQLite.Simple
 
+import           Common
+
 data TestType = TestType Int Int Int
 
 -- Hook up sqlite-simple to know how to read Test rows
 instance FromRow TestType where
   fromRow = TestType <$> field <*> field <*> field
 
-foo :: IO ()
-foo = do
+test1 :: IO ()
+test1 = do
   conn <- open ":memory:"
-  [Only _v] <- query_ conn "SELECT * FROM test" :: IO [Only Int]
-  [_v] <- query_ conn "SELECT * FROM test" :: IO [(Int,Int)]
-  [_v] <- query_ conn "SELECT * FROM test" :: IO [TestType]
-  [_v] <- query conn "SELECT ?+?" (3::Int,4::Int):: IO [(Only Int)]
+  execute_ conn "CREATE TABLE testimp (id INTEGER PRIMARY KEY, id2 INTEGER, id3 INTEGER)"
+  execute_ conn "INSERT INTO testimp (id, id2, id3) VALUES (1, 2, 3)"
+  [_v] <- query_ conn "SELECT * FROM testimp" :: IO [TestType]
+  [_v] <- query conn "SELECT ?+?" (3::Int,4::Int) :: IO [(Only Int)]
   close conn
 
-foo2 :: IO ()
-foo2 = do
-  conn <- open ":memory:"
+test2 :: Connection -> IO ()
+test2 conn = do
+  execute_ conn "CREATE TABLE testimp (id INTEGER PRIMARY KEY)"
+  execute_ conn "INSERT INTO testimp (id) VALUES (1)"
   [Only _v] <- query_ conn (Query q) :: IO [Only Int]
-  close conn
+  return ()
   where
-    q = T.concat ["SELECT * FROM ", "test"]
+    q = T.concat ["SELECT * FROM ", "testimp"]
+
+test3 :: Connection -> IO ()
+test3 conn = do
+  [_v] <- query conn "SELECT ?+?" (3::Int,4::Int) :: IO [(Only Int)]
+  return ()
+
+testImports :: TestEnv -> Test
+testImports env = TestCase $ do
+  test1
+  withConnection ":memory:" test2
+  test3 (conn env)
diff --git a/test/UserInstances.hs b/test/UserInstances.hs
--- a/test/UserInstances.hs
+++ b/test/UserInstances.hs
@@ -26,8 +26,8 @@
   execute_ conn "CREATE TABLE fromfield (t TEXT)"
   execute conn "INSERT INTO fromfield (t) VALUES (?)" (Only ("test string" :: String))
   [Only r] <- query_ conn "SELECT t FROM fromfield" :: IO [(Only MyType)]
-  assertEqual "fromField" (MyType "fromField test string") r
+  (MyType "fromField test string") @=? r
   execute_ conn "DELETE FROM fromfield"
   execute conn "INSERT INTO fromfield (t) VALUES (?)" (Only (MyType "test2"))
   [Only r] <- query_ conn "SELECT t FROM fromfield" :: IO [(Only String)]
-  assertEqual "toield" "toField test2" r
+  "toField test2" @=? r
