diff --git a/Database/MySQL/Simple.hs b/Database/MySQL/Simple.hs index 03a144f..16cbb18 100644 --- a/Database/MySQL/Simple.hs +++ b/Database/MySQL/Simple.hs @@ -26,7 +26,7 @@ module Database.MySQL.Simple -- $inference -- ** Substituting a single parameter - -- $only + -- $only_param -- ** Representing a list of values -- $in @@ -34,6 +34,15 @@ module Database.MySQL.Simple -- ** Modifying multiple rows at once -- $many + -- * Extracting results + -- $result + + -- ** Handling null values + -- $null + + -- ** Type conversions + -- $types + -- * Types Base.ConnectInfo(..) , Connection @@ -244,16 +253,17 @@ finishQuery q conn = do -- | Execute an action inside a SQL transaction. -- --- You are assumed to have started the transaction yourself. --- --- If your action succeeds, the transaction will be 'Base.commit'ted --- before this function returns. +-- 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 +-- 'Base.commit' before this function returns. -- --- If your action throws any exception (not just a SQL exception), the --- transaction will be rolled back 'Base.rollback' before the --- exception is propagated. +-- If the action throws /any/ kind of exception (not just a +-- MySQL-related exception), the transaction will be rolled back using +-- 'Base.rollback', then the exception will be rethrown. withTransaction :: Connection -> IO a -> IO a withTransaction conn act = do + execute_ conn "start transaction" r <- act `onException` Base.rollback conn Base.commit conn return r @@ -358,7 +368,7 @@ fmtError msg q xs = throw FormatError { -- the @OverloadedStrings@ language extension enabled. Again, just -- use an explicit type signature if this happens. --- $only +-- $only_param -- -- Haskell lacks a single-element tuple type, so if you have just one -- value you want substituted into a query, what should you do? @@ -428,7 +438,7 @@ fmtError msg q xs = throw FormatError { -- 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@.. +-- or @UPDATE@. -- -- Here is an example: -- @@ -441,3 +451,79 @@ fmtError msg q xs = throw FormatError { -- -- > insert into users (first_name,last_name) values -- > ('Boris','Karloff'),('Ed','Wood') + +-- $result +-- +-- 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 a 'Text', due to our use of the +-- @unpack@ function. However, we have to tell it the type of @age@, +-- as it has no other information to determine the exact type. + +-- $null +-- +-- 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. + +-- $only_result +-- +-- To specify that a query returns a single-column result, use the +-- 'Only' type. +-- +-- > xs <- query_ conn "select id from users" +-- > forM_ xs $ \(Only dbid) -> {- ... -} + +-- $types +-- +-- 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 Haskell 'Int'. The Haskell 'Float' type can accurately +-- represent MySQL integer types of size up to @INT24@, so it is +-- considered compatble 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 Haskell 'Int8', +-- even if it contains the value @1@. +-- +-- * If a numeric incompatibility is found, 'query' will throw a +-- 'ResultError'. +-- +-- * The 'String' and 'Text' 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 a @newtype@ wrapper and a +-- custom 'Result' instance to handle your encoding. diff --git a/Database/MySQL/Simple/QueryParams.hs b/Database/MySQL/Simple/QueryParams.hs index e86e8ef..e297c41 100644 --- a/Database/MySQL/Simple/QueryParams.hs +++ b/Database/MySQL/Simple/QueryParams.hs @@ -8,6 +8,9 @@ -- -- The 'QueryParams' typeclass, for rendering a collection of -- parameters to a SQL query. +-- +-- Predefined instances are provided for tuples containing up to ten +-- elements. module Database.MySQL.Simple.QueryParams ( @@ -19,6 +22,9 @@ import Database.MySQL.Simple.Types (Only(..)) -- | A collection type that can be turned into a list of rendering -- 'Action's. +-- +-- Instances should use the 'render' method of the 'Param' class +-- to perform conversion of each element of the collection. class QueryParams a where renderParams :: a -> [Action] -- ^ Render a collection of values. diff --git a/Database/MySQL/Simple/QueryResults.hs b/Database/MySQL/Simple/QueryResults.hs index 4c21896..b27c4d3 100644 --- a/Database/MySQL/Simple/QueryResults.hs +++ b/Database/MySQL/Simple/QueryResults.hs @@ -10,6 +10,9 @@ -- -- The 'QueryResults' typeclass, for converting a row of results -- returned by a SQL query into a more useful Haskell representation. +-- +-- Predefined instances are provided for tuples containing up to ten +-- elements. module Database.MySQL.Simple.QueryResults ( @@ -51,6 +54,20 @@ import Database.MySQL.Simple.Types (Only(..)) -- * Ensure that any 'ResultError' that might arise is thrown -- immediately, rather than some place later in application code -- that cannot handle it. +-- +-- You can also declare Haskell types of your own to be instances of +-- 'QueryResults'. +-- +-- @ +--data User { firstName :: String, lastName :: String } +-- +--instance 'QueryResults' User where +-- 'convertResults' [fa,fb] [va,vb] = User a b +-- where !a = 'convert' fa va +-- !b = 'convert' fb vb +-- 'convertResults' fs vs = 'convertError' fs vs +-- @ + class QueryResults a where convertResults :: [Field] -> [Maybe ByteString] -> a -- ^ Convert values from a row into a Haskell collection. diff --git a/Database/MySQL/Simple/Types.hs b/Database/MySQL/Simple/Types.hs index 4505d90..34c22f0 100644 --- a/Database/MySQL/Simple/Types.hs +++ b/Database/MySQL/Simple/Types.hs @@ -50,6 +50,10 @@ instance Eq Null where -- > -- > 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. newtype Query = Query { fromQuery :: ByteString } deriving (Eq, Ord, Typeable) @@ -68,14 +72,19 @@ instance Monoid Query where mappend (Query a) (Query b) = Query (B.append a b) {-# INLINE mappend #-} --- | A single-value collection. +-- | A single-value \"collection\". -- --- This can be handy if you need to supply a single parameter to a SQL --- query. +-- This is useful if you need to supply a single parameter to a SQL +-- query, or extract a single column from a SQL result. -- --- Example: +-- Parameter example: +-- +-- @query c \"select x from scores where x > ?\" ('Only' (42::Int))@ +-- +-- Result example: -- --- @query \"select x from scores where x > ?\" ('Only' (42::Int))@ +-- @xs <- query_ c \"select id from users\" +--forM_ xs $ \\('Only' id) -> {- ... -}@ newtype Only a = Only { fromOnly :: a } deriving (Eq, Ord, Read, Show, Typeable, Functor) @@ -86,6 +95,6 @@ newtype Only a = Only { -- -- Example: -- --- > query "select * from whatever where id in ?" (In [3,4,5]) +-- > query c "select * from whatever where id in ?" (In [3,4,5]) newtype In a = In a deriving (Eq, Ord, Read, Show, Typeable, Functor)