mysql-haskell-0.1.0.0: Database/MySQL/Base.hs
{-|
Module : Database.MySQL.Base
Description : Prelude of mysql-haskell
Copyright : (c) Winterland, 2016
License : BSD
Maintainer : drkoster@qq.com
Stability : experimental
Portability : PORTABLE
This module provide common MySQL operations,
NOTEs on 'Exception's: This package use 'Exception' to deal with unexpected situations,
but you shouldn't try to catch them if you don't have a recovery plan,
for example: there's no meaning to catch a 'ERRException' during authentication unless you want to try different passwords.
By using this library you will meet:
* 'NetworkException': underline network is broken.
* 'UnconsumedResultSet': you should consume previous resultset before sending new command.
* 'ERRException': you receive a 'ERR' packet when you shouldn't.
* 'UnexpectedPacket': you receive a unexpected packet when you shouldn't.
* 'DecodePacketException': there's a packet we can't decode.
* 'WrongParamsCount': you're giving wrong number of params to 'renderParams'.
Both 'UnexpectedPacket' and 'DecodePacketException' may indicate a bug of this library rather your code, so please report!
-}
module Database.MySQL.Base
( -- * Setting up and control connection
MySQLConn
, ConnectInfo(..)
, defaultConnectInfo
, connect
, connectDetail
, close
, ping
-- * direct query
, execute
, execute_
, query_
, query
-- * prepared query statement
, prepareStmt
, prepareStmtDetail
, executeStmt
, queryStmt
, closeStmt
, resetStmt
-- * MySQL protocol
, module Database.MySQL.Protocol.Auth
, module Database.MySQL.Protocol.Command
, module Database.MySQL.Protocol.ColumnDef
, module Database.MySQL.Protocol.Packet
, module Database.MySQL.Protocol.MySQLValue
-- * helpers
, Query(..)
, renderParams
, command
, Stream.skipToEof
) where
import Control.Applicative
import Control.Exception (throwIO)
import Control.Monad
import Data.IORef (writeIORef)
import Database.MySQL.Connection
import Database.MySQL.Protocol.Auth
import Database.MySQL.Protocol.ColumnDef
import Database.MySQL.Protocol.Command
import Database.MySQL.Protocol.MySQLValue
import Database.MySQL.Protocol.Packet
import System.IO.Streams (InputStream, OutputStream)
import qualified System.IO.Streams as Stream
import Database.MySQL.Query
--------------------------------------------------------------------------------
-- | Execute a MySQL query with parameters which don't return a resultSet.
--
-- The query may contain placeholders @?@, for filling up parameters, the parameters
-- will be escaped before get filled into the query, please DO NOT enable @NO_BACKSLASH_ESCAPES@,
-- and you should consider using prepared statement if this's not an one shot query.
--
execute :: MySQLConn -> Query -> [MySQLValue] -> IO OK
execute conn qry params = execute_ conn (renderParams qry params)
-- | Execute a MySQL query which don't return a resultSet.
--
execute_ :: MySQLConn -> Query -> IO OK
execute_ conn (Query qry) = command conn (COM_QUERY qry)
{-
executeBatch :: MySQLConn -> Query -> OutputStream [MySQLValue] -> IO (InputStream OK)
executeBatch
-}
-- | Execute a MySQL query which return a result-set with parameters.
--
-- Note that you must fully consumed the result-set before start a new query on
-- the same 'MySQLConn', or an 'UnconsumedResultSet' will be thrown.
-- if you want to skip the result-set, use 'Stream.skipToEof'.
--
query :: MySQLConn -> Query -> [MySQLValue] -> IO ([ColumnDef], InputStream [MySQLValue])
query conn qry params = query_ conn (renderParams qry params)
-- | Execute a MySQL query which return a resultSet.
--
query_ :: MySQLConn -> Query -> IO ([ColumnDef], InputStream [MySQLValue])
query_ conn@(MySQLConn is os _ consumed) (Query qry) = do
guardUnconsumed conn
writeCommand (COM_QUERY qry) os
p <- readPacket is
if isERR p
then decodeFromPacket p >>= throwIO . ERRException
else do
len <- getFromPacket getLenEncInt p
fields <- replicateM len $ (decodeFromPacket <=< readPacket) is
_ <- readPacket is -- eof packet, we don't verify this though
writeIORef consumed False
rows <- Stream.makeInputStream $ do
q <- readPacket is
if | isEOF q -> writeIORef consumed True >> return Nothing
| isERR q -> decodeFromPacket q >>= throwIO . ERRException
| otherwise -> Just <$> getFromPacket (getTextRow fields) q
return (fields, rows)
-- | Ask MySQL to prepare a query statement.
--
prepareStmt :: MySQLConn -> Query -> IO StmtID
prepareStmt conn@(MySQLConn is os _ _) (Query stmt) = do
guardUnconsumed conn
writeCommand (COM_STMT_PREPARE stmt) os
p <- readPacket is
if isERR p
then decodeFromPacket p >>= throwIO . ERRException
else do
StmtPrepareOK stid colCnt paramCnt _ <- getFromPacket getStmtPrepareOK p
_ <- replicateM paramCnt (readPacket is)
_ <- unless (colCnt == 0) (void (readPacket is)) -- EOF
_ <- replicateM colCnt (readPacket is)
_ <- unless (paramCnt == 0) (void (readPacket is)) -- EOF
return stid
-- | Ask MySQL to prepare a query statement.
--
-- All details from @COM_STMT_PREPARE@ Response are returned: the 'StmtPrepareOK' packet,
-- params's 'ColumnDef', table's 'ColumnDef'.
--
prepareStmtDetail :: MySQLConn -> Query -> IO (StmtPrepareOK, [ColumnDef], [ColumnDef])
prepareStmtDetail conn@(MySQLConn is os _ _) (Query stmt) = do
guardUnconsumed conn
writeCommand (COM_STMT_PREPARE stmt) os
p <- readPacket is
if isERR p
then decodeFromPacket p >>= throwIO . ERRException
else do
sOK@(StmtPrepareOK _ colCnt paramCnt _) <- getFromPacket getStmtPrepareOK p
pdefs <- replicateM paramCnt ((decodeFromPacket <=< readPacket) is)
_ <- unless (colCnt == 0) (void (readPacket is)) -- EOF
cdefs <- replicateM colCnt ((decodeFromPacket <=< readPacket) is)
_ <- unless (paramCnt == 0) (void (readPacket is)) -- EOF
return (sOK, pdefs, cdefs)
-- | Ask MySQL to closed a query statement.
--
closeStmt :: MySQLConn -> StmtID -> IO ()
closeStmt (MySQLConn _ os _ _) stid = do
writeCommand (COM_STMT_CLOSE stid) os
-- | Ask MySQL to reset a query statement, all previous resultset will be cleared.
--
resetStmt :: MySQLConn -> StmtID -> IO ()
resetStmt (MySQLConn is os _ consumed) stid = do
writeCommand (COM_STMT_RESET stid) os -- previous result-set may still be unconsumed
p <- readPacket is
if isERR p
then decodeFromPacket p >>= throwIO . ERRException
else writeIORef consumed True
-- | Execute prepared query statement with parameters, expecting no resultset.
--
executeStmt :: MySQLConn -> StmtID -> [MySQLValue] -> IO OK
executeStmt conn stid params = command conn (COM_STMT_EXECUTE stid params)
-- | Execute prepared query statement with parameters, expecting resultset.
--
queryStmt :: MySQLConn -> StmtID -> [MySQLValue] -> IO ([ColumnDef], InputStream [MySQLValue])
queryStmt conn@(MySQLConn is os _ consumed) stid params = do
guardUnconsumed conn
writeCommand (COM_STMT_EXECUTE stid params) os
p <- readPacket is
if isERR p
then decodeFromPacket p >>= throwIO . ERRException
else do
len <- getFromPacket getLenEncInt p
fields <- replicateM len $ (decodeFromPacket <=< readPacket) is
_ <- readPacket is -- eof packet, we don't verify this though
writeIORef consumed False
rows <- Stream.makeInputStream $ do
q <- readPacket is
if | isOK q -> Just <$> getFromPacket (getBinaryRow fields len) q -- fast path for decode rows
| isEOF q -> writeIORef consumed True >> return Nothing
| isERR q -> decodeFromPacket q >>= throwIO . ERRException
| otherwise -> throwIO (UnexpectedPacket q)
return (fields, rows)