459 lines
16 KiB
C++
459 lines
16 KiB
C++
/* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include "sql/rpl_trx_boundary_parser.h"
|
|
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "libbinlogevents/include/binlog_event.h"
|
|
#include "m_string.h"
|
|
#include "my_byteorder.h"
|
|
#include "my_dbug.h"
|
|
#include "my_loglevel.h"
|
|
#include "mysql/components/services/log_builtins.h"
|
|
#include "mysqld_error.h"
|
|
#include "sql/log.h"
|
|
#include "sql/log_event.h" // Log_event
|
|
|
|
#ifndef DBUG_OFF
|
|
/* Event parser state names */
|
|
static const char *event_parser_state_names[] = {"None", "GTID", "DDL", "DML",
|
|
"Error"};
|
|
#endif
|
|
|
|
/*
|
|
-----------------------------------------
|
|
Transaction_boundary_parser class methods
|
|
-----------------------------------------
|
|
*/
|
|
|
|
/**
|
|
Reset the transaction boundary parser.
|
|
|
|
This method initialize the boundary parser state.
|
|
*/
|
|
void Transaction_boundary_parser::reset() {
|
|
DBUG_TRACE;
|
|
DBUG_PRINT("info", ("transaction boundary parser is changing state "
|
|
"from '%s' to '%s'",
|
|
event_parser_state_names[current_parser_state],
|
|
event_parser_state_names[EVENT_PARSER_NONE]));
|
|
current_parser_state = EVENT_PARSER_NONE;
|
|
last_parser_state = EVENT_PARSER_NONE;
|
|
}
|
|
|
|
/**
|
|
Feed the transaction boundary parser with a Log_event of any type,
|
|
serialized into a char* buffer.
|
|
|
|
@param buf Pointer to the event buffer.
|
|
@param length The size of the event buffer.
|
|
@param fd_event The description event of the master which logged
|
|
the event.
|
|
@param throw_warnings If the function should throw warning messages while
|
|
updating the boundary parser state.
|
|
While initializing the Relay_log_info the
|
|
relay log is scanned backwards and this could
|
|
generate false warnings. So, in this case, we
|
|
don't want to throw warnings.
|
|
|
|
@return false if the transaction boundary parser accepted the event.
|
|
true if the transaction boundary parser didn't accepted the event.
|
|
*/
|
|
bool Transaction_boundary_parser::feed_event(
|
|
const char *buf, size_t length, const Format_description_event *fd_event,
|
|
bool throw_warnings) {
|
|
DBUG_TRACE;
|
|
enum_event_boundary_type event_boundary_type =
|
|
get_event_boundary_type(buf, length, fd_event, throw_warnings);
|
|
return update_state(event_boundary_type, throw_warnings);
|
|
}
|
|
|
|
/**
|
|
Get the boundary type for a given Log_event of any type,
|
|
serialized into a char* buffer, based on event parser logic.
|
|
|
|
@param buf Pointer to the event buffer.
|
|
@param length The size of the event buffer.
|
|
@param fd_event The description event of the master which logged
|
|
the event.
|
|
@param throw_warnings If the function should throw warnings getting the
|
|
event boundary type.
|
|
Please see comments on this at feed_event().
|
|
|
|
@return the transaction boundary type of the event.
|
|
*/
|
|
Transaction_boundary_parser::enum_event_boundary_type
|
|
Transaction_boundary_parser::get_event_boundary_type(
|
|
const char *buf, size_t length, const Format_description_event *fd_event,
|
|
bool throw_warnings) {
|
|
DBUG_TRACE;
|
|
|
|
Log_event_type event_type;
|
|
enum_event_boundary_type boundary_type = EVENT_BOUNDARY_TYPE_ERROR;
|
|
uint header_size = fd_event->common_header_len;
|
|
|
|
/* Error if the event content is smaller than header size for the format */
|
|
if (length < header_size) goto end;
|
|
|
|
event_type = (Log_event_type)buf[EVENT_TYPE_OFFSET];
|
|
DBUG_PRINT("info", ("trx boundary parser was fed with an event of type %s",
|
|
Log_event::get_type_str(event_type)));
|
|
|
|
switch (event_type) {
|
|
case binary_log::GTID_LOG_EVENT:
|
|
case binary_log::ANONYMOUS_GTID_LOG_EVENT:
|
|
boundary_type = EVENT_BOUNDARY_TYPE_GTID;
|
|
break;
|
|
|
|
/*
|
|
There are four types of queries that we have to deal with: BEGIN, COMMIT,
|
|
ROLLBACK and the rest.
|
|
*/
|
|
case binary_log::QUERY_EVENT: {
|
|
const char *query = nullptr;
|
|
size_t qlen = 0;
|
|
/* Get the query to let us check for BEGIN/COMMIT/ROLLBACK */
|
|
qlen = Query_log_event::get_query(buf, length, fd_event, &query);
|
|
if (qlen == 0) {
|
|
DBUG_ASSERT(query == nullptr);
|
|
boundary_type = EVENT_BOUNDARY_TYPE_ERROR;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
BEGIN is always the begin of a DML transaction.
|
|
*/
|
|
if (!strncmp(query, "BEGIN", qlen) ||
|
|
!strncmp(query, STRING_WITH_LEN("XA START")))
|
|
boundary_type = EVENT_BOUNDARY_TYPE_BEGIN_TRX;
|
|
/*
|
|
COMMIT and ROLLBACK are always the end of a transaction.
|
|
*/
|
|
else if (!strncmp(query, "COMMIT", qlen) ||
|
|
(!native_strncasecmp(query, STRING_WITH_LEN("ROLLBACK")) &&
|
|
native_strncasecmp(query, STRING_WITH_LEN("ROLLBACK TO "))))
|
|
boundary_type = EVENT_BOUNDARY_TYPE_END_TRX;
|
|
/*
|
|
XA ROLLBACK is always the end of a XA transaction.
|
|
*/
|
|
else if (!native_strncasecmp(query, STRING_WITH_LEN("XA ROLLBACK")))
|
|
boundary_type = EVENT_BOUNDARY_TYPE_END_XA_TRX;
|
|
/*
|
|
If the query is not (BEGIN | XA START | COMMIT | [XA] ROLLBACK), it can
|
|
be considered an ordinary statement.
|
|
*/
|
|
else
|
|
boundary_type = EVENT_BOUNDARY_TYPE_STATEMENT;
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
XID events are always the end of a transaction.
|
|
*/
|
|
case binary_log::XID_EVENT:
|
|
boundary_type = EVENT_BOUNDARY_TYPE_END_TRX;
|
|
break;
|
|
/*
|
|
XA_prepare event ends XA-prepared group of events (prepared XA
|
|
transaction).
|
|
*/
|
|
case binary_log::XA_PREPARE_LOG_EVENT:
|
|
boundary_type = EVENT_BOUNDARY_TYPE_END_TRX;
|
|
break;
|
|
|
|
/*
|
|
Intvar, Rand and User_var events are always considered as pre-statements.
|
|
*/
|
|
case binary_log::INTVAR_EVENT:
|
|
case binary_log::RAND_EVENT:
|
|
case binary_log::USER_VAR_EVENT:
|
|
boundary_type = EVENT_BOUNDARY_TYPE_PRE_STATEMENT;
|
|
break;
|
|
|
|
/*
|
|
The following event types are always considered as statements
|
|
because they will always be wrapped between BEGIN/COMMIT.
|
|
*/
|
|
case binary_log::EXECUTE_LOAD_QUERY_EVENT:
|
|
case binary_log::TABLE_MAP_EVENT:
|
|
case binary_log::APPEND_BLOCK_EVENT:
|
|
case binary_log::BEGIN_LOAD_QUERY_EVENT:
|
|
case binary_log::ROWS_QUERY_LOG_EVENT:
|
|
case binary_log::WRITE_ROWS_EVENT:
|
|
case binary_log::UPDATE_ROWS_EVENT:
|
|
case binary_log::DELETE_ROWS_EVENT:
|
|
case binary_log::WRITE_ROWS_EVENT_V1:
|
|
case binary_log::UPDATE_ROWS_EVENT_V1:
|
|
case binary_log::DELETE_ROWS_EVENT_V1:
|
|
case binary_log::VIEW_CHANGE_EVENT:
|
|
case binary_log::PARTIAL_UPDATE_ROWS_EVENT:
|
|
boundary_type = EVENT_BOUNDARY_TYPE_STATEMENT;
|
|
break;
|
|
|
|
/*
|
|
Incident events have their own boundary type.
|
|
*/
|
|
case binary_log::INCIDENT_EVENT:
|
|
boundary_type = EVENT_BOUNDARY_TYPE_INCIDENT;
|
|
break;
|
|
|
|
/*
|
|
Rotate, Format_description and Heartbeat should be ignored.
|
|
Also, any other kind of event not listed in the "cases" above
|
|
will be ignored.
|
|
*/
|
|
case binary_log::ROTATE_EVENT:
|
|
case binary_log::FORMAT_DESCRIPTION_EVENT:
|
|
case binary_log::HEARTBEAT_LOG_EVENT:
|
|
case binary_log::PREVIOUS_GTIDS_LOG_EVENT:
|
|
case binary_log::PREVIOUS_CONSENSUS_INDEX_LOG_EVENT:
|
|
case binary_log::STOP_EVENT:
|
|
case binary_log::SLAVE_EVENT:
|
|
case binary_log::DELETE_FILE_EVENT:
|
|
case binary_log::TRANSACTION_CONTEXT_EVENT:
|
|
boundary_type = EVENT_BOUNDARY_TYPE_IGNORE;
|
|
break;
|
|
|
|
/*
|
|
If the event is none of above supported event types, this is probably
|
|
an event type unsupported by this server version. So, we must check if
|
|
this event is ignorable or not.
|
|
*/
|
|
default:
|
|
if (uint2korr(buf + FLAGS_OFFSET) & LOG_EVENT_IGNORABLE_F)
|
|
boundary_type = EVENT_BOUNDARY_TYPE_IGNORE;
|
|
else {
|
|
boundary_type = EVENT_BOUNDARY_TYPE_ERROR;
|
|
if (throw_warnings)
|
|
LogErr(WARNING_LEVEL, ER_RPL_UNSUPPORTED_UNIGNORABLE_EVENT_IN_STREAM);
|
|
}
|
|
} /* End of switch(event_type) */
|
|
|
|
end:
|
|
return boundary_type;
|
|
}
|
|
|
|
/**
|
|
Update the boundary parser state based on a given boundary type.
|
|
|
|
@param event_boundary_type The event boundary type of the event used to
|
|
fed the boundary parser.
|
|
@param throw_warnings If the function should throw warnings while
|
|
updating the boundary parser state.
|
|
Please see comments on this at feed_event().
|
|
|
|
@return false State updated successfully.
|
|
true There was an error updating the state.
|
|
*/
|
|
bool Transaction_boundary_parser::update_state(
|
|
enum_event_boundary_type event_boundary_type, bool throw_warnings) {
|
|
DBUG_TRACE;
|
|
|
|
enum_event_parser_state new_parser_state = EVENT_PARSER_NONE;
|
|
|
|
bool error = false;
|
|
|
|
switch (event_boundary_type) {
|
|
/*
|
|
GTIDs are always the start of a transaction stream.
|
|
*/
|
|
case EVENT_BOUNDARY_TYPE_GTID:
|
|
/* In any case, we will update the state to GTID */
|
|
new_parser_state = EVENT_PARSER_GTID;
|
|
/* The following switch is mostly to differentiate the warning messages */
|
|
switch (current_parser_state) {
|
|
case EVENT_PARSER_GTID:
|
|
case EVENT_PARSER_DDL:
|
|
case EVENT_PARSER_DML:
|
|
if (throw_warnings)
|
|
LogErr(WARNING_LEVEL, ER_RPL_GTID_LOG_EVENT_IN_STREAM,
|
|
current_parser_state == EVENT_PARSER_GTID
|
|
? "after a GTID_LOG_EVENT or an ANONYMOUS_GTID_LOG_EVENT"
|
|
: current_parser_state == EVENT_PARSER_DDL
|
|
? "in the middle of a DDL"
|
|
: "in the middle of a DML"); /* EVENT_PARSER_DML */
|
|
error = true;
|
|
break;
|
|
case EVENT_PARSER_ERROR: /* we probably threw a warning before */
|
|
error = true;
|
|
/* FALL THROUGH */
|
|
case EVENT_PARSER_NONE:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
/*
|
|
There are four types of queries that we have to deal with: BEGIN, COMMIT,
|
|
ROLLBACK and the rest.
|
|
*/
|
|
case EVENT_BOUNDARY_TYPE_BEGIN_TRX:
|
|
/* In any case, we will update the state to DML */
|
|
new_parser_state = EVENT_PARSER_DML;
|
|
/* The following switch is mostly to differentiate the warning messages */
|
|
switch (current_parser_state) {
|
|
case EVENT_PARSER_DDL:
|
|
case EVENT_PARSER_DML:
|
|
if (throw_warnings)
|
|
LogErr(WARNING_LEVEL, ER_RPL_UNEXPECTED_BEGIN_IN_STREAM,
|
|
current_parser_state == EVENT_PARSER_DDL ? "DDL" : "DML");
|
|
error = true;
|
|
break;
|
|
case EVENT_PARSER_ERROR: /* we probably threw a warning before */
|
|
error = true;
|
|
/* FALL THROUGH */
|
|
case EVENT_PARSER_NONE:
|
|
case EVENT_PARSER_GTID:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case EVENT_BOUNDARY_TYPE_END_TRX:
|
|
/* In any case, we will update the state to NONE */
|
|
new_parser_state = EVENT_PARSER_NONE;
|
|
/* The following switch is mostly to differentiate the warning messages */
|
|
switch (current_parser_state) {
|
|
case EVENT_PARSER_NONE:
|
|
case EVENT_PARSER_GTID:
|
|
case EVENT_PARSER_DDL:
|
|
if (throw_warnings)
|
|
LogErr(WARNING_LEVEL,
|
|
ER_RPL_UNEXPECTED_COMMIT_ROLLBACK_OR_XID_LOG_EVENT_IN_STREAM,
|
|
current_parser_state == EVENT_PARSER_NONE
|
|
? "outside a transaction"
|
|
: current_parser_state == EVENT_PARSER_GTID
|
|
? "after a GTID_LOG_EVENT"
|
|
: "in the middle of a DDL"); /* EVENT_PARSER_DDL */
|
|
error = true;
|
|
break;
|
|
case EVENT_PARSER_ERROR: /* we probably threw a warning before */
|
|
error = true;
|
|
/* FALL THROUGH */
|
|
case EVENT_PARSER_DML:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case EVENT_BOUNDARY_TYPE_END_XA_TRX:
|
|
/* In any case, we will update the state to NONE */
|
|
new_parser_state = EVENT_PARSER_NONE;
|
|
/* The following switch is mostly to differentiate the warning messages */
|
|
switch (current_parser_state) {
|
|
case EVENT_PARSER_NONE:
|
|
case EVENT_PARSER_DDL:
|
|
if (throw_warnings)
|
|
LogErr(WARNING_LEVEL, ER_RPL_UNEXPECTED_XA_ROLLBACK_IN_STREAM,
|
|
current_parser_state == EVENT_PARSER_NONE
|
|
? "outside a transaction"
|
|
: "in the middle of a DDL"); /* EVENT_PARSER_DDL */
|
|
error = true;
|
|
break;
|
|
case EVENT_PARSER_ERROR: /* we probably threw a warning before */
|
|
error = true;
|
|
/* FALL THROUGH */
|
|
case EVENT_PARSER_DML:
|
|
/* XA ROLLBACK can appear after a GTID event */
|
|
case EVENT_PARSER_GTID:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case EVENT_BOUNDARY_TYPE_STATEMENT:
|
|
switch (current_parser_state) {
|
|
case EVENT_PARSER_NONE:
|
|
new_parser_state = EVENT_PARSER_NONE;
|
|
break;
|
|
case EVENT_PARSER_GTID:
|
|
case EVENT_PARSER_DDL:
|
|
new_parser_state = EVENT_PARSER_NONE;
|
|
break;
|
|
case EVENT_PARSER_DML:
|
|
new_parser_state = current_parser_state;
|
|
break;
|
|
case EVENT_PARSER_ERROR: /* we probably threw a warning before */
|
|
error = true;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
/*
|
|
Intvar, Rand and User_var events might be inside of a transaction stream
|
|
if any Intvar, Rand and User_var was fed before, if BEGIN was fed before
|
|
or if GTID was fed before. In the case of no GTID, no BEGIN and no
|
|
previous Intvar, Rand or User_var it will be considered the start of a
|
|
transaction stream.
|
|
*/
|
|
case EVENT_BOUNDARY_TYPE_PRE_STATEMENT:
|
|
switch (current_parser_state) {
|
|
case EVENT_PARSER_NONE:
|
|
case EVENT_PARSER_GTID:
|
|
new_parser_state = EVENT_PARSER_DDL;
|
|
break;
|
|
case EVENT_PARSER_DDL:
|
|
case EVENT_PARSER_DML:
|
|
new_parser_state = current_parser_state;
|
|
break;
|
|
case EVENT_PARSER_ERROR: /* we probably threw a warning before */
|
|
error = true;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
/*
|
|
Incident events can happen without a GTID (before BUG#19594845 fix) or
|
|
with its own GTID in order to be skipped. In any case, it should always
|
|
mark "the end" of a transaction.
|
|
*/
|
|
case EVENT_BOUNDARY_TYPE_INCIDENT:
|
|
/* In any case, we will update the state to NONE */
|
|
new_parser_state = EVENT_PARSER_NONE;
|
|
break;
|
|
|
|
/*
|
|
Rotate, Format_description and Heartbeat should be ignored.
|
|
The rotate might be fake, like when the IO thread receives from dump
|
|
thread Previous_gtid and Heartbeat events due to reconnection/auto
|
|
positioning.
|
|
*/
|
|
case EVENT_BOUNDARY_TYPE_IGNORE:
|
|
new_parser_state = current_parser_state;
|
|
break;
|
|
|
|
case EVENT_BOUNDARY_TYPE_ERROR:
|
|
error = true;
|
|
new_parser_state = EVENT_PARSER_ERROR;
|
|
break;
|
|
}
|
|
|
|
DBUG_PRINT("info", ("transaction boundary parser is changing state "
|
|
"from '%s' to '%s'",
|
|
event_parser_state_names[current_parser_state],
|
|
event_parser_state_names[new_parser_state]));
|
|
|
|
last_parser_state = current_parser_state;
|
|
current_parser_state = new_parser_state;
|
|
|
|
return error;
|
|
}
|