content: package com.taobao.tddl.dbsync.binlog.event;
  
  import com.taobao.tddl.dbsync.binlog.LogBuffer;
  import com.taobao.tddl.dbsync.binlog.LogEvent;
  
  /**
   * This log event corresponds to a "LOAD DATA INFILE" SQL query on the following
   * form:
   * 
   * <pre>
   *    (1)    USE db;
   *    (2)    LOAD DATA [CONCURRENT] [LOCAL] INFILE 'file_name'
   *    (3)    [REPLACE | IGNORE]
   *    (4)    INTO TABLE 'table_name'
   *    (5)    [FIELDS
   *    (6)      [TERMINATED BY 'field_term']
   *    (7)      [[OPTIONALLY] ENCLOSED BY 'enclosed']
   *    (8)      [ESCAPED BY 'escaped']
   *    (9)    ]
   *   (10)    [LINES
   *   (11)      [TERMINATED BY 'line_term']
   *   (12)      [LINES STARTING BY 'line_start']
   *   (13)    ]
   *   (14)    [IGNORE skip_lines LINES]
   *   (15)    (field_1, field_2, ..., field_n)
   * </pre>
   * 
   * Binary Format: The Post-Header consists of the following six components.
   * <table>
   * <caption>Post-Header for Load_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>skip_lines</td>
   * <td>4 byte unsigned integer</td>
   * <td>The number on line (14) above, if present, or 0 if line (14) is left out.
   * </td>
   * </tr>
   * <tr>
   * <td>table_name_len</td>
   * <td>1 byte unsigned integer</td>
   * <td>The length of 'table_name' on line (4) above.</td>
   * </tr>
   * <tr>
   * <td>db_len</td>
   * <td>1 byte unsigned integer</td>
   * <td>The length of 'db' on line (1) above.</td>
   * </tr>
   * <tr>
   * <td>num_fields</td>
   * <td>4 byte unsigned integer</td>
   * <td>The number n of fields on line (15) above.</td>
   * </tr>
   * </table>
   * The Body contains the following components.
   * <table>
   * <caption>Body of Load_log_event</caption>
   * <tr>
   * <th>Name</th>
   * <th>Format</th>
   * <th>Description</th>
   * </tr>
   * <tr>
   * <td>sql_ex</td>
   * <td>variable length</td>
   * <td>Describes the part of the query on lines (3) and (5)&ndash;(13) above.
   * More precisely, it stores the five strings (on lines) field_term (6),
   * enclosed (7), escaped (8), line_term (11), and line_start (12); as well as a
   * bitfield indicating the presence of the keywords REPLACE (3), IGNORE (3), and
   * OPTIONALLY (7). The data is stored in one of two formats, called "old" and
   * "new". The type field of Common-Header determines which of these two formats
   * is used: type LOAD_EVENT means that the old format is used, and type
   * NEW_LOAD_EVENT means that the new format is used. When MySQL writes a
   * Load_log_event, it uses the new format if at least one of the five strings is
   * two or more bytes long. Otherwise (i.e., if all strings are 0 or 1 bytes
   * long), the old format is used. The new and old format differ in the way the
   * five strings are stored.
   * <ul>
   * <li>In the new format, the strings are stored in the order field_term,
   * enclosed, escaped, line_term, line_start. Each string consists of a length (1
   * byte), followed by a sequence of characters (0-255 bytes). Finally, a boolean
   * combination of the following flags is stored in 1 byte: REPLACE_FLAG==0x4,
   * IGNORE_FLAG==0x8, and OPT_ENCLOSED_FLAG==0x2. If a flag is set, it indicates
   * the presence of the corresponding keyword in the SQL query.
   * <li>In the old format, we know that each string has length 0 or 1. Therefore,
   * only the first byte of each string is stored. The order of the strings is the
   * same as in the new format. These five bytes are followed by the same 1 byte
   * bitfield as in the new format. Finally, a 1 byte bitfield called empty_flags
   * is stored. The low 5 bits of empty_flags indicate which of the five strings
   * have length 0. For each of the following flags that is set, the corresponding
   * string has length 0; for the flags that are not set, the string has length 1:
   * FIELD_TERM_EMPTY==0x1, ENCLOSED_EMPTY==0x2, LINE_TERM_EMPTY==0x4,
   * LINE_START_EMPTY==0x8, ESCAPED_EMPTY==0x10.
   * </ul>
   * Thus, the size of the new format is 6 bytes + the sum of the sizes of the
   * five strings. The size of the old format is always 7 bytes.</td>
   * </tr>
   * <tr>
   * <td>field_lens</td>
   * <td>num_fields 1 byte unsigned integers</td>
   * <td>An array of num_fields integers representing the length of each field in
   * the query. (num_fields is from the Post-Header).</td>
   * </tr>
   * <tr>
   * <td>fields</td>
   * <td>num_fields null-terminated strings</td>
   * <td>An array of num_fields null-terminated strings, each representing a field
   * in the query. (The trailing zero is redundant, since the length are stored in
   * the num_fields array.) The total length of all strings equals to the sum of
   * all field_lens, plus num_fields bytes for all the trailing zeros.</td>
   * </tr>
   * <tr>
   * <td>table_name</td>
   * <td>null-terminated string of length table_len+1 bytes</td>
   * <td>The 'table_name' from the query, as a null-terminated string. (The
   * trailing zero is actually redundant since the table_len is known from
   * Post-Header.)</td>
   * </tr>
   * <tr>
   * <td>db</td>
   * <td>null-terminated string of length db_len+1 bytes</td>
   * <td>The 'db' from the query, as a null-terminated string. (The trailing zero
   * is actually redundant since the db_len is known from Post-Header.)</td>
   * </tr>
   * <tr>
   * <td>file_name</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 'file_name' from the query.</td>
   * </tr>
   * </table>
   * This event type is understood by current versions, but only generated by
   * MySQL 3.23 and earlier.
   * 
   * @author <a href="mailto:changyuan.lh@taobao.com">Changyuan.lh</a>
   * @version 1.0
   */
  public class LoadLogEvent extends LogEvent {
  
      private String          table;
      private String          db;
      private String          fname;
      private int             skipLines;
      private int             numFields;
      private String[]        fields;
  
      /* sql_ex_info */
      private String          fieldTerm;
      private String          lineTerm;
      private String          lineStart;
      private String          enclosed;
      private String          escaped;
      private int             optFlags;
      private int             emptyFlags;
  
      private long            execTime;
  
      /* Load event post-header */
      public static final int L_THREAD_ID_OFFSET  = 0;
      public static final int L_EXEC_TIME_OFFSET  = 4;
      public static final int L_SKIP_LINES_OFFSET = 8;
      public static final int L_TBL_LEN_OFFSET    = 12;
      public static final int L_DB_LEN_OFFSET     = 13;
      public static final int L_NUM_FIELDS_OFFSET = 14;
      public static final int L_SQL_EX_OFFSET     = 18;
      public static final int L_DATA_OFFSET       = FormatDescriptionLogEvent.LOAD_HEADER_LEN;
  
      /*
       * These are flags and structs to handle all the LOAD DATA INFILE options
       * (LINES TERMINATED etc). DUMPFILE_FLAG is probably useless (DUMPFILE is a
       * clause of SELECT, not of LOAD DATA).
       */
      public static final int DUMPFILE_FLAG       = 0x1;
      public static final int OPT_ENCLOSED_FLAG   = 0x2;
      public static final int REPLACE_FLAG        = 0x4;
      public static final int IGNORE_FLAG         = 0x8;
  
      public static final int FIELD_TERM_EMPTY    = 0x1;
      public static final int ENCLOSED_EMPTY      = 0x2;
      public static final int LINE_TERM_EMPTY     = 0x4;
      public static final int LINE_START_EMPTY    = 0x8;
      public static final int ESCAPED_EMPTY       = 0x10;
  
      public LoadLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){
          super(header);
  
          final int loadHeaderLen = FormatDescriptionLogEvent.LOAD_HEADER_LEN;
          /*
           * 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.
           */
          copyLogEvent(buffer,
              ((header.type == LOAD_EVENT) ? loadHeaderLen + descriptionEvent.commonHeaderLen : loadHeaderLen
                                                                                                + FormatDescriptionLogEvent.LOG_EVENT_HEADER_LEN),
              descriptionEvent);
      }
  
      /**
       * @see mysql-5.1.60/sql/log_event.cc - Load_log_event::copy_log_event
       */
      protected final void copyLogEvent(LogBuffer buffer, final int bodyOffset, FormatDescriptionLogEvent descriptionEvent) {
          /* this is the beginning of the post-header */
          buffer.position(descriptionEvent.commonHeaderLen + L_EXEC_TIME_OFFSET);
  
          execTime = buffer.getUint32(); // L_EXEC_TIME_OFFSET
          skipLines = (int) buffer.getUint32(); // L_SKIP_LINES_OFFSET
          final int tableNameLen = buffer.getUint8(); // L_TBL_LEN_OFFSET
          final int dbLen = buffer.getUint8(); // L_DB_LEN_OFFSET
          numFields = (int) buffer.getUint32(); // L_NUM_FIELDS_OFFSET
  
          buffer.position(bodyOffset);
          /*
           * 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 (header.type != LOAD_EVENT /* use_new_format */) {
              /*
               * 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.
               */
              fieldTerm = buffer.getString();
              enclosed = buffer.getString();
              lineTerm = buffer.getString();
              lineStart = buffer.getString();
              escaped = buffer.getString();
              optFlags = buffer.getInt8();
              emptyFlags = 0;
          } else {
              fieldTerm = buffer.getFixString(1);
              enclosed = buffer.getFixString(1);
              lineTerm = buffer.getFixString(1);
              lineStart = buffer.getFixString(1);
              escaped = buffer.getFixString(1);
              optFlags = buffer.getUint8();
              emptyFlags = buffer.getUint8();
  
              if ((emptyFlags & FIELD_TERM_EMPTY) != 0) fieldTerm = null;
              if ((emptyFlags & ENCLOSED_EMPTY) != 0) enclosed = null;
              if ((emptyFlags & LINE_TERM_EMPTY) != 0) lineTerm = null;
              if ((emptyFlags & LINE_START_EMPTY) != 0) lineStart = null;
              if ((emptyFlags & ESCAPED_EMPTY) != 0) escaped = null;
          }
  
          final int fieldLenPos = buffer.position();
          buffer.forward(numFields);
          fields = new String[numFields];
          for (int i = 0; i < numFields; i++) {
              final int fieldLen = buffer.getUint8(fieldLenPos + i);
              fields[i] = buffer.getFixString(fieldLen + 1);
          }
  
          table = buffer.getFixString(tableNameLen + 1);
          db = buffer.getFixString(dbLen + 1);
  
          // null termination is accomplished by the caller
          final int from = buffer.position();
          final int end = from + buffer.limit();
          int found = from;
          for (; (found < end) && buffer.getInt8(found) != '\0'; found++)
              /* empty loop */;
          fname = buffer.getString(found);
          buffer.forward(1); // The + 1 is for \0 terminating fname
      }
  
      public final String getTable() {
          return table;
      }
  
      public final String getDb() {
          return db;
      }
  
      public final String getFname() {
          return fname;
      }
  
      public final int getSkipLines() {
          return skipLines;
      }
  
      public final String[] getFields() {
          return fields;
      }
  
      public final String getFieldTerm() {
          return fieldTerm;
      }
  
      public final String getLineTerm() {
          return lineTerm;
      }
  
      public final String getLineStart() {
          return lineStart;
      }
  
      public final String getEnclosed() {
          return enclosed;
      }
  
      public final String getEscaped() {
          return escaped;
      }
  
      public final int getOptFlags() {
          return optFlags;
      }
  
      public final int getEmptyFlags() {
          return emptyFlags;
      }
  
      public final long getExecTime() {
          return execTime;
      }
  }
  