innodb_ruby 0.9.16 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +5 -6
- data/bin/innodb_log +13 -18
- data/bin/innodb_space +377 -757
- data/lib/innodb.rb +4 -5
- data/lib/innodb/checksum.rb +26 -24
- data/lib/innodb/data_dictionary.rb +490 -550
- data/lib/innodb/data_type.rb +362 -326
- 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 +154 -155
- 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 +417 -414
- 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 +964 -943
- 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 -160
- data/lib/innodb/record_describer.rb +66 -68
- data/lib/innodb/space.rb +380 -418
- 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 +80 -11
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,493 +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
|
-
|
34
|
-
|
35
|
-
# Allow the specialized class to do something that isn't 'new' with this page.
|
36
|
-
def self.handle(page, space, buffer, page_number=nil)
|
37
|
-
self.new(space, buffer, page_number)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Initialize a page by passing in a buffer containing the raw page contents.
|
41
|
-
# The buffer size should match the space's page size.
|
42
|
-
def initialize(space, buffer, page_number=nil)
|
43
|
-
unless space && buffer
|
44
|
-
raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})"
|
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
|
45
36
|
end
|
46
37
|
|
47
|
-
|
48
|
-
|
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
|
49
58
|
end
|
50
59
|
|
51
|
-
|
52
|
-
|
53
|
-
@page_number = page_number
|
54
|
-
end
|
55
|
-
|
56
|
-
attr_reader :space
|
57
|
-
|
58
|
-
# Return the page size, to eventually be able to deal with non-16kB pages.
|
59
|
-
def size
|
60
|
-
@size ||= @buffer.size
|
61
|
-
end
|
62
|
-
|
63
|
-
# Return a simple string to uniquely identify this page within the space.
|
64
|
-
# Be careful not to call anything which would instantiate a BufferCursor
|
65
|
-
# so that we can use this method in cursor initialization.
|
66
|
-
def name
|
67
|
-
page_offset = BinData::Uint32be.read(@buffer.slice(4, 4))
|
68
|
-
page_type = BinData::Uint16be.read(@buffer.slice(24, 2))
|
69
|
-
"%i,%s" % [
|
70
|
-
page_offset,
|
71
|
-
PAGE_TYPE_BY_VALUE[page_type],
|
72
|
-
]
|
73
|
-
end
|
74
|
-
|
75
|
-
# If no block is passed, return an BufferCursor object positioned at a
|
76
|
-
# specific offset. If a block is passed, create a cursor at the provided
|
77
|
-
# offset and yield it to the provided block one time, and then return the
|
78
|
-
# return value of the block.
|
79
|
-
def cursor(buffer_offset)
|
80
|
-
new_cursor = BufferCursor.new(@buffer, buffer_offset)
|
81
|
-
new_cursor.push_name("space[#{space.name}]")
|
82
|
-
new_cursor.push_name("page[#{name}]")
|
83
|
-
|
84
|
-
if block_given?
|
85
|
-
# Call the block once and return its return value.
|
86
|
-
yield new_cursor
|
87
|
-
else
|
88
|
-
# Return the cursor itself.
|
89
|
-
new_cursor
|
60
|
+
def self.register_specialization(page_type, specialized_class)
|
61
|
+
@specialized_classes[page_type] = specialized_class
|
90
62
|
end
|
91
|
-
end
|
92
63
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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)
|
68
|
+
end
|
98
69
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end
|
70
|
+
def self.specialization_for?(page_type)
|
71
|
+
Innodb::Page.specialized_classes.include?(page_type)
|
72
|
+
end
|
103
73
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
108
92
|
|
109
|
-
|
110
|
-
|
111
|
-
# :checksum (offset 4, size 4)
|
112
|
-
# :flush_lsn (offset 26, size 8)
|
113
|
-
# :space_id (offset 34, size 4)
|
114
|
-
def size_partial_page_header
|
115
|
-
size_fil_header - 4 - 8 - 4
|
116
|
-
end
|
93
|
+
page
|
94
|
+
end
|
117
95
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
end
|
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
|
123
100
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
128
107
|
|
129
|
-
|
130
|
-
# header.
|
131
|
-
def pos_page_body
|
132
|
-
pos_fil_header + size_fil_header
|
133
|
-
end
|
108
|
+
raise "Buffer size #{buffer.size} is different than space page size" unless space.page_size == buffer.size
|
134
109
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
110
|
+
@space = space
|
111
|
+
@buffer = buffer
|
112
|
+
@page_number = page_number
|
113
|
+
end
|
139
114
|
|
140
|
-
|
141
|
-
PAGE_TYPE = {
|
142
|
-
:ALLOCATED => {
|
143
|
-
:value => 0,
|
144
|
-
:description => "Freshly allocated",
|
145
|
-
:usage => "page type field has not been initialized",
|
146
|
-
},
|
147
|
-
:UNDO_LOG => {
|
148
|
-
:value => 2,
|
149
|
-
:description => "Undo log",
|
150
|
-
:usage => "stores previous values of modified records",
|
151
|
-
},
|
152
|
-
:INODE => {
|
153
|
-
:value => 3,
|
154
|
-
:description => "File segment inode",
|
155
|
-
:usage => "bookkeeping for file segments",
|
156
|
-
},
|
157
|
-
:IBUF_FREE_LIST => {
|
158
|
-
:value => 4,
|
159
|
-
:description => "Insert buffer free list",
|
160
|
-
:usage => "bookkeeping for insert buffer free space management",
|
161
|
-
},
|
162
|
-
:IBUF_BITMAP => {
|
163
|
-
:value => 5,
|
164
|
-
:description => "Insert buffer bitmap",
|
165
|
-
:usage => "bookkeeping for insert buffer writes to be merged",
|
166
|
-
},
|
167
|
-
:SYS => {
|
168
|
-
:value => 6,
|
169
|
-
:description => "System internal",
|
170
|
-
:usage => "used for various purposes in the system tablespace",
|
171
|
-
},
|
172
|
-
:TRX_SYS => {
|
173
|
-
:value => 7,
|
174
|
-
:description => "Transaction system header",
|
175
|
-
:usage => "bookkeeping for the transaction system in system tablespace",
|
176
|
-
},
|
177
|
-
:FSP_HDR => {
|
178
|
-
:value => 8,
|
179
|
-
:description => "File space header",
|
180
|
-
:usage => "header page (page 0) for each tablespace file",
|
181
|
-
},
|
182
|
-
:XDES => {
|
183
|
-
:value => 9,
|
184
|
-
:description => "Extent descriptor",
|
185
|
-
:usage => "header page for subsequent blocks of 16,384 pages",
|
186
|
-
},
|
187
|
-
:BLOB => {
|
188
|
-
:value => 10,
|
189
|
-
:description => "Uncompressed BLOB",
|
190
|
-
:usage => "externally-stored uncompressed BLOB column data",
|
191
|
-
},
|
192
|
-
:ZBLOB => {
|
193
|
-
:value => 11,
|
194
|
-
:description => "First compressed BLOB",
|
195
|
-
:usage => "externally-stored compressed BLOB column data, first page",
|
196
|
-
},
|
197
|
-
:ZBLOB2 => {
|
198
|
-
:value => 12,
|
199
|
-
:description => "Subsequent compressed BLOB",
|
200
|
-
:usage => "externally-stored compressed BLOB column data, subsequent page",
|
201
|
-
},
|
202
|
-
:INDEX => {
|
203
|
-
:value => 17855,
|
204
|
-
:description => "B+Tree index",
|
205
|
-
:usage => "table and index data stored in B+Tree structure",
|
206
|
-
},
|
207
|
-
}
|
208
|
-
|
209
|
-
PAGE_TYPE_BY_VALUE = PAGE_TYPE.inject({}) { |h, (k, v)| h[v[:value]] = k; h }
|
210
|
-
|
211
|
-
# A helper to convert "undefined" values stored in previous and next pointers
|
212
|
-
# in the page header to nil.
|
213
|
-
def self.maybe_undefined(value)
|
214
|
-
value == 4294967295 ? nil : value
|
215
|
-
end
|
115
|
+
attr_reader :space
|
216
116
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
{
|
221
|
-
:checksum => c.name("checksum") { c.get_uint32 },
|
222
|
-
:offset => c.name("offset") { c.get_uint32 },
|
223
|
-
:prev => c.name("prev") {
|
224
|
-
Innodb::Page.maybe_undefined(c.get_uint32)
|
225
|
-
},
|
226
|
-
:next => c.name("next") {
|
227
|
-
Innodb::Page.maybe_undefined(c.get_uint32)
|
228
|
-
},
|
229
|
-
:lsn => c.name("lsn") { c.get_uint64 },
|
230
|
-
:type => c.name("type") { PAGE_TYPE_BY_VALUE[c.get_uint16] },
|
231
|
-
:flush_lsn => c.name("flush_lsn") { c.get_uint64 },
|
232
|
-
:space_id => c.name("space_id") { c.get_uint32 },
|
233
|
-
}
|
117
|
+
# Return the page size, to eventually be able to deal with non-16kB pages.
|
118
|
+
def size
|
119
|
+
@size ||= @buffer.size
|
234
120
|
end
|
235
|
-
end
|
236
121
|
|
237
|
-
|
238
|
-
|
239
|
-
@fil_trailer ||= cursor(pos_fil_trailer).name("fil_trailer") do |c|
|
240
|
-
{
|
241
|
-
:checksum => c.name("checksum") { c.get_uint32 },
|
242
|
-
:lsn_low32 => c.name("lsn_low32") { c.get_uint32 },
|
243
|
-
}
|
122
|
+
def default_page_size?
|
123
|
+
size == Innodb::Space::DEFAULT_PAGE_SIZE
|
244
124
|
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# A helper function to return the checksum from the "fil" header, for easier
|
248
|
-
# access.
|
249
|
-
def checksum
|
250
|
-
fil_header[:checksum]
|
251
|
-
end
|
252
125
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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
|
258
137
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
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
|
264
155
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
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
|
271
161
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
fil_header[:next]
|
277
|
-
end
|
162
|
+
# Return the size of the "fil" header, in bytes.
|
163
|
+
def size_fil_header
|
164
|
+
4 + 4 + 4 + 4 + 8 + 2 + 8 + 4
|
165
|
+
end
|
278
166
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
167
|
+
# The start of the checksummed portion of the file header.
|
168
|
+
def pos_partial_page_header
|
169
|
+
pos_fil_header + 4
|
170
|
+
end
|
283
171
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
289
180
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
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
|
295
186
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
end
|
187
|
+
# Return the size of the "fil" trailer, in bytes.
|
188
|
+
def size_fil_trailer
|
189
|
+
4 + 4
|
190
|
+
end
|
301
191
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
307
197
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
return enum_for(:each_page_header_byte_as_uint8)
|
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
|
312
201
|
end
|
313
202
|
|
314
|
-
|
315
|
-
|
316
|
-
|
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
|
317
280
|
end
|
318
|
-
end
|
319
281
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
return enum_for(:each_page_body_byte_as_uint8)
|
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)
|
325
286
|
end
|
326
287
|
|
327
|
-
|
328
|
-
|
329
|
-
|
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 }
|
300
|
+
)
|
301
|
+
end
|
330
302
|
end
|
331
|
-
end
|
332
303
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
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 }
|
310
|
+
)
|
311
|
+
end
|
337
312
|
end
|
338
313
|
|
339
|
-
|
340
|
-
|
341
|
-
|
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
|
342
321
|
|
343
|
-
|
344
|
-
|
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?
|
345
325
|
|
346
|
-
|
347
|
-
(c_partial_header + c_page_body) & Innodb::Checksum::MAX
|
326
|
+
cursor(pos_partial_page_header).each_byte_as_uint8(size_partial_page_header, &block)
|
348
327
|
end
|
349
|
-
end
|
350
328
|
|
351
|
-
|
352
|
-
|
353
|
-
|
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?
|
354
333
|
|
355
|
-
|
356
|
-
def checksum_crc32
|
357
|
-
unless size == 16384
|
358
|
-
raise "Checksum calculation is only supported for 16 KiB pages"
|
334
|
+
cursor(pos_page_body).each_byte_as_uint8(size_page_body, &block)
|
359
335
|
end
|
360
336
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
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?
|
340
|
+
|
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
|
366
350
|
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def checksum_innodb?
|
354
|
+
checksum == checksum_innodb
|
355
|
+
end
|
367
356
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
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
|
372
376
|
end
|
377
|
+
end
|
373
378
|
|
374
|
-
|
375
|
-
|
379
|
+
def checksum_crc32?
|
380
|
+
checksum == checksum_crc32
|
376
381
|
end
|
377
|
-
end
|
378
382
|
|
379
|
-
|
380
|
-
|
381
|
-
|
383
|
+
# Is the page checksum correct?
|
384
|
+
def checksum_valid?
|
385
|
+
checksum_crc32? || checksum_innodb?
|
386
|
+
end
|
382
387
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
388
|
+
# Is the page checksum incorrect?
|
389
|
+
def checksum_invalid?
|
390
|
+
!checksum_valid?
|
391
|
+
end
|
387
392
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
end
|
393
|
+
def checksum_type
|
394
|
+
return :crc32 if checksum_crc32?
|
395
|
+
return :innodb if checksum_innodb?
|
392
396
|
|
393
|
-
|
394
|
-
case
|
395
|
-
when checksum_crc32?
|
396
|
-
:crc32
|
397
|
-
when checksum_innodb?
|
398
|
-
:innodb
|
397
|
+
nil
|
399
398
|
end
|
400
|
-
end
|
401
399
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
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
|
404
|
+
end
|
407
405
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
406
|
+
# Is the page in the doublewrite buffer?
|
407
|
+
def in_doublewrite_buffer?
|
408
|
+
space&.system_space? && space&.doublewrite_page?(offset)
|
409
|
+
end
|
412
410
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
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
|
418
416
|
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
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
|
424
422
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
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
|
429
427
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
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
|
435
433
|
|
436
|
-
|
437
|
-
|
438
|
-
|
434
|
+
# Is this an extent descriptor page (either FSP_HDR or XDES)?
|
435
|
+
def extent_descriptor?
|
436
|
+
type == :FSP_HDR || type == :XDES
|
439
437
|
end
|
440
438
|
|
441
|
-
|
442
|
-
:
|
443
|
-
:length => size_fil_header,
|
444
|
-
:name => :fil_header,
|
445
|
-
:info => "FIL Header",
|
446
|
-
})
|
439
|
+
def each_region
|
440
|
+
return enum_for(:each_region) unless block_given?
|
447
441
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
442
|
+
yield Region.new(
|
443
|
+
offset: pos_fil_header,
|
444
|
+
length: size_fil_header,
|
445
|
+
name: :fil_header,
|
446
|
+
info: "FIL Header"
|
447
|
+
)
|
454
448
|
|
455
|
-
|
456
|
-
|
449
|
+
yield Region.new(
|
450
|
+
offset: pos_fil_trailer,
|
451
|
+
length: size_fil_trailer,
|
452
|
+
name: :fil_trailer,
|
453
|
+
info: "FIL Trailer"
|
454
|
+
)
|
457
455
|
|
458
|
-
|
459
|
-
# the page buffer, since it's very large and mostly not interesting.
|
460
|
-
def inspect
|
461
|
-
if fil_header
|
462
|
-
"#<%s: size=%i, space_id=%i, offset=%i, type=%s, prev=%s, next=%s, checksum_valid?=%s (%s), torn?=%s, misplaced?=%s>" % [
|
463
|
-
self.class,
|
464
|
-
size,
|
465
|
-
fil_header[:space_id],
|
466
|
-
fil_header[:offset],
|
467
|
-
fil_header[:type],
|
468
|
-
fil_header[:prev] || "nil",
|
469
|
-
fil_header[:next] || "nil",
|
470
|
-
checksum_valid?,
|
471
|
-
checksum_type ? checksum_type : "unknown",
|
472
|
-
torn?,
|
473
|
-
misplaced?,
|
474
|
-
]
|
475
|
-
else
|
476
|
-
"#<#{self.class}>"
|
456
|
+
nil
|
477
457
|
end
|
478
|
-
end
|
479
458
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
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
|
484
481
|
|
485
|
-
|
486
|
-
|
487
|
-
|
482
|
+
# Dump the contents of a page for debugging purposes.
|
483
|
+
def dump
|
484
|
+
puts "#{self}:"
|
485
|
+
puts
|
488
486
|
|
489
|
-
|
490
|
-
|
491
|
-
|
487
|
+
puts "fil header:"
|
488
|
+
pp fil_header
|
489
|
+
puts
|
490
|
+
|
491
|
+
puts "fil trailer:"
|
492
|
+
pp fil_trailer
|
493
|
+
puts
|
494
|
+
end
|
492
495
|
end
|
493
496
|
end
|