mysql_binlog 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,6 +14,13 @@ module MysqlBinlog
14
14
  # event.
15
15
  class ShortReadException < Exception; end
16
16
 
17
+ # After an event or other structure was fully read, the log position exceeded
18
+ # the end of the structure being read. This would indicate a bug in parsing
19
+ # the fields in the structure. For example, reading garbage data for a length
20
+ # field may cause a string read based on that length to read data well past
21
+ # the end of the event or structure. This is essentially always fatal.
22
+ class OverReadException < Exception; end
23
+
17
24
  # Read a binary log, parsing and returning events.
18
25
  #
19
26
  # == Examples
@@ -69,17 +76,24 @@ module MysqlBinlog
69
76
 
70
77
  # Read the content of the event, which follows the header.
71
78
  def read_event_fields(header)
72
- event_type = EVENT_TYPES[header[:event_type]]
73
-
74
79
  # Delegate the parsing of the event content to a method of the same name
75
80
  # in BinlogEventParser.
76
- if event_parser.methods.include? event_type.to_s
77
- fields = event_parser.send(event_type, header)
81
+ if event_parser.methods.include? header[:event_type].to_s
82
+ fields = event_parser.send(header[:event_type], header)
83
+ end
84
+
85
+ # Check if we've read past the end of the event. This is normally because
86
+ # of an unsupported substructure in the event causing field misalignment
87
+ # or a bug in the event reader method in BinlogEventParser. This may also
88
+ # be due to user error in providing an initial start position or later
89
+ # seeking to a position which is not a valid event start position.
90
+ if reader.position > header[:next_position]
91
+ raise OverReadException.new("Read past end of event; corrupted event, bad start position, or bug in mysql_binlog?")
78
92
  end
79
93
 
80
94
  # Anything left unread at this point is skipped based on the event length
81
95
  # provided in the header. In this way, it is possible to skip over events
82
- # that are not able to be parsed correctly by this library.
96
+ # that are not able to be parsed completely by this library.
83
97
  skip_event(header)
84
98
 
85
99
  fields
@@ -102,10 +116,8 @@ module MysqlBinlog
102
116
  return nil
103
117
  end
104
118
 
105
- event_type = EVENT_TYPES[header[:event_type]]
106
-
107
119
  if @filter_event_types
108
- unless @filter_event_types.include? event_type
120
+ unless @filter_event_types.include? header[:event_type]
109
121
  skip_this_event = true
110
122
  end
111
123
  end
@@ -119,15 +131,15 @@ module MysqlBinlog
119
131
  # Never skip over rotate_event or format_description_event as they
120
132
  # are critical to understanding the format of this event stream.
121
133
  if skip_this_event
122
- unless [:rotate_event, :format_description_event].include? event_type
134
+ unless [:rotate_event, :format_description_event].include? header[:event_type]
123
135
  skip_event(header)
124
136
  next
125
137
  end
126
138
  end
127
-
139
+
128
140
  fields = read_event_fields(header)
129
141
 
130
- case event_type
142
+ case header[:event_type]
131
143
  when :rotate_event
132
144
  next if ignore_rotate
133
145
  reader.rotate(fields[:name], fields[:pos])
@@ -139,7 +151,7 @@ module MysqlBinlog
139
151
  end
140
152
 
141
153
  {
142
- :type => event_type,
154
+ :type => header[:event_type],
143
155
  :filename => filename,
144
156
  :position => position,
145
157
  :header => header,
@@ -167,6 +179,10 @@ module MysqlBinlog
167
179
 
168
180
  # Iterate through all events.
169
181
  def each_event
182
+ unless block_given?
183
+ return Enumerable::Enumerator.new(self, :each_event)
184
+ end
185
+
170
186
  while event = read_event
171
187
  yield event
172
188
  end
@@ -1,40 +1,21 @@
1
1
  module MysqlBinlog
2
- # A common fixed-length header that is included with each event.
3
- EVENT_HEADER = [
4
- { :name => :timestamp, :length => 4, :format => "V" },
5
- { :name => :event_type, :length => 1, :format => "C" },
6
- { :name => :server_id, :length => 4, :format => "V" },
7
- { :name => :event_length, :length => 4, :format => "V" },
8
- { :name => :next_position, :length => 4, :format => "V" },
9
- { :name => :flags, :length => 2, :format => "v" },
10
- ]
11
-
12
- # Values for the +flags+ field that may appear in binary logs. There are
13
- # several other values that never appear in a file but may be used
14
- # in events in memory.
15
- EVENT_HEADER_FLAGS = {
16
- :binlog_in_use => 0x01,
17
- :thread_specific => 0x04,
18
- :suppress_use => 0x08,
19
- :artificial => 0x20,
20
- :relay_log => 0x40,
21
- }
22
-
23
2
  # An array to quickly map an integer event type to its symbol.
3
+ #
4
+ # Enumerated in sql/log_event.h line ~539 as Log_event_type
24
5
  EVENT_TYPES = [
25
6
  :unknown_event, # 0
26
- :start_event_v3, # 1
7
+ :start_event_v3, # 1 (deprecated)
27
8
  :query_event, # 2
28
9
  :stop_event, # 3
29
10
  :rotate_event, # 4
30
11
  :intvar_event, # 5
31
- :load_event, # 6
32
- :slave_event, # 7
33
- :create_file_event, # 8
12
+ :load_event, # 6 (deprecated)
13
+ :slave_event, # 7 (deprecated)
14
+ :create_file_event, # 8 (deprecated)
34
15
  :append_block_event, # 9
35
- :exec_load_event, # 10
16
+ :exec_load_event, # 10 (deprecated)
36
17
  :delete_file_event, # 11
37
- :new_load_event, # 12
18
+ :new_load_event, # 12 (deprecated)
38
19
  :rand_event, # 13
39
20
  :user_var_event, # 14
40
21
  :format_description_event, # 15
@@ -42,9 +23,9 @@ module MysqlBinlog
42
23
  :begin_load_query_event, # 17
43
24
  :execute_load_query_event, # 18
44
25
  :table_map_event, # 19
45
- :pre_ga_write_rows_event, # 20
46
- :pre_ga_update_rows_event, # 21
47
- :pre_ga_delete_rows_event, # 22
26
+ :pre_ga_write_rows_event, # 20 (deprecated)
27
+ :pre_ga_update_rows_event, # 21 (deprecated)
28
+ :pre_ga_delete_rows_event, # 22 (deprecated)
48
29
  :write_rows_event, # 23
49
30
  :update_rows_event, # 24
50
31
  :delete_rows_event, # 25
@@ -52,38 +33,80 @@ module MysqlBinlog
52
33
  :heartbeat_log_event, # 27
53
34
  ]
54
35
 
36
+ # Values for the +flags+ field that may appear in binary logs. There are
37
+ # several other values that never appear in a file but may be used
38
+ # in events in memory.
39
+ #
40
+ # Defined in sql/log_event.h line ~448
41
+ EVENT_HEADER_FLAGS = {
42
+ :binlog_in_use => 0x01, # LOG_EVENT_BINLOG_IN_USE_F
43
+ :thread_specific => 0x04, # LOG_EVENT_THREAD_SPECIFIC_F
44
+ :suppress_use => 0x08, # LOG_EVENT_SUPPRESS_USE_F
45
+ :artificial => 0x20, # LOG_EVENT_ARTIFICIAL_F
46
+ :relay_log => 0x40, # LOG_EVENT_RELAY_LOG_F
47
+ }
48
+
55
49
  # A mapping array for all values that may appear in the +status+ field of
56
50
  # a query_event.
51
+ #
52
+ # Defined in sql/log_event.h line ~316
57
53
  QUERY_EVENT_STATUS_TYPES = [
58
- :flags2, # 0
59
- :sql_mode, # 1
60
- :catalog_deprecated, # 2
61
- :auto_increment, # 3
62
- :charset, # 4
63
- :time_zone, # 5
64
- :catalog, # 6
65
- :lc_time_names, # 7
66
- :charset_database, # 8
67
- :table_map_for_update, # 9
54
+ :flags2, # 0 (Q_FLAGS2_CODE)
55
+ :sql_mode, # 1 (Q_SQL_MODE_CODE)
56
+ :catalog_deprecated, # 2 (Q_CATALOG_CODE)
57
+ :auto_increment, # 3 (Q_AUTO_INCREMENT)
58
+ :charset, # 4 (Q_CHARSET_CODE)
59
+ :time_zone, # 5 (Q_TIME_ZONE_CODE)
60
+ :catalog, # 6 (Q_CATALOG_NZ_CODE)
61
+ :lc_time_names, # 7 (Q_LC_TIME_NAMES_CODE)
62
+ :charset_database, # 8 (Q_CHARSET_DATABASE_CODE)
63
+ :table_map_for_update, # 9 (Q_TABLE_MAP_FOR_UPDATE_CODE)
64
+ :master_data_written, # 10 (Q_MASTER_DATA_WRITTEN_CODE)
65
+ :invoker, # 11 (Q_INVOKER)
68
66
  ]
69
67
 
70
68
  # A mapping hash for all values that may appear in the +flags2+ field of
71
69
  # a query_event.
70
+ #
71
+ # Defined in sql/log_event.h line ~521 in OPTIONS_WRITTEN_TO_BIN_LOG
72
+ #
73
+ # Defined in sql/sql_priv.h line ~84
72
74
  QUERY_EVENT_FLAGS2 = {
73
- :auto_is_null => 1 << 14,
74
- :not_autocommit => 1 << 19,
75
- :no_foreign_key_checks => 1 << 26,
76
- :relaxed_unique_checks => 1 << 27,
75
+ :auto_is_null => 1 << 14, # OPTION_AUTO_IS_NULL
76
+ :not_autocommit => 1 << 19, # OPTION_NOT_AUTOCOMMIT
77
+ :no_foreign_key_checks => 1 << 26, # OPTION_NO_FOREIGN_KEY_CHECKS
78
+ :relaxed_unique_checks => 1 << 27, # OPTION_RELAXED_UNIQUE_CHECKS
77
79
  }
78
80
 
79
81
  # A mapping array for all values that may appear in the +Intvar_type+ field
80
82
  # of an intvar_event.
83
+ #
84
+ # Enumerated in sql/log_event.h line ~613 as Int_event_type
81
85
  INTVAR_EVENT_INTVAR_TYPES = [
82
- nil,
83
- :last_insert_id,
84
- :insert_id,
86
+ nil, # INVALID_INT_EVENT
87
+ :last_insert_id, # LAST_INSERT_ID_EVENT
88
+ :insert_id, # INSERT_ID_EVENT
85
89
  ]
86
90
 
91
+ # A mapping array for all values that may appear in the +flags+ field of a
92
+ # table_map_event.
93
+ #
94
+ # Enumerated in sql/log_event.h line ~3413 within Table_map_log_event
95
+ TABLE_MAP_EVENT_FLAGS = {
96
+ :bit_len_exact => 1 << 0, # TM_BIT_LEN_EXACT_F
97
+ }
98
+
99
+ # A mapping array for all values that may appear in the +flags+ field of a
100
+ # write_rows_event, update_rows_event, or delete_rows_event.
101
+ #
102
+ # Enumerated in sql/log_event.h line ~3533 within Rows_log_event
103
+ GENERIC_ROWS_EVENT_FLAGS = {
104
+ :stmt_end => 1 << 0, # STMT_END_F
105
+ :no_foreign_key_checks => 1 << 1, # NO_FOREIGN_KEY_CHECKS_F
106
+ :relaxed_unique_checks => 1 << 2, # RELAXED_UNIQUE_CHECKS_F
107
+ :complete_rows => 1 << 3, # COMPLETE_ROWS_F
108
+ }
109
+
87
110
  # Parse binary log events from a provided binary log. Must be driven
88
111
  # externally, but handles all the details of parsing an event header
89
112
  # and the content of the various event types.
@@ -105,26 +128,25 @@ module MysqlBinlog
105
128
  @table_map = {}
106
129
  end
107
130
 
108
- # Parse an event header, described by the +EVENT_HEADER+ structure above.
131
+ # Parse an event header, which is consistent for all event types.
132
+ #
133
+ # Documented in sql/log_event.h line ~749 as "Common-Header"
134
+ #
135
+ # Implemented in sql/log_event.cc line ~936 in Log_event::write_header
109
136
  def event_header
110
- header = parser.read_and_unpack(EVENT_HEADER)
111
-
112
- # Merge the read +flags+ bitmap with the +EVENT_HEADER_FLAGS+ hash to
113
- # return the flags by name instead of returning the bitmap as an integer.
114
- flags = EVENT_HEADER_FLAGS.inject([]) do |result, (flag_name, flag_bit_value)|
115
- if (header[:flags] & flag_bit_value) != 0
116
- result << flag_name
117
- end
118
- result
119
- end
120
-
121
- # Overwrite the integer version of +flags+ with the array of names.
122
- header[:flags] = flags
123
-
137
+ header = {}
138
+ header[:timestamp] = parser.read_uint32
139
+ header[:event_type] = EVENT_TYPES[parser.read_uint8]
140
+ header[:server_id] = parser.read_uint32
141
+ header[:event_length] = parser.read_uint32
142
+ header[:next_position] = parser.read_uint32
143
+ header[:flags] = parser.read_uint_bitmap_by_size_and_name(2, EVENT_HEADER_FLAGS)
124
144
  header
125
145
  end
126
146
 
127
147
  # Parse fields for a +Format_description+ event.
148
+ #
149
+ # Implemented in sql/log_event.cc line ~4123 in Format_description_log_event::write
128
150
  def format_description_event(header)
129
151
  fields = {}
130
152
  fields[:binlog_version] = parser.read_uint16
@@ -135,6 +157,8 @@ module MysqlBinlog
135
157
  end
136
158
 
137
159
  # Parse fields for a +Rotate+ event.
160
+ #
161
+ # Implemented in sql/log_event.cc line ~5157 in Rotate_log_event::write
138
162
  def rotate_event(header)
139
163
  fields = {}
140
164
  fields[:pos] = parser.read_uint64
@@ -157,7 +181,7 @@ module MysqlBinlog
157
181
  status_type = QUERY_EVENT_STATUS_TYPES[parser.read_uint8]
158
182
  status[status_type] = case status_type
159
183
  when :flags2
160
- parser.read_uint32_bitmap_by_name(QUERY_EVENT_FLAGS2)
184
+ parser.read_uint_bitmap_by_size_and_name(4, QUERY_EVENT_FLAGS2)
161
185
  when :sql_mode
162
186
  parser.read_uint64
163
187
  when :catalog_deprecated
@@ -185,11 +209,21 @@ module MysqlBinlog
185
209
  parser.read_uint64
186
210
  end
187
211
  end
212
+
213
+ # We may have read too much due to an invalid string read especially.
214
+ # Raise a more specific exception here instead of the generic
215
+ # OverReadException from the entire event.
216
+ if reader.position > end_position
217
+ raise OverReadException.new("Read past end of Query event status field")
218
+ end
219
+
188
220
  status
189
221
  end
190
222
  private :_query_event_status
191
223
 
192
224
  # Parse fields for a +Query+ event.
225
+ #
226
+ # Implemented in sql/log_event.cc line ~2214 in Query_log_event::write
193
227
  def query_event(header)
194
228
  fields = {}
195
229
  fields[:thread_id] = parser.read_uint32
@@ -204,6 +238,8 @@ module MysqlBinlog
204
238
  end
205
239
 
206
240
  # Parse fields for an +Intvar+ event.
241
+ #
242
+ # Implemented in sql/log_event.cc line ~5326 in Intvar_log_event::write
207
243
  def intvar_event(header)
208
244
  fields = {}
209
245
 
@@ -215,6 +251,8 @@ module MysqlBinlog
215
251
  end
216
252
 
217
253
  # Parse fields for an +Xid+ event.
254
+ #
255
+ # Implemented in sql/log_event.cc line ~5559 in Xid_log_event::write
218
256
  def xid_event(header)
219
257
  fields = {}
220
258
  fields[:xid] = parser.read_uint64
@@ -222,6 +260,8 @@ module MysqlBinlog
222
260
  end
223
261
 
224
262
  # Parse fields for an +Rand+ event.
263
+ #
264
+ # Implemented in sql/log_event.cc line ~5454 in Rand_log_event::write
225
265
  def rand_event(header)
226
266
  fields = {}
227
267
  fields[:seed1] = parser.read_uint64
@@ -239,9 +279,10 @@ module MysqlBinlog
239
279
  when :varchar
240
280
  { :max_length => parser.read_uint16 }
241
281
  when :bit
282
+ bits = parser.read_uint8
283
+ bytes = parser.read_uint8
242
284
  {
243
- :size_bits => parser.read_uint8,
244
- :size_bytes => parser.read_uint8,
285
+ :bits => (bytes * 8) + bits
245
286
  }
246
287
  when :newdecimal
247
288
  {
@@ -276,10 +317,14 @@ module MysqlBinlog
276
317
  private :_table_map_event_column_metadata
277
318
 
278
319
  # Parse fields for a +Table_map+ event.
320
+ #
321
+ # Implemented in sql/log_event.cc line ~8638
322
+ # in Table_map_log_event::write_data_header
323
+ # and Table_map_log_event::write_data_body
279
324
  def table_map_event(header)
280
325
  fields = {}
281
326
  fields[:table_id] = parser.read_uint48
282
- fields[:flags] = parser.read_uint16
327
+ fields[:flags] = parser.read_uint_bitmap_by_size_and_name(2, TABLE_MAP_EVENT_FLAGS)
283
328
  map_entry = @table_map[fields[:table_id]] = {}
284
329
  map_entry[:db] = parser.read_lpstringz
285
330
  map_entry[:table] = parser.read_lpstringz
@@ -337,7 +382,7 @@ module MysqlBinlog
337
382
  end_position = reader.position + reader.remaining(header)
338
383
  while reader.position < end_position
339
384
  row_image = {}
340
- case EVENT_TYPES[header[:event_type]]
385
+ case header[:event_type]
341
386
  when :write_rows_event
342
387
  row_image[:after] = _generic_rows_event_row_image(header, fields, columns_used[:after])
343
388
  when :delete_rows_event
@@ -348,6 +393,14 @@ module MysqlBinlog
348
393
  end
349
394
  row_images << row_image
350
395
  end
396
+
397
+ # We may have read too much, especially if any of the fields in the row
398
+ # image were misunderstood. Raise a more specific exception here instead
399
+ # of the generic OverReadException from the entire event.
400
+ if reader.position > end_position
401
+ raise OverReadException.new("Read past end of row image")
402
+ end
403
+
351
404
  row_images
352
405
  end
353
406
  private :_generic_rows_event_row_images
@@ -356,14 +409,18 @@ module MysqlBinlog
356
409
  # * +Write_rows+ which is used for +INSERT+.
357
410
  # * +Update_rows+ which is used for +UPDATE+.
358
411
  # * +Delete_rows+ which is used for +DELETE+.
412
+ #
413
+ # Implemented in sql/log_event.cc line ~8039
414
+ # in Rows_log_event::write_data_header
415
+ # and Rows_log_event::write_data_body
359
416
  def generic_rows_event(header)
360
417
  fields = {}
361
418
  table_id = parser.read_uint48
362
419
  fields[:table] = @table_map[table_id]
363
- fields[:flags] = parser.read_uint16
420
+ fields[:flags] = parser.read_uint_bitmap_by_size_and_name(2, GENERIC_ROWS_EVENT_FLAGS)
364
421
  columns = parser.read_varint
365
422
  columns_used = {}
366
- case EVENT_TYPES[header[:event_type]]
423
+ case header[:event_type]
367
424
  when :write_rows_event
368
425
  columns_used[:after] = parser.read_bit_array(columns)
369
426
  when :delete_rows_event
@@ -99,12 +99,12 @@ module MysqlBinlog
99
99
 
100
100
  # Read a single-precision (4-byte) floating point number.
101
101
  def read_float
102
- reader.read(4).unpack("g").first
102
+ reader.read(4).unpack("e").first
103
103
  end
104
104
 
105
105
  # Read a double-precision (8-byte) floating point number.
106
106
  def read_double
107
- reader.read(8).unpack("G").first
107
+ reader.read(8).unpack("E").first
108
108
  end
109
109
 
110
110
  # Read a variable-length "Length Coded Binary" integer. This is derived
@@ -174,16 +174,18 @@ module MysqlBinlog
174
174
  end
175
175
 
176
176
  # Read an arbitrary-length bitmap, provided its length. Returns an array
177
- # of true/false values.
177
+ # of true/false values. This is used both for internal usage in RBR
178
+ # events that need bitmaps, as well as for the BIT type.
178
179
  def read_bit_array(length)
179
180
  data = reader.read((length+7)/8)
180
- data.unpack("b*").first.bytes.to_a.map { |i| (i-48) == 1 }.shift(length)
181
+ data.unpack("B*").first.reverse. # Unpack into a string of "10101"
182
+ split("").map { |c| c == "1" }.shift(length) # Return true/false array
181
183
  end
182
184
 
183
- # Read a uint32 value, and convert it to an array of symbols derived
184
- # from a mapping table provided.
185
- def read_uint32_bitmap_by_name(names)
186
- value = read_uint32
185
+ # Read a uint value using the provided size, and convert it to an array
186
+ # of symbols derived from a mapping table provided.
187
+ def read_uint_bitmap_by_size_and_name(size, names)
188
+ value = read_uint_by_size(size)
187
189
  names.inject([]) do |result, (name, bit_value)|
188
190
  if (value & bit_value) != 0
189
191
  result << name
@@ -192,25 +194,6 @@ module MysqlBinlog
192
194
  end
193
195
  end
194
196
 
195
- # Read a series of fields, provided an array of field descriptions. This
196
- # can be used to read many types of fixed-length structures.
197
- def read_and_unpack(format_description)
198
- @format_cache[format_description] ||= {}
199
- this_format = @format_cache[format_description][:format] ||=
200
- format_description.inject("") { |o, f| o+(f[:format] || "") }
201
- this_length = @format_cache[format_description][:length] ||=
202
- format_description.inject(0) { |o, f| o+(f[:length] || 0) }
203
-
204
- fields = {}
205
-
206
- fields_array = reader.read(this_length).unpack(this_format)
207
- format_description.each_with_index do |field, index|
208
- fields[field[:name]] = fields_array[index]
209
- end
210
-
211
- fields
212
- end
213
-
214
197
  # Extract a number of sequential bits at a given offset within an integer.
215
198
  # This is used to unpack bit-packed fields.
216
199
  def extract_bits(value, bits, offset)
@@ -289,7 +272,8 @@ module MysqlBinlog
289
272
  convert_mysql_type_datetime(read_uint64)
290
273
  when :enum, :set
291
274
  read_uint_by_size(metadata[:size])
292
- #when :bit
275
+ when :bit
276
+ read_bit_array(metadata[:bits])
293
277
  #when :newdecimal
294
278
  end
295
279
  end
@@ -1,6 +1,9 @@
1
1
  module MysqlBinlog
2
2
  # Read a binary log from a file on disk.
3
3
  class BinlogFileReader
4
+ MAGIC_SIZE = 4
5
+ MAGIC_VALUE = 1852400382
6
+
4
7
  attr_accessor :tail
5
8
 
6
9
  def initialize(filename)
@@ -8,14 +11,18 @@ module MysqlBinlog
8
11
  open_file(filename)
9
12
  end
10
13
 
14
+ def verify_magic
15
+ if (magic = read(MAGIC_SIZE).unpack("V").first) != MAGIC_VALUE
16
+ raise MalformedBinlogException.new("Magic number #{magic} is incorrect")
17
+ end
18
+ end
19
+
11
20
  def open_file(filename)
12
21
  @dirname = File.dirname(filename)
13
22
  @filename = File.basename(filename)
14
23
  @binlog = File.open(filename, mode="r")
15
24
 
16
- if (magic = read(4).unpack("V").first) != 1852400382
17
- raise MalformedBinlogException.new("Magic number #{magic} is incorrect")
18
- end
25
+ verify_magic
19
26
  end
20
27
 
21
28
  def rotate(filename, position)
@@ -24,6 +31,9 @@ module MysqlBinlog
24
31
  open_file(@dirname + "/" + filename)
25
32
  seek(position)
26
33
  rescue Errno::ENOENT
34
+ # A rotate event will be seen in the previous log file before the
35
+ # new file exists. Retry a few times with a little sleep to give
36
+ # the server a chance to create the new file.
27
37
  if (retries -= 1) > 0
28
38
  sleep 0.01
29
39
  retry
@@ -42,7 +52,7 @@ module MysqlBinlog
42
52
  end
43
53
 
44
54
  def rewind
45
- @binlog.rewind
55
+ seek(MAGIC_SIZE)
46
56
  end
47
57
 
48
58
  def seek(pos)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysql_binlog
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 8
10
- version: 0.1.8
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jeremy Cole
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-07-02 00:00:00 Z
18
+ date: 2012-07-05 00:00:00 Z
19
19
  dependencies: []
20
20
 
21
21
  description: Library for parsing MySQL binary logs in Ruby