# ==== Purpose ====
#
# Assert that the binary log contains a specific sequence of event types.
#
# ==== Usage ====
#
# --let $event_sequence= SEQUENCE_OF_EVENTS
# [--let $event_separator= CHAR]
# [--let $invert= 1]
# [--let $limit= [OFFSET,] ROW_COUNT]
# [--let $binlog_file= FILE]
# [--let $binlog_position= POSITION]
# [--let $relay_log= 1]
# [--let $include_header_events= 1]
# [--let $wait_for_binlog_events= 1]
# [--let $slave_timeout= 1]
# [--let $rpl_debug= 1]
# [--let $keep_temp_files= 1]
# [--let $dont_print_pattern= 1]
# --source include/assert_binlog_events.inc
#
# Parameters:
#
# $event_sequence
# This is a (perl) regular expression that will be matched with a
# sequence of event specifications. Here is an example:
#
# Query/BEGIN # Query/INSERT.* # Query # (Query/COMMIT|Xid)
#
# Match a sequence of a BEGIN event, followed by an
# Query_log_event where the query begins with INSERT, followed
# by any query, followed by either a Query_log_event with the
# query equal to COMMIT, or a Xid_log_event.
#
# A single event is specified using either just the 'Type' field
# of SHOW BINLOG EVENTS, or using both the 'Type' and the 'Info'
# fields:
#
# Query
# Match any query_log_event
# Query/BEGIN
# Match any query_log_event where the query is equal to BEGIN.
# Query/INSERT.*
# Match any query_log_event where the query begins with INSERT.
#
# A sequence of events is specified by separating multiple event
# specifications with a # character:
#
# Query/BEGIN # Query/INSERT.* # Query/COMMIT
#
# Regular expressions can span multiple events:
#
# Query/BEGIN # (Query/INSERT.*){1,2} # Query/COMMIT
# Allow either one or two INSERT statements
#
# The '.' wildcard does *not* match the special characters # or /.
# So the following is safe in the sense that the .* cannot
# 'swallow' multiple events:
#
# Query/INSERT.*
#
# The pattern must match from the beginning until the end. To
# allow arbitrary events after the pattern, append '(#.*)*' to the
# pattern.
#
# The following shortcuts are available as syntactic sugar:
#
# !Q(STATEMENT) -> Query/(use .*; )?STATEMENT
# !Begin -> !Q(BEGIN)
# !Commit -> !Q(COMMIT)
# !Insert -> (!Q(INSERT.*) | Table_map # Write_rows)
# !Update -> (!Q(UPDATE.*) | Table_map # Update_rows)
# !Delete -> (!Q(DELETE.*) | Table_map # Delete_rows)
# !Single_DML -> (!Insert | !Update | !Delete)
# !Multi_DML -> multiple DML statements, each possibly touching multiple tables
# !DML_transaction -> Begin # Multi_DML # Commit
# !DDL -> !Q(neither BEGIN or COMMIT)
# !Gtid_transaction -> Gtid # (DML_transaction | DDL)
# !Empty_gtid_transaction -> Gtid # Begin # Commit
#
# Whitespace around / and # is ignored.
#
# $event_separator
# Use $event_separator instead of # to delimit events. This must
# only be one character long and should not be used elsewhere in
# the pattern.
#
# $invert
# By default, this script asserts that SHOW BINLOG EVENTS matches
# the specification. If $invert is set, this scripts instead asserts
# that SHOW BINLOG EVENTS does *not* match the specification.
#
# $limit
# Pass this as the LIMIT clause of SHOW BINLOG EVENTS.
#
# $binlog_position
# Pass this as the FROM clause of SHOW BINLOG EVENTS.
# Note that you can use include/save_binlog_position.inc to read
# both $binlog_file and $binlog_position.
#
# $binlog_file
# Pass this as the IN clause of SHOW BINLOG EVENTS.
# Note that you can use include/save_binlog_position.inc to read
# both $binlog_file and $binlog_position.
#
# $relay_log
# Use SHOW RELAYLOG EVENTS instead of SHOW BINLOG EVENTS
#
# $include_header_events
# By default, Format_desc, Rotate, and Previous_gtids events are
# ignore. If this parameter is enabled, the events are included
# in the match.
#
# $wait_for_binlog_events
# If this is true, the script retries until the binlog contains
# the expected events. The timeout is given by $slave_timeout.
#
# $slave_timeout
# Default timeout used if $wait_for_binlog_events is enabled.
# The default timeout is 300 seconds. You can change the timeout by
# setting $slave_timeout. The unit for time is seconds.
#
# $rpl_debug
# Print lots of debug info.
#
# $keep_temp_files
# Keep the two temporary files that this script uses. This is for
# debugging only and should not be used in a checked-in version of
# a test.
#
# $dont_print_pattern
# By default, the pattern is printed to the result log. If this
# variable is set, the pattern is not printed.
if ($dont_print_pattern)
{
--let $include_filename= assert_binlog_events.inc
}
if (!$dont_print_pattern)
{
--let $include_filename= assert_binlog_events.inc [$event_sequence]
}
--source include/begin_include_file.inc
if (!$event_sequence)
{
--die ERROR IN TEST: specify $event_sequence before sourcing assert_binlog_events.inc. To assert that nothing was generated, set $event_sequence= ()
}
# Execute statement, write result to file.
if (!$relay_log)
{
--let $statement= SHOW BINLOG EVENTS
}
if ($relay_log)
{
--let $statement= SHOW RELAYLOG EVENTS
}
if ($binlog_file)
{
--let $statement= $statement IN '$binlog_file'
}
if ($binlog_position)
{
--let $statement= $statement FROM $binlog_position
}
if ($limit != "")
{
--let $statement= $statement LIMIT $limit
}
if ($wait_for_binlog_events)
{
--let $_abe_counter= 0
--let $_abe_timeout= $slave_timeout
# Wait 300 seconds for binlog events
if (!$_abe_timeout)
{
--let $_abe_timeout= 300
}
}
--let $_abe_verdict=
while ($_abe_verdict != 'ok')
{
--let $output_file= GENERATE
--source include/write_result_to_file.inc
if ($rpl_debug)
{
--echo Wrote output to $output_file
}
# Set environment variables used in perl.
--let _ABE_FILE= $output_file
--let _ABE_EVENT_SEQUENCE= $event_sequence
--let _ABE_INVERT= $invert
--let _ABE_DEBUG= $rpl_debug
--let _ABE_EVENT_SEPARATOR= $event_separator
--let _ABE_INCLUDE_HEADER_EVENTS= $include_header_events
############################################################################
perl;
my $event_sequence = $ENV{'_ABE_EVENT_SEQUENCE'};
my $file = $ENV{'_ABE_FILE'};
my $invert = $ENV{'_ABE_INVERT'};
my $debug = $ENV{'_ABE_DEBUG'};
my $include_header_events = $ENV{'_ABE_INCLUDE_HEADER_EVENTS'};
my $event_separator= $ENV{'_ABE_EVENT_SEPARATOR'};
if ($event_separator == '')
{
$event_separator = '#';
}
$event_sequence =~ s/$event_separator/\n/g;
# Ignore whitespace at beginning, end, and around separators.
$event_sequence =~ s{^\s*}{};
$event_sequence =~ s{\s*$}{};
$event_sequence =~ s{\s*\n\s*}{\n}g;
$event_sequence =~ s{\s*/\s*}{/}g;
# Expand syntactic sugar definitions.
$event_sequence =~ s{!Gtid_transaction}{Gtid\n(!DDL|!DML_transaction)}g;
$event_sequence =~ s{!Empty_gtid_transaction}{Gtid\n!Begin\n!Commit}g;
$event_sequence =~ s{!DML_transaction}{!Begin\n!Multi_DML\n!Commit}g;
$event_sequence =~ s{!DDL}{Query/(?!BEGIN|COMMIT).*}g;
$event_sequence =~ s{!Single_DML}{(?:Query|Table_map\n(?:Write|Update|Delete)_rows)}g;
$event_sequence =~ s{!Multi_DML}{(?:Query|(?:Table_map\n)+(?:(?:Write|Update|Delete)_rows))(?:\nTable_map|\n(?:Write|Update|Delete)_rows|\nQuery)*}g;
$event_sequence =~ s{!Begin}{Query/BEGIN}g;
$event_sequence =~ s{!Commit}{(?:Query/COMMIT|Xid/COMMIT.*)}g;
$event_sequence =~ s{!Insert}{(?:!Q(INSERT.*)|Table_map\nWrite_rows)}g;
$event_sequence =~ s{!Update}{(?:!Q(UPDATE.*)|Table_map\nUpdate_rows)}g;
$event_sequence =~ s{!Delete}{(?:!Q(DELETE.*)|Table_map\nDelete_rows)}g;
$event_sequence =~ s{!Q\(([^\n]+)\)}{Query/(?:use.*; )?$1}g;
# Allow matching 'Type' without 'Info'
# (e.g., 'Query' instead of 'Query/xyz').
$event_sequence =~ s{\n}{(?:/[^\n]*)?\n}g;
# Allow 'Type' without 'Info' at the end. Require match until the
# end. Allow missing \n at the end.
$event_sequence .= "(?:/[^\n]*)?\n?" . '$';
# Require match from the beginning.
$event_sequence = '^' . $event_sequence;
if ($debug)
{
print "Regex: $event_sequence\n";
}
# Read and filter file.
my $result= '';
open FILE, "< $file" or die "Error $? opening $file: $!";
my $line_number= 1;
while ()
{
if ($line_number > 1)
{
# Six tab-separated fields; pick number 3 and number 6.
s{^[^\t]+\t[^\t]+\t([^\t]+)\t[^\t]+\t[^\t]+\t([^\t]*)$}{$1/$2}
or die "Unexpected line format in output line $line_number: $_";
if ($include_header_events or
($1 ne 'Format_desc' && $1 ne 'Rotate' && $1 ne 'Previous_gtids'))
{
chomp;
$result .= $_;
$result .= "\n";
}
}
$line_number++;
}
close FILE or die "Error $? closing $file: $!";
if ($debug)
{
print "Formatted output: $result\n";
}
# Check if there is a match
my $matches = eval("\$result =~ m{$event_sequence}");
my $is_ok = ($matches == !$invert);
# Write 'ok', or write some debug info.
open FILE, "> $file.verdict" or die "Error $? opening $file.verdict: $!";
print FILE ($is_ok ? 'ok' :
"Regex:\n$event_sequence\nFile contents:\n$result")
or die "Error $? writing to $file.verdict: $!";
close FILE or die "Error $? writing to $file.verdict: $!";
EOF
############################################################################
--let $_abe_verdict= `SELECT LOAD_FILE('$output_file.verdict')`
if ($_abe_verdict != 'ok')
{
--let $_abe_fail= 1
if ($wait_for_binlog_events)
{
--sleep 1
--inc $_abe_counter
if ($_abe_counter < $_abe_timeout)
{
--let $_abe_fail= 0
}
}
if ($_abe_fail)
{
--source include/show_rpl_debug_info.inc
--echo event_sequence=$event_sequence
--echo $_abe_verdict
--echo statement=$statement
--echo invert=$invert
--echo include_header_events=$include_header_events
--echo event_separator=$_ABE_EVENT_SEPARATOR
--die Binlog contents did not match expected pattern.
}
}
if (!$keep_temp_files)
{
--remove_file $output_file
--remove_file $output_file.verdict
}
--let $output_file=
}
--let $include_filename= assert_binlog_events.inc
--source include/end_include_file.inc