innodb_ruby 0.9.13 → 0.11.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 +14 -19
- data/bin/innodb_space +592 -745
- data/lib/innodb.rb +5 -5
- 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 -275
- data/lib/innodb/inode.rb +166 -124
- data/lib/innodb/list.rb +191 -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.rb +446 -291
- 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/record.rb +121 -164
- data/lib/innodb/record_describer.rb +66 -68
- data/lib/innodb/space.rb +386 -391
- 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
- metadata +112 -21
data/lib/innodb/lsn.rb
CHANGED
@@ -1,103 +1,108 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# A Log Sequence Number and its byte offset into the log group.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
module Innodb
|
5
|
+
class LSN
|
6
|
+
# The Log Sequence Number.
|
7
|
+
attr_reader :lsn_no
|
8
|
+
|
9
|
+
# Alias :lsn_no attribute.
|
10
|
+
alias no lsn_no
|
11
|
+
|
12
|
+
# Initialize coordinates.
|
13
|
+
def initialize(lsn, offset)
|
14
|
+
@lsn_no = lsn
|
15
|
+
@lsn_offset = offset
|
16
|
+
end
|
10
17
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
18
|
+
# Place LSN in a new position.
|
19
|
+
def reposition(new_lsn_no, group)
|
20
|
+
new_offset = offset_of(@lsn_no, @lsn_offset, new_lsn_no, group)
|
21
|
+
@lsn_no = new_lsn_no
|
22
|
+
@lsn_offset = new_offset
|
16
23
|
|
17
|
-
|
18
|
-
|
19
|
-
new_offset = offset_of(@lsn_no, @lsn_offset, new_lsn_no, group)
|
20
|
-
@lsn_no, @lsn_offset = [new_lsn_no, new_offset]
|
21
|
-
end
|
24
|
+
[@lsn_no, @lsn_offset]
|
25
|
+
end
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
# Advance by a given LSN amount.
|
28
|
+
def advance(count_lsn_no, group)
|
29
|
+
new_lsn_no = @lsn_no + count_lsn_no
|
30
|
+
reposition(new_lsn_no, group)
|
31
|
+
end
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
+
# Returns the location coordinates of this LSN.
|
34
|
+
def location(group)
|
35
|
+
location_of(@lsn_offset, group)
|
36
|
+
end
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
fragment.between?(0, LOG_BLOCK_DATA_SIZE - 1)
|
39
|
-
length + (fragment + length) / LOG_BLOCK_DATA_SIZE * LOG_BLOCK_FRAME_SIZE
|
40
|
-
end
|
38
|
+
# Returns the LSN delta for the given amount of data.
|
39
|
+
def delta(length)
|
40
|
+
fragment = (@lsn_no % LOG_BLOCK_SIZE) - LOG_BLOCK_HEADER_SIZE
|
41
|
+
raise "Invalid fragment #{fragment} for LSN #{@lsn_no}" unless fragment.between?(0, LOG_BLOCK_DATA_SIZE - 1)
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
data_offset?(@lsn_offset, group)
|
45
|
-
end
|
43
|
+
length + (fragment + length) / LOG_BLOCK_DATA_SIZE * LOG_BLOCK_FRAME_SIZE
|
44
|
+
end
|
46
45
|
|
47
|
-
|
46
|
+
# Whether LSN might point to log record data.
|
47
|
+
def record?(group)
|
48
|
+
data_offset?(@lsn_offset, group)
|
49
|
+
end
|
48
50
|
|
49
|
-
|
50
|
-
LOG_HEADER_SIZE = Innodb::Log::LOG_HEADER_SIZE
|
51
|
+
private
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
LOG_BLOCK_HEADER_SIZE = Innodb::LogBlock::HEADER_SIZE
|
55
|
-
LOG_BLOCK_TRAILER_SIZE = Innodb::LogBlock::TRAILER_SIZE
|
56
|
-
LOG_BLOCK_DATA_SIZE = Innodb::LogBlock::DATA_SIZE
|
57
|
-
LOG_BLOCK_FRAME_SIZE = LOG_BLOCK_HEADER_SIZE + LOG_BLOCK_TRAILER_SIZE
|
53
|
+
# Short alias for the size of a log file header.
|
54
|
+
LOG_HEADER_SIZE = Innodb::Log::LOG_HEADER_SIZE
|
58
55
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
56
|
+
# Short aliases for the sizes of the subparts of a log block.
|
57
|
+
LOG_BLOCK_SIZE = Innodb::LogBlock::BLOCK_SIZE
|
58
|
+
LOG_BLOCK_HEADER_SIZE = Innodb::LogBlock::HEADER_SIZE
|
59
|
+
LOG_BLOCK_TRAILER_SIZE = Innodb::LogBlock::TRAILER_SIZE
|
60
|
+
LOG_BLOCK_DATA_SIZE = Innodb::LogBlock::DATA_SIZE
|
61
|
+
LOG_BLOCK_FRAME_SIZE = LOG_BLOCK_HEADER_SIZE + LOG_BLOCK_TRAILER_SIZE
|
65
62
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
# Calculate the offset in LSN.
|
72
|
-
if new_lsn >= lsn
|
73
|
-
lsn_offset = new_lsn - lsn
|
74
|
-
else
|
75
|
-
lsn_offset = lsn - new_lsn
|
76
|
-
lsn_offset %= group_capacity
|
77
|
-
lsn_offset = group_capacity - lsn_offset
|
63
|
+
# Returns the coordinates of the given offset.
|
64
|
+
def location_of(offset, group)
|
65
|
+
log_no, log_offset = offset.divmod(group.size)
|
66
|
+
block_no, block_offset = (log_offset - LOG_HEADER_SIZE).divmod(LOG_BLOCK_SIZE)
|
67
|
+
[log_no, block_no, block_offset]
|
78
68
|
end
|
79
69
|
|
80
|
-
#
|
81
|
-
|
70
|
+
# Returns the offset of the given LSN within a log group.
|
71
|
+
def offset_of(lsn, offset, new_lsn, group)
|
72
|
+
log_size = group.log_size
|
73
|
+
group_capacity = group.capacity
|
82
74
|
|
83
|
-
|
75
|
+
# Calculate the offset in LSN.
|
76
|
+
if new_lsn >= lsn
|
77
|
+
lsn_offset = new_lsn - lsn
|
78
|
+
else
|
79
|
+
lsn_offset = lsn - new_lsn
|
80
|
+
lsn_offset %= group_capacity
|
81
|
+
lsn_offset = group_capacity - lsn_offset
|
82
|
+
end
|
84
83
|
|
85
|
-
|
86
|
-
|
87
|
-
end
|
84
|
+
# Transpose group size offset to a group capacity offset.
|
85
|
+
group_offset = offset - (LOG_HEADER_SIZE * (1 + offset / log_size))
|
88
86
|
|
89
|
-
|
90
|
-
def data_offset?(offset, group)
|
91
|
-
log_offset = offset % group.size
|
92
|
-
log_no, block_no, block_offset = location_of(offset, group)
|
87
|
+
offset = (lsn_offset + group_offset) % group_capacity
|
93
88
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
89
|
+
# Transpose group capacity offset to a group size offset.
|
90
|
+
offset + LOG_HEADER_SIZE * (1 + offset / (log_size - LOG_HEADER_SIZE))
|
91
|
+
end
|
92
|
+
|
93
|
+
# Whether offset points to the data area of an existing log block.
|
94
|
+
def data_offset?(offset, group)
|
95
|
+
log_offset = offset % group.size
|
96
|
+
log_no, block_no, block_offset = location_of(offset, group)
|
100
97
|
|
101
|
-
|
98
|
+
status ||= log_no > group.logs
|
99
|
+
status ||= log_offset <= LOG_HEADER_SIZE
|
100
|
+
status ||= block_no.negative?
|
101
|
+
status ||= block_no >= group.log(log_no).blocks
|
102
|
+
status ||= block_offset < Innodb::LogBlock::DATA_OFFSET
|
103
|
+
status ||= block_offset >= Innodb::LogBlock::TRAILER_OFFSET
|
104
|
+
|
105
|
+
!status
|
106
|
+
end
|
102
107
|
end
|
103
108
|
end
|
data/lib/innodb/page.rb
CHANGED
@@ -1,341 +1,496 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
2
4
|
|
3
5
|
# A generic class for any type of page, which handles reading the common
|
4
6
|
# FIL header and trailer, and can handle (via #parse) dispatching to a more
|
5
7
|
# specialized class depending on page type (which comes from the FIL header).
|
6
8
|
# A page being handled by Innodb::Page indicates that its type is not currently
|
7
9
|
# handled by any more specialized class.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
10
|
+
module Innodb
|
11
|
+
class Page
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
Address = Struct.new(
|
15
|
+
:page,
|
16
|
+
:offset,
|
17
|
+
keyword_init: true
|
18
|
+
)
|
19
|
+
|
20
|
+
FilHeader = Struct.new(
|
21
|
+
:checksum,
|
22
|
+
:offset,
|
23
|
+
:prev,
|
24
|
+
:next,
|
25
|
+
:lsn,
|
26
|
+
:type,
|
27
|
+
:flush_lsn,
|
28
|
+
:space_id,
|
29
|
+
keyword_init: true
|
30
|
+
)
|
31
|
+
|
32
|
+
class FilHeader
|
33
|
+
def lsn_low32
|
34
|
+
lsn & 0xffffffff
|
35
|
+
end
|
36
|
+
end
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
FilTrailer = Struct.new(
|
39
|
+
:checksum,
|
40
|
+
:lsn_low32,
|
41
|
+
keyword_init: true
|
42
|
+
)
|
43
|
+
|
44
|
+
Region = Struct.new(
|
45
|
+
:offset,
|
46
|
+
:length, # rubocop:disable Lint/StructNewOverride
|
47
|
+
:name,
|
48
|
+
:info,
|
49
|
+
keyword_init: true
|
50
|
+
)
|
51
|
+
|
52
|
+
# A hash of page types to specialized classes to handle them. Normally
|
53
|
+
# subclasses will register themselves in this list.
|
54
|
+
@specialized_classes = {}
|
55
|
+
|
56
|
+
class << self
|
57
|
+
attr_reader :specialized_classes
|
58
|
+
end
|
39
59
|
|
40
|
-
|
41
|
-
|
42
|
-
def initialize(space, buffer)
|
43
|
-
unless space && buffer
|
44
|
-
raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})"
|
60
|
+
def self.register_specialization(page_type, specialized_class)
|
61
|
+
@specialized_classes[page_type] = specialized_class
|
45
62
|
end
|
46
63
|
|
47
|
-
|
48
|
-
|
64
|
+
def self.specialization_for(page_type)
|
65
|
+
# This needs to intentionally use Innodb::Page because we need to register
|
66
|
+
# in the class instance variable in *that* class.
|
67
|
+
Innodb::Page.register_specialization(page_type, self)
|
49
68
|
end
|
50
69
|
|
51
|
-
|
52
|
-
|
53
|
-
|
70
|
+
def self.specialization_for?(page_type)
|
71
|
+
Innodb::Page.specialized_classes.include?(page_type)
|
72
|
+
end
|
54
73
|
|
55
|
-
|
74
|
+
# Load a page as a generic page in order to make the "fil" header accessible,
|
75
|
+
# and then attempt to hand off the page to a specialized class to be
|
76
|
+
# re-parsed if possible. If there is no specialized class for this type
|
77
|
+
# of page, return the generic object.
|
78
|
+
#
|
79
|
+
# This could be optimized to reach into the page buffer and efficiently
|
80
|
+
# extract the page type in order to avoid throwing away a generic
|
81
|
+
# Innodb::Page object when parsing every specialized page, but this is
|
82
|
+
# a bit cleaner, and we're not particularly performance sensitive.
|
83
|
+
def self.parse(space, buffer, page_number = nil)
|
84
|
+
# Create a page object as a generic page.
|
85
|
+
page = Innodb::Page.new(space, buffer, page_number)
|
86
|
+
|
87
|
+
# If there is a specialized class available for this page type, re-create
|
88
|
+
# the page object using that specialized class.
|
89
|
+
if (specialized_class = specialized_classes[page.type])
|
90
|
+
page = specialized_class.handle(page, space, buffer, page_number)
|
91
|
+
end
|
92
|
+
|
93
|
+
page
|
94
|
+
end
|
56
95
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
96
|
+
# Allow the specialized class to do something that isn't 'new' with this page.
|
97
|
+
def self.handle(_page, space, buffer, page_number = nil)
|
98
|
+
new(space, buffer, page_number)
|
99
|
+
end
|
61
100
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
PAGE_TYPE_BY_VALUE[page_type],
|
71
|
-
]
|
72
|
-
end
|
101
|
+
# Initialize a page by passing in a buffer containing the raw page contents.
|
102
|
+
# The buffer size should match the space's page size.
|
103
|
+
def initialize(space, buffer, page_number = nil)
|
104
|
+
unless space && buffer
|
105
|
+
raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})"
|
106
|
+
end
|
107
|
+
|
108
|
+
raise "Buffer size #{buffer.size} is different than space page size" unless space.page_size == buffer.size
|
73
109
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
# return value of the block.
|
78
|
-
def cursor(buffer_offset)
|
79
|
-
new_cursor = BufferCursor.new(@buffer, buffer_offset)
|
80
|
-
new_cursor.push_name("space[#{space.name}]")
|
81
|
-
new_cursor.push_name("page[#{name}]")
|
82
|
-
|
83
|
-
if block_given?
|
84
|
-
# Call the block once and return its return value.
|
85
|
-
yield new_cursor
|
86
|
-
else
|
87
|
-
# Return the cursor itself.
|
88
|
-
new_cursor
|
110
|
+
@space = space
|
111
|
+
@buffer = buffer
|
112
|
+
@page_number = page_number
|
89
113
|
end
|
90
|
-
end
|
91
114
|
|
92
|
-
|
93
|
-
# beginning of the page. Included here primarily for completeness.
|
94
|
-
def pos_fil_header
|
95
|
-
0
|
96
|
-
end
|
115
|
+
attr_reader :space
|
97
116
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
117
|
+
# Return the page size, to eventually be able to deal with non-16kB pages.
|
118
|
+
def size
|
119
|
+
@size ||= @buffer.size
|
120
|
+
end
|
102
121
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
size - size_fil_trailer
|
107
|
-
end
|
122
|
+
def default_page_size?
|
123
|
+
size == Innodb::Space::DEFAULT_PAGE_SIZE
|
124
|
+
end
|
108
125
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
126
|
+
# Return a simple string to uniquely identify this page within the space.
|
127
|
+
# Be careful not to call anything which would instantiate a BufferCursor
|
128
|
+
# so that we can use this method in cursor initialization.
|
129
|
+
def name
|
130
|
+
page_offset = BinData::Uint32be.read(@buffer.slice(4, 4))
|
131
|
+
page_type = BinData::Uint16be.read(@buffer.slice(24, 2))
|
132
|
+
"%i,%s" % [
|
133
|
+
page_offset,
|
134
|
+
PAGE_TYPE_BY_VALUE[page_type],
|
135
|
+
]
|
136
|
+
end
|
113
137
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
138
|
+
# If no block is passed, return an BufferCursor object positioned at a
|
139
|
+
# specific offset. If a block is passed, create a cursor at the provided
|
140
|
+
# offset and yield it to the provided block one time, and then return the
|
141
|
+
# return value of the block.
|
142
|
+
def cursor(buffer_offset)
|
143
|
+
new_cursor = BufferCursor.new(@buffer, buffer_offset)
|
144
|
+
new_cursor.push_name("space[#{space.name}]")
|
145
|
+
new_cursor.push_name("page[#{name}]")
|
146
|
+
|
147
|
+
if block_given?
|
148
|
+
# Call the block once and return its return value.
|
149
|
+
yield new_cursor
|
150
|
+
else
|
151
|
+
# Return the cursor itself.
|
152
|
+
new_cursor
|
153
|
+
end
|
154
|
+
end
|
119
155
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
:usage => "page type field has not been initialized",
|
126
|
-
},
|
127
|
-
:UNDO_LOG => {
|
128
|
-
:value => 2,
|
129
|
-
:description => "Undo log",
|
130
|
-
:usage => "stores previous values of modified records",
|
131
|
-
},
|
132
|
-
:INODE => {
|
133
|
-
:value => 3,
|
134
|
-
:description => "File segment inode",
|
135
|
-
:usage => "bookkeeping for file segments",
|
136
|
-
},
|
137
|
-
:IBUF_FREE_LIST => {
|
138
|
-
:value => 4,
|
139
|
-
:description => "Insert buffer free list",
|
140
|
-
:usage => "bookkeeping for insert buffer free space management",
|
141
|
-
},
|
142
|
-
:IBUF_BITMAP => {
|
143
|
-
:value => 5,
|
144
|
-
:description => "Insert buffer bitmap",
|
145
|
-
:usage => "bookkeeping for insert buffer writes to be merged",
|
146
|
-
},
|
147
|
-
:SYS => {
|
148
|
-
:value => 6,
|
149
|
-
:description => "System internal",
|
150
|
-
:usage => "used for various purposes in the system tablespace",
|
151
|
-
},
|
152
|
-
:TRX_SYS => {
|
153
|
-
:value => 7,
|
154
|
-
:description => "Transaction system header",
|
155
|
-
:usage => "bookkeeping for the transaction system in system tablespace",
|
156
|
-
},
|
157
|
-
:FSP_HDR => {
|
158
|
-
:value => 8,
|
159
|
-
:description => "File space header",
|
160
|
-
:usage => "header page (page 0) for each tablespace file",
|
161
|
-
},
|
162
|
-
:XDES => {
|
163
|
-
:value => 9,
|
164
|
-
:description => "Extent descriptor",
|
165
|
-
:usage => "header page for subsequent blocks of 16,384 pages",
|
166
|
-
},
|
167
|
-
:BLOB => {
|
168
|
-
:value => 10,
|
169
|
-
:description => "Uncompressed BLOB",
|
170
|
-
:usage => "externally-stored uncompressed BLOB column data",
|
171
|
-
},
|
172
|
-
:ZBLOB => {
|
173
|
-
:value => 11,
|
174
|
-
:description => "First compressed BLOB",
|
175
|
-
:usage => "externally-stored compressed BLOB column data, first page",
|
176
|
-
},
|
177
|
-
:ZBLOB2 => {
|
178
|
-
:value => 12,
|
179
|
-
:description => "Subsequent compressed BLOB",
|
180
|
-
:usage => "externally-stored compressed BLOB column data, subsequent page",
|
181
|
-
},
|
182
|
-
:INDEX => {
|
183
|
-
:value => 17855,
|
184
|
-
:description => "B+Tree index",
|
185
|
-
:usage => "table and index data stored in B+Tree structure",
|
186
|
-
},
|
187
|
-
}
|
188
|
-
|
189
|
-
PAGE_TYPE_BY_VALUE = PAGE_TYPE.inject({}) { |h, (k, v)| h[v[:value]] = k; h }
|
190
|
-
|
191
|
-
# A helper to convert "undefined" values stored in previous and next pointers
|
192
|
-
# in the page header to nil.
|
193
|
-
def self.maybe_undefined(value)
|
194
|
-
value == 4294967295 ? nil : value
|
195
|
-
end
|
156
|
+
# Return the byte offset of the start of the "fil" header, which is at the
|
157
|
+
# beginning of the page. Included here primarily for completeness.
|
158
|
+
def pos_fil_header
|
159
|
+
0
|
160
|
+
end
|
196
161
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
{
|
201
|
-
:checksum => c.name("checksum") { c.get_uint32 },
|
202
|
-
:offset => c.name("offset") { c.get_uint32 },
|
203
|
-
:prev => c.name("prev") {
|
204
|
-
Innodb::Page.maybe_undefined(c.get_uint32)
|
205
|
-
},
|
206
|
-
:next => c.name("next") {
|
207
|
-
Innodb::Page.maybe_undefined(c.get_uint32)
|
208
|
-
},
|
209
|
-
:lsn => c.name("lsn") { c.get_uint64 },
|
210
|
-
:type => c.name("type") { PAGE_TYPE_BY_VALUE[c.get_uint16] },
|
211
|
-
:flush_lsn => c.name("flush_lsn") { c.get_uint64 },
|
212
|
-
:space_id => c.name("space_id") { c.get_uint32 },
|
213
|
-
}
|
162
|
+
# Return the size of the "fil" header, in bytes.
|
163
|
+
def size_fil_header
|
164
|
+
4 + 4 + 4 + 4 + 8 + 2 + 8 + 4
|
214
165
|
end
|
215
|
-
end
|
216
166
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
end
|
167
|
+
# The start of the checksummed portion of the file header.
|
168
|
+
def pos_partial_page_header
|
169
|
+
pos_fil_header + 4
|
170
|
+
end
|
222
171
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
172
|
+
# The size of the portion of the fil header that is included in the
|
173
|
+
# checksum. Exclude the following:
|
174
|
+
# :checksum (offset 4, size 4)
|
175
|
+
# :flush_lsn (offset 26, size 8)
|
176
|
+
# :space_id (offset 34, size 4)
|
177
|
+
def size_partial_page_header
|
178
|
+
size_fil_header - 4 - 8 - 4
|
179
|
+
end
|
228
180
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
end
|
181
|
+
# Return the byte offset of the start of the "fil" trailer, which is at
|
182
|
+
# the end of the page.
|
183
|
+
def pos_fil_trailer
|
184
|
+
size - size_fil_trailer
|
185
|
+
end
|
235
186
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
fil_header[:next]
|
241
|
-
end
|
187
|
+
# Return the size of the "fil" trailer, in bytes.
|
188
|
+
def size_fil_trailer
|
189
|
+
4 + 4
|
190
|
+
end
|
242
191
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
192
|
+
# Return the position of the "body" of the page, which starts after the FIL
|
193
|
+
# header.
|
194
|
+
def pos_page_body
|
195
|
+
pos_fil_header + size_fil_header
|
196
|
+
end
|
247
197
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
end
|
198
|
+
# Return the size of the page body, excluding the header and trailer.
|
199
|
+
def size_page_body
|
200
|
+
size - size_fil_trailer - size_fil_header
|
201
|
+
end
|
253
202
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
203
|
+
# InnoDB Page Type constants from include/fil0fil.h.
|
204
|
+
PAGE_TYPE = {
|
205
|
+
ALLOCATED: {
|
206
|
+
value: 0,
|
207
|
+
description: "Freshly allocated",
|
208
|
+
usage: "page type field has not been initialized",
|
209
|
+
},
|
210
|
+
UNDO_LOG: {
|
211
|
+
value: 2,
|
212
|
+
description: "Undo log",
|
213
|
+
usage: "stores previous values of modified records",
|
214
|
+
},
|
215
|
+
INODE: {
|
216
|
+
value: 3,
|
217
|
+
description: "File segment inode",
|
218
|
+
usage: "bookkeeping for file segments",
|
219
|
+
},
|
220
|
+
IBUF_FREE_LIST: {
|
221
|
+
value: 4,
|
222
|
+
description: "Insert buffer free list",
|
223
|
+
usage: "bookkeeping for insert buffer free space management",
|
224
|
+
},
|
225
|
+
IBUF_BITMAP: {
|
226
|
+
value: 5,
|
227
|
+
description: "Insert buffer bitmap",
|
228
|
+
usage: "bookkeeping for insert buffer writes to be merged",
|
229
|
+
},
|
230
|
+
SYS: {
|
231
|
+
value: 6,
|
232
|
+
description: "System internal",
|
233
|
+
usage: "used for various purposes in the system tablespace",
|
234
|
+
},
|
235
|
+
TRX_SYS: {
|
236
|
+
value: 7,
|
237
|
+
description: "Transaction system header",
|
238
|
+
usage: "bookkeeping for the transaction system in system tablespace",
|
239
|
+
},
|
240
|
+
FSP_HDR: {
|
241
|
+
value: 8,
|
242
|
+
description: "File space header",
|
243
|
+
usage: "header page (page 0) for each tablespace file",
|
244
|
+
},
|
245
|
+
XDES: {
|
246
|
+
value: 9,
|
247
|
+
description: "Extent descriptor",
|
248
|
+
usage: "header page for subsequent blocks of 16,384 pages",
|
249
|
+
},
|
250
|
+
BLOB: {
|
251
|
+
value: 10,
|
252
|
+
description: "Uncompressed BLOB",
|
253
|
+
usage: "externally-stored uncompressed BLOB column data",
|
254
|
+
},
|
255
|
+
ZBLOB: {
|
256
|
+
value: 11,
|
257
|
+
description: "First compressed BLOB",
|
258
|
+
usage: "externally-stored compressed BLOB column data, first page",
|
259
|
+
},
|
260
|
+
ZBLOB2: {
|
261
|
+
value: 12,
|
262
|
+
description: "Subsequent compressed BLOB",
|
263
|
+
usage: "externally-stored compressed BLOB column data, subsequent page",
|
264
|
+
},
|
265
|
+
INDEX: {
|
266
|
+
value: 17_855,
|
267
|
+
description: "B+Tree index",
|
268
|
+
usage: "table and index data stored in B+Tree structure",
|
269
|
+
},
|
270
|
+
}.freeze
|
271
|
+
|
272
|
+
PAGE_TYPE_BY_VALUE = PAGE_TYPE.each_with_object({}) { |(k, v), h| h[v[:value]] = k }
|
273
|
+
|
274
|
+
# A page number representing "undefined" values, (4294967295).
|
275
|
+
UNDEFINED_PAGE_NUMBER = 2**32 - 1
|
276
|
+
|
277
|
+
# A helper to check if a page number is the undefined page number.
|
278
|
+
def self.undefined?(page_number)
|
279
|
+
page_number == UNDEFINED_PAGE_NUMBER
|
260
280
|
end
|
261
281
|
|
262
|
-
#
|
263
|
-
#
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
282
|
+
# A helper to convert "undefined" values stored in previous and next pointers
|
283
|
+
# in the page header to nil.
|
284
|
+
def self.maybe_undefined(page_number)
|
285
|
+
page_number unless undefined?(page_number)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Return the "fil" header from the page, which is common for all page types.
|
289
|
+
def fil_header
|
290
|
+
@fil_header ||= cursor(pos_fil_header).name("fil_header") do |c|
|
291
|
+
FilHeader.new(
|
292
|
+
checksum: c.name("checksum") { c.read_uint32 },
|
293
|
+
offset: c.name("offset") { c.read_uint32 },
|
294
|
+
prev: c.name("prev") { Innodb::Page.maybe_undefined(c.read_uint32) },
|
295
|
+
next: c.name("next") { Innodb::Page.maybe_undefined(c.read_uint32) },
|
296
|
+
lsn: c.name("lsn") { c.read_uint64 },
|
297
|
+
type: c.name("type") { PAGE_TYPE_BY_VALUE[c.read_uint16] },
|
298
|
+
flush_lsn: c.name("flush_lsn") { c.read_uint64 },
|
299
|
+
space_id: c.name("space_id") { c.read_uint32 }
|
270
300
|
)
|
271
|
-
|
301
|
+
end
|
302
|
+
end
|
272
303
|
|
273
|
-
#
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
304
|
+
# Return the "fil" trailer from the page, which is common for all page types.
|
305
|
+
def fil_trailer
|
306
|
+
@fil_trailer ||= cursor(pos_fil_trailer).name("fil_trailer") do |c|
|
307
|
+
FilTrailer.new(
|
308
|
+
checksum: c.name("checksum") { c.read_uint32 },
|
309
|
+
lsn_low32: c.name("lsn_low32") { c.read_uint32 }
|
279
310
|
)
|
280
|
-
|
311
|
+
end
|
312
|
+
end
|
281
313
|
|
282
|
-
|
283
|
-
|
284
|
-
|
314
|
+
def_delegator :fil_header, :checksum
|
315
|
+
def_delegator :fil_header, :offset
|
316
|
+
def_delegator :fil_header, :prev
|
317
|
+
def_delegator :fil_header, :next
|
318
|
+
def_delegator :fil_header, :lsn
|
319
|
+
def_delegator :fil_header, :type
|
320
|
+
def_delegator :fil_header, :space_id
|
285
321
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
checksum != calculate_checksum
|
290
|
-
end
|
322
|
+
# Iterate each byte of the FIL header.
|
323
|
+
def each_page_header_byte_as_uint8(&block)
|
324
|
+
return enum_for(:each_page_header_byte_as_uint8) unless block_given?
|
291
325
|
|
292
|
-
|
293
|
-
unless block_given?
|
294
|
-
return enum_for(:each_region)
|
326
|
+
cursor(pos_partial_page_header).each_byte_as_uint8(size_partial_page_header, &block)
|
295
327
|
end
|
296
328
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
:
|
301
|
-
:info => "FIL Header",
|
302
|
-
})
|
329
|
+
# Iterate each byte of the page body, except for the FIL header and
|
330
|
+
# the FIL trailer.
|
331
|
+
def each_page_body_byte_as_uint8(&block)
|
332
|
+
return enum_for(:each_page_body_byte_as_uint8) unless block_given?
|
303
333
|
|
304
|
-
|
305
|
-
|
306
|
-
:length => size_fil_trailer,
|
307
|
-
:name => :fil_trailer,
|
308
|
-
:info => "FIL Trailer",
|
309
|
-
})
|
334
|
+
cursor(pos_page_body).each_byte_as_uint8(size_page_body, &block)
|
335
|
+
end
|
310
336
|
|
311
|
-
|
312
|
-
|
337
|
+
# Calculate the checksum of the page using InnoDB's algorithm.
|
338
|
+
def checksum_innodb
|
339
|
+
raise "Checksum calculation is only supported for 16 KiB pages" unless default_page_size?
|
313
340
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
341
|
+
@checksum_innodb ||= begin
|
342
|
+
# Calculate the InnoDB checksum of the page header.
|
343
|
+
c_partial_header = Innodb::Checksum.fold_enumerator(each_page_header_byte_as_uint8)
|
344
|
+
|
345
|
+
# Calculate the InnoDB checksum of the page body.
|
346
|
+
c_page_body = Innodb::Checksum.fold_enumerator(each_page_body_byte_as_uint8)
|
347
|
+
|
348
|
+
# Add the two checksums together, and mask the result back to 32 bits.
|
349
|
+
(c_partial_header + c_page_body) & Innodb::Checksum::MAX
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def checksum_innodb?
|
354
|
+
checksum == checksum_innodb
|
355
|
+
end
|
356
|
+
|
357
|
+
# Calculate the checksum of the page using the CRC32c algorithm.
|
358
|
+
def checksum_crc32
|
359
|
+
raise "Checksum calculation is only supported for 16 KiB pages" unless default_page_size?
|
360
|
+
|
361
|
+
@checksum_crc32 ||= begin
|
362
|
+
# Calculate the CRC32c of the page header.
|
363
|
+
crc_partial_header = Digest::CRC32c.new
|
364
|
+
each_page_header_byte_as_uint8 do |byte|
|
365
|
+
crc_partial_header << byte.chr
|
366
|
+
end
|
367
|
+
|
368
|
+
# Calculate the CRC32c of the page body.
|
369
|
+
crc_page_body = Digest::CRC32c.new
|
370
|
+
each_page_body_byte_as_uint8 do |byte|
|
371
|
+
crc_page_body << byte.chr
|
372
|
+
end
|
373
|
+
|
374
|
+
# Bitwise XOR the two checksums together.
|
375
|
+
crc_partial_header.checksum ^ crc_page_body.checksum
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
def checksum_crc32?
|
380
|
+
checksum == checksum_crc32
|
381
|
+
end
|
382
|
+
|
383
|
+
# Is the page checksum correct?
|
384
|
+
def checksum_valid?
|
385
|
+
checksum_crc32? || checksum_innodb?
|
386
|
+
end
|
387
|
+
|
388
|
+
# Is the page checksum incorrect?
|
389
|
+
def checksum_invalid?
|
390
|
+
!checksum_valid?
|
391
|
+
end
|
392
|
+
|
393
|
+
def checksum_type
|
394
|
+
return :crc32 if checksum_crc32?
|
395
|
+
return :innodb if checksum_innodb?
|
396
|
+
|
397
|
+
nil
|
398
|
+
end
|
399
|
+
|
400
|
+
# Is the LSN stored in the header different from the one stored in the
|
401
|
+
# trailer?
|
402
|
+
def torn?
|
403
|
+
fil_header.lsn_low32 != fil_trailer.lsn_low32
|
329
404
|
end
|
330
|
-
end
|
331
405
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
406
|
+
# Is the page in the doublewrite buffer?
|
407
|
+
def in_doublewrite_buffer?
|
408
|
+
space&.system_space? && space&.doublewrite_page?(offset)
|
409
|
+
end
|
410
|
+
|
411
|
+
# Is the space ID stored in the header different from that of the space
|
412
|
+
# provided when initializing this page?
|
413
|
+
def misplaced_space?
|
414
|
+
space && (space_id != space.space_id)
|
415
|
+
end
|
416
|
+
|
417
|
+
# Is the page number stored in the header different from the page number
|
418
|
+
# which was supposed to be read?
|
419
|
+
def misplaced_offset?
|
420
|
+
offset != @page_number
|
421
|
+
end
|
422
|
+
|
423
|
+
# Is the page misplaced in the wrong file or by offset in the file?
|
424
|
+
def misplaced?
|
425
|
+
!in_doublewrite_buffer? && (misplaced_space? || misplaced_offset?)
|
426
|
+
end
|
336
427
|
|
337
|
-
|
338
|
-
|
339
|
-
|
428
|
+
# Is the page corrupt, either due to data corruption, tearing, or in the
|
429
|
+
# wrong place?
|
430
|
+
def corrupt?
|
431
|
+
checksum_invalid? || torn? || misplaced?
|
432
|
+
end
|
433
|
+
|
434
|
+
# Is this an extent descriptor page (either FSP_HDR or XDES)?
|
435
|
+
def extent_descriptor?
|
436
|
+
type == :FSP_HDR || type == :XDES
|
437
|
+
end
|
438
|
+
|
439
|
+
def each_region
|
440
|
+
return enum_for(:each_region) unless block_given?
|
441
|
+
|
442
|
+
yield Region.new(
|
443
|
+
offset: pos_fil_header,
|
444
|
+
length: size_fil_header,
|
445
|
+
name: :fil_header,
|
446
|
+
info: "FIL Header"
|
447
|
+
)
|
448
|
+
|
449
|
+
yield Region.new(
|
450
|
+
offset: pos_fil_trailer,
|
451
|
+
length: size_fil_trailer,
|
452
|
+
name: :fil_trailer,
|
453
|
+
info: "FIL Trailer"
|
454
|
+
)
|
455
|
+
|
456
|
+
nil
|
457
|
+
end
|
458
|
+
|
459
|
+
def inspect_header_fields
|
460
|
+
return nil unless fil_header
|
461
|
+
|
462
|
+
%i[
|
463
|
+
size
|
464
|
+
space_id
|
465
|
+
offset
|
466
|
+
type
|
467
|
+
prev
|
468
|
+
next
|
469
|
+
checksum_valid?
|
470
|
+
checksum_type
|
471
|
+
torn?
|
472
|
+
misplaced?
|
473
|
+
].map { |m| "#{m}=#{send(m).inspect}" }.join(", ")
|
474
|
+
end
|
475
|
+
|
476
|
+
# Implement a custom inspect method to avoid irb printing the contents of
|
477
|
+
# the page buffer, since it's very large and mostly not interesting.
|
478
|
+
def inspect
|
479
|
+
"#<#{self.class} #{inspect_header_fields || '(page header unavailable)'}>"
|
480
|
+
end
|
481
|
+
|
482
|
+
# Dump the contents of a page for debugging purposes.
|
483
|
+
def dump
|
484
|
+
puts "#{self}:"
|
485
|
+
puts
|
486
|
+
|
487
|
+
puts "fil header:"
|
488
|
+
pp fil_header
|
489
|
+
puts
|
490
|
+
|
491
|
+
puts "fil trailer:"
|
492
|
+
pp fil_trailer
|
493
|
+
puts
|
494
|
+
end
|
340
495
|
end
|
341
496
|
end
|