mysql_binlog 0.1.4 → 0.1.5
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.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require 'mysql_binlog/
|
2
|
-
require 'mysql_binlog/
|
3
|
-
require 'mysql_binlog/
|
1
|
+
require 'mysql_binlog/binlog'
|
2
|
+
require 'mysql_binlog/binlog_field_parser'
|
3
|
+
require 'mysql_binlog/binlog_event_parser'
|
4
4
|
require 'mysql_binlog/reader/debugging_reader'
|
5
5
|
require 'mysql_binlog/reader/binlog_file_reader'
|
6
6
|
require 'mysql_binlog/reader/binlog_stream_reader'
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module MysqlBinlog
|
2
|
+
class UnsupportedVersionException < Exception; end
|
3
|
+
class MalformedBinlogException < Exception; end
|
4
|
+
class ZeroReadException < Exception; end
|
5
|
+
class ShortReadException < Exception; end
|
6
|
+
|
7
|
+
class Binlog
|
8
|
+
attr_reader :fde
|
9
|
+
attr_accessor :reader
|
10
|
+
attr_accessor :field_parser
|
11
|
+
attr_accessor :event_parser
|
12
|
+
attr_accessor :filter_event_types
|
13
|
+
attr_accessor :filter_flags
|
14
|
+
attr_accessor :max_query_length
|
15
|
+
|
16
|
+
def initialize(reader)
|
17
|
+
@reader = reader
|
18
|
+
@field_parser = BinlogFieldParser.new(self)
|
19
|
+
@event_parser = BinlogEventParser.new(self)
|
20
|
+
@fde = nil
|
21
|
+
@filter_event_types = nil
|
22
|
+
@filter_flags = nil
|
23
|
+
@max_query_length = 1048576
|
24
|
+
end
|
25
|
+
|
26
|
+
# Rewind to the beginning of the log, if supported by the reader. The
|
27
|
+
# reader may throw an exception if rewinding is not supported (e.g. for
|
28
|
+
# a stream-based reader).
|
29
|
+
def rewind
|
30
|
+
reader.rewind
|
31
|
+
end
|
32
|
+
|
33
|
+
# Skip the remainder of this event. This can be used to skip an entire
|
34
|
+
# event or merely the parts of the event this library does not understand.
|
35
|
+
def skip_event(header)
|
36
|
+
reader.skip(header)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Read the content of the event, which follows the header.
|
40
|
+
def read_event_fields(header)
|
41
|
+
event_type = EVENT_TYPES[header[:event_type]]
|
42
|
+
|
43
|
+
# Delegate the parsing of the event content to a method of the same name
|
44
|
+
# in BinlogEventParser.
|
45
|
+
if event_parser.methods.include? event_type.to_s
|
46
|
+
fields = event_parser.send(event_type, header)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Anything left unread at this point is skipped based on the event length
|
50
|
+
# provided in the header. In this way, it is possible to skip over events
|
51
|
+
# that are not able to be parsed correctly by this library.
|
52
|
+
skip_event(header)
|
53
|
+
|
54
|
+
fields
|
55
|
+
end
|
56
|
+
|
57
|
+
# Scan events until finding one that isn't rejected by the filter rules.
|
58
|
+
# If there are no filter rules, this will return the next event provided
|
59
|
+
# by the reader.
|
60
|
+
def read_event
|
61
|
+
while true
|
62
|
+
skip_this_event = false
|
63
|
+
return nil if reader.end?
|
64
|
+
|
65
|
+
filename = reader.filename
|
66
|
+
position = reader.position
|
67
|
+
|
68
|
+
# Read the common header for an event. Every event has a header.
|
69
|
+
unless header = event_parser.event_header
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
|
73
|
+
event_type = EVENT_TYPES[header[:event_type]]
|
74
|
+
|
75
|
+
if @filter_event_types
|
76
|
+
unless @filter_event_types.include? event_type
|
77
|
+
skip_this_event = true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if @filter_flags
|
82
|
+
unless @filter_flags.include? header[:flags]
|
83
|
+
skip_this_event = true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Never skip over rotate_event or format_description_event as they
|
88
|
+
# are critical to understanding the format of this event stream.
|
89
|
+
if skip_this_event
|
90
|
+
unless [:rotate_event, :format_description_event].include? event_type
|
91
|
+
skip_event(header)
|
92
|
+
next
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
fields = read_event_fields(header)
|
97
|
+
|
98
|
+
case event_type
|
99
|
+
when :rotate_event
|
100
|
+
reader.rotate(fields[:name], fields[:pos])
|
101
|
+
when :format_description_event
|
102
|
+
process_fde(fields)
|
103
|
+
end
|
104
|
+
|
105
|
+
break
|
106
|
+
end
|
107
|
+
|
108
|
+
{
|
109
|
+
:type => event_type,
|
110
|
+
:filename => filename,
|
111
|
+
:position => position,
|
112
|
+
:header => header,
|
113
|
+
:event => fields,
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
# Process a format description event, which describes the version of this
|
118
|
+
# file, and the format of events which will appear in this file. This also
|
119
|
+
# provides the version of the MySQL server which generated this file.
|
120
|
+
def process_fde(fde)
|
121
|
+
if (version = fde[:binlog_version]) != 4
|
122
|
+
raise UnsupportedVersionException.new("Binlog version #{version} is not supported")
|
123
|
+
end
|
124
|
+
|
125
|
+
# Save the interesting fields from an FDE so that this information is
|
126
|
+
# available at any time later.
|
127
|
+
@fde = {
|
128
|
+
:header_length => fde[:header_length],
|
129
|
+
:binlog_version => fde[:binlog_version],
|
130
|
+
:server_version => fde[:server_version],
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Iterate through all events.
|
135
|
+
def each_event
|
136
|
+
while event = read_event
|
137
|
+
yield event
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -1,4 +1,57 @@
|
|
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 binlogs. 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
|
+
# An array to quickly map an integer event type to its symbol.
|
24
|
+
EVENT_TYPES = [
|
25
|
+
:unknown_event, # 0
|
26
|
+
:start_event_v3, # 1
|
27
|
+
:query_event, # 2
|
28
|
+
:stop_event, # 3
|
29
|
+
:rotate_event, # 4
|
30
|
+
:intvar_event, # 5
|
31
|
+
:load_event, # 6
|
32
|
+
:slave_event, # 7
|
33
|
+
:create_file_event, # 8
|
34
|
+
:append_block_event, # 9
|
35
|
+
:exec_load_event, # 10
|
36
|
+
:delete_file_event, # 11
|
37
|
+
:new_load_event, # 12
|
38
|
+
:rand_event, # 13
|
39
|
+
:user_var_event, # 14
|
40
|
+
:format_description_event, # 15
|
41
|
+
:xid_event, # 16
|
42
|
+
:begin_load_query_event, # 17
|
43
|
+
:execute_load_query_event, # 18
|
44
|
+
: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
|
48
|
+
:write_rows_event, # 23
|
49
|
+
:update_rows_event, # 24
|
50
|
+
:delete_rows_event, # 25
|
51
|
+
:incident_event, # 26
|
52
|
+
:heartbeat_log_event, # 27
|
53
|
+
]
|
54
|
+
|
2
55
|
# A mapping array for all values that may appear in the +status+ field of
|
3
56
|
# a query_event.
|
4
57
|
QUERY_EVENT_STATUS_TYPES = [
|
@@ -23,7 +76,13 @@ module MysqlBinlog
|
|
23
76
|
:relaxed_unique_checks => 1 << 27,
|
24
77
|
}
|
25
78
|
|
26
|
-
|
79
|
+
INTVAR_EVENT_INTVAR_TYPES = [
|
80
|
+
nil,
|
81
|
+
:last_insert_id,
|
82
|
+
:insert_id,
|
83
|
+
]
|
84
|
+
|
85
|
+
class BinlogEventParser
|
27
86
|
attr_accessor :binlog
|
28
87
|
attr_accessor :reader
|
29
88
|
attr_accessor :parser
|
@@ -31,14 +90,46 @@ module MysqlBinlog
|
|
31
90
|
def initialize(binlog_instance)
|
32
91
|
@binlog = binlog_instance
|
33
92
|
@reader = binlog_instance.reader
|
34
|
-
@parser = binlog_instance.
|
93
|
+
@parser = binlog_instance.field_parser
|
35
94
|
@table_map = {}
|
36
95
|
end
|
37
96
|
|
38
|
-
# Parse
|
39
|
-
def
|
97
|
+
# Parse an event header, described by the EVENT_HEADER structure above.
|
98
|
+
def event_header
|
99
|
+
header = parser.read_and_unpack(EVENT_HEADER)
|
100
|
+
|
101
|
+
# Merge the read 'flags' bitmap with the EVENT_HEADER_FLAGS hash to return
|
102
|
+
# the flags by name instead of returning the bitmap as an integer.
|
103
|
+
flags = EVENT_HEADER_FLAGS.inject([]) do |result, (flag_name, flag_bit_value)|
|
104
|
+
if (header[:flags] & flag_bit_value) != 0
|
105
|
+
result << flag_name
|
106
|
+
end
|
107
|
+
result
|
108
|
+
end
|
109
|
+
|
110
|
+
# Overwrite the integer version of 'flags' with the array of names.
|
111
|
+
header[:flags] = flags
|
112
|
+
|
113
|
+
header
|
114
|
+
end
|
115
|
+
|
116
|
+
# Parse fields for a +Format_description+ event.
|
117
|
+
def format_description_event(header)
|
118
|
+
fields = {}
|
119
|
+
fields[:binlog_version] = parser.read_uint16
|
120
|
+
fields[:server_version] = parser.read_nstringz(50)
|
121
|
+
fields[:create_timestamp] = parser.read_uint32
|
122
|
+
fields[:header_length] = parser.read_uint8
|
123
|
+
fields
|
124
|
+
end
|
125
|
+
|
126
|
+
# Parse fields for a +Rotate+ event.
|
127
|
+
def rotate_event(header)
|
128
|
+
fields = {}
|
129
|
+
fields[:pos] = parser.read_uint64
|
40
130
|
name_length = reader.remaining(header)
|
41
|
-
fields[:name] =
|
131
|
+
fields[:name] = parser.read_nstring(name_length)
|
132
|
+
fields
|
42
133
|
end
|
43
134
|
|
44
135
|
# Parse a dynamic +status+ structure within a query_event, which consists
|
@@ -86,25 +177,44 @@ module MysqlBinlog
|
|
86
177
|
status
|
87
178
|
end
|
88
179
|
|
89
|
-
# Parse
|
90
|
-
def query_event(header
|
180
|
+
# Parse fields for a +Query+ event.
|
181
|
+
def query_event(header)
|
182
|
+
fields = {}
|
183
|
+
fields[:thread_id] = parser.read_uint32
|
184
|
+
fields[:elapsed_time] = parser.read_uint32
|
185
|
+
db_length = parser.read_uint8
|
186
|
+
fields[:error_code] = parser.read_uint16
|
91
187
|
fields[:status] = _query_event_status(header, fields)
|
92
|
-
fields[:db] = parser.read_nstringz(
|
93
|
-
fields.delete :db_length
|
188
|
+
fields[:db] = parser.read_nstringz(db_length + 1)
|
94
189
|
query_length = reader.remaining(header)
|
95
190
|
fields[:query] = reader.read([query_length, binlog.max_query_length].min)
|
191
|
+
fields
|
96
192
|
end
|
97
193
|
|
98
|
-
# Parse
|
99
|
-
def intvar_event(header
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
194
|
+
# Parse fields for an +Intvar+ event.
|
195
|
+
def intvar_event(header)
|
196
|
+
fields = {}
|
197
|
+
|
198
|
+
fields[:intvar_type] = parser.read_uint8
|
199
|
+
fields[:intvar_name] = INTVAR_EVENT_INTVAR_TYPES[fields[:intvar_type]]
|
200
|
+
fields[:intvar_value] = parser.read_uint64
|
201
|
+
|
202
|
+
fields
|
203
|
+
end
|
204
|
+
|
205
|
+
# Parse fields for an +Xid+ event.
|
206
|
+
def xid_event(header)
|
207
|
+
fields = {}
|
208
|
+
fields[:xid] = parser.read_uint64
|
209
|
+
fields
|
210
|
+
end
|
211
|
+
|
212
|
+
# Parse fields for an +Rand+ event.
|
213
|
+
def rand_event(header)
|
214
|
+
fields = {}
|
215
|
+
fields[:seed1] = parser.read_uint64
|
216
|
+
fields[:seed2] = parser.read_uint64
|
217
|
+
fields
|
108
218
|
end
|
109
219
|
|
110
220
|
# Parse column metadata within a table map event.
|
@@ -115,8 +225,9 @@ module MysqlBinlog
|
|
115
225
|
end
|
116
226
|
end
|
117
227
|
|
118
|
-
# Parse
|
119
|
-
def table_map_event(header
|
228
|
+
# Parse fields for a +Table_map+ event.
|
229
|
+
def table_map_event(header)
|
230
|
+
fields = {}
|
120
231
|
fields[:table_id] = parser.read_uint48
|
121
232
|
fields[:flags] = parser.read_uint16
|
122
233
|
map_entry = @table_map[fields[:table_id]] = {}
|
@@ -136,6 +247,7 @@ module MysqlBinlog
|
|
136
247
|
end
|
137
248
|
|
138
249
|
fields[:map_entry] = map_entry
|
250
|
+
fields
|
139
251
|
end
|
140
252
|
|
141
253
|
# Parse a single row image, which is comprised of a series of columns. Not
|
@@ -180,11 +292,12 @@ module MysqlBinlog
|
|
180
292
|
row_images
|
181
293
|
end
|
182
294
|
|
183
|
-
# Parse
|
295
|
+
# Parse fields for any of the row-based replication row events:
|
184
296
|
# * +Write_rows+ which is used for +INSERT+.
|
185
297
|
# * +Update_rows+ which is used for +UPDATE+.
|
186
298
|
# * +Delete_rows+ which is used for +DELETE+.
|
187
|
-
def generic_rows_event(header
|
299
|
+
def generic_rows_event(header)
|
300
|
+
fields = {}
|
188
301
|
table_id = parser.read_uint48
|
189
302
|
fields[:table] = @table_map[table_id]
|
190
303
|
fields[:flags] = parser.read_uint16
|
@@ -200,7 +313,9 @@ module MysqlBinlog
|
|
200
313
|
columns_used[:after] = parser.read_bit_array(columns)
|
201
314
|
end
|
202
315
|
fields[:row_image] = _generic_rows_event_row_images(header, fields, columns_used)
|
316
|
+
fields
|
203
317
|
end
|
318
|
+
|
204
319
|
alias :write_rows_event :generic_rows_event
|
205
320
|
alias :update_rows_event :generic_rows_event
|
206
321
|
alias :delete_rows_event :generic_rows_event
|
@@ -36,7 +36,7 @@ module MysqlBinlog
|
|
36
36
|
type_array
|
37
37
|
end
|
38
38
|
|
39
|
-
class
|
39
|
+
class BinlogFieldParser
|
40
40
|
attr_accessor :binlog
|
41
41
|
attr_accessor :reader
|
42
42
|
|
@@ -120,11 +120,9 @@ module MysqlBinlog
|
|
120
120
|
reader.read(length)
|
121
121
|
end
|
122
122
|
|
123
|
-
# Read a null-terminated string, provided its length (
|
123
|
+
# Read a null-terminated string, provided its length (with the null).
|
124
124
|
def read_nstringz(length)
|
125
|
-
|
126
|
-
reader.read(1) # null
|
127
|
-
string
|
125
|
+
reader.read(length).unpack("A*").first
|
128
126
|
end
|
129
127
|
|
130
128
|
# Read a (Pascal-style) length-prefixed string. The length is stored as a
|
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: 17
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 5
|
10
|
+
version: 0.1.5
|
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-06-
|
18
|
+
date: 2012-06-28 00:00:00 Z
|
19
19
|
dependencies: []
|
20
20
|
|
21
21
|
description: Library for parsing MySQL binary logs in Ruby
|
@@ -28,9 +28,9 @@ extra_rdoc_files: []
|
|
28
28
|
|
29
29
|
files:
|
30
30
|
- lib/mysql_binlog.rb
|
31
|
-
- lib/mysql_binlog/
|
32
|
-
- lib/mysql_binlog/
|
33
|
-
- lib/mysql_binlog/
|
31
|
+
- lib/mysql_binlog/binlog.rb
|
32
|
+
- lib/mysql_binlog/binlog_field_parser.rb
|
33
|
+
- lib/mysql_binlog/binlog_event_parser.rb
|
34
34
|
- lib/mysql_binlog/reader/binlog_file_reader.rb
|
35
35
|
- lib/mysql_binlog/reader/binlog_stream_reader.rb
|
36
36
|
- bin/mysql_binlog_dump
|
@@ -1,262 +0,0 @@
|
|
1
|
-
module MysqlBinlog
|
2
|
-
# An array to quickly map an integer event type to its symbol.
|
3
|
-
EVENT_TYPES = [
|
4
|
-
:unknown_event, # 0
|
5
|
-
:start_event_v3, # 1
|
6
|
-
:query_event, # 2
|
7
|
-
:stop_event, # 3
|
8
|
-
:rotate_event, # 4
|
9
|
-
:intvar_event, # 5
|
10
|
-
:load_event, # 6
|
11
|
-
:slave_event, # 7
|
12
|
-
:create_file_event, # 8
|
13
|
-
:append_block_event, # 9
|
14
|
-
:exec_load_event, # 10
|
15
|
-
:delete_file_event, # 11
|
16
|
-
:new_load_event, # 12
|
17
|
-
:rand_event, # 13
|
18
|
-
:user_var_event, # 14
|
19
|
-
:format_description_event, # 15
|
20
|
-
:xid_event, # 16
|
21
|
-
:begin_load_query_event, # 17
|
22
|
-
:execute_load_query_event, # 18
|
23
|
-
:table_map_event, # 19
|
24
|
-
:pre_ga_write_rows_event, # 20
|
25
|
-
:pre_ga_update_rows_event, # 21
|
26
|
-
:pre_ga_delete_rows_event, # 22
|
27
|
-
:write_rows_event, # 23
|
28
|
-
:update_rows_event, # 24
|
29
|
-
:delete_rows_event, # 25
|
30
|
-
:incident_event, # 26
|
31
|
-
:heartbeat_log_event, # 27
|
32
|
-
]
|
33
|
-
|
34
|
-
# A common fixed-length header that is included with each event.
|
35
|
-
EVENT_HEADER = [
|
36
|
-
{ :name => :timestamp, :length => 4, :format => "V" },
|
37
|
-
{ :name => :event_type, :length => 1, :format => "C" },
|
38
|
-
{ :name => :server_id, :length => 4, :format => "V" },
|
39
|
-
{ :name => :event_length, :length => 4, :format => "V" },
|
40
|
-
{ :name => :next_position, :length => 4, :format => "V" },
|
41
|
-
{ :name => :flags, :length => 2, :format => "v" },
|
42
|
-
]
|
43
|
-
|
44
|
-
# Values for the 'flags' field that may appear in binlogs. There are
|
45
|
-
# several other values that never appear in a file but may be used
|
46
|
-
# in events in memory.
|
47
|
-
EVENT_FLAGS = {
|
48
|
-
:binlog_in_use => 0x01,
|
49
|
-
:thread_specific => 0x04,
|
50
|
-
:suppress_use => 0x08,
|
51
|
-
:artificial => 0x20,
|
52
|
-
:relay_log => 0x40,
|
53
|
-
}
|
54
|
-
|
55
|
-
# Format descriptions for fixed-length fields that may appear in the data
|
56
|
-
# for each event. Additional fields may be dynamic and are parsed by the
|
57
|
-
# methods in the BinlogEventFieldParser class.
|
58
|
-
EVENT_FORMATS = {
|
59
|
-
:format_description_event => [
|
60
|
-
{ :name => :binlog_version, :length => 2, :format => "v" },
|
61
|
-
{ :name => :server_version, :length => 50, :format => "A50" },
|
62
|
-
{ :name => :create_timestamp, :length => 4, :format => "V" },
|
63
|
-
{ :name => :header_length, :length => 1, :format => "C" },
|
64
|
-
],
|
65
|
-
:rotate_event => [
|
66
|
-
{ :name => :pos, :length => 8, :format => "Q" },
|
67
|
-
],
|
68
|
-
:query_event => [
|
69
|
-
{ :name => :thread_id, :length => 4, :format => "V" },
|
70
|
-
{ :name => :elapsed_time, :length => 4, :format => "V" },
|
71
|
-
{ :name => :db_length, :length => 1, :format => "C" },
|
72
|
-
{ :name => :error_code, :length => 2, :format => "v" },
|
73
|
-
],
|
74
|
-
:intvar_event => [
|
75
|
-
{ :name => :intvar_type, :length => 1, :format => "C" },
|
76
|
-
{ :name => :intvar_value, :length => 8, :format => "Q" },
|
77
|
-
],
|
78
|
-
:xid_event => [
|
79
|
-
{ :name => :xid, :length => 8, :format => "Q" },
|
80
|
-
],
|
81
|
-
:rand_event => [ # Untested
|
82
|
-
{ :name => :seed1, :length => 8, :format => "Q" },
|
83
|
-
{ :name => :seed2, :length => 8, :format => "Q" },
|
84
|
-
],
|
85
|
-
}
|
86
|
-
|
87
|
-
class UnsupportedVersionException < Exception; end
|
88
|
-
class MalformedBinlogException < Exception; end
|
89
|
-
class ZeroReadException < Exception; end
|
90
|
-
class ShortReadException < Exception; end
|
91
|
-
|
92
|
-
class Binlog
|
93
|
-
attr_reader :fde
|
94
|
-
attr_accessor :reader
|
95
|
-
attr_accessor :parser
|
96
|
-
attr_accessor :event_field_parser
|
97
|
-
attr_accessor :filter_event_types
|
98
|
-
attr_accessor :filter_flags
|
99
|
-
attr_accessor :max_query_length
|
100
|
-
|
101
|
-
def initialize(reader)
|
102
|
-
@reader = reader
|
103
|
-
@parser = BinlogParser.new(self)
|
104
|
-
@event_field_parser = BinlogEventFieldParser.new(self)
|
105
|
-
@fde = nil
|
106
|
-
@filter_event_types = nil
|
107
|
-
@filter_flags = nil
|
108
|
-
@max_query_length = 1048576
|
109
|
-
end
|
110
|
-
|
111
|
-
# Rewind to the beginning of the log, if supported by the reader. The
|
112
|
-
# reader may throw an exception if rewinding is not supported (e.g. for
|
113
|
-
# a stream-based reader).
|
114
|
-
def rewind
|
115
|
-
reader.rewind
|
116
|
-
end
|
117
|
-
|
118
|
-
# Read fixed fields using format definitions in 'unpack' format provided
|
119
|
-
# in the EVENT_FORMATS hash.
|
120
|
-
def read_fixed_fields(event_type, header)
|
121
|
-
if EVENT_FORMATS.include? event_type
|
122
|
-
parser.read_and_unpack(EVENT_FORMATS[event_type])
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
# Read dynamic fields or fields that require more processing before being
|
127
|
-
# saved in the event.
|
128
|
-
def read_additional_fields(event_type, header, fields)
|
129
|
-
if event_field_parser.methods.include? event_type.to_s
|
130
|
-
event_field_parser.send(event_type, header, fields)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# Skip the remainder of this event. This can be used to skip an entire
|
135
|
-
# event or merely the parts of the event this library does not understand.
|
136
|
-
def skip_event(header)
|
137
|
-
reader.skip(header)
|
138
|
-
end
|
139
|
-
|
140
|
-
# Read the common header for an event. Every event has a header.
|
141
|
-
def read_event_header
|
142
|
-
header = parser.read_and_unpack(EVENT_HEADER)
|
143
|
-
|
144
|
-
# Merge the read 'flags' bitmap with the EVENT_FLAGS hash to return
|
145
|
-
# the flags by name instead of returning the bitmap as an integer.
|
146
|
-
flags = EVENT_FLAGS.inject([]) do |result, (flag_name, flag_bit_value)|
|
147
|
-
if (header[:flags] & flag_bit_value) != 0
|
148
|
-
result << flag_name
|
149
|
-
end
|
150
|
-
result
|
151
|
-
end
|
152
|
-
|
153
|
-
# Overwrite the integer version of 'flags' with the array of names.
|
154
|
-
header[:flags] = flags
|
155
|
-
|
156
|
-
header
|
157
|
-
end
|
158
|
-
|
159
|
-
# Read the content of the event, which consists of an optional fixed field
|
160
|
-
# portion and an optional dynamic portion.
|
161
|
-
def read_event_content(header)
|
162
|
-
event_type = EVENT_TYPES[header[:event_type]]
|
163
|
-
|
164
|
-
# Read the fixed portion of the event, if it is understood, or there is
|
165
|
-
# one. If not, initialize content with an empty hash instead.
|
166
|
-
content = read_fixed_fields(event_type, header) || {}
|
167
|
-
|
168
|
-
# Read additional fields from the dynamic portion of the event. Some of
|
169
|
-
# these may actually be fixed width but needed more processing in a
|
170
|
-
# function instead of the unpack formats possible in read_fixed_fields.
|
171
|
-
read_additional_fields(event_type, header, content)
|
172
|
-
|
173
|
-
# Anything left unread at this point is skipped based on the event length
|
174
|
-
# provided in the header. In this way, it is possible to skip over events
|
175
|
-
# that are not able to be parsed correctly by this library.
|
176
|
-
skip_event(header)
|
177
|
-
|
178
|
-
content
|
179
|
-
end
|
180
|
-
|
181
|
-
# Scan events until finding one that isn't rejected by the filter rules.
|
182
|
-
# If there are no filter rules, this will return the next event provided
|
183
|
-
# by the reader.
|
184
|
-
def read_event
|
185
|
-
while true
|
186
|
-
skip_this_event = false
|
187
|
-
return nil if reader.end?
|
188
|
-
|
189
|
-
filename = reader.filename
|
190
|
-
position = reader.position
|
191
|
-
|
192
|
-
unless header = read_event_header
|
193
|
-
return nil
|
194
|
-
end
|
195
|
-
|
196
|
-
event_type = EVENT_TYPES[header[:event_type]]
|
197
|
-
|
198
|
-
if @filter_event_types
|
199
|
-
unless @filter_event_types.include? event_type or
|
200
|
-
event_type == :format_description_event
|
201
|
-
skip_event(header)
|
202
|
-
skip_this_event = true
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
if @filter_flags
|
207
|
-
unless @filter_flags.include? header[:flags]
|
208
|
-
skip_event(header)
|
209
|
-
skip_this_event = true
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
# Never skip over rotate_event or format_description_event as they
|
214
|
-
# are critical to understanding the format of this event stream.
|
215
|
-
unless [:rotate_event, :format_description_event].include? event_type
|
216
|
-
next if skip_this_event
|
217
|
-
end
|
218
|
-
|
219
|
-
content = read_event_content(header)
|
220
|
-
|
221
|
-
case event_type
|
222
|
-
when :rotate_event
|
223
|
-
reader.rotate(content[:name], content[:pos])
|
224
|
-
when :format_description_event
|
225
|
-
process_fde(content)
|
226
|
-
end
|
227
|
-
|
228
|
-
break
|
229
|
-
end
|
230
|
-
|
231
|
-
{
|
232
|
-
:type => event_type,
|
233
|
-
:filename => filename,
|
234
|
-
:position => position,
|
235
|
-
:header => header,
|
236
|
-
:event => content,
|
237
|
-
}
|
238
|
-
end
|
239
|
-
|
240
|
-
# Process a format description event, which describes the version of this
|
241
|
-
# file, and the format of events which will appear in this file. This also
|
242
|
-
# provides the version of the MySQL server which generated this file.
|
243
|
-
def process_fde(fde)
|
244
|
-
if (version = fde[:binlog_version]) != 4
|
245
|
-
raise UnsupportedVersionException.new("Binlog version #{version} is not supported")
|
246
|
-
end
|
247
|
-
|
248
|
-
@fde = {
|
249
|
-
:header_length => fde[:header_length],
|
250
|
-
:binlog_version => fde[:binlog_version],
|
251
|
-
:server_version => fde[:server_version],
|
252
|
-
}
|
253
|
-
end
|
254
|
-
|
255
|
-
# Iterate through all events.
|
256
|
-
def each_event
|
257
|
-
while event = read_event
|
258
|
-
yield event
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
end
|