summaryrefslogtreecommitdiffstats
path: root/sql/log_event.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:00:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:00:34 +0000
commit3f619478f796eddbba6e39502fe941b285dd97b1 (patch)
treee2c7b5777f728320e5b5542b6213fd3591ba51e2 /sql/log_event.cc
parentInitial commit. (diff)
downloadmariadb-3f619478f796eddbba6e39502fe941b285dd97b1.tar.xz
mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.zip
Adding upstream version 1:10.11.6.upstream/1%10.11.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sql/log_event.cc')
-rw-r--r--sql/log_event.cc4182
1 files changed, 4182 insertions, 0 deletions
diff --git a/sql/log_event.cc b/sql/log_event.cc
new file mode 100644
index 00000000..5e255646
--- /dev/null
+++ b/sql/log_event.cc
@@ -0,0 +1,4182 @@
+/*
+ Copyright (c) 2000, 2018, Oracle and/or its affiliates.
+ Copyright (c) 2009, 2022, MariaDB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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-1335 USA */
+
+
+#include "mariadb.h"
+#include "sql_priv.h"
+#include "handler.h"
+#ifndef MYSQL_CLIENT
+#include "unireg.h"
+#include "log_event.h"
+#include "sql_base.h" // close_thread_tables
+#include "sql_cache.h" // QUERY_CACHE_FLAGS_SIZE
+#include "sql_locale.h" // MY_LOCALE, my_locale_by_number, my_locale_en_US
+#include "key.h" // key_copy
+#include "lock.h" // mysql_unlock_tables
+#include "sql_parse.h" // mysql_test_parse_for_slave
+#include "tztime.h" // struct Time_zone
+#include "sql_load.h" // mysql_load
+#include "sql_db.h" // load_db_opt_by_name
+#include "slave.h"
+#include "rpl_rli.h"
+#include "rpl_mi.h"
+#include "rpl_filter.h"
+#include "rpl_record.h"
+#include "transaction.h"
+#include <my_dir.h>
+#include "sql_show.h" // append_identifier
+#include <mysql/psi/mysql_statement.h>
+#include <strfunc.h>
+#include "compat56.h"
+#include "sql_insert.h"
+#ifdef WITH_WSREP
+#include "wsrep_mysqld.h"
+#endif /* WITH_WSREP */
+#else
+#include "mysqld_error.h"
+#endif /* MYSQL_CLIENT */
+
+#include <my_bitmap.h>
+#include "rpl_utility.h"
+#include "rpl_constants.h"
+#include "sql_digest.h"
+#include "zlib.h"
+#include "myisampack.h"
+#include <algorithm>
+
+#define my_b_write_string(A, B) my_b_write((A), (uchar*)(B), (uint) (sizeof(B) - 1))
+
+#ifndef _AIX
+PSI_memory_key key_memory_log_event;
+#endif
+PSI_memory_key key_memory_Incident_log_event_message;
+PSI_memory_key key_memory_Rows_query_log_event_rows_query;
+
+/**
+ BINLOG_CHECKSUM variable.
+*/
+const char *binlog_checksum_type_names[]= {
+ "NONE",
+ "CRC32",
+ NullS
+};
+
+unsigned int binlog_checksum_type_length[]= {
+ sizeof("NONE") - 1,
+ sizeof("CRC32") - 1,
+ 0
+};
+
+TYPELIB binlog_checksum_typelib=
+{
+ array_elements(binlog_checksum_type_names) - 1, "",
+ binlog_checksum_type_names,
+ binlog_checksum_type_length
+};
+
+
+#define FLAGSTR(V,F) ((V)&(F)?#F" ":"")
+
+/*
+ Size of buffer for printing a double in format %.<PREC>g
+
+ optional '-' + optional zero + '.' + PREC digits + 'e' + sign +
+ exponent digits + '\0'
+*/
+#define FMT_G_BUFSIZE(PREC) (3 + (PREC) + 5 + 1)
+
+/*
+ replication event checksum is introduced in the following "checksum-home" version.
+ The checksum-aware servers extract FD's version to decide whether the FD event
+ carries checksum info.
+
+ TODO: correct the constant when it has been determined
+ (which main tree to push and when)
+*/
+const Version checksum_version_split_mysql(5, 6, 1);
+const Version checksum_version_split_mariadb(5, 3, 0);
+
+// First MySQL version with fraction seconds
+const Version fsp_version_split_mysql(5, 6, 0);
+
+/*
+ Cache that will automatically be written to a dedicated file on
+ destruction.
+
+ DESCRIPTION
+
+ */
+class Write_on_release_cache
+{
+public:
+ enum flag
+ {
+ FLUSH_F
+ };
+
+ typedef unsigned short flag_set;
+
+ /*
+ Constructor.
+
+ SYNOPSIS
+ Write_on_release_cache
+ cache Pointer to cache to use
+ file File to write cache to upon destruction
+ flags Flags for the cache
+
+ DESCRIPTION
+ Cache common parameters and ensure common flush_data() code
+ on successful copy of the cache, the cache will be reinited as a
+ WRITE_CACHE.
+
+ Currently, a pointer to the cache is provided in the
+ constructor, but it would be possible to create a subclass
+ holding the IO_CACHE itself.
+ */
+ Write_on_release_cache(IO_CACHE *cache, FILE *file, flag_set flags= 0, Log_event *ev= NULL)
+ : m_cache(cache), m_file(file), m_flags(flags), m_ev(ev)
+ {
+ reinit_io_cache(m_cache, WRITE_CACHE, 0L, FALSE, TRUE);
+ }
+
+ ~Write_on_release_cache() = default;
+
+ bool flush_data()
+ {
+#ifdef MYSQL_CLIENT
+ if (m_ev == NULL)
+ {
+ if (copy_event_cache_to_file_and_reinit(m_cache, m_file))
+ return 1;
+ if ((m_flags & FLUSH_F) && fflush(m_file))
+ return 1;
+ }
+ else // if m_ev<>NULL, then storing the output in output_buf
+ {
+ LEX_STRING tmp_str;
+ bool res;
+ if (copy_event_cache_to_string_and_reinit(m_cache, &tmp_str))
+ return 1;
+ /* use 2 argument append as tmp_str is not \0 terminated */
+ res= m_ev->output_buf.append(tmp_str.str, tmp_str.length);
+ my_free(tmp_str.str);
+ return res ? res : 0;
+ }
+#else /* MySQL_SERVER */
+ if (copy_event_cache_to_file_and_reinit(m_cache, m_file))
+ return 1;
+ if ((m_flags & FLUSH_F) && fflush(m_file))
+ return 1;
+#endif
+ return 0;
+ }
+
+ /*
+ Return a pointer to the internal IO_CACHE.
+
+ SYNOPSIS
+ operator&()
+
+ DESCRIPTION
+
+ Function to return a pointer to the internal cache, so that the
+ object can be treated as a IO_CACHE and used with the my_b_*
+ IO_CACHE functions
+
+ RETURN VALUE
+ A pointer to the internal IO_CACHE.
+ */
+ IO_CACHE *operator&()
+ {
+ return m_cache;
+ }
+
+private:
+ // Hidden, to prevent usage.
+ Write_on_release_cache(Write_on_release_cache const&);
+
+ IO_CACHE *m_cache;
+ FILE *m_file;
+ flag_set m_flags;
+ Log_event *m_ev; // Used for Flashback
+};
+
+#ifndef DBUG_OFF
+#define DBUG_DUMP_EVENT_BUF(B,L) \
+ do { \
+ const uchar *_buf=(uchar*)(B); \
+ size_t _len=(L); \
+ if (_len >= LOG_EVENT_MINIMAL_HEADER_LEN) \
+ { \
+ DBUG_PRINT("data", ("header: timestamp:%u type:%u server_id:%u len:%u log_pos:%u flags:%u", \
+ uint4korr(_buf), _buf[EVENT_TYPE_OFFSET], \
+ uint4korr(_buf+SERVER_ID_OFFSET), \
+ uint4korr(_buf+EVENT_LEN_OFFSET), \
+ uint4korr(_buf+LOG_POS_OFFSET), \
+ uint4korr(_buf+FLAGS_OFFSET))); \
+ DBUG_DUMP("data", _buf+LOG_EVENT_MINIMAL_HEADER_LEN, \
+ _len-LOG_EVENT_MINIMAL_HEADER_LEN); \
+ } \
+ else \
+ DBUG_DUMP("data", _buf, _len); \
+ } while(0)
+#else
+#define DBUG_DUMP_EVENT_BUF(B,L) do { } while(0)
+#endif
+
+/*
+ read_str()
+*/
+
+static inline bool read_str(const uchar **buf, const uchar *buf_end,
+ const char **str, uint8 *len)
+{
+ if (*buf + ((uint) **buf) >= buf_end)
+ return 1;
+ *len= (uint8) **buf;
+ *str= (char*) (*buf)+1;
+ (*buf)+= (uint) *len+1;
+ return 0;
+}
+
+
+/**
+ Transforms a string into "" or its expression in X'HHHH' form.
+*/
+
+char *str_to_hex(char *to, const char *from, size_t len)
+{
+ if (len)
+ {
+ *to++= 'X';
+ *to++= '\'';
+ to= octet2hex(to, from, len);
+ *to++= '\'';
+ *to= '\0';
+ }
+ else
+ to= strmov(to, "\"\"");
+ return to; // pointer to end 0 of 'to'
+}
+
+#define BINLOG_COMPRESSED_HEADER_LEN 1
+#define BINLOG_COMPRESSED_ORIGINAL_LENGTH_MAX_BYTES 4
+/**
+ Compressed Record
+ Record Header: 1 Byte
+ 7 Bit: Always 1, mean compressed;
+ 4-6 Bit: Compressed algorithm - Always 0, means zlib
+ It maybe support other compression algorithm in the future.
+ 0-3 Bit: Bytes of "Record Original Length"
+ Record Original Length: 1-4 Bytes
+ Compressed Buf:
+*/
+
+/**
+ Get the length of compress content.
+*/
+
+uint32 binlog_get_compress_len(uint32 len)
+{
+ /* 5 for the begin content, 1 reserved for a '\0'*/
+ return ALIGN_SIZE((BINLOG_COMPRESSED_HEADER_LEN + BINLOG_COMPRESSED_ORIGINAL_LENGTH_MAX_BYTES)
+ + compressBound(len) + 1);
+}
+
+/**
+ Compress buf from 'src' to 'dst'.
+
+ Note: 1) Then the caller should guarantee the length of 'dst', which
+ can be got by binlog_get_uncompress_len, is enough to hold
+ the content uncompressed.
+ 2) The 'comlen' should stored the length of 'dst', and it will
+ be set as the size of compressed content after return.
+
+ return zero if successful, others otherwise.
+*/
+int binlog_buf_compress(const uchar *src, uchar *dst, uint32 len, uint32 *comlen)
+{
+ uchar lenlen;
+ if (len & 0xFF000000)
+ {
+ dst[1]= uchar(len >> 24);
+ dst[2]= uchar(len >> 16);
+ dst[3]= uchar(len >> 8);
+ dst[4]= uchar(len);
+ lenlen= 4;
+ }
+ else if (len & 0x00FF0000)
+ {
+ dst[1]= uchar(len >> 16);
+ dst[2]= uchar(len >> 8);
+ dst[3]= uchar(len);
+ lenlen= 3;
+ }
+ else if (len & 0x0000FF00)
+ {
+ dst[1]= uchar(len >> 8);
+ dst[2]= uchar(len);
+ lenlen= 2;
+ }
+ else
+ {
+ dst[1]= uchar(len);
+ lenlen= 1;
+ }
+ dst[0]= 0x80 | (lenlen & 0x07);
+
+ uLongf tmplen= (uLongf)*comlen - BINLOG_COMPRESSED_HEADER_LEN - lenlen - 1;
+ if (compress((Bytef *)dst + BINLOG_COMPRESSED_HEADER_LEN + lenlen, &tmplen,
+ (const Bytef *)src, (uLongf)len) != Z_OK)
+ {
+ return 1;
+ }
+ *comlen= (uint32)tmplen + BINLOG_COMPRESSED_HEADER_LEN + lenlen;
+ return 0;
+}
+
+/**
+ Convert a query_compressed_log_event to query_log_event
+ from 'src' to 'dst', the size after compression stored in 'newlen'.
+
+ @Note:
+ 1) The caller should call my_free to release 'dst' if *is_malloc is
+ returned as true.
+ 2) If *is_malloc is retuened as false, then 'dst' reuses the passed-in
+ 'buf'.
+
+ return zero if successful, non-zero otherwise.
+*/
+
+int
+query_event_uncompress(const Format_description_log_event *description_event,
+ bool contain_checksum, const uchar *src, ulong src_len,
+ uchar* buf, ulong buf_size, bool* is_malloc, uchar **dst,
+ ulong *newlen)
+{
+ ulong len= uint4korr(src + EVENT_LEN_OFFSET);
+ const uchar *tmp= src;
+ const uchar *end= src + len;
+ uchar *new_dst;
+
+ // bad event
+ if (src_len < len)
+ return 1;
+
+ DBUG_ASSERT((uchar)src[EVENT_TYPE_OFFSET] == QUERY_COMPRESSED_EVENT);
+
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 post_header_len=
+ description_event->post_header_len[QUERY_COMPRESSED_EVENT-1];
+
+ *is_malloc= false;
+
+ tmp+= common_header_len;
+ // bad event
+ if (end <= tmp)
+ return 1;
+
+ uint db_len= (uint)tmp[Q_DB_LEN_OFFSET];
+ uint16 status_vars_len= uint2korr(tmp + Q_STATUS_VARS_LEN_OFFSET);
+
+ tmp+= post_header_len + status_vars_len + db_len + 1;
+ // bad event
+ if (end <= tmp)
+ return 1;
+
+ int32 comp_len= (int32)(len - (tmp - src) -
+ (contain_checksum ? BINLOG_CHECKSUM_LEN : 0));
+ uint32 un_len= binlog_get_uncompress_len(tmp);
+
+ // bad event
+ if (comp_len < 0 || un_len == 0)
+ return 1;
+
+ *newlen= (ulong)(tmp - src) + un_len;
+ if (contain_checksum)
+ *newlen+= BINLOG_CHECKSUM_LEN;
+
+ uint32 alloc_size= (uint32)ALIGN_SIZE(*newlen);
+
+ if (alloc_size <= buf_size)
+ new_dst= buf;
+ else
+ {
+ new_dst= (uchar *) my_malloc(PSI_INSTRUMENT_ME, alloc_size, MYF(MY_WME));
+ if (!new_dst)
+ return 1;
+ *is_malloc= true;
+ }
+
+ /* copy the head*/
+ memcpy(new_dst, src , tmp - src);
+ if (binlog_buf_uncompress(tmp, new_dst + (tmp - src), comp_len, &un_len))
+ {
+ if (*is_malloc)
+ {
+ *is_malloc= false;
+ my_free(new_dst);
+ }
+ return 1;
+ }
+
+ new_dst[EVENT_TYPE_OFFSET]= QUERY_EVENT;
+ int4store(new_dst + EVENT_LEN_OFFSET, *newlen);
+ if (contain_checksum)
+ {
+ ulong clear_len= *newlen - BINLOG_CHECKSUM_LEN;
+ int4store(new_dst + clear_len,
+ my_checksum(0L, (uchar *)new_dst, clear_len));
+ }
+ *dst= new_dst;
+ return 0;
+}
+
+int
+row_log_event_uncompress(const Format_description_log_event *description_event,
+ bool contain_checksum, const uchar *src, ulong src_len,
+ uchar* buf, ulong buf_size, bool* is_malloc,
+ uchar **dst, ulong *newlen)
+{
+ Log_event_type type= (Log_event_type)(uchar)src[EVENT_TYPE_OFFSET];
+ ulong len= uint4korr(src + EVENT_LEN_OFFSET);
+ const uchar *tmp= src;
+ uchar *new_dst= NULL;
+ const uchar *end= tmp + len;
+
+ if (src_len < len)
+ return 1; // bad event
+
+ DBUG_ASSERT(LOG_EVENT_IS_ROW_COMPRESSED(type));
+
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 post_header_len= description_event->post_header_len[type-1];
+
+ tmp+= common_header_len + ROWS_HEADER_LEN_V1;
+ if (post_header_len == ROWS_HEADER_LEN_V2)
+ {
+ /*
+ Have variable length header, check length,
+ which includes length bytes
+ */
+
+ if (end - tmp <= 2)
+ return 1; // bad event
+
+ uint16 var_header_len= uint2korr(tmp);
+ DBUG_ASSERT(var_header_len >= 2);
+
+ /* skip over var-len header, extracting 'chunks' */
+ tmp+= var_header_len;
+
+ /* get the uncompressed event type */
+ type=
+ (Log_event_type)(type - WRITE_ROWS_COMPRESSED_EVENT + WRITE_ROWS_EVENT);
+ }
+ else
+ {
+ /* get the uncompressed event type */
+ type= (Log_event_type)
+ (type - WRITE_ROWS_COMPRESSED_EVENT_V1 + WRITE_ROWS_EVENT_V1);
+ }
+
+ if (end <= tmp)
+ return 1; //bad event
+
+ ulong m_width= net_field_length((uchar **)&tmp);
+ tmp+= (m_width + 7) / 8;
+
+ if (type == UPDATE_ROWS_EVENT_V1 || type == UPDATE_ROWS_EVENT)
+ {
+ tmp+= (m_width + 7) / 8;
+ }
+
+ if (end <= tmp)
+ return 1; //bad event
+
+ uint32 un_len= binlog_get_uncompress_len(tmp);
+ if (un_len == 0)
+ return 1; //bad event
+
+ int32 comp_len= (int32)(len - (tmp - src) -
+ (contain_checksum ? BINLOG_CHECKSUM_LEN : 0));
+ if (comp_len <=0)
+ return 1; //bad event
+
+ *newlen= ulong(tmp - src) + un_len;
+ if (contain_checksum)
+ *newlen+= BINLOG_CHECKSUM_LEN;
+
+ size_t alloc_size= ALIGN_SIZE(*newlen);
+
+ *is_malloc= false;
+ if (alloc_size <= buf_size)
+ {
+ new_dst= buf;
+ }
+ else
+ {
+ new_dst= (uchar*) my_malloc(PSI_INSTRUMENT_ME, alloc_size, MYF(MY_WME));
+ if (!new_dst)
+ return 1;
+ *is_malloc= true;
+ }
+
+ /* Copy the head. */
+ memcpy(new_dst, src , tmp - src);
+ /* Uncompress the body. */
+ if (binlog_buf_uncompress(tmp, new_dst + (tmp - src),
+ comp_len, &un_len))
+ {
+ if (*is_malloc)
+ my_free(new_dst);
+ return 1;
+ }
+
+ new_dst[EVENT_TYPE_OFFSET]= type;
+ int4store(new_dst + EVENT_LEN_OFFSET, *newlen);
+ if (contain_checksum)
+ {
+ ulong clear_len= *newlen - BINLOG_CHECKSUM_LEN;
+ int4store(new_dst + clear_len,
+ my_checksum(0L, (uchar *)new_dst, clear_len));
+ }
+ *dst= new_dst;
+ return 0;
+}
+
+/**
+ Get the length of uncompress content.
+ return 0 means error.
+*/
+
+uint32 binlog_get_uncompress_len(const uchar *buf)
+{
+ uint32 len, lenlen;
+
+ if ((buf == NULL) || ((buf[0] & 0xe0) != 0x80))
+ return 0;
+
+ lenlen= buf[0] & 0x07;
+
+ buf++;
+ /* Length is stored in high byte first order, like myisam keys */
+ switch(lenlen) {
+ case 1:
+ len= buf[0];
+ break;
+ case 2:
+ len= mi_uint2korr(buf);
+ break;
+ case 3:
+ len= mi_uint3korr(buf);
+ break;
+ case 4:
+ len= mi_uint4korr(buf);
+ break;
+ default:
+ DBUG_ASSERT(lenlen >= 1 && lenlen <= 4);
+ len= 0;
+ break;
+ }
+ return len;
+}
+
+/**
+ Uncompress the content in 'src' with length of 'len' to 'dst'.
+
+ Note: 1) Then the caller should guarantee the length of 'dst' (which
+ can be got by statement_get_uncompress_len) is enough to hold
+ the content uncompressed.
+ 2) The 'newlen' should stored the length of 'dst', and it will
+ be set as the size of uncompressed content after return.
+
+ return zero if successful, others otherwise.
+*/
+int binlog_buf_uncompress(const uchar *src, uchar *dst, uint32 len,
+ uint32 *newlen)
+{
+ if ((src[0] & 0x80) == 0)
+ return 1;
+
+ uint32 lenlen= src[0] & 0x07;
+ uLongf buflen= *newlen; // zlib type
+
+ uint32 alg= (src[0] & 0x70) >> 4;
+ switch(alg) {
+ case 0:
+ // zlib
+ if (uncompress((Bytef *)dst, &buflen,
+ (const Bytef*)src + 1 + lenlen, len - 1 - lenlen) != Z_OK)
+ return 1;
+ break;
+ default:
+ //TODO
+ //bad algorithm
+ return 1;
+ }
+
+ DBUG_ASSERT(*newlen == (uint32)buflen);
+ *newlen= (uint32)buflen;
+ return 0;
+}
+
+
+/**************************************************************************
+ Log_event methods (= the parent class of all events)
+**************************************************************************/
+
+/**
+ @return
+ returns the human readable name of the event's type
+*/
+
+const char* Log_event::get_type_str(Log_event_type type)
+{
+ switch(type) {
+ case START_EVENT_V3: return "Start_v3";
+ case STOP_EVENT: return "Stop";
+ case QUERY_EVENT: return "Query";
+ case ROTATE_EVENT: return "Rotate";
+ case INTVAR_EVENT: return "Intvar";
+ case LOAD_EVENT: return "Load";
+ case NEW_LOAD_EVENT: return "New_load";
+ case SLAVE_EVENT: return "Slave";
+ case CREATE_FILE_EVENT: return "Create_file";
+ case APPEND_BLOCK_EVENT: return "Append_block";
+ case DELETE_FILE_EVENT: return "Delete_file";
+ case EXEC_LOAD_EVENT: return "Exec_load";
+ case RAND_EVENT: return "RAND";
+ case XID_EVENT: return "Xid";
+ case USER_VAR_EVENT: return "User var";
+ case FORMAT_DESCRIPTION_EVENT: return "Format_desc";
+ case TABLE_MAP_EVENT: return "Table_map";
+ case PRE_GA_WRITE_ROWS_EVENT: return "Write_rows_event_old";
+ case PRE_GA_UPDATE_ROWS_EVENT: return "Update_rows_event_old";
+ case PRE_GA_DELETE_ROWS_EVENT: return "Delete_rows_event_old";
+ case WRITE_ROWS_EVENT_V1: return "Write_rows_v1";
+ case UPDATE_ROWS_EVENT_V1: return "Update_rows_v1";
+ case DELETE_ROWS_EVENT_V1: return "Delete_rows_v1";
+ case WRITE_ROWS_EVENT: return "Write_rows";
+ case UPDATE_ROWS_EVENT: return "Update_rows";
+ case DELETE_ROWS_EVENT: return "Delete_rows";
+ case BEGIN_LOAD_QUERY_EVENT: return "Begin_load_query";
+ case EXECUTE_LOAD_QUERY_EVENT: return "Execute_load_query";
+ case INCIDENT_EVENT: return "Incident";
+ case ANNOTATE_ROWS_EVENT: return "Annotate_rows";
+ case BINLOG_CHECKPOINT_EVENT: return "Binlog_checkpoint";
+ case GTID_EVENT: return "Gtid";
+ case GTID_LIST_EVENT: return "Gtid_list";
+ case START_ENCRYPTION_EVENT: return "Start_encryption";
+
+ /* The following is only for mysqlbinlog */
+ case IGNORABLE_LOG_EVENT: return "Ignorable log event";
+ case ROWS_QUERY_LOG_EVENT: return "MySQL Rows_query";
+ case GTID_LOG_EVENT: return "MySQL Gtid";
+ case ANONYMOUS_GTID_LOG_EVENT: return "MySQL Anonymous_Gtid";
+ case PREVIOUS_GTIDS_LOG_EVENT: return "MySQL Previous_gtids";
+ case HEARTBEAT_LOG_EVENT: return "Heartbeat";
+ case TRANSACTION_CONTEXT_EVENT: return "Transaction_context";
+ case VIEW_CHANGE_EVENT: return "View_change";
+ case XA_PREPARE_LOG_EVENT: return "XA_prepare";
+ case QUERY_COMPRESSED_EVENT: return "Query_compressed";
+ case WRITE_ROWS_COMPRESSED_EVENT: return "Write_rows_compressed";
+ case UPDATE_ROWS_COMPRESSED_EVENT: return "Update_rows_compressed";
+ case DELETE_ROWS_COMPRESSED_EVENT: return "Delete_rows_compressed";
+ case WRITE_ROWS_COMPRESSED_EVENT_V1: return "Write_rows_compressed_v1";
+ case UPDATE_ROWS_COMPRESSED_EVENT_V1: return "Update_rows_compressed_v1";
+ case DELETE_ROWS_COMPRESSED_EVENT_V1: return "Delete_rows_compressed_v1";
+
+ default: return "Unknown"; /* impossible */
+ }
+}
+
+const char* Log_event::get_type_str()
+{
+ return get_type_str(get_type_code());
+}
+
+
+/*
+ Log_event::Log_event()
+*/
+
+Log_event::Log_event(const uchar *buf,
+ const Format_description_log_event* description_event)
+ :temp_buf(0), exec_time(0), cache_type(Log_event::EVENT_INVALID_CACHE),
+ checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF)
+{
+#ifndef MYSQL_CLIENT
+ thd= 0;
+#endif
+ when= uint4korr(buf);
+ when_sec_part= ~0UL;
+ server_id= uint4korr(buf + SERVER_ID_OFFSET);
+ data_written= uint4korr(buf + EVENT_LEN_OFFSET);
+ if (description_event->binlog_version==1)
+ {
+ log_pos= 0;
+ flags= 0;
+ return;
+ }
+ /* 4.0 or newer */
+ log_pos= uint4korr(buf + LOG_POS_OFFSET);
+ /*
+ If the log is 4.0 (so here it can only be a 4.0 relay log read by
+ the SQL thread or a 4.0 master binlog read by the I/O thread),
+ log_pos is the beginning of the event: we transform it into the end
+ of the event, which is more useful.
+ But how do you know that the log is 4.0: you know it if
+ description_event is version 3 *and* you are not reading a
+ Format_desc (remember that mysqlbinlog starts by assuming that 5.0
+ logs are in 4.0 format, until it finds a Format_desc).
+ */
+ if (description_event->binlog_version==3 &&
+ (uchar)buf[EVENT_TYPE_OFFSET]<FORMAT_DESCRIPTION_EVENT && log_pos)
+ {
+ /*
+ If log_pos=0, don't change it. log_pos==0 is a marker to mean
+ "don't change rli->group_master_log_pos" (see
+ inc_group_relay_log_pos()). As it is unreal log_pos, adding the
+ event len's is nonsense. For example, a fake Rotate event should
+ not have its log_pos (which is 0) changed or it will modify
+ Exec_master_log_pos in SHOW SLAVE STATUS, displaying a nonsense
+ value of (a non-zero offset which does not exist in the master's
+ binlog, so which will cause problems if the user uses this value
+ in CHANGE MASTER).
+ */
+ log_pos+= data_written; /* purecov: inspected */
+ }
+ DBUG_PRINT("info", ("log_pos: %llu", log_pos));
+
+ flags= uint2korr(buf + FLAGS_OFFSET);
+ if (((uchar)buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT) ||
+ ((uchar)buf[EVENT_TYPE_OFFSET] == ROTATE_EVENT))
+ {
+ /*
+ These events always have a header which stops here (i.e. their
+ header is FROZEN).
+ */
+ /*
+ Initialization to zero of all other Log_event members as they're
+ not specified. Currently there are no such members; in the future
+ there will be an event UID (but Format_description and Rotate
+ don't need this UID, as they are not propagated through
+ --log-slave-updates (remember the UID is used to not play a query
+ twice when you have two masters which are slaves of a 3rd master).
+ Then we are done.
+ */
+ return;
+ }
+ /* otherwise, go on with reading the header from buf (nothing now) */
+}
+
+
+/**
+ This needn't be format-tolerant, because we only parse the first
+ LOG_EVENT_MINIMAL_HEADER_LEN bytes (just need the event's length).
+*/
+
+int Log_event::read_log_event(IO_CACHE* file, String* packet,
+ const Format_description_log_event *fdle,
+ enum enum_binlog_checksum_alg checksum_alg_arg)
+{
+ ulong data_len;
+ char buf[LOG_EVENT_MINIMAL_HEADER_LEN];
+ uchar ev_offset= packet->length();
+#if !defined(MYSQL_CLIENT)
+ THD *thd=current_thd;
+ ulong max_allowed_packet= thd ? thd->slave_thread ? slave_max_allowed_packet
+ : thd->variables.max_allowed_packet
+ : ~(uint)0;
+#endif
+ DBUG_ENTER("Log_event::read_log_event(IO_CACHE*,String*...)");
+
+ if (my_b_read(file, (uchar*) buf, sizeof(buf)))
+ {
+ /*
+ If the read hits eof, we must report it as eof so the caller
+ will know it can go into cond_wait to be woken up on the next
+ update to the log.
+ */
+ DBUG_PRINT("error",("file->error: %d", file->error));
+ DBUG_RETURN(file->error == 0 ? LOG_READ_EOF :
+ file->error > 0 ? LOG_READ_TRUNC : LOG_READ_IO);
+ }
+ data_len= uint4korr(buf + EVENT_LEN_OFFSET);
+
+ /* Append the log event header to packet */
+ if (packet->append(buf, sizeof(buf)))
+ DBUG_RETURN(LOG_READ_MEM);
+
+ if (data_len < LOG_EVENT_MINIMAL_HEADER_LEN)
+ DBUG_RETURN(LOG_READ_BOGUS);
+
+ if (data_len > MY_MAX(max_allowed_packet,
+ opt_binlog_rows_event_max_size + MAX_LOG_EVENT_HEADER))
+ DBUG_RETURN(LOG_READ_TOO_LARGE);
+
+ if (likely(data_len > LOG_EVENT_MINIMAL_HEADER_LEN))
+ {
+ /* Append rest of event, read directly from file into packet */
+ if (packet->append(file, data_len - LOG_EVENT_MINIMAL_HEADER_LEN))
+ {
+ /*
+ Fatal error occurred when appending rest of the event
+ to packet, possible failures:
+ 1. EOF occurred when reading from file, it's really an error
+ as there's supposed to be more bytes available.
+ file->error will have been set to number of bytes left to read
+ 2. Read was interrupted, file->error would normally be set to -1
+ 3. Failed to allocate memory for packet, my_errno
+ will be ENOMEM(file->error should be 0, but since the
+ memory allocation occurs before the call to read it might
+ be uninitialized)
+ */
+ DBUG_RETURN(my_errno == ENOMEM ? LOG_READ_MEM :
+ (file->error >= 0 ? LOG_READ_TRUNC: LOG_READ_IO));
+ }
+ }
+
+ if (fdle->crypto_data.scheme)
+ {
+ uchar iv[BINLOG_IV_LENGTH];
+ fdle->crypto_data.set_iv(iv, (uint32) (my_b_tell(file) - data_len));
+ size_t sz= data_len + ev_offset + 1;
+#ifdef HAVE_WOLFSSL
+ /*
+ Workaround for MDEV-19582.
+ WolfSSL reads memory out of bounds with decryption/NOPAD)
+ We allocate a little more memory therefore.
+ */
+ sz+= MY_AES_BLOCK_SIZE;
+#endif
+ char *newpkt= (char*)my_malloc(PSI_INSTRUMENT_ME, sz, MYF(MY_WME));
+ if (!newpkt)
+ DBUG_RETURN(LOG_READ_MEM);
+ memcpy(newpkt, packet->ptr(), ev_offset);
+
+ uint dstlen;
+ uchar *src= (uchar*)packet->ptr() + ev_offset;
+ uchar *dst= (uchar*)newpkt + ev_offset;
+ memcpy(src + EVENT_LEN_OFFSET, src, 4);
+ if (encryption_crypt(src + 4, data_len - 4, dst + 4, &dstlen,
+ fdle->crypto_data.key, fdle->crypto_data.key_length, iv,
+ sizeof(iv), ENCRYPTION_FLAG_DECRYPT | ENCRYPTION_FLAG_NOPAD,
+ ENCRYPTION_KEY_SYSTEM_DATA, fdle->crypto_data.key_version))
+ {
+ my_free(newpkt);
+ DBUG_RETURN(LOG_READ_DECRYPT);
+ }
+ DBUG_ASSERT(dstlen == data_len - 4);
+ memcpy(dst, dst + EVENT_LEN_OFFSET, 4);
+ int4store(dst + EVENT_LEN_OFFSET, data_len);
+ packet->reset(newpkt, data_len + ev_offset, data_len + ev_offset + 1,
+ &my_charset_bin);
+ }
+
+ /*
+ CRC verification of the Dump thread
+ */
+ if (data_len > LOG_EVENT_MINIMAL_HEADER_LEN)
+ {
+ /* Corrupt the event for Dump thread*/
+ DBUG_EXECUTE_IF("corrupt_read_log_event2",
+ uchar *debug_event_buf_c= (uchar*) packet->ptr() + ev_offset;
+ if (debug_event_buf_c[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT)
+ {
+ int debug_cor_pos= rand() % (data_len - BINLOG_CHECKSUM_LEN);
+ debug_event_buf_c[debug_cor_pos] =~ debug_event_buf_c[debug_cor_pos];
+ DBUG_PRINT("info", ("Corrupt the event at Log_event::read_log_event: byte on position %d", debug_cor_pos));
+ DBUG_SET("-d,corrupt_read_log_event2");
+ }
+ );
+ if (event_checksum_test((uchar*) packet->ptr() + ev_offset,
+ data_len, checksum_alg_arg))
+ DBUG_RETURN(LOG_READ_CHECKSUM_FAILURE);
+ }
+ DBUG_RETURN(0);
+}
+
+Log_event* Log_event::read_log_event(IO_CACHE* file,
+ const Format_description_log_event *fdle,
+ my_bool crc_check)
+{
+ DBUG_ENTER("Log_event::read_log_event(IO_CACHE*,Format_description_log_event*...)");
+ DBUG_ASSERT(fdle != 0);
+ String event;
+ const char *error= 0;
+ Log_event *res= 0;
+
+ switch (read_log_event(file, &event, fdle, BINLOG_CHECKSUM_ALG_OFF))
+ {
+ case 0:
+ break;
+ case LOG_READ_EOF: // no error here; we are at the file's end
+ goto err;
+ case LOG_READ_BOGUS:
+ error= "Event invalid";
+ goto err;
+ case LOG_READ_IO:
+ error= "read error";
+ goto err;
+ case LOG_READ_MEM:
+ error= "Out of memory";
+ goto err;
+ case LOG_READ_TRUNC:
+ error= "Event truncated";
+ goto err;
+ case LOG_READ_TOO_LARGE:
+ error= "Event too big";
+ goto err;
+ case LOG_READ_DECRYPT:
+ error= "Event decryption failure";
+ goto err;
+ case LOG_READ_CHECKSUM_FAILURE:
+ default:
+ DBUG_ASSERT(0);
+ error= "internal error";
+ goto err;
+ }
+
+ if ((res= read_log_event((uchar*) event.ptr(), event.length(),
+ &error, fdle, crc_check)))
+ res->register_temp_buf((uchar*) event.release(), true);
+
+err:
+ if (unlikely(error))
+ {
+ DBUG_ASSERT(!res);
+#ifdef MYSQL_CLIENT
+ if (force_opt)
+ DBUG_RETURN(new Unknown_log_event());
+#endif
+ if (event.length() >= OLD_HEADER_LEN)
+ sql_print_error("Error in Log_event::read_log_event(): '%s',"
+ " data_len: %lu, event_type: %u", error,
+ (ulong) uint4korr(&event[EVENT_LEN_OFFSET]),
+ (uint) (uchar)event[EVENT_TYPE_OFFSET]);
+ else
+ sql_print_error("Error in Log_event::read_log_event(): '%s'", error);
+ /*
+ The SQL slave thread will check if file->error<0 to know
+ if there was an I/O error. Even if there is no "low-level" I/O errors
+ with 'file', any of the high-level above errors is worrying
+ enough to stop the SQL thread now ; as we are skipping the current event,
+ going on with reading and successfully executing other events can
+ only corrupt the slave's databases. So stop.
+ */
+ file->error= -1;
+ }
+ DBUG_RETURN(res);
+}
+
+
+/**
+ Binlog format tolerance is in (buf, event_len, fdle)
+ constructors.
+*/
+
+Log_event* Log_event::read_log_event(const uchar *buf, uint event_len,
+ const char **error,
+ const Format_description_log_event *fdle,
+ my_bool crc_check)
+{
+ Log_event* ev;
+ enum enum_binlog_checksum_alg alg;
+ DBUG_ENTER("Log_event::read_log_event(char*,...)");
+ DBUG_ASSERT(fdle != 0);
+ DBUG_PRINT("info", ("binlog_version: %d", fdle->binlog_version));
+ DBUG_DUMP_EVENT_BUF(buf, event_len);
+
+ /*
+ Check the integrity; This is needed because handle_slave_io() doesn't
+ check if packet is of proper length.
+ */
+ if (event_len < EVENT_LEN_OFFSET)
+ {
+ *error="Sanity check failed"; // Needed to free buffer
+ DBUG_RETURN(NULL); // general sanity check - will fail on a partial read
+ }
+
+ uint event_type= buf[EVENT_TYPE_OFFSET];
+ // all following START events in the current file are without checksum
+ if (event_type == START_EVENT_V3)
+ (const_cast< Format_description_log_event *>(fdle))->checksum_alg= BINLOG_CHECKSUM_ALG_OFF;
+ /*
+ CRC verification by SQL and Show-Binlog-Events master side.
+ The caller has to provide @fdle->checksum_alg to
+ be the last seen FD's (A) descriptor.
+ If event is FD the descriptor is in it.
+ Notice, FD of the binlog can be only in one instance and therefore
+ Show-Binlog-Events executing master side thread needs just to know
+ the only FD's (A) value - whereas RL can contain more.
+ In the RL case, the alg is kept in FD_e (@fdle) which is reset
+ to the newer read-out event after its execution with possibly new alg descriptor.
+ Therefore in a typical sequence of RL:
+ {FD_s^0, FD_m, E_m^1} E_m^1
+ will be verified with (A) of FD_m.
+
+ See legends definition on MYSQL_BIN_LOG::relay_log_checksum_alg docs
+ lines (log.h).
+
+ Notice, a pre-checksum FD version forces alg := BINLOG_CHECKSUM_ALG_UNDEF.
+ */
+ alg= (event_type != FORMAT_DESCRIPTION_EVENT) ?
+ fdle->checksum_alg : get_checksum_alg(buf, event_len);
+ // Emulate the corruption during reading an event
+ DBUG_EXECUTE_IF("corrupt_read_log_event_char",
+ if (event_type != FORMAT_DESCRIPTION_EVENT)
+ {
+ uchar *debug_event_buf_c= const_cast<uchar*>(buf);
+ int debug_cor_pos= rand() % (event_len - BINLOG_CHECKSUM_LEN);
+ debug_event_buf_c[debug_cor_pos]=~ debug_event_buf_c[debug_cor_pos];
+ DBUG_PRINT("info", ("Corrupt the event at Log_event::read_log_event(char*,...): byte on position %d", debug_cor_pos));
+ DBUG_SET("-d,corrupt_read_log_event_char");
+ }
+ );
+ if (crc_check && event_checksum_test(const_cast<uchar*>(buf), event_len, alg))
+ {
+#ifdef MYSQL_CLIENT
+ *error= "Event crc check failed! Most likely there is event corruption.";
+ if (force_opt)
+ {
+ ev= new Unknown_log_event(buf, fdle);
+ DBUG_RETURN(ev);
+ }
+ else
+ DBUG_RETURN(NULL);
+#else
+ *error= ER_THD_OR_DEFAULT(current_thd, ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE);
+ sql_print_error("%s", *error);
+ DBUG_RETURN(NULL);
+#endif
+ }
+
+ if (event_type > fdle->number_of_event_types &&
+ event_type != FORMAT_DESCRIPTION_EVENT)
+ {
+ /*
+ It is unsafe to use the fdle if its post_header_len
+ array does not include the event type.
+ */
+ DBUG_PRINT("error", ("event type %d found, but the current "
+ "Format_description_log_event supports only %d event "
+ "types", event_type,
+ fdle->number_of_event_types));
+ ev= NULL;
+ }
+ else
+ {
+ /*
+ In some previuos versions (see comment in
+ Format_description_log_event::Format_description_log_event(char*,...)),
+ event types were assigned different id numbers than in the
+ present version. In order to replicate from such versions to the
+ present version, we must map those event type id's to our event
+ type id's. The mapping is done with the event_type_permutation
+ array, which was set up when the Format_description_log_event
+ was read.
+ */
+ if (fdle->event_type_permutation)
+ {
+ int new_event_type= fdle->event_type_permutation[event_type];
+ DBUG_PRINT("info", ("converting event type %d to %d (%s)",
+ event_type, new_event_type,
+ get_type_str((Log_event_type)new_event_type)));
+ event_type= new_event_type;
+ }
+
+ if (alg != BINLOG_CHECKSUM_ALG_UNDEF &&
+ (event_type == FORMAT_DESCRIPTION_EVENT ||
+ alg != BINLOG_CHECKSUM_ALG_OFF))
+ event_len= event_len - BINLOG_CHECKSUM_LEN;
+
+ /*
+ Create an object of Ignorable_log_event for unrecognized sub-class.
+ So that SLAVE SQL THREAD will only update the position and continue.
+ We should look for this flag first instead of judging by event_type
+ Any event can be Ignorable_log_event if it has this flag on.
+ look into @note of Ignorable_log_event
+ */
+ if (uint2korr(buf + FLAGS_OFFSET) & LOG_EVENT_IGNORABLE_F)
+ {
+ ev= new Ignorable_log_event(buf, fdle,
+ get_type_str((Log_event_type) event_type));
+ goto exit;
+ }
+ switch(event_type) {
+ case QUERY_EVENT:
+ ev= new Query_log_event(buf, event_len, fdle, QUERY_EVENT);
+ break;
+ case QUERY_COMPRESSED_EVENT:
+ ev= new Query_compressed_log_event(buf, event_len, fdle,
+ QUERY_COMPRESSED_EVENT);
+ break;
+ case LOAD_EVENT:
+ ev= new Load_log_event(buf, event_len, fdle);
+ break;
+ case NEW_LOAD_EVENT:
+ ev= new Load_log_event(buf, event_len, fdle);
+ break;
+ case ROTATE_EVENT:
+ ev= new Rotate_log_event(buf, event_len, fdle);
+ break;
+ case BINLOG_CHECKPOINT_EVENT:
+ ev= new Binlog_checkpoint_log_event(buf, event_len, fdle);
+ break;
+ case GTID_EVENT:
+ ev= new Gtid_log_event(buf, event_len, fdle);
+ break;
+ case GTID_LIST_EVENT:
+ ev= new Gtid_list_log_event(buf, event_len, fdle);
+ break;
+ case CREATE_FILE_EVENT:
+ ev= new Create_file_log_event(buf, event_len, fdle);
+ break;
+ case APPEND_BLOCK_EVENT:
+ ev= new Append_block_log_event(buf, event_len, fdle);
+ break;
+ case DELETE_FILE_EVENT:
+ ev= new Delete_file_log_event(buf, event_len, fdle);
+ break;
+ case EXEC_LOAD_EVENT:
+ ev= new Execute_load_log_event(buf, event_len, fdle);
+ break;
+ case START_EVENT_V3: /* this is sent only by MySQL <=4.x */
+ ev= new Start_log_event_v3(buf, event_len, fdle);
+ break;
+ case STOP_EVENT:
+ ev= new Stop_log_event(buf, fdle);
+ break;
+ case INTVAR_EVENT:
+ ev= new Intvar_log_event(buf, fdle);
+ break;
+ case XID_EVENT:
+ ev= new Xid_log_event(buf, fdle);
+ break;
+ case XA_PREPARE_LOG_EVENT:
+ ev= new XA_prepare_log_event(buf, fdle);
+ break;
+ case RAND_EVENT:
+ ev= new Rand_log_event(buf, fdle);
+ break;
+ case USER_VAR_EVENT:
+ ev= new User_var_log_event(buf, event_len, fdle);
+ break;
+ case FORMAT_DESCRIPTION_EVENT:
+ ev= new Format_description_log_event(buf, event_len, fdle);
+ break;
+#if defined(HAVE_REPLICATION)
+ case PRE_GA_WRITE_ROWS_EVENT:
+ ev= new Write_rows_log_event_old(buf, event_len, fdle);
+ break;
+ case PRE_GA_UPDATE_ROWS_EVENT:
+ ev= new Update_rows_log_event_old(buf, event_len, fdle);
+ break;
+ case PRE_GA_DELETE_ROWS_EVENT:
+ ev= new Delete_rows_log_event_old(buf, event_len, fdle);
+ break;
+ case WRITE_ROWS_EVENT_V1:
+ case WRITE_ROWS_EVENT:
+ ev= new Write_rows_log_event(buf, event_len, fdle);
+ break;
+ case UPDATE_ROWS_EVENT_V1:
+ case UPDATE_ROWS_EVENT:
+ ev= new Update_rows_log_event(buf, event_len, fdle);
+ break;
+ case DELETE_ROWS_EVENT_V1:
+ case DELETE_ROWS_EVENT:
+ ev= new Delete_rows_log_event(buf, event_len, fdle);
+ break;
+
+ case WRITE_ROWS_COMPRESSED_EVENT:
+ case WRITE_ROWS_COMPRESSED_EVENT_V1:
+ ev= new Write_rows_compressed_log_event(buf, event_len, fdle);
+ break;
+ case UPDATE_ROWS_COMPRESSED_EVENT:
+ case UPDATE_ROWS_COMPRESSED_EVENT_V1:
+ ev= new Update_rows_compressed_log_event(buf, event_len, fdle);
+ break;
+ case DELETE_ROWS_COMPRESSED_EVENT:
+ case DELETE_ROWS_COMPRESSED_EVENT_V1:
+ ev= new Delete_rows_compressed_log_event(buf, event_len, fdle);
+ break;
+
+ /* MySQL GTID events are ignored */
+ case GTID_LOG_EVENT:
+ case ANONYMOUS_GTID_LOG_EVENT:
+ case PREVIOUS_GTIDS_LOG_EVENT:
+ case TRANSACTION_CONTEXT_EVENT:
+ case VIEW_CHANGE_EVENT:
+ ev= new Ignorable_log_event(buf, fdle,
+ get_type_str((Log_event_type) event_type));
+ break;
+
+ case TABLE_MAP_EVENT:
+ ev= new Table_map_log_event(buf, event_len, fdle);
+ break;
+#endif
+ case BEGIN_LOAD_QUERY_EVENT:
+ ev= new Begin_load_query_log_event(buf, event_len, fdle);
+ break;
+ case EXECUTE_LOAD_QUERY_EVENT:
+ ev= new Execute_load_query_log_event(buf, event_len, fdle);
+ break;
+ case INCIDENT_EVENT:
+ ev= new Incident_log_event(buf, event_len, fdle);
+ break;
+ case ANNOTATE_ROWS_EVENT:
+ ev= new Annotate_rows_log_event(buf, event_len, fdle);
+ break;
+ case START_ENCRYPTION_EVENT:
+ ev= new Start_encryption_log_event(buf, event_len, fdle);
+ break;
+ default:
+ DBUG_PRINT("error",("Unknown event code: %d",
+ (uchar) buf[EVENT_TYPE_OFFSET]));
+ ev= NULL;
+ break;
+ }
+ }
+exit:
+
+ if (ev)
+ {
+ ev->checksum_alg= alg;
+#ifdef MYSQL_CLIENT
+ if (ev->checksum_alg != BINLOG_CHECKSUM_ALG_OFF &&
+ ev->checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF)
+ ev->crc= uint4korr(buf + (event_len));
+#endif
+ }
+
+ DBUG_PRINT("read_event", ("%s(type_code: %u; event_len: %u)",
+ ev ? ev->get_type_str() : "<unknown>",
+ (uchar)buf[EVENT_TYPE_OFFSET],
+ event_len));
+ /*
+ is_valid() are small event-specific sanity tests which are
+ important; for example there are some my_malloc() in constructors
+ (e.g. Query_log_event::Query_log_event(char*...)); when these
+ my_malloc() fail we can't return an error out of the constructor
+ (because constructor is "void") ; so instead we leave the pointer we
+ wanted to allocate (e.g. 'query') to 0 and we test it in is_valid().
+ Same for Format_description_log_event, member 'post_header_len'.
+
+ SLAVE_EVENT is never used, so it should not be read ever.
+ */
+ if (!ev || !ev->is_valid() || (event_type == SLAVE_EVENT))
+ {
+ DBUG_PRINT("error",("Found invalid event in binary log"));
+
+ delete ev;
+#ifdef MYSQL_CLIENT
+ if (!force_opt) /* then mysqlbinlog dies */
+ {
+ *error= "Found invalid event in binary log";
+ DBUG_RETURN(0);
+ }
+ ev= new Unknown_log_event(buf, fdle);
+#else
+ *error= "Found invalid event in binary log";
+ DBUG_RETURN(0);
+#endif
+ }
+ DBUG_RETURN(ev);
+}
+
+
+
+/* 2 utility functions for the next method */
+
+/**
+ Read a string with length from memory.
+
+ This function reads the string-with-length stored at
+ <code>src</code> and extract the length into <code>*len</code> and
+ a pointer to the start of the string into <code>*dst</code>. The
+ string can then be copied using <code>memcpy()</code> with the
+ number of bytes given in <code>*len</code>.
+
+ @param src Pointer to variable holding a pointer to the memory to
+ read the string from.
+ @param dst Pointer to variable holding a pointer where the actual
+ string starts. Starting from this position, the string
+ can be copied using @c memcpy().
+ @param len Pointer to variable where the length will be stored.
+ @param end One-past-the-end of the memory where the string is
+ stored.
+
+ @return Zero if the entire string can be copied successfully,
+ @c UINT_MAX if the length could not be read from memory
+ (that is, if <code>*src >= end</code>), otherwise the
+ number of bytes that are missing to read the full
+ string, which happends <code>*dst + *len >= end</code>.
+*/
+static int
+get_str_len_and_pointer(const Log_event::Byte **src,
+ const char **dst,
+ uint *len,
+ const Log_event::Byte *end)
+{
+ if (*src >= end)
+ return -1; // Will be UINT_MAX in two-complement arithmetics
+ uint length= **src;
+ if (length > 0)
+ {
+ if (*src + length >= end)
+ return (int)(*src + length - end + 1); // Number of bytes missing
+ *dst= (char *)*src + 1; // Will be copied later
+ }
+ *len= length;
+ *src+= length + 1;
+ return 0;
+}
+
+static void copy_str_and_move(const char **src, Log_event::Byte **dst,
+ size_t len)
+{
+ memcpy(*dst, *src, len);
+ *src= (const char *)*dst;
+ (*dst)+= len;
+ *(*dst)++= 0;
+}
+
+
+#ifdef DBUG_TRACE
+static char const *
+code_name(int code)
+{
+ static char buf[255];
+ switch (code) {
+ case Q_FLAGS2_CODE: return "Q_FLAGS2_CODE";
+ case Q_SQL_MODE_CODE: return "Q_SQL_MODE_CODE";
+ case Q_CATALOG_CODE: return "Q_CATALOG_CODE";
+ case Q_AUTO_INCREMENT: return "Q_AUTO_INCREMENT";
+ case Q_CHARSET_CODE: return "Q_CHARSET_CODE";
+ case Q_TIME_ZONE_CODE: return "Q_TIME_ZONE_CODE";
+ case Q_CATALOG_NZ_CODE: return "Q_CATALOG_NZ_CODE";
+ case Q_LC_TIME_NAMES_CODE: return "Q_LC_TIME_NAMES_CODE";
+ case Q_CHARSET_DATABASE_CODE: return "Q_CHARSET_DATABASE_CODE";
+ case Q_TABLE_MAP_FOR_UPDATE_CODE: return "Q_TABLE_MAP_FOR_UPDATE_CODE";
+ case Q_MASTER_DATA_WRITTEN_CODE: return "Q_MASTER_DATA_WRITTEN_CODE";
+ case Q_HRNOW: return "Q_HRNOW";
+ case Q_XID: return "XID";
+ case Q_GTID_FLAGS3: return "Q_GTID_FLAGS3";
+ }
+ sprintf(buf, "CODE#%d", code);
+ return buf;
+}
+#endif
+
+#define VALIDATE_BYTES_READ(CUR_POS, START, EVENT_LEN) \
+ do { \
+ uchar *cur_pos= (uchar *)CUR_POS; \
+ uchar *start= (uchar *)START; \
+ uint len= EVENT_LEN; \
+ uint bytes_read= (uint)(cur_pos - start); \
+ DBUG_PRINT("info", ("Bytes read: %u event_len:%u.\n",\
+ bytes_read, len)); \
+ if (bytes_read >= len) \
+ DBUG_VOID_RETURN; \
+ } while (0)
+
+/**
+ Macro to check that there is enough space to read from memory.
+
+ @param PTR Pointer to memory
+ @param END End of memory
+ @param CNT Number of bytes that should be read.
+ */
+#define CHECK_SPACE(PTR,END,CNT) \
+ do { \
+ DBUG_PRINT("info", ("Read %s", code_name(pos[-1]))); \
+ if ((PTR) + (CNT) > (END)) { \
+ DBUG_PRINT("info", ("query= 0")); \
+ query= 0; \
+ DBUG_VOID_RETURN; \
+ } \
+ } while (0)
+
+
+/**
+ This is used by the SQL slave thread to prepare the event before execution.
+*/
+Query_log_event::Query_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event,
+ Log_event_type event_type)
+ :Log_event(buf, description_event), data_buf(0), query(NullS),
+ db(NullS), catalog_len(0), status_vars_len(0),
+ flags2_inited(0), sql_mode_inited(0), charset_inited(0), flags2(0),
+ auto_increment_increment(1), auto_increment_offset(1),
+ time_zone_len(0), lc_time_names_number(0), charset_database_number(0),
+ table_map_for_update(0), xid(0), master_data_written(0), gtid_flags_extra(0),
+ sa_seq_no(0)
+{
+ ulong data_len;
+ uint32 tmp;
+ uint8 common_header_len, post_header_len;
+ Log_event::Byte *start;
+ const Log_event::Byte *end;
+ bool catalog_nz= 1;
+ DBUG_ENTER("Query_log_event::Query_log_event(char*,...)");
+
+ memset(&user, 0, sizeof(user));
+ memset(&host, 0, sizeof(host));
+ common_header_len= description_event->common_header_len;
+ post_header_len= description_event->post_header_len[event_type-1];
+ DBUG_PRINT("info",("event_len: %u common_header_len: %d post_header_len: %d",
+ event_len, common_header_len, post_header_len));
+
+ /*
+ We test if the event's length is sensible, and if so we compute data_len.
+ We cannot rely on QUERY_HEADER_LEN here as it would not be format-tolerant.
+ We use QUERY_HEADER_MINIMAL_LEN which is the same for 3.23, 4.0 & 5.0.
+ */
+ if (event_len < (uint)(common_header_len + post_header_len))
+ DBUG_VOID_RETURN;
+ data_len= event_len - (common_header_len + post_header_len);
+ buf+= common_header_len;
+
+ thread_id = slave_proxy_id = uint4korr(buf + Q_THREAD_ID_OFFSET);
+ exec_time = uint4korr(buf + Q_EXEC_TIME_OFFSET);
+ db_len = (uchar)buf[Q_DB_LEN_OFFSET]; // TODO: add a check of all *_len vars
+ error_code = uint2korr(buf + Q_ERR_CODE_OFFSET);
+
+ /*
+ 5.0 format starts here.
+ Depending on the format, we may or not have affected/warnings etc
+ The remnent post-header to be parsed has length:
+ */
+ tmp= post_header_len - QUERY_HEADER_MINIMAL_LEN;
+ if (tmp)
+ {
+ status_vars_len= uint2korr(buf + Q_STATUS_VARS_LEN_OFFSET);
+ /*
+ Check if status variable length is corrupt and will lead to very
+ wrong data. We could be even more strict and require data_len to
+ be even bigger, but this will suffice to catch most corruption
+ errors that can lead to a crash.
+ */
+ if (status_vars_len > MY_MIN(data_len, MAX_SIZE_LOG_EVENT_STATUS))
+ {
+ DBUG_PRINT("info", ("status_vars_len (%u) > data_len (%lu); query= 0",
+ status_vars_len, data_len));
+ query= 0;
+ DBUG_VOID_RETURN;
+ }
+ data_len-= status_vars_len;
+ DBUG_PRINT("info", ("Query_log_event has status_vars_len: %u",
+ (uint) status_vars_len));
+ tmp-= 2;
+ }
+ else
+ {
+ /*
+ server version < 5.0 / binlog_version < 4 master's event is
+ relay-logged with storing the original size of the event in
+ Q_MASTER_DATA_WRITTEN_CODE status variable.
+ The size is to be restored at reading Q_MASTER_DATA_WRITTEN_CODE-marked
+ event from the relay log.
+ */
+ DBUG_ASSERT(description_event->binlog_version < 4);
+ master_data_written= (uint32)data_written;
+ }
+ /*
+ We have parsed everything we know in the post header for QUERY_EVENT,
+ the rest of post header is either comes from older version MySQL or
+ dedicated to derived events (e.g. Execute_load_query...)
+ */
+
+ /* variable-part: the status vars; only in MySQL 5.0 */
+
+ start= (Log_event::Byte*) (buf+post_header_len);
+ end= (const Log_event::Byte*) (start+status_vars_len);
+ for (const Log_event::Byte* pos= start; pos < end;)
+ {
+ switch (*pos++) {
+ case Q_FLAGS2_CODE:
+ CHECK_SPACE(pos, end, 4);
+ flags2_inited= description_event->options_written_to_bin_log;
+ flags2= uint4korr(pos);
+ DBUG_PRINT("info",("In Query_log_event, read flags2: %lu", (ulong) flags2));
+ pos+= 4;
+ break;
+ case Q_SQL_MODE_CODE:
+ {
+ CHECK_SPACE(pos, end, 8);
+ sql_mode_inited= 1;
+ sql_mode= (sql_mode_t) uint8korr(pos);
+ DBUG_PRINT("info",("In Query_log_event, read sql_mode: %llu", sql_mode));
+ pos+= 8;
+ break;
+ }
+ case Q_CATALOG_NZ_CODE:
+ DBUG_PRINT("info", ("case Q_CATALOG_NZ_CODE; pos:%p; end:%p",
+ pos, end));
+ if (get_str_len_and_pointer(&pos, &catalog, &catalog_len, end))
+ {
+ DBUG_PRINT("info", ("query= 0"));
+ query= 0;
+ DBUG_VOID_RETURN;
+ }
+ break;
+ case Q_AUTO_INCREMENT:
+ CHECK_SPACE(pos, end, 4);
+ auto_increment_increment= uint2korr(pos);
+ auto_increment_offset= uint2korr(pos+2);
+ pos+= 4;
+ break;
+ case Q_CHARSET_CODE:
+ {
+ CHECK_SPACE(pos, end, 6);
+ charset_inited= 1;
+ memcpy(charset, pos, 6);
+ pos+= 6;
+ break;
+ }
+ case Q_TIME_ZONE_CODE:
+ {
+ if (get_str_len_and_pointer(&pos, &time_zone_str, &time_zone_len, end))
+ {
+ DBUG_PRINT("info", ("Q_TIME_ZONE_CODE: query= 0"));
+ query= 0;
+ DBUG_VOID_RETURN;
+ }
+ break;
+ }
+ case Q_CATALOG_CODE: /* for 5.0.x where 0<=x<=3 masters */
+ CHECK_SPACE(pos, end, 1);
+ if ((catalog_len= *pos))
+ catalog= (char*) pos+1; // Will be copied later
+ CHECK_SPACE(pos, end, catalog_len + 2);
+ pos+= catalog_len+2; // leap over end 0
+ catalog_nz= 0; // catalog has end 0 in event
+ break;
+ case Q_LC_TIME_NAMES_CODE:
+ CHECK_SPACE(pos, end, 2);
+ lc_time_names_number= uint2korr(pos);
+ pos+= 2;
+ break;
+ case Q_CHARSET_DATABASE_CODE:
+ CHECK_SPACE(pos, end, 2);
+ charset_database_number= uint2korr(pos);
+ pos+= 2;
+ break;
+ case Q_TABLE_MAP_FOR_UPDATE_CODE:
+ CHECK_SPACE(pos, end, 8);
+ table_map_for_update= uint8korr(pos);
+ pos+= 8;
+ break;
+ case Q_MASTER_DATA_WRITTEN_CODE:
+ CHECK_SPACE(pos, end, 4);
+ data_written= master_data_written= uint4korr(pos);
+ pos+= 4;
+ break;
+ case Q_INVOKER:
+ {
+ CHECK_SPACE(pos, end, 1);
+ user.length= *pos++;
+ CHECK_SPACE(pos, end, user.length);
+ user.str= (char *)pos;
+ pos+= user.length;
+
+ CHECK_SPACE(pos, end, 1);
+ host.length= *pos++;
+ CHECK_SPACE(pos, end, host.length);
+ host.str= (char *)pos;
+ pos+= host.length;
+ break;
+ }
+ case Q_HRNOW:
+ {
+ CHECK_SPACE(pos, end, 3);
+ when_sec_part= uint3korr(pos);
+ pos+= 3;
+ break;
+ }
+ case Q_XID:
+ {
+ CHECK_SPACE(pos, end, 8);
+ xid= uint8korr(pos);
+ pos+= 8;
+ break;
+ }
+ case Q_GTID_FLAGS3:
+ {
+ CHECK_SPACE(pos, end, 1);
+ gtid_flags_extra= *pos++;
+ if (gtid_flags_extra & (Gtid_log_event::FL_COMMIT_ALTER_E1 |
+ Gtid_log_event::FL_ROLLBACK_ALTER_E1))
+ {
+ CHECK_SPACE(pos, end, 8);
+ sa_seq_no = uint8korr(pos);
+ pos+= 8;
+ }
+ break;
+ }
+ default:
+ /* That's why you must write status vars in growing order of code */
+ DBUG_PRINT("info",("Query_log_event has unknown status vars (first has\
+ code: %u), skipping the rest of them", (uint) *(pos-1)));
+ pos= (const uchar*) end; // Break loop
+ }
+ }
+
+#if !defined(MYSQL_CLIENT)
+ if (description_event->server_version_split.kind ==
+ Format_description_log_event::master_version_split::KIND_MYSQL)
+ {
+ // Handle MariaDB/MySQL incompatible sql_mode bits
+ sql_mode_t mysql_sql_mode= sql_mode;
+ sql_mode&= MODE_MASK_MYSQL_COMPATIBLE; // Unset MySQL specific bits
+
+ /*
+ sql_mode flags related to fraction second rounding/truncation
+ have opposite meaning in MySQL vs MariaDB.
+ MySQL:
+ - rounds fractional seconds by default
+ - truncates if TIME_TRUNCATE_FRACTIONAL is set
+ MariaDB:
+ - truncates fractional seconds by default
+ - rounds if TIME_ROUND_FRACTIONAL is set
+ */
+ if (description_event->server_version_split >= fsp_version_split_mysql &&
+ !(mysql_sql_mode & MODE_MYSQL80_TIME_TRUNCATE_FRACTIONAL))
+ sql_mode|= MODE_TIME_ROUND_FRACTIONAL;
+ }
+#endif
+
+ /**
+ Layout for the data buffer is as follows
+ +--------+-----------+------+------+---------+----+-------+
+ | catlog | time_zone | user | host | db name | \0 | Query |
+ +--------+-----------+------+------+---------+----+-------+
+
+ To support the query cache we append the following buffer to the above
+ +-------+----------------------------------------+-------+
+ |db len | uninitiatlized space of size of db len | FLAGS |
+ +-------+----------------------------------------+-------+
+
+ The area of buffer starting from Query field all the way to the end belongs
+ to the Query buffer and its structure is described in alloc_query() in
+ sql_parse.cc
+ */
+
+#if !defined(MYSQL_CLIENT) && defined(HAVE_QUERY_CACHE)
+ if (!(start= data_buf= (Log_event::Byte*) my_malloc(PSI_INSTRUMENT_ME,
+ catalog_len + 1
+ + time_zone_len + 1
+ + user.length + 1
+ + host.length + 1
+ + data_len + 1
+ + sizeof(size_t)//for db_len
+ + db_len + 1
+ + QUERY_CACHE_DB_LENGTH_SIZE
+ + QUERY_CACHE_FLAGS_SIZE,
+ MYF(MY_WME))))
+#else
+ if (!(start= data_buf= (Log_event::Byte*) my_malloc(PSI_INSTRUMENT_ME,
+ catalog_len + 1
+ + time_zone_len + 1
+ + user.length + 1
+ + host.length + 1
+ + data_len + 1,
+ MYF(MY_WME))))
+#endif
+ DBUG_VOID_RETURN;
+ if (catalog_len) // If catalog is given
+ {
+ /**
+ @todo we should clean up and do only copy_str_and_move; it
+ works for both cases. Then we can remove the catalog_nz
+ flag. /sven
+ */
+ if (likely(catalog_nz)) // true except if event comes from 5.0.0|1|2|3.
+ copy_str_and_move(&catalog, &start, catalog_len);
+ else
+ {
+ memcpy(start, catalog, catalog_len+1); // copy end 0
+ catalog= (const char *)start;
+ start+= catalog_len+1;
+ }
+ }
+ if (time_zone_len)
+ copy_str_and_move(&time_zone_str, &start, time_zone_len);
+
+ if (user.length)
+ {
+ copy_str_and_move(&user.str, &start, user.length);
+ }
+ else
+ {
+ user.str= (char*) start;
+ *(start++)= 0;
+ }
+
+ if (host.length)
+ copy_str_and_move(&host.str, &start, host.length);
+ else
+ {
+ host.str= (char*) start;
+ *(start++)= 0;
+ }
+
+ /**
+ if time_zone_len or catalog_len are 0, then time_zone and catalog
+ are uninitialized at this point. shouldn't they point to the
+ zero-length null-terminated strings we allocated space for in the
+ my_alloc call above? /sven
+ */
+
+ /* A 2nd variable part; this is common to all versions */
+ memcpy((char*) start, end, data_len); // Copy db and query
+ start[data_len]= '\0'; // End query with \0 (For safetly)
+ db= (char *)start;
+ query= (char *)(start + db_len + 1);
+ q_len= data_len - db_len -1;
+
+ if (data_len && (data_len < db_len ||
+ data_len < q_len ||
+ data_len != (db_len + q_len + 1)))
+ {
+ q_len= 0;
+ query= NULL;
+ DBUG_VOID_RETURN;
+ }
+
+ uint32 max_length= uint32(event_len - ((end + db_len + 1) -
+ (buf - common_header_len)));
+ if (q_len != max_length ||
+ (event_len < uint((end + db_len + 1) - (buf - common_header_len))))
+ {
+ q_len= 0;
+ query= NULL;
+ DBUG_VOID_RETURN;
+ }
+ /**
+ Append the db length at the end of the buffer. This will be used by
+ Query_cache::send_result_to_client() in case the query cache is On.
+ */
+#if !defined(MYSQL_CLIENT) && defined(HAVE_QUERY_CACHE)
+ size_t db_length= (size_t)db_len;
+ memcpy(start + data_len + 1, &db_length, sizeof(size_t));
+#endif
+ DBUG_VOID_RETURN;
+}
+
+Query_compressed_log_event::Query_compressed_log_event(const uchar *buf,
+ uint event_len,
+ const Format_description_log_event
+ *description_event,
+ Log_event_type event_type)
+ :Query_log_event(buf, event_len, description_event, event_type),
+ query_buf(NULL)
+{
+ if (query)
+ {
+ uint32 un_len= binlog_get_uncompress_len((uchar*) query);
+ if (!un_len)
+ {
+ query= 0;
+ return;
+ }
+
+ /* Reserve one byte for '\0' */
+ query_buf= (Log_event::Byte*) my_malloc(PSI_INSTRUMENT_ME,
+ ALIGN_SIZE(un_len + 1), MYF(MY_WME));
+ if (query_buf && !binlog_buf_uncompress((uchar*) query, (uchar *) query_buf,
+ q_len, &un_len))
+ {
+ query_buf[un_len]= 0;
+ query= (char*) query_buf;
+ q_len= un_len;
+ }
+ else
+ {
+ query= 0;
+ }
+ }
+}
+
+
+/*
+ Replace a binlog event read into a packet with a dummy event. Either a
+ Query_log_event that has just a comment, or if that will not fit in the
+ space used for the event to be replaced, then a NULL user_var event.
+
+ This is used when sending binlog data to a slave which does not understand
+ this particular event and which is too old to support informational events
+ or holes in the event stream.
+
+ This allows to write such events into the binlog on the master and still be
+ able to replicate against old slaves without them breaking.
+
+ Clears the flag LOG_EVENT_THREAD_SPECIFIC_F and set LOG_EVENT_SUPPRESS_USE_F.
+ Overwrites the type with QUERY_EVENT (or USER_VAR_EVENT), and replaces the
+ body with a minimal query / NULL user var.
+
+ Returns zero on success, -1 if error due to too little space in original
+ event. A minimum of 25 bytes (19 bytes fixed header + 6 bytes in the body)
+ is needed in any event to be replaced with a dummy event.
+*/
+int
+Query_log_event::dummy_event(String *packet, ulong ev_offset,
+ enum enum_binlog_checksum_alg checksum_alg)
+{
+ uchar *p= (uchar *)packet->ptr() + ev_offset;
+ size_t data_len= packet->length() - ev_offset;
+ uint16 flags;
+ static const size_t min_user_var_event_len=
+ LOG_EVENT_HEADER_LEN + UV_NAME_LEN_SIZE + 1 + UV_VAL_IS_NULL; // 25
+ static const size_t min_query_event_len=
+ LOG_EVENT_HEADER_LEN + QUERY_HEADER_LEN + 1 + 1; // 34
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ data_len-= BINLOG_CHECKSUM_LEN;
+ else
+ DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_OFF);
+
+ if (data_len < min_user_var_event_len)
+ /* Cannot replace with dummy, event too short. */
+ return -1;
+
+ flags= uint2korr(p + FLAGS_OFFSET);
+ flags&= ~LOG_EVENT_THREAD_SPECIFIC_F;
+ flags|= LOG_EVENT_SUPPRESS_USE_F;
+ int2store(p + FLAGS_OFFSET, flags);
+
+ if (data_len < min_query_event_len)
+ {
+ /*
+ Have to use dummy user_var event for such a short packet.
+
+ This works, but the event will be considered part of an event group with
+ the following event. So for example @@global.sql_slave_skip_counter=1
+ will skip not only the dummy event, but also the immediately following
+ event.
+
+ We write a NULL user var with the name @`!dummyvar` (or as much
+ as that as will fit within the size of the original event - so
+ possibly just @`!`).
+ */
+ static const char var_name[]= "!dummyvar";
+ size_t name_len= data_len - (min_user_var_event_len - 1);
+
+ p[EVENT_TYPE_OFFSET]= USER_VAR_EVENT;
+ int4store(p + LOG_EVENT_HEADER_LEN, name_len);
+ memcpy(p + LOG_EVENT_HEADER_LEN + UV_NAME_LEN_SIZE, var_name, name_len);
+ p[LOG_EVENT_HEADER_LEN + UV_NAME_LEN_SIZE + name_len]= 1; // indicates NULL
+ }
+ else
+ {
+ /*
+ Use a dummy query event, just a comment.
+ */
+ static const char message[]=
+ "# Dummy event replacing event type %u that slave cannot handle.";
+ char buf[sizeof(message)+1]; /* +1, as %u can expand to 3 digits. */
+ uchar old_type= p[EVENT_TYPE_OFFSET];
+ uchar *q= p + LOG_EVENT_HEADER_LEN;
+ size_t comment_len, len;
+
+ p[EVENT_TYPE_OFFSET]= QUERY_EVENT;
+ int4store(q + Q_THREAD_ID_OFFSET, 0);
+ int4store(q + Q_EXEC_TIME_OFFSET, 0);
+ q[Q_DB_LEN_OFFSET]= 0;
+ int2store(q + Q_ERR_CODE_OFFSET, 0);
+ int2store(q + Q_STATUS_VARS_LEN_OFFSET, 0);
+ q[Q_DATA_OFFSET]= 0; /* Zero terminator for empty db */
+ q+= Q_DATA_OFFSET + 1;
+ len= my_snprintf(buf, sizeof(buf), message, old_type);
+ comment_len= data_len - (min_query_event_len - 1);
+ if (comment_len <= len)
+ memcpy(q, buf, comment_len);
+ else
+ {
+ memcpy(q, buf, len);
+ memset(q+len, ' ', comment_len - len);
+ }
+ }
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ {
+ ha_checksum crc= my_checksum(0, p, data_len);
+ int4store(p + data_len, crc);
+ }
+ return 0;
+}
+
+/*
+ Replace an event (GTID event) with a BEGIN query event, to be compatible
+ with an old slave.
+*/
+int
+Query_log_event::begin_event(String *packet, ulong ev_offset,
+ enum enum_binlog_checksum_alg checksum_alg)
+{
+ uchar *p= (uchar *)packet->ptr() + ev_offset;
+ uchar *q= p + LOG_EVENT_HEADER_LEN;
+ size_t data_len= packet->length() - ev_offset;
+ uint16 flags;
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ data_len-= BINLOG_CHECKSUM_LEN;
+ else
+ DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_OFF);
+
+ /*
+ Currently we only need to replace GTID event.
+ The length of GTID differs depending on whether it contains commit id.
+ */
+ DBUG_ASSERT(data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN ||
+ data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN + 2);
+ if (data_len != LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN &&
+ data_len != LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN + 2)
+ return 1;
+
+ flags= uint2korr(p + FLAGS_OFFSET);
+ flags&= ~LOG_EVENT_THREAD_SPECIFIC_F;
+ flags|= LOG_EVENT_SUPPRESS_USE_F;
+ int2store(p + FLAGS_OFFSET, flags);
+
+ p[EVENT_TYPE_OFFSET]= QUERY_EVENT;
+ int4store(q + Q_THREAD_ID_OFFSET, 0);
+ int4store(q + Q_EXEC_TIME_OFFSET, 0);
+ q[Q_DB_LEN_OFFSET]= 0;
+ int2store(q + Q_ERR_CODE_OFFSET, 0);
+ if (data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN)
+ {
+ int2store(q + Q_STATUS_VARS_LEN_OFFSET, 0);
+ q[Q_DATA_OFFSET]= 0; /* Zero terminator for empty db */
+ q+= Q_DATA_OFFSET + 1;
+ }
+ else
+ {
+ DBUG_ASSERT(data_len == LOG_EVENT_HEADER_LEN + GTID_HEADER_LEN + 2);
+ /* Put in an empty time_zone_str to take up the extra 2 bytes. */
+ int2store(q + Q_STATUS_VARS_LEN_OFFSET, 2);
+ q[Q_DATA_OFFSET]= Q_TIME_ZONE_CODE;
+ q[Q_DATA_OFFSET+1]= 0; /* Zero length for empty time_zone_str */
+ q[Q_DATA_OFFSET+2]= 0; /* Zero terminator for empty db */
+ q+= Q_DATA_OFFSET + 3;
+ }
+ memcpy(q, "BEGIN", 5);
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ {
+ ha_checksum crc= my_checksum(0, p, data_len);
+ int4store(p + data_len, crc);
+ }
+ return 0;
+}
+
+
+/**************************************************************************
+ Start_log_event_v3 methods
+**************************************************************************/
+
+
+Start_log_event_v3::Start_log_event_v3(const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ :Log_event(buf, description_event), binlog_version(BINLOG_VERSION)
+{
+ if (event_len < LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET)
+ {
+ server_version[0]= 0;
+ return;
+ }
+ buf+= LOG_EVENT_MINIMAL_HEADER_LEN;
+ binlog_version= uint2korr(buf+ST_BINLOG_VER_OFFSET);
+ memcpy(server_version, buf+ST_SERVER_VER_OFFSET,
+ ST_SERVER_VER_LEN);
+ // prevent overrun if log is corrupted on disk
+ server_version[ST_SERVER_VER_LEN-1]= 0;
+ created= uint4korr(buf+ST_CREATED_OFFSET);
+ dont_set_created= 1;
+}
+
+
+/***************************************************************************
+ Format_description_log_event methods
+****************************************************************************/
+
+/**
+ Format_description_log_event 1st ctor.
+
+ Ctor. Can be used to create the event to write to the binary log (when the
+ server starts or when FLUSH LOGS), or to create artificial events to parse
+ binlogs from MySQL 3.23 or 4.x.
+ When in a client, only the 2nd use is possible.
+
+ @param binlog_version the binlog version for which we want to build
+ an event. Can be 1 (=MySQL 3.23), 3 (=4.0.x
+ x>=2 and 4.1) or 4 (MySQL 5.0). Note that the
+ old 4.0 (binlog version 2) is not supported;
+ it should not be used for replication with
+ 5.0.
+ @param server_ver a string containing the server version.
+*/
+
+Format_description_log_event::
+Format_description_log_event(uint8 binlog_ver, const char* server_ver)
+ :Start_log_event_v3(), event_type_permutation(0)
+{
+ binlog_version= binlog_ver;
+ switch (binlog_ver) {
+ case 4: /* MySQL 5.0 */
+ memcpy(server_version, ::server_version, ST_SERVER_VER_LEN);
+ DBUG_EXECUTE_IF("pretend_version_50034_in_binlog",
+ strmov(server_version, "5.0.34"););
+ common_header_len= LOG_EVENT_HEADER_LEN;
+ number_of_event_types= LOG_EVENT_TYPES;
+ /* we'll catch my_malloc() error in is_valid() */
+ post_header_len=(uint8*) my_malloc(PSI_INSTRUMENT_ME,
+ number_of_event_types*sizeof(uint8)
+ + BINLOG_CHECKSUM_ALG_DESC_LEN,
+ MYF(0));
+ /*
+ This long list of assignments is not beautiful, but I see no way to
+ make it nicer, as the right members are #defines, not array members, so
+ it's impossible to write a loop.
+ */
+ if (post_header_len)
+ {
+#ifndef DBUG_OFF
+ // Allows us to sanity-check that all events initialized their
+ // events (see the end of this 'if' block).
+ memset(post_header_len, 255, number_of_event_types*sizeof(uint8));
+#endif
+
+ /* Note: all event types must explicitly fill in their lengths here. */
+ post_header_len[START_EVENT_V3-1]= START_V3_HEADER_LEN;
+ post_header_len[QUERY_EVENT-1]= QUERY_HEADER_LEN;
+ post_header_len[STOP_EVENT-1]= STOP_HEADER_LEN;
+ post_header_len[ROTATE_EVENT-1]= ROTATE_HEADER_LEN;
+ post_header_len[INTVAR_EVENT-1]= INTVAR_HEADER_LEN;
+ post_header_len[LOAD_EVENT-1]= LOAD_HEADER_LEN;
+ post_header_len[SLAVE_EVENT-1]= SLAVE_HEADER_LEN;
+ post_header_len[CREATE_FILE_EVENT-1]= CREATE_FILE_HEADER_LEN;
+ post_header_len[APPEND_BLOCK_EVENT-1]= APPEND_BLOCK_HEADER_LEN;
+ post_header_len[EXEC_LOAD_EVENT-1]= EXEC_LOAD_HEADER_LEN;
+ post_header_len[DELETE_FILE_EVENT-1]= DELETE_FILE_HEADER_LEN;
+ post_header_len[NEW_LOAD_EVENT-1]= NEW_LOAD_HEADER_LEN;
+ post_header_len[RAND_EVENT-1]= RAND_HEADER_LEN;
+ post_header_len[USER_VAR_EVENT-1]= USER_VAR_HEADER_LEN;
+ post_header_len[FORMAT_DESCRIPTION_EVENT-1]= FORMAT_DESCRIPTION_HEADER_LEN;
+ post_header_len[XID_EVENT-1]= XID_HEADER_LEN;
+ post_header_len[XA_PREPARE_LOG_EVENT-1]= XA_PREPARE_HEADER_LEN;
+ post_header_len[BEGIN_LOAD_QUERY_EVENT-1]= BEGIN_LOAD_QUERY_HEADER_LEN;
+ post_header_len[EXECUTE_LOAD_QUERY_EVENT-1]= EXECUTE_LOAD_QUERY_HEADER_LEN;
+ /*
+ The PRE_GA events are never be written to any binlog, but
+ their lengths are included in Format_description_log_event.
+ Hence, we need to be assign some value here, to avoid reading
+ uninitialized memory when the array is written to disk.
+ */
+ post_header_len[PRE_GA_WRITE_ROWS_EVENT-1]= 0;
+ post_header_len[PRE_GA_UPDATE_ROWS_EVENT-1]= 0;
+ post_header_len[PRE_GA_DELETE_ROWS_EVENT-1]= 0;
+
+ post_header_len[TABLE_MAP_EVENT-1]= TABLE_MAP_HEADER_LEN;
+ post_header_len[WRITE_ROWS_EVENT_V1-1]= ROWS_HEADER_LEN_V1;
+ post_header_len[UPDATE_ROWS_EVENT_V1-1]= ROWS_HEADER_LEN_V1;
+ post_header_len[DELETE_ROWS_EVENT_V1-1]= ROWS_HEADER_LEN_V1;
+ /*
+ We here have the possibility to simulate a master of before we changed
+ the table map id to be stored in 6 bytes: when it was stored in 4
+ bytes (=> post_header_len was 6). This is used to test backward
+ compatibility.
+ This code can be removed after a few months (today is Dec 21st 2005),
+ when we know that the 4-byte masters are not deployed anymore (check
+ with Tomas Ulin first!), and the accompanying test (rpl_row_4_bytes)
+ too.
+ */
+ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master",
+ post_header_len[TABLE_MAP_EVENT-1]=
+ post_header_len[WRITE_ROWS_EVENT_V1-1]=
+ post_header_len[UPDATE_ROWS_EVENT_V1-1]=
+ post_header_len[DELETE_ROWS_EVENT_V1-1]= 6;);
+ post_header_len[INCIDENT_EVENT-1]= INCIDENT_HEADER_LEN;
+ post_header_len[HEARTBEAT_LOG_EVENT-1]= 0;
+ post_header_len[IGNORABLE_LOG_EVENT-1]= 0;
+ post_header_len[ROWS_QUERY_LOG_EVENT-1]= 0;
+ post_header_len[GTID_LOG_EVENT-1]= 0;
+ post_header_len[ANONYMOUS_GTID_LOG_EVENT-1]= 0;
+ post_header_len[PREVIOUS_GTIDS_LOG_EVENT-1]= 0;
+ post_header_len[TRANSACTION_CONTEXT_EVENT-1]= 0;
+ post_header_len[VIEW_CHANGE_EVENT-1]= 0;
+ post_header_len[XA_PREPARE_LOG_EVENT-1]= 0;
+ post_header_len[WRITE_ROWS_EVENT-1]= ROWS_HEADER_LEN_V2;
+ post_header_len[UPDATE_ROWS_EVENT-1]= ROWS_HEADER_LEN_V2;
+ post_header_len[DELETE_ROWS_EVENT-1]= ROWS_HEADER_LEN_V2;
+
+ // Set header length of the reserved events to 0
+ memset(post_header_len + MYSQL_EVENTS_END - 1, 0,
+ (MARIA_EVENTS_BEGIN - MYSQL_EVENTS_END)*sizeof(uint8));
+
+ // Set header lengths of Maria events
+ post_header_len[ANNOTATE_ROWS_EVENT-1]= ANNOTATE_ROWS_HEADER_LEN;
+ post_header_len[BINLOG_CHECKPOINT_EVENT-1]=
+ BINLOG_CHECKPOINT_HEADER_LEN;
+ post_header_len[GTID_EVENT-1]= GTID_HEADER_LEN;
+ post_header_len[GTID_LIST_EVENT-1]= GTID_LIST_HEADER_LEN;
+ post_header_len[START_ENCRYPTION_EVENT-1]= START_ENCRYPTION_HEADER_LEN;
+
+ //compressed event
+ post_header_len[QUERY_COMPRESSED_EVENT-1]= QUERY_HEADER_LEN;
+ post_header_len[WRITE_ROWS_COMPRESSED_EVENT-1]= ROWS_HEADER_LEN_V2;
+ post_header_len[UPDATE_ROWS_COMPRESSED_EVENT-1]= ROWS_HEADER_LEN_V2;
+ post_header_len[DELETE_ROWS_COMPRESSED_EVENT-1]= ROWS_HEADER_LEN_V2;
+ post_header_len[WRITE_ROWS_COMPRESSED_EVENT_V1-1]= ROWS_HEADER_LEN_V1;
+ post_header_len[UPDATE_ROWS_COMPRESSED_EVENT_V1-1]= ROWS_HEADER_LEN_V1;
+ post_header_len[DELETE_ROWS_COMPRESSED_EVENT_V1-1]= ROWS_HEADER_LEN_V1;
+
+ // Sanity-check that all post header lengths are initialized.
+ int i;
+ for (i=0; i<number_of_event_types; i++)
+ DBUG_ASSERT(post_header_len[i] != 255);
+ }
+ break;
+
+ case 1: /* 3.23 */
+ case 3: /* 4.0.x x>=2 */
+ /*
+ We build an artificial (i.e. not sent by the master) event, which
+ describes what those old master versions send.
+ */
+ if (binlog_ver==1)
+ strmov(server_version, server_ver ? server_ver : "3.23");
+ else
+ strmov(server_version, server_ver ? server_ver : "4.0");
+ common_header_len= binlog_ver==1 ? OLD_HEADER_LEN :
+ LOG_EVENT_MINIMAL_HEADER_LEN;
+ /*
+ The first new event in binlog version 4 is Format_desc. So any event type
+ after that does not exist in older versions. We use the events known by
+ version 3, even if version 1 had only a subset of them (this is not a
+ problem: it uses a few bytes for nothing but unifies code; it does not
+ make the slave detect less corruptions).
+ */
+ number_of_event_types= FORMAT_DESCRIPTION_EVENT - 1;
+ post_header_len=(uint8*) my_malloc(PSI_INSTRUMENT_ME,
+ number_of_event_types*sizeof(uint8), MYF(0));
+ if (post_header_len)
+ {
+ post_header_len[START_EVENT_V3-1]= START_V3_HEADER_LEN;
+ post_header_len[QUERY_EVENT-1]= QUERY_HEADER_MINIMAL_LEN;
+ post_header_len[STOP_EVENT-1]= 0;
+ post_header_len[ROTATE_EVENT-1]= (binlog_ver==1) ? 0 : ROTATE_HEADER_LEN;
+ post_header_len[INTVAR_EVENT-1]= 0;
+ post_header_len[LOAD_EVENT-1]= LOAD_HEADER_LEN;
+ post_header_len[SLAVE_EVENT-1]= 0;
+ post_header_len[CREATE_FILE_EVENT-1]= CREATE_FILE_HEADER_LEN;
+ post_header_len[APPEND_BLOCK_EVENT-1]= APPEND_BLOCK_HEADER_LEN;
+ post_header_len[EXEC_LOAD_EVENT-1]= EXEC_LOAD_HEADER_LEN;
+ post_header_len[DELETE_FILE_EVENT-1]= DELETE_FILE_HEADER_LEN;
+ post_header_len[NEW_LOAD_EVENT-1]= post_header_len[LOAD_EVENT-1];
+ post_header_len[RAND_EVENT-1]= 0;
+ post_header_len[USER_VAR_EVENT-1]= 0;
+ }
+ break;
+ default: /* Includes binlog version 2 i.e. 4.0.x x<=1 */
+ post_header_len= 0; /* will make is_valid() fail */
+ break;
+ }
+ calc_server_version_split();
+ deduct_options_written_to_bin_log();
+ checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF;
+ reset_crypto();
+}
+
+
+/**
+ The problem with this constructor is that the fixed header may have a
+ length different from this version, but we don't know this length as we
+ have not read the Format_description_log_event which says it, yet. This
+ length is in the post-header of the event, but we don't know where the
+ post-header starts.
+
+ So this type of event HAS to:
+ - either have the header's length at the beginning (in the header, at a
+ fixed position which will never be changed), not in the post-header. That
+ would make the header be "shifted" compared to other events.
+ - or have a header of size LOG_EVENT_MINIMAL_HEADER_LEN (19), in all future
+ versions, so that we know for sure.
+
+ I (Guilhem) chose the 2nd solution. Rotate has the same constraint (because
+ it is sent before Format_description_log_event).
+*/
+
+Format_description_log_event::
+Format_description_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event*
+ description_event)
+ :Start_log_event_v3(buf, event_len, description_event),
+ common_header_len(0), post_header_len(NULL), event_type_permutation(0)
+{
+ DBUG_ENTER("Format_description_log_event::Format_description_log_event(char*,...)");
+ if (!Start_log_event_v3::is_valid())
+ DBUG_VOID_RETURN; /* sanity check */
+ buf+= LOG_EVENT_MINIMAL_HEADER_LEN;
+ if ((common_header_len=buf[ST_COMMON_HEADER_LEN_OFFSET]) < OLD_HEADER_LEN)
+ DBUG_VOID_RETURN; /* sanity check */
+ number_of_event_types=
+ event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET + 1);
+ DBUG_PRINT("info", ("common_header_len=%d number_of_event_types=%d",
+ common_header_len, number_of_event_types));
+ /* If alloc fails, we'll detect it in is_valid() */
+
+ post_header_len= (uint8*) my_memdup(PSI_INSTRUMENT_ME,
+ buf+ST_COMMON_HEADER_LEN_OFFSET+1,
+ number_of_event_types*
+ sizeof(*post_header_len),
+ MYF(0));
+ calc_server_version_split();
+ if (!is_version_before_checksum(&server_version_split))
+ {
+ /* the last bytes are the checksum alg desc and value (or value's room) */
+ number_of_event_types -= BINLOG_CHECKSUM_ALG_DESC_LEN;
+ checksum_alg= (enum_binlog_checksum_alg)post_header_len[number_of_event_types];
+ }
+ else
+ {
+ checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF;
+ }
+ deduct_options_written_to_bin_log();
+ reset_crypto();
+
+ DBUG_VOID_RETURN;
+}
+
+bool Format_description_log_event::start_decryption(Start_encryption_log_event* sele)
+{
+ DBUG_ASSERT(crypto_data.scheme == 0);
+
+ if (!sele->is_valid())
+ return 1;
+
+ memcpy(crypto_data.nonce, sele->nonce, BINLOG_NONCE_LENGTH);
+ return crypto_data.init(sele->crypto_scheme, sele->key_version);
+}
+
+
+Version::Version(const char *version, const char **endptr)
+{
+ const char *p= version;
+ ulong number;
+ for (uint i= 0; i<=2; i++)
+ {
+ char *r;
+ number= strtoul(p, &r, 10);
+ /*
+ It is an invalid version if any version number greater than 255 or
+ first number is not followed by '.'.
+ */
+ if (number < 256 && (*r == '.' || i != 0))
+ m_ver[i]= (uchar) number;
+ else
+ {
+ *this= Version();
+ break;
+ }
+
+ p= r;
+ if (*r == '.')
+ p++; // skip the dot
+ }
+ endptr[0]= p;
+}
+
+
+Format_description_log_event::
+ master_version_split::master_version_split(const char *version)
+{
+ const char *p;
+ static_cast<Version*>(this)[0]= Version(version, &p);
+ if (strstr(p, "MariaDB") != 0 || strstr(p, "-maria-") != 0)
+ kind= KIND_MARIADB;
+ else
+ kind= KIND_MYSQL;
+}
+
+
+/**
+ Splits the event's 'server_version' string into three numeric pieces stored
+ into 'server_version_split':
+ X.Y.Zabc (X,Y,Z numbers, a not a digit) -> {X,Y,Z}
+ X.Yabc -> {X,Y,0}
+ 'server_version_split' is then used for lookups to find if the server which
+ created this event has some known bug.
+*/
+void Format_description_log_event::calc_server_version_split()
+{
+ server_version_split= master_version_split(server_version);
+
+ DBUG_PRINT("info",("Format_description_log_event::server_version_split:"
+ " '%s' %d %d %d", server_version,
+ server_version_split[0],
+ server_version_split[1], server_version_split[2]));
+}
+
+
+void Format_description_log_event::deduct_options_written_to_bin_log()
+{
+ options_written_to_bin_log= OPTION_AUTO_IS_NULL | OPTION_NOT_AUTOCOMMIT |
+ OPTION_NO_FOREIGN_KEY_CHECKS | OPTION_RELAXED_UNIQUE_CHECKS |
+ OPTION_INSERT_HISTORY;
+ if (!server_version_split.version_is_valid() ||
+ server_version_split.kind == master_version_split::KIND_MYSQL ||
+ server_version_split < Version(10,5,2))
+ return;
+ options_written_to_bin_log|= OPTION_IF_EXISTS;
+ if (server_version_split[0] == 10)
+ {
+ const static char v[10]={99,99,99,99,99,17,9,5,4,2};
+ if (server_version_split[1] < 10 &&
+ server_version_split[2] < v[server_version_split[1]])
+ return;
+ }
+ options_written_to_bin_log|= OPTION_EXPLICIT_DEF_TIMESTAMP;
+
+ DBUG_ASSERT(options_written_to_bin_log == OPTIONS_WRITTEN_TO_BIN_LOG);
+}
+
+/**
+ @return TRUE is the event's version is earlier than one that introduced
+ the replication event checksum. FALSE otherwise.
+*/
+bool
+Format_description_log_event::is_version_before_checksum(const master_version_split
+ *version_split)
+{
+ return *version_split <
+ (version_split->kind == master_version_split::KIND_MARIADB ?
+ checksum_version_split_mariadb : checksum_version_split_mysql);
+}
+
+/**
+ @param buf buffer holding serialized FD event
+ @param len netto (possible checksum is stripped off) length of the event buf
+
+ @return the version-safe checksum alg descriptor where zero
+ designates no checksum, 255 - the orginator is
+ checksum-unaware (effectively no checksum) and the actuall
+ [1-254] range alg descriptor.
+*/
+enum enum_binlog_checksum_alg get_checksum_alg(const uchar *buf, ulong len)
+{
+ enum enum_binlog_checksum_alg ret;
+ char version[ST_SERVER_VER_LEN];
+
+ DBUG_ENTER("get_checksum_alg");
+ DBUG_ASSERT(buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT);
+
+ memcpy(version,
+ buf + LOG_EVENT_MINIMAL_HEADER_LEN + ST_SERVER_VER_OFFSET,
+ ST_SERVER_VER_LEN);
+ version[ST_SERVER_VER_LEN - 1]= 0;
+
+ Format_description_log_event::master_version_split version_split(version);
+ ret= Format_description_log_event::is_version_before_checksum(&version_split)
+ ? BINLOG_CHECKSUM_ALG_UNDEF
+ : (enum_binlog_checksum_alg)buf[len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN];
+ DBUG_ASSERT(ret == BINLOG_CHECKSUM_ALG_OFF ||
+ ret == BINLOG_CHECKSUM_ALG_UNDEF ||
+ ret == BINLOG_CHECKSUM_ALG_CRC32);
+ DBUG_RETURN(ret);
+}
+
+Start_encryption_log_event::
+Start_encryption_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event)
+{
+ if ((int)event_len ==
+ LOG_EVENT_MINIMAL_HEADER_LEN + Start_encryption_log_event::get_data_size())
+ {
+ buf+= LOG_EVENT_MINIMAL_HEADER_LEN;
+ crypto_scheme= *buf;
+ key_version= uint4korr(buf + BINLOG_CRYPTO_SCHEME_LENGTH);
+ memcpy(nonce,
+ buf + BINLOG_CRYPTO_SCHEME_LENGTH + BINLOG_KEY_VERSION_LENGTH,
+ BINLOG_NONCE_LENGTH);
+ }
+ else
+ crypto_scheme= ~0; // invalid
+}
+
+
+/**************************************************************************
+ Load_log_event methods
+ General note about Load_log_event: the binlogging of LOAD DATA INFILE is
+ going to be changed in 5.0 (or maybe in 5.1; not decided yet).
+ However, the 5.0 slave could still have to read such events (from a 4.x
+ master), convert them (which just means maybe expand the header, when 5.0
+ servers have a UID in events) (remember that whatever is after the header
+ will be like in 4.x, as this event's format is not modified in 5.0 as we
+ will use new types of events to log the new LOAD DATA INFILE features).
+ To be able to read/convert, we just need to not assume that the common
+ header is of length LOG_EVENT_HEADER_LEN (we must use the description
+ event).
+ Note that I (Guilhem) manually tested replication of a big LOAD DATA INFILE
+ between 3.23 and 5.0, and between 4.0 and 5.0, and it works fine (and the
+ positions displayed in SHOW SLAVE STATUS then are fine too).
+**************************************************************************/
+
+
+/**
+ @note
+ The caller must do buf[event_len]= 0 before he starts using the
+ constructed event.
+*/
+
+Load_log_event::Load_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ :Log_event(buf, description_event), num_fields(0), fields(0),
+ field_lens(0),field_block_len(0),
+ table_name(0), db(0), fname(0), local_fname(FALSE),
+ /*
+ Load_log_event which comes from the binary log does not contain
+ information about the type of insert which was used on the master.
+ Assume that it was an ordinary, non-concurrent LOAD DATA.
+ */
+ is_concurrent(FALSE)
+{
+ DBUG_ENTER("Load_log_event");
+ /*
+ I (Guilhem) manually tested replication of LOAD DATA INFILE for 3.23->5.0,
+ 4.0->5.0 and 5.0->5.0 and it works.
+ */
+ if (event_len)
+ copy_log_event(buf, event_len,
+ (((uchar)buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) ?
+ LOAD_HEADER_LEN +
+ description_event->common_header_len :
+ LOAD_HEADER_LEN + LOG_EVENT_HEADER_LEN),
+ description_event);
+ /* otherwise it's a derived class, will call copy_log_event() itself */
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Load_log_event::copy_log_event()
+*/
+
+int Load_log_event::copy_log_event(const uchar *buf, ulong event_len,
+ int body_offset,
+ const Format_description_log_event
+ *description_event)
+{
+ DBUG_ENTER("Load_log_event::copy_log_event");
+ uint data_len;
+ if ((int) event_len <= body_offset)
+ DBUG_RETURN(1);
+ const uchar *buf_end= buf + event_len;
+ /* this is the beginning of the post-header */
+ const uchar *data_head= buf + description_event->common_header_len;
+ thread_id= slave_proxy_id= uint4korr(data_head + L_THREAD_ID_OFFSET);
+ exec_time= uint4korr(data_head + L_EXEC_TIME_OFFSET);
+ skip_lines= uint4korr(data_head + L_SKIP_LINES_OFFSET);
+ table_name_len= (uint)data_head[L_TBL_LEN_OFFSET];
+ db_len= (uint)data_head[L_DB_LEN_OFFSET];
+ num_fields= uint4korr(data_head + L_NUM_FIELDS_OFFSET);
+
+ /*
+ Sql_ex.init() on success returns the pointer to the first byte after
+ the sql_ex structure, which is the start of field lengths array.
+ */
+ if (!(field_lens= (uchar*) sql_ex.init(buf + body_offset, buf_end,
+ buf[EVENT_TYPE_OFFSET] != LOAD_EVENT)))
+ DBUG_RETURN(1);
+
+ data_len= event_len - body_offset;
+ if (num_fields > data_len) // simple sanity check against corruption
+ DBUG_RETURN(1);
+ for (uint i= 0; i < num_fields; i++)
+ field_block_len+= (uint)field_lens[i] + 1;
+
+ fields= (char*) field_lens + num_fields;
+ table_name= fields + field_block_len;
+ if (strlen(table_name) > NAME_LEN)
+ goto err;
+
+ db= table_name + table_name_len + 1;
+ DBUG_EXECUTE_IF("simulate_invalid_address", db_len= data_len;);
+ fname= db + db_len + 1;
+ if ((db_len > data_len) || (fname > (char*) buf_end))
+ goto err;
+ fname_len= (uint) strlen(fname);
+ if ((fname_len > data_len) || (fname + fname_len > (char*) buf_end))
+ goto err;
+ // null termination is accomplished by the caller doing buf[event_len]=0
+
+ DBUG_RETURN(0);
+
+err:
+ // Invalid event.
+ table_name= 0;
+ DBUG_RETURN(1);
+}
+
+
+/**************************************************************************
+ Rotate_log_event methods
+**************************************************************************/
+
+Rotate_log_event::Rotate_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event*
+ description_event)
+ :Log_event(buf, description_event) ,new_log_ident(0), flags(DUP_NAME)
+{
+ DBUG_ENTER("Rotate_log_event::Rotate_log_event(char*,...)");
+ // The caller will ensure that event_len is what we have at EVENT_LEN_OFFSET
+ uint8 post_header_len= description_event->post_header_len[ROTATE_EVENT-1];
+ uint ident_offset;
+ if (event_len < (uint)(LOG_EVENT_MINIMAL_HEADER_LEN + post_header_len))
+ DBUG_VOID_RETURN;
+ buf+= LOG_EVENT_MINIMAL_HEADER_LEN;
+ pos= post_header_len ? uint8korr(buf + R_POS_OFFSET) : 4;
+ ident_len= (uint)(event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + post_header_len));
+ ident_offset= post_header_len;
+ set_if_smaller(ident_len,FN_REFLEN-1);
+ new_log_ident= my_strndup(PSI_INSTRUMENT_ME, (char*) buf + ident_offset,
+ (uint) ident_len, MYF(MY_WME));
+ DBUG_PRINT("debug", ("new_log_ident: '%s'", new_log_ident));
+ DBUG_VOID_RETURN;
+}
+
+
+/**************************************************************************
+ Binlog_checkpoint_log_event methods
+**************************************************************************/
+
+Binlog_checkpoint_log_event::Binlog_checkpoint_log_event(
+ const uchar *buf, uint event_len,
+ const Format_description_log_event *description_event)
+ :Log_event(buf, description_event), binlog_file_name(0)
+{
+ uint8 header_size= description_event->common_header_len;
+ uint8 post_header_len=
+ description_event->post_header_len[BINLOG_CHECKPOINT_EVENT-1];
+ if (event_len < (uint) header_size + (uint) post_header_len ||
+ post_header_len < BINLOG_CHECKPOINT_HEADER_LEN)
+ return;
+ buf+= header_size;
+ /* See uint4korr and int4store below */
+ compile_time_assert(BINLOG_CHECKPOINT_HEADER_LEN == 4);
+ binlog_file_len= uint4korr(buf);
+ if (event_len - (header_size + post_header_len) < binlog_file_len)
+ return;
+ binlog_file_name= my_strndup(PSI_INSTRUMENT_ME, (char*) buf + post_header_len,
+ binlog_file_len, MYF(MY_WME));
+ return;
+}
+
+
+/**************************************************************************
+ Global transaction ID stuff
+**************************************************************************/
+
+Gtid_log_event::Gtid_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ : Log_event(buf, description_event), seq_no(0), commit_id(0),
+ flags_extra(0), extra_engines(0)
+{
+ uint8 header_size= description_event->common_header_len;
+ uint8 post_header_len= description_event->post_header_len[GTID_EVENT-1];
+ const uchar *buf_0= buf;
+ if (event_len < (uint) header_size + (uint) post_header_len ||
+ post_header_len < GTID_HEADER_LEN)
+ return;
+
+ buf+= header_size;
+ seq_no= uint8korr(buf);
+ buf+= 8;
+ domain_id= uint4korr(buf);
+ buf+= 4;
+ flags2= *(buf++);
+ if (flags2 & FL_GROUP_COMMIT_ID)
+ {
+ if (event_len < (uint)header_size + GTID_HEADER_LEN + 2)
+ {
+ seq_no= 0; // So is_valid() returns false
+ return;
+ }
+ commit_id= uint8korr(buf);
+ buf+= 8;
+ }
+ if (flags2 & (FL_PREPARED_XA | FL_COMPLETED_XA))
+ {
+ xid.formatID= uint4korr(buf);
+ buf+= 4;
+
+ xid.gtrid_length= (long) buf[0];
+ xid.bqual_length= (long) buf[1];
+ buf+= 2;
+
+ long data_length= xid.bqual_length + xid.gtrid_length;
+ memcpy(xid.data, buf, data_length);
+ buf+= data_length;
+ }
+
+ /* the extra flags check and actions */
+ if (static_cast<uint>(buf - buf_0) < event_len)
+ {
+ flags_extra= *buf++;
+ /*
+ extra engines flags presence is identifed by non-zero byte value
+ at this point
+ */
+ if (flags_extra & FL_EXTRA_MULTI_ENGINE_E1)
+ {
+ DBUG_ASSERT(static_cast<uint>(buf - buf_0) < event_len);
+
+ extra_engines= *buf++;
+
+ DBUG_ASSERT(extra_engines > 0);
+ }
+ if (flags_extra & (FL_COMMIT_ALTER_E1 | FL_ROLLBACK_ALTER_E1))
+ {
+ sa_seq_no= uint8korr(buf);
+ buf+= 8;
+ }
+ }
+ /*
+ the strict '<' part of the assert corresponds to extra zero-padded
+ trailing bytes,
+ */
+ DBUG_ASSERT(static_cast<uint>(buf - buf_0) <= event_len);
+ /* and the last of them is tested. */
+#ifdef MYSQL_SERVER
+#ifdef WITH_WSREP
+ if (!WSREP_ON)
+#endif
+#endif
+ DBUG_ASSERT(static_cast<uint>(buf - buf_0) == event_len ||
+ buf_0[event_len - 1] == 0);
+}
+
+int compare_glle_gtids(const void * _gtid1, const void *_gtid2)
+{
+ rpl_gtid *gtid1= (rpl_gtid *) _gtid1;
+ rpl_gtid *gtid2= (rpl_gtid *) _gtid2;
+
+ int ret;
+ if (*gtid1 < *gtid2)
+ ret= -1;
+ else if (*gtid1 > *gtid2)
+ ret= 1;
+ else
+ ret= 0;
+ return ret;
+}
+
+/* GTID list. */
+
+Gtid_list_log_event::Gtid_list_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ : Log_event(buf, description_event), count(0), list(0), sub_id_list(0)
+{
+ uint32 i;
+ uint32 val;
+ uint8 header_size= description_event->common_header_len;
+ uint8 post_header_len= description_event->post_header_len[GTID_LIST_EVENT-1];
+ if (event_len < (uint) header_size + (uint) post_header_len ||
+ post_header_len < GTID_LIST_HEADER_LEN)
+ return;
+
+ buf+= header_size;
+ val= uint4korr(buf);
+ count= val & ((1<<28)-1);
+ gl_flags= val & ((uint32)0xf << 28);
+ buf+= 4;
+ if (event_len - (header_size + post_header_len) < count*element_size ||
+ (!(list= (rpl_gtid *)my_malloc(PSI_INSTRUMENT_ME,
+ count*sizeof(*list) + (count == 0), MYF(MY_WME)))))
+ return;
+
+ for (i= 0; i < count; ++i)
+ {
+ list[i].domain_id= uint4korr(buf);
+ buf+= 4;
+ list[i].server_id= uint4korr(buf);
+ buf+= 4;
+ list[i].seq_no= uint8korr(buf);
+ buf+= 8;
+ }
+
+#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+ if ((gl_flags & FLAG_IGN_GTIDS))
+ {
+ uint32 i;
+ if (!(sub_id_list= (uint64 *)my_malloc(PSI_INSTRUMENT_ME,
+ count*sizeof(uint64), MYF(MY_WME))))
+ {
+ my_free(list);
+ list= NULL;
+ return;
+ }
+ for (i= 0; i < count; ++i)
+ {
+ if (!(sub_id_list[i]=
+ rpl_global_gtid_slave_state->next_sub_id(list[i].domain_id)))
+ {
+ my_free(list);
+ my_free(sub_id_list);
+ list= NULL;
+ sub_id_list= NULL;
+ return;
+ }
+ }
+ }
+#endif
+}
+
+
+/*
+ Used to record gtid_list event while sending binlog to slave, without having to
+ fully contruct the event object.
+*/
+bool
+Gtid_list_log_event::peek(const char *event_start, size_t event_len,
+ enum enum_binlog_checksum_alg checksum_alg,
+ rpl_gtid **out_gtid_list, uint32 *out_list_len,
+ const Format_description_log_event *fdev)
+{
+ const char *p;
+ uint32 count_field, count;
+ rpl_gtid *gtid_list;
+
+ if (checksum_alg == BINLOG_CHECKSUM_ALG_CRC32)
+ {
+ if (event_len > BINLOG_CHECKSUM_LEN)
+ event_len-= BINLOG_CHECKSUM_LEN;
+ else
+ event_len= 0;
+ }
+ else
+ DBUG_ASSERT(checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF ||
+ checksum_alg == BINLOG_CHECKSUM_ALG_OFF);
+
+ if (event_len < (uint32)fdev->common_header_len + GTID_LIST_HEADER_LEN)
+ return true;
+ p= event_start + fdev->common_header_len;
+ count_field= uint4korr(p);
+ p+= 4;
+ count= count_field & ((1<<28)-1);
+ if (event_len < (uint32)fdev->common_header_len + GTID_LIST_HEADER_LEN +
+ element_size * count)
+ return true;
+ if (!(gtid_list= (rpl_gtid *)my_malloc(PSI_INSTRUMENT_ME,
+ sizeof(rpl_gtid)*count + (count == 0), MYF(MY_WME))))
+ return true;
+ *out_gtid_list= gtid_list;
+ *out_list_len= count;
+ while (count--)
+ {
+ gtid_list->domain_id= uint4korr(p);
+ p+= 4;
+ gtid_list->server_id= uint4korr(p);
+ p+= 4;
+ gtid_list->seq_no= uint8korr(p);
+ p+= 8;
+ ++gtid_list;
+ }
+
+ return false;
+}
+
+
+/**************************************************************************
+ Intvar_log_event methods
+**************************************************************************/
+
+/*
+ Intvar_log_event::Intvar_log_event()
+*/
+
+Intvar_log_event::Intvar_log_event(const uchar *buf,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event)
+{
+ /* The Post-Header is empty. The Variable Data part begins immediately. */
+ buf+= description_event->common_header_len +
+ description_event->post_header_len[INTVAR_EVENT-1];
+ type= buf[I_TYPE_OFFSET];
+ val= uint8korr(buf+I_VAL_OFFSET);
+}
+
+
+/*
+ Intvar_log_event::get_var_type_name()
+*/
+
+const char* Intvar_log_event::get_var_type_name()
+{
+ switch(type) {
+ case LAST_INSERT_ID_EVENT: return "LAST_INSERT_ID";
+ case INSERT_ID_EVENT: return "INSERT_ID";
+ default: /* impossible */ return "UNKNOWN";
+ }
+}
+
+
+/**************************************************************************
+ Rand_log_event methods
+**************************************************************************/
+
+Rand_log_event::Rand_log_event(const uchar *buf,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event)
+{
+ /* The Post-Header is empty. The Variable Data part begins immediately. */
+ buf+= description_event->common_header_len +
+ description_event->post_header_len[RAND_EVENT-1];
+ seed1= uint8korr(buf+RAND_SEED1_OFFSET);
+ seed2= uint8korr(buf+RAND_SEED2_OFFSET);
+}
+
+
+/**************************************************************************
+ Xid_log_event methods
+**************************************************************************/
+
+/**
+ @note
+ It's ok not to use int8store here,
+ as long as xid_t::set(ulonglong) and
+ xid_t::get_my_xid doesn't do it either.
+ We don't care about actual values of xids as long as
+ identical numbers compare identically
+*/
+
+Xid_log_event::
+Xid_log_event(const uchar *buf,
+ const Format_description_log_event *description_event)
+ :Xid_apply_log_event(buf, description_event)
+{
+ /* The Post-Header is empty. The Variable Data part begins immediately. */
+ buf+= description_event->common_header_len +
+ description_event->post_header_len[XID_EVENT-1];
+ memcpy((char*) &xid, buf, sizeof(xid));
+}
+
+/**************************************************************************
+ XA_prepare_log_event methods
+**************************************************************************/
+XA_prepare_log_event::
+XA_prepare_log_event(const uchar *buf,
+ const Format_description_log_event *description_event)
+ :Xid_apply_log_event(buf, description_event)
+{
+ buf+= description_event->common_header_len +
+ description_event->post_header_len[XA_PREPARE_LOG_EVENT-1];
+ one_phase= * (bool *) buf;
+ buf+= 1;
+
+ m_xid.formatID= uint4korr(buf);
+ buf+= 4;
+ m_xid.gtrid_length= uint4korr(buf);
+ buf+= 4;
+ // Todo: validity here and elsewhere checks to be replaced by MDEV-21839 fixes
+ if (m_xid.gtrid_length <= 0 || m_xid.gtrid_length > MAXGTRIDSIZE)
+ {
+ m_xid.formatID= -1;
+ return;
+ }
+ m_xid.bqual_length= uint4korr(buf);
+ buf+= 4;
+ if (m_xid.bqual_length < 0 || m_xid.bqual_length > MAXBQUALSIZE)
+ {
+ m_xid.formatID= -1;
+ return;
+ }
+ DBUG_ASSERT(m_xid.gtrid_length + m_xid.bqual_length <= XIDDATASIZE);
+
+ memcpy(m_xid.data, buf, m_xid.gtrid_length + m_xid.bqual_length);
+
+ xid= NULL;
+}
+
+
+/**************************************************************************
+ User_var_log_event methods
+**************************************************************************/
+
+User_var_log_event::
+User_var_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event)
+#ifndef MYSQL_CLIENT
+ , deferred(false), query_id(0)
+#endif
+{
+ bool error= false;
+ const uchar *buf_start= buf, *buf_end= buf + event_len;
+
+ /* The Post-Header is empty. The Variable Data part begins immediately. */
+ buf+= description_event->common_header_len +
+ description_event->post_header_len[USER_VAR_EVENT-1];
+ name_len= uint4korr(buf);
+ /* Avoid reading out of buffer */
+ if ((buf - buf_start) + UV_NAME_LEN_SIZE + name_len > event_len)
+ {
+ error= true;
+ goto err;
+ }
+
+ name= (char *) buf + UV_NAME_LEN_SIZE;
+
+ /*
+ We don't know yet is_null value, so we must assume that name_len
+ may have the bigger value possible, is_null= True and there is no
+ payload for val, or even that name_len is 0.
+ */
+ if (name + name_len + UV_VAL_IS_NULL > (char*) buf_end)
+ {
+ error= true;
+ goto err;
+ }
+
+ buf+= UV_NAME_LEN_SIZE + name_len;
+ is_null= (bool) *buf;
+ flags= User_var_log_event::UNDEF_F; // defaults to UNDEF_F
+ if (is_null)
+ {
+ type= STRING_RESULT;
+ charset_number= my_charset_bin.number;
+ val_len= 0;
+ val= 0;
+ }
+ else
+ {
+ val= (char *) (buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
+ UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE);
+
+ if (val > (char*) buf_end)
+ {
+ error= true;
+ goto err;
+ }
+
+ type= (Item_result) buf[UV_VAL_IS_NULL];
+ charset_number= uint4korr(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE);
+ val_len= uint4korr(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
+ UV_CHARSET_NUMBER_SIZE);
+
+ /**
+ We need to check if this is from an old server
+ that did not pack information for flags.
+ We do this by checking if there are extra bytes
+ after the packed value. If there are we take the
+ extra byte and it's value is assumed to contain
+ the flags value.
+
+ Old events will not have this extra byte, thence,
+ we keep the flags set to UNDEF_F.
+ */
+ size_t bytes_read= (val + val_len) - (char*) buf_start;
+ if (bytes_read > event_len)
+ {
+ error= true;
+ goto err;
+ }
+ if ((data_written - bytes_read) > 0)
+ {
+ flags= (uint) *(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
+ UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE +
+ val_len);
+ }
+ }
+
+err:
+ if (unlikely(error))
+ name= 0;
+}
+
+
+/**************************************************************************
+ Create_file_log_event methods
+**************************************************************************/
+
+/*
+ Create_file_log_event ctor
+*/
+
+Create_file_log_event::
+Create_file_log_event(const uchar *buf, uint len,
+ const Format_description_log_event* description_event)
+ :Load_log_event(buf,0,description_event),fake_base(0),block(0),
+ inited_from_old(0)
+{
+ DBUG_ENTER("Create_file_log_event::Create_file_log_event(char*,...)");
+ uint block_offset;
+ uint header_len= description_event->common_header_len;
+ uint8 load_header_len= description_event->post_header_len[LOAD_EVENT-1];
+ uint8 create_file_header_len= description_event->post_header_len[CREATE_FILE_EVENT-1];
+ if (!(event_buf= (uchar*) my_memdup(PSI_INSTRUMENT_ME, buf, len,
+ MYF(MY_WME))) ||
+ copy_log_event(event_buf,len,
+ (((uchar)buf[EVENT_TYPE_OFFSET] == LOAD_EVENT) ?
+ load_header_len + header_len :
+ (fake_base ? (header_len+load_header_len) :
+ (header_len+load_header_len) +
+ create_file_header_len)),
+ description_event))
+ DBUG_VOID_RETURN;
+ if (description_event->binlog_version!=1)
+ {
+ file_id= uint4korr(buf +
+ header_len +
+ load_header_len + CF_FILE_ID_OFFSET);
+ /*
+ Note that it's ok to use get_data_size() below, because it is computed
+ with values we have already read from this event (because we called
+ copy_log_event()); we are not using slave's format info to decode
+ master's format, we are really using master's format info.
+ Anyway, both formats should be identical (except the common_header_len)
+ as these Load events are not changed between 4.0 and 5.0 (as logging of
+ LOAD DATA INFILE does not use Load_log_event in 5.0).
+
+ The + 1 is for \0 terminating fname
+ */
+ block_offset= (description_event->common_header_len +
+ Load_log_event::get_data_size() +
+ create_file_header_len + 1);
+ if (len < block_offset)
+ DBUG_VOID_RETURN;
+ block= const_cast<uchar*>(buf) + block_offset;
+ block_len= len - block_offset;
+ }
+ else
+ {
+ sql_ex.force_new_format();
+ inited_from_old= 1;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+/**************************************************************************
+ Append_block_log_event methods
+**************************************************************************/
+
+/*
+ Append_block_log_event ctor
+*/
+
+Append_block_log_event::
+Append_block_log_event(const uchar *buf, uint len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event),block(0)
+{
+ DBUG_ENTER("Append_block_log_event::Append_block_log_event(char*,...)");
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 append_block_header_len=
+ description_event->post_header_len[APPEND_BLOCK_EVENT-1];
+ uint total_header_len= common_header_len+append_block_header_len;
+ if (len < total_header_len)
+ DBUG_VOID_RETURN;
+ file_id= uint4korr(buf + common_header_len + AB_FILE_ID_OFFSET);
+ block= const_cast<uchar*>(buf) + total_header_len;
+ block_len= len - total_header_len;
+ DBUG_VOID_RETURN;
+}
+
+
+/**************************************************************************
+ Delete_file_log_event methods
+**************************************************************************/
+
+/*
+ Delete_file_log_event ctor
+*/
+
+Delete_file_log_event::
+Delete_file_log_event(const uchar *buf, uint len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event),file_id(0)
+{
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 delete_file_header_len= description_event->post_header_len[DELETE_FILE_EVENT-1];
+ if (len < (uint)(common_header_len + delete_file_header_len))
+ return;
+ file_id= uint4korr(buf + common_header_len + DF_FILE_ID_OFFSET);
+}
+
+
+/**************************************************************************
+ Execute_load_log_event methods
+**************************************************************************/
+
+/*
+ Execute_load_log_event ctor
+*/
+
+Execute_load_log_event::
+Execute_load_log_event(const uchar *buf, uint len,
+ const Format_description_log_event* description_event)
+ :Log_event(buf, description_event), file_id(0)
+{
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 exec_load_header_len= description_event->post_header_len[EXEC_LOAD_EVENT-1];
+ if (len < (uint)(common_header_len+exec_load_header_len))
+ return;
+ file_id= uint4korr(buf + common_header_len + EL_FILE_ID_OFFSET);
+}
+
+
+/**************************************************************************
+ Begin_load_query_log_event methods
+**************************************************************************/
+
+Begin_load_query_log_event::
+Begin_load_query_log_event(const uchar *buf, uint len,
+ const Format_description_log_event* desc_event)
+ :Append_block_log_event(buf, len, desc_event)
+{
+}
+
+
+/**************************************************************************
+ Execute_load_query_log_event methods
+**************************************************************************/
+
+
+Execute_load_query_log_event::
+Execute_load_query_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event* desc_event):
+ Query_log_event(buf, event_len, desc_event, EXECUTE_LOAD_QUERY_EVENT),
+ file_id(0), fn_pos_start(0), fn_pos_end(0)
+{
+ if (!Query_log_event::is_valid())
+ return;
+
+ buf+= desc_event->common_header_len;
+
+ fn_pos_start= uint4korr(buf + ELQ_FN_POS_START_OFFSET);
+ fn_pos_end= uint4korr(buf + ELQ_FN_POS_END_OFFSET);
+ dup_handling= (enum_load_dup_handling)(*(buf + ELQ_DUP_HANDLING_OFFSET));
+
+ if (fn_pos_start > q_len || fn_pos_end > q_len ||
+ dup_handling > LOAD_DUP_REPLACE)
+ return;
+
+ file_id= uint4korr(buf + ELQ_FILE_ID_OFFSET);
+}
+
+
+ulong Execute_load_query_log_event::get_post_header_size_for_derived()
+{
+ return EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN;
+}
+
+
+/**************************************************************************
+ sql_ex_info methods
+**************************************************************************/
+
+/*
+ sql_ex_info::init()
+*/
+
+const uchar *sql_ex_info::init(const uchar *buf, const uchar *buf_end,
+ bool use_new_format)
+{
+ cached_new_format= use_new_format;
+ if (use_new_format)
+ {
+ empty_flags=0;
+ /*
+ The code below assumes that buf will not disappear from
+ under our feet during the lifetime of the event. This assumption
+ holds true in the slave thread if the log is in new format, but is not
+ the case when we have old format because we will be reusing net buffer
+ to read the actual file before we write out the Create_file event.
+ */
+ if (read_str(&buf, buf_end, &field_term, &field_term_len) ||
+ read_str(&buf, buf_end, &enclosed, &enclosed_len) ||
+ read_str(&buf, buf_end, &line_term, &line_term_len) ||
+ read_str(&buf, buf_end, &line_start, &line_start_len) ||
+ read_str(&buf, buf_end, &escaped, &escaped_len))
+ return 0;
+ opt_flags= *buf++;
+ }
+ else
+ {
+ if (buf_end - buf < 7)
+ return 0; // Wrong data
+ field_term_len= enclosed_len= line_term_len= line_start_len= escaped_len=1;
+ field_term= (char*) buf++; // Use first byte in string
+ enclosed= (char*) buf++;
+ line_term= (char*) buf++;
+ line_start= (char*) buf++;
+ escaped= (char*) buf++;
+ opt_flags= *buf++;
+ empty_flags= *buf++;
+ if (empty_flags & FIELD_TERM_EMPTY)
+ field_term_len=0;
+ if (empty_flags & ENCLOSED_EMPTY)
+ enclosed_len=0;
+ if (empty_flags & LINE_TERM_EMPTY)
+ line_term_len=0;
+ if (empty_flags & LINE_START_EMPTY)
+ line_start_len=0;
+ if (empty_flags & ESCAPED_EMPTY)
+ escaped_len=0;
+ }
+ return buf;
+}
+
+
+
+/**************************************************************************
+ Rows_log_event member functions
+**************************************************************************/
+
+
+Rows_log_event::Rows_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ : Log_event(buf, description_event),
+ m_row_count(0),
+#ifndef MYSQL_CLIENT
+ m_table(NULL),
+#endif
+ m_table_id(0), m_rows_buf(0), m_rows_cur(0), m_rows_end(0),
+ m_extra_row_data(0)
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ , m_curr_row(NULL), m_curr_row_end(NULL),
+ m_key(NULL), m_key_info(NULL), m_key_nr(0),
+ master_had_triggers(0)
+#endif
+{
+ DBUG_ENTER("Rows_log_event::Rows_log_event(const char*,...)");
+ uint8 const common_header_len= description_event->common_header_len;
+ Log_event_type event_type= (Log_event_type)(uchar)buf[EVENT_TYPE_OFFSET];
+ m_type= event_type;
+ m_cols_ai.bitmap= 0;
+
+ uint8 const post_header_len= description_event->post_header_len[event_type-1];
+
+ if (event_len < (uint)(common_header_len + post_header_len))
+ {
+ m_cols.bitmap= 0;
+ DBUG_VOID_RETURN;
+ }
+
+ DBUG_PRINT("enter",("event_len: %u common_header_len: %d "
+ "post_header_len: %d",
+ event_len, common_header_len,
+ post_header_len));
+
+ const uchar *post_start= buf + common_header_len;
+ post_start+= RW_MAPID_OFFSET;
+ if (post_header_len == 6)
+ {
+ /* Master is of an intermediate source tree before 5.1.4. Id is 4 bytes */
+ m_table_id= uint4korr(post_start);
+ post_start+= 4;
+ }
+ else
+ {
+ m_table_id= (ulong) uint6korr(post_start);
+ post_start+= RW_FLAGS_OFFSET;
+ }
+
+ m_flags_pos= post_start - buf;
+ m_flags= uint2korr(post_start);
+ post_start+= 2;
+
+ uint16 var_header_len= 0;
+ if (post_header_len == ROWS_HEADER_LEN_V2)
+ {
+ /*
+ Have variable length header, check length,
+ which includes length bytes
+ */
+ var_header_len= uint2korr(post_start);
+ /* Check length and also avoid out of buffer read */
+ if (var_header_len < 2 ||
+ event_len < static_cast<unsigned int>(var_header_len +
+ (post_start - buf)))
+ {
+ m_cols.bitmap= 0;
+ DBUG_VOID_RETURN;
+ }
+ var_header_len-= 2;
+
+ /* Iterate over var-len header, extracting 'chunks' */
+ const uchar *start= post_start + 2;
+ const uchar *end= start + var_header_len;
+ for (const uchar* pos= start; pos < end;)
+ {
+ switch(*pos++)
+ {
+ case RW_V_EXTRAINFO_TAG:
+ {
+ /* Have an 'extra info' section, read it in */
+ assert((end - pos) >= EXTRA_ROW_INFO_HDR_BYTES);
+ uint8 infoLen= pos[EXTRA_ROW_INFO_LEN_OFFSET];
+ assert((end - pos) >= infoLen);
+ /* Just store/use the first tag of this type, skip others */
+ if (likely(!m_extra_row_data))
+ {
+ m_extra_row_data= (uchar*) my_malloc(PSI_INSTRUMENT_ME, infoLen,
+ MYF(MY_WME));
+ if (likely(m_extra_row_data != NULL))
+ {
+ memcpy(m_extra_row_data, pos, infoLen);
+ }
+ }
+ pos+= infoLen;
+ break;
+ }
+ default:
+ /* Unknown code, we will not understand anything further here */
+ pos= end; /* Break loop */
+ }
+ }
+ }
+
+ uchar const *const var_start=
+ (const uchar *)buf + common_header_len + post_header_len + var_header_len;
+ uchar const *const ptr_width= var_start;
+ uchar *ptr_after_width= (uchar*) ptr_width;
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+ m_width= net_field_length(&ptr_after_width);
+ DBUG_PRINT("debug", ("m_width=%lu", m_width));
+
+ /* Avoid reading out of buffer */
+ if (ptr_after_width + (m_width + 7) / 8 > (uchar*)buf + event_len)
+ {
+ m_cols.bitmap= NULL;
+ DBUG_VOID_RETURN;
+ }
+
+ /* if my_bitmap_init fails, caught in is_valid() */
+ if (likely(!my_bitmap_init(&m_cols,
+ m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL,
+ m_width)))
+ {
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+ memcpy(m_cols.bitmap, ptr_after_width, (m_width + 7) / 8);
+ create_last_word_mask(&m_cols);
+ ptr_after_width+= (m_width + 7) / 8;
+ DBUG_DUMP("m_cols", (uchar*) m_cols.bitmap, no_bytes_in_map(&m_cols));
+ }
+ else
+ {
+ // Needed because my_bitmap_init() does not set it to null on failure
+ m_cols.bitmap= NULL;
+ DBUG_VOID_RETURN;
+ }
+
+ m_cols_ai.bitmap= m_cols.bitmap; /* See explanation in is_valid() */
+
+ if (LOG_EVENT_IS_UPDATE_ROW(event_type))
+ {
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+
+ /* if my_bitmap_init fails, caught in is_valid() */
+ if (likely(!my_bitmap_init(&m_cols_ai,
+ m_width <= sizeof(m_bitbuf_ai)*8 ? m_bitbuf_ai : NULL,
+ m_width)))
+ {
+ DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
+ memcpy(m_cols_ai.bitmap, ptr_after_width, (m_width + 7) / 8);
+ create_last_word_mask(&m_cols_ai);
+ ptr_after_width+= (m_width + 7) / 8;
+ DBUG_DUMP("m_cols_ai", (uchar*) m_cols_ai.bitmap,
+ no_bytes_in_map(&m_cols_ai));
+ }
+ else
+ {
+ // Needed because my_bitmap_init() does not set it to null on failure
+ m_cols_ai.bitmap= 0;
+ DBUG_VOID_RETURN;
+ }
+ }
+
+ const uchar* const ptr_rows_data= (const uchar*) ptr_after_width;
+
+ size_t const read_size= ptr_rows_data - (const unsigned char *) buf;
+ if (read_size > event_len)
+ {
+ DBUG_VOID_RETURN;
+ }
+ size_t const data_size= event_len - read_size;
+ DBUG_PRINT("info",("m_table_id: %llu m_flags: %d m_width: %lu data_size: %lu",
+ m_table_id, m_flags, m_width, (ulong) data_size));
+
+ m_rows_buf= (uchar*) my_malloc(PSI_INSTRUMENT_ME, data_size, MYF(MY_WME));
+ if (likely((bool)m_rows_buf))
+ {
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ m_curr_row= m_rows_buf;
+#endif
+ m_rows_end= m_rows_buf + data_size;
+ m_rows_cur= m_rows_end;
+ memcpy(m_rows_buf, ptr_rows_data, data_size);
+ m_rows_before_size= ptr_rows_data - (const uchar *) buf; // Get the size that before SET part
+ }
+ else
+ m_cols.bitmap= 0; // to not free it
+
+ DBUG_VOID_RETURN;
+}
+
+void Rows_log_event::uncompress_buf()
+{
+ uint32 un_len= binlog_get_uncompress_len(m_rows_buf);
+ if (!un_len)
+ return;
+
+ uchar *new_buf= (uchar*) my_malloc(PSI_INSTRUMENT_ME, ALIGN_SIZE(un_len),
+ MYF(MY_WME));
+ if (new_buf)
+ {
+ if (!binlog_buf_uncompress(m_rows_buf, new_buf,
+ (uint32)(m_rows_cur - m_rows_buf), &un_len))
+ {
+ my_free(m_rows_buf);
+ m_rows_buf= new_buf;
+#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
+ m_curr_row= m_rows_buf;
+#endif
+ m_rows_end= m_rows_buf + un_len;
+ m_rows_cur= m_rows_end;
+ return;
+ }
+ else
+ {
+ my_free(new_buf);
+ }
+ }
+ m_cols.bitmap= 0; // catch it in is_valid
+}
+
+Rows_log_event::~Rows_log_event()
+{
+ if (m_cols.bitmap == m_bitbuf) // no my_malloc happened
+ m_cols.bitmap= 0; // so no my_free in my_bitmap_free
+ my_bitmap_free(&m_cols); // To pair with my_bitmap_init().
+ my_free(m_rows_buf);
+ my_free(m_extra_row_data);
+}
+
+int Rows_log_event::get_data_size()
+{
+ int const general_type_code= get_general_type_code();
+
+ uchar buf[MAX_INT_WIDTH];
+ uchar *end= net_store_length(buf, m_width);
+
+ DBUG_EXECUTE_IF("old_row_based_repl_4_byte_map_id_master",
+ return (int)(6 + no_bytes_in_map(&m_cols) + (end - buf) +
+ (general_type_code == UPDATE_ROWS_EVENT ? no_bytes_in_map(&m_cols_ai) : 0) +
+ m_rows_cur - m_rows_buf););
+ int data_size= 0;
+ Log_event_type type= get_type_code();
+ bool is_v2_event= LOG_EVENT_IS_ROW_V2(type);
+ if (is_v2_event)
+ {
+ data_size= ROWS_HEADER_LEN_V2 +
+ (m_extra_row_data ?
+ RW_V_TAG_LEN + m_extra_row_data[EXTRA_ROW_INFO_LEN_OFFSET]:
+ 0);
+ }
+ else
+ {
+ data_size= ROWS_HEADER_LEN_V1;
+ }
+ data_size+= no_bytes_in_map(&m_cols);
+ data_size+= (uint) (end - buf);
+
+ if (general_type_code == UPDATE_ROWS_EVENT)
+ data_size+= no_bytes_in_map(&m_cols_ai);
+
+ data_size+= (uint) (m_rows_cur - m_rows_buf);
+ return data_size;
+}
+
+
+/**************************************************************************
+ Annotate_rows_log_event member functions
+**************************************************************************/
+
+Annotate_rows_log_event::
+Annotate_rows_log_event(const uchar *buf,
+ uint event_len,
+ const Format_description_log_event *desc)
+ : Log_event(buf, desc),
+ m_save_thd_query_txt(0),
+ m_save_thd_query_len(0),
+ m_saved_thd_query(false),
+ m_used_query_txt(0)
+{
+ m_query_len= event_len - desc->common_header_len;
+ m_query_txt= (char*) buf + desc->common_header_len;
+}
+
+Annotate_rows_log_event::~Annotate_rows_log_event()
+{
+ DBUG_ENTER("Annotate_rows_log_event::~Annotate_rows_log_event");
+#ifndef MYSQL_CLIENT
+ if (m_saved_thd_query)
+ thd->set_query(m_save_thd_query_txt, m_save_thd_query_len);
+ else if (m_used_query_txt)
+ thd->reset_query();
+#endif
+ DBUG_VOID_RETURN;
+}
+
+int Annotate_rows_log_event::get_data_size()
+{
+ return m_query_len;
+}
+
+Log_event_type Annotate_rows_log_event::get_type_code()
+{
+ return ANNOTATE_ROWS_EVENT;
+}
+
+bool Annotate_rows_log_event::is_valid() const
+{
+ return (m_query_txt != NULL && m_query_len != 0);
+}
+
+
+/**************************************************************************
+ Table_map_log_event member functions and support functions
+**************************************************************************/
+
+/**
+ @page How replication of field metadata works.
+
+ When a table map is created, the master first calls
+ Table_map_log_event::save_field_metadata() which calculates how many
+ values will be in the field metadata. Only those fields that require the
+ extra data are added. The method also loops through all of the fields in
+ the table calling the method Field::save_field_metadata() which returns the
+ values for the field that will be saved in the metadata and replicated to
+ the slave. Once all fields have been processed, the table map is written to
+ the binlog adding the size of the field metadata and the field metadata to
+ the end of the body of the table map.
+
+ When a table map is read on the slave, the field metadata is read from the
+ table map and passed to the table_def class constructor which saves the
+ field metadata from the table map into an array based on the type of the
+ field. Field metadata values not present (those fields that do not use extra
+ data) in the table map are initialized as zero (0). The array size is the
+ same as the columns for the table on the slave.
+
+ Additionally, values saved for field metadata on the master are saved as a
+ string of bytes (uchar) in the binlog. A field may require 1 or more bytes
+ to store the information. In cases where values require multiple bytes
+ (e.g. values > 255), the endian-safe methods are used to properly encode
+ the values on the master and decode them on the slave. When the field
+ metadata values are captured on the slave, they are stored in an array of
+ type uint16. This allows the least number of casts to prevent casting bugs
+ when the field metadata is used in comparisons of field attributes. When
+ the field metadata is used for calculating addresses in pointer math, the
+ type used is uint32.
+*/
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#if defined(HAVE_REPLICATION)
+Table_map_log_event::Table_map_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+
+ : Log_event(buf, description_event),
+#ifndef MYSQL_CLIENT
+ m_table(NULL),
+#endif
+ m_dbnam(NULL), m_dblen(0), m_tblnam(NULL), m_tbllen(0),
+ m_colcnt(0), m_coltype(0),
+ m_memory(NULL), m_table_id(ULONGLONG_MAX), m_flags(0),
+ m_data_size(0), m_field_metadata(0), m_field_metadata_size(0),
+ m_null_bits(0), m_meta_memory(NULL),
+ m_optional_metadata_len(0), m_optional_metadata(NULL)
+{
+ unsigned int bytes_read= 0;
+ DBUG_ENTER("Table_map_log_event::Table_map_log_event(const char*,uint,...)");
+
+ uint8 common_header_len= description_event->common_header_len;
+ uint8 post_header_len= description_event->post_header_len[TABLE_MAP_EVENT-1];
+ DBUG_PRINT("info",("event_len: %u common_header_len: %d post_header_len: %d",
+ event_len, common_header_len, post_header_len));
+
+ /*
+ Don't print debug messages when running valgrind since they can
+ trigger false warnings.
+ */
+#ifndef HAVE_valgrind
+ DBUG_DUMP("event buffer", (uchar*) buf, event_len);
+#endif
+
+ if (event_len < (uint)(common_header_len + post_header_len))
+ DBUG_VOID_RETURN;
+
+ /* Read the post-header */
+ const uchar *post_start= buf + common_header_len;
+
+ post_start+= TM_MAPID_OFFSET;
+ VALIDATE_BYTES_READ(post_start, buf, event_len);
+ if (post_header_len == 6)
+ {
+ /* Master is of an intermediate source tree before 5.1.4. Id is 4 bytes */
+ m_table_id= uint4korr(post_start);
+ post_start+= 4;
+ }
+ else
+ {
+ DBUG_ASSERT(post_header_len == TABLE_MAP_HEADER_LEN);
+ m_table_id= (ulong) uint6korr(post_start);
+ post_start+= TM_FLAGS_OFFSET;
+ }
+
+ DBUG_ASSERT(m_table_id != ~0ULL);
+
+ m_flags= uint2korr(post_start);
+
+ /* Read the variable part of the event */
+ const uchar *const vpart= buf + common_header_len + post_header_len;
+
+ /* Extract the length of the various parts from the buffer */
+ uchar const *const ptr_dblen= (uchar const*)vpart + 0;
+ VALIDATE_BYTES_READ(ptr_dblen, buf, event_len);
+ m_dblen= *(uchar*) ptr_dblen;
+
+ /* Length of database name + counter + terminating null */
+ uchar const *const ptr_tbllen= ptr_dblen + m_dblen + 2;
+ VALIDATE_BYTES_READ(ptr_tbllen, buf, event_len);
+ m_tbllen= *(uchar*) ptr_tbllen;
+
+ /* Length of table name + counter + terminating null */
+ uchar const *const ptr_colcnt= ptr_tbllen + m_tbllen + 2;
+ uchar *ptr_after_colcnt= (uchar*) ptr_colcnt;
+ VALIDATE_BYTES_READ(ptr_after_colcnt, buf, event_len);
+ m_colcnt= net_field_length(&ptr_after_colcnt);
+
+ DBUG_PRINT("info",("m_dblen: %lu off: %ld m_tbllen: %lu off: %ld m_colcnt: %lu off: %ld",
+ (ulong) m_dblen, (long) (ptr_dblen - vpart),
+ (ulong) m_tbllen, (long) (ptr_tbllen - vpart),
+ m_colcnt, (long) (ptr_colcnt - vpart)));
+
+ /* Allocate mem for all fields in one go. If fails, caught in is_valid() */
+ m_memory= (uchar*) my_multi_malloc(PSI_INSTRUMENT_ME, MYF(MY_WME),
+ &m_dbnam, (uint) m_dblen + 1,
+ &m_tblnam, (uint) m_tbllen + 1,
+ &m_coltype, (uint) m_colcnt,
+ NullS);
+
+ if (m_memory)
+ {
+ /* Copy the different parts into their memory */
+ strncpy(const_cast<char*>(m_dbnam), (const char*)ptr_dblen + 1, m_dblen + 1);
+ strncpy(const_cast<char*>(m_tblnam), (const char*)ptr_tbllen + 1, m_tbllen + 1);
+ memcpy(m_coltype, ptr_after_colcnt, m_colcnt);
+
+ ptr_after_colcnt= ptr_after_colcnt + m_colcnt;
+ VALIDATE_BYTES_READ(ptr_after_colcnt, buf, event_len);
+ m_field_metadata_size= net_field_length(&ptr_after_colcnt);
+ if (m_field_metadata_size <= (m_colcnt * 2))
+ {
+ uint num_null_bytes= (m_colcnt + 7) / 8;
+ m_meta_memory= (uchar *)my_multi_malloc(PSI_INSTRUMENT_ME, MYF(MY_WME),
+ &m_null_bits, num_null_bytes,
+ &m_field_metadata, m_field_metadata_size,
+ NULL);
+ memcpy(m_field_metadata, ptr_after_colcnt, m_field_metadata_size);
+ ptr_after_colcnt= (uchar*)ptr_after_colcnt + m_field_metadata_size;
+ memcpy(m_null_bits, ptr_after_colcnt, num_null_bytes);
+ ptr_after_colcnt= (unsigned char*)ptr_after_colcnt + num_null_bytes;
+ }
+ else
+ {
+ m_coltype= NULL;
+ my_free(m_memory);
+ m_memory= NULL;
+ DBUG_VOID_RETURN;
+ }
+
+ bytes_read= (uint) (ptr_after_colcnt - (uchar *)buf);
+
+ /* After null_bits field, there are some new fields for extra metadata. */
+ if (bytes_read < event_len)
+ {
+ m_optional_metadata_len= event_len - bytes_read;
+ m_optional_metadata=
+ static_cast<unsigned char*>(my_malloc(PSI_INSTRUMENT_ME, m_optional_metadata_len, MYF(MY_WME)));
+ memcpy(m_optional_metadata, ptr_after_colcnt, m_optional_metadata_len);
+ }
+ }
+#ifdef MYSQL_SERVER
+ if (!m_table)
+ DBUG_VOID_RETURN;
+ binlog_type_info_array= (Binlog_type_info *)thd->alloc(m_table->s->fields *
+ sizeof(Binlog_type_info));
+ for (uint i= 0; i < m_table->s->fields; i++)
+ binlog_type_info_array[i]= m_table->field[i]->binlog_type_info();
+#endif
+
+ DBUG_VOID_RETURN;
+}
+#endif
+
+Table_map_log_event::~Table_map_log_event()
+{
+ my_free(m_meta_memory);
+ my_free(m_memory);
+ my_free(m_optional_metadata);
+ m_optional_metadata= NULL;
+}
+
+/**
+ Parses SIGNEDNESS field.
+
+ @param[out] vec stores the signedness flags extracted from field.
+ @param[in] field SIGNEDNESS field in table_map_event.
+ @param[in] length length of the field
+ */
+static void parse_signedness(std::vector<bool> &vec,
+ unsigned char *field, unsigned int length)
+{
+ for (unsigned int i= 0; i < length; i++)
+ {
+ for (unsigned char c= 0x80; c != 0; c>>= 1)
+ vec.push_back(field[i] & c);
+ }
+}
+
+/**
+ Parses DEFAULT_CHARSET field.
+
+ @param[out] default_charset stores collation numbers extracted from field.
+ @param[in] field DEFAULT_CHARSET field in table_map_event.
+ @param[in] length length of the field
+ */
+static void parse_default_charset(Table_map_log_event::Optional_metadata_fields::
+ Default_charset &default_charset,
+ unsigned char *field, unsigned int length)
+{
+ unsigned char* p= field;
+
+ default_charset.default_charset= net_field_length(&p);
+ while (p < field + length)
+ {
+ unsigned int col_index= net_field_length(&p);
+ unsigned int col_charset= net_field_length(&p);
+
+ default_charset.charset_pairs.push_back(std::make_pair(col_index,
+ col_charset));
+ }
+}
+
+/**
+ Parses COLUMN_CHARSET field.
+
+ @param[out] vec stores collation numbers extracted from field.
+ @param[in] field COLUMN_CHARSET field in table_map_event.
+ @param[in] length length of the field
+ */
+static void parse_column_charset(std::vector<unsigned int> &vec,
+ unsigned char *field, unsigned int length)
+{
+ unsigned char* p= field;
+
+ while (p < field + length)
+ vec.push_back(net_field_length(&p));
+}
+
+/**
+ Parses COLUMN_NAME field.
+
+ @param[out] vec stores column names extracted from field.
+ @param[in] field COLUMN_NAME field in table_map_event.
+ @param[in] length length of the field
+ */
+static void parse_column_name(std::vector<std::string> &vec,
+ unsigned char *field, unsigned int length)
+{
+ unsigned char* p= field;
+
+ while (p < field + length)
+ {
+ unsigned len= net_field_length(&p);
+ vec.push_back(std::string(reinterpret_cast<char *>(p), len));
+ p+= len;
+ }
+}
+
+/**
+ Parses SET_STR_VALUE/ENUM_STR_VALUE field.
+
+ @param[out] vec stores SET/ENUM column's string values extracted from
+ field. Each SET/ENUM column's string values are stored
+ into a string separate vector. All of them are stored
+ in 'vec'.
+ @param[in] field COLUMN_NAME field in table_map_event.
+ @param[in] length length of the field
+ */
+static void parse_set_str_value(std::vector<Table_map_log_event::
+ Optional_metadata_fields::str_vector> &vec,
+ unsigned char *field, unsigned int length)
+{
+ unsigned char* p= field;
+
+ while (p < field + length)
+ {
+ unsigned int count= net_field_length(&p);
+
+ vec.push_back(std::vector<std::string>());
+ for (unsigned int i= 0; i < count; i++)
+ {
+ unsigned len1= net_field_length(&p);
+ vec.back().push_back(std::string(reinterpret_cast<char *>(p), len1));
+ p+= len1;
+ }
+ }
+}
+
+/**
+ Parses GEOMETRY_TYPE field.
+
+ @param[out] vec stores geometry column's types extracted from field.
+ @param[in] field GEOMETRY_TYPE field in table_map_event.
+ @param[in] length length of the field
+ */
+static void parse_geometry_type(std::vector<unsigned int> &vec,
+ unsigned char *field, unsigned int length)
+{
+ unsigned char* p= field;
+
+ while (p < field + length)
+ vec.push_back(net_field_length(&p));
+}
+
+/**
+ Parses SIMPLE_PRIMARY_KEY field.
+
+ @param[out] vec stores primary key's column information extracted from
+ field. Each column has an index and a prefix which are
+ stored as a unit_pair. prefix is always 0 for
+ SIMPLE_PRIMARY_KEY field.
+ @param[in] field SIMPLE_PRIMARY_KEY field in table_map_event.
+ @param[in] length length of the field
+ */
+static void parse_simple_pk(std::vector<Table_map_log_event::
+ Optional_metadata_fields::uint_pair> &vec,
+ unsigned char *field, unsigned int length)
+{
+ unsigned char* p= field;
+
+ while (p < field + length)
+ vec.push_back(std::make_pair(net_field_length(&p), 0));
+}
+
+/**
+ Parses PRIMARY_KEY_WITH_PREFIX field.
+
+ @param[out] vec stores primary key's column information extracted from
+ field. Each column has an index and a prefix which are
+ stored as a unit_pair.
+ @param[in] field PRIMARY_KEY_WITH_PREFIX field in table_map_event.
+ @param[in] length length of the field
+ */
+
+static void parse_pk_with_prefix(std::vector<Table_map_log_event::
+ Optional_metadata_fields::uint_pair> &vec,
+ unsigned char *field, unsigned int length)
+{
+ unsigned char* p= field;
+
+ while (p < field + length)
+ {
+ unsigned int col_index= net_field_length(&p);
+ unsigned int col_prefix= net_field_length(&p);
+ vec.push_back(std::make_pair(col_index, col_prefix));
+ }
+}
+
+Table_map_log_event::Optional_metadata_fields::
+Optional_metadata_fields(unsigned char* optional_metadata,
+ unsigned int optional_metadata_len)
+{
+ unsigned char* field= optional_metadata;
+
+ if (optional_metadata == NULL)
+ return;
+
+ while (field < optional_metadata + optional_metadata_len)
+ {
+ unsigned int len;
+ Optional_metadata_field_type type=
+ static_cast<Optional_metadata_field_type>(field[0]);
+
+ // Get length and move field to the value.
+ field++;
+ len= net_field_length(&field);
+
+ switch(type)
+ {
+ case SIGNEDNESS:
+ parse_signedness(m_signedness, field, len);
+ break;
+ case DEFAULT_CHARSET:
+ parse_default_charset(m_default_charset, field, len);
+ break;
+ case COLUMN_CHARSET:
+ parse_column_charset(m_column_charset, field, len);
+ break;
+ case COLUMN_NAME:
+ parse_column_name(m_column_name, field, len);
+ break;
+ case SET_STR_VALUE:
+ parse_set_str_value(m_set_str_value, field, len);
+ break;
+ case ENUM_STR_VALUE:
+ parse_set_str_value(m_enum_str_value, field, len);
+ break;
+ case GEOMETRY_TYPE:
+ parse_geometry_type(m_geometry_type, field, len);
+ break;
+ case SIMPLE_PRIMARY_KEY:
+ parse_simple_pk(m_primary_key, field, len);
+ break;
+ case PRIMARY_KEY_WITH_PREFIX:
+ parse_pk_with_prefix(m_primary_key, field, len);
+ break;
+ case ENUM_AND_SET_DEFAULT_CHARSET:
+ parse_default_charset(m_enum_and_set_default_charset, field, len);
+ break;
+ case ENUM_AND_SET_COLUMN_CHARSET:
+ parse_column_charset(m_enum_and_set_column_charset, field, len);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ // next field
+ field+= len;
+ }
+}
+
+
+/**************************************************************************
+ Write_rows_log_event member functions
+**************************************************************************/
+
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#ifdef HAVE_REPLICATION
+Write_rows_log_event::Write_rows_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+: Rows_log_event(buf, event_len, description_event)
+{
+}
+
+Write_rows_compressed_log_event::Write_rows_compressed_log_event(
+ const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+: Write_rows_log_event(buf, event_len, description_event)
+{
+ uncompress_buf();
+}
+#endif
+
+
+/**************************************************************************
+ Delete_rows_log_event member functions
+**************************************************************************/
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#ifdef HAVE_REPLICATION
+Delete_rows_log_event::Delete_rows_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ : Rows_log_event(buf, event_len, description_event)
+{
+}
+
+Delete_rows_compressed_log_event::Delete_rows_compressed_log_event(
+ const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ : Delete_rows_log_event(buf, event_len, description_event)
+{
+ uncompress_buf();
+}
+#endif
+
+/**************************************************************************
+ Update_rows_log_event member functions
+**************************************************************************/
+
+Update_rows_log_event::~Update_rows_log_event()
+{
+ if (m_cols_ai.bitmap)
+ {
+ if (m_cols_ai.bitmap == m_bitbuf_ai) // no my_malloc happened
+ m_cols_ai.bitmap= 0; // so no my_free in my_bitmap_free
+ my_bitmap_free(&m_cols_ai); // To pair with my_bitmap_init().
+ }
+}
+
+
+/*
+ Constructor used by slave to read the event from the binary log.
+ */
+#ifdef HAVE_REPLICATION
+Update_rows_log_event::Update_rows_log_event(const uchar *buf, uint event_len,
+ const
+ Format_description_log_event
+ *description_event)
+ : Rows_log_event(buf, event_len, description_event)
+{
+}
+
+Update_rows_compressed_log_event::Update_rows_compressed_log_event(
+ const uchar *buf, uint event_len,
+ const Format_description_log_event
+ *description_event)
+ : Update_rows_log_event(buf, event_len, description_event)
+{
+ uncompress_buf();
+}
+#endif
+
+Incident_log_event::Incident_log_event(const uchar *buf, uint event_len,
+ const Format_description_log_event *descr_event)
+ : Log_event(buf, descr_event)
+{
+ DBUG_ENTER("Incident_log_event::Incident_log_event");
+ uint8 const common_header_len=
+ descr_event->common_header_len;
+ uint8 const post_header_len=
+ descr_event->post_header_len[INCIDENT_EVENT-1];
+
+ DBUG_PRINT("info",("event_len: %u; common_header_len: %d; post_header_len: %d",
+ event_len, common_header_len, post_header_len));
+
+ m_message.str= NULL;
+ m_message.length= 0;
+ int incident_number= uint2korr(buf + common_header_len);
+ if (incident_number >= INCIDENT_COUNT ||
+ incident_number <= INCIDENT_NONE)
+ {
+ // If the incident is not recognized, this binlog event is
+ // invalid. If we set incident_number to INCIDENT_NONE, the
+ // invalidity will be detected by is_valid().
+ m_incident= INCIDENT_NONE;
+ DBUG_VOID_RETURN;
+ }
+ m_incident= static_cast<Incident>(incident_number);
+ uchar const *ptr= buf + common_header_len + post_header_len;
+ uchar const *const str_end= buf + event_len;
+ uint8 len= 0; // Assignment to keep compiler happy
+ const char *str= NULL; // Assignment to keep compiler happy
+ if (read_str(&ptr, str_end, &str, &len))
+ {
+ /* Mark this event invalid */
+ m_incident= INCIDENT_NONE;
+ DBUG_VOID_RETURN;
+ }
+ if (!(m_message.str= (char*) my_malloc(key_memory_log_event, len+1, MYF(MY_WME))))
+ {
+ /* Mark this event invalid */
+ m_incident= INCIDENT_NONE;
+ DBUG_VOID_RETURN;
+ }
+ strmake(m_message.str, str, len);
+ m_message.length= len;
+ DBUG_PRINT("info", ("m_incident: %d", m_incident));
+ DBUG_VOID_RETURN;
+}
+
+
+Incident_log_event::~Incident_log_event()
+{
+ if (m_message.str)
+ my_free(m_message.str);
+}
+
+
+const char *
+Incident_log_event::description() const
+{
+ static const char *const description[]= {
+ "NOTHING", // Not used
+ "LOST_EVENTS"
+ };
+
+ DBUG_PRINT("info", ("m_incident: %d", m_incident));
+ return description[m_incident];
+}
+
+
+Ignorable_log_event::Ignorable_log_event(const uchar *buf,
+ const Format_description_log_event
+ *descr_event,
+ const char *event_name)
+ :Log_event(buf, descr_event), number((int) (uchar) buf[EVENT_TYPE_OFFSET]),
+ description(event_name)
+{
+ DBUG_ENTER("Ignorable_log_event::Ignorable_log_event");
+ DBUG_VOID_RETURN;
+}
+
+Ignorable_log_event::~Ignorable_log_event() = default;
+
+bool copy_event_cache_to_file_and_reinit(IO_CACHE *cache, FILE *file)
+{
+ return (my_b_copy_all_to_file(cache, file) ||
+ reinit_io_cache(cache, WRITE_CACHE, 0, FALSE, TRUE));
+}
+
+#if defined(MYSQL_SERVER) && defined(HAVE_REPLICATION)
+int Log_event::apply_event(rpl_group_info* rgi)
+{
+ int res;
+ THD_STAGE_INFO(thd, stage_apply_event);
+ rgi->current_event= this;
+ res= do_apply_event(rgi);
+ rgi->current_event= NULL;
+ THD_STAGE_INFO(thd, stage_after_apply_event);
+ return res;
+}
+#endif