innodb_ruby 0.9.14 → 0.12.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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +13 -18
  4. data/bin/innodb_space +654 -778
  5. data/lib/innodb/checksum.rb +26 -24
  6. data/lib/innodb/data_dictionary.rb +490 -550
  7. data/lib/innodb/data_type.rb +362 -325
  8. data/lib/innodb/field.rb +102 -89
  9. data/lib/innodb/fseg_entry.rb +22 -26
  10. data/lib/innodb/history.rb +21 -21
  11. data/lib/innodb/history_list.rb +72 -76
  12. data/lib/innodb/ibuf_bitmap.rb +36 -36
  13. data/lib/innodb/ibuf_index.rb +6 -2
  14. data/lib/innodb/index.rb +245 -276
  15. data/lib/innodb/inode.rb +166 -124
  16. data/lib/innodb/list.rb +196 -183
  17. data/lib/innodb/log.rb +139 -110
  18. data/lib/innodb/log_block.rb +100 -91
  19. data/lib/innodb/log_group.rb +53 -64
  20. data/lib/innodb/log_reader.rb +97 -96
  21. data/lib/innodb/log_record.rb +328 -279
  22. data/lib/innodb/lsn.rb +86 -81
  23. data/lib/innodb/page/blob.rb +82 -83
  24. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  25. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  26. data/lib/innodb/page/index.rb +965 -924
  27. data/lib/innodb/page/index_compressed.rb +34 -34
  28. data/lib/innodb/page/inode.rb +103 -112
  29. data/lib/innodb/page/sys.rb +13 -15
  30. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  31. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  32. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  33. data/lib/innodb/page/trx_sys.rb +204 -182
  34. data/lib/innodb/page/undo_log.rb +106 -92
  35. data/lib/innodb/page.rb +417 -414
  36. data/lib/innodb/record.rb +121 -164
  37. data/lib/innodb/record_describer.rb +66 -68
  38. data/lib/innodb/space.rb +381 -413
  39. data/lib/innodb/stats.rb +33 -35
  40. data/lib/innodb/system.rb +149 -171
  41. data/lib/innodb/undo_log.rb +129 -107
  42. data/lib/innodb/undo_record.rb +255 -247
  43. data/lib/innodb/util/buffer_cursor.rb +81 -79
  44. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  45. data/lib/innodb/version.rb +2 -2
  46. data/lib/innodb/xdes.rb +144 -142
  47. data/lib/innodb.rb +4 -5
  48. metadata +100 -25
@@ -1,84 +1,73 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  # Group of InnoDB logs files that make up the redo log.
4
- class Innodb::LogGroup
5
-
6
- # Initialize group given a set of sorted log files.
7
- def initialize(log_files)
8
- @logs = log_files.map { |fn| Innodb::Log.new(fn) }
9
- sizes = @logs.map { |log| log.size }
10
- raise "Log file sizes do not match" unless sizes.uniq.size == 1
11
- end
12
-
13
- # Iterate through all logs.
14
- def each_log
15
- unless block_given?
16
- return enum_for(:each_log)
4
+ module Innodb
5
+ class LogGroup
6
+ # Initialize group given a set of sorted log files.
7
+ def initialize(log_files)
8
+ @logs = log_files.map { |fn| Innodb::Log.new(fn) }
9
+ raise "Log file sizes do not match" unless @logs.map(&:size).uniq.size == 1
17
10
  end
18
11
 
19
- @logs.each do |log|
20
- yield log
21
- end
22
- end
12
+ # Iterate through all logs.
13
+ def each_log(&block)
14
+ return enum_for(:each_log) unless block_given?
23
15
 
24
- # Iterate through all blocks.
25
- def each_block
26
- unless block_given?
27
- return enum_for(:each_block)
16
+ @logs.each(&block)
28
17
  end
29
18
 
30
- each_log do |log|
31
- log.each_block do |block_index, block|
32
- yield block_index, block
19
+ # Iterate through all blocks.
20
+ def each_block(&block)
21
+ return enum_for(:each_block) unless block_given?
22
+
23
+ each_log do |log|
24
+ log.each_block(&block)
33
25
  end
34
26
  end
35
- end
36
27
 
37
- # The number of log files in the group.
38
- def logs
39
- @logs.count
40
- end
28
+ # The number of log files in the group.
29
+ def logs
30
+ @logs.count
31
+ end
41
32
 
42
- # Returns the log at the given position in the log group.
43
- def log(log_no)
44
- @logs.at(log_no)
45
- end
33
+ # Returns the log at the given position in the log group.
34
+ def log(log_no)
35
+ @logs.at(log_no)
36
+ end
46
37
 
47
- # The size in byes of each and every log in the group.
48
- def log_size
49
- @logs.first.size
50
- end
38
+ # The size in byes of each and every log in the group.
39
+ def log_size
40
+ @logs.first.size
41
+ end
51
42
 
52
- # The size of the log group (in bytes)
53
- def size
54
- @logs.first.size * @logs.count
55
- end
43
+ # The size of the log group (in bytes)
44
+ def size
45
+ @logs.first.size * @logs.count
46
+ end
56
47
 
57
- # The log group capacity (in bytes).
58
- def capacity
59
- @logs.first.capacity * @logs.count
60
- end
48
+ # The log group capacity (in bytes).
49
+ def capacity
50
+ @logs.first.capacity * @logs.count
51
+ end
61
52
 
62
- # Returns the LSN coordinates of the data at the start of the log group.
63
- def start_lsn
64
- [@logs.first.header[:start_lsn], Innodb::Log::LOG_HEADER_SIZE]
65
- end
53
+ # Returns the LSN coordinates of the data at the start of the log group.
54
+ def start_lsn
55
+ [@logs.first.header[:start_lsn], Innodb::Log::LOG_HEADER_SIZE]
56
+ end
66
57
 
67
- # Returns the LSN coordinates of the most recent (highest) checkpoint.
68
- def max_checkpoint_lsn
69
- checkpoint = @logs.first.checkpoint.max_by{|f,v| v[:number]}.last
70
- checkpoint.values_at(:lsn, :lsn_offset)
71
- end
58
+ # Returns the LSN coordinates of the most recent (highest) checkpoint.
59
+ def max_checkpoint_lsn
60
+ @logs.first.checkpoint.max_by(&:number).to_h.values_at(:lsn, :lsn_offset)
61
+ end
72
62
 
73
- # Returns a LogReader using the given LSN reference coordinates.
74
- def reader(lsn_coord = start_lsn)
75
- lsn_no, lsn_offset = lsn_coord
76
- lsn = Innodb::LSN.new(lsn_no, lsn_offset)
77
- Innodb::LogReader.new(lsn, self)
78
- end
63
+ # Returns a LogReader using the given LSN reference coordinates.
64
+ def reader(lsn_coordinates = start_lsn)
65
+ Innodb::LogReader.new(Innodb::LSN.new(*lsn_coordinates), self)
66
+ end
79
67
 
80
- # Parse and return a record at a given LSN.
81
- def record(lsn_no)
82
- reader.seek(lsn_no).record
68
+ # Parse and return a record at a given LSN.
69
+ def record(lsn_no)
70
+ reader.seek(lsn_no).record
71
+ end
83
72
  end
84
73
  end
@@ -1,116 +1,117 @@
1
- # -*- encoding : utf-8 -*-
2
-
3
- require "ostruct"
1
+ # frozen_string_literal: true
4
2
 
5
3
  # Representation of the log group as a seekable stream of log records.
6
- class Innodb::LogReader
7
-
8
- # Whether to checksum blocks.
9
- attr_accessor :checksum
10
-
11
- def initialize(lsn, group)
12
- @group = group
13
- @context = OpenStruct.new(:buffer => String.new,
14
- :buffer_lsn => lsn.dup, :record_lsn => lsn.dup)
15
- end
4
+ module Innodb
5
+ class LogReader
6
+ # Checksum failed exception.
7
+ class ChecksumError < RuntimeError; end
8
+
9
+ # EOF reached exception.
10
+ class EOFError < EOFError; end
11
+
12
+ Context = Struct.new(
13
+ :buffer,
14
+ :buffer_lsn,
15
+ :record_lsn,
16
+ keyword_init: true
17
+ )
18
+
19
+ # Whether to checksum blocks.
20
+ # TODO: Hmm, nothing seems to actually set this.
21
+ attr_accessor :checksum
22
+
23
+ def initialize(lsn, group)
24
+ @group = group
25
+ @context = Context.new(buffer: String.new, buffer_lsn: lsn.dup, record_lsn: lsn.dup)
26
+ end
16
27
 
17
- # Seek to record starting position.
18
- def seek(lsn_no)
19
- check_lsn_no(lsn_no)
20
- @context.buffer = String.new
21
- @context.buffer_lsn.reposition(lsn_no, @group)
22
- @context.record_lsn = @context.buffer_lsn.dup
23
- self
24
- end
28
+ # Seek to record starting position.
29
+ def seek(lsn_no)
30
+ check_lsn_no(lsn_no)
31
+ @context.buffer = String.new
32
+ @context.buffer_lsn.reposition(lsn_no, @group)
33
+ @context.record_lsn = @context.buffer_lsn.dup
34
+ self
35
+ end
25
36
 
26
- # Returns the current LSN starting position.
27
- def tell
28
- @context.record_lsn.no
29
- end
37
+ # Returns the current LSN starting position.
38
+ def tell
39
+ @context.record_lsn.no
40
+ end
30
41
 
31
- # Read a record.
32
- def record
33
- cursor = BufferCursor.new(self, 0)
34
- record = Innodb::LogRecord.new
35
- record.read(cursor)
36
- record.lsn = reposition(cursor.position)
37
- record
38
- end
42
+ # Read a record.
43
+ def record
44
+ cursor = BufferCursor.new(self, 0)
45
+ record = Innodb::LogRecord.new
46
+ record.read(cursor)
47
+ record.lsn = reposition(cursor.position)
48
+ record
49
+ end
39
50
 
40
- # Call the given block once for each record in the log until the
41
- # end of the log (or a corrupted block) is reached. If the follow
42
- # argument is true, retry.
43
- def each_record(follow, wait=0.5)
44
- begin
51
+ # Call the given block once for each record in the log until the
52
+ # end of the log (or a corrupted block) is reached. If the follow
53
+ # argument is true, retry.
54
+ def each_record(follow, wait = 0.5)
45
55
  loop { yield record }
46
56
  rescue EOFError, ChecksumError
47
- sleep(wait) and retry if follow
48
- end
49
- end
50
-
51
- # Read a slice of log data (that is, log data used for records).
52
- def slice(position, length)
53
- buffer = @context.buffer
54
- length = position + length
55
-
56
- if length > buffer.size
57
- preload(length)
57
+ sleep(wait) && retry if follow
58
58
  end
59
59
 
60
- buffer.slice(position, length - position)
61
- end
60
+ # Read a slice of log data (that is, log data used for records).
61
+ def slice(position, length)
62
+ buffer = @context.buffer
63
+ length = position + length
62
64
 
63
- # Checksum failed exception.
64
- class ChecksumError < RuntimeError
65
- end
65
+ preload(length) if length > buffer.size
66
66
 
67
- # EOF reached exception.
68
- class EOFError < EOFError
69
- end
67
+ buffer.slice(position, length - position)
68
+ end
70
69
 
71
- private
70
+ private
72
71
 
73
- # Check if LSN points to where records may be located.
74
- def check_lsn_no(lsn_no)
75
- lsn = @context.record_lsn.dup
76
- lsn.reposition(lsn_no, @group)
77
- raise "LSN #{lsn_no} is out of bounds" unless lsn.record?(@group)
78
- end
79
-
80
- # Reposition to the beginning of the next record.
81
- def reposition(length)
82
- start_lsn_no = @context.record_lsn.no
83
- delta_lsn_no = @context.record_lsn.delta(length)
84
- @context.record_lsn.advance(delta_lsn_no, @group)
85
- @context.buffer.slice!(0, length)
86
- [start_lsn_no, start_lsn_no + delta_lsn_no]
87
- end
72
+ # Check if LSN points to where records may be located.
73
+ def check_lsn_no(lsn_no)
74
+ lsn = @context.record_lsn.dup
75
+ lsn.reposition(lsn_no, @group)
76
+ raise "LSN #{lsn_no} is out of bounds" unless lsn.record?(@group)
77
+ end
88
78
 
89
- # Reads the log block at the given LSN position.
90
- def get_block(lsn)
91
- log_no, block_no, block_offset = lsn.location(@group)
92
- [@group.log(log_no).block(block_no), block_offset]
93
- end
79
+ # Reposition to the beginning of the next record.
80
+ def reposition(length)
81
+ start_lsn_no = @context.record_lsn.no
82
+ delta_lsn_no = @context.record_lsn.delta(length)
83
+ @context.record_lsn.advance(delta_lsn_no, @group)
84
+ @context.buffer.slice!(0, length)
85
+ [start_lsn_no, start_lsn_no + delta_lsn_no]
86
+ end
94
87
 
95
- # Preload the log buffer with enough data to satisfy the requested amount.
96
- def preload(size)
97
- buffer = @context.buffer
98
- buffer_lsn = @context.buffer_lsn
99
-
100
- # If reading for the first time, offset points to the start of the
101
- # record (somewhere in the block). Otherwise, the block is read as
102
- # a whole and offset points to the start of the next block to read.
103
- while buffer.size < size
104
- block, offset = get_block(buffer_lsn)
105
- break if checksum && corrupt = block.corrupt?
106
- data = offset == 0 ? block.data : block.data(offset)
107
- data_length = block.header[:data_length]
108
- buffer << data
109
- buffer_lsn.advance(data_length - offset, @group)
110
- break if data_length < Innodb::LogBlock::BLOCK_SIZE
88
+ # Reads the log block at the given LSN position.
89
+ def get_block(lsn)
90
+ log_no, block_no, block_offset = lsn.location(@group)
91
+ [@group.log(log_no).block(block_no), block_offset]
111
92
  end
112
93
 
113
- raise ChecksumError, "Block is corrupted" if corrupt
114
- raise EOFError, "End of log reached" if buffer.size < size
94
+ # Preload the log buffer with enough data to satisfy the requested amount.
95
+ def preload(size)
96
+ buffer = @context.buffer
97
+ buffer_lsn = @context.buffer_lsn
98
+
99
+ # If reading for the first time, offset points to the start of the
100
+ # record (somewhere in the block). Otherwise, the block is read as
101
+ # a whole and offset points to the start of the next block to read.
102
+ while buffer.size < size
103
+ block, offset = get_block(buffer_lsn)
104
+ break if checksum && (corrupt = block.corrupt?)
105
+
106
+ data = offset.zero? ? block.data : block.data(offset)
107
+ data_length = block.header[:data_length]
108
+ buffer << data
109
+ buffer_lsn.advance(data_length - offset, @group)
110
+ break if data_length < Innodb::LogBlock::BLOCK_SIZE
111
+ end
112
+
113
+ raise ChecksumError, "Block is corrupted" if corrupt
114
+ raise EOFError, "End of log reached" if buffer.size < size
115
+ end
115
116
  end
116
117
  end
@@ -1,317 +1,366 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  # An InnoDB transaction log block.
4
- class Innodb::LogRecord
5
- # Start and end LSNs for this record.
6
- attr_accessor :lsn
4
+ module Innodb
5
+ class LogRecord
6
+ Preamble = Struct.new(
7
+ :type,
8
+ :single_record,
9
+ :space,
10
+ :page_number,
11
+ keyword_init: true
12
+ )
7
13
 
8
- # The size (in bytes) of the record.
9
- attr_reader :size
14
+ IndexFieldInfo = Struct.new(
15
+ :mtype,
16
+ :prtype,
17
+ :length, # rubocop:disable Lint/StructNewOverride
18
+ keyword_init: true
19
+ )
10
20
 
11
- attr_reader :preamble
21
+ Index = Struct.new(
22
+ :n_cols,
23
+ :n_uniq,
24
+ :cols,
25
+ keyword_init: true
26
+ )
12
27
 
13
- attr_reader :payload
28
+ # Start and end LSNs for this record.
29
+ attr_accessor :lsn
14
30
 
15
- # InnoDB log record types.
16
- RECORD_TYPES =
17
- {
18
- 1 => :MLOG_1BYTE, 2 => :MLOG_2BYTE,
19
- 4 => :MLOG_4BYTE, 8 => :MLOG_8BYTE,
20
- 9 => :REC_INSERT, 10 => :REC_CLUST_DELETE_MARK,
21
- 11 => :REC_SEC_DELETE_MARK, 13 => :REC_UPDATE_IN_PLACE,
22
- 14 => :REC_DELETE, 15 => :LIST_END_DELETE,
23
- 16 => :LIST_START_DELETE, 17 => :LIST_END_COPY_CREATED,
24
- 18 => :PAGE_REORGANIZE, 19 => :PAGE_CREATE,
25
- 20 => :UNDO_INSERT, 21 => :UNDO_ERASE_END,
26
- 22 => :UNDO_INIT, 23 => :UNDO_HDR_DISCARD,
27
- 24 => :UNDO_HDR_REUSE, 25 => :UNDO_HDR_CREATE,
28
- 26 => :REC_MIN_MARK, 27 => :IBUF_BITMAP_INIT,
29
- 28 => :LSN, 29 => :INIT_FILE_PAGE,
30
- 30 => :WRITE_STRING, 31 => :MULTI_REC_END,
31
- 32 => :DUMMY_RECORD, 33 => :FILE_CREATE,
32
- 34 => :FILE_RENAME, 35 => :FILE_DELETE,
33
- 36 => :COMP_REC_MIN_MARK, 37 => :COMP_PAGE_CREATE,
34
- 38 => :COMP_REC_INSERT, 39 => :COMP_REC_CLUST_DELETE_MARK,
35
- 40 => :COMP_REC_SEC_DELETE_MARK, 41 => :COMP_REC_UPDATE_IN_PLACE,
36
- 42 => :COMP_REC_DELETE, 43 => :COMP_LIST_END_DELETE,
37
- 44 => :COMP_LIST_START_DELETE, 45 => :COMP_LIST_END_COPY_CREATE,
38
- 46 => :COMP_PAGE_REORGANIZE, 47 => :FILE_CREATE2,
39
- 48 => :ZIP_WRITE_NODE_PTR, 49 => :ZIP_WRITE_BLOB_PTR,
40
- 50 => :ZIP_WRITE_HEADER, 51 => :ZIP_PAGE_COMPRESS,
41
- }
31
+ # The size (in bytes) of the record.
32
+ attr_reader :size
42
33
 
43
- # Types of undo log segments.
44
- UNDO_TYPES = { 1 => :UNDO_INSERT, 2 => :UNDO_UPDATE }
34
+ attr_reader :preamble
45
35
 
46
- def read(cursor)
47
- origin = cursor.position
48
- @preamble = read_preamble(cursor)
49
- @payload = read_payload(@preamble[:type], cursor)
50
- @size = cursor.position - origin
51
- end
36
+ attr_reader :payload
52
37
 
53
- # Dump the contents of the record.
54
- def dump
55
- pp({:lsn => lsn, :size => size, :content => @preamble.merge(@payload)})
56
- end
38
+ # InnoDB log record types.
39
+ RECORD_TYPES = {
40
+ 1 => :MLOG_1BYTE,
41
+ 2 => :MLOG_2BYTE,
42
+ 4 => :MLOG_4BYTE,
43
+ 8 => :MLOG_8BYTE,
44
+ 9 => :REC_INSERT,
45
+ 10 => :REC_CLUST_DELETE_MARK,
46
+ 11 => :REC_SEC_DELETE_MARK,
47
+ 13 => :REC_UPDATE_IN_PLACE,
48
+ 14 => :REC_DELETE,
49
+ 15 => :LIST_END_DELETE,
50
+ 16 => :LIST_START_DELETE,
51
+ 17 => :LIST_END_COPY_CREATED,
52
+ 18 => :PAGE_REORGANIZE,
53
+ 19 => :PAGE_CREATE,
54
+ 20 => :UNDO_INSERT,
55
+ 21 => :UNDO_ERASE_END,
56
+ 22 => :UNDO_INIT,
57
+ 23 => :UNDO_HDR_DISCARD,
58
+ 24 => :UNDO_HDR_REUSE,
59
+ 25 => :UNDO_HDR_CREATE,
60
+ 26 => :REC_MIN_MARK,
61
+ 27 => :IBUF_BITMAP_INIT,
62
+ 28 => :LSN,
63
+ 29 => :INIT_FILE_PAGE,
64
+ 30 => :WRITE_STRING,
65
+ 31 => :MULTI_REC_END,
66
+ 32 => :DUMMY_RECORD,
67
+ 33 => :FILE_CREATE,
68
+ 34 => :FILE_RENAME,
69
+ 35 => :FILE_DELETE,
70
+ 36 => :COMP_REC_MIN_MARK,
71
+ 37 => :COMP_PAGE_CREATE,
72
+ 38 => :COMP_REC_INSERT,
73
+ 39 => :COMP_REC_CLUST_DELETE_MARK,
74
+ 40 => :COMP_REC_SEC_DELETE_MARK,
75
+ 41 => :COMP_REC_UPDATE_IN_PLACE,
76
+ 42 => :COMP_REC_DELETE,
77
+ 43 => :COMP_LIST_END_DELETE,
78
+ 44 => :COMP_LIST_START_DELETE,
79
+ 45 => :COMP_LIST_END_COPY_CREATE,
80
+ 46 => :COMP_PAGE_REORGANIZE,
81
+ 47 => :FILE_CREATE2,
82
+ 48 => :ZIP_WRITE_NODE_PTR,
83
+ 49 => :ZIP_WRITE_BLOB_PTR,
84
+ 50 => :ZIP_WRITE_HEADER,
85
+ 51 => :ZIP_PAGE_COMPRESS,
86
+ }.freeze
57
87
 
58
- # Single record flag is masked in the record type.
59
- SINGLE_RECORD_MASK = 0x80
60
- RECORD_TYPE_MASK = 0x7f
88
+ # Types of undo log segments.
89
+ UNDO_TYPES = {
90
+ 1 => :UNDO_INSERT,
91
+ 2 => :UNDO_UPDATE,
92
+ }.freeze
61
93
 
62
- # Return a preamble of the first record in this block.
63
- def read_preamble(c)
64
- type_and_flag = c.name("type") { c.get_uint8 }
65
- type = type_and_flag & RECORD_TYPE_MASK
66
- type = RECORD_TYPES[type] || type
67
- # Whether this is a single record for a single page.
68
- single_record = (type_and_flag & SINGLE_RECORD_MASK) > 0
69
- case type
70
- when :MULTI_REC_END, :DUMMY_RECORD
71
- { :type => type }
72
- else
73
- {
74
- :type => type,
75
- :single_record => single_record,
76
- :space => c.name("space") { c.get_ic_uint32 },
77
- :page_number => c.name("page_number") { c.get_ic_uint32 },
78
- }
94
+ def read(cursor)
95
+ origin = cursor.position
96
+ @preamble = read_preamble(cursor)
97
+ @payload = read_payload(@preamble.type, cursor)
98
+ @size = cursor.position - origin
79
99
  end
80
- end
81
100
 
82
- # Read the index part of a log record for a compact record insert.
83
- # Ref. mlog_parse_index
84
- def read_index(c)
85
- n_cols = c.name("n_cols") { c.get_uint16 }
86
- n_uniq = c.name("n_uniq") { c.get_uint16 }
87
- cols = n_cols.times.collect do
88
- info = c.name("field_info") { c.get_uint16 }
89
- {
90
- :mtype => ((info + 1) & 0x7fff) <= 1 ? :BINARY : :FIXBINARY,
91
- :prtype => (info & 0x8000) != 0 ? :NOT_NULL : nil,
92
- :length => info & 0x7fff
93
- }
101
+ # Dump the contents of the record.
102
+ def dump
103
+ pp({ lsn: lsn, size: size, preamble: @preamble, payload: @payload })
94
104
  end
95
- {
96
- :n_cols => n_cols,
97
- :n_uniq => n_uniq,
98
- :cols => cols,
99
- }
100
- end
101
105
 
102
- # Flag of whether an insert log record contains info and status.
103
- INFO_AND_STATUS_MASK = 0x1
106
+ # Single record flag is masked in the record type.
107
+ SINGLE_RECORD_MASK = 0x80
108
+ RECORD_TYPE_MASK = 0x7f
109
+
110
+ # Return a preamble of the first record in this block.
111
+ def read_preamble(cursor)
112
+ type_and_flag = cursor.name("type") { cursor.read_uint8 }
113
+ type = type_and_flag & RECORD_TYPE_MASK
114
+ type = RECORD_TYPES[type] || type
115
+ # Whether this is a single record for a single page.
116
+ single_record = (type_and_flag & SINGLE_RECORD_MASK).positive?
117
+ case type
118
+ when :MULTI_REC_END, :DUMMY_RECORD
119
+ Preamble.new(type: type)
120
+ else
121
+ Preamble.new(
122
+ type: type,
123
+ single_record: single_record,
124
+ space: cursor.name("space") { cursor.read_ic_uint32 },
125
+ page_number: cursor.name("page_number") { cursor.read_ic_uint32 }
126
+ )
127
+ end
128
+ end
104
129
 
105
- # Read the insert record into page part of a insert log.
106
- # Ref. page_cur_parse_insert_rec
107
- def read_insert_record(c)
108
- page_offset = c.name("page_offset") { c.get_uint16 }
109
- end_seg_len = c.name("end_seg_len") { c.get_ic_uint32 }
130
+ # Read the index part of a log record for a compact record insert.
131
+ # Ref. mlog_parse_index
132
+ def read_index(cursor)
133
+ n_cols = cursor.name("n_cols") { cursor.read_uint16 }
134
+ n_uniq = cursor.name("n_uniq") { cursor.read_uint16 }
135
+ cols = n_cols.times.collect do
136
+ info = cursor.name("field_info") { cursor.read_uint16 }
137
+ IndexFieldInfo.new(
138
+ mtype: ((info + 1) & 0x7fff) <= 1 ? :BINARY : :FIXBINARY,
139
+ prtype: (info & 0x8000).zero? ? nil : :NOT_NULL,
140
+ length: info & 0x7fff
141
+ )
142
+ end
110
143
 
111
- if (end_seg_len & INFO_AND_STATUS_MASK) != 0
112
- info_and_status_bits = c.get_uint8
113
- origin_offset = c.get_ic_uint32
114
- mismatch_index = c.get_ic_uint32
144
+ Index.new(n_cols: n_cols, n_uniq: n_uniq, cols: cols)
115
145
  end
116
146
 
117
- {
118
- :page_offset => page_offset,
119
- :end_seg_len => end_seg_len >> 1,
120
- :info_and_status_bits => info_and_status_bits,
121
- :origin_offset => origin_offset,
122
- :mismatch_index => mismatch_index,
123
- :record => c.name("record") { c.get_bytes(end_seg_len >> 1) },
124
- }
125
- end
147
+ # Flag of whether an insert log record contains info and status.
148
+ INFO_AND_STATUS_MASK = 0x1
126
149
 
127
- # Read the log record for an in-place update.
128
- # Ref. btr_cur_parse_update_in_place
129
- def read_update_in_place_record(c)
130
- {
131
- :flags => c.name("flags") { c.get_uint8 },
132
- :sys_fields => read_sys_fields(c),
133
- :rec_offset => c.name("rec_offset") { c.get_uint16 },
134
- :update_index => read_update_index(c),
135
- }
136
- end
150
+ # Read the insert record into page part of a insert log.
151
+ # Ref. page_cur_parse_insert_rec
152
+ def read_insert_record(cursor)
153
+ page_offset = cursor.name("page_offset") { cursor.read_uint16 }
154
+ end_seg_len = cursor.name("end_seg_len") { cursor.read_ic_uint32 }
137
155
 
138
- LENGTH_NULL = 0xFFFFFFFF
156
+ if (end_seg_len & INFO_AND_STATUS_MASK) != 0
157
+ info_and_status_bits = cursor.read_uint8
158
+ origin_offset = cursor.read_ic_uint32
159
+ mismatch_index = cursor.read_ic_uint32
160
+ end
139
161
 
140
- # Read the update vector for an update log record.
141
- # Ref. row_upd_index_parse
142
- def read_update_index(c)
143
- info_bits = c.name("info_bits") { c.get_uint8 }
144
- n_fields = c.name("n_fields") { c.get_ic_uint32 }
145
- fields = n_fields.times.collect do
146
162
  {
147
- :field_no => c.name("field_no") { c.get_ic_uint32 },
148
- :len => len = c.name("len") { c.get_ic_uint32 },
149
- :data => c.name("data") { len != LENGTH_NULL ? c.get_bytes(len) : :NULL },
163
+ page_offset: page_offset,
164
+ end_seg_len: end_seg_len >> 1,
165
+ info_and_status_bits: info_and_status_bits,
166
+ origin_offset: origin_offset,
167
+ mismatch_index: mismatch_index,
168
+ record: cursor.name("record") { cursor.read_bytes(end_seg_len >> 1) },
150
169
  }
151
170
  end
152
- {
153
- :info_bits => info_bits,
154
- :n_fields => n_fields,
155
- :fields => fields,
156
- }
157
- end
158
-
159
- # Read system fields values in a log record.
160
- # Ref. row_upd_parse_sys_vals
161
- def read_sys_fields(c)
162
- {
163
- :trx_id_pos => c.name("trx_id_pos") { c.get_ic_uint32 },
164
- :roll_ptr => c.name("roll_ptr") { c.get_bytes(7) },
165
- :trx_id => c.name("trx_id") { c.get_ic_uint64 },
166
- }
167
- end
168
171
 
169
- # Read the log record for delete marking or unmarking of a clustered
170
- # index record.
171
- # Ref. btr_cur_parse_del_mark_set_clust_rec
172
- def read_clust_delete_mark(c)
173
- {
174
- :flags => c.name("flags") { c.get_uint8 },
175
- :value => c.name("value") { c.get_uint8 },
176
- :sys_fields => c.name("sys_fields") { read_sys_fields(c) },
177
- :offset => c.name("offset") { c.get_uint16 },
178
- }
179
- end
180
-
181
- def read_payload(type, c)
182
- case type
183
- when :MLOG_1BYTE, :MLOG_2BYTE, :MLOG_4BYTE
184
- {
185
- :page_offset => c.name("page_offset") { c.get_uint16 },
186
- :value => c.name("value") { c.get_ic_uint32 }
187
- }
188
- when :MLOG_8BYTE
189
- {
190
- :offset => c.name("offset") { c.get_uint16 },
191
- :value => c.name("value") { c.get_ic_uint64 }
192
- }
193
- when :UNDO_HDR_CREATE, :UNDO_HDR_REUSE
172
+ # Read the log record for an in-place update.
173
+ # Ref. btr_cur_parse_update_in_place
174
+ def read_update_in_place_record(cursor)
194
175
  {
195
- :trx_id => c.name("trx_id") { c.get_ic_uint64 }
176
+ flags: cursor.name("flags") { cursor.read_uint8 },
177
+ sys_fields: read_sys_fields(cursor),
178
+ rec_offset: cursor.name("rec_offset") { cursor.read_uint16 },
179
+ update_index: read_update_index(cursor),
196
180
  }
197
- when :UNDO_INSERT
198
- {
199
- :length => len = c.name("length") { c.get_uint16 },
200
- :value => c.name("value") { c.get_bytes(len) }
201
- }
202
- when :REC_INSERT
203
- {
204
- :record => c.name("record") { read_insert_record(c) }
205
- }
206
- when :COMP_REC_INSERT
207
- {
208
- :index => c.name("index") { read_index(c) },
209
- :record => c.name("record") { read_insert_record(c) }
210
- }
211
- when :COMP_REC_UPDATE_IN_PLACE
212
- {
213
- :index => c.name("index") { read_index(c) },
214
- :record => c.name("record") { read_update_in_place_record(c) }
215
- }
216
- when :REC_UPDATE_IN_PLACE
217
- {
218
- :record => c.name("record") { read_update_in_place_record(c) }
219
- }
220
- when :WRITE_STRING
221
- {
222
- :offset => c.name("offset") { c.get_uint16 },
223
- :length => length = c.name("length") { c.get_uint16 },
224
- :value => c.name("value") { c.get_bytes(length) },
225
- }
226
- when :UNDO_INIT
227
- {
228
- :type => c.name("type") { UNDO_TYPES[c.get_ic_uint32] }
229
- }
230
- when :FILE_CREATE, :FILE_DELETE
231
- {
232
- :name_len => len = c.name("name_len") { c.get_uint16 },
233
- :name => c.name("name") { c.get_bytes(len) },
234
- }
235
- when :FILE_CREATE2
236
- {
237
- :flags => c.name("flags") { c.get_uint32 },
238
- :name_len => len = c.name("name_len") { c.get_uint16 },
239
- :name => c.name("name") { c.get_bytes(len) },
240
- }
241
- when :FILE_RENAME
242
- {
243
- :old => {
244
- :name_len => len = c.name("name_len") { c.get_uint16 },
245
- :name => c.name("name") { c.get_bytes(len) },
246
- },
247
- :new => {
248
- :name_len => len = c.name("name_len") { c.get_uint16 },
249
- :name => c.name("name") { c.get_bytes(len) },
181
+ end
182
+
183
+ LENGTH_NULL = 0xFFFFFFFF
184
+
185
+ # Read the update vector for an update log record.
186
+ # Ref. row_upd_index_parse
187
+ def read_update_index(cursor)
188
+ info_bits = cursor.name("info_bits") { cursor.read_uint8 }
189
+ n_fields = cursor.name("n_fields") { cursor.read_ic_uint32 }
190
+ fields = n_fields.times.collect do
191
+ {
192
+ field_no: cursor.name("field_no") { cursor.read_ic_uint32 },
193
+ len: len = cursor.name("len") { cursor.read_ic_uint32 },
194
+ data: cursor.name("data") { len == LENGTH_NULL ? :NULL : cursor.read_bytes(len) },
250
195
  }
251
- }
252
- when :COMP_REC_CLUST_DELETE_MARK
196
+ end
253
197
  {
254
- :index => c.name("index") { read_index(c) },
255
- :record => c.name("record") { read_clust_delete_mark(c) }
198
+ info_bits: info_bits,
199
+ n_fields: n_fields,
200
+ fields: fields,
256
201
  }
257
- when :REC_CLUST_DELETE_MARK
258
- {
259
- :record => c.name("record") { read_clust_delete_mark(c) }
260
- }
261
- when :COMP_REC_SEC_DELETE_MARK
262
- {
263
- :index => c.name("index") { read_index(c) },
264
- :value => c.name("value") { c.get_uint8 },
265
- :offset => c.name("offset") { c.get_uint16 },
266
- }
267
- when :REC_SEC_DELETE_MARK
268
- {
269
- :value => c.name("value") { c.get_uint8 },
270
- :offset => c.name("offset") { c.get_uint16 },
271
- }
272
- when :REC_DELETE
273
- {
274
- :offset => c.name("offset") { c.get_uint16 },
275
- }
276
- when :COMP_REC_DELETE
277
- {
278
- :index => c.name("index") { read_index(c) },
279
- :offset => c.name("offset") { c.get_uint16 },
280
- }
281
- when :REC_MIN_MARK, :COMP_REC_MIN_MARK
282
- {
283
- :offset => c.name("offset") { c.get_uint16 },
284
- }
285
- when :LIST_START_DELETE, :LIST_END_DELETE
286
- {
287
- :offset => c.name("offset") { c.get_uint16 },
288
- }
289
- when :COMP_LIST_START_DELETE, :COMP_LIST_END_DELETE
290
- {
291
- :index => c.name("index") { read_index(c) },
292
- :offset => c.name("offset") { c.get_uint16 },
293
- }
294
- when :LIST_END_COPY_CREATED
295
- {
296
- :length => len = c.name("length") { c.get_uint32 },
297
- :data => c.name("data") { c.get_bytes(len) }
298
- }
299
- when :COMP_LIST_END_COPY_CREATE
202
+ end
203
+
204
+ # Read system fields values in a log record.
205
+ # Ref. row_upd_parse_sys_vals
206
+ def read_sys_fields(cursor)
300
207
  {
301
- :index => c.name("index") { read_index(c) },
302
- :length => len = c.name("length") { c.get_uint32 },
303
- :data => c.name("data") { c.get_bytes(len) }
208
+ trx_id_pos: cursor.name("trx_id_pos") { cursor.read_ic_uint32 },
209
+ roll_ptr: cursor.name("roll_ptr") { cursor.read_bytes(7) },
210
+ trx_id: cursor.name("trx_id") { cursor.read_ic_uint64 },
304
211
  }
305
- when :COMP_PAGE_REORGANIZE
212
+ end
213
+
214
+ # Read the log record for delete marking or unmarking of a clustered
215
+ # index record.
216
+ # Ref. btr_cur_parse_del_mark_set_clust_rec
217
+ def read_clust_delete_mark(cursor)
306
218
  {
307
- :index => c.name("index") { read_index(c) },
219
+ flags: cursor.name("flags") { cursor.read_uint8 },
220
+ value: cursor.name("value") { cursor.read_uint8 },
221
+ sys_fields: cursor.name("sys_fields") { read_sys_fields(cursor) },
222
+ offset: cursor.name("offset") { cursor.read_uint16 },
308
223
  }
309
- when :DUMMY_RECORD, :MULTI_REC_END, :INIT_FILE_PAGE,
310
- :IBUF_BITMAP_INIT, :PAGE_CREATE, :COMP_PAGE_CREATE,
311
- :PAGE_REORGANIZE, :UNDO_ERASE_END, :UNDO_HDR_DISCARD
312
- {}
313
- else
314
- raise "Unsupported log record type: #{type.to_s}"
315
224
  end
225
+
226
+ # The bodies of the branches here are sometimes duplicates, but logically distinct.
227
+ # rubocop:disable Lint/DuplicateBranch
228
+ def read_payload(type, cursor)
229
+ case type
230
+ when :MLOG_1BYTE, :MLOG_2BYTE, :MLOG_4BYTE
231
+ {
232
+ page_offset: cursor.name("page_offset") { cursor.read_uint16 },
233
+ value: cursor.name("value") { cursor.read_ic_uint32 },
234
+ }
235
+ when :MLOG_8BYTE
236
+ {
237
+ offset: cursor.name("offset") { cursor.read_uint16 },
238
+ value: cursor.name("value") { cursor.read_ic_uint64 },
239
+ }
240
+ when :UNDO_HDR_CREATE, :UNDO_HDR_REUSE
241
+ {
242
+ trx_id: cursor.name("trx_id") { cursor.read_ic_uint64 },
243
+ }
244
+ when :UNDO_INSERT
245
+ {
246
+ length: length = cursor.name("length") { cursor.read_uint16 },
247
+ value: cursor.name("value") { cursor.read_bytes(length) },
248
+ }
249
+ when :REC_INSERT
250
+ {
251
+ record: cursor.name("record") { read_insert_record(cursor) },
252
+ }
253
+ when :COMP_REC_INSERT
254
+ {
255
+ index: cursor.name("index") { read_index(cursor) },
256
+ record: cursor.name("record") { read_insert_record(cursor) },
257
+ }
258
+ when :COMP_REC_UPDATE_IN_PLACE
259
+ {
260
+ index: cursor.name("index") { read_index(cursor) },
261
+ record: cursor.name("record") { read_update_in_place_record(cursor) },
262
+ }
263
+ when :REC_UPDATE_IN_PLACE
264
+ {
265
+ record: cursor.name("record") { read_update_in_place_record(cursor) },
266
+ }
267
+ when :WRITE_STRING
268
+ {
269
+ offset: cursor.name("offset") { cursor.read_uint16 },
270
+ length: length = cursor.name("length") { cursor.read_uint16 },
271
+ value: cursor.name("value") { cursor.read_bytes(length) },
272
+ }
273
+ when :UNDO_INIT
274
+ {
275
+ type: cursor.name("type") { UNDO_TYPES[cursor.read_ic_uint32] },
276
+ }
277
+ when :FILE_CREATE, :FILE_DELETE
278
+ {
279
+ name_len: name_len = cursor.name("name_len") { cursor.read_uint16 },
280
+ name: cursor.name("name") { cursor.read_bytes(name_len) },
281
+ }
282
+ when :FILE_CREATE2
283
+ {
284
+ flags: cursor.name("flags") { cursor.read_uint32 },
285
+ name_len: name_len = cursor.name("name_len") { cursor.read_uint16 },
286
+ name: cursor.name("name") { cursor.read_bytes(name_len) },
287
+ }
288
+ when :FILE_RENAME
289
+ {
290
+ old: {
291
+ name_len: name_len = cursor.name("name_len") { cursor.read_uint16 },
292
+ name: cursor.name("name") { cursor.read_bytes(name_len) },
293
+ },
294
+ new: {
295
+ name_len: name_len = cursor.name("name_len") { cursor.read_uint16 },
296
+ name: cursor.name("name") { cursor.read_bytes(name_len) },
297
+ },
298
+ }
299
+ when :COMP_REC_CLUST_DELETE_MARK
300
+ {
301
+ index: cursor.name("index") { read_index(cursor) },
302
+ record: cursor.name("record") { read_clust_delete_mark(cursor) },
303
+ }
304
+ when :REC_CLUST_DELETE_MARK
305
+ {
306
+ record: cursor.name("record") { read_clust_delete_mark(cursor) },
307
+ }
308
+ when :COMP_REC_SEC_DELETE_MARK
309
+ {
310
+ index: cursor.name("index") { read_index(cursor) },
311
+ value: cursor.name("value") { cursor.read_uint8 },
312
+ offset: cursor.name("offset") { cursor.read_uint16 },
313
+ }
314
+ when :REC_SEC_DELETE_MARK
315
+ {
316
+ value: cursor.name("value") { cursor.read_uint8 },
317
+ offset: cursor.name("offset") { cursor.read_uint16 },
318
+ }
319
+ when :REC_DELETE
320
+ {
321
+ offset: cursor.name("offset") { cursor.read_uint16 },
322
+ }
323
+ when :COMP_REC_DELETE
324
+ {
325
+ index: cursor.name("index") { read_index(cursor) },
326
+ offset: cursor.name("offset") { cursor.read_uint16 },
327
+ }
328
+ when :REC_MIN_MARK, :COMP_REC_MIN_MARK
329
+ {
330
+ offset: cursor.name("offset") { cursor.read_uint16 },
331
+ }
332
+ when :LIST_START_DELETE, :LIST_END_DELETE
333
+ {
334
+ offset: cursor.name("offset") { cursor.read_uint16 },
335
+ }
336
+ when :COMP_LIST_START_DELETE, :COMP_LIST_END_DELETE
337
+ {
338
+ index: cursor.name("index") { read_index(cursor) },
339
+ offset: cursor.name("offset") { cursor.read_uint16 },
340
+ }
341
+ when :LIST_END_COPY_CREATED
342
+ {
343
+ length: length = cursor.name("length") { cursor.read_uint32 },
344
+ data: cursor.name("data") { cursor.read_bytes(length) },
345
+ }
346
+ when :COMP_LIST_END_COPY_CREATE
347
+ {
348
+ index: cursor.name("index") { read_index(cursor) },
349
+ length: length = cursor.name("length") { cursor.read_uint32 },
350
+ data: cursor.name("data") { cursor.read_bytes(length) },
351
+ }
352
+ when :COMP_PAGE_REORGANIZE
353
+ {
354
+ index: cursor.name("index") { read_index(cursor) },
355
+ }
356
+ when :DUMMY_RECORD, :MULTI_REC_END, :INIT_FILE_PAGE,
357
+ :IBUF_BITMAP_INIT, :PAGE_CREATE, :COMP_PAGE_CREATE,
358
+ :PAGE_REORGANIZE, :UNDO_ERASE_END, :UNDO_HDR_DISCARD
359
+ {}
360
+ else
361
+ raise "Unsupported log record type: #{type}"
362
+ end
363
+ end
364
+ # rubocop:enable Lint/DuplicateBranch
316
365
  end
317
366
  end