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