mysql_binlog 0.1.5 → 0.1.6
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 +5 -0
- data/lib/mysql_binlog/binlog.rb +31 -0
- data/lib/mysql_binlog/binlog_event_parser.rb +54 -9
- data/lib/mysql_binlog/binlog_field_parser.rb +49 -33
- data/lib/mysql_binlog/reader/binlog_file_reader.rb +1 -0
- data/lib/mysql_binlog/reader/binlog_stream_reader.rb +3 -1
- metadata +3 -3
data/lib/mysql_binlog.rb
CHANGED
@@ -4,3 +4,8 @@ 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'
|
7
|
+
|
8
|
+
# The MysqlBinlog module contains a series of classes for reading and
|
9
|
+
# parsing binary log events from MySQL binary logs.
|
10
|
+
module MysqlBinlog
|
11
|
+
end
|
data/lib/mysql_binlog/binlog.rb
CHANGED
@@ -1,9 +1,37 @@
|
|
1
1
|
module MysqlBinlog
|
2
|
+
# This version of the binary log format is not supported by this library.
|
2
3
|
class UnsupportedVersionException < Exception; end
|
4
|
+
|
5
|
+
# An error was encountered when trying to read the log, which was likely
|
6
|
+
# due to garbage data in the log. Continuing is likely impossible.
|
3
7
|
class MalformedBinlogException < Exception; end
|
8
|
+
|
9
|
+
# When attempting a read, no data was returned.
|
4
10
|
class ZeroReadException < Exception; end
|
11
|
+
|
12
|
+
# When attempting a read, fewer bytes of data were returned than were
|
13
|
+
# requested by the reader, likely indicating a truncated file or corrupted
|
14
|
+
# event.
|
5
15
|
class ShortReadException < Exception; end
|
6
16
|
|
17
|
+
# Read a binary log, parsing and returning events.
|
18
|
+
#
|
19
|
+
# == Examples
|
20
|
+
#
|
21
|
+
# A basic example of using the Binlog class:
|
22
|
+
#
|
23
|
+
# require 'mysql_binlog'
|
24
|
+
# include MysqlBinlog
|
25
|
+
#
|
26
|
+
# # Open a binary log from a file on disk.
|
27
|
+
# binlog = Binlog.new(BinlogFileReader.new("mysql-bin.000001"))
|
28
|
+
#
|
29
|
+
# # Iterate over all events from the log, printing the event type (such
|
30
|
+
# # as :query_event, :write_rows_event, etc.)
|
31
|
+
# binlog.each_event do |event|
|
32
|
+
# puts event[:type]
|
33
|
+
# end
|
34
|
+
#
|
7
35
|
class Binlog
|
8
36
|
attr_reader :fde
|
9
37
|
attr_accessor :reader
|
@@ -35,6 +63,7 @@ module MysqlBinlog
|
|
35
63
|
def skip_event(header)
|
36
64
|
reader.skip(header)
|
37
65
|
end
|
66
|
+
private :skip_event
|
38
67
|
|
39
68
|
# Read the content of the event, which follows the header.
|
40
69
|
def read_event_fields(header)
|
@@ -53,6 +82,7 @@ module MysqlBinlog
|
|
53
82
|
|
54
83
|
fields
|
55
84
|
end
|
85
|
+
private :read_event_fields
|
56
86
|
|
57
87
|
# Scan events until finding one that isn't rejected by the filter rules.
|
58
88
|
# If there are no filter rules, this will return the next event provided
|
@@ -130,6 +160,7 @@ module MysqlBinlog
|
|
130
160
|
:server_version => fde[:server_version],
|
131
161
|
}
|
132
162
|
end
|
163
|
+
private :process_fde
|
133
164
|
|
134
165
|
# Iterate through all events.
|
135
166
|
def each_event
|
@@ -9,7 +9,7 @@ module MysqlBinlog
|
|
9
9
|
{ :name => :flags, :length => 2, :format => "v" },
|
10
10
|
]
|
11
11
|
|
12
|
-
# Values for the
|
12
|
+
# Values for the +flags+ field that may appear in binary logs. There are
|
13
13
|
# several other values that never appear in a file but may be used
|
14
14
|
# in events in memory.
|
15
15
|
EVENT_HEADER_FLAGS = {
|
@@ -76,15 +76,26 @@ module MysqlBinlog
|
|
76
76
|
:relaxed_unique_checks => 1 << 27,
|
77
77
|
}
|
78
78
|
|
79
|
+
# A mapping array for all values that may appear in the +Intvar_type+ field
|
80
|
+
# of an intvar_event.
|
79
81
|
INTVAR_EVENT_INTVAR_TYPES = [
|
80
82
|
nil,
|
81
83
|
:last_insert_id,
|
82
84
|
:insert_id,
|
83
85
|
]
|
84
86
|
|
87
|
+
# Parse binary log events from a provided binary log. Must be driven
|
88
|
+
# externally, but handles all the details of parsing an event header
|
89
|
+
# and the content of the various event types.
|
85
90
|
class BinlogEventParser
|
91
|
+
# The binary log object this event parser will parse events from.
|
86
92
|
attr_accessor :binlog
|
93
|
+
|
94
|
+
# The binary log reader extracted from the binlog object for convenience.
|
87
95
|
attr_accessor :reader
|
96
|
+
|
97
|
+
# The binary log field parser extracted from the binlog object for
|
98
|
+
# convenience.
|
88
99
|
attr_accessor :parser
|
89
100
|
|
90
101
|
def initialize(binlog_instance)
|
@@ -94,12 +105,12 @@ module MysqlBinlog
|
|
94
105
|
@table_map = {}
|
95
106
|
end
|
96
107
|
|
97
|
-
# Parse an event header, described by the EVENT_HEADER structure above.
|
108
|
+
# Parse an event header, described by the +EVENT_HEADER+ structure above.
|
98
109
|
def event_header
|
99
110
|
header = parser.read_and_unpack(EVENT_HEADER)
|
100
111
|
|
101
|
-
# Merge the read
|
102
|
-
# the flags by name instead of returning the bitmap as an integer.
|
112
|
+
# Merge the read +flags+ bitmap with the +EVENT_HEADER_FLAGS+ hash to
|
113
|
+
# return the flags by name instead of returning the bitmap as an integer.
|
103
114
|
flags = EVENT_HEADER_FLAGS.inject([]) do |result, (flag_name, flag_bit_value)|
|
104
115
|
if (header[:flags] & flag_bit_value) != 0
|
105
116
|
result << flag_name
|
@@ -107,7 +118,7 @@ module MysqlBinlog
|
|
107
118
|
result
|
108
119
|
end
|
109
120
|
|
110
|
-
# Overwrite the integer version of
|
121
|
+
# Overwrite the integer version of +flags+ with the array of names.
|
111
122
|
header[:flags] = flags
|
112
123
|
|
113
124
|
header
|
@@ -135,7 +146,7 @@ module MysqlBinlog
|
|
135
146
|
# Parse a dynamic +status+ structure within a query_event, which consists
|
136
147
|
# of a status_length (uint16) followed by a number of status variables
|
137
148
|
# (determined by the +status_length+) each of which consist of:
|
138
|
-
# * A type code (uint8), one of QUERY_EVENT_STATUS_TYPES
|
149
|
+
# * A type code (+uint8+), one of +QUERY_EVENT_STATUS_TYPES+.
|
139
150
|
# * The content itself, determined by the type. Additional processing is
|
140
151
|
# required based on the type.
|
141
152
|
def _query_event_status(header, fields)
|
@@ -176,6 +187,7 @@ module MysqlBinlog
|
|
176
187
|
end
|
177
188
|
status
|
178
189
|
end
|
190
|
+
private :_query_event_status
|
179
191
|
|
180
192
|
# Parse fields for a +Query+ event.
|
181
193
|
def query_event(header)
|
@@ -217,13 +229,44 @@ module MysqlBinlog
|
|
217
229
|
fields
|
218
230
|
end
|
219
231
|
|
220
|
-
# Parse
|
232
|
+
# Parse a number of bytes from the metadata section of a +Table_map+ event
|
233
|
+
# representing various fields based on the column type of the column
|
234
|
+
# being processed.
|
235
|
+
def _table_map_event_column_metadata_read(column_type)
|
236
|
+
case column_type
|
237
|
+
when :float, :double
|
238
|
+
{ :size => parser.read_uint8 }
|
239
|
+
when :varchar
|
240
|
+
{ :max_length => parser.read_uint16 }
|
241
|
+
when :bit
|
242
|
+
{
|
243
|
+
:size_bits => parser.read_uint8,
|
244
|
+
:size_bytes => parser.read_uint8,
|
245
|
+
}
|
246
|
+
when :newdecimal
|
247
|
+
{
|
248
|
+
:precision => parser.read_uint8,
|
249
|
+
:decimals => parser.read_uint8,
|
250
|
+
}
|
251
|
+
when :blob, :geometry
|
252
|
+
{ :length_size => parser.read_uint8 }
|
253
|
+
when :string, :var_string
|
254
|
+
{
|
255
|
+
:real_type => MYSQL_TYPES[parser.read_uint8],
|
256
|
+
:max_length => parser.read_uint8,
|
257
|
+
}
|
258
|
+
end
|
259
|
+
end
|
260
|
+
private :_table_map_event_column_metadata_read
|
261
|
+
|
262
|
+
# Parse column metadata within a +Table_map+ event.
|
221
263
|
def _table_map_event_column_metadata(columns_type)
|
222
264
|
length = parser.read_varint
|
223
|
-
columns_type.map do |
|
224
|
-
|
265
|
+
columns_type.map do |column|
|
266
|
+
_table_map_event_column_metadata_read(column)
|
225
267
|
end
|
226
268
|
end
|
269
|
+
private :_table_map_event_column_metadata
|
227
270
|
|
228
271
|
# Parse fields for a +Table_map+ event.
|
229
272
|
def table_map_event(header)
|
@@ -269,6 +312,7 @@ module MysqlBinlog
|
|
269
312
|
end
|
270
313
|
row_image
|
271
314
|
end
|
315
|
+
private :_generic_rows_event_row_image
|
272
316
|
|
273
317
|
# Parse the row images present in a row-based replication row event. This
|
274
318
|
# is rather incomplete right now due missing support for many MySQL types,
|
@@ -291,6 +335,7 @@ module MysqlBinlog
|
|
291
335
|
end
|
292
336
|
row_images
|
293
337
|
end
|
338
|
+
private :_generic_rows_event_row_images
|
294
339
|
|
295
340
|
# Parse fields for any of the row-based replication row events:
|
296
341
|
# * +Write_rows+ which is used for +INSERT+.
|
@@ -36,6 +36,8 @@ module MysqlBinlog
|
|
36
36
|
type_array
|
37
37
|
end
|
38
38
|
|
39
|
+
# Parse various types of standard and non-standard data types from a
|
40
|
+
# provided binary log using its reader to read data.
|
39
41
|
class BinlogFieldParser
|
40
42
|
attr_accessor :binlog
|
41
43
|
attr_accessor :reader
|
@@ -201,29 +203,46 @@ module MysqlBinlog
|
|
201
203
|
fields
|
202
204
|
end
|
203
205
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
206
|
+
# Extract a number of sequential bits at a given offset within an integer.
|
207
|
+
# This is used to unpack bit-packed fields.
|
208
|
+
def extract_bits(value, bits, offset)
|
209
|
+
(value & ((1 << bits) - 1) << offset) >> offset
|
210
|
+
end
|
211
|
+
|
212
|
+
# Convert a packed +DATE+ from a uint24 into a string representing
|
213
|
+
# the date.
|
214
|
+
def convert_mysql_type_date(value)
|
215
|
+
"%04i-%02i-%02i" % [
|
216
|
+
extract_bits(value, 15, 9),
|
217
|
+
extract_bits(value, 4, 5),
|
218
|
+
extract_bits(value, 5, 0),
|
219
|
+
]
|
220
|
+
end
|
221
|
+
|
222
|
+
# Convert a packed +TIME+ from a uint24 into a string representing
|
223
|
+
# the time.
|
224
|
+
def convert_mysql_type_time(value)
|
225
|
+
"%02i:%02i:%02i" % [
|
226
|
+
value / 10000,
|
227
|
+
(value % 10000) / 100,
|
228
|
+
value % 100,
|
229
|
+
]
|
230
|
+
end
|
231
|
+
|
232
|
+
# Convert a packed +DATETIME+ from a uint64 into a string representing
|
233
|
+
# the date and time.
|
234
|
+
def convert_mysql_type_datetime(value)
|
235
|
+
date = value / 1000000
|
236
|
+
time = value % 1000000
|
237
|
+
|
238
|
+
"%04i-%02i-%02i %02i:%02i:%02i" % [
|
239
|
+
date / 10000,
|
240
|
+
(date % 10000) / 100,
|
241
|
+
date % 100,
|
242
|
+
time / 10000,
|
243
|
+
(time % 10000) / 100,
|
244
|
+
time % 100,
|
245
|
+
]
|
227
246
|
end
|
228
247
|
|
229
248
|
# Read a single field, provided the MySQL column type as a symbol. Not all
|
@@ -248,24 +267,21 @@ module MysqlBinlog
|
|
248
267
|
read_varstring
|
249
268
|
when :varchar
|
250
269
|
read_lpstring(2)
|
251
|
-
when :blob
|
270
|
+
when :blob, :geometry
|
252
271
|
read_lpstring(metadata[:length_size])
|
253
272
|
when :timestamp
|
254
273
|
read_uint32
|
255
274
|
when :year
|
256
275
|
read_uint8 + 1900
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
276
|
+
when :date
|
277
|
+
convert_mysql_type_date(read_uint24)
|
278
|
+
when :time
|
279
|
+
convert_mysql_type_time(read_uint24)
|
280
|
+
when :datetime
|
281
|
+
convert_mysql_type_datetime(read_uint64)
|
261
282
|
#when :bit
|
262
|
-
#when :decimal
|
263
283
|
#when :newdecimal
|
264
|
-
#when :enum
|
265
|
-
#when :set
|
266
|
-
#when :geometry
|
267
284
|
end
|
268
285
|
end
|
269
|
-
|
270
286
|
end
|
271
287
|
end
|
@@ -1,4 +1,7 @@
|
|
1
1
|
module MysqlBinlog
|
2
|
+
# Read a binary log from a stream dumped using the +MysqlBinlogDump+
|
3
|
+
# library to request a +COM_BINLOG_DUMP+ from a MySQL server via the
|
4
|
+
# +Mysql+ library.
|
2
5
|
class BinlogStreamReader
|
3
6
|
def initialize(connection, filename, position)
|
4
7
|
require 'mysql_binlog_dump'
|
@@ -11,7 +14,6 @@ module MysqlBinlog
|
|
11
14
|
end
|
12
15
|
|
13
16
|
def rotate(filename, position)
|
14
|
-
puts "rotate called with #{filename}:#{position}"
|
15
17
|
@filename = filename
|
16
18
|
@position = position
|
17
19
|
end
|
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
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 6
|
10
|
+
version: 0.1.6
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jeremy Cole
|