{-|
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
    , defaultConnectInfoMB4
    , connect
    , connectDetail
    , close
    , ping
      -- * Direct query
    , execute
    , executeMany
    , executeMany_
    , execute_
    , query_
    , queryVector_
    , query
    , queryVector
      -- * Prepared query statement
    , prepareStmt
    , prepareStmtDetail
    , executeStmt
    , queryStmt
    , queryStmtVector
    , closeStmt
    , resetStmt
      -- * Helpers
    , withTransaction
    , QueryParam(..)
    , Param (..)
    , Query(..)
    , renderParams
    , command
    , Stream.skipToEof
      -- * Exceptions
    , NetworkException(..)
    , UnconsumedResultSet(..)
    , ERRException(..)
    , UnexpectedPacket(..)
    , DecodePacketException(..)
    , WrongParamsCount(..)
      -- * 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
    ) where

import           Control.Exception                  (mask, onException, throwIO)
import           Control.Monad
import qualified Data.ByteString.Lazy               as L
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           Database.MySQL.Query
import           System.IO.Streams                  (InputStream)
import qualified System.IO.Streams                  as Stream
import qualified Data.Vector                        as V

--------------------------------------------------------------------------------

-- | Execute a MySQL query with parameters which don't return a result-set.
--
-- 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 :: QueryParam p => MySQLConn -> Query -> [p] -> IO OK
execute :: forall p. QueryParam p => MySQLConn -> Query -> [p] -> IO OK
execute MySQLConn
conn Query
qry [p]
params = MySQLConn -> Query -> IO OK
execute_ MySQLConn
conn (Query -> [p] -> Query
forall p. QueryParam p => Query -> [p] -> Query
renderParams Query
qry [p]
params)

{-# SPECIALIZE execute :: MySQLConn -> Query -> [MySQLValue] -> IO OK #-}
{-# SPECIALIZE execute :: MySQLConn -> Query -> [Param]      -> IO OK #-}

-- | Execute a multi-row query which don't return result-set.
--
-- Leverage MySQL's multi-statement support to do batch insert\/update\/delete,
-- you may want to use 'withTransaction' to make sure it's atomic, and
-- use @sum . map okAffectedRows@ to get all affected rows count.
--
-- @since 0.2.0.0
--
executeMany :: QueryParam p => MySQLConn -> Query -> [[p]] -> IO [OK]
executeMany :: forall p. QueryParam p => MySQLConn -> Query -> [[p]] -> IO [OK]
executeMany conn :: MySQLConn
conn@(MySQLConn InputStream Packet
is Packet -> IO ()
os IO ()
_ IORef Bool
_) Query
qry [[p]]
paramsList = do
    MySQLConn -> IO ()
guardUnconsumed MySQLConn
conn
    let qry' :: ByteString
qry' = ByteString -> [ByteString] -> ByteString
L.intercalate ByteString
";" ([ByteString] -> ByteString) -> [ByteString] -> ByteString
forall a b. (a -> b) -> a -> b
$ ([p] -> ByteString) -> [[p]] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map (Query -> ByteString
fromQuery (Query -> ByteString) -> ([p] -> Query) -> [p] -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Query -> [p] -> Query
forall p. QueryParam p => Query -> [p] -> Query
renderParams Query
qry) [[p]]
paramsList
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (ByteString -> Command
COM_QUERY ByteString
qry') Packet -> IO ()
os
    ([p] -> IO OK) -> [[p]] -> IO [OK]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (\ [p]
_ -> InputStream Packet -> IO OK
waitCommandReply InputStream Packet
is) [[p]]
paramsList

{-# SPECIALIZE executeMany :: MySQLConn -> Query -> [[MySQLValue]] -> IO [OK] #-}
{-# SPECIALIZE executeMany :: MySQLConn -> Query -> [[Param]]      -> IO [OK] #-}

-- | Execute multiple querys (without param) which don't return result-set.
--
-- This's useful when your want to execute multiple SQLs without params, e.g. from a
-- SQL dump, or a table migration plan.
--
-- @since 0.8.4.0
--
executeMany_ :: MySQLConn -> Query -> IO [OK]
executeMany_ :: MySQLConn -> Query -> IO [OK]
executeMany_ conn :: MySQLConn
conn@(MySQLConn InputStream Packet
is Packet -> IO ()
os IO ()
_ IORef Bool
_) Query
qry = do
    MySQLConn -> IO ()
guardUnconsumed MySQLConn
conn
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (ByteString -> Command
COM_QUERY (Query -> ByteString
fromQuery Query
qry)) Packet -> IO ()
os
    InputStream Packet -> IO [OK]
waitCommandReplys InputStream Packet
is

-- | Execute a MySQL query which don't return a result-set.
--
execute_ :: MySQLConn -> Query -> IO OK
execute_ :: MySQLConn -> Query -> IO OK
execute_ MySQLConn
conn (Query ByteString
qry) = MySQLConn -> Command -> IO OK
command MySQLConn
conn (ByteString -> Command
COM_QUERY ByteString
qry)

-- | 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 :: QueryParam p => MySQLConn -> Query -> [p] -> IO ([ColumnDef], InputStream [MySQLValue])
query :: forall p.
QueryParam p =>
MySQLConn
-> Query -> [p] -> IO ([ColumnDef], InputStream [MySQLValue])
query MySQLConn
conn Query
qry [p]
params = MySQLConn -> Query -> IO ([ColumnDef], InputStream [MySQLValue])
query_ MySQLConn
conn (Query -> [p] -> Query
forall p. QueryParam p => Query -> [p] -> Query
renderParams Query
qry [p]
params)

{-# SPECIALIZE query :: MySQLConn -> Query -> [MySQLValue] -> IO ([ColumnDef], InputStream [MySQLValue]) #-}
{-# SPECIALIZE query :: MySQLConn -> Query -> [Param]      -> IO ([ColumnDef], InputStream [MySQLValue]) #-}

-- | 'V.Vector' version of 'query'.
--
-- @since 0.5.1.0
--
queryVector :: QueryParam p => MySQLConn -> Query -> [p] -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))
queryVector :: forall p.
QueryParam p =>
MySQLConn
-> Query
-> [p]
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
queryVector MySQLConn
conn Query
qry [p]
params = MySQLConn
-> Query -> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
queryVector_ MySQLConn
conn (Query -> [p] -> Query
forall p. QueryParam p => Query -> [p] -> Query
renderParams Query
qry [p]
params)

{-# SPECIALIZE queryVector :: MySQLConn -> Query -> [MySQLValue] -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue)) #-}
{-# SPECIALIZE queryVector :: MySQLConn -> Query -> [Param]      -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue)) #-}

-- | Execute a MySQL query which return a result-set.
--
query_ :: MySQLConn -> Query -> IO ([ColumnDef], InputStream [MySQLValue])
query_ :: MySQLConn -> Query -> IO ([ColumnDef], InputStream [MySQLValue])
query_ conn :: MySQLConn
conn@(MySQLConn InputStream Packet
is Packet -> IO ()
os IO ()
_ IORef Bool
consumed) (Query ByteString
qry) = do
    MySQLConn -> IO ()
guardUnconsumed MySQLConn
conn
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (ByteString -> Command
COM_QUERY ByteString
qry) Packet -> IO ()
os
    Packet
p <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
    if Packet -> Bool
isERR Packet
p
    then Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
p IO ERR
-> (ERR -> IO ([ColumnDef], InputStream [MySQLValue]))
-> IO ([ColumnDef], InputStream [MySQLValue])
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException -> IO ([ColumnDef], InputStream [MySQLValue])
forall e a. Exception e => e -> IO a
throwIO (ERRException -> IO ([ColumnDef], InputStream [MySQLValue]))
-> (ERR -> ERRException)
-> ERR
-> IO ([ColumnDef], InputStream [MySQLValue])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
    else do
        Int
len <- Get Int -> Packet -> IO Int
forall a. Get a -> Packet -> IO a
getFromPacket Get Int
getLenEncInt Packet
p
        [ColumnDef]
fields <- Int -> IO ColumnDef -> IO [ColumnDef]
forall (m :: * -> *) a. Applicative m => Int -> m a -> m [a]
replicateM Int
len (IO ColumnDef -> IO [ColumnDef]) -> IO ColumnDef -> IO [ColumnDef]
forall a b. (a -> b) -> a -> b
$ (Packet -> IO ColumnDef
forall a. Binary a => Packet -> IO a
decodeFromPacket (Packet -> IO ColumnDef)
-> (InputStream Packet -> IO Packet)
-> InputStream Packet
-> IO ColumnDef
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< InputStream Packet -> IO Packet
readPacket) InputStream Packet
is
        Packet
_ <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is -- eof packet, we don't verify this though
        IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Bool
consumed Bool
False
        InputStream [MySQLValue]
rows <- IO (Maybe [MySQLValue]) -> IO (InputStream [MySQLValue])
forall a. IO (Maybe a) -> IO (InputStream a)
Stream.makeInputStream (IO (Maybe [MySQLValue]) -> IO (InputStream [MySQLValue]))
-> IO (Maybe [MySQLValue]) -> IO (InputStream [MySQLValue])
forall a b. (a -> b) -> a -> b
$ do
            Packet
q <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
            if  | Packet -> Bool
isEOF Packet
q  -> IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Bool
consumed Bool
True IO () -> IO (Maybe [MySQLValue]) -> IO (Maybe [MySQLValue])
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe [MySQLValue] -> IO (Maybe [MySQLValue])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe [MySQLValue]
forall a. Maybe a
Nothing
                | Packet -> Bool
isERR Packet
q  -> Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
q IO ERR
-> (ERR -> IO (Maybe [MySQLValue])) -> IO (Maybe [MySQLValue])
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException -> IO (Maybe [MySQLValue])
forall e a. Exception e => e -> IO a
throwIO (ERRException -> IO (Maybe [MySQLValue]))
-> (ERR -> ERRException) -> ERR -> IO (Maybe [MySQLValue])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
                | Bool
otherwise -> [MySQLValue] -> Maybe [MySQLValue]
forall a. a -> Maybe a
Just ([MySQLValue] -> Maybe [MySQLValue])
-> IO [MySQLValue] -> IO (Maybe [MySQLValue])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get [MySQLValue] -> Packet -> IO [MySQLValue]
forall a. Get a -> Packet -> IO a
getFromPacket ([ColumnDef] -> Get [MySQLValue]
getTextRow [ColumnDef]
fields) Packet
q
        ([ColumnDef], InputStream [MySQLValue])
-> IO ([ColumnDef], InputStream [MySQLValue])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([ColumnDef]
fields, InputStream [MySQLValue]
rows)

-- | 'V.Vector' version of 'query_'.
--
-- @since 0.5.1.0
--
queryVector_ :: MySQLConn -> Query -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))
queryVector_ :: MySQLConn
-> Query -> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
queryVector_ conn :: MySQLConn
conn@(MySQLConn InputStream Packet
is Packet -> IO ()
os IO ()
_ IORef Bool
consumed) (Query ByteString
qry) = do
    MySQLConn -> IO ()
guardUnconsumed MySQLConn
conn
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (ByteString -> Command
COM_QUERY ByteString
qry) Packet -> IO ()
os
    Packet
p <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
    if Packet -> Bool
isERR Packet
p
    then Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
p IO ERR
-> (ERR -> IO (Vector ColumnDef, InputStream (Vector MySQLValue)))
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
forall e a. Exception e => e -> IO a
throwIO (ERRException
 -> IO (Vector ColumnDef, InputStream (Vector MySQLValue)))
-> (ERR -> ERRException)
-> ERR
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
    else do
        Int
len <- Get Int -> Packet -> IO Int
forall a. Get a -> Packet -> IO a
getFromPacket Get Int
getLenEncInt Packet
p
        Vector ColumnDef
fields <- Int -> IO ColumnDef -> IO (Vector ColumnDef)
forall (m :: * -> *) a. Monad m => Int -> m a -> m (Vector a)
V.replicateM Int
len (IO ColumnDef -> IO (Vector ColumnDef))
-> IO ColumnDef -> IO (Vector ColumnDef)
forall a b. (a -> b) -> a -> b
$ (Packet -> IO ColumnDef
forall a. Binary a => Packet -> IO a
decodeFromPacket (Packet -> IO ColumnDef)
-> (InputStream Packet -> IO Packet)
-> InputStream Packet
-> IO ColumnDef
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< InputStream Packet -> IO Packet
readPacket) InputStream Packet
is
        Packet
_ <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is -- eof packet, we don't verify this though
        IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Bool
consumed Bool
False
        InputStream (Vector MySQLValue)
rows <- IO (Maybe (Vector MySQLValue))
-> IO (InputStream (Vector MySQLValue))
forall a. IO (Maybe a) -> IO (InputStream a)
Stream.makeInputStream (IO (Maybe (Vector MySQLValue))
 -> IO (InputStream (Vector MySQLValue)))
-> IO (Maybe (Vector MySQLValue))
-> IO (InputStream (Vector MySQLValue))
forall a b. (a -> b) -> a -> b
$ do
            Packet
q <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
            if  | Packet -> Bool
isEOF Packet
q  -> IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Bool
consumed Bool
True IO ()
-> IO (Maybe (Vector MySQLValue)) -> IO (Maybe (Vector MySQLValue))
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe (Vector MySQLValue) -> IO (Maybe (Vector MySQLValue))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe (Vector MySQLValue)
forall a. Maybe a
Nothing
                | Packet -> Bool
isERR Packet
q  -> Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
q IO ERR
-> (ERR -> IO (Maybe (Vector MySQLValue)))
-> IO (Maybe (Vector MySQLValue))
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException -> IO (Maybe (Vector MySQLValue))
forall e a. Exception e => e -> IO a
throwIO (ERRException -> IO (Maybe (Vector MySQLValue)))
-> (ERR -> ERRException) -> ERR -> IO (Maybe (Vector MySQLValue))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
                | Bool
otherwise -> Vector MySQLValue -> Maybe (Vector MySQLValue)
forall a. a -> Maybe a
Just (Vector MySQLValue -> Maybe (Vector MySQLValue))
-> IO (Vector MySQLValue) -> IO (Maybe (Vector MySQLValue))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get (Vector MySQLValue) -> Packet -> IO (Vector MySQLValue)
forall a. Get a -> Packet -> IO a
getFromPacket (Vector ColumnDef -> Get (Vector MySQLValue)
getTextRowVector Vector ColumnDef
fields) Packet
q
        (Vector ColumnDef, InputStream (Vector MySQLValue))
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Vector ColumnDef
fields, InputStream (Vector MySQLValue)
rows)

-- | Ask MySQL to prepare a query statement.
--
prepareStmt :: MySQLConn -> Query -> IO StmtID
prepareStmt :: MySQLConn -> Query -> IO StmtID
prepareStmt conn :: MySQLConn
conn@(MySQLConn InputStream Packet
is Packet -> IO ()
os IO ()
_ IORef Bool
_) (Query ByteString
stmt) = do
    MySQLConn -> IO ()
guardUnconsumed MySQLConn
conn
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (ByteString -> Command
COM_STMT_PREPARE ByteString
stmt) Packet -> IO ()
os
    Packet
p <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
    if Packet -> Bool
isERR Packet
p
    then Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
p IO ERR -> (ERR -> IO StmtID) -> IO StmtID
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException -> IO StmtID
forall e a. Exception e => e -> IO a
throwIO (ERRException -> IO StmtID)
-> (ERR -> ERRException) -> ERR -> IO StmtID
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
    else do
        StmtPrepareOK StmtID
stid Int
colCnt Int
paramCnt Int
_ <- Get StmtPrepareOK -> Packet -> IO StmtPrepareOK
forall a. Get a -> Packet -> IO a
getFromPacket Get StmtPrepareOK
getStmtPrepareOK Packet
p
        ()
_ <- Int -> IO Packet -> IO ()
forall (m :: * -> *) a. Applicative m => Int -> m a -> m ()
replicateM_ Int
paramCnt (InputStream Packet -> IO Packet
readPacket InputStream Packet
is)
        ()
_ <- Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Int
paramCnt Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0) (IO Packet -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (InputStream Packet -> IO Packet
readPacket InputStream Packet
is))  -- EOF
        ()
_ <- Int -> IO Packet -> IO ()
forall (m :: * -> *) a. Applicative m => Int -> m a -> m ()
replicateM_ Int
colCnt (InputStream Packet -> IO Packet
readPacket InputStream Packet
is)
        ()
_ <- Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Int
colCnt Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0) (IO Packet -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (InputStream Packet -> IO Packet
readPacket InputStream Packet
is))  -- EOF
        StmtID -> IO StmtID
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return StmtID
stid

-- | Ask MySQL to prepare a query statement.
--
-- All details from @COM_STMT_PREPARE@ Response are returned: the 'StmtPrepareOK' packet,
-- params's 'ColumnDef', result's 'ColumnDef'.
--
prepareStmtDetail :: MySQLConn -> Query -> IO (StmtPrepareOK, [ColumnDef], [ColumnDef])
prepareStmtDetail :: MySQLConn -> Query -> IO (StmtPrepareOK, [ColumnDef], [ColumnDef])
prepareStmtDetail conn :: MySQLConn
conn@(MySQLConn InputStream Packet
is Packet -> IO ()
os IO ()
_ IORef Bool
_) (Query ByteString
stmt) = do
    MySQLConn -> IO ()
guardUnconsumed MySQLConn
conn
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (ByteString -> Command
COM_STMT_PREPARE ByteString
stmt) Packet -> IO ()
os
    Packet
p <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
    if Packet -> Bool
isERR Packet
p
    then Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
p IO ERR
-> (ERR -> IO (StmtPrepareOK, [ColumnDef], [ColumnDef]))
-> IO (StmtPrepareOK, [ColumnDef], [ColumnDef])
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException -> IO (StmtPrepareOK, [ColumnDef], [ColumnDef])
forall e a. Exception e => e -> IO a
throwIO (ERRException -> IO (StmtPrepareOK, [ColumnDef], [ColumnDef]))
-> (ERR -> ERRException)
-> ERR
-> IO (StmtPrepareOK, [ColumnDef], [ColumnDef])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
    else do
        sOK :: StmtPrepareOK
sOK@(StmtPrepareOK StmtID
_ Int
colCnt Int
paramCnt Int
_) <- Get StmtPrepareOK -> Packet -> IO StmtPrepareOK
forall a. Get a -> Packet -> IO a
getFromPacket Get StmtPrepareOK
getStmtPrepareOK Packet
p
        [ColumnDef]
pdefs <- Int -> IO ColumnDef -> IO [ColumnDef]
forall (m :: * -> *) a. Applicative m => Int -> m a -> m [a]
replicateM Int
paramCnt ((Packet -> IO ColumnDef
forall a. Binary a => Packet -> IO a
decodeFromPacket (Packet -> IO ColumnDef)
-> (InputStream Packet -> IO Packet)
-> InputStream Packet
-> IO ColumnDef
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< InputStream Packet -> IO Packet
readPacket) InputStream Packet
is)
        ()
_ <- Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Int
paramCnt Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0) (IO Packet -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (InputStream Packet -> IO Packet
readPacket InputStream Packet
is))  -- EOF
        [ColumnDef]
cdefs <- Int -> IO ColumnDef -> IO [ColumnDef]
forall (m :: * -> *) a. Applicative m => Int -> m a -> m [a]
replicateM Int
colCnt ((Packet -> IO ColumnDef
forall a. Binary a => Packet -> IO a
decodeFromPacket (Packet -> IO ColumnDef)
-> (InputStream Packet -> IO Packet)
-> InputStream Packet
-> IO ColumnDef
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< InputStream Packet -> IO Packet
readPacket) InputStream Packet
is)
        ()
_ <- Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Int
colCnt Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0) (IO Packet -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (InputStream Packet -> IO Packet
readPacket InputStream Packet
is))  -- EOF
        (StmtPrepareOK, [ColumnDef], [ColumnDef])
-> IO (StmtPrepareOK, [ColumnDef], [ColumnDef])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (StmtPrepareOK
sOK, [ColumnDef]
pdefs, [ColumnDef]
cdefs)

-- | Ask MySQL to closed a query statement.
--
closeStmt :: MySQLConn -> StmtID -> IO ()
closeStmt :: MySQLConn -> StmtID -> IO ()
closeStmt (MySQLConn InputStream Packet
_ Packet -> IO ()
os IO ()
_ IORef Bool
_) StmtID
stid = do
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (StmtID -> Command
COM_STMT_CLOSE StmtID
stid) Packet -> IO ()
os

-- | Ask MySQL to reset a query statement, all previous resultset will be cleared.
--
resetStmt :: MySQLConn -> StmtID -> IO ()
resetStmt :: MySQLConn -> StmtID -> IO ()
resetStmt (MySQLConn InputStream Packet
is Packet -> IO ()
os IO ()
_ IORef Bool
consumed) StmtID
stid = do
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (StmtID -> Command
COM_STMT_RESET StmtID
stid) Packet -> IO ()
os  -- previous result-set may still be unconsumed
    Packet
p <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
    if Packet -> Bool
isERR Packet
p
    then Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
p IO ERR -> (ERR -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException -> IO ()
forall e a. Exception e => e -> IO a
throwIO (ERRException -> IO ()) -> (ERR -> ERRException) -> ERR -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
    else IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Bool
consumed Bool
True

-- | Execute prepared query statement with parameters, expecting no resultset.
--
executeStmt :: MySQLConn -> StmtID -> [MySQLValue] -> IO OK
executeStmt :: MySQLConn -> StmtID -> [MySQLValue] -> IO OK
executeStmt MySQLConn
conn StmtID
stid [MySQLValue]
params =
  MySQLConn -> Command -> IO OK
command MySQLConn
conn (StmtID -> [MySQLValue] -> BitMap -> Command
COM_STMT_EXECUTE StmtID
stid [MySQLValue]
params ([MySQLValue] -> BitMap
makeNullMap [MySQLValue]
params))

-- | Execute prepared query statement with parameters, expecting resultset.
--
-- Rules about 'UnconsumedResultSet' applied here too.
--
queryStmt :: MySQLConn -> StmtID -> [MySQLValue] -> IO ([ColumnDef], InputStream [MySQLValue])
queryStmt :: MySQLConn
-> StmtID
-> [MySQLValue]
-> IO ([ColumnDef], InputStream [MySQLValue])
queryStmt conn :: MySQLConn
conn@(MySQLConn InputStream Packet
is Packet -> IO ()
os IO ()
_ IORef Bool
consumed) StmtID
stid [MySQLValue]
params = do
    MySQLConn -> IO ()
guardUnconsumed MySQLConn
conn
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (StmtID -> [MySQLValue] -> BitMap -> Command
COM_STMT_EXECUTE StmtID
stid [MySQLValue]
params ([MySQLValue] -> BitMap
makeNullMap [MySQLValue]
params)) Packet -> IO ()
os
    Packet
p <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
    if Packet -> Bool
isERR Packet
p
    then Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
p IO ERR
-> (ERR -> IO ([ColumnDef], InputStream [MySQLValue]))
-> IO ([ColumnDef], InputStream [MySQLValue])
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException -> IO ([ColumnDef], InputStream [MySQLValue])
forall e a. Exception e => e -> IO a
throwIO (ERRException -> IO ([ColumnDef], InputStream [MySQLValue]))
-> (ERR -> ERRException)
-> ERR
-> IO ([ColumnDef], InputStream [MySQLValue])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
    else do
        Int
len <- Get Int -> Packet -> IO Int
forall a. Get a -> Packet -> IO a
getFromPacket Get Int
getLenEncInt Packet
p
        [ColumnDef]
fields <- Int -> IO ColumnDef -> IO [ColumnDef]
forall (m :: * -> *) a. Applicative m => Int -> m a -> m [a]
replicateM Int
len (IO ColumnDef -> IO [ColumnDef]) -> IO ColumnDef -> IO [ColumnDef]
forall a b. (a -> b) -> a -> b
$ (Packet -> IO ColumnDef
forall a. Binary a => Packet -> IO a
decodeFromPacket (Packet -> IO ColumnDef)
-> (InputStream Packet -> IO Packet)
-> InputStream Packet
-> IO ColumnDef
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< InputStream Packet -> IO Packet
readPacket) InputStream Packet
is
        Packet
_ <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is -- eof packet, we don't verify this though
        IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Bool
consumed Bool
False
        InputStream [MySQLValue]
rows <- IO (Maybe [MySQLValue]) -> IO (InputStream [MySQLValue])
forall a. IO (Maybe a) -> IO (InputStream a)
Stream.makeInputStream (IO (Maybe [MySQLValue]) -> IO (InputStream [MySQLValue]))
-> IO (Maybe [MySQLValue]) -> IO (InputStream [MySQLValue])
forall a b. (a -> b) -> a -> b
$ do
            Packet
q <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
            if  | Packet -> Bool
isOK  Packet
q  -> [MySQLValue] -> Maybe [MySQLValue]
forall a. a -> Maybe a
Just ([MySQLValue] -> Maybe [MySQLValue])
-> IO [MySQLValue] -> IO (Maybe [MySQLValue])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get [MySQLValue] -> Packet -> IO [MySQLValue]
forall a. Get a -> Packet -> IO a
getFromPacket ([ColumnDef] -> Int -> Get [MySQLValue]
getBinaryRow [ColumnDef]
fields Int
len) Packet
q
                | Packet -> Bool
isEOF Packet
q  -> IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Bool
consumed Bool
True IO () -> IO (Maybe [MySQLValue]) -> IO (Maybe [MySQLValue])
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe [MySQLValue] -> IO (Maybe [MySQLValue])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe [MySQLValue]
forall a. Maybe a
Nothing
                | Packet -> Bool
isERR Packet
q  -> Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
q IO ERR
-> (ERR -> IO (Maybe [MySQLValue])) -> IO (Maybe [MySQLValue])
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException -> IO (Maybe [MySQLValue])
forall e a. Exception e => e -> IO a
throwIO (ERRException -> IO (Maybe [MySQLValue]))
-> (ERR -> ERRException) -> ERR -> IO (Maybe [MySQLValue])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
                | Bool
otherwise -> UnexpectedPacket -> IO (Maybe [MySQLValue])
forall e a. Exception e => e -> IO a
throwIO (Packet -> UnexpectedPacket
UnexpectedPacket Packet
q)
        ([ColumnDef], InputStream [MySQLValue])
-> IO ([ColumnDef], InputStream [MySQLValue])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([ColumnDef]
fields, InputStream [MySQLValue]
rows)

-- | 'V.Vector' version of 'queryStmt'
--
-- @since 0.5.1.0
--
queryStmtVector :: MySQLConn -> StmtID -> [MySQLValue] -> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))
queryStmtVector :: MySQLConn
-> StmtID
-> [MySQLValue]
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
queryStmtVector conn :: MySQLConn
conn@(MySQLConn InputStream Packet
is Packet -> IO ()
os IO ()
_ IORef Bool
consumed) StmtID
stid [MySQLValue]
params = do
    MySQLConn -> IO ()
guardUnconsumed MySQLConn
conn
    Command -> (Packet -> IO ()) -> IO ()
writeCommand (StmtID -> [MySQLValue] -> BitMap -> Command
COM_STMT_EXECUTE StmtID
stid [MySQLValue]
params ([MySQLValue] -> BitMap
makeNullMap [MySQLValue]
params)) Packet -> IO ()
os
    Packet
p <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
    if Packet -> Bool
isERR Packet
p
    then Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
p IO ERR
-> (ERR -> IO (Vector ColumnDef, InputStream (Vector MySQLValue)))
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
forall e a. Exception e => e -> IO a
throwIO (ERRException
 -> IO (Vector ColumnDef, InputStream (Vector MySQLValue)))
-> (ERR -> ERRException)
-> ERR
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
    else do
        Int
len <- Get Int -> Packet -> IO Int
forall a. Get a -> Packet -> IO a
getFromPacket Get Int
getLenEncInt Packet
p
        Vector ColumnDef
fields <- Int -> IO ColumnDef -> IO (Vector ColumnDef)
forall (m :: * -> *) a. Monad m => Int -> m a -> m (Vector a)
V.replicateM Int
len (IO ColumnDef -> IO (Vector ColumnDef))
-> IO ColumnDef -> IO (Vector ColumnDef)
forall a b. (a -> b) -> a -> b
$ (Packet -> IO ColumnDef
forall a. Binary a => Packet -> IO a
decodeFromPacket (Packet -> IO ColumnDef)
-> (InputStream Packet -> IO Packet)
-> InputStream Packet
-> IO ColumnDef
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< InputStream Packet -> IO Packet
readPacket) InputStream Packet
is
        Packet
_ <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is -- eof packet, we don't verify this though
        IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Bool
consumed Bool
False
        InputStream (Vector MySQLValue)
rows <- IO (Maybe (Vector MySQLValue))
-> IO (InputStream (Vector MySQLValue))
forall a. IO (Maybe a) -> IO (InputStream a)
Stream.makeInputStream (IO (Maybe (Vector MySQLValue))
 -> IO (InputStream (Vector MySQLValue)))
-> IO (Maybe (Vector MySQLValue))
-> IO (InputStream (Vector MySQLValue))
forall a b. (a -> b) -> a -> b
$ do
            Packet
q <- InputStream Packet -> IO Packet
readPacket InputStream Packet
is
            if  | Packet -> Bool
isOK  Packet
q  -> Vector MySQLValue -> Maybe (Vector MySQLValue)
forall a. a -> Maybe a
Just (Vector MySQLValue -> Maybe (Vector MySQLValue))
-> IO (Vector MySQLValue) -> IO (Maybe (Vector MySQLValue))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get (Vector MySQLValue) -> Packet -> IO (Vector MySQLValue)
forall a. Get a -> Packet -> IO a
getFromPacket (Vector ColumnDef -> Int -> Get (Vector MySQLValue)
getBinaryRowVector Vector ColumnDef
fields Int
len) Packet
q
                | Packet -> Bool
isEOF Packet
q  -> IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Bool
consumed Bool
True IO ()
-> IO (Maybe (Vector MySQLValue)) -> IO (Maybe (Vector MySQLValue))
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe (Vector MySQLValue) -> IO (Maybe (Vector MySQLValue))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe (Vector MySQLValue)
forall a. Maybe a
Nothing
                | Packet -> Bool
isERR Packet
q  -> Packet -> IO ERR
forall a. Binary a => Packet -> IO a
decodeFromPacket Packet
q IO ERR
-> (ERR -> IO (Maybe (Vector MySQLValue)))
-> IO (Maybe (Vector MySQLValue))
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ERRException -> IO (Maybe (Vector MySQLValue))
forall e a. Exception e => e -> IO a
throwIO (ERRException -> IO (Maybe (Vector MySQLValue)))
-> (ERR -> ERRException) -> ERR -> IO (Maybe (Vector MySQLValue))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ERR -> ERRException
ERRException
                | Bool
otherwise -> UnexpectedPacket -> IO (Maybe (Vector MySQLValue))
forall e a. Exception e => e -> IO a
throwIO (Packet -> UnexpectedPacket
UnexpectedPacket Packet
q)
        (Vector ColumnDef, InputStream (Vector MySQLValue))
-> IO (Vector ColumnDef, InputStream (Vector MySQLValue))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Vector ColumnDef
fields, InputStream (Vector MySQLValue)
rows)

-- | Run querys inside a transaction, querys will be rolled back if exception arise.
--
-- @since 0.2.0.0
--
withTransaction :: MySQLConn -> IO a -> IO a
withTransaction :: forall a. MySQLConn -> IO a -> IO a
withTransaction MySQLConn
conn IO a
procedure = ((forall a. IO a -> IO a) -> IO a) -> IO a
forall b. ((forall a. IO a -> IO a) -> IO b) -> IO b
mask (((forall a. IO a -> IO a) -> IO a) -> IO a)
-> ((forall a. IO a -> IO a) -> IO a) -> IO a
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
restore -> do
  OK
_ <- MySQLConn -> Query -> IO OK
execute_ MySQLConn
conn Query
"BEGIN"
  a
r <- IO a -> IO a
forall a. IO a -> IO a
restore IO a
procedure IO a -> IO OK -> IO a
forall a b. IO a -> IO b -> IO a
`onException` (MySQLConn -> Query -> IO OK
execute_ MySQLConn
conn Query
"ROLLBACK")
  OK
_ <- MySQLConn -> Query -> IO OK
execute_ MySQLConn
conn Query
"COMMIT"
  a -> IO a
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure a
r