mariadb-haskell-0.1.0.0: validation/hs/Validation/CDC.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Validation.CDC (cases) where
import Control.Concurrent
import Control.Exception hiding (assert)
import Control.Monad
import Data.Binary.Put
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Data.IORef
import Data.Word
import Database.MySQL.Base
import Database.MySQL.BinLog
import qualified System.IO.Streams as S
import Validation.Common
import qualified Validation.Stage5 as Stage5
format :: FormatDescription
format=FormatDescription 4 "12.3.3-MariaDB" 0 (B.replicate 171 8)
make :: BinLogEventType -> L.ByteString -> BinLogPacket
make typ body=BinLogPacket 0 typ 1 (fromIntegral (19+L.length body)) 4 0 body False
tableMap :: Word64 -> BinLogPacket
tableMap tid=make BINLOG_TABLE_MAP_EVENT $ runPut $ do
putWord48le tid; putWord16le 0
putWord8 1; putByteString "d"; putWord8 0
putWord8 1; putByteString "t"; putWord8 0
putLenEncInt 1; putWord8 3; putLenEncInt 0; putWord8 0
writeRow :: Word64 -> Int -> BinLogPacket
writeRow tid n=make BINLOG_WRITE_ROWS_EVENTv1 $ runPut $ do
putWord48le tid; putWord16le 0; putLenEncInt 1; putWord8 1; putWord8 0; putWord32le (fromIntegral n)
decodeEvents :: [BinLogPacket] -> IO [RowBinLogEvent]
decodeEvents packets=do
ref <- newIORef "mariadb-bin.000001"
input <- S.fromList packets
decodeRowBinLogEvent (format,ref,input) >>= S.toList
cases :: [Case]
cases =
[ Case "cdc.single-table-map" "cdc" $ do
actual <- decodeEvents [tableMap 1,writeRow 1 7]
equal "single row event" 1 (length actual)
, Case "cdc.multiple-table-maps" "cdc" $ do
actual <- decodeEvents [tableMap 1,tableMap 2,writeRow 1 7,writeRow 2 8]
equal "both mapped row events" 2 (length actual)
, Case "cdc.repeated-row-events" "cdc" $ do
actual <- decodeEvents [tableMap 1,writeRow 1 7,writeRow 1 8]
equal "map reused for both row events" 2 (length actual)
, Case "cdc.table-id-mismatch" "cdc" $ do
result <- try (decodeEvents [tableMap 1,writeRow 2 7]) :: IO (Either SomeException [RowBinLogEvent])
case result of
Left _ -> pure ()
Right events -> forM_ events $ \e -> case e of
RowWriteEvent _ _ tm wr -> equal "row matched correct table ID" (tmTableId tm) (writeTableId wr)
_ -> pure ()
, Case "cdc.crc32-corruption-rejected" "cdc" $ do
let body=runPut $ do
putWord8 0; putWord32le 0; putWord8 16; putWord32le 1
putWord32le 31; putWord32le 4; putWord16le 0
putWord64le 7; putWord32le 0xdeadbeef
expectRejection (getFromPacket (getBinLogPacket True False) (Packet (L.length body) 1 body))
, Case "cdc.live-row-insert" "cdc" $ withConn $ \source -> do
exec source "DROP TABLE IF EXISTS mb_cdc_live"
exec source "CREATE TABLE mb_cdc_live(id INT PRIMARY KEY,n INT) ENGINE=InnoDB"
tracker <- getLastBinLogTracker source >>= maybe (throwIO (Unavailable "binary log position unavailable")) pure
stream <- dumpBinLog source 610124 tracker False >>= decodeRowBinLogEvent
_ <- forkIO $ do
threadDelay 50000
withConn (\writer -> exec writer "INSERT INTO mb_cdc_live VALUES(1,7)")
event <- S.read stream
case event of
Just (RowWriteEvent _ _ _ row) -> equal "live insert image" [[BinLogLong 1,BinLogLong 7]] (writeRowData row)
_ -> throwIO (userError ("missing live row event: " ++ show event))
, Case "cdc.gtid-resume" "cdc" $ Stage5.gtidResumeFeature
, Case "cdc.durable-ack-control" "cdc" $ Stage5.manualAckFeature
] ++
[ Case ("cdc.native-event-" ++ show typ) "cdc" $ do
let body=runPut $ do
putWord8 0; putWord32le 0; putWord8 typ; putWord32le 1
putWord32le 19; putWord32le 4; putWord16le 0
result <- try $ do
p <- getFromPacket (getBinLogPacket False False) (Packet (L.length body) 1 body)
evaluate (length (show (blEventType p)))
case result of
Left e | Just (_ :: DecodePacketException) <- fromException e -> pure ()
Left (e :: SomeException) -> throwIO (userError ("partial event decoder: " ++ displayException e))
Right _ -> pure ()
| typ <- [160..171]
]