Copyright | (c) 2011 MailRank Inc. |
---|---|
License | BSD3 |
Maintainer | Paul Rouse <[email protected]> |
Stability | experimental |
Portability | portable |
Safe Haskell | None |
Language | Haskell2010 |
Database.MySQL.Simple
Description
A mid-level client library for the MySQL database, aimed at ease of use and high performance.
Synopsis
- data ConnectInfo = ConnectInfo {}
- data Connection
- data Query
- newtype In a = In a
- newtype VaArgs a = VaArgs a
- newtype Binary a = Binary a
- newtype Only a = Only {
- fromOnly :: a
- class Param a
- class Result a
- data FormatError
- data QueryError
- data ResultError
- connect :: ConnectInfo -> IO Connection
- defaultConnectInfo :: ConnectInfo
- close :: Connection -> IO ()
- query :: (QueryParams q, QueryResults r) => Connection -> Query -> q -> IO [r]
- query_ :: QueryResults r => Connection -> Query -> IO [r]
- fold :: (QueryParams q, QueryResults r) => Connection -> Query -> q -> a -> (a -> r -> IO a) -> IO a
- fold_ :: QueryResults r => Connection -> Query -> a -> (a -> r -> IO a) -> IO a
- forEach :: (QueryParams q, QueryResults r) => Connection -> Query -> q -> (r -> IO ()) -> IO ()
- forEach_ :: QueryResults r => Connection -> Query -> (r -> IO ()) -> IO ()
- execute :: QueryParams q => Connection -> Query -> q -> IO Int64
- execute_ :: Connection -> Query -> IO Int64
- executeMany :: QueryParams q => Connection -> Query -> [q] -> IO Int64
- insertID :: Connection -> IO Word64
- withTransaction :: Connection -> IO a -> IO a
- autocommit :: Connection -> Bool -> IO ()
- commit :: Connection -> IO ()
- rollback :: Connection -> IO ()
- formatMany :: QueryParams q => Connection -> Query -> [q] -> IO ByteString
- formatQuery :: QueryParams q => Connection -> Query -> q -> IO ByteString
- splitQuery :: ByteString -> [Builder]
- class FromField a where
- fromField :: ([Type], ByteString -> Either String a)
- class ToField a where
- toField :: a -> ByteString
Writing queries
SQL-based applications are somewhat notorious for their susceptibility to attacks through the injection of maliciously crafted data. The primary reason for widespread vulnerability to SQL injections is that many applications are sloppy in handling user data when constructing SQL queries.
This library provides a Query
type and a parameter substitution
facility to address both ease of use and security.
The Query type
A Query
is a newtype
-wrapped ByteString
. It intentionally
exposes a tiny API that is not compatible with the ByteString
API; this makes it difficult to construct queries from fragments of
strings. The query
and execute
functions require queries to be
of type Query
.
To most easily construct a query, enable GHC's OverloadedStrings
language extension and write your query as a normal literal string.
{-# LANGUAGE OverloadedStrings #-} import Database.MySQL.Simple hello :: IO Int hello = do conn <- connect defaultConnectInfo [Only i] <- query_ conn "select 2 + 2" return i
A Query
value does not represent the actual query that will be
executed, but is a template for constructing the final query.
Parameter substitution
Since applications need to be able to construct queries with parameters that change, this library provides a 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.
For example, given the following Query
template:
select * from user where first_name = ? and age > ?
And a tuple of this form:
("Boris" :: String, 37 :: Int)
The query to be executed will look like this after substitution:
select * from user where first_name = 'Boris' and age > 37
If there is a mismatch between the number of "?
" characters in
your template and the number of elements in your tuple, a
FormatError
will be thrown.
Note that the substitution functions do not attempt to parse or
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.
Type inference
Automated type inference means that you will often be able to avoid supplying explicit type signatures for the elements of a tuple. However, sometimes the compiler will not be able to infer your types. Consider a care where you write a numeric literal in a parameter tuple:
query conn "select ? + ?" (40,2)
The above query will be rejected by the compiler, because it does
not know the specific numeric types of the literals 40
and 2
.
This is easily fixed:
query conn "select ? + ?" (40 :: Double, 2 :: Double)
The same kind of problem can arise with string literals if you have
the OverloadedStrings
language extension enabled. Again, just
use an explicit type signature if this happens.
Finally, remember that the compiler must be able to infer the type of a query's results as well as its parameters. We might like the following example to work:
print =<< query_ conn "select 2 + 2"
Unfortunately, while a quick glance tells us that the result type should be a single row containing a single numeric column, the compiler has no way to infer what the types are. We can easily fix this by providing an explicit type annotation:
xs <- query_ conn "select 2 + 2" print (xs :: [Only Int])
Substituting a single parameter
Haskell lacks a single-element tuple type, so if you have just one value you want substituted into a query or a single-column result, what should you do?
The obvious approach would appear to be something like this:
instance (Param a) => QueryParam a where ...
Unfortunately, this wreaks havoc with type inference, so we take a
different tack. To represent a single value val
as a parameter,
write a singleton list [val]
, use Just
val
, or use Only
val
.
Here's an example using a singleton list:
execute conn "insert into users (first_name) values (?)" ["Nuala"]
A row of n query results is represented using an n-tuple, so
you should use Only
to represent a single-column result.
Representing a list of values
Suppose you want to write a query using an IN
clause:
select * from users where first_name in ('Anna', 'Boris', 'Carla')
In such cases, it's common for both the elements and length of the
list after the IN
keyword to vary from query to query.
To address this case, use the In
type wrapper, and use a single
"?
" character to represent the list. Omit the parentheses
around the list; these will be added for you.
Here's an example:
query conn "select * from users where first_name in ?" $ In ["Anna", "Boris", "Carla"]
If your In
-wrapped list is empty, the string "(null)"
will be
substituted instead, to ensure that your clause remains
syntactically valid.
Modifying multiple rows at once
If you know that you have many rows of data to insert into a table,
it is much more efficient to perform all the insertions in a single
multi-row INSERT
statement than individually.
The executeMany
function is intended specifically for helping
with multi-row INSERT
and UPDATE
statements. Its rules for
query substitution are different than those for execute
.
What executeMany
searches for in your Query
template is a
single substring of the form:
values (?,?,?)
The rules are as follows:
- The keyword
VALUES
is matched case insensitively. - There must be no other "
?
" characters anywhere in your template. - There must one or more "
?
" in the parentheses. - Extra white space is fine.
The last argument to executeMany
is a list of parameter
tuples. These will be substituted into the query where the (?,?)
string appears, in a form suitable for use in a multi-row INSERT
or UPDATE
.
Here is an example:
executeMany conn "insert into users (first_name,last_name) values (?,?)" [("Boris","Karloff"),("Ed","Wood")]
The query that will be executed here will look like this (reformatted for tidiness):
insert into users (first_name,last_name) values ('Boris','Karloff'),('Ed','Wood')
Extracting results
The query
and query_
functions return a list of values in the
QueryResults
typeclass. This class performs automatic extraction
and type conversion of rows from a query result.
Here is a simple example of how to extract results:
import qualified Data.Text as Text xs <- query_ conn "select name,age from users" forM_ xs $ \(name,age) -> putStrLn $ Text.unpack name ++ " is " ++ show (age :: Int)
Notice two important details about this code:
- The number of columns we ask for in the query template must
exactly match the number of elements we specify in a row of the
result tuple. If they do not match, a
ResultError
exception will be thrown. - Sometimes, the compiler needs our help in specifying types. It
can infer that
name
must be aText
, due to our use of theunpack
function. However, we have to tell it the type ofage
, as it has no other information to determine the exact type.
Handling null values
The type of a result tuple will look something like this:
(Text, Int, Int)
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
tuple.
(Text, Maybe Int, Int)
If query
encounters a NULL
in a row where the corresponding
Haskell type is not Maybe
, it will throw a ResultError
exception.
Type conversions
Conversion of SQL values to Haskell values is somewhat permissive. Here are the rules.
- For numeric types, any Haskell type that can accurately represent
all values of the given MySQL type is considered "compatible".
For instance, you can always extract a MySQL
TINYINT
column to a HaskellInt
. The HaskellFloat
type can accurately represent MySQL integer types of size up toINT24
, so it is considered compatible with those types. - A numeric compatibility check is based only on the type of a
column, not on its values. For instance, a MySQL
LONG_LONG
column will be considered incompatible with a HaskellInt8
, even if it contains the value1
. - If a numeric incompatibility is found,
query
will throw aResultError
. - The
String
andText
types are assumed to be encoded as UTF-8. If you use some other encoding, decoding may fail or give wrong results. In such cases, write anewtype
wrapper and a customResult
instance to handle your encoding.
When a user-defined type is represented by a TEXT
, BLOB
, JSON
, or
similar type of column, it can be encoded and decoded using
hooks which take or receive a ByteString
. See the classes ToField
and FromField
in the Extension hooks section below.
Types
data ConnectInfo #
Constructors
ConnectInfo | |
Fields
|
Instances
Read ConnectInfo | |
Defined in Database.MySQL.Base Methods readsPrec :: Int -> ReadS ConnectInfo # readList :: ReadS [ConnectInfo] # readPrec :: ReadPrec ConnectInfo # readListPrec :: ReadPrec [ConnectInfo] # | |
Show ConnectInfo | |
Defined in Database.MySQL.Base Methods showsPrec :: Int -> ConnectInfo -> ShowS # show :: ConnectInfo -> String # showList :: [ConnectInfo] -> ShowS # | |
Eq ConnectInfo | |
Defined in Database.MySQL.Base |
data Connection #
Connection to a MySQL database.
A query string. This type is intended to make it difficult to construct a SQL query by concatenating string fragments, as that is an extremely common way to accidentally introduce SQL injection vulnerabilities into an application.
This type is an instance of IsString
, so the easiest way to
construct a query is to enable the OverloadedStrings
language
extension and then simply write the query in double quotes.
{-# LANGUAGE OverloadedStrings #-} import Database.MySQL.Simple q :: Query q = "select ?"
The underlying type is a ByteString
, and literal Haskell strings
that contain Unicode characters will be correctly transformed to
UTF-8.
Wrap a list of values for use in an IN
clause. Replaces a
single "?
" character with a parenthesized list of rendered
values.
Example:
query c "select * from whatever where id in ?" (Only (In [3,4,5]))
Constructors
In a |
Wrap a list of values for use in a function with variable arguments.
Replaces a single "?
" character with a non-parenthesized list of
rendered values.
Example:
query conn "SELECT * FROM example_table ORDER BY field(f,?)" (Only (VaArgs [3,2,1]))
Constructors
VaArgs a |
Wrap a mostly-binary string to be escaped in hexadecimal.
Constructors
Binary a |
Instances
Functor Binary Source # | |
Read a => Read (Binary a) Source # | |
Show a => Show (Binary a) Source # | |
Eq a => Eq (Binary a) Source # | |
Ord a => Ord (Binary a) Source # | |
Defined in Database.MySQL.Simple.Types | |
Param (Binary ByteString) Source # | |
Defined in Database.MySQL.Simple.Param | |
Param (Binary ByteString) Source # | |
Defined in Database.MySQL.Simple.Param |
A single-value "collection".
This is useful if you need to supply a single parameter to a SQL query, or extract a single column from a SQL result.
Parameter example:
query c "select x from scores where x > ?" (Only
(42::Int))
Result example:
xs <- query_ c "select id from users"
forM_ xs $ \(Only
id) -> {- ... -}
Instances
Functor Only Source # | |
Read a => Read (Only a) Source # | |
Show a => Show (Only a) Source # | |
Eq a => Eq (Only a) Source # | |
Ord a => Ord (Only a) Source # | |
Param a => QueryParams (Only a) Source # | |
Defined in Database.MySQL.Simple.QueryParams Methods renderParams :: Only a -> [Action] Source # | |
Result a => QueryResults (Only a) Source # | |
Defined in Database.MySQL.Simple.QueryResults Methods convertResults :: [Field] -> [Maybe ByteString] -> Only a Source # |
A type that may be used as a single parameter to a SQL query.
A default implementation is provided for any type which is an instance
of ToField
, providing a simple mechanism for user-defined encoding
to text- or blob-like fields (including JSON
).
Instances
Param ByteString Source # | |
Defined in Database.MySQL.Simple.Param Methods render :: ByteString -> Action Source # | |
Param ByteString Source # | |
Defined in Database.MySQL.Simple.Param Methods render :: ByteString -> Action Source # | |
Param Int16 Source # | |
Param Int32 Source # | |
Param Int64 Source # | |
Param Int8 Source # | |
Param Word16 Source # | |
Param Word32 Source # | |
Param Word64 Source # | |
Param Word8 Source # | |
Param Action Source # | |
Param Null Source # | |
Param Text Source # | |
Param Text Source # | |
Param Day Source # | |
Param UTCTime Source # | |
Param TimeOfDay Source # | |
Param Integer Source # | |
Param Bool Source # | |
Param Double Source # | |
Param Float Source # | |
Param Int Source # | |
Param Word Source # | |
Param (Binary ByteString) Source # | |
Defined in Database.MySQL.Simple.Param | |
Param (Binary ByteString) Source # | |
Defined in Database.MySQL.Simple.Param | |
Param a => Param (In (Set a)) Source # | |
Param a => Param (In [a]) Source # | |
Param a => Param (VaArgs [a]) Source # | |
Param a => Param (Maybe a) Source # | |
Param [Char] Source # | |
A type that may be converted from a SQL type.
A default implementation is provided for any type which is an instance
of both FromField
and Typeable
, providing a simple mechanism for
user-defined decoding from text- or blob-like fields (including JSON
).
Instances
Exceptions
data FormatError Source #
Exception thrown if a Query
could not be formatted correctly.
This may occur if the number of '?
' characters in the query
string does not match the number of parameters provided.
Instances
Exception FormatError Source # | |
Defined in Database.MySQL.Simple Methods toException :: FormatError -> SomeException # fromException :: SomeException -> Maybe FormatError # displayException :: FormatError -> String # backtraceDesired :: FormatError -> Bool # | |
Show FormatError Source # | |
Defined in Database.MySQL.Simple Methods showsPrec :: Int -> FormatError -> ShowS # show :: FormatError -> String # showList :: [FormatError] -> ShowS # | |
Eq FormatError Source # | |
Defined in Database.MySQL.Simple |
data QueryError Source #
Exception thrown if query
is used to perform an INSERT
-like
operation, or execute
is used to perform a SELECT
-like operation.
Instances
Exception QueryError Source # | |
Defined in Database.MySQL.Simple Methods toException :: QueryError -> SomeException # fromException :: SomeException -> Maybe QueryError # displayException :: QueryError -> String # backtraceDesired :: QueryError -> Bool # | |
Show QueryError Source # | |
Defined in Database.MySQL.Simple Methods showsPrec :: Int -> QueryError -> ShowS # show :: QueryError -> String # showList :: [QueryError] -> ShowS # | |
Eq QueryError Source # | |
Defined in Database.MySQL.Simple |
data ResultError Source #
Exception thrown if conversion from a SQL value to a Haskell value fails.
Instances
Exception ResultError Source # | |
Defined in Database.MySQL.Simple.Result Methods toException :: ResultError -> SomeException # fromException :: SomeException -> Maybe ResultError # displayException :: ResultError -> String # backtraceDesired :: ResultError -> Bool # | |
Show ResultError Source # | |
Defined in Database.MySQL.Simple.Result Methods showsPrec :: Int -> ResultError -> ShowS # show :: ResultError -> String # showList :: [ResultError] -> ShowS # | |
Eq ResultError Source # | |
Defined in Database.MySQL.Simple.Result |
Connection management
connect :: ConnectInfo -> IO Connection #
Connect to a database.
defaultConnectInfo :: ConnectInfo #
Default information for setting up a connection.
Defaults are as follows:
- Server on
localhost
- User
root
- No password
- Database
test
- Character set
utf8
Use as in the following example:
connect defaultConnectInfo { connectHost = "db.example.com" }
close :: Connection -> IO () #
Close a connection, and mark any outstanding Result
as
invalid.
Queries that return results
query :: (QueryParams q, QueryResults r) => Connection -> Query -> q -> IO [r] Source #
Perform a SELECT
or other SQL query that is expected to return
results. All results are retrieved and converted before this
function returns.
When processing large results, this function will consume a lot of
client-side memory. Consider using fold
instead.
Exceptions that may be thrown:
FormatError
: the query string could not be formatted correctly.QueryError
: the result contains no columns (i.e. you should be usingexecute
instead ofquery
).ResultError
: result conversion failed.
query_ :: QueryResults r => Connection -> Query -> IO [r] Source #
A version of query
that does not perform query substitution.
Queries that stream results
Arguments
:: (QueryParams q, QueryResults r) | |
=> Connection | |
-> Query | Query template. |
-> q | Query parameters. |
-> a | Initial state for result consumer. |
-> (a -> r -> IO a) | Result consumer. |
-> IO a |
Perform a SELECT
or other SQL query that is expected to return
results. Results are streamed incrementally from the server, and
consumed via a left fold.
The result consumer must be carefully written to execute quickly. If the consumer is slow, server resources will be tied up, and other clients may not be able to update the tables from which the results are being streamed.
When dealing with small results, it may be simpler (and perhaps
faster) to use query
instead.
This fold is not strict. The stream consumer is responsible for forcing the evaluation of its result to avoid space leaks.
Exceptions that may be thrown:
FormatError
: the query string could not be formatted correctly.QueryError
: the result contains no columns (i.e. you should be usingexecute
instead ofquery
).ResultError
: result conversion failed.
Arguments
:: QueryResults r | |
=> Connection | |
-> Query | Query. |
-> a | Initial state for result consumer. |
-> (a -> r -> IO a) | Result consumer. |
-> IO a |
A version of fold
that does not perform query substitution.
Arguments
:: (QueryParams q, QueryResults r) | |
=> Connection | |
-> Query | Query template. |
-> q | Query parameters. |
-> (r -> IO ()) | Result consumer. |
-> IO () |
A version of fold
that does not transform a state value.
Arguments
:: QueryResults r | |
=> Connection | |
-> Query | Query template. |
-> (r -> IO ()) | Result consumer. |
-> IO () |
A version of forEach
that does not perform query substitution.
Statements that do not return results
execute :: QueryParams q => Connection -> Query -> q -> IO Int64 Source #
Execute an INSERT
, UPDATE
, or other SQL query that is not
expected to return results.
Returns the number of rows affected.
Throws FormatError
if the query could not be formatted correctly.
execute_ :: Connection -> Query -> IO Int64 Source #
A version of execute
that does not perform query substitution.
executeMany :: QueryParams q => Connection -> Query -> [q] -> IO Int64 Source #
Execute a multi-row INSERT
, UPDATE
, or other SQL query that is not
expected to return results.
Returns the number of rows affected.
Throws FormatError
if the query could not be formatted correctly.
insertID :: Connection -> IO Word64 #
Return the value generated for an AUTO_INCREMENT
column by the
previous INSERT
or UPDATE
statement.
Transaction handling
withTransaction :: Connection -> IO a -> IO a Source #
Execute an action inside a SQL transaction.
This function initiates a transaction with a "begin
transaction
" statement, then executes the supplied action. If
the action succeeds, the transaction will be completed with
commit
before this function returns.
If the action throws any kind of exception (not just a
MySQL-related exception), the transaction will be rolled back using
rollback
, then the exception will be rethrown.
autocommit :: Connection -> Bool -> IO () #
Turn autocommit on or off.
By default, MySQL runs with autocommit mode enabled. In this mode, as soon as you modify a table, MySQL stores your modification permanently.
commit :: Connection -> IO () #
Commit the current transaction.
rollback :: Connection -> IO () #
Roll back the current transaction.
Helper functions
formatMany :: QueryParams q => Connection -> Query -> [q] -> IO ByteString Source #
Format a query string with a variable number of rows.
This function is exposed to help with debugging and logging. Do not use it to prepare queries for execution.
The query string must contain exactly one substitution group,
identified by the SQL keyword "VALUES
" (case insensitive)
followed by an "(
" character, a series of one or more "?
"
characters separated by commas, and a ")
" character. White
space in a substitution group is permitted.
Throws FormatError
if the query string could not be formatted
correctly.
formatQuery :: QueryParams q => Connection -> Query -> q -> IO ByteString Source #
Format a query string.
This function is exposed to help with debugging and logging. Do not use it to prepare queries for execution.
String parameters are escaped according to the character set in use
on the Connection
.
Throws FormatError
if the query string could not be formatted
correctly.
splitQuery :: ByteString -> [Builder] Source #
Split a query into fragments separated by ?
characters. Does not
break a fragment if the question mark is in a string literal.
Extension hooks
These classes provide a simple mechanism for encoding and decoding user-defined types in cases where the underlying encoding is a sequence of bytes.
Example
Assuming Foo
has instances of FromJSON
, ToJSON
,
and Typeable
, its decoding and encoding can be specified like this:
instance FromField Foo where fromField = ([Database.MySQL.Base.Types.Json], Data.Aeson.eitherDecodeStrict') instance Result Foo instance ToField Foo where toField = Data.ByteString.Lazy.toStrict . Data.Aeson.encode instance Param Foo
class FromField a where Source #
A type that can be converted from a ByteString
. Any type which is
an instance of this class, and is Typeable
, can use the default
implementation of Result
. This provides a method of implementing
a decoder for any text-like column, such as TEXT
, BLOB
, or JSON
,
instead of implementing Result
directly.
The first component of the tuple returned by fromField
is a list of
acceptable column types, expressed in terms of
Type
.
Since: 0.4.8
class ToField a where Source #
A type that can be converted to a ByteString
for use as a parameter
to an SQL query.
Any type which is an instance of this class can use the default
implementation of Param
, which will wrap encodings with Escape
.
Since: 0.4.8
Methods
toField :: a -> ByteString Source #