innodb_ruby 0.9.13 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +14 -19
  4. data/bin/innodb_space +592 -745
  5. data/lib/innodb.rb +5 -5
  6. data/lib/innodb/checksum.rb +26 -24
  7. data/lib/innodb/data_dictionary.rb +490 -550
  8. data/lib/innodb/data_type.rb +362 -325
  9. data/lib/innodb/field.rb +102 -89
  10. data/lib/innodb/fseg_entry.rb +22 -26
  11. data/lib/innodb/history.rb +21 -21
  12. data/lib/innodb/history_list.rb +72 -76
  13. data/lib/innodb/ibuf_bitmap.rb +36 -36
  14. data/lib/innodb/ibuf_index.rb +6 -2
  15. data/lib/innodb/index.rb +245 -275
  16. data/lib/innodb/inode.rb +166 -124
  17. data/lib/innodb/list.rb +191 -183
  18. data/lib/innodb/log.rb +139 -110
  19. data/lib/innodb/log_block.rb +100 -91
  20. data/lib/innodb/log_group.rb +53 -64
  21. data/lib/innodb/log_reader.rb +97 -96
  22. data/lib/innodb/log_record.rb +328 -279
  23. data/lib/innodb/lsn.rb +86 -81
  24. data/lib/innodb/page.rb +446 -291
  25. data/lib/innodb/page/blob.rb +82 -83
  26. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  27. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  28. data/lib/innodb/page/index.rb +965 -924
  29. data/lib/innodb/page/index_compressed.rb +34 -34
  30. data/lib/innodb/page/inode.rb +103 -112
  31. data/lib/innodb/page/sys.rb +13 -15
  32. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  33. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  34. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  35. data/lib/innodb/page/trx_sys.rb +204 -182
  36. data/lib/innodb/page/undo_log.rb +106 -92
  37. data/lib/innodb/record.rb +121 -164
  38. data/lib/innodb/record_describer.rb +66 -68
  39. data/lib/innodb/space.rb +386 -391
  40. data/lib/innodb/stats.rb +33 -35
  41. data/lib/innodb/system.rb +149 -171
  42. data/lib/innodb/undo_log.rb +129 -107
  43. data/lib/innodb/undo_record.rb +255 -247
  44. data/lib/innodb/util/buffer_cursor.rb +81 -79
  45. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  46. data/lib/innodb/version.rb +2 -2
  47. data/lib/innodb/xdes.rb +144 -142
  48. metadata +112 -21
@@ -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