class MysqlBinlog::Binlog

Read a binary log, parsing and returning events.

Examples

A basic example of using the Binlog class:

require 'mysql_binlog'
include MysqlBinlog

# Open a binary log from a file on disk.
binlog = Binlog.new(BinlogFileReader.new("mysql-bin.000001"))

# Iterate over all events from the log, printing the event type (such
# as :query_event, :write_rows_event, etc.)
binlog.each_event do |event|
  puts event[:type]
end

Attributes

checksum[RW]
event_parser[RW]
fde[R]
field_parser[RW]
filter_event_types[RW]
filter_flags[RW]
ignore_rotate[RW]
max_query_length[RW]
reader[RW]

Public Class Methods

new(reader) click to toggle source
# File lib/mysql_binlog/binlog.rb, line 56
def initialize(reader)
  @reader = reader
  @field_parser = BinlogFieldParser.new(self)
  @event_parser = BinlogEventParser.new(self)
  @fde = nil
  @filter_event_types = nil
  @filter_flags = nil
  @ignore_rotate = false
  @max_query_length = 1048576
  @checksum = :nil
end

Public Instance Methods

checksum_length() click to toggle source
# File lib/mysql_binlog/binlog.rb, line 114
def checksum_length
  case @checksum
  when :crc32
    4
  else
    0
  end
end
each_event() { |event| ... } click to toggle source

Iterate through all events.

# File lib/mysql_binlog/binlog.rb, line 218
def each_event
  unless block_given?
    return Enumerable::Enumerator.new(self, :each_event)
  end

  while event = read_event
    yield event
  end
end
payload_length(header) click to toggle source
# File lib/mysql_binlog/binlog.rb, line 123
def payload_length(header)
  @fde ? (header[:event_length] - @fde[:header_length] - checksum_length) : 0
end
read_event() click to toggle source

Scan events until finding one that isn't rejected by the filter rules. If there are no filter rules, this will return the next event provided by the reader.

# File lib/mysql_binlog/binlog.rb, line 130
def read_event
  while true
    skip_this_event = false
    return nil if reader.end?

    filename = reader.filename
    position = reader.position

    # Read the common header for an event. Every event has a header.
    unless header = event_parser.event_header
      return nil
    end

    # Skip the remaining part of the header which might not have been
    # parsed.
    if @fde
      reader.seek(position + @fde[:header_length])
      header[:payload_length] = payload_length(header)
      header[:payload_end] = position + @fde[:header_length] + payload_length(header)
    else
      header[:payload_length] = 0
      header[:payload_end] = header[:next_position]
    end


    if @filter_event_types
      unless @filter_event_types.include? header[:event_type]
        skip_this_event = true
      end
    end
    
    if @filter_flags
      unless @filter_flags.include? header[:flags]
        skip_this_event = true
      end
    end

    # Never skip over rotate_event or format_description_event as they
    # are critical to understanding the format of this event stream.
    if skip_this_event
      unless [:rotate_event, :format_description_event].include? header[:event_type]
        skip_event(header)
        next
      end
    end

    fields = read_event_fields(header)

    case header[:event_type]
    when :rotate_event
      unless ignore_rotate
        reader.rotate(fields[:name], fields[:pos])
      end
    when :format_description_event
      process_fde(fields)
    end

    break
  end

  {
    :type     => header[:event_type],
    :filename => filename,
    :position => position,
    :header   => header,
    :event    => fields,
  }
end
rewind() click to toggle source

Rewind to the beginning of the log, if supported by the reader. The reader may throw an exception if rewinding is not supported (e.g. for a stream-based reader).

# File lib/mysql_binlog/binlog.rb, line 71
def rewind
  reader.rewind
end

Private Instance Methods

process_fde(fde) click to toggle source

Process a format description event, which describes the version of this file, and the format of events which will appear in this file. This also provides the version of the MySQL server which generated this file.

# File lib/mysql_binlog/binlog.rb, line 202
def process_fde(fde)
  if (version = fde[:binlog_version]) != 4
    raise UnsupportedVersionException.new("Binlog version #{version} is not supported")
  end

  # Save the interesting fields from an FDE so that this information is
  # available at any time later.
  @fde = {
    :header_length  => fde[:header_length],
    :binlog_version => fde[:binlog_version],
    :server_version => fde[:server_version],
  }
end
read_event_fields(header) click to toggle source

Read the content of the event, which follows the header.

# File lib/mysql_binlog/binlog.rb, line 83
def read_event_fields(header)
  # Delegate the parsing of the event content to a method of the same name
  # in BinlogEventParser.
  if event_parser.methods.map(&:to_sym).include? header[:event_type]
    fields = event_parser.send(header[:event_type], header)
  end

  unless fields
    fields = {
      payload: reader.read(header[:payload_length]),
    }
  end

  # Check if we've read past the end of the event. This is normally because
  # of an unsupported substructure in the event causing field misalignment
  # or a bug in the event reader method in BinlogEventParser. This may also
  # be due to user error in providing an initial start position or later
  # seeking to a position which is not a valid event start position.
  if reader.position > header[:next_position]
    raise OverReadException.new("Read past end of event; corrupted event, bad start position, or bug in mysql_binlog?")
  end

  # Anything left unread at this point is skipped based on the event length
  # provided in the header. In this way, it is possible to skip over events
  # that are not able to be parsed completely by this library.
  skip_event(header)

  fields
end
skip_event(header) click to toggle source

Skip the remainder of this event. This can be used to skip an entire event or merely the parts of the event this library does not understand.

# File lib/mysql_binlog/binlog.rb, line 77
def skip_event(header)
  reader.skip(header)
end