mysql_binlog 0.3.2 → 0.3.3

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d4cb94c1699fea0fcfb90ef92d1d09d15dadb5a5
4
+ data.tar.gz: d6dcf06a12be0f42347344d250156f34a2ecf962
5
+ SHA512:
6
+ metadata.gz: 7920efabe0e87c67d041d6712afeb2d27cd72978504e314ab4b9ee78fdeeaff2895e363c896a45972d3c0eeabd6ad84495c8f591013e137ed65893762111291b
7
+ data.tar.gz: c82e4f6b2fbf47de40505c70a310eca59e96f03c2cd85da4435cb69d582d80a3a597ea865ef43257547a927df83b03caa42b29bcc29f36b374b0e7a0f649b12c
@@ -15,13 +15,16 @@ def usage(exit_code, message = nil)
15
15
 
16
16
  Usage:
17
17
  To read from a binary log file on disk:
18
- mysql_binlog_dump [options] -f <filename>
18
+ mysql_binlog_dump [options] <filename(s)>
19
19
 
20
20
  --help, -?
21
21
  Show this help.
22
22
 
23
23
  --file, -f <filename>
24
- Read from a binary log file on disk.
24
+ Read from a binary log file on disk (deprecated).
25
+
26
+ --checksum, -c
27
+ Enable CRC32 checksums.
25
28
 
26
29
  --debug, -d
27
30
  Debug reading from the binary log, showing calls into the reader and the
@@ -45,13 +48,16 @@ end
45
48
 
46
49
  @options = OpenStruct.new
47
50
  @options.file = nil
51
+ @options.checksum = nil
48
52
  @options.debug = false
49
53
  @options.tail = false
50
54
  @options.rotate = false
55
+ @options.filenames = []
51
56
 
52
57
  getopt_options = [
53
58
  [ "--help", "-?", GetoptLong::NO_ARGUMENT ],
54
59
  [ "--file", "-f", GetoptLong::REQUIRED_ARGUMENT ],
60
+ [ "--checksum", "-c", GetoptLong::NO_ARGUMENT ],
55
61
  [ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
56
62
  [ "--tail", "-t", GetoptLong::NO_ARGUMENT ],
57
63
  [ "--rotate", "-r", GetoptLong::NO_ARGUMENT ],
@@ -64,7 +70,9 @@ getopt.each do |opt, arg|
64
70
  when "--help"
65
71
  usage 0
66
72
  when "--file"
67
- @options.file = arg
73
+ @options.filenames << arg
74
+ when "--checksum"
75
+ @options.checksum = :crc32
68
76
  when "--debug"
69
77
  @options.debug = true
70
78
  when "--tail"
@@ -74,34 +82,25 @@ getopt.each do |opt, arg|
74
82
  end
75
83
  end
76
84
 
77
- unless @options.file
78
- usage 1, "A file must be provided with --file/-f"
79
- end
85
+ @options.filenames.concat(ARGV)
80
86
 
81
- reader = BinlogFileReader.new(@options.file)
82
- if @options.debug
83
- reader = DebuggingReader.new(reader, :data => true, :calls => true)
87
+ if @options.filenames.empty?
88
+ usage 1, "One or more filenames must be provided"
84
89
  end
85
- binlog = Binlog.new(reader)
86
90
 
87
- if @options.tail
88
- reader.tail = true
89
- else
90
- reader.tail = false
91
- end
91
+ @options.filenames.each do |filename|
92
+ reader = BinlogFileReader.new(filename)
93
+ if @options.debug
94
+ reader = DebuggingReader.new(reader, :data => true, :calls => true)
95
+ end
96
+ binlog = Binlog.new(reader)
97
+ binlog.checksum = @options.checksum
92
98
 
93
- if @options.rotate
94
- binlog.ignore_rotate = false
95
- else
96
- binlog.ignore_rotate = true
97
- end
99
+ reader.tail = @options.tail
100
+ binlog.ignore_rotate = !@options.rotate
98
101
 
99
- binlog.each_event do |event|
100
- puts "%-30s%-20s%20d" % [
101
- event[:type],
102
- event[:filename],
103
- event[:position],
104
- ]
105
- #pp event
106
- #puts
107
- end
102
+ binlog.each_event do |event|
103
+ pp event
104
+ puts
105
+ end
106
+ end
@@ -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].to_s
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
- :write_rows_event => 23, #
30
- :update_rows_event => 24, #
31
- :delete_rows_event => 25, #
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,9 @@ 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
- :write_rows_event,
48
- :update_rows_event,
49
- :delete_rows_event,
59
+ :write_rows_event_v1,
60
+ :update_rows_event_v1,
61
+ :delete_rows_event_v1,
50
62
  ]
51
63
 
52
64
  # Values for the +flags+ field that may appear in binary logs. There are
@@ -55,11 +67,14 @@ module MysqlBinlog
55
67
  #
56
68
  # Defined in sql/log_event.h line ~448
57
69
  EVENT_HEADER_FLAGS = {
58
- :binlog_in_use => 0x01, # LOG_EVENT_BINLOG_IN_USE_F
59
- :thread_specific => 0x04, # LOG_EVENT_THREAD_SPECIFIC_F
60
- :suppress_use => 0x08, # LOG_EVENT_SUPPRESS_USE_F
61
- :artificial => 0x20, # LOG_EVENT_ARTIFICIAL_F
62
- :relay_log => 0x40, # LOG_EVENT_RELAY_LOG_F
70
+ :binlog_in_use => 0x0001, # LOG_EVENT_BINLOG_IN_USE_F
71
+ :thread_specific => 0x0004, # LOG_EVENT_THREAD_SPECIFIC_F
72
+ :suppress_use => 0x0008, # LOG_EVENT_SUPPRESS_USE_F
73
+ :artificial => 0x0020, # LOG_EVENT_ARTIFICIAL_F
74
+ :relay_log => 0x0040, # LOG_EVENT_RELAY_LOG_F
75
+ :ignorable => 0x0080, # LOG_EVENT_IGNORABLE_F
76
+ :no_filter => 0x0100, # LOG_EVENT_NO_FILTER_F
77
+ :mts_isolate => 0x0200, # LOG_EVENT_MTS_ISOLATE_F
63
78
  }
64
79
 
65
80
  # A mapping array for all values that may appear in the +status+ field of
@@ -79,8 +94,15 @@ module MysqlBinlog
79
94
  :table_map_for_update, # 9 (Q_TABLE_MAP_FOR_UPDATE_CODE)
80
95
  :master_data_written, # 10 (Q_MASTER_DATA_WRITTEN_CODE)
81
96
  :invoker, # 11 (Q_INVOKER)
97
+ :updated_db_names, # 12 (Q_UPDATED_DB_NAMES)
98
+ :microseconds, # 13 (Q_MICROSECONDS)
99
+ :commit_ts, # 14 (Q_COMMIT_TS)
100
+ :commit_ts2, # 15
101
+ :explicit_defaults_for_timestamp, # 16
82
102
  ]
83
103
 
104
+ QUERY_EVENT_OVER_MAX_DBS_IN_EVENT_MTS = 254
105
+
84
106
  # A mapping hash for all values that may appear in the +flags2+ field of
85
107
  # a query_event.
86
108
  #
@@ -181,11 +203,13 @@ module MysqlBinlog
181
203
  def event_header
182
204
  header = {}
183
205
  header[:timestamp] = parser.read_uint32
184
- header[:event_type] = EVENT_TYPES[parser.read_uint8]
206
+ event_type = parser.read_uint8
207
+ header[:event_type] = EVENT_TYPES[event_type] || "unknown_#{event_type}".to_sym
185
208
  header[:server_id] = parser.read_uint32
186
209
  header[:event_length] = parser.read_uint32
187
210
  header[:next_position] = parser.read_uint32
188
211
  header[:flags] = parser.read_uint_bitmap_by_size_and_name(2, EVENT_HEADER_FLAGS)
212
+
189
213
  header
190
214
  end
191
215
 
@@ -212,6 +236,25 @@ module MysqlBinlog
212
236
  fields
213
237
  end
214
238
 
239
+ def _query_event_status_updated_db_names
240
+ db_count = parser.read_uint8
241
+ return nil if db_count == QUERY_EVENT_OVER_MAX_DBS_IN_EVENT_MTS
242
+
243
+ db_names = []
244
+ db_count.times do |n|
245
+ db_name = ""
246
+ loop do
247
+ c = reader.read(1)
248
+ break if c == "\0"
249
+ db_name << c
250
+ end
251
+ db_names << db_name
252
+ end
253
+
254
+ db_names
255
+ end
256
+ private :_query_event_status_updated_db_names
257
+
215
258
  # Parse a dynamic +status+ structure within a query_event, which consists
216
259
  # of a status_length (uint16) followed by a number of status variables
217
260
  # (determined by the +status_length+) each of which consist of:
@@ -223,7 +266,8 @@ module MysqlBinlog
223
266
  status_length = parser.read_uint16
224
267
  end_position = reader.position + status_length
225
268
  while reader.position < end_position
226
- status_type = QUERY_EVENT_STATUS_TYPES[parser.read_uint8]
269
+ status_type_id = parser.read_uint8
270
+ status_type = QUERY_EVENT_STATUS_TYPES[status_type_id]
227
271
  status[status_type] = case status_type
228
272
  when :flags2
229
273
  parser.read_uint_bitmap_by_size_and_name(4, QUERY_EVENT_FLAGS2)
@@ -252,6 +296,12 @@ module MysqlBinlog
252
296
  parser.read_uint16
253
297
  when :table_map_for_update
254
298
  parser.read_uint64
299
+ when :updated_db_names
300
+ _query_event_status_updated_db_names
301
+ when :commit_ts
302
+ parser.read_uint64
303
+ else
304
+ raise "Unknown status type #{status_type_id}"
255
305
  end
256
306
  end
257
307
 
@@ -334,7 +384,7 @@ module MysqlBinlog
334
384
  :precision => parser.read_uint8,
335
385
  :decimals => parser.read_uint8,
336
386
  }
337
- when :blob, :geometry
387
+ when :blob, :geometry, :json
338
388
  { :length_size => parser.read_uint8 }
339
389
  when :string, :var_string
340
390
  # The :string type sets a :real_type field to indicate the actual type
@@ -350,6 +400,10 @@ module MysqlBinlog
350
400
  else
351
401
  { :max_length => (((metadata >> 4) & 0x300) ^ 0x300) + (metadata & 0x00ff) }
352
402
  end
403
+ when :timestamp2, :datetime2, :time2
404
+ {
405
+ :decimals => parser.read_uint8,
406
+ }
353
407
  end
354
408
  end
355
409
  private :_table_map_event_column_metadata_read
@@ -376,7 +430,7 @@ module MysqlBinlog
376
430
  map_entry[:db] = parser.read_lpstringz
377
431
  map_entry[:table] = parser.read_lpstringz
378
432
  columns = parser.read_varint
379
- columns_type = parser.read_uint8_array(columns).map { |c| MYSQL_TYPES[c] }
433
+ columns_type = parser.read_uint8_array(columns).map { |c| MYSQL_TYPES[c] || "unknown_#{c}".to_sym }
380
434
  columns_metadata = _table_map_event_column_metadata(columns_type)
381
435
  columns_nullable = parser.read_bit_array(columns)
382
436
 
@@ -415,8 +469,9 @@ module MysqlBinlog
415
469
  fields[:flags] = parser.read_uint16
416
470
  fields[:columns] = columns.times.map do |c|
417
471
  descriptor_length = parser.read_uint32
472
+ column_type = parser.read_uint8
418
473
  @table_map[table_id][:columns][c][:description] = {
419
- :type => MYSQL_TYPES[parser.read_uint8],
474
+ :type => MYSQL_TYPES[column_type] || "unknown_#{column_type}".to_sym,
420
475
  :length => parser.read_uint32,
421
476
  :scale => parser.read_uint8,
422
477
  :character_set => COLLATION[parser.read_uint16],
@@ -433,41 +488,62 @@ module MysqlBinlog
433
488
  # Parse a single row image, which is comprised of a series of columns. Not
434
489
  # all columns are present in the row image, the columns_used array of true
435
490
  # and false values identifies which columns are present.
436
- def _generic_rows_event_row_image(header, fields, columns_used)
491
+ def _generic_rows_event_row_image_v1(header, fields, columns_used)
437
492
  row_image = []
493
+ start_position = reader.position
438
494
  columns_null = parser.read_bit_array(fields[:table][:columns].size)
439
495
  fields[:table][:columns].each_with_index do |column, column_index|
496
+ #puts "column #{column_index} #{column}: used=#{columns_used[column_index]}, null=#{columns_null[column_index]}"
440
497
  if !columns_used[column_index]
441
498
  row_image << nil
442
499
  elsif columns_null[column_index]
443
500
  row_image << { column_index => nil }
444
501
  else
502
+ value = parser.read_mysql_type(column[:type], column[:metadata])
445
503
  row_image << {
446
- column_index =>
447
- parser.read_mysql_type(column[:type], column[:metadata])
504
+ column_index => value,
448
505
  }
449
506
  end
450
507
  end
451
- row_image
508
+ end_position = reader.position
509
+
510
+ {
511
+ image: row_image,
512
+ size: end_position-start_position
513
+ }
514
+ end
515
+ private :_generic_rows_event_row_image_v1
516
+
517
+ def diff_row_images(before, after)
518
+ diff = {}
519
+ before.each_with_index do |before_column, index|
520
+ after_column = after[index]
521
+ before_value = before_column.first[1]
522
+ after_value = after_column.first[1]
523
+ if before_value != after_value
524
+ diff[index] = { before: before_value, after: after_value }
525
+ end
526
+ end
527
+ diff
452
528
  end
453
- private :_generic_rows_event_row_image
454
529
 
455
530
  # Parse the row images present in a row-based replication row event. This
456
531
  # is rather incomplete right now due missing support for many MySQL types,
457
532
  # but can parse some basic events.
458
- def _generic_rows_event_row_images(header, fields, columns_used)
533
+ def _generic_rows_event_row_images_v1(header, fields, columns_used)
459
534
  row_images = []
460
535
  end_position = reader.position + reader.remaining(header)
461
536
  while reader.position < end_position
462
537
  row_image = {}
463
538
  case header[:event_type]
464
- when :write_rows_event
465
- row_image[:after] = _generic_rows_event_row_image(header, fields, columns_used[:after])
466
- when :delete_rows_event
467
- row_image[:before] = _generic_rows_event_row_image(header, fields, columns_used[:before])
468
- when :update_rows_event
469
- row_image[:before] = _generic_rows_event_row_image(header, fields, columns_used[:before])
470
- row_image[:after] = _generic_rows_event_row_image(header, fields, columns_used[:after])
539
+ when :write_rows_event_v1
540
+ row_image[:after] = _generic_rows_event_row_image_v1(header, fields, columns_used[:after])
541
+ when :delete_rows_event_v1
542
+ row_image[:before] = _generic_rows_event_row_image_v1(header, fields, columns_used[:before])
543
+ when :update_rows_event_v1
544
+ row_image[:before] = _generic_rows_event_row_image_v1(header, fields, columns_used[:before])
545
+ row_image[:after] = _generic_rows_event_row_image_v1(header, fields, columns_used[:after])
546
+ row_image[:diff] = diff_row_images(row_image[:before][:image], row_image[:after][:image])
471
547
  end
472
548
  row_images << row_image
473
549
  end
@@ -481,7 +557,7 @@ module MysqlBinlog
481
557
 
482
558
  row_images
483
559
  end
484
- private :_generic_rows_event_row_images
560
+ private :_generic_rows_event_row_images_v1
485
561
 
486
562
  # Parse fields for any of the row-based replication row events:
487
563
  # * +Write_rows+ which is used for +INSERT+.
@@ -491,7 +567,7 @@ module MysqlBinlog
491
567
  # Implemented in sql/log_event.cc line ~8039
492
568
  # in Rows_log_event::write_data_header
493
569
  # and Rows_log_event::write_data_body
494
- def generic_rows_event(header)
570
+ def generic_rows_event_v1(header)
495
571
  fields = {}
496
572
  table_id = parser.read_uint48
497
573
  fields[:table] = @table_map[table_id]
@@ -499,21 +575,78 @@ module MysqlBinlog
499
575
  columns = parser.read_varint
500
576
  columns_used = {}
501
577
  case header[:event_type]
502
- when :write_rows_event
578
+ when :write_rows_event_v1
503
579
  columns_used[:after] = parser.read_bit_array(columns)
504
- when :delete_rows_event
580
+ when :delete_rows_event_v1
505
581
  columns_used[:before] = parser.read_bit_array(columns)
506
- when :update_rows_event
582
+ when :update_rows_event_v1
507
583
  columns_used[:before] = parser.read_bit_array(columns)
508
584
  columns_used[:after] = parser.read_bit_array(columns)
509
585
  end
510
- fields[:row_image] = _generic_rows_event_row_images(header, fields, columns_used)
586
+ fields[:row_image] = _generic_rows_event_row_images_v1(header, fields, columns_used)
511
587
  fields
512
588
  end
513
589
 
514
- alias :write_rows_event :generic_rows_event
515
- alias :update_rows_event :generic_rows_event
516
- alias :delete_rows_event :generic_rows_event
590
+ alias :write_rows_event_v1 :generic_rows_event_v1
591
+ alias :update_rows_event_v1 :generic_rows_event_v1
592
+ alias :delete_rows_event_v1 :generic_rows_event_v1
593
+
594
+ def rows_query_log_event(header)
595
+ reader.read(1) # skip useless byte length which is unused
596
+ { query: reader.read(header[:payload_length]-1) }
597
+ end
598
+
599
+ def in_hex(bytes)
600
+ bytes.each_byte.map { |c| "%02x" % c.ord }.join
601
+ end
602
+
603
+ def format_gtid_sid(sid)
604
+ [0..3, 4..5, 6..7, 8..9, 10..15].map { |r| in_hex(sid[r]) }.join("-")
605
+ end
517
606
 
607
+ # 6d9190a2-cca6-11e8-aa8c-42010aef0019:551845019
608
+ def format_gtid(sid, gno_or_ivs)
609
+ "#{format_gtid_sid(sid)}:#{gno_or_ivs}"
610
+ end
611
+
612
+ def previous_gtids_log_event(header)
613
+ n_sids = parser.read_uint64
614
+
615
+ gtids = []
616
+ n_sids.times do
617
+ sid = parser.read_nstring(16)
618
+ n_ivs = parser.read_uint64
619
+ ivs = []
620
+ n_ivs.times do
621
+ iv_start = parser.read_uint64
622
+ iv_end = parser.read_uint64
623
+ ivs << "#{iv_start}-#{iv_end}"
624
+ end
625
+ gtids << format_gtid(sid, ivs.join(":"))
626
+ end
627
+
628
+ {
629
+ previous_gtids: gtids
630
+ }
631
+ end
632
+
633
+ def gtid_log_event(header)
634
+ flags = parser.read_uint8
635
+ sid = parser.read_nstring(16)
636
+ gno = parser.read_uint64
637
+ lts_type = parser.read_uint8
638
+ lts_last_committed = parser.read_uint64
639
+ lts_sequence_number = parser.read_uint64
640
+
641
+ {
642
+ flags: flags,
643
+ gtid: format_gtid(sid, gno),
644
+ lts: {
645
+ type: lts_type,
646
+ last_committed: lts_last_committed,
647
+ sequence_number: lts_sequence_number,
648
+ },
649
+ }
650
+ end
518
651
  end
519
652
  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,
@@ -64,6 +70,17 @@ module MysqlBinlog
64
70
  a + (b << 8) + (c << 16)
65
71
  end
66
72
 
73
+ # Read an unsigned 24-bit (3-byte) big-endian integer.
74
+ def read_uint24_be
75
+ a, b = reader.read(3).unpack("nC")
76
+ (a << 8) + b
77
+ end
78
+
79
+ # Read an unsigned 32-bit (4-byte) integer.
80
+ def read_uint32_be
81
+ reader.read(4).unpack("N").first
82
+ end
83
+
67
84
  # Read an unsigned 32-bit (4-byte) integer.
68
85
  def read_uint32
69
86
  reader.read(4).unpack("V").first
@@ -75,6 +92,12 @@ module MysqlBinlog
75
92
  a + (b << 8)
76
93
  end
77
94
 
95
+ # Read an unsigned 40-bit (5-byte) big-endian integer.
96
+ def read_uint40_be
97
+ a, b = reader.read(5).unpack("NC")
98
+ (a << 8) + b
99
+ end
100
+
78
101
  # Read an unsigned 48-bit (6-byte) integer.
79
102
  def read_uint48
80
103
  a, b, c = reader.read(6).unpack("vvv")
@@ -89,7 +112,12 @@ module MysqlBinlog
89
112
 
90
113
  # Read an unsigned 64-bit (8-byte) integer.
91
114
  def read_uint64
92
- reader.read(8).unpack("Q").first
115
+ reader.read(8).unpack("Q<").first
116
+ end
117
+
118
+ # Read an unsigned 64-bit (8-byte) integer.
119
+ def read_uint64_be
120
+ reader.read(8).unpack("Q>").first
93
121
  end
94
122
 
95
123
  # Read a signed 8-bit (1-byte) integer.
@@ -99,13 +127,13 @@ module MysqlBinlog
99
127
 
100
128
  # Read a signed 16-bit (2-byte) big-endian integer.
101
129
  def read_int16_be
102
- reader.read(2).reverse.unpack('s').first
130
+ reader.read(2).unpack('n').first
103
131
  end
104
132
 
105
133
  # Read a signed 24-bit (3-byte) big-endian integer.
106
134
  def read_int24_be
107
135
  a, b, c = reader.read(3).unpack('CCC')
108
- if a & 128
136
+ if (a & 128) == 0
109
137
  (a << 16) | (b << 8) | c
110
138
  else
111
139
  (-1 << 24) | (a << 16) | (b << 8) | c
@@ -114,9 +142,9 @@ module MysqlBinlog
114
142
 
115
143
  # Read a signed 32-bit (4-byte) big-endian integer.
116
144
  def read_int32_be
117
- reader.read(4).reverse.unpack('l').first
145
+ reader.read(4).unpack('N').first
118
146
  end
119
-
147
+
120
148
  def read_uint_by_size(size)
121
149
  case size
122
150
  when 1
@@ -272,7 +300,7 @@ module MysqlBinlog
272
300
  str << value.to_s
273
301
  end
274
302
 
275
- BigDecimal.new(str)
303
+ BigDecimal(str)
276
304
  end
277
305
 
278
306
  # Read an array of unsigned 8-bit (1-byte) integers.
@@ -359,6 +387,41 @@ module MysqlBinlog
359
387
  ]
360
388
  end
361
389
 
390
+ def convert_mysql_type_datetimef(int_part, frac_part)
391
+ year_month = extract_bits(int_part, 17, 22)
392
+ year = year_month / 13
393
+ month = year_month % 13
394
+ day = extract_bits(int_part, 5, 17)
395
+ hour = extract_bits(int_part, 5, 12)
396
+ minute = extract_bits(int_part, 6, 6)
397
+ second = extract_bits(int_part, 6, 0)
398
+
399
+ "%04i-%02i-%02i %02i:%02i:%02i.%06i" % [
400
+ year,
401
+ month,
402
+ day,
403
+ hour,
404
+ minute,
405
+ second,
406
+ frac_part,
407
+ ]
408
+ end
409
+
410
+ def read_datetimef(decimals)
411
+ int_part = read_uint40_be
412
+ frac_part = case decimals
413
+ when 0
414
+ 0
415
+ when 1, 2
416
+ read_uint8 * 10000
417
+ when 3, 4
418
+ read_uint16_be * 100
419
+ when 5, 6
420
+ read_uint24_be
421
+ end
422
+ convert_mysql_type_datetimef(int_part, frac_part)
423
+ end
424
+
362
425
  # Read a single field, provided the MySQL column type as a symbol. Not all
363
426
  # types are currently supported.
364
427
  def read_mysql_type(type, metadata=nil)
@@ -382,7 +445,7 @@ module MysqlBinlog
382
445
  when :varchar, :string
383
446
  prefix_size = (metadata[:max_length] > 255) ? 2 : 1
384
447
  read_lpstring(prefix_size)
385
- when :blob, :geometry
448
+ when :blob, :geometry, :json
386
449
  read_lpstring(metadata[:length_size])
387
450
  when :timestamp
388
451
  read_uint32
@@ -394,6 +457,8 @@ module MysqlBinlog
394
457
  convert_mysql_type_time(read_uint24)
395
458
  when :datetime
396
459
  convert_mysql_type_datetime(read_uint64)
460
+ when :datetime2
461
+ read_datetimef(metadata[:decimals])
397
462
  when :enum, :set
398
463
  read_uint_by_size(metadata[:size])
399
464
  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, mode="r")
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[:next_position] - @binlog.tell
72
+ header[:payload_end] - @binlog.tell
73
73
  end
74
74
 
75
75
  def skip(header)
@@ -1,3 +1,3 @@
1
1
  module MysqlBinlog
2
- VERSION = "0.3.2"
2
+ VERSION = "0.3.3"
3
3
  end
metadata CHANGED
@@ -1,32 +1,23 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: mysql_binlog
3
- version: !ruby/object:Gem::Version
4
- hash: 23
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.3
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: 2019-07-13 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
25
17
  extensions: []
26
-
27
18
  extra_rdoc_files: []
28
-
29
- files:
19
+ files:
20
+ - bin/mysql_binlog_dump
30
21
  - lib/mysql_binlog.rb
31
22
  - lib/mysql_binlog/binlog.rb
32
23
  - lib/mysql_binlog/binlog_event_parser.rb
@@ -36,40 +27,27 @@ files:
36
27
  - lib/mysql_binlog/reader/binlog_stream_reader.rb
37
28
  - lib/mysql_binlog/reader/debugging_reader.rb
38
29
  - lib/mysql_binlog/version.rb
39
- - bin/mysql_binlog_dump
40
30
  homepage: http://jcole.us/
41
31
  licenses: []
42
-
32
+ metadata: {}
43
33
  post_install_message:
44
34
  rdoc_options: []
45
-
46
- require_paths:
35
+ require_paths:
47
36
  - lib
48
- required_ruby_version: !ruby/object:Gem::Requirement
49
- none: false
50
- requirements:
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
51
39
  - - ">="
52
- - !ruby/object:Gem::Version
53
- hash: 3
54
- segments:
55
- - 0
56
- version: "0"
57
- required_rubygems_version: !ruby/object:Gem::Requirement
58
- none: false
59
- requirements:
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
60
44
  - - ">="
61
- - !ruby/object:Gem::Version
62
- hash: 3
63
- segments:
64
- - 0
65
- version: "0"
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
66
47
  requirements: []
67
-
68
48
  rubyforge_project:
69
- rubygems_version: 1.8.10
49
+ rubygems_version: 2.5.2.3
70
50
  signing_key:
71
- specification_version: 3
51
+ specification_version: 4
72
52
  summary: MySQL Binary Log Parser
73
53
  test_files: []
74
-
75
- has_rdoc: