mysql_binlog 0.1.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.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pp'
4
+ require 'mysql_binlog'
5
+
6
+ include MysqlBinlog
7
+
8
+ b = Binlog.new(BinlogFileReader, ARGV.first)
9
+
10
+ b.each_event do |event|
11
+ pp event
12
+ end
@@ -0,0 +1,162 @@
1
+ module MysqlBinlog
2
+ QUERY_EVENT_STATUS_TYPES = [
3
+ :flags2,
4
+ :sql_mode,
5
+ :catalog,
6
+ :auto_increment,
7
+ :charset,
8
+ :time_zone,
9
+ :catalog_nz,
10
+ :lc_time_names,
11
+ :charset_database,
12
+ :table_map_for_update,
13
+ ]
14
+
15
+ QUERY_EVENT_FLAGS2 = {
16
+ :auto_is_null => 1 << 14,
17
+ :not_autocommit => 1 << 19,
18
+ :no_foreign_key_checks => 1 << 26,
19
+ :relaxed_unique_checks => 1 << 27,
20
+ }
21
+
22
+ class BinlogEventFieldParser
23
+ attr_accessor :binlog
24
+ attr_accessor :reader
25
+ attr_accessor :parser
26
+
27
+ def initialize(binlog_instance)
28
+ @binlog = binlog_instance
29
+ @reader = binlog_instance.reader
30
+ @parser = binlog_instance.parser
31
+ @table_map = {}
32
+ end
33
+
34
+ def rotate_event(header, fields)
35
+ name_length = reader.remaining(header)
36
+ fields[:name_length] = name_length
37
+ fields[:name] = reader.read(name_length)
38
+ end
39
+
40
+ def _query_event_status(header, fields)
41
+ status = {}
42
+ end_position = reader.position + fields[:status_length]
43
+ while reader.position < end_position
44
+ status_type = QUERY_EVENT_STATUS_TYPES[parser.read_uint8]
45
+ status[status_type] = case status_type
46
+ when :flags2
47
+ flags2 = parser.read_uint32
48
+ QUERY_EVENT_FLAGS2.inject([]) do |result, (flag_name, flag_bit_value)|
49
+ if (flags2 & flag_bit_value) != 0
50
+ result << flag_name
51
+ end
52
+ result
53
+ end
54
+ when :sql_mode
55
+ parser.read_uint64
56
+ when :catalog
57
+ parser.read_lpstringz
58
+ when :auto_increment
59
+ {
60
+ :increment => parser.read_uint16,
61
+ :offset => parser.read_uint16,
62
+ }
63
+ when :charset
64
+ {
65
+ :character_set_client => parser.read_uint16,
66
+ :collation_connection => parser.read_uint16,
67
+ :collation_server => parser.read_uint16,
68
+ }
69
+ when :time_zone
70
+ parser.read_lpstring
71
+ when :catalog_nz
72
+ parser.read_lpstring
73
+ when :lc_time_names
74
+ parser.read_uint16
75
+ when :charset_database
76
+ parser.read_uint16
77
+ when :table_map_for_update
78
+ parser.read_uint64
79
+ end
80
+ end
81
+ status
82
+ end
83
+
84
+ def query_event(header, fields)
85
+ fields[:status] = _query_event_status(header, fields)
86
+ fields.delete :status_length
87
+ fields[:db] = parser.read_nstringz(fields[:db_length])
88
+ fields.delete :db_length
89
+ query_length = reader.remaining(header)
90
+ fields[:query] = reader.read([query_length, binlog.max_query_length].min)
91
+ end
92
+
93
+ def intvar_event(header, fields)
94
+ case fields[:intvar_type]
95
+ when 1
96
+ fields[:intvar_name] = :last_insert_id
97
+ when 2
98
+ fields[:intvar_name] = :insert_id
99
+ else
100
+ fields[:intvar_name] = nil
101
+ end
102
+ end
103
+
104
+ def table_map_event(header, fields)
105
+ fields[:table_id] = parser.read_uint48
106
+ fields[:flags] = parser.read_uint16
107
+ map_entry = @table_map[fields[:table_id]] = {}
108
+ map_entry[:db] = parser.read_lpstringz
109
+ map_entry[:table] = parser.read_lpstringz
110
+ columns = parser.read_varint
111
+ columns_type = parser.read_uint8_array(columns).map { |c| MYSQL_TYPES[c] }
112
+ columns_metadata = parser.read_lpstring
113
+ columns_nullable = parser.read_bit_array(columns)
114
+
115
+ map_entry[:columns] = columns.times.map do |c|
116
+ {
117
+ :type => columns_type[c],
118
+ :nullable => columns_nullable[c],
119
+ }
120
+ end
121
+
122
+ fields[:map_entry] = map_entry
123
+ end
124
+
125
+ def _generic_rows_event_row_images(header, fields)
126
+ row_images = []
127
+ end_position = reader.position + reader.remaining(header)
128
+ while reader.position < end_position
129
+ row_image = []
130
+ columns_null = parser.read_bit_array(fields[:table][:columns].size)
131
+ fields[:table][:columns].each_with_index do |column, column_index|
132
+ if !fields[:columns_used][column_index]
133
+ row_image << nil
134
+ elsif columns_null[column_index]
135
+ row_image << { column => nil }
136
+ else
137
+ row_image << { column => parser.read_mysql_type(column[:type]) }
138
+ end
139
+ end
140
+ row_images << row_image
141
+ end
142
+ row_images
143
+ end
144
+
145
+ def generic_rows_event(header, fields)
146
+ table_id = parser.read_uint48
147
+ fields[:table] = @table_map[table_id]
148
+ fields[:flags] = parser.read_uint16
149
+ columns = parser.read_varint
150
+ fields[:columns_used] = parser.read_bit_array(columns)
151
+ if EVENT_TYPES[header[:event_type]] == :update_rows_event
152
+ fields[:columns_update] = parser.read_bit_array(columns)
153
+ end
154
+ fields[:row_image] = _generic_rows_event_row_images(header, fields)
155
+ fields.delete :columns_used
156
+ end
157
+ alias :write_rows_event :generic_rows_event
158
+ alias :update_rows_event :generic_rows_event
159
+ alias :delete_rows_event :generic_rows_event
160
+
161
+ end
162
+ end
@@ -0,0 +1,179 @@
1
+ module MysqlBinlog
2
+ MYSQL_TYPES_HASH = {
3
+ :decimal => 0,
4
+ :tiny => 1,
5
+ :short => 2,
6
+ :long => 3,
7
+ :float => 4,
8
+ :double => 5,
9
+ :null => 6,
10
+ :timestamp => 7,
11
+ :longlong => 8,
12
+ :int24 => 9,
13
+ :date => 10,
14
+ :time => 11,
15
+ :datetime => 12,
16
+ :year => 13,
17
+ :newdate => 14,
18
+ :varchar => 15,
19
+ :bit => 16,
20
+ :newdecimal => 246,
21
+ :enum => 247,
22
+ :set => 248,
23
+ :tiny_blob => 249,
24
+ :medium_blob => 250,
25
+ :long_blob => 251,
26
+ :blob => 252,
27
+ :var_string => 253,
28
+ :string => 254,
29
+ :geometry => 255,
30
+ }
31
+
32
+ MYSQL_TYPES = MYSQL_TYPES_HASH.inject(Array.new(256)) do |type_array, item|
33
+ type_array[item[1]] = item[0]
34
+ type_array
35
+ end
36
+
37
+ class BinlogParser
38
+ attr_accessor :binlog
39
+ attr_accessor :reader
40
+
41
+ def initialize(binlog_instance)
42
+ @format_cache = {}
43
+ @binlog = binlog_instance
44
+ @reader = binlog_instance.reader
45
+ end
46
+
47
+ def read_uint8
48
+ reader.read(1).unpack("C").first
49
+ end
50
+
51
+ def read_uint16
52
+ reader.read(2).unpack("v").first
53
+ end
54
+
55
+ def read_uint24
56
+ a, b, c = reader.read(3).unpack("CCC")
57
+ a + (b << 8) + (c << 16)
58
+ end
59
+
60
+ def read_uint32
61
+ reader.read(4).unpack("V").first
62
+ end
63
+
64
+ def read_uint48
65
+ a, b, c = reader.read(6).unpack("vvv")
66
+ a + (b << 16) + (c << 32)
67
+ end
68
+
69
+ def read_uint64
70
+ reader.read(8).unpack("Q").first
71
+ end
72
+
73
+ def read_float
74
+ reader.read(4).unpack("g").first
75
+ end
76
+
77
+ def read_double
78
+ reader.read(8).unpack("G").first
79
+ end
80
+
81
+ def read_varint
82
+ # Cheating for now.
83
+ read_uint8
84
+ end
85
+
86
+ def read_lpstring
87
+ length = reader.read(1).unpack("C").first
88
+ reader.read(length)
89
+ end
90
+
91
+ def read_lpstringz
92
+ string = read_lpstring
93
+ reader.read(1) # null
94
+ string
95
+ end
96
+
97
+ def read_nstring(length)
98
+ string = reader.read(length)
99
+ string
100
+ end
101
+
102
+ def read_nstringz(length)
103
+ string = reader.read(length)
104
+ reader.read(1) # null
105
+ string
106
+ end
107
+
108
+ def read_uint8_array(length)
109
+ reader.read(length).bytes.to_a
110
+ end
111
+
112
+ def read_bit_array(length)
113
+ data = reader.read((length+7)/8)
114
+ data.unpack("b*").first.bytes.to_a.map { |i| (i-48) == 1 }.shift(length)
115
+ end
116
+
117
+ def read_and_unpack(format_description)
118
+ @format_cache[format_description] ||= {}
119
+ this_format = @format_cache[format_description][:format] ||=
120
+ format_description.inject("") { |o, f| o+(f[:format] || "") }
121
+ this_length = @format_cache[format_description][:length] ||=
122
+ format_description.inject(0) { |o, f| o+(f[:length] || 0) }
123
+
124
+ fields = {}
125
+
126
+ fields_array = reader.read(this_length).unpack(this_format)
127
+ format_description.each_with_index do |field, index|
128
+ fields[field[:name]] = fields_array[index]
129
+ end
130
+
131
+ fields
132
+ end
133
+
134
+ def read_mysql_type(column_type)
135
+ case column_type
136
+ #when :decimal
137
+ when :tiny
138
+ read_uint8
139
+ when :short
140
+ read_uint16
141
+ when :int24
142
+ read_uint24
143
+ when :long
144
+ read_uint32
145
+ when :longlong
146
+ read_uint64
147
+ when :string
148
+ length = read_varint
149
+ read_nstring(length)
150
+
151
+ when :float
152
+ read_float
153
+ when :double
154
+ read_double
155
+ #when :null
156
+ when :timestamp
157
+ read_uint32
158
+ #when :date
159
+ #when :time
160
+ #when :datetime
161
+ #when :year
162
+ #when :newdate
163
+ #when :varchar
164
+ #when :bit
165
+ #when :newdecimal
166
+ #when :enum
167
+ #when :set
168
+ #when :tiny_blob
169
+ #when :medium_blob
170
+ #when :long_blob
171
+ #when :blob
172
+ #when :var_string
173
+ #when :string
174
+ #when :geometry
175
+ end
176
+ end
177
+
178
+ end
179
+ end
@@ -0,0 +1,234 @@
1
+ require 'mysql_binlog/binlog_parser'
2
+
3
+ module MysqlBinlog
4
+ def puts_hex(data)
5
+ hex = data.bytes.each_slice(24).inject("") do |string, slice|
6
+ string << slice.map { |b| "%02x" % b }.join(" ") + "\n"
7
+ string
8
+ end
9
+ puts hex
10
+ end
11
+
12
+ MAGIC = [
13
+ { :name => :magic, :length => 4, :format => "V" },
14
+ ]
15
+
16
+ EVENT_HEADER = [
17
+ { :name => :timestamp, :length => 4, :format => "V" },
18
+ { :name => :event_type, :length => 1, :format => "C" },
19
+ { :name => :server_id, :length => 4, :format => "V" },
20
+ { :name => :event_length, :length => 4, :format => "V" },
21
+ { :name => :next_position, :length => 4, :format => "V" },
22
+ { :name => :flags, :length => 2, :format => "v" },
23
+ ]
24
+
25
+ EVENT_TYPES = [
26
+ :unknown_event, # 0
27
+ :start_event_v3, # 1
28
+ :query_event, # 2
29
+ :stop_event, # 3
30
+ :rotate_event, # 4
31
+ :intvar_event, # 5
32
+ :load_event, # 6
33
+ :slave_event, # 7
34
+ :create_file_event, # 8
35
+ :append_block_event, # 9
36
+ :exec_load_event, # 10
37
+ :delete_file_event, # 11
38
+ :new_load_event, # 12
39
+ :rand_event, # 13
40
+ :user_var_event, # 14
41
+ :format_description_event, # 15
42
+ :xid_event, # 16
43
+ :begin_load_query_event, # 17
44
+ :execute_load_query_event, # 18
45
+ :table_map_event, # 19
46
+ :pre_ga_write_rows_event, # 20
47
+ :pre_ga_update_rows_event, # 21
48
+ :pre_ga_delete_rows_event, # 22
49
+ :write_rows_event, # 23
50
+ :update_rows_event, # 24
51
+ :delete_rows_event, # 25
52
+ :incident_event, # 26
53
+ :heartbeat_log_event, # 27
54
+ ]
55
+
56
+ EVENT_FLAGS = {
57
+ :binlog_in_use => 0x01,
58
+ :thread_specific => 0x04,
59
+ :suppress_use => 0x08,
60
+ :artificial => 0x20,
61
+ :relay_log => 0x40,
62
+ }
63
+
64
+ EVENT_FORMATS = {
65
+ :format_description_event => [
66
+ { :name => :binlog_version, :length => 2, :format => "v" },
67
+ { :name => :server_version, :length => 50, :format => "A50" },
68
+ { :name => :create_timestamp, :length => 4, :format => "V" },
69
+ { :name => :header_length, :length => 1, :format => "C" },
70
+ ],
71
+ :rotate_event => [
72
+ { :name => :pos, :length => 8, :format => "Q" },
73
+ ],
74
+ :query_event => [
75
+ { :name => :thread_id, :length => 4, :format => "V" },
76
+ { :name => :elapsed_time, :length => 4, :format => "V" },
77
+ { :name => :db_length, :length => 1, :format => "C" },
78
+ { :name => :error_code, :length => 2, :format => "v" },
79
+ { :name => :status_length, :length => 2, :format => "v" },
80
+ ],
81
+ :intvar_event => [
82
+ { :name => :intvar_type, :length => 1, :format => "C" },
83
+ { :name => :intvar_value, :length => 8, :format => "Q" },
84
+ ],
85
+ :xid_event => [
86
+ { :name => :xid, :length => 8, :format => "Q" },
87
+ ],
88
+ :rand_event => [ # Untested
89
+ { :name => :seed1, :length => 8, :format => "Q" },
90
+ { :name => :seed2, :length => 8, :format => "Q" },
91
+ ],
92
+ }
93
+
94
+ class UnsupportedVersionException < Exception; end
95
+ class MalformedBinlogException < Exception; end
96
+ class ZeroReadException < Exception; end
97
+ class ShortReadException < Exception; end
98
+
99
+ class Binlog
100
+ attr_reader :fde
101
+ attr_accessor :reader
102
+ attr_accessor :parser
103
+ attr_accessor :event_field_parser
104
+ attr_accessor :filter_event_types
105
+ attr_accessor :filter_flags
106
+ attr_accessor :max_query_length
107
+
108
+ def initialize(reader_class, *args)
109
+ @reader = reader_class.new(*args)
110
+ @parser = BinlogParser.new(self)
111
+ @event_field_parser = BinlogEventFieldParser.new(self)
112
+ @fde = nil
113
+ @filter_event_types = nil
114
+ @filter_flags = nil
115
+ @max_query_length = 1048576
116
+ end
117
+
118
+ def rewind
119
+ reader.rewind
120
+ read_file_header
121
+ end
122
+
123
+ def read_additional_fields(event_type, header, fields)
124
+ if event_field_parser.methods.include? event_type.to_s
125
+ event_field_parser.send(event_type, header, fields)
126
+ end
127
+ end
128
+
129
+ def skip_event(header)
130
+ reader.skip(header)
131
+ end
132
+
133
+ def read_event_header
134
+ header = parser.read_and_unpack(EVENT_HEADER)
135
+ flags = EVENT_FLAGS.inject([]) do |result, (flag_name, flag_bit_value)|
136
+ if (header[:flags] & flag_bit_value) != 0
137
+ result << flag_name
138
+ end
139
+ result
140
+ end
141
+ header[:flags] = flags
142
+ header
143
+ end
144
+
145
+ def read_event_content(header)
146
+ content = nil
147
+
148
+ event_type = EVENT_TYPES[header[:event_type]]
149
+ if EVENT_FORMATS.include? event_type
150
+ content = parser.read_and_unpack(EVENT_FORMATS[event_type])
151
+ else
152
+ content = {}
153
+ end
154
+
155
+ read_additional_fields(event_type, header, content)
156
+
157
+ skip_event(header)
158
+ content
159
+ end
160
+
161
+ def read_event
162
+ while true
163
+ skip_this_event = false
164
+ return nil if reader.end?
165
+
166
+ filename = reader.filename
167
+ position = reader.position
168
+
169
+ unless header = read_event_header
170
+ return nil
171
+ end
172
+
173
+ event_type = EVENT_TYPES[header[:event_type]]
174
+
175
+ if @filter_event_types
176
+ unless @filter_event_types.include? event_type or
177
+ event_type == :format_description_event
178
+ skip_event(header)
179
+ skip_this_event = true
180
+ end
181
+ end
182
+
183
+ if @filter_flags
184
+ unless @filter_flags.include? header[:flags]
185
+ skip_event(header)
186
+ skip_this_event = true
187
+ end
188
+ end
189
+
190
+ unless [:rotate_event, :format_description_event].include? event_type
191
+ next if skip_this_event
192
+ end
193
+
194
+ content = read_event_content(header)
195
+ content
196
+
197
+ case event_type
198
+ when :rotate_event
199
+ reader.rotate(content[:name], content[:pos])
200
+ when :format_description_event
201
+ process_fde(content)
202
+ end
203
+
204
+ break
205
+ end
206
+
207
+ {
208
+ :type => event_type,
209
+ :filename => filename,
210
+ :position => position,
211
+ :header => header,
212
+ :event => content,
213
+ }
214
+ end
215
+
216
+ def process_fde(fde)
217
+ if (version = fde[:binlog_version]) != 4
218
+ raise UnsupportedVersionException.new("Binlog version #{version} is not supported")
219
+ end
220
+
221
+ @fde = {
222
+ :header_length => fde[:header_length],
223
+ :binlog_version => fde[:binlog_version],
224
+ :server_version => fde[:server_version],
225
+ }
226
+ end
227
+
228
+ def each_event
229
+ while event = read_event
230
+ yield event
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,65 @@
1
+ module MysqlBinlog
2
+ class BinlogFileReader
3
+ def initialize(filename)
4
+ @filename = filename
5
+ @binlog = nil
6
+
7
+ open_file(filename)
8
+ end
9
+
10
+ def open_file(filename)
11
+ @filename = filename
12
+ @binlog = File.open(filename, mode="r")
13
+
14
+ if (magic = read(4).unpack("V").first) != 1852400382
15
+ raise MalformedBinlogException.new("Magic number #{magic} is incorrect")
16
+ end
17
+ end
18
+
19
+ def rotate(filename, position)
20
+ open_file(filename)
21
+ seek(position)
22
+ end
23
+
24
+ def filename
25
+ @filename
26
+ end
27
+
28
+ def position
29
+ @binlog.tell
30
+ end
31
+
32
+ def rewind
33
+ @binlog.rewind
34
+ end
35
+
36
+ def seek(pos)
37
+ @binlog.seek(pos)
38
+ end
39
+
40
+ def end?
41
+ @binlog.eof?
42
+ end
43
+
44
+ def remaining(header)
45
+ header[:next_position] - @binlog.tell
46
+ end
47
+
48
+ def skip(header)
49
+ seek(header[:next_position])
50
+ end
51
+
52
+ def read(length)
53
+ return "" if length == 0
54
+ data = @binlog.read(length)
55
+ if !data
56
+ raise MalformedBinlogException.new
57
+ elsif data.length == 0
58
+ raise ZeroReadException.new
59
+ elsif data.length < length
60
+ raise ShortReadException.new
61
+ end
62
+ data
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,64 @@
1
+ module MysqlBinlog
2
+ class BinlogStreamReader
3
+ def initialize(connection, filename, position)
4
+ require 'mysql_binlog_dump'
5
+ @filename = nil
6
+ @position = nil
7
+ @packet_data = nil
8
+ @packet_pos = nil
9
+ @connection = connection
10
+ MysqlBinlogDump.binlog_dump(connection, filename, position)
11
+ end
12
+
13
+ def rotate(filename, position)
14
+ puts "rotate called with #{filename}:#{position}"
15
+ @filename = filename
16
+ @position = position
17
+ end
18
+
19
+ def filename
20
+ @filename
21
+ end
22
+
23
+ def position
24
+ @position
25
+ end
26
+
27
+ def rewind
28
+ false
29
+ end
30
+
31
+ def tell
32
+ @packet_pos
33
+ end
34
+
35
+ def end?
36
+ false
37
+ end
38
+
39
+ def remaining(header)
40
+ @packet_data.length - @packet_pos
41
+ end
42
+
43
+ def skip(header)
44
+ @packet_data = nil
45
+ @packet_pos = nil
46
+ end
47
+
48
+ def read_packet
49
+ @packet_data = MysqlBinlogDump.next_packet(@connection)
50
+ @packet_pos = 0
51
+ end
52
+
53
+ def read(length)
54
+ unless @packet_data
55
+ read_packet
56
+ return nil unless @packet_data
57
+ end
58
+ pos = @packet_pos
59
+ @position += length if @position
60
+ @packet_pos += length
61
+ @packet_data[pos...(pos+length)]
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,4 @@
1
+ require 'mysql_binlog/mysql_binlog'
2
+ require 'mysql_binlog/binlog_event_field_parser'
3
+ require 'mysql_binlog/reader/binlog_file_reader'
4
+ require 'mysql_binlog/reader/binlog_stream_reader'
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mysql_binlog
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Jeremy Cole
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-06-24 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: Library for parsing MySQL binary logs in Ruby
22
+ email: jeremy@jcole.us
23
+ executables:
24
+ - mysql_binlog_dump
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - lib/mysql_binlog.rb
31
+ - lib/mysql_binlog/mysql_binlog.rb
32
+ - lib/mysql_binlog/binlog_parser.rb
33
+ - lib/mysql_binlog/binlog_event_field_parser.rb
34
+ - lib/mysql_binlog/reader/binlog_file_reader.rb
35
+ - lib/mysql_binlog/reader/binlog_stream_reader.rb
36
+ - bin/mysql_binlog_dump
37
+ homepage: http://jcole.us/
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ hash: 3
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.8.10
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: MySQL Binary Log Parser
70
+ test_files: []
71
+