Commits

Bryan O'Sullivan committed e3ed4df

More doc improvement.

  • Participants
  • Parent commits 1db298b

Comments (0)

Files changed (3)

Database/MySQL/Simple.hs

 
 module Database.MySQL.Simple
     (
-      FormatError(fmtMessage, fmtQuery, fmtParams)
+    -- * Types
+      Query
     , Only(..)
-    , Query
+    -- ** Exceptions
+    , FormatError(fmtMessage, fmtQuery, fmtParams)
+    , QueryError(qeMessage, qeQuery)
+    , ResultError(errSQLType, errHaskellType, errMessage)
+    -- * Connection management
+    , Base.connect
+    , Base.defaultConnectInfo
+    , Base.close
+    -- * Queries that return results
+    , query
+    , query_
+    -- * Statements that do not return results
     , execute
     , execute_
-    , query
-    , query_
+    , Base.insertID
+    -- * Transaction handling
+    , withTransaction
+    , Base.autocommit
+    , Base.commit
+    , Base.rollback
+    -- * Helper functions
     , formatQuery
     ) where
 
 import Blaze.ByteString.Builder (fromByteString, toByteString)
 import Control.Applicative ((<$>), pure)
-import Control.Exception (Exception, throw)
+import Control.Exception (Exception, onException, throw)
 import Control.Monad.Fix (fix)
 import Data.ByteString (ByteString)
 import Data.Int (Int64)
 import Database.MySQL.Simple.Param (Action(..), inQuotes)
 import Database.MySQL.Simple.QueryParams (QueryParams(..))
 import Database.MySQL.Simple.QueryResults (QueryResults(..))
+import Database.MySQL.Simple.Result (ResultError(..))
 import Database.MySQL.Simple.Types (Only(..), Query(..))
 import qualified Data.ByteString.Char8 as B
 import qualified Database.MySQL.Base as Base
 
+-- | 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.
 data FormatError = FormatError {
       fmtMessage :: String
     , fmtQuery :: Query
 
 instance Exception FormatError
 
+-- | Exception thrown if 'query' is used to perform an @INSERT@-like
+-- operation, or 'execute' is used to perform a @SELECT@-like operation.
+data QueryError = QueryError {
+      qeMessage :: String
+    , qeQuery :: Query
+    } deriving (Eq, Show, Typeable)
+
+instance Exception QueryError
+
+-- | Format a query string.
+--
+-- String parameters are escaped according to the character set in use
+-- on the 'Connection'.
+--
+-- Exceptions that may be thrown:
+--
+-- * 'FormatError': the query string could not be formatted correctly.
+--
+-- * 'QueryError': the result contains a non-zero number of columns
+--   (i.e. you should be using 'query' instead of 'execute').
 formatQuery :: QueryParams q => Connection -> Query -> q -> IO ByteString
 formatQuery conn q@(Query template) qs
     | null xs && '?' `B.notElem` template = return template
                                   " '?' characters, but " ++
                                   show (length xs) ++ " parameters") q xs
 
+-- | 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 string could not be formatted correctly.
 execute :: (QueryParams q) => Connection -> Query -> q -> IO Int64
 execute conn template qs = do
   Base.query conn =<< formatQuery conn template qs
-  finishExecute conn
+  finishExecute template conn
 
+-- | A version of 'execute' that does not perform query substitution.
 execute_ :: Connection -> Query -> IO Int64
-execute_ conn (Query stmt) = do
+execute_ conn q@(Query stmt) = do
   Base.query conn stmt
-  finishExecute conn
+  finishExecute q conn
 
-finishExecute :: Connection -> IO Int64
-finishExecute conn = do
+finishExecute :: Query -> Connection -> IO Int64
+finishExecute q conn = do
   ncols <- Base.fieldCount (Left conn)
   if ncols /= 0
-    then error "execute: executed a select!"
+    then throw $ QueryError ("execute resulted in " ++ show ncols ++
+                             "-column result") q
     else Base.affectedRows conn
-  
+
+-- | Perform a @SELECT@ or other SQL query that is expected to return
+-- results.
+--
+-- All results are retrieved and converted before this function
+-- returns.
+--
+-- 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
+--   using 'execute' instead of 'query').
+--
+-- * 'ResultError': result conversion failed.
 query :: (QueryParams q, QueryResults r)
          => Connection -> Query -> q -> IO [r]
 query conn template qs = do
   Base.query conn =<< formatQuery conn template qs
-  finishQuery conn
-  
+  finishQuery template conn
+
+-- | A version of 'query' that does not perform query substitution.
 query_ :: (QueryResults r) => Connection -> Query -> IO [r]
-query_ conn (Query q) = do
-  Base.query conn q
-  finishQuery conn
+query_ conn q@(Query que) = do
+  Base.query conn que
+  finishQuery q conn
 
-finishQuery :: (QueryResults r) => Connection -> IO [r]
-finishQuery conn = do
+finishQuery :: (QueryResults r) => Query -> Connection -> IO [r]
+finishQuery q conn = do
   r <- Base.storeResult conn
   ncols <- Base.fieldCount (Right r)
   if ncols == 0
-    then return []
+    then throw $ QueryError "query resulted in zero-column result" q
     else do
       fs <- Base.fetchFields r
       flip fix [] $ \loop acc -> do
           _  -> let !c = convertResults fs row
                 in loop (c:acc)
 
+-- | 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.
+--
+-- If your action throws any exception (not just a SQL exception), the
+-- transaction will be rolled back 'Base.rollback' before the
+-- exception is propagated.
+withTransaction :: Connection -> IO a -> IO a
+withTransaction conn act = do
+  r <- act `onException` Base.rollback conn
+  Base.commit conn
+  return r
+
 fmtError :: String -> Query -> [Action] -> a
 fmtError msg q xs = throw FormatError {
                       fmtMessage = msg

Database/MySQL/Simple/Result.hs

 import qualified Data.Text.Encoding as ST
 import qualified Data.Text.Lazy as LT
 
--- | This exception is thrown if conversion from a SQL value to a
--- Haskell value fails.
+-- | Exception thrown if conversion from a SQL value to a Haskell
+-- value fails.
 data ResultError = Incompatible { errSQLType :: String
                                 , errHaskellType :: String
                                 , errMessage :: String }

mysql-simple.cabal

     blaze-builder,
     blaze-textual,
     bytestring >= 0.9,
-    mysql,
+    mysql >= 0.1.0.1,
     old-locale,
     text >= 0.11.0.2,
     time