mariadb-haskell-0.1.0.0: src/Database/MySQL/Protocol/Command.hs
{-# OPTIONS_GHC -funbox-strict-fields #-}
{-|
Module : Database.MySQL.Protocol.Command
Description : MariaDB command encodings and bulk indicators
Copyright : (c) Winterland, 2016
License : BSD
Maintainer : Anton Antich <anton.antich@gmail.com>
Stability : experimental
Portability : PORTABLE
Common MySQL commands supports.
-}
module Database.MySQL.Protocol.Command where
import Control.Monad
import Data.Binary
import Data.Binary.Get
import Data.Binary.Parser
import Data.Binary.Put
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as L
import Database.MySQL.Protocol.MySQLValue
import Database.MySQL.Protocol.Packet
--------------------------------------------------------------------------------
-- Commands
type StmtID = Word32
-- | One cell in a MariaDB binary bulk batch. NULL is 'BulkValue MySQLNull'.
-- DEFAULT and IGNORE are wire indicators, not SQL text; IGNORE means a default
-- on INSERT and leaves that column unchanged on UPDATE.
data BulkParameter = BulkValue !MySQLValue | BulkDefault | BulkIgnore
deriving (Show, Eq)
-- | EXECUTE binding descriptors. Uploaded values carry a type but no inline
-- bytes. Preserve text versus binary semantics even though both upload through
-- COM_STMT_SEND_LONG_DATA; their server collations need not be the same.
data ExecuteParameter = ExecuteValue !MySQLValue | UploadedBytes | UploadedText
deriving (Show, Eq)
-- | Type exemplar and NULL-bitmap value for an EXECUTE binding. Empty uploaded
-- parameters remain non-NULL; these exemplars are never sent as inline values.
executeParameterValue :: ExecuteParameter -> MySQLValue
executeParameterValue (ExecuteValue value) = value
executeParameterValue UploadedBytes = MySQLBytes ""
executeParameterValue UploadedText = MySQLText ""
-- | Wire commands used by MariaDB. Prefer managed operations in
-- "Database.MySQL.Base"; constructing/sending these directly can bypass arity,
-- cache-generation, ownership and negotiated-capability checks.
--
data Command
= COM_QUIT -- ^ 0x01
| COM_INIT_DB !ByteString -- ^ 0x02
| COM_QUERY !L.ByteString -- ^ 0x03
| COM_PING -- ^ 0x0E
| COM_BINLOG_DUMP !Word32 !Word16 !Word32 !ByteString -- ^ 0x12
-- binlog-pos, flags(0x01), server-id, binlog-filename
| COM_REGISTER_SLAVE !Word32 !ByteString !ByteString !ByteString !Word16 !Word32 !Word32 -- ^ 0x15
-- server-id, slaves hostname, slaves user, slaves password, slaves port, replication rank(ignored), master-id(usually 0)
| COM_STMT_PREPARE !L.ByteString -- ^ 0x16 statement
| COM_STMT_EXECUTE !StmtID ![MySQLValue] !BitMap -- ^ 0x17 stmtId, params
| COM_STMT_EXECUTE_EXT !StmtID !Word8 ![ExecuteParameter] !BitMap
-- ^ 0x17: cursor flag and typed inline/uploaded parameters.
| COM_STMT_SEND_LONG_DATA !StmtID !Word16 !ByteString
-- ^ 0x18: statement, zero-based parameter index, one chunk; no reply.
| COM_STMT_CLOSE !StmtID -- ^ 0x19 stmtId
| COM_STMT_RESET !StmtID -- ^ 0x1A stmtId
| COM_STMT_FETCH !StmtID !Word32 -- ^ 0x1C: read-only cursor batch.
| COM_RESET_CONNECTION -- ^ 0x1F; use the guarded reset API.
| COM_STMT_BULK_EXECUTE !StmtID !Word16 ![MySQLValue] ![[BulkParameter]]
-- ^ 0xFA: statement, flags, one type exemplar per column, batch rows.
| COM_UNSUPPORTED
deriving (Show, Eq)
putCommand :: Command -> Put
putCommand COM_QUIT = putWord8 0x01
putCommand (COM_INIT_DB db) = putWord8 0x02 >> putByteString db
putCommand (COM_QUERY q) = putWord8 0x03 >> putLazyByteString q
putCommand COM_PING = putWord8 0x0E
putCommand (COM_BINLOG_DUMP pos flags sid fname) = do
putWord8 0x12
putWord32le pos
putWord16le flags
putWord32le sid
putByteString fname
putCommand (COM_REGISTER_SLAVE sid shost susr spass sport rrank mid) = do
putWord8 0x15
putWord32le sid
putLenEncBytes shost
putLenEncBytes susr
putLenEncBytes spass
putWord16le sport
putWord32le rrank
putWord32le mid
putCommand (COM_STMT_PREPARE stmt) = putWord8 0x16 >> putLazyByteString stmt
putCommand (COM_STMT_EXECUTE stid params nullmap) = do
-- Preserve the ordinary encoder's direct value path: uploaded/cursor
-- descriptors must not add a wrapper/list allocation to existing callers.
putExecuteHeader stid 0
unless (null params) $ do
putByteString (fromBitMap nullmap)
putWord8 1
mapM_ putParamMySQLType params
mapM_ putBinaryField params
putCommand (COM_STMT_EXECUTE_EXT stid flags params nullmap) =
putExecute stid flags params nullmap
putCommand (COM_STMT_SEND_LONG_DATA ident parameter bytes) =
putWord8 0x18 >> putWord32le ident >> putWord16le parameter >> putByteString bytes
putCommand (COM_STMT_FETCH ident count) = putWord8 0x1c >> putWord32le ident >> putWord32le count
putCommand (COM_STMT_CLOSE stid) = putWord8 0x19 >> putWord32le stid
putCommand (COM_STMT_RESET stid) = putWord8 0x1A >> putWord32le stid
putCommand COM_RESET_CONNECTION = putWord8 0x1f
putCommand (COM_STMT_BULK_EXECUTE ident flags types rows) = do
putWord8 0xfa
putWord32le ident
putWord16le flags
mapM_ putParamMySQLType types
forM_ rows $ mapM_ $ \cell -> case cell of
BulkValue MySQLNull -> putWord8 1
BulkValue value -> putWord8 0 >> putBinaryField value
BulkDefault -> putWord8 2
BulkIgnore -> putWord8 3
putCommand _ = error "unsupported command"
-- Uploaded parameters still have a type descriptor, but no inline value.
-- Their NULL bit must be clear, including when the uploaded value is empty.
putExecute :: StmtID -> Word8 -> [ExecuteParameter] -> BitMap -> Put
putExecute stid flags params nullmap = do
putExecuteHeader stid flags
unless (null params) $ do
putByteString (fromBitMap nullmap)
putWord8 0x01 -- always use new-params-bound-flag
mapM_ (putParamMySQLType . executeParameterValue) params
forM_ params $ \parameter -> case parameter of
ExecuteValue value -> putBinaryField value
_ -> pure ()
-- | Fixed EXECUTE header shared without wrapping ordinary scalar parameters.
putExecuteHeader :: StmtID -> Word8 -> Put
putExecuteHeader ident flags = putWord8 0x17 >> putWord32le ident >>
putWord8 flags >> putWord32le 1
--------------------------------------------------------------------------------
-- Prepared statment related
-- | call 'isOK' with this packet return true
data StmtPrepareOK = StmtPrepareOK
{ stmtId :: !StmtID
, stmtColumnCnt :: !Int
, stmtParamCnt :: !Int
, stmtWarnCnt :: !Int
} deriving (Show, Eq)
getStmtPrepareOK :: Get StmtPrepareOK
getStmtPrepareOK = do
skipN 1 -- OK byte
stmtid <- getWord32le
cc <- fromIntegral <$> getWord16le
pc <- fromIntegral <$> getWord16le
skipN 1 -- reserved
wc <- fromIntegral <$> getWord16le
return (StmtPrepareOK stmtid cc pc wc)
{-# INLINE getStmtPrepareOK #-}