mysql_binlog 0.3.2 → 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/mysql_binlog_dump +28 -31
- data/bin/mysql_binlog_summary +241 -0
- data/lib/mysql_binlog/binlog.rb +34 -1
- data/lib/mysql_binlog/binlog_event_parser.rb +199 -29
- data/lib/mysql_binlog/binlog_field_parser.rb +85 -7
- data/lib/mysql_binlog/reader/binlog_file_reader.rb +2 -2
- data/lib/mysql_binlog/version.rb +1 -1
- metadata +24 -44
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 68751dcb93a315960a1ea2ec299a9f26eef45678d58828ccd423c4140bbb6636
|
4
|
+
data.tar.gz: 2ccb823feaf7f0bc50912ecfb9e08c4f8bb44a14dd4e72c88acb60485ad2a2f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 50d4c8cb1385c63e6a74ab825f71691cbefbbe66c73786462d690750bd64c8d919c63aa5d3c564c8b8d46d10741a82abe754f482e77416b3ff5c7e1f368abd41
|
7
|
+
data.tar.gz: 381f7c4302b0a8d0964e4fcd1b3bcf034ca4401bd6fc68e78768ae7a53931a70b36d7ff2486f72464fd6d5598c0b2723ee375ff762b76efb8647776855cb1c4c
|
data/bin/mysql_binlog_dump
CHANGED
@@ -6,8 +6,6 @@ require 'getoptlong'
|
|
6
6
|
require 'ostruct'
|
7
7
|
require 'pp'
|
8
8
|
|
9
|
-
include MysqlBinlog
|
10
|
-
|
11
9
|
def usage(exit_code, message = nil)
|
12
10
|
print "Error: #{message}\n\n" unless message.nil?
|
13
11
|
|
@@ -15,13 +13,16 @@ def usage(exit_code, message = nil)
|
|
15
13
|
|
16
14
|
Usage:
|
17
15
|
To read from a binary log file on disk:
|
18
|
-
mysql_binlog_dump [options]
|
16
|
+
mysql_binlog_dump [options] <filename(s)>
|
19
17
|
|
20
18
|
--help, -?
|
21
19
|
Show this help.
|
22
20
|
|
23
21
|
--file, -f <filename>
|
24
|
-
Read from a binary log file on disk.
|
22
|
+
Read from a binary log file on disk (deprecated).
|
23
|
+
|
24
|
+
--checksum, -c
|
25
|
+
Enable CRC32 checksums.
|
25
26
|
|
26
27
|
--debug, -d
|
27
28
|
Debug reading from the binary log, showing calls into the reader and the
|
@@ -45,13 +46,16 @@ end
|
|
45
46
|
|
46
47
|
@options = OpenStruct.new
|
47
48
|
@options.file = nil
|
49
|
+
@options.checksum = nil
|
48
50
|
@options.debug = false
|
49
51
|
@options.tail = false
|
50
52
|
@options.rotate = false
|
53
|
+
@options.filenames = []
|
51
54
|
|
52
55
|
getopt_options = [
|
53
56
|
[ "--help", "-?", GetoptLong::NO_ARGUMENT ],
|
54
57
|
[ "--file", "-f", GetoptLong::REQUIRED_ARGUMENT ],
|
58
|
+
[ "--checksum", "-c", GetoptLong::NO_ARGUMENT ],
|
55
59
|
[ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
|
56
60
|
[ "--tail", "-t", GetoptLong::NO_ARGUMENT ],
|
57
61
|
[ "--rotate", "-r", GetoptLong::NO_ARGUMENT ],
|
@@ -64,7 +68,9 @@ getopt.each do |opt, arg|
|
|
64
68
|
when "--help"
|
65
69
|
usage 0
|
66
70
|
when "--file"
|
67
|
-
@options.
|
71
|
+
@options.filenames << arg
|
72
|
+
when "--checksum"
|
73
|
+
@options.checksum = :crc32
|
68
74
|
when "--debug"
|
69
75
|
@options.debug = true
|
70
76
|
when "--tail"
|
@@ -74,34 +80,25 @@ getopt.each do |opt, arg|
|
|
74
80
|
end
|
75
81
|
end
|
76
82
|
|
77
|
-
|
78
|
-
usage 1, "A file must be provided with --file/-f"
|
79
|
-
end
|
83
|
+
@options.filenames.concat(ARGV)
|
80
84
|
|
81
|
-
|
82
|
-
|
83
|
-
reader = DebuggingReader.new(reader, :data => true, :calls => true)
|
85
|
+
if @options.filenames.empty?
|
86
|
+
usage 1, "One or more filenames must be provided"
|
84
87
|
end
|
85
|
-
binlog = Binlog.new(reader)
|
86
88
|
|
87
|
-
|
88
|
-
reader
|
89
|
-
|
90
|
-
|
91
|
-
end
|
89
|
+
@options.filenames.each do |filename|
|
90
|
+
reader = MysqlBinlog::BinlogFileReader.new(filename)
|
91
|
+
if @options.debug
|
92
|
+
reader = MysqlBinlog::DebuggingReader.new(reader, :data => true, :calls => true)
|
93
|
+
end
|
94
|
+
binlog = MysqlBinlog::Binlog.new(reader)
|
95
|
+
binlog.checksum = @options.checksum
|
92
96
|
|
93
|
-
|
94
|
-
binlog.ignore_rotate =
|
95
|
-
else
|
96
|
-
binlog.ignore_rotate = true
|
97
|
-
end
|
97
|
+
reader.tail = @options.tail
|
98
|
+
binlog.ignore_rotate = !@options.rotate
|
98
99
|
|
99
|
-
binlog.each_event do |event|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
]
|
105
|
-
#pp event
|
106
|
-
#puts
|
107
|
-
end
|
100
|
+
binlog.each_event do |event|
|
101
|
+
pp event
|
102
|
+
puts
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mysql_binlog'
|
4
|
+
require 'getoptlong'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
def usage(exit_code, message = nil)
|
8
|
+
print "Error: #{message}\n\n" unless message.nil?
|
9
|
+
|
10
|
+
print <<'END_OF_USAGE'
|
11
|
+
|
12
|
+
Usage:
|
13
|
+
To read from a binary log file on disk:
|
14
|
+
mysql_binlog_summary [options] <filename(s)>
|
15
|
+
|
16
|
+
--help, -?
|
17
|
+
Show this help.
|
18
|
+
|
19
|
+
--file, -f <filename>
|
20
|
+
Read from a binary log file on disk (deprecated).
|
21
|
+
|
22
|
+
--checksum, -c
|
23
|
+
Enable CRC32 checksums.
|
24
|
+
|
25
|
+
--tail, -t
|
26
|
+
When reading from a file, follow the end of the binary log file instead
|
27
|
+
of exiting when reaching the end. Exit with Control-C.
|
28
|
+
|
29
|
+
--rotate, -r
|
30
|
+
When reading from a file, follow the rotate events which may be at the
|
31
|
+
end of a file (due to log rotation) so that the stream can be followed
|
32
|
+
through multiple files. This is especially useful with --tail.
|
33
|
+
|
34
|
+
END_OF_USAGE
|
35
|
+
|
36
|
+
exit exit_code
|
37
|
+
end
|
38
|
+
|
39
|
+
@options = OpenStruct.new
|
40
|
+
@options.tail = false
|
41
|
+
@options.rotate = false
|
42
|
+
@options.checksum = nil
|
43
|
+
@options.filenames = []
|
44
|
+
|
45
|
+
getopt_options = [
|
46
|
+
[ "--help", "-?", GetoptLong::NO_ARGUMENT ],
|
47
|
+
[ "--file", "-f", GetoptLong::REQUIRED_ARGUMENT ],
|
48
|
+
[ "--tail", "-t", GetoptLong::NO_ARGUMENT ],
|
49
|
+
[ "--rotate", "-r", GetoptLong::NO_ARGUMENT ],
|
50
|
+
[ "--checksum", "-c", GetoptLong::NO_ARGUMENT ],
|
51
|
+
]
|
52
|
+
|
53
|
+
getopt = GetoptLong.new(*getopt_options)
|
54
|
+
|
55
|
+
getopt.each do |opt, arg|
|
56
|
+
case opt
|
57
|
+
when "--help"
|
58
|
+
usage 0
|
59
|
+
when "--file"
|
60
|
+
@options.filenames << arg
|
61
|
+
when "--tail"
|
62
|
+
@options.tail = true
|
63
|
+
when "--rotate"
|
64
|
+
@options.rotate = true
|
65
|
+
when "--checksum"
|
66
|
+
@options.checksum = :crc32
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@options.filenames.concat(ARGV)
|
71
|
+
|
72
|
+
if @options.filenames.empty?
|
73
|
+
usage 1, "A file must be provided"
|
74
|
+
end
|
75
|
+
|
76
|
+
files = {}
|
77
|
+
min_timestamp = nil
|
78
|
+
max_timestamp = nil
|
79
|
+
events = []
|
80
|
+
events_processed = 0
|
81
|
+
|
82
|
+
@options.filenames.each do |filename|
|
83
|
+
reader = MysqlBinlog::BinlogFileReader.new(filename)
|
84
|
+
binlog = MysqlBinlog::Binlog.new(reader)
|
85
|
+
reader.tail = @options.tail
|
86
|
+
binlog.ignore_rotate = !@options.rotate
|
87
|
+
binlog.checksum = @options.checksum
|
88
|
+
|
89
|
+
file_min_timestamp = nil
|
90
|
+
file_max_timestamp = nil
|
91
|
+
file_events_processed = 0
|
92
|
+
|
93
|
+
#binlog.filter_event_types = [:query_event]
|
94
|
+
#binlog.filter_flags = [0]
|
95
|
+
query_pattern = /^(INSERT|UPDATE|DELETE)\s+(?:(?:INTO|FROM)\s+)?[`]?(\S+?)[`]?\s+/i
|
96
|
+
|
97
|
+
binlog.each_event do |event|
|
98
|
+
verb = nil
|
99
|
+
table = nil
|
100
|
+
|
101
|
+
if event[:type] == :query_event
|
102
|
+
if match_query = event[:event][:query].match(query_pattern)
|
103
|
+
verb = match_query[1].downcase
|
104
|
+
table = match_query[2]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if MysqlBinlog::ROW_EVENT_TYPES.include? event[:type]
|
109
|
+
verb = event[:type].to_s.sub(/_event_v[12]/, '')
|
110
|
+
table = event[:event][:table][:table]
|
111
|
+
end
|
112
|
+
|
113
|
+
timestamp = event[:header][:timestamp]
|
114
|
+
|
115
|
+
file_min_timestamp = [file_min_timestamp || timestamp, timestamp].min
|
116
|
+
file_max_timestamp = [file_max_timestamp || timestamp, timestamp].max
|
117
|
+
|
118
|
+
net_change = 0
|
119
|
+
event[:event][:row_image]&.each do |row_image|
|
120
|
+
case verb
|
121
|
+
when "delete_rows"
|
122
|
+
net_change -= row_image[:before][:size]
|
123
|
+
when "update_rows"
|
124
|
+
net_change += row_image[:after][:size] - row_image[:before][:size]
|
125
|
+
when "write_rows"
|
126
|
+
net_change += row_image[:after][:size]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
events << {
|
131
|
+
timestamp: timestamp,
|
132
|
+
size: event[:header][:payload_length],
|
133
|
+
type: event[:type],
|
134
|
+
verb: verb,
|
135
|
+
table: table,
|
136
|
+
net_change: net_change,
|
137
|
+
}
|
138
|
+
|
139
|
+
file_events_processed += 1
|
140
|
+
events_processed += 1
|
141
|
+
|
142
|
+
if (file_events_processed % 1000) == 0
|
143
|
+
puts "%-32s %6d MiB %10d %10d" % [
|
144
|
+
filename, event[:position]/(1024**2), file_events_processed, events_processed
|
145
|
+
]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
files[filename] = {
|
150
|
+
filename: filename,
|
151
|
+
events: file_events_processed,
|
152
|
+
min_timestamp: file_min_timestamp,
|
153
|
+
max_timestamp: file_max_timestamp,
|
154
|
+
}
|
155
|
+
|
156
|
+
min_timestamp = [min_timestamp || file_min_timestamp, file_min_timestamp].min
|
157
|
+
max_timestamp = [max_timestamp || file_max_timestamp, file_max_timestamp].max
|
158
|
+
end
|
159
|
+
puts "Done."
|
160
|
+
puts
|
161
|
+
|
162
|
+
duration = max_timestamp - min_timestamp
|
163
|
+
|
164
|
+
puts "File summary:"
|
165
|
+
files.each do |filename, file|
|
166
|
+
puts " %-32s%10s%26s%26s" % [
|
167
|
+
File.basename(filename),
|
168
|
+
file[:events],
|
169
|
+
Time.at(file[:min_timestamp]).utc,
|
170
|
+
Time.at(file[:max_timestamp]).utc,
|
171
|
+
]
|
172
|
+
end
|
173
|
+
puts
|
174
|
+
|
175
|
+
puts "Summary:"
|
176
|
+
puts " Files: %d" % [files.size]
|
177
|
+
puts " Events: %d" % [events_processed]
|
178
|
+
puts " Min Time: %s" % [Time.at(min_timestamp).utc]
|
179
|
+
puts " Max Time: %s" % [Time.at(max_timestamp).utc]
|
180
|
+
puts " Duration: %ds" % [duration]
|
181
|
+
puts " Event Rate: %0.2f/s" % [events_processed.to_f / duration.to_f]
|
182
|
+
puts
|
183
|
+
|
184
|
+
events_by_type = Hash.new(0)
|
185
|
+
events_by_verb_and_table = {}
|
186
|
+
size_by_verb_and_table = {}
|
187
|
+
size_by_table = Hash.new(0)
|
188
|
+
net_change_by_verb_and_table = {}
|
189
|
+
net_change_by_table = Hash.new(0)
|
190
|
+
events.each do |event|
|
191
|
+
events_by_type[event[:type]] += 1
|
192
|
+
if event[:verb]
|
193
|
+
events_by_verb_and_table[event[:verb]] ||= Hash.new(0)
|
194
|
+
events_by_verb_and_table[event[:verb]][event[:table]] += 1
|
195
|
+
size_by_verb_and_table[event[:verb]] ||= Hash.new(0)
|
196
|
+
size_by_verb_and_table[event[:verb]][event[:table]] += event[:size]
|
197
|
+
size_by_table[event[:table]] += event[:size]
|
198
|
+
net_change_by_verb_and_table[event[:verb]] ||= Hash.new(0)
|
199
|
+
net_change_by_verb_and_table[event[:verb]][event[:table]] += event[:net_change]
|
200
|
+
net_change_by_table[event[:table]] += event[:net_change]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
puts "Events by type:"
|
205
|
+
events_by_type.sort { |a, b| b[1] <=> a[1] }.each do |type, count|
|
206
|
+
puts " %-50s%10d%10.2f/s" % [type, count, count.to_f / duration.to_f]
|
207
|
+
end
|
208
|
+
puts
|
209
|
+
|
210
|
+
puts "Events by verb and table:"
|
211
|
+
events_by_verb_and_table.sort.each do |verb, table_and_count|
|
212
|
+
puts "%s\n" % [verb]
|
213
|
+
puts " %-50s%10s%14s%14s%14s" % [
|
214
|
+
"", "Count", "Rate/s", "Net (KiB/s)", "Size (KiB/s)"
|
215
|
+
]
|
216
|
+
table_and_count.sort { |a, b| b[1] <=> a[1] }.each do |table, count|
|
217
|
+
puts " %-50s%10d%14s%+14.2f%14.2f" % [
|
218
|
+
table, count, "%10.2f/s" % [count.to_f / duration.to_f],
|
219
|
+
net_change_by_verb_and_table[verb][table] / 1024.0 / duration.to_f,
|
220
|
+
size_by_verb_and_table[verb][table] / 1024.0 / duration.to_f,
|
221
|
+
]
|
222
|
+
end
|
223
|
+
puts
|
224
|
+
end
|
225
|
+
|
226
|
+
puts "Event payload by table (top 10):"
|
227
|
+
size_by_table.sort { |a, b| b[1].abs <=> a[1].abs }.first(10).each do |table, size|
|
228
|
+
puts " %-50s%+10.2f KiB/s" % [
|
229
|
+
table, size.to_f / 1024.0 / duration.to_f
|
230
|
+
]
|
231
|
+
end
|
232
|
+
puts
|
233
|
+
|
234
|
+
puts "Net change by table (top 10):"
|
235
|
+
net_change_by_table.sort { |a, b| b[1].abs <=> a[1].abs }.first(10).each do |table, net_change|
|
236
|
+
puts " %-50s%+10.2f KiB/s" % [
|
237
|
+
table, net_change.to_f / 1024.0 / duration.to_f
|
238
|
+
]
|
239
|
+
end
|
240
|
+
puts
|
241
|
+
|
data/lib/mysql_binlog/binlog.rb
CHANGED
@@ -51,6 +51,7 @@ module MysqlBinlog
|
|
51
51
|
attr_accessor :filter_flags
|
52
52
|
attr_accessor :ignore_rotate
|
53
53
|
attr_accessor :max_query_length
|
54
|
+
attr_accessor :checksum
|
54
55
|
|
55
56
|
def initialize(reader)
|
56
57
|
@reader = reader
|
@@ -61,6 +62,7 @@ module MysqlBinlog
|
|
61
62
|
@filter_flags = nil
|
62
63
|
@ignore_rotate = false
|
63
64
|
@max_query_length = 1048576
|
65
|
+
@checksum = :nil
|
64
66
|
end
|
65
67
|
|
66
68
|
# Rewind to the beginning of the log, if supported by the reader. The
|
@@ -81,10 +83,16 @@ module MysqlBinlog
|
|
81
83
|
def read_event_fields(header)
|
82
84
|
# Delegate the parsing of the event content to a method of the same name
|
83
85
|
# in BinlogEventParser.
|
84
|
-
if event_parser.methods.include? header[:event_type]
|
86
|
+
if event_parser.methods.map(&:to_sym).include? header[:event_type]
|
85
87
|
fields = event_parser.send(header[:event_type], header)
|
86
88
|
end
|
87
89
|
|
90
|
+
unless fields
|
91
|
+
fields = {
|
92
|
+
payload: reader.read(header[:payload_length]),
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
88
96
|
# Check if we've read past the end of the event. This is normally because
|
89
97
|
# of an unsupported substructure in the event causing field misalignment
|
90
98
|
# or a bug in the event reader method in BinlogEventParser. This may also
|
@@ -103,6 +111,19 @@ module MysqlBinlog
|
|
103
111
|
end
|
104
112
|
private :read_event_fields
|
105
113
|
|
114
|
+
def checksum_length
|
115
|
+
case @checksum
|
116
|
+
when :crc32
|
117
|
+
4
|
118
|
+
else
|
119
|
+
0
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def payload_length(header)
|
124
|
+
@fde ? (header[:event_length] - @fde[:header_length] - checksum_length) : 0
|
125
|
+
end
|
126
|
+
|
106
127
|
# Scan events until finding one that isn't rejected by the filter rules.
|
107
128
|
# If there are no filter rules, this will return the next event provided
|
108
129
|
# by the reader.
|
@@ -119,6 +140,18 @@ module MysqlBinlog
|
|
119
140
|
return nil
|
120
141
|
end
|
121
142
|
|
143
|
+
# Skip the remaining part of the header which might not have been
|
144
|
+
# parsed.
|
145
|
+
if @fde
|
146
|
+
reader.seek(position + @fde[:header_length])
|
147
|
+
header[:payload_length] = payload_length(header)
|
148
|
+
header[:payload_end] = position + @fde[:header_length] + payload_length(header)
|
149
|
+
else
|
150
|
+
header[:payload_length] = 0
|
151
|
+
header[:payload_end] = header[:next_position]
|
152
|
+
end
|
153
|
+
|
154
|
+
|
122
155
|
if @filter_event_types
|
123
156
|
unless @filter_event_types.include? header[:event_type]
|
124
157
|
skip_this_event = true
|
@@ -26,11 +26,23 @@ module MysqlBinlog
|
|
26
26
|
:pre_ga_write_rows_event => 20, # (deprecated)
|
27
27
|
:pre_ga_update_rows_event => 21, # (deprecated)
|
28
28
|
:pre_ga_delete_rows_event => 22, # (deprecated)
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
29
|
+
:write_rows_event_v1 => 23, #
|
30
|
+
:update_rows_event_v1 => 24, #
|
31
|
+
:delete_rows_event_v1 => 25, #
|
32
32
|
:incident_event => 26, #
|
33
33
|
:heartbeat_log_event => 27, #
|
34
|
+
:ignorable_log_event => 28, #
|
35
|
+
:rows_query_log_event => 29, #
|
36
|
+
:write_rows_event_v2 => 30, #
|
37
|
+
:update_rows_event_v2 => 31, #
|
38
|
+
:delete_rows_event_v2 => 32, #
|
39
|
+
:gtid_log_event => 33, #
|
40
|
+
:anonymous_gtid_log_event => 34, #
|
41
|
+
:previous_gtids_log_event => 35, #
|
42
|
+
:transaction_context_event => 36, #
|
43
|
+
:view_change_event => 37, #
|
44
|
+
:xa_prepare_log_event => 38, #
|
45
|
+
|
34
46
|
:table_metadata_event => 50, # Only in Twitter MySQL
|
35
47
|
}
|
36
48
|
|
@@ -44,9 +56,12 @@ module MysqlBinlog
|
|
44
56
|
# have an identical structure, this list can be used by other programs to
|
45
57
|
# know which events can be treated as row events.
|
46
58
|
ROW_EVENT_TYPES = [
|
47
|
-
:
|
48
|
-
:
|
49
|
-
:
|
59
|
+
:write_rows_event_v1,
|
60
|
+
:update_rows_event_v1,
|
61
|
+
:delete_rows_event_v1,
|
62
|
+
:write_rows_event_v2,
|
63
|
+
:update_rows_event_v2,
|
64
|
+
:delete_rows_event_v2,
|
50
65
|
]
|
51
66
|
|
52
67
|
# Values for the +flags+ field that may appear in binary logs. There are
|
@@ -55,11 +70,14 @@ module MysqlBinlog
|
|
55
70
|
#
|
56
71
|
# Defined in sql/log_event.h line ~448
|
57
72
|
EVENT_HEADER_FLAGS = {
|
58
|
-
:binlog_in_use
|
59
|
-
:thread_specific
|
60
|
-
:suppress_use
|
61
|
-
:artificial
|
62
|
-
:relay_log
|
73
|
+
:binlog_in_use => 0x0001, # LOG_EVENT_BINLOG_IN_USE_F
|
74
|
+
:thread_specific => 0x0004, # LOG_EVENT_THREAD_SPECIFIC_F
|
75
|
+
:suppress_use => 0x0008, # LOG_EVENT_SUPPRESS_USE_F
|
76
|
+
:artificial => 0x0020, # LOG_EVENT_ARTIFICIAL_F
|
77
|
+
:relay_log => 0x0040, # LOG_EVENT_RELAY_LOG_F
|
78
|
+
:ignorable => 0x0080, # LOG_EVENT_IGNORABLE_F
|
79
|
+
:no_filter => 0x0100, # LOG_EVENT_NO_FILTER_F
|
80
|
+
:mts_isolate => 0x0200, # LOG_EVENT_MTS_ISOLATE_F
|
63
81
|
}
|
64
82
|
|
65
83
|
# A mapping array for all values that may appear in the +status+ field of
|
@@ -79,8 +97,15 @@ module MysqlBinlog
|
|
79
97
|
:table_map_for_update, # 9 (Q_TABLE_MAP_FOR_UPDATE_CODE)
|
80
98
|
:master_data_written, # 10 (Q_MASTER_DATA_WRITTEN_CODE)
|
81
99
|
:invoker, # 11 (Q_INVOKER)
|
100
|
+
:updated_db_names, # 12 (Q_UPDATED_DB_NAMES)
|
101
|
+
:microseconds, # 13 (Q_MICROSECONDS)
|
102
|
+
:commit_ts, # 14 (Q_COMMIT_TS)
|
103
|
+
:commit_ts2, # 15
|
104
|
+
:explicit_defaults_for_timestamp, # 16
|
82
105
|
]
|
83
106
|
|
107
|
+
QUERY_EVENT_OVER_MAX_DBS_IN_EVENT_MTS = 254
|
108
|
+
|
84
109
|
# A mapping hash for all values that may appear in the +flags2+ field of
|
85
110
|
# a query_event.
|
86
111
|
#
|
@@ -152,6 +177,10 @@ module MysqlBinlog
|
|
152
177
|
:complete_rows => 1 << 3, # COMPLETE_ROWS_F
|
153
178
|
}
|
154
179
|
|
180
|
+
GENERIC_ROWS_EVENT_VH_FIELD_TYPES = [
|
181
|
+
:extra_rows_info, # ROWS_V_EXTRAINFO_TAG
|
182
|
+
]
|
183
|
+
|
155
184
|
# Parse binary log events from a provided binary log. Must be driven
|
156
185
|
# externally, but handles all the details of parsing an event header
|
157
186
|
# and the content of the various event types.
|
@@ -181,11 +210,13 @@ module MysqlBinlog
|
|
181
210
|
def event_header
|
182
211
|
header = {}
|
183
212
|
header[:timestamp] = parser.read_uint32
|
184
|
-
|
213
|
+
event_type = parser.read_uint8
|
214
|
+
header[:event_type] = EVENT_TYPES[event_type] || "unknown_#{event_type}".to_sym
|
185
215
|
header[:server_id] = parser.read_uint32
|
186
216
|
header[:event_length] = parser.read_uint32
|
187
217
|
header[:next_position] = parser.read_uint32
|
188
218
|
header[:flags] = parser.read_uint_bitmap_by_size_and_name(2, EVENT_HEADER_FLAGS)
|
219
|
+
|
189
220
|
header
|
190
221
|
end
|
191
222
|
|
@@ -212,6 +243,25 @@ module MysqlBinlog
|
|
212
243
|
fields
|
213
244
|
end
|
214
245
|
|
246
|
+
def _query_event_status_updated_db_names
|
247
|
+
db_count = parser.read_uint8
|
248
|
+
return nil if db_count == QUERY_EVENT_OVER_MAX_DBS_IN_EVENT_MTS
|
249
|
+
|
250
|
+
db_names = []
|
251
|
+
db_count.times do |n|
|
252
|
+
db_name = ""
|
253
|
+
loop do
|
254
|
+
c = reader.read(1)
|
255
|
+
break if c == "\0"
|
256
|
+
db_name << c
|
257
|
+
end
|
258
|
+
db_names << db_name
|
259
|
+
end
|
260
|
+
|
261
|
+
db_names
|
262
|
+
end
|
263
|
+
private :_query_event_status_updated_db_names
|
264
|
+
|
215
265
|
# Parse a dynamic +status+ structure within a query_event, which consists
|
216
266
|
# of a status_length (uint16) followed by a number of status variables
|
217
267
|
# (determined by the +status_length+) each of which consist of:
|
@@ -223,7 +273,8 @@ module MysqlBinlog
|
|
223
273
|
status_length = parser.read_uint16
|
224
274
|
end_position = reader.position + status_length
|
225
275
|
while reader.position < end_position
|
226
|
-
|
276
|
+
status_type_id = parser.read_uint8
|
277
|
+
status_type = QUERY_EVENT_STATUS_TYPES[status_type_id]
|
227
278
|
status[status_type] = case status_type
|
228
279
|
when :flags2
|
229
280
|
parser.read_uint_bitmap_by_size_and_name(4, QUERY_EVENT_FLAGS2)
|
@@ -252,6 +303,14 @@ module MysqlBinlog
|
|
252
303
|
parser.read_uint16
|
253
304
|
when :table_map_for_update
|
254
305
|
parser.read_uint64
|
306
|
+
when :updated_db_names
|
307
|
+
_query_event_status_updated_db_names
|
308
|
+
when :commit_ts
|
309
|
+
parser.read_uint64
|
310
|
+
when :microseconds
|
311
|
+
parser.read_uint24
|
312
|
+
else
|
313
|
+
raise "Unknown status type #{status_type_id}"
|
255
314
|
end
|
256
315
|
end
|
257
316
|
|
@@ -334,7 +393,7 @@ module MysqlBinlog
|
|
334
393
|
:precision => parser.read_uint8,
|
335
394
|
:decimals => parser.read_uint8,
|
336
395
|
}
|
337
|
-
when :blob, :geometry
|
396
|
+
when :blob, :geometry, :json
|
338
397
|
{ :length_size => parser.read_uint8 }
|
339
398
|
when :string, :var_string
|
340
399
|
# The :string type sets a :real_type field to indicate the actual type
|
@@ -350,6 +409,10 @@ module MysqlBinlog
|
|
350
409
|
else
|
351
410
|
{ :max_length => (((metadata >> 4) & 0x300) ^ 0x300) + (metadata & 0x00ff) }
|
352
411
|
end
|
412
|
+
when :timestamp2, :datetime2, :time2
|
413
|
+
{
|
414
|
+
:decimals => parser.read_uint8,
|
415
|
+
}
|
353
416
|
end
|
354
417
|
end
|
355
418
|
private :_table_map_event_column_metadata_read
|
@@ -376,7 +439,7 @@ module MysqlBinlog
|
|
376
439
|
map_entry[:db] = parser.read_lpstringz
|
377
440
|
map_entry[:table] = parser.read_lpstringz
|
378
441
|
columns = parser.read_varint
|
379
|
-
columns_type = parser.read_uint8_array(columns).map { |c| MYSQL_TYPES[c] }
|
442
|
+
columns_type = parser.read_uint8_array(columns).map { |c| MYSQL_TYPES[c] || "unknown_#{c}".to_sym }
|
380
443
|
columns_metadata = _table_map_event_column_metadata(columns_type)
|
381
444
|
columns_nullable = parser.read_bit_array(columns)
|
382
445
|
|
@@ -415,8 +478,9 @@ module MysqlBinlog
|
|
415
478
|
fields[:flags] = parser.read_uint16
|
416
479
|
fields[:columns] = columns.times.map do |c|
|
417
480
|
descriptor_length = parser.read_uint32
|
481
|
+
column_type = parser.read_uint8
|
418
482
|
@table_map[table_id][:columns][c][:description] = {
|
419
|
-
:type => MYSQL_TYPES[
|
483
|
+
:type => MYSQL_TYPES[column_type] || "unknown_#{column_type}".to_sym,
|
420
484
|
:length => parser.read_uint32,
|
421
485
|
:scale => parser.read_uint8,
|
422
486
|
:character_set => COLLATION[parser.read_uint16],
|
@@ -435,23 +499,43 @@ module MysqlBinlog
|
|
435
499
|
# and false values identifies which columns are present.
|
436
500
|
def _generic_rows_event_row_image(header, fields, columns_used)
|
437
501
|
row_image = []
|
502
|
+
start_position = reader.position
|
438
503
|
columns_null = parser.read_bit_array(fields[:table][:columns].size)
|
439
504
|
fields[:table][:columns].each_with_index do |column, column_index|
|
505
|
+
#puts "column #{column_index} #{column}: used=#{columns_used[column_index]}, null=#{columns_null[column_index]}"
|
440
506
|
if !columns_used[column_index]
|
441
507
|
row_image << nil
|
442
508
|
elsif columns_null[column_index]
|
443
509
|
row_image << { column_index => nil }
|
444
510
|
else
|
511
|
+
value = parser.read_mysql_type(column[:type], column[:metadata])
|
445
512
|
row_image << {
|
446
|
-
column_index =>
|
447
|
-
parser.read_mysql_type(column[:type], column[:metadata])
|
513
|
+
column_index => value,
|
448
514
|
}
|
449
515
|
end
|
450
516
|
end
|
451
|
-
|
517
|
+
end_position = reader.position
|
518
|
+
|
519
|
+
{
|
520
|
+
image: row_image,
|
521
|
+
size: end_position-start_position
|
522
|
+
}
|
452
523
|
end
|
453
524
|
private :_generic_rows_event_row_image
|
454
525
|
|
526
|
+
def diff_row_images(before, after)
|
527
|
+
diff = {}
|
528
|
+
before.each_with_index do |before_column, index|
|
529
|
+
after_column = after[index]
|
530
|
+
before_value = before_column.first[1]
|
531
|
+
after_value = after_column.first[1]
|
532
|
+
if before_value != after_value
|
533
|
+
diff[index] = { before: before_value, after: after_value }
|
534
|
+
end
|
535
|
+
end
|
536
|
+
diff
|
537
|
+
end
|
538
|
+
|
455
539
|
# Parse the row images present in a row-based replication row event. This
|
456
540
|
# is rather incomplete right now due missing support for many MySQL types,
|
457
541
|
# but can parse some basic events.
|
@@ -461,13 +545,14 @@ module MysqlBinlog
|
|
461
545
|
while reader.position < end_position
|
462
546
|
row_image = {}
|
463
547
|
case header[:event_type]
|
464
|
-
when :
|
548
|
+
when :write_rows_event_v1, :write_rows_event_v2
|
465
549
|
row_image[:after] = _generic_rows_event_row_image(header, fields, columns_used[:after])
|
466
|
-
when :
|
550
|
+
when :delete_rows_event_v1, :delete_rows_event_v1
|
467
551
|
row_image[:before] = _generic_rows_event_row_image(header, fields, columns_used[:before])
|
468
|
-
when :
|
552
|
+
when :update_rows_event_v1, :update_rows_event_v2
|
469
553
|
row_image[:before] = _generic_rows_event_row_image(header, fields, columns_used[:before])
|
470
554
|
row_image[:after] = _generic_rows_event_row_image(header, fields, columns_used[:after])
|
555
|
+
row_image[:diff] = diff_row_images(row_image[:before][:image], row_image[:after][:image])
|
471
556
|
end
|
472
557
|
row_images << row_image
|
473
558
|
end
|
@@ -483,6 +568,16 @@ module MysqlBinlog
|
|
483
568
|
end
|
484
569
|
private :_generic_rows_event_row_images
|
485
570
|
|
571
|
+
# Parse the variable header from a v2 rows event. This is only used for
|
572
|
+
# ROWS_V_EXTRAINFO_TAG which is used by NDB. Ensure it can be skipped
|
573
|
+
# properly but don't bother parsing it.
|
574
|
+
def _generic_rows_event_vh
|
575
|
+
vh_payload_len = parser.read_uint16 - 2
|
576
|
+
return unless vh_payload_len > 0
|
577
|
+
|
578
|
+
reader.read(vh_payload_len)
|
579
|
+
end
|
580
|
+
|
486
581
|
# Parse fields for any of the row-based replication row events:
|
487
582
|
# * +Write_rows+ which is used for +INSERT+.
|
488
583
|
# * +Update_rows+ which is used for +UPDATE+.
|
@@ -491,29 +586,104 @@ module MysqlBinlog
|
|
491
586
|
# Implemented in sql/log_event.cc line ~8039
|
492
587
|
# in Rows_log_event::write_data_header
|
493
588
|
# and Rows_log_event::write_data_body
|
494
|
-
def
|
589
|
+
def _generic_rows_event(header, contains_vh: false)
|
495
590
|
fields = {}
|
496
591
|
table_id = parser.read_uint48
|
497
592
|
fields[:table] = @table_map[table_id]
|
498
593
|
fields[:flags] = parser.read_uint_bitmap_by_size_and_name(2, GENERIC_ROWS_EVENT_FLAGS)
|
594
|
+
|
595
|
+
# Rows_log_event v2 events contain a variable-sized header. Only NDB
|
596
|
+
# uses it right now, so let's just make sure it's skipped properly.
|
597
|
+
_generic_rows_event_vh if contains_vh
|
598
|
+
|
499
599
|
columns = parser.read_varint
|
500
600
|
columns_used = {}
|
501
601
|
case header[:event_type]
|
502
|
-
when :
|
602
|
+
when :write_rows_event_v1, :write_rows_event_v2
|
503
603
|
columns_used[:after] = parser.read_bit_array(columns)
|
504
|
-
when :
|
604
|
+
when :delete_rows_event_v1, :delete_rows_event_v2
|
505
605
|
columns_used[:before] = parser.read_bit_array(columns)
|
506
|
-
when :
|
606
|
+
when :update_rows_event_v1, :update_rows_event_v2
|
507
607
|
columns_used[:before] = parser.read_bit_array(columns)
|
508
608
|
columns_used[:after] = parser.read_bit_array(columns)
|
509
609
|
end
|
510
610
|
fields[:row_image] = _generic_rows_event_row_images(header, fields, columns_used)
|
511
611
|
fields
|
512
612
|
end
|
613
|
+
private :_generic_rows_event
|
614
|
+
|
615
|
+
def generic_rows_event_v1(header)
|
616
|
+
_generic_rows_event(header, contains_vh: false)
|
617
|
+
end
|
618
|
+
|
619
|
+
alias :write_rows_event_v1 :generic_rows_event_v1
|
620
|
+
alias :update_rows_event_v1 :generic_rows_event_v1
|
621
|
+
alias :delete_rows_event_v1 :generic_rows_event_v1
|
513
622
|
|
514
|
-
|
515
|
-
|
516
|
-
|
623
|
+
def generic_rows_event_v2(header)
|
624
|
+
_generic_rows_event(header, contains_vh: true)
|
625
|
+
end
|
626
|
+
|
627
|
+
alias :write_rows_event_v2 :generic_rows_event_v2
|
628
|
+
alias :update_rows_event_v2 :generic_rows_event_v2
|
629
|
+
alias :delete_rows_event_v2 :generic_rows_event_v2
|
630
|
+
|
631
|
+
def rows_query_log_event(header)
|
632
|
+
reader.read(1) # skip useless byte length which is unused
|
633
|
+
{ query: reader.read(header[:payload_length]-1) }
|
634
|
+
end
|
635
|
+
|
636
|
+
def in_hex(bytes)
|
637
|
+
bytes.each_byte.map { |c| "%02x" % c.ord }.join
|
638
|
+
end
|
517
639
|
|
640
|
+
def format_gtid_sid(sid)
|
641
|
+
[0..3, 4..5, 6..7, 8..9, 10..15].map { |r| in_hex(sid[r]) }.join("-")
|
642
|
+
end
|
643
|
+
|
644
|
+
# 6d9190a2-cca6-11e8-aa8c-42010aef0019:551845019
|
645
|
+
def format_gtid(sid, gno_or_ivs)
|
646
|
+
"#{format_gtid_sid(sid)}:#{gno_or_ivs}"
|
647
|
+
end
|
648
|
+
|
649
|
+
def previous_gtids_log_event(header)
|
650
|
+
n_sids = parser.read_uint64
|
651
|
+
|
652
|
+
gtids = []
|
653
|
+
n_sids.times do
|
654
|
+
sid = parser.read_nstring(16)
|
655
|
+
n_ivs = parser.read_uint64
|
656
|
+
ivs = []
|
657
|
+
n_ivs.times do
|
658
|
+
iv_start = parser.read_uint64
|
659
|
+
iv_end = parser.read_uint64
|
660
|
+
ivs << "#{iv_start}-#{iv_end}"
|
661
|
+
end
|
662
|
+
gtids << format_gtid(sid, ivs.join(":"))
|
663
|
+
end
|
664
|
+
|
665
|
+
{
|
666
|
+
previous_gtids: gtids
|
667
|
+
}
|
668
|
+
end
|
669
|
+
|
670
|
+
def gtid_log_event(header)
|
671
|
+
flags = parser.read_uint8
|
672
|
+
sid = parser.read_nstring(16)
|
673
|
+
gno = parser.read_uint64
|
674
|
+
lts_type = parser.read_uint8
|
675
|
+
lts_last_committed = parser.read_uint64
|
676
|
+
lts_sequence_number = parser.read_uint64
|
677
|
+
|
678
|
+
{
|
679
|
+
flags: flags,
|
680
|
+
gtid: format_gtid(sid, gno),
|
681
|
+
lts: {
|
682
|
+
type: lts_type,
|
683
|
+
last_committed: lts_last_committed,
|
684
|
+
sequence_number: lts_sequence_number,
|
685
|
+
},
|
686
|
+
}
|
687
|
+
end
|
518
688
|
end
|
519
689
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
1
3
|
module MysqlBinlog
|
2
4
|
# All MySQL types mapping to their integer values.
|
3
5
|
MYSQL_TYPES_HASH = {
|
@@ -18,6 +20,10 @@ module MysqlBinlog
|
|
18
20
|
:newdate => 14,
|
19
21
|
:varchar => 15,
|
20
22
|
:bit => 16,
|
23
|
+
:timestamp2 => 17,
|
24
|
+
:datetime2 => 18,
|
25
|
+
:time2 => 19,
|
26
|
+
:json => 245,
|
21
27
|
:newdecimal => 246,
|
22
28
|
:enum => 247,
|
23
29
|
:set => 248,
|
@@ -58,12 +64,28 @@ module MysqlBinlog
|
|
58
64
|
reader.read(2).unpack("v").first
|
59
65
|
end
|
60
66
|
|
67
|
+
# Read an unsigned 16-bit (2-byte) big-endian integer.
|
68
|
+
def read_uint16_be
|
69
|
+
reader.read(2).unpack("n").first
|
70
|
+
end
|
71
|
+
|
61
72
|
# Read an unsigned 24-bit (3-byte) integer.
|
62
73
|
def read_uint24
|
63
74
|
a, b, c = reader.read(3).unpack("CCC")
|
64
75
|
a + (b << 8) + (c << 16)
|
65
76
|
end
|
66
77
|
|
78
|
+
# Read an unsigned 24-bit (3-byte) big-endian integer.
|
79
|
+
def read_uint24_be
|
80
|
+
a, b = reader.read(3).unpack("nC")
|
81
|
+
(a << 8) + b
|
82
|
+
end
|
83
|
+
|
84
|
+
# Read an unsigned 32-bit (4-byte) integer.
|
85
|
+
def read_uint32_be
|
86
|
+
reader.read(4).unpack("N").first
|
87
|
+
end
|
88
|
+
|
67
89
|
# Read an unsigned 32-bit (4-byte) integer.
|
68
90
|
def read_uint32
|
69
91
|
reader.read(4).unpack("V").first
|
@@ -75,6 +97,12 @@ module MysqlBinlog
|
|
75
97
|
a + (b << 8)
|
76
98
|
end
|
77
99
|
|
100
|
+
# Read an unsigned 40-bit (5-byte) big-endian integer.
|
101
|
+
def read_uint40_be
|
102
|
+
a, b = reader.read(5).unpack("NC")
|
103
|
+
(a << 8) + b
|
104
|
+
end
|
105
|
+
|
78
106
|
# Read an unsigned 48-bit (6-byte) integer.
|
79
107
|
def read_uint48
|
80
108
|
a, b, c = reader.read(6).unpack("vvv")
|
@@ -89,7 +117,12 @@ module MysqlBinlog
|
|
89
117
|
|
90
118
|
# Read an unsigned 64-bit (8-byte) integer.
|
91
119
|
def read_uint64
|
92
|
-
reader.read(8).unpack("Q").first
|
120
|
+
reader.read(8).unpack("Q<").first
|
121
|
+
end
|
122
|
+
|
123
|
+
# Read an unsigned 64-bit (8-byte) integer.
|
124
|
+
def read_uint64_be
|
125
|
+
reader.read(8).unpack("Q>").first
|
93
126
|
end
|
94
127
|
|
95
128
|
# Read a signed 8-bit (1-byte) integer.
|
@@ -99,13 +132,13 @@ module MysqlBinlog
|
|
99
132
|
|
100
133
|
# Read a signed 16-bit (2-byte) big-endian integer.
|
101
134
|
def read_int16_be
|
102
|
-
reader.read(2).
|
135
|
+
reader.read(2).unpack('n').first
|
103
136
|
end
|
104
137
|
|
105
138
|
# Read a signed 24-bit (3-byte) big-endian integer.
|
106
139
|
def read_int24_be
|
107
140
|
a, b, c = reader.read(3).unpack('CCC')
|
108
|
-
if a & 128
|
141
|
+
if (a & 128) == 0
|
109
142
|
(a << 16) | (b << 8) | c
|
110
143
|
else
|
111
144
|
(-1 << 24) | (a << 16) | (b << 8) | c
|
@@ -114,9 +147,9 @@ module MysqlBinlog
|
|
114
147
|
|
115
148
|
# Read a signed 32-bit (4-byte) big-endian integer.
|
116
149
|
def read_int32_be
|
117
|
-
reader.read(4).
|
150
|
+
reader.read(4).unpack('N').first
|
118
151
|
end
|
119
|
-
|
152
|
+
|
120
153
|
def read_uint_by_size(size)
|
121
154
|
case size
|
122
155
|
when 1
|
@@ -272,7 +305,7 @@ module MysqlBinlog
|
|
272
305
|
str << value.to_s
|
273
306
|
end
|
274
307
|
|
275
|
-
BigDecimal
|
308
|
+
BigDecimal(str)
|
276
309
|
end
|
277
310
|
|
278
311
|
# Read an array of unsigned 8-bit (1-byte) integers.
|
@@ -359,6 +392,47 @@ module MysqlBinlog
|
|
359
392
|
]
|
360
393
|
end
|
361
394
|
|
395
|
+
def convert_mysql_type_datetimef(int_part, frac_part)
|
396
|
+
year_month = extract_bits(int_part, 17, 22)
|
397
|
+
year = year_month / 13
|
398
|
+
month = year_month % 13
|
399
|
+
day = extract_bits(int_part, 5, 17)
|
400
|
+
hour = extract_bits(int_part, 5, 12)
|
401
|
+
minute = extract_bits(int_part, 6, 6)
|
402
|
+
second = extract_bits(int_part, 6, 0)
|
403
|
+
|
404
|
+
"%04i-%02i-%02i %02i:%02i:%02i.%06i" % [
|
405
|
+
year,
|
406
|
+
month,
|
407
|
+
day,
|
408
|
+
hour,
|
409
|
+
minute,
|
410
|
+
second,
|
411
|
+
frac_part,
|
412
|
+
]
|
413
|
+
end
|
414
|
+
|
415
|
+
def read_frac_part(decimals)
|
416
|
+
case decimals
|
417
|
+
when 0
|
418
|
+
0
|
419
|
+
when 1, 2
|
420
|
+
read_uint8 * 10000
|
421
|
+
when 3, 4
|
422
|
+
read_uint16_be * 100
|
423
|
+
when 5, 6
|
424
|
+
read_uint24_be
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
def read_datetimef(decimals)
|
429
|
+
convert_mysql_type_datetimef(read_uint40_be, read_frac_part(decimals))
|
430
|
+
end
|
431
|
+
|
432
|
+
def read_timestamp2(decimals)
|
433
|
+
read_uint32_be + (read_frac_part(decimals) / 1000000)
|
434
|
+
end
|
435
|
+
|
362
436
|
# Read a single field, provided the MySQL column type as a symbol. Not all
|
363
437
|
# types are currently supported.
|
364
438
|
def read_mysql_type(type, metadata=nil)
|
@@ -382,10 +456,12 @@ module MysqlBinlog
|
|
382
456
|
when :varchar, :string
|
383
457
|
prefix_size = (metadata[:max_length] > 255) ? 2 : 1
|
384
458
|
read_lpstring(prefix_size)
|
385
|
-
when :blob, :geometry
|
459
|
+
when :blob, :geometry, :json
|
386
460
|
read_lpstring(metadata[:length_size])
|
387
461
|
when :timestamp
|
388
462
|
read_uint32
|
463
|
+
when :timestamp2
|
464
|
+
read_timestamp2(metadata[:decimals])
|
389
465
|
when :year
|
390
466
|
read_uint8 + 1900
|
391
467
|
when :date
|
@@ -394,6 +470,8 @@ module MysqlBinlog
|
|
394
470
|
convert_mysql_type_time(read_uint24)
|
395
471
|
when :datetime
|
396
472
|
convert_mysql_type_datetime(read_uint64)
|
473
|
+
when :datetime2
|
474
|
+
read_datetimef(metadata[:decimals])
|
397
475
|
when :enum, :set
|
398
476
|
read_uint_by_size(metadata[:size])
|
399
477
|
when :bit
|
@@ -20,7 +20,7 @@ module MysqlBinlog
|
|
20
20
|
def open_file(filename)
|
21
21
|
@dirname = File.dirname(filename)
|
22
22
|
@filename = File.basename(filename)
|
23
|
-
@binlog = File.open(filename,
|
23
|
+
@binlog = File.open(filename, "r:BINARY")
|
24
24
|
|
25
25
|
verify_magic
|
26
26
|
end
|
@@ -69,7 +69,7 @@ module MysqlBinlog
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def remaining(header)
|
72
|
-
header[:
|
72
|
+
header[:payload_end] - @binlog.tell
|
73
73
|
end
|
74
74
|
|
75
75
|
def skip(header)
|
data/lib/mysql_binlog/version.rb
CHANGED
metadata
CHANGED
@@ -1,32 +1,25 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: mysql_binlog
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 3
|
9
|
-
- 2
|
10
|
-
version: 0.3.2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.7
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Jeremy Cole
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
date: 2012-11-07 00:00:00 Z
|
11
|
+
date: 2021-04-14 00:00:00.000000000 Z
|
19
12
|
dependencies: []
|
20
|
-
|
21
13
|
description: Library for parsing MySQL binary logs in Ruby
|
22
14
|
email: jeremy@jcole.us
|
23
|
-
executables:
|
15
|
+
executables:
|
24
16
|
- mysql_binlog_dump
|
17
|
+
- mysql_binlog_summary
|
25
18
|
extensions: []
|
26
|
-
|
27
19
|
extra_rdoc_files: []
|
28
|
-
|
29
|
-
|
20
|
+
files:
|
21
|
+
- bin/mysql_binlog_dump
|
22
|
+
- bin/mysql_binlog_summary
|
30
23
|
- lib/mysql_binlog.rb
|
31
24
|
- lib/mysql_binlog/binlog.rb
|
32
25
|
- lib/mysql_binlog/binlog_event_parser.rb
|
@@ -36,40 +29,27 @@ files:
|
|
36
29
|
- lib/mysql_binlog/reader/binlog_stream_reader.rb
|
37
30
|
- lib/mysql_binlog/reader/debugging_reader.rb
|
38
31
|
- lib/mysql_binlog/version.rb
|
39
|
-
- bin/mysql_binlog_dump
|
40
32
|
homepage: http://jcole.us/
|
41
|
-
licenses:
|
42
|
-
|
33
|
+
licenses:
|
34
|
+
- BSD-3-Clause
|
35
|
+
metadata: {}
|
43
36
|
post_install_message:
|
44
37
|
rdoc_options: []
|
45
|
-
|
46
|
-
require_paths:
|
38
|
+
require_paths:
|
47
39
|
- lib
|
48
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
-
|
50
|
-
requirements:
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
51
42
|
- - ">="
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
version: "0"
|
57
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
|
-
requirements:
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
60
47
|
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
|
63
|
-
segments:
|
64
|
-
- 0
|
65
|
-
version: "0"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
66
50
|
requirements: []
|
67
|
-
|
68
|
-
rubyforge_project:
|
69
|
-
rubygems_version: 1.8.10
|
51
|
+
rubygems_version: 3.1.4
|
70
52
|
signing_key:
|
71
|
-
specification_version:
|
53
|
+
specification_version: 4
|
72
54
|
summary: MySQL Binary Log Parser
|
73
55
|
test_files: []
|
74
|
-
|
75
|
-
has_rdoc:
|