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.
data/lib/mysql_binlog/binlog.rb
CHANGED
@@ -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
|
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, #
|
59
|
-
:sql_mode, #
|
60
|
-
:catalog_deprecated, #
|
61
|
-
:auto_increment, #
|
62
|
-
:charset, #
|
63
|
-
:time_zone, #
|
64
|
-
:catalog, #
|
65
|
-
:lc_time_names, #
|
66
|
-
:charset_database, #
|
67
|
-
:table_map_for_update, #
|
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,
|
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 =
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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.
|
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
|
-
:
|
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.
|
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
|
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.
|
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
|
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("
|
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("
|
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("
|
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
|
184
|
-
# from a mapping table provided.
|
185
|
-
def
|
186
|
-
value =
|
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
|
-
|
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
|
-
|
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
|
-
|
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:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
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-
|
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
|