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.
- checksums.yaml +7 -0
- data/README.md +5 -6
- data/bin/innodb_log +13 -18
- data/bin/innodb_space +654 -778
- data/lib/innodb/checksum.rb +26 -24
- data/lib/innodb/data_dictionary.rb +490 -550
- data/lib/innodb/data_type.rb +362 -325
- data/lib/innodb/field.rb +102 -89
- data/lib/innodb/fseg_entry.rb +22 -26
- data/lib/innodb/history.rb +21 -21
- data/lib/innodb/history_list.rb +72 -76
- data/lib/innodb/ibuf_bitmap.rb +36 -36
- data/lib/innodb/ibuf_index.rb +6 -2
- data/lib/innodb/index.rb +245 -276
- data/lib/innodb/inode.rb +166 -124
- data/lib/innodb/list.rb +196 -183
- data/lib/innodb/log.rb +139 -110
- data/lib/innodb/log_block.rb +100 -91
- data/lib/innodb/log_group.rb +53 -64
- data/lib/innodb/log_reader.rb +97 -96
- data/lib/innodb/log_record.rb +328 -279
- data/lib/innodb/lsn.rb +86 -81
- data/lib/innodb/page/blob.rb +82 -83
- data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
- data/lib/innodb/page/ibuf_bitmap.rb +34 -34
- data/lib/innodb/page/index.rb +965 -924
- data/lib/innodb/page/index_compressed.rb +34 -34
- data/lib/innodb/page/inode.rb +103 -112
- data/lib/innodb/page/sys.rb +13 -15
- data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
- data/lib/innodb/page/sys_ibuf_header.rb +45 -42
- data/lib/innodb/page/sys_rseg_header.rb +88 -82
- data/lib/innodb/page/trx_sys.rb +204 -182
- data/lib/innodb/page/undo_log.rb +106 -92
- data/lib/innodb/page.rb +417 -414
- data/lib/innodb/record.rb +121 -164
- data/lib/innodb/record_describer.rb +66 -68
- data/lib/innodb/space.rb +381 -413
- data/lib/innodb/stats.rb +33 -35
- data/lib/innodb/system.rb +149 -171
- data/lib/innodb/undo_log.rb +129 -107
- data/lib/innodb/undo_record.rb +255 -247
- data/lib/innodb/util/buffer_cursor.rb +81 -79
- data/lib/innodb/util/read_bits_at_offset.rb +2 -1
- data/lib/innodb/version.rb +2 -2
- data/lib/innodb/xdes.rb +144 -142
- data/lib/innodb.rb +4 -5
- metadata +100 -25
data/lib/innodb/log_group.rb
CHANGED
@@ -1,84 +1,73 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Group of InnoDB logs files that make up the redo log.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
12
|
+
# Iterate through all logs.
|
13
|
+
def each_log(&block)
|
14
|
+
return enum_for(:each_log) unless block_given?
|
23
15
|
|
24
|
-
|
25
|
-
def each_block
|
26
|
-
unless block_given?
|
27
|
-
return enum_for(:each_block)
|
16
|
+
@logs.each(&block)
|
28
17
|
end
|
29
18
|
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
28
|
+
# The number of log files in the group.
|
29
|
+
def logs
|
30
|
+
@logs.count
|
31
|
+
end
|
41
32
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
43
|
+
# The size of the log group (in bytes)
|
44
|
+
def size
|
45
|
+
@logs.first.size * @logs.count
|
46
|
+
end
|
56
47
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
48
|
+
# The log group capacity (in bytes).
|
49
|
+
def capacity
|
50
|
+
@logs.first.capacity * @logs.count
|
51
|
+
end
|
61
52
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
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
|
data/lib/innodb/log_reader.rb
CHANGED
@@ -1,116 +1,117 @@
|
|
1
|
-
#
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
37
|
+
# Returns the current LSN starting position.
|
38
|
+
def tell
|
39
|
+
@context.record_lsn.no
|
40
|
+
end
|
30
41
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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)
|
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
|
-
|
61
|
-
|
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
|
-
|
64
|
-
class ChecksumError < RuntimeError
|
65
|
-
end
|
65
|
+
preload(length) if length > buffer.size
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
end
|
67
|
+
buffer.slice(position, length - position)
|
68
|
+
end
|
70
69
|
|
71
|
-
|
70
|
+
private
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
114
|
-
|
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
|
data/lib/innodb/log_record.rb
CHANGED
@@ -1,317 +1,366 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# An InnoDB transaction log block.
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
14
|
+
IndexFieldInfo = Struct.new(
|
15
|
+
:mtype,
|
16
|
+
:prtype,
|
17
|
+
:length, # rubocop:disable Lint/StructNewOverride
|
18
|
+
keyword_init: true
|
19
|
+
)
|
10
20
|
|
11
|
-
|
21
|
+
Index = Struct.new(
|
22
|
+
:n_cols,
|
23
|
+
:n_uniq,
|
24
|
+
:cols,
|
25
|
+
keyword_init: true
|
26
|
+
)
|
12
27
|
|
13
|
-
|
28
|
+
# Start and end LSNs for this record.
|
29
|
+
attr_accessor :lsn
|
14
30
|
|
15
|
-
|
16
|
-
|
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
|
-
|
44
|
-
UNDO_TYPES = { 1 => :UNDO_INSERT, 2 => :UNDO_UPDATE }
|
34
|
+
attr_reader :preamble
|
45
35
|
|
46
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
88
|
+
# Types of undo log segments.
|
89
|
+
UNDO_TYPES = {
|
90
|
+
1 => :UNDO_INSERT,
|
91
|
+
2 => :UNDO_UPDATE,
|
92
|
+
}.freeze
|
61
93
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
103
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
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
|
-
:
|
148
|
-
:
|
149
|
-
:
|
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
|
-
|
170
|
-
|
171
|
-
|
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
|
-
:
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
}
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
:
|
255
|
-
:
|
198
|
+
info_bits: info_bits,
|
199
|
+
n_fields: n_fields,
|
200
|
+
fields: fields,
|
256
201
|
}
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
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
|
-
:
|
302
|
-
:
|
303
|
-
:
|
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
|
-
|
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
|
-
:
|
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
|