content: package com.taobao.tddl.dbsync.binlog.event;
  
  import java.io.IOException;
  import java.math.BigInteger;
  import java.nio.charset.Charset;
  
  import com.taobao.tddl.dbsync.binlog.CharsetConversion;
  import com.taobao.tddl.dbsync.binlog.LogBuffer;
  import com.taobao.tddl.dbsync.binlog.LogEvent;
  
  /**
   * A Query_log_event is created for each query that modifies the database,
   * unless the query is logged row-based. The Post-Header has five components:
   * <table>
   * <caption>Post-Header for Query_log_event</caption>
   * <tr>
   * <th>Name</th>
   * <th>Format</th>
   * <th>Description</th>
   * </tr>
   * <tr>
   * <td>slave_proxy_id</td>
   * <td>4 byte unsigned integer</td>
   * <td>An integer identifying the client thread that issued the query. The id is
   * unique per server. (Note, however, that two threads on different servers may
   * have the same slave_proxy_id.) This is used when a client thread creates a
   * temporary table local to the client. The slave_proxy_id is used to
   * distinguish temporary tables that belong to different clients.</td>
   * </tr>
   * <tr>
   * <td>exec_time</td>
   * <td>4 byte unsigned integer</td>
   * <td>The time from when the query started to when it was logged in the binlog,
   * in seconds.</td>
   * </tr>
   * <tr>
   * <td>db_len</td>
   * <td>1 byte integer</td>
   * <td>The length of the name of the currently selected database.</td>
   * </tr>
   * <tr>
   * <td>error_code</td>
   * <td>2 byte unsigned integer</td>
   * <td>Error code generated by the master. If the master fails, the slave will
   * fail with the same error code, except for the error codes ER_DB_CREATE_EXISTS
   * == 1007 and ER_DB_DROP_EXISTS == 1008.</td>
   * </tr>
   * <tr>
   * <td>status_vars_len</td>
   * <td>2 byte unsigned integer</td>
   * <td>The length of the status_vars block of the Body, in bytes. See
   * query_log_event_status_vars "below".</td>
   * </tr>
   * </table>
   * The Body has the following components:
   * <table>
   * <caption>Body for Query_log_event</caption>
   * <tr>
   * <th>Name</th>
   * <th>Format</th>
   * <th>Description</th>
   * </tr>
   * <tr>
   * <td>query_log_event_status_vars status_vars</td>
   * <td>status_vars_len bytes</td>
   * <td>Zero or more status variables. Each status variable consists of one byte
   * identifying the variable stored, followed by the value of the variable. The
   * possible variables are listed separately in the table
   * Table_query_log_event_status_vars "below". MySQL always writes events in the
   * order defined below; however, it is capable of reading them in any order.</td>
   * </tr>
   * <tr>
   * <td>db</td>
   * <td>db_len+1</td>
   * <td>The currently selected database, as a null-terminated string. (The
   * trailing zero is redundant since the length is already known; it is db_len
   * from Post-Header.)</td>
   * </tr>
   * <tr>
   * <td>query</td>
   * <td>variable length string without trailing zero, extending to the end of the
   * event (determined by the length field of the Common-Header)</td>
   * <td>The SQL query.</td>
   * </tr>
   * </table>
   * The following table lists the status variables that may appear in the
   * status_vars field. Table_query_log_event_status_vars
   * <table>
   * <caption>Status variables for Query_log_event</caption>
   * <tr>
   * <th>Status variable</th>
   * <th>1 byte identifier</th>
   * <th>Format</th>
   * <th>Description</th>
   * </tr>
   * <tr>
   * <td>flags2</td>
   * <td>Q_FLAGS2_CODE == 0</td>
   * <td>4 byte bitfield</td>
   * <td>The flags in thd->options, binary AND-ed with OPTIONS_WRITTEN_TO_BIN_LOG.
   * The thd->options bitfield contains options for "SELECT". OPTIONS_WRITTEN
   * identifies those options that need to be written to the binlog (not all do).
   * Specifically, OPTIONS_WRITTEN_TO_BIN_LOG equals (OPTION_AUTO_IS_NULL |
   * OPTION_NO_FOREIGN_KEY_CHECKS | OPTION_RELAXED_UNIQUE_CHECKS |
   * OPTION_NOT_AUTOCOMMIT), or 0x0c084000 in hex. These flags correspond to the
   * SQL variables SQL_AUTO_IS_NULL, FOREIGN_KEY_CHECKS, UNIQUE_CHECKS, and
   * AUTOCOMMIT, documented in the "SET Syntax" section of the MySQL Manual. This
   * field is always written to the binlog in version >= 5.0, and never written in
   * version < 5.0.</td>
   * </tr>
   * <tr>
   * <td>sql_mode</td>
   * <td>Q_SQL_MODE_CODE == 1</td>
   * <td>8 byte bitfield</td>
   * <td>The sql_mode variable. See the section "SQL Modes" in the MySQL manual,
   * and see mysql_priv.h for a list of the possible flags. Currently
   * (2007-10-04), the following flags are available:
   * 
   * <pre>
   *     MODE_REAL_AS_FLOAT==0x1
   *     MODE_PIPES_AS_CONCAT==0x2
   *     MODE_ANSI_QUOTES==0x4
   *     MODE_IGNORE_SPACE==0x8
   *     MODE_NOT_USED==0x10
   *     MODE_ONLY_FULL_GROUP_BY==0x20
   *     MODE_NO_UNSIGNED_SUBTRACTION==0x40
   *     MODE_NO_DIR_IN_CREATE==0x80
   *     MODE_POSTGRESQL==0x100
   *     MODE_ORACLE==0x200
   *     MODE_MSSQL==0x400
   *     MODE_DB2==0x800
   *     MODE_MAXDB==0x1000
   *     MODE_NO_KEY_OPTIONS==0x2000
   *     MODE_NO_TABLE_OPTIONS==0x4000
   *     MODE_NO_FIELD_OPTIONS==0x8000
   *     MODE_MYSQL323==0x10000
   *     MODE_MYSQL323==0x20000
   *     MODE_MYSQL40==0x40000
   *     MODE_ANSI==0x80000
   *     MODE_NO_AUTO_VALUE_ON_ZERO==0x100000
   *     MODE_NO_BACKSLASH_ESCAPES==0x200000
   *     MODE_STRICT_TRANS_TABLES==0x400000
   *     MODE_STRICT_ALL_TABLES==0x800000
   *     MODE_NO_ZERO_IN_DATE==0x1000000
   *     MODE_NO_ZERO_DATE==0x2000000
   *     MODE_INVALID_DATES==0x4000000
   *     MODE_ERROR_FOR_DIVISION_BY_ZERO==0x8000000
   *     MODE_TRADITIONAL==0x10000000
   *     MODE_NO_AUTO_CREATE_USER==0x20000000
   *     MODE_HIGH_NOT_PRECEDENCE==0x40000000
   *     MODE_PAD_CHAR_TO_FULL_LENGTH==0x80000000
   * </pre>
   * 
   * All these flags are replicated from the server. However, all flags except
   * MODE_NO_DIR_IN_CREATE are honored by the slave; the slave always preserves
   * its old value of MODE_NO_DIR_IN_CREATE. For a rationale, see comment in
   * Query_log_event::do_apply_event in log_event.cc. This field is always written
   * to the binlog.</td>
   * </tr>
   * <tr>
   * <td>catalog</td>
   * <td>Q_CATALOG_NZ_CODE == 6</td>
   * <td>Variable-length string: the length in bytes (1 byte) followed by the
   * characters (at most 255 bytes)</td>
   * <td>Stores the client's current catalog. Every database belongs to a catalog,
   * the same way that every table belongs to a database. Currently, there is only
   * one catalog, "std". This field is written if the length of the catalog is >
   * 0; otherwise it is not written.</td>
   * </tr>
   * <tr>
   * <td>auto_increment</td>
   * <td>Q_AUTO_INCREMENT == 3</td>
   * <td>two 2 byte unsigned integers, totally 2+2=4 bytes</td>
   * <td>The two variables auto_increment_increment and auto_increment_offset, in
   * that order. For more information, see "System variables" in the MySQL manual.
   * This field is written if auto_increment > 1. Otherwise, it is not written.</td>
   * </tr>
   * <tr>
   * <td>charset</td>
   * <td>Q_CHARSET_CODE == 4</td>
   * <td>three 2 byte unsigned integers, totally 2+2+2=6 bytes</td>
   * <td>The three variables character_set_client, collation_connection, and
   * collation_server, in that order. character_set_client is a code identifying
   * the character set and collation used by the client to encode the query.
   * collation_connection identifies the character set and collation that the
   * master converts the query to when it receives it; this is useful when
   * comparing literal strings. collation_server is the default character set and
   * collation used when a new database is created. See also
   * "Connection Character Sets and Collations" in the MySQL 5.1 manual. All three
   * variables are codes identifying a (character set, collation) pair. To see
   * which codes map to which pairs, run the query "SELECT id, character_set_name,
   * collation_name FROM COLLATIONS". Cf. Q_CHARSET_DATABASE_CODE below. This
   * field is always written.</td>
   * </tr>
   * <tr>
   * <td>time_zone</td>
   * <td>Q_TIME_ZONE_CODE == 5</td>
   * <td>Variable-length string: the length in bytes (1 byte) followed by the
   * characters (at most 255 bytes).
   * <td>The time_zone of the master. See also "System Variables" and
   * "MySQL Server Time Zone Support" in the MySQL manual. This field is written
   * if the length of the time zone string is > 0; otherwise, it is not written.</td>
   * </tr>
   * <tr>
   * <td>lc_time_names_number</td>
   * <td>Q_LC_TIME_NAMES_CODE == 7</td>
   * <td>2 byte integer</td>
   * <td>A code identifying a table of month and day names. The mapping from codes
   * to languages is defined in sql_locale.cc. This field is written if it is not
   * 0, i.e., if the locale is not en_US.</td>
   * </tr>
   * <tr>
   * <td>charset_database_number</td>
   * <td>Q_CHARSET_DATABASE_CODE == 8</td>
   * <td>2 byte integer</td>
   * <td>The value of the collation_database system variable (in the source code
   * stored in thd->variables.collation_database), which holds the code for a
   * (character set, collation) pair as described above (see Q_CHARSET_CODE).
   * collation_database was used in old versions (???WHEN). Its value was loaded
   * when issuing a "use db" query and could be changed by issuing a
   * "SET collation_database=xxx" query. It used to affect the "LOAD DATA INFILE"
   * and "CREATE TABLE" commands. In newer versions, "CREATE TABLE" has been
   * changed to take the character set from the database of the created table,
   * rather than the character set of the current database. This makes a
   * difference when creating a table in another database than the current one.
   * "LOAD DATA INFILE" has not yet changed to do this, but there are plans to
   * eventually do it, and to make collation_database read-only. This field is
   * written if it is not 0.</td>
   * </tr>
   * <tr>
   * <td>table_map_for_update</td>
   * <td>Q_TABLE_MAP_FOR_UPDATE_CODE == 9</td>
   * <td>8 byte integer</td>
   * <td>The value of the table map that is to be updated by the multi-table
   * update query statement. Every bit of this variable represents a table, and is
   * set to 1 if the corresponding table is to be updated by this statement. The
   * value of this variable is set when executing a multi-table update statement
   * and used by slave to apply filter rules without opening all the tables on
   * slave. This is required because some tables may not exist on slave because of
   * the filter rules.</td>
   * </tr>
   * </table>
   * Query_log_event_notes_on_previous_versions Notes on Previous Versions Status
   * vars were introduced in version 5.0. To read earlier versions correctly,
   * check the length of the Post-Header. The status variable Q_CATALOG_CODE == 2
   * existed in MySQL 5.0.x, where 0<=x<=3. It was identical to Q_CATALOG_CODE,
   * except that the string had a trailing '\0'. The '\0' was removed in 5.0.4
   * since it was redundant (the string length is stored before the string). The
   * Q_CATALOG_CODE will never be written by a new master, but can still be
   * understood by a new slave. See Q_CHARSET_DATABASE_CODE in the table above.
   * When adding new status vars, please don't forget to update the
   * MAX_SIZE_LOG_EVENT_STATUS, and update function code_name
   * 
   * @see mysql-5.1.6/sql/logevent.cc - Query_log_event
   * @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a>
   * @version 1.0
   */
  public class QueryLogEvent extends LogEvent {
  
      /**
       * The maximum number of updated databases that a status of Query-log-event
       * can carry. It can redefined within a range [1..
       * OVER_MAX_DBS_IN_EVENT_MTS].
       */
      public static final int MAX_DBS_IN_EVENT_MTS      = 16;
  
      /**
       * When the actual number of databases exceeds MAX_DBS_IN_EVENT_MTS the
       * value of OVER_MAX_DBS_IN_EVENT_MTS is is put into the mts_accessed_dbs
       * status.
       */
      public static final int OVER_MAX_DBS_IN_EVENT_MTS = 254;
  
      public static final int SYSTEM_CHARSET_MBMAXLEN   = 3;
      public static final int NAME_CHAR_LEN             = 64;
      /* Field/table name length */
      public static final int NAME_LEN                  = (NAME_CHAR_LEN * SYSTEM_CHARSET_MBMAXLEN);
  
      /**
       * Max number of possible extra bytes in a replication event compared to a
       * packet (i.e. a query) sent from client to master; First, an auxiliary
       * log_event status vars estimation:
       */
      public static final int MAX_SIZE_LOG_EVENT_STATUS = (1 + 4 /* type, flags2 */
                                                           + 1 + 8 /*
                                                                    * type,
                                                                    * sql_mode
                                                                    */
                                                           + 1 + 1 + 255/*
                                                                         * type,
                                                                         * length
                                                                         * ,
                                                                         * catalog
                                                                         */
                                                           + 1 + 4 /*
                                                                    * type,
                                                                    * auto_increment
                                                                    */
                                                           + 1 + 6 /*
                                                                    * type,
                                                                    * charset
                                                                    */
                                                           + 1 + 1 + 255 /*
                                                                          * type,
                                                                          * length
                                                                          * ,
                                                                          * time_zone
                                                                          */
                                                           + 1 + 2 /*
                                                                    * type,
                                                                    * lc_time_names_number
                                                                    */
                                                           + 1 + 2 /*
                                                                    * type,
                                                                    * charset_database_number
                                                                    */
                                                           + 1 + 8 /*
                                                                    * type,
                                                                    * table_map_for_update
                                                                    */
                                                           + 1 + 4 /*
                                                                    * type,
                                                                    * master_data_written
                                                                    */
                                                           /*
                                                            * type, db_1, db_2,
                                                            * ...
                                                            */
                                                           /* type, microseconds */
                                                           /*
                                                            * MariaDb type,
                                                            * sec_part of NOW()
                                                            */
                                                           + 1 + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + 3 /*
                                                                                                              * type
                                                                                                              * ,
                                                                                                              * microseconds
                                                                                                              */+ 1 + 32
                                                           * 3 + 1 + 60/*
                                                                        * type ,
                                                                        * user_len
                                                                        * , user ,
                                                                        * host_len
                                                                        * , host
                                                                        */)
                                                          + 1 + 1 /*
                                                                   * type,
                                                                   * explicit_def
                                                                   * ..ts
                                                                   */+ 1 + 8 /*
                                                                              * type,
                                                                              * xid
                                                                              * of
                                                                              * DDL
                                                                              */+ 1 + 2 /*
                                                                                         * type
                                                                                         * ,
                                                                                         * default_collation_for_utf8mb4_number
                                                                                         */+ 1 /* sql_require_primary_key */;
      /**
       * Fixed data part:
       * <ul>
       * <li>4 bytes. The ID of the thread that issued this statement. Needed for
       * temporary tables. This is also useful for a DBA for knowing who did what
       * on the master.</li>
       * <li>4 bytes. The time in seconds that the statement took to execute. Only
       * useful for inspection by the DBA.</li>
       * <li>1 byte. The length of the name of the database which was the default
       * database when the statement was executed. This name appears later, in the
       * variable data part. It is necessary for statements such as INSERT INTO t
       * VALUES(1) that don't specify the database and rely on the default
       * database previously selected by USE.</li>
       * <li>2 bytes. The error code resulting from execution of the statement on
       * the master. Error codes are defined in include/mysqld_error.h. 0 means no
       * error. How come statements with a non-zero error code can exist in the
       * binary log? This is mainly due to the use of non-transactional tables
       * within transactions. For example, if an INSERT ... SELECT fails after
       * inserting 1000 rows into a MyISAM table (for example, with a
       * duplicate-key violation), we have to write this statement to the binary
       * log, because it truly modified the MyISAM table. For transactional
       * tables, there should be no event with a non-zero error code (though it
       * can happen, for example if the connection was interrupted (Control-C)).
       * The slave checks the error code: After executing the statement itself, it
       * compares the error code it got with the error code in the event, and if
       * they are different it stops replicating (unless --slave-skip-errors was
       * used to ignore the error).</li>
       * <li>2 bytes (not present in v1, v3). The length of the status variable
       * block.</li>
       * </ul>
       * Variable part:
       * <ul>
       * <li>Zero or more status variables (not present in v1, v3). Each status
       * variable consists of one byte code identifying the variable stored,
       * followed by the value of the variable. The format of the value is
       * variable-specific, as described later.</li>
       * <li>The default database name (null-terminated).</li>
       * <li>The SQL statement. The slave knows the size of the other fields in
       * the variable part (the sizes are given in the fixed data part), so by
       * subtraction it can know the size of the statement.</li>
       * </ul>
       * Source : http://forge.mysql.com/wiki/MySQL_Internals_Binary_Log
       */
      private String          user;
      private String          host;
  
      /* using byte for query string */
      protected String        query;
      protected String        catalog;
      protected final String  dbname;
  
      /** The number of seconds the query took to run on the master. */
      // The time in seconds that the statement took to execute. Only useful for
      // inspection by the DBA
      private final long      execTime;
      private final int       errorCode;
      private final long      sessionId;                                                                                     /* thread_id */
  
      /**
       * 'flags2' is a second set of flags (on top of those in Log_event), for
       * session variables. These are thd->options which is & against a mask
       * (OPTIONS_WRITTEN_TO_BIN_LOG).
       */
      private long            flags2;
  
      /** In connections sql_mode is 32 bits now but will be 64 bits soon */
      private long            sql_mode;
  
      private long            autoIncrementIncrement    = -1;
      private long            autoIncrementOffset       = -1;
  
      private int             clientCharset             = -1;
      private int             clientCollation           = -1;
      private int             serverCollation           = -1;
      private int             tvSec                     = -1;
      private BigInteger      ddlXid                    = BigInteger.valueOf(-1L);
      private Charset         charset;
      private String          timezone;
      private boolean         compatiablePercona        = false;
  
      public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent)
                                                                                                           throws IOException{
          this(header, buffer, descriptionEvent, false);
      }
  
      public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent,
                           boolean compatiablePercona) throws IOException{
          this(header, buffer, descriptionEvent, compatiablePercona, false);
      }
  
      public QueryLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent,
                           boolean compatiablePercona,
                           boolean compress) throws IOException{
          super(header);
          this.compatiablePercona = compatiablePercona;
          final int commonHeaderLen = descriptionEvent.commonHeaderLen;
          final int postHeaderLen = descriptionEvent.postHeaderLen[header.type - 1];
          /*
           * 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 (buffer.limit() < (commonHeaderLen + postHeaderLen)) {
              throw new IOException("Query event length is too short.");
          }
          int dataLen = buffer.limit() - (commonHeaderLen + postHeaderLen);
          buffer.position(commonHeaderLen + Q_THREAD_ID_OFFSET);
  
          sessionId = buffer.getUint32(); // Q_THREAD_ID_OFFSET
          execTime = buffer.getUint32(); // Q_EXEC_TIME_OFFSET
  
          // TODO: add a check of all *_len vars
          final int dbLen = buffer.getUint8(); // Q_DB_LEN_OFFSET
          errorCode = buffer.getUint16(); // Q_ERR_CODE_OFFSET
  
          /*
           * 5.0 format starts here. Depending on the format, we may or not have
           * affected/warnings etc The remaining post-header to be parsed has
           * length:
           */
          int statusVarsLen = 0;
          if (postHeaderLen > QUERY_HEADER_MINIMAL_LEN) {
              statusVarsLen = buffer.getUint16(); // 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 (statusVarsLen > Math.min(dataLen, MAX_SIZE_LOG_EVENT_STATUS)) {
                  throw new IOException("status_vars_len (" + statusVarsLen + ") > data_len (" + dataLen + ")");
              }
              dataLen -= statusVarsLen;
          }
          /*
           * 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 */
          final int start = commonHeaderLen + postHeaderLen;
          final int limit = buffer.limit(); /* for restore */
          final int end = start + statusVarsLen;
          buffer.position(start).limit(end);
          unpackVariables(buffer, end);
          buffer.position(end);
          buffer.limit(limit);
          /* A 2nd variable part; this is common to all versions */
          dbname = buffer.getFixName(dbLen + 1);
          int queryLen = dataLen - dbLen - 1;
          if (compress) {
              // mariadb compress log event
              // see https://github.com/alibaba/canal/issues/4388
              buffer = buffer.uncompressBuf();
              queryLen = buffer.limit();
          }
          if (clientCharset >= 0) {
              charset = CharsetConversion.getNioCharset(clientCharset);
  
              if (charset != null) {
                  query = buffer.getFixString(queryLen, charset);
              } else {
                  logger.warn("unsupported character set in query log: " + "\n    ID = " + clientCharset + ", Charset = "
                              + CharsetConversion.getCharset(clientCharset) + ", Collation = "
                              + CharsetConversion.getCollation(clientCharset));
  
                  query = buffer.getFixString(queryLen);
              }
          } else {
              query = buffer.getFixString(queryLen);
          }
      }
  
      /* query event post-header */
      public static final int Q_THREAD_ID_OFFSET                = 0;
      public static final int Q_EXEC_TIME_OFFSET                = 4;
      public static final int Q_DB_LEN_OFFSET                   = 8;
      public static final int Q_ERR_CODE_OFFSET                 = 9;
      public static final int Q_STATUS_VARS_LEN_OFFSET          = 11;
      public static final int Q_DATA_OFFSET                     = QUERY_HEADER_LEN;
  
      /* these are codes, not offsets; not more than 256 values (1 byte). */
      public static final int Q_FLAGS2_CODE                     = 0;
      public static final int Q_SQL_MODE_CODE                   = 1;
  
      /**
       * Q_CATALOG_CODE is catalog with end zero stored; it is used only by MySQL
       * 5.0.x where 0<=x<=3. We have to keep it to be able to replicate these old
       * masters.
       */
      public static final int Q_CATALOG_CODE                    = 2;
      public static final int Q_AUTO_INCREMENT                  = 3;
      public static final int Q_CHARSET_CODE                    = 4;
      public static final int Q_TIME_ZONE_CODE                  = 5;
  
      /**
       * Q_CATALOG_NZ_CODE is catalog withOUT end zero stored; it is used by MySQL
       * 5.0.x where x>=4. Saves one byte in every Query_log_event in binlog,
       * compared to Q_CATALOG_CODE. The reason we didn't simply re-use
       * Q_CATALOG_CODE is that then a 5.0.3 slave of this 5.0.x (x>=4) master
       * would crash (segfault etc) because it would expect a 0 when there is
       * none.
       */
      public static final int Q_CATALOG_NZ_CODE                 = 6;
  
      public static final int Q_LC_TIME_NAMES_CODE              = 7;
  
      public static final int Q_CHARSET_DATABASE_CODE           = 8;
  
      public static final int Q_TABLE_MAP_FOR_UPDATE_CODE       = 9;
  
      public static final int Q_MASTER_DATA_WRITTEN_CODE        = 10;
  
      public static final int Q_INVOKER                         = 11;
  
      /**
       * Q_UPDATED_DB_NAMES status variable collects of the updated databases
       * total number and their names to be propagated to the slave in order to
       * facilitate the parallel applying of the Query events.
       */
      public static final int Q_UPDATED_DB_NAMES                = 12;
  
      public static final int Q_MICROSECONDS                    = 13;
      /**
       * A old (unused now) code for Query_log_event status similar to
       * G_COMMIT_TS.
       */
      public static final int Q_COMMIT_TS                       = 14;
      /**
       * A code for Query_log_event status, similar to G_COMMIT_TS2.
       */
      public static final int Q_COMMIT_TS2                      = 15;
      /**
       * The master connection @@session.explicit_defaults_for_timestamp which is
       * recorded for queries, CREATE and ALTER table that is defined with a
       * TIMESTAMP column, that are dependent on that feature. For pre-WL6292
       * master's the associated with this code value is zero.
       */
      public static final int Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP = 16;
  
      /**
       * The variable carries xid info of 2pc-aware (recoverable) DDL queries.
       */
      public static final int Q_DDL_LOGGED_WITH_XID             = 17;
      /**
       * This variable stores the default collation for the utf8mb4 character set.
       * Used to support cross-version replication.
       */
      public static final int Q_DEFAULT_COLLATION_FOR_UTF8MB4   = 18;
  
      /**
       * Replicate sql_require_primary_key.
       */
      public static final int Q_SQL_REQUIRE_PRIMARY_KEY         = 19;
  
      /**
       * Replicate default_table_encryption.
       */
      public static final int Q_DEFAULT_TABLE_ENCRYPTION        = 20;
  
      /**
       * Replicate PolarDB-X
       * 
       * @since PolarDB-X 8.0.32
       */
      public static final int Q_OPT_FLASHBACK_AREA              = 21;
  
      /**
       * Replicate PolarDB-X
       * 
       * @since PolarDB-X 8.0.32
       */
      public static final int Q_OPT_INDEX_FORMAT_GPP_ENABLED    = 22;
  
      /**
       * Replicate ddl_skip_rewrite.
       *
       * @since percona 8.0.31
       */
      public static final int Q_DDL_SKIP_REWRITE                = 21;
  
      /**
       * Replicate Q_WSREP_SKIP_READONLY_CHECKS.
       *
       * @since percona 8.0.31
       */
      public static final int Q_WSREP_SKIP_READONLY_CHECKS      = 128;
  
      /**
       * FROM MariaDB 5.5.34
       */
      public static final int Q_HRNOW                           = 128;
  
      /**
       * Support MariaDB 10.10.1
       */
      public static final int Q_XID                             = 129;
  
      public static final int Q_GTID_FLAGS3                     = 130;
  
      public static final int Q_CHARACTER_SET_COLLATIONS        = 131;
  
      /**
       * support PolarDB-X used for storing snapshot tso or commit tso snapshot tso is
       * stored in XA End Event with Query_log commit tso is stored in XA Commit Event
       * with Query_log
       */
      public static final int Q_LIZARD_COMMIT_GCN               = 200;
  
      public static final int Q_LIZARD_PREPARE_GCN              = 201;
  
      public static final int Q_OPT_INDEX_FORMAT_PANDA_ENABLED  = 255;
  
      private final void unpackVariables(LogBuffer buffer, final int end) throws IOException {
          int code = -1;
          try {
              while (buffer.position() < end) {
                  switch (code = buffer.getUint8()) {
                      case Q_FLAGS2_CODE:
                          flags2 = buffer.getUint32();
                          break;
                      case Q_SQL_MODE_CODE:
                          sql_mode = buffer.getLong64(); // QQ: Fix when sql_mode
                          // is ulonglong
                          break;
                      case Q_CATALOG_NZ_CODE:
                          catalog = buffer.getName();
                          break;
                      case Q_AUTO_INCREMENT:
                          autoIncrementIncrement = buffer.getUint16();
                          autoIncrementOffset = buffer.getUint16();
                          break;
                      case Q_CHARSET_CODE:
                          // Charset: 6 byte character set flag.
                          // 1-2 = character set client
                          // 3-4 = collation client
                          // 5-6 = collation server
                          clientCharset = buffer.getUint16();
                          clientCollation = buffer.getUint16();
                          serverCollation = buffer.getUint16();
                          break;
                      case Q_TIME_ZONE_CODE:
                          timezone = buffer.getName();
                          break;
                      case Q_CATALOG_CODE: /* for 5.0.x where 0<=x<=3 masters */
                          final int len = buffer.getUint8();
                          catalog = buffer.getFixString(len + 1);
                          break;
                      case Q_LC_TIME_NAMES_CODE:
                          // lc_time_names_number = buffer.getUint16();
                          buffer.forward(2);
                          break;
                      case Q_CHARSET_DATABASE_CODE:
                          // charset_database_number = buffer.getUint16();
                          buffer.forward(2);
                          break;
                      case Q_TABLE_MAP_FOR_UPDATE_CODE:
                          // table_map_for_update = buffer.getUlong64();
                          buffer.forward(8);
                          break;
                      case Q_MASTER_DATA_WRITTEN_CODE:
                          // data_written = master_data_written =
                          // buffer.getUint32();
                          buffer.forward(4);
                          break;
                      case Q_INVOKER:
                          user = buffer.getName();
                          host = buffer.getName();
                          break;
                      case Q_MICROSECONDS:
                          // when.tv_usec= uint3korr(pos);
                          tvSec = buffer.getInt24();
                          break;
                      case Q_UPDATED_DB_NAMES:
                          int mtsAccessedDbs = buffer.getUint8();
                          /**
                           * Notice, the following check is positive also in case
                           * of the master's MAX_DBS_IN_EVENT_MTS > the slave's
                           * one and the event contains e.g the master's
                           * MAX_DBS_IN_EVENT_MTS db:s.
                           */
                          if (mtsAccessedDbs > MAX_DBS_IN_EVENT_MTS) {
                              mtsAccessedDbs = OVER_MAX_DBS_IN_EVENT_MTS;
                              break;
                          }
                          String mtsAccessedDbNames[] = new String[mtsAccessedDbs];
                          for (int i = 0; i < mtsAccessedDbs && buffer.position() < end; i++) {
                              int length = end - buffer.position();
                              mtsAccessedDbNames[i] = buffer.getFixName(length < NAME_LEN ? length : NAME_LEN);
                          }
                          break;
                      case Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP:
                          // thd->variables.explicit_defaults_for_timestamp
                          buffer.forward(1);
                          break;
                      case Q_DDL_LOGGED_WITH_XID:
                          ddlXid = buffer.getUlong64();
                          break;
                      case Q_DEFAULT_COLLATION_FOR_UTF8MB4:
                          // int2store(start,
                          // default_collation_for_utf8mb4_number);
                          buffer.forward(2);
                          break;
                      case Q_SQL_REQUIRE_PRIMARY_KEY:
                          // *start++ = thd->variables.sql_require_primary_key;
                          buffer.forward(1);
                          break;
                      case Q_DEFAULT_TABLE_ENCRYPTION:
                          // *start++ = thd->variables.default_table_encryption;
                          buffer.forward(1);
                          break;
                      case Q_OPT_FLASHBACK_AREA:
                          if (compatiablePercona) {
                              // percona
                              // *start++ = thd->variables.binlog_ddl_skip_rewrite;
                              buffer.forward(1);
                          } else {
                              // PolarDB-X
                              // *start++ = thd->variables.opt_flashback_area;
                              buffer.forward(1);
                          }
                          break;
                      case Q_OPT_INDEX_FORMAT_GPP_ENABLED :
                          // *start++ = thd->variables.opt_index_format_gpp_enabled;
                          buffer.forward(1);
                          break;
                      case Q_HRNOW:
                          // https://github.com/alibaba/canal/issues/4940
                          // percona 和 mariadb各自扩展mysql binlog的格式后有冲突
                          // 需要精确识别一下数据库类型做兼容处理
                          if (compatiablePercona) {
                              // percona 8.0.31
                              // Q_WSREP_SKIP_READONLY_CHECKS *start++ = 1;
                              buffer.forward(1);
                          } else {
                              // int when_sec_part = buffer.getUint24();
                              buffer.forward(3);
                          }
                          break;
                      case Q_XID:
                          // xid= uint8korr(pos);
                          buffer.forward(8);
                          break;
                      case Q_GTID_FLAGS3:
                          // gtid_flags_extra= *pos++;
                          // if (gtid_flags_extra & (Gtid_log_event::FL_COMMIT_ALTER_E1 |
                          // Gtid_log_event::FL_ROLLBACK_ALTER_E1)) {
                          // sa_seq_no = uint8korr(pos);
                          // pos+= 8;
                          // }
                          int gtid_flags_extra = buffer.getUint8();
                          final int FL_COMMIT_ALTER_E1= 4;
                          final int FL_ROLLBACK_ALTER_E1= 8;
                          if ((gtid_flags_extra & (FL_COMMIT_ALTER_E1 | FL_ROLLBACK_ALTER_E1))> 0) {
                              buffer.forward(8);
                          }
                          break;
                      case Q_CHARACTER_SET_COLLATIONS :
                          // mariadb
                          int count = buffer.getUint8();
                          // character_set_collations= Lex_cstring((const char *) pos0 , (const char *) pos);
                          buffer.forward(count * 4);
                          break;
                      case Q_LIZARD_COMMIT_GCN:
                          // commitGCN = buffer.getLong64();
                          buffer.forward(8);
                          break;
                      case Q_LIZARD_PREPARE_GCN:
                          // prepareGCN = buffer.getLong64();
                          buffer.forward(8);
                          break;
                      case Q_OPT_INDEX_FORMAT_PANDA_ENABLED:
                          // *start++ = thd->variables.opt_index_format_panda_enabled;
                          buffer.forward(1);
                          break;
                      default:
                          /*
                           * That's why you must write status vars in growing
                           * order of code
                           */
                          logger.error("Query_log_event has unknown status vars (first has code: " + code
                                       + "), skipping the rest of them");
                          return; // Break loop
                  }
              }
          } catch (RuntimeException e) {
              throw new IOException("Read " + findCodeName(code) + " error: " + e.getMessage(), e);
          }
      }
  
      private static final String findCodeName(final int code) {
          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_INVOKER:
                  return "Q_INVOKER";
              case Q_MICROSECONDS:
                  return "Q_MICROSECONDS";
              case Q_UPDATED_DB_NAMES:
                  return "Q_UPDATED_DB_NAMES";
              case Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP:
                  return "Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP";
              case Q_DDL_LOGGED_WITH_XID:
                  return "Q_DDL_LOGGED_WITH_XID";
              case Q_DEFAULT_COLLATION_FOR_UTF8MB4:
                  return "Q_DEFAULT_COLLATION_FOR_UTF8MB4";
              case Q_SQL_REQUIRE_PRIMARY_KEY:
                  return "Q_SQL_REQUIRE_PRIMARY_KEY";
              case Q_DEFAULT_TABLE_ENCRYPTION:
                  return "Q_DEFAULT_TABLE_ENCRYPTION";
              case Q_OPT_FLASHBACK_AREA:
                  // or Q_DDL_SKIP_REWRITE
                  return "Q_OPT_FLASHBACK_AREA";
              case Q_OPT_INDEX_FORMAT_GPP_ENABLED:
                  return "Q_OPT_INDEX_FORMAT_GPP_ENABLED";
              case Q_OPT_INDEX_FORMAT_PANDA_ENABLED:
                  return "Q_OPT_INDEX_FORMAT_PANDA_ENABLED";
              case Q_HRNOW:
                  // or Q_WSREP_SKIP_READONLY_CHECKS
                  return "Q_HRNOW";
              case Q_XID:
                  return "Q_XID";
              case Q_GTID_FLAGS3:
                  return "Q_GTID_FLAGS3";
              case Q_CHARACTER_SET_COLLATIONS :
                  return "Q_CHARACTER_SET_COLLATIONS";
          }
          return "CODE#" + code;
      }
  
      public final String getUser() {
          return user;
      }
  
      public final String getHost() {
          return host;
      }
  
      public final String getQuery() {
          return query;
      }
  
      public final String getCatalog() {
          return catalog;
      }
  
      public final String getDbName() {
          return dbname;
      }
  
      /**
       * The number of seconds the query took to run on the master.
       */
      public final long getExecTime() {
          return execTime;
      }
  
      public final int getErrorCode() {
          return errorCode;
      }
  
      public final long getSessionId() {
          return sessionId;
      }
  
      public final long getAutoIncrementIncrement() {
          return autoIncrementIncrement;
      }
  
      public final long getAutoIncrementOffset() {
          return autoIncrementOffset;
      }
  
      public final Charset getCharset() {
          return charset;
      }
  
      public final String getTimezone() {
          return timezone;
      }
  
      /**
       * Returns the charsetID value.
       * 
       * @return Returns the charsetID.
       */
      public final int getClientCharset() {
          return clientCharset;
      }
  
      /**
       * Returns the clientCollationId value.
       * 
       * @return Returns the clientCollationId.
       */
      public final int getClientCollation() {
          return clientCollation;
      }
  
      /**
       * Returns the serverCollationId value.
       * 
       * @return Returns the serverCollationId.
       */
      public final int getServerCollation() {
          return serverCollation;
      }
  
      public int getTvSec() {
          return tvSec;
      }
  
      public BigInteger getDdlXid() {
          return ddlXid;
      }
  
      /**
       * Returns the sql_mode value.
       * <p>
       * The sql_mode variable. See the section "SQL Modes" in the MySQL manual,
       * and see mysql_priv.h for a list of the possible flags. Currently
       * (2007-10-04), the following flags are available:
       * <ul>
       * <li>MODE_REAL_AS_FLOAT==0x1</li>
       * <li>MODE_PIPES_AS_CONCAT==0x2</li>
       * <li>MODE_ANSI_QUOTES==0x4</li>
       * <li>MODE_IGNORE_SPACE==0x8</li>
       * <li>MODE_NOT_USED==0x10</li>
       * <li>MODE_ONLY_FULL_GROUP_BY==0x20</li>
       * <li>MODE_NO_UNSIGNED_SUBTRACTION==0x40</li>
       * <li>MODE_NO_DIR_IN_CREATE==0x80</li>
       * <li>MODE_POSTGRESQL==0x100</li>
       * <li>MODE_ORACLE==0x200</li>
       * <li>MODE_MSSQL==0x400</li>
       * <li>MODE_DB2==0x800</li>
       * <li>MODE_MAXDB==0x1000</li>
       * <li>MODE_NO_KEY_OPTIONS==0x2000</li>
       * <li>MODE_NO_TABLE_OPTIONS==0x4000</li>
       * <li>MODE_NO_FIELD_OPTIONS==0x8000</li>
       * <li>MODE_MYSQL323==0x10000</li>
       * <li>MODE_MYSQL40==0x20000</li>
       * <li>MODE_ANSI==0x40000</li>
       * <li>MODE_NO_AUTO_VALUE_ON_ZERO==0x80000</li>
       * <li>MODE_NO_BACKSLASH_ESCAPES==0x100000</li>
       * <li>MODE_STRICT_TRANS_TABLES==0x200000</li>
       * <li>MODE_STRICT_ALL_TABLES==0x400000</li>
       * <li>MODE_NO_ZERO_IN_DATE==0x800000</li>
       * <li>MODE_NO_ZERO_DATE==0x1000000</li>
       * <li>MODE_INVALID_DATES==0x2000000</li>
       * <li>MODE_ERROR_FOR_DIVISION_BY_ZERO==0x4000000</li>
       * <li>MODE_TRADITIONAL==0x8000000</li>
       * <li>MODE_NO_AUTO_CREATE_USER==0x10000000</li>
       * <li>MODE_HIGH_NOT_PRECEDENCE==0x20000000</li>
       * <li>MODE_NO_ENGINE_SUBSTITUTION=0x40000000</li>
       * <li>MODE_PAD_CHAR_TO_FULL_LENGTH==0x80000000</li>
       * </ul>
       * All these flags are replicated from the server. However, all flags except
       * MODE_NO_DIR_IN_CREATE are honored by the slave; the slave always
       * preserves its old value of MODE_NO_DIR_IN_CREATE. This field is always
       * written to the binlog.
       */
      public final long getSqlMode() {
          return sql_mode;
      }
  
      /* FLAGS2 values that can be represented inside the binlog */
      public static final int OPTION_AUTO_IS_NULL          = 1 << 14;
      public static final int OPTION_NOT_AUTOCOMMIT        = 1 << 19;
      public static final int OPTION_NO_FOREIGN_KEY_CHECKS = 1 << 26;
      public static final int OPTION_RELAXED_UNIQUE_CHECKS = 1 << 27;
  
      /**
       * The flags in thd->options, binary AND-ed with OPTIONS_WRITTEN_TO_BIN_LOG.
       * The thd->options bitfield contains options for "SELECT". OPTIONS_WRITTEN
       * identifies those options that need to be written to the binlog (not all
       * do). Specifically, OPTIONS_WRITTEN_TO_BIN_LOG equals (OPTION_AUTO_IS_NULL
       * | OPTION_NO_FOREIGN_KEY_CHECKS | OPTION_RELAXED_UNIQUE_CHECKS |
       * OPTION_NOT_AUTOCOMMIT), or 0x0c084000 in hex. These flags correspond to
       * the SQL variables SQL_AUTO_IS_NULL, FOREIGN_KEY_CHECKS, UNIQUE_CHECKS,
       * and AUTOCOMMIT, documented in the "SET Syntax" section of the MySQL
       * Manual. This field is always written to the binlog in version >= 5.0, and
       * never written in version < 5.0.
       */
      public final long getFlags2() {
          return flags2;
      }
  
      /**
       * Returns the OPTION_AUTO_IS_NULL flag.
       */
      public final boolean isAutoIsNull() {
          return ((flags2 & OPTION_AUTO_IS_NULL) == OPTION_AUTO_IS_NULL);
      }
  
      /**
       * Returns the OPTION_NO_FOREIGN_KEY_CHECKS flag.
       */
      public final boolean isForeignKeyChecks() {
          return ((flags2 & OPTION_NO_FOREIGN_KEY_CHECKS) != OPTION_NO_FOREIGN_KEY_CHECKS);
      }
  
      /**
       * Returns the OPTION_NOT_AUTOCOMMIT flag.
       */
      public final boolean isAutocommit() {
          return ((flags2 & OPTION_NOT_AUTOCOMMIT) != OPTION_NOT_AUTOCOMMIT);
      }
  
      /**
       * Returns the OPTION_NO_FOREIGN_KEY_CHECKS flag.
       */
      public final boolean isUniqueChecks() {
          return ((flags2 & OPTION_RELAXED_UNIQUE_CHECKS) != OPTION_RELAXED_UNIQUE_CHECKS);
      }
  
  }
  