mysql_binlog 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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