1 /* Copyright (c) 2015, 2021, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 #include "rpl_trx_boundary_parser.h"
24
25 #include "log.h" // sql_print_warning
26 #include "log_event.h" // Log_event
27
28
29 #ifndef NDEBUG
30 /* Event parser state names */
31 static const char *event_parser_state_names[]= {
32 "None",
33 "GTID",
34 "DDL",
35 "DML",
36 "Error"
37 };
38 #endif
39
40 /*
41 -----------------------------------------
42 Transaction_boundary_parser class methods
43 -----------------------------------------
44 */
45
46 /**
47 Reset the transaction boundary parser.
48
49 This method initialize the boundary parser state.
50 */
reset()51 void Transaction_boundary_parser::reset()
52 {
53 DBUG_ENTER("Transaction_boundary_parser::reset");
54 DBUG_PRINT("info", ("transaction boundary parser is changing state "
55 "from '%s' to '%s'",
56 event_parser_state_names[current_parser_state],
57 event_parser_state_names[EVENT_PARSER_NONE]));
58 current_parser_state= EVENT_PARSER_NONE;
59 DBUG_VOID_RETURN;
60 }
61
62 /**
63 Feed the transaction boundary parser with a Log_event of any type,
64 serialized into a char* buffer.
65
66 @param buf Pointer to the event buffer.
67 @param length The size of the event buffer.
68 @param fd_event The description event of the master which logged
69 the event.
70 @param throw_warnings If the function should throw warning messages while
71 updating the boundary parser state.
72 While initializing the Relay_log_info the
73 relay log is scanned backwards and this could
74 generate false warnings. So, in this case, we
75 don't want to throw warnings.
76
77 @return false if the transaction boundary parser accepted the event.
78 true if the transaction boundary parser didn't accepted the event.
79 */
feed_event(const char * buf,size_t length,const Format_description_log_event * fd_event,bool throw_warnings)80 bool Transaction_boundary_parser::feed_event(const char *buf, size_t length,
81 const Format_description_log_event
82 *fd_event,
83 bool throw_warnings)
84 {
85 DBUG_ENTER("Transaction_boundary_parser::feed_event");
86 enum_event_boundary_type event_boundary_type=
87 get_event_boundary_type(buf, length, fd_event, throw_warnings);
88 DBUG_RETURN(update_state(event_boundary_type, throw_warnings));
89 }
90
91 /**
92 Get the boundary type for a given Log_event of any type,
93 serialized into a char* buffer, based on event parser logic.
94
95 @param buf Pointer to the event buffer.
96 @param length The size of the event buffer.
97 @param description_event The description event of the master which logged
98 the event.
99 @param throw_warnings If the function should throw warnings getting the
100 event boundary type.
101 Please see comments on this at feed_event().
102
103 @return the transaction boundary type of the event.
104 */
105 Transaction_boundary_parser::enum_event_boundary_type
get_event_boundary_type(const char * buf,size_t length,const Format_description_log_event * fd_event,bool throw_warnings)106 Transaction_boundary_parser::get_event_boundary_type(
107 const char *buf, size_t length, const Format_description_log_event *fd_event,
108 bool throw_warnings)
109 {
110 DBUG_ENTER("Transaction_boundary_parser::get_event_boundary_type");
111
112 Log_event_type event_type;
113 enum_event_boundary_type boundary_type= EVENT_BOUNDARY_TYPE_ERROR;
114 uint header_size= fd_event->common_header_len;
115
116 /* Error if the event content is smaller than header size for the format */
117 if (length < header_size)
118 goto end;
119
120 event_type= (Log_event_type)static_cast<unsigned char>(buf[EVENT_TYPE_OFFSET]);
121 DBUG_PRINT("info",("trx boundary parser was fed with an event of type %s",
122 Log_event::get_type_str(event_type)));
123
124 switch (event_type)
125 {
126 case binary_log::GTID_LOG_EVENT:
127 case binary_log::ANONYMOUS_GTID_LOG_EVENT:
128 boundary_type= EVENT_BOUNDARY_TYPE_GTID;
129 break;
130
131 /*
132 There are four types of queries that we have to deal with: BEGIN, COMMIT,
133 ROLLBACK and the rest.
134 */
135 case binary_log::QUERY_EVENT:
136 {
137 char *query= NULL;
138 size_t qlen= 0;
139 /* Get the query to let us check for BEGIN/COMMIT/ROLLBACK */
140 qlen= Query_log_event::get_query(buf, length, fd_event, &query);
141 if (qlen == 0)
142 {
143 assert(query == NULL);
144 boundary_type= EVENT_BOUNDARY_TYPE_ERROR;
145 break;
146 }
147
148 /*
149 BEGIN is always the begin of a DML transaction.
150 */
151 if (!strncmp(query, "BEGIN", qlen) ||
152 !strncmp(query, STRING_WITH_LEN("XA START")))
153 boundary_type= EVENT_BOUNDARY_TYPE_BEGIN_TRX;
154 /*
155 COMMIT and ROLLBACK are always the end of a transaction.
156 */
157 else if (!strncmp(query, "COMMIT", qlen) ||
158 (!native_strncasecmp(query, STRING_WITH_LEN("ROLLBACK")) &&
159 native_strncasecmp(query, STRING_WITH_LEN("ROLLBACK TO "))))
160 boundary_type= EVENT_BOUNDARY_TYPE_END_TRX;
161 /*
162 XA ROLLBACK is always the end of a XA transaction.
163 */
164 else if (!native_strncasecmp(query, STRING_WITH_LEN("XA ROLLBACK")))
165 boundary_type= EVENT_BOUNDARY_TYPE_END_XA_TRX;
166 /*
167 If the query is not (BEGIN | XA START | COMMIT | [XA] ROLLBACK), it can
168 be considered an ordinary statement.
169 */
170 else
171 boundary_type= EVENT_BOUNDARY_TYPE_STATEMENT;
172
173 break;
174 }
175
176 /*
177 XID events are always the end of a transaction.
178 */
179 case binary_log::XID_EVENT:
180 boundary_type= EVENT_BOUNDARY_TYPE_END_TRX;
181 break;
182 /*
183 XA_prepare event ends XA-prepared group of events (prepared XA transaction).
184 */
185 case binary_log::XA_PREPARE_LOG_EVENT:
186 boundary_type= EVENT_BOUNDARY_TYPE_END_TRX;
187 break;
188
189 /*
190 Intvar, Rand and User_var events are always considered as pre-statements.
191 */
192 case binary_log::INTVAR_EVENT:
193 case binary_log::RAND_EVENT:
194 case binary_log::USER_VAR_EVENT:
195 boundary_type= EVENT_BOUNDARY_TYPE_PRE_STATEMENT;
196 break;
197
198 /*
199 The following event types are always considered as statements
200 because they will always be wrapped between BEGIN/COMMIT.
201 */
202 case binary_log::EXECUTE_LOAD_QUERY_EVENT:
203 case binary_log::TABLE_MAP_EVENT:
204 case binary_log::APPEND_BLOCK_EVENT:
205 case binary_log::BEGIN_LOAD_QUERY_EVENT:
206 case binary_log::ROWS_QUERY_LOG_EVENT:
207 case binary_log::WRITE_ROWS_EVENT:
208 case binary_log::UPDATE_ROWS_EVENT:
209 case binary_log::DELETE_ROWS_EVENT:
210 case binary_log::WRITE_ROWS_EVENT_V1:
211 case binary_log::UPDATE_ROWS_EVENT_V1:
212 case binary_log::DELETE_ROWS_EVENT_V1:
213 case binary_log::PRE_GA_WRITE_ROWS_EVENT:
214 case binary_log::PRE_GA_DELETE_ROWS_EVENT:
215 case binary_log::PRE_GA_UPDATE_ROWS_EVENT:
216 case binary_log::VIEW_CHANGE_EVENT:
217 boundary_type= EVENT_BOUNDARY_TYPE_STATEMENT;
218 break;
219
220 /*
221 Incident events have their own boundary type.
222 */
223 case binary_log::INCIDENT_EVENT:
224 boundary_type= EVENT_BOUNDARY_TYPE_INCIDENT;
225 break;
226
227 /*
228 Rotate, Format_description and Heartbeat should be ignored.
229 Also, any other kind of event not listed in the "cases" above
230 will be ignored.
231 */
232 case binary_log::ROTATE_EVENT:
233 case binary_log::FORMAT_DESCRIPTION_EVENT:
234 case binary_log::HEARTBEAT_LOG_EVENT:
235 case binary_log::PREVIOUS_GTIDS_LOG_EVENT:
236 case binary_log::START_EVENT_V3:
237 case binary_log::STOP_EVENT:
238 case binary_log::LOAD_EVENT:
239 case binary_log::SLAVE_EVENT:
240 case binary_log::CREATE_FILE_EVENT:
241 case binary_log::DELETE_FILE_EVENT:
242 case binary_log::NEW_LOAD_EVENT:
243 case binary_log::EXEC_LOAD_EVENT:
244 case binary_log::TRANSACTION_CONTEXT_EVENT:
245 case binary_log::START_ENCRYPTION_EVENT:
246 boundary_type= EVENT_BOUNDARY_TYPE_IGNORE;
247 break;
248
249 /*
250 If the event is none of above supported event types, this is probably
251 an event type unsupported by this server version. So, we must check if
252 this event is ignorable or not.
253 */
254 default:
255 if (uint2korr(buf + FLAGS_OFFSET) & LOG_EVENT_IGNORABLE_F)
256 boundary_type= EVENT_BOUNDARY_TYPE_IGNORE;
257 else
258 {
259 boundary_type= EVENT_BOUNDARY_TYPE_ERROR;
260 if (throw_warnings)
261 sql_print_warning(
262 "Unsupported non-ignorable event fed into the "
263 "event stream.");
264 }
265 } /* End of switch(event_type) */
266
267 end:
268 DBUG_RETURN(boundary_type);
269 }
270
271 /**
272 Update the boundary parser state based on a given boundary type.
273
274 @param event_boundary_type The event boundary type of the event used to
275 fed the boundary parser.
276 @param throw_warnings If the function should throw warnings while
277 updating the boundary parser state.
278 Please see comments on this at feed_event().
279
280 @return false State updated successfully.
281 true There was an error updating the state.
282 */
update_state(enum_event_boundary_type event_boundary_type,bool throw_warnings)283 bool Transaction_boundary_parser::update_state(
284 enum_event_boundary_type event_boundary_type, bool throw_warnings)
285 {
286 DBUG_ENTER("Transaction_boundary_parser::update_state");
287
288 enum_event_parser_state new_parser_state= EVENT_PARSER_NONE;
289
290 bool error= false;
291
292 switch (event_boundary_type)
293 {
294 /*
295 GTIDs are always the start of a transaction stream.
296 */
297 case EVENT_BOUNDARY_TYPE_GTID:
298 /* In any case, we will update the state to GTID */
299 new_parser_state= EVENT_PARSER_GTID;
300 /* The following switch is mostly to differentiate the warning messages */
301 switch(current_parser_state) {
302 case EVENT_PARSER_GTID:
303 case EVENT_PARSER_DDL:
304 case EVENT_PARSER_DML:
305 if (throw_warnings)
306 sql_print_warning(
307 "GTID_LOG_EVENT or ANONYMOUS_GTID_LOG_EVENT "
308 "is not expected in an event stream %s.",
309 current_parser_state == EVENT_PARSER_GTID ?
310 "after a GTID_LOG_EVENT or an ANONYMOUS_GTID_LOG_EVENT" :
311 current_parser_state == EVENT_PARSER_DDL ?
312 "in the middle of a DDL" :
313 "in the middle of a DML"); /* EVENT_PARSER_DML */
314 error= true;
315 break;
316 case EVENT_PARSER_ERROR: /* we probably threw a warning before */
317 error= true;
318 /* FALL THROUGH */
319 case EVENT_PARSER_NONE:
320 break;
321 }
322 break;
323
324 /*
325 There are four types of queries that we have to deal with: BEGIN, COMMIT,
326 ROLLBACK and the rest.
327 */
328 case EVENT_BOUNDARY_TYPE_BEGIN_TRX:
329 /* In any case, we will update the state to DML */
330 new_parser_state= EVENT_PARSER_DML;
331 /* The following switch is mostly to differentiate the warning messages */
332 switch(current_parser_state) {
333 case EVENT_PARSER_DDL:
334 case EVENT_PARSER_DML:
335 if (throw_warnings)
336 sql_print_warning(
337 "QUERY(BEGIN) is not expected in an event stream "
338 "in the middle of a %s.",
339 current_parser_state == EVENT_PARSER_DDL ? "DDL" : "DML");
340 error= true;
341 break;
342 case EVENT_PARSER_ERROR: /* we probably threw a warning before */
343 error= true;
344 /* FALL THROUGH */
345 case EVENT_PARSER_NONE:
346 case EVENT_PARSER_GTID:
347 break;
348 }
349 break;
350
351 case EVENT_BOUNDARY_TYPE_END_TRX:
352 /* In any case, we will update the state to NONE */
353 new_parser_state= EVENT_PARSER_NONE;
354 /* The following switch is mostly to differentiate the warning messages */
355 switch(current_parser_state) {
356 case EVENT_PARSER_NONE:
357 case EVENT_PARSER_GTID:
358 case EVENT_PARSER_DDL:
359 if (throw_warnings)
360 sql_print_warning(
361 "QUERY(COMMIT or ROLLBACK) or "
362 "XID_LOG_EVENT is not expected "
363 "in an event stream %s.",
364 current_parser_state == EVENT_PARSER_NONE ? "outside a transaction" :
365 current_parser_state == EVENT_PARSER_GTID ? "after a GTID_LOG_EVENT" :
366 "in the middle of a DDL"); /* EVENT_PARSER_DDL */
367 error= true;
368 break;
369 case EVENT_PARSER_ERROR: /* we probably threw a warning before */
370 error= true;
371 /* FALL THROUGH */
372 case EVENT_PARSER_DML:
373 break;
374 }
375 break;
376
377 case EVENT_BOUNDARY_TYPE_END_XA_TRX:
378 /* In any case, we will update the state to NONE */
379 new_parser_state= EVENT_PARSER_NONE;
380 /* The following switch is mostly to differentiate the warning messages */
381 switch(current_parser_state) {
382 case EVENT_PARSER_NONE:
383 case EVENT_PARSER_DDL:
384 if (throw_warnings)
385 sql_print_warning(
386 "QUERY(XA ROLLBACK) is "
387 "not expected in an event stream %s.",
388 current_parser_state == EVENT_PARSER_NONE ? "outside a transaction" :
389 "in the middle of a DDL"); /* EVENT_PARSER_DDL */
390 error= true;
391 break;
392 case EVENT_PARSER_ERROR: /* we probably threw a warning before */
393 error= true;
394 /* FALL THROUGH */
395 case EVENT_PARSER_DML:
396 /* XA ROLLBACK can appear after a GTID event */
397 case EVENT_PARSER_GTID:
398 break;
399 }
400 break;
401
402 case EVENT_BOUNDARY_TYPE_STATEMENT:
403 switch(current_parser_state) {
404 case EVENT_PARSER_NONE:
405 new_parser_state= EVENT_PARSER_NONE;
406 break;
407 case EVENT_PARSER_GTID:
408 case EVENT_PARSER_DDL:
409 new_parser_state= EVENT_PARSER_NONE;
410 break;
411 case EVENT_PARSER_DML:
412 new_parser_state= current_parser_state;
413 break;
414 case EVENT_PARSER_ERROR: /* we probably threw a warning before */
415 error= true;
416 break;
417 }
418 break;
419
420 /*
421 Intvar, Rand and User_var events might be inside of a transaction stream if
422 any Intvar, Rand and User_var was fed before, if BEGIN was fed before or if
423 GTID was fed before.
424 In the case of no GTID, no BEGIN and no previous Intvar, Rand or User_var
425 it will be considered the start of a transaction stream.
426 */
427 case EVENT_BOUNDARY_TYPE_PRE_STATEMENT:
428 switch(current_parser_state) {
429 case EVENT_PARSER_NONE:
430 case EVENT_PARSER_GTID:
431 new_parser_state= EVENT_PARSER_DDL;
432 break;
433 case EVENT_PARSER_DDL:
434 case EVENT_PARSER_DML:
435 new_parser_state= current_parser_state;
436 break;
437 case EVENT_PARSER_ERROR: /* we probably threw a warning before */
438 error= true;
439 break;
440 }
441 break;
442
443 /*
444 Incident events can happen without a GTID (before BUG#19594845 fix) or
445 with its own GTID in order to be skipped. In any case, it should always
446 mark "the end" of a transaction.
447 */
448 case EVENT_BOUNDARY_TYPE_INCIDENT:
449 /* In any case, we will update the state to NONE */
450 new_parser_state= EVENT_PARSER_NONE;
451 break;
452
453 /*
454 Rotate, Format_description and Heartbeat should be ignored.
455 The rotate might be fake, like when the IO thread receives from dump thread
456 Previous_gtid and Heartbeat events due to reconnection/auto positioning.
457 */
458 case EVENT_BOUNDARY_TYPE_IGNORE:
459 new_parser_state= current_parser_state;
460 break;
461
462 case EVENT_BOUNDARY_TYPE_ERROR:
463 error= true;
464 new_parser_state= EVENT_PARSER_ERROR;
465 break;
466 }
467
468 DBUG_PRINT("info", ("transaction boundary parser is changing state "
469 "from '%s' to '%s'",
470 event_parser_state_names[current_parser_state],
471 event_parser_state_names[new_parser_state]));
472 current_parser_state= new_parser_state;
473
474 DBUG_RETURN(error);
475 }
476