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/page/undo_log.rb
CHANGED
@@ -1,95 +1,109 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Innodb
|
4
|
+
class Page
|
5
|
+
class UndoLog < Page
|
6
|
+
specialization_for :UNDO_LOG
|
7
|
+
|
8
|
+
PageHeader = Struct.new(
|
9
|
+
:type,
|
10
|
+
:latest_log_record_offset,
|
11
|
+
:free_offset,
|
12
|
+
:page_list_node,
|
13
|
+
keyword_init: true
|
14
|
+
)
|
15
|
+
|
16
|
+
SegmentHeader = Struct.new(
|
17
|
+
:state,
|
18
|
+
:last_log_offset,
|
19
|
+
:fseg,
|
20
|
+
:page_list,
|
21
|
+
keyword_init: true
|
22
|
+
)
|
23
|
+
|
24
|
+
def pos_undo_page_header
|
25
|
+
pos_page_body
|
26
|
+
end
|
27
|
+
|
28
|
+
def size_undo_page_header
|
29
|
+
2 + 2 + 2 + Innodb::List::NODE_SIZE
|
30
|
+
end
|
31
|
+
|
32
|
+
def pos_undo_segment_header
|
33
|
+
pos_undo_page_header + size_undo_page_header
|
34
|
+
end
|
35
|
+
|
36
|
+
def size_undo_segment_header
|
37
|
+
2 + 2 + Innodb::FsegEntry::SIZE + Innodb::List::BASE_NODE_SIZE
|
38
|
+
end
|
39
|
+
|
40
|
+
def pos_undo_logs
|
41
|
+
pos_undo_segment_header + size_undo_segment_header
|
42
|
+
end
|
43
|
+
|
44
|
+
UNDO_PAGE_TYPES = {
|
45
|
+
1 => :insert,
|
46
|
+
2 => :update,
|
47
|
+
}.freeze
|
48
|
+
|
49
|
+
UNDO_SEGMENT_STATES = {
|
50
|
+
1 => :active,
|
51
|
+
2 => :cached,
|
52
|
+
3 => :to_free,
|
53
|
+
4 => :to_purge,
|
54
|
+
5 => :prepared,
|
55
|
+
}.freeze
|
56
|
+
|
57
|
+
def undo_page_header
|
58
|
+
@undo_page_header ||= cursor(pos_undo_page_header).name("undo_page_header") do |c|
|
59
|
+
PageHeader.new(
|
60
|
+
type: c.name("type") { UNDO_PAGE_TYPES[c.read_uint16] },
|
61
|
+
latest_log_record_offset: c.name("latest_log_record_offset") { c.read_uint16 },
|
62
|
+
free_offset: c.name("free_offset") { c.read_uint16 },
|
63
|
+
page_list_node: c.name("page_list") { Innodb::List.get_node(c) }
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def prev_address
|
69
|
+
undo_page_header[:page_list_node][:prev]
|
70
|
+
end
|
71
|
+
|
72
|
+
def next_address
|
73
|
+
undo_page_header[:page_list_node][:next]
|
74
|
+
end
|
75
|
+
|
76
|
+
def undo_segment_header
|
77
|
+
@undo_segment_header ||= cursor(pos_undo_segment_header).name("undo_segment_header") do |c|
|
78
|
+
SegmentHeader.new(
|
79
|
+
state: c.name("state") { UNDO_SEGMENT_STATES[c.read_uint16] },
|
80
|
+
last_log_offset: c.name("last_log_offset") { c.read_uint16 },
|
81
|
+
fseg: c.name("fseg") { Innodb::FsegEntry.get_inode(@space, c) },
|
82
|
+
page_list: c.name("page_list") { Innodb::List::UndoPage.new(@space, Innodb::List.get_base_node(c)) }
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def undo_log(pos)
|
88
|
+
Innodb::UndoLog.new(self, pos)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Dump the contents of a page for debugging purposes.
|
92
|
+
def dump
|
93
|
+
super
|
94
|
+
|
95
|
+
puts "undo page header:"
|
96
|
+
pp undo_page_header
|
97
|
+
puts
|
98
|
+
|
99
|
+
puts "undo segment header:"
|
100
|
+
pp undo_segment_header
|
101
|
+
puts
|
102
|
+
|
103
|
+
puts "last undo log:"
|
104
|
+
undo_log(undo_segment_header[:last_log_offset]).dump unless undo_segment_header[:last_log_offset].zero?
|
105
|
+
puts
|
106
|
+
end
|
46
107
|
end
|
47
108
|
end
|
48
|
-
|
49
|
-
def prev_address
|
50
|
-
undo_page_header[:page_list_node][:prev]
|
51
|
-
end
|
52
|
-
|
53
|
-
def next_address
|
54
|
-
undo_page_header[:page_list_node][:next]
|
55
|
-
end
|
56
|
-
|
57
|
-
def undo_segment_header
|
58
|
-
@undo_segment_header ||=
|
59
|
-
cursor(pos_undo_segment_header).name("undo_segment_header") do |c|
|
60
|
-
{
|
61
|
-
:state => c.name("state") { UNDO_SEGMENT_STATES[c.get_uint16] },
|
62
|
-
:last_log_offset => c.name("last_log_offset") { c.get_uint16 },
|
63
|
-
:fseg => c.name("fseg") { Innodb::FsegEntry.get_inode(@space, c) },
|
64
|
-
:page_list => c.name("page_list") {
|
65
|
-
Innodb::List::UndoPage.new(@space, Innodb::List.get_base_node(c))
|
66
|
-
},
|
67
|
-
}
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def undo_log(pos)
|
72
|
-
Innodb::UndoLog.new(self, pos)
|
73
|
-
end
|
74
|
-
|
75
|
-
# Dump the contents of a page for debugging purposes.
|
76
|
-
def dump
|
77
|
-
super
|
78
|
-
|
79
|
-
puts "undo page header:"
|
80
|
-
pp undo_page_header
|
81
|
-
puts
|
82
|
-
|
83
|
-
puts "undo segment header:"
|
84
|
-
pp undo_segment_header
|
85
|
-
puts
|
86
|
-
|
87
|
-
puts "last undo log:"
|
88
|
-
if undo_segment_header[:last_log_offset] != 0
|
89
|
-
undo_log(undo_segment_header[:last_log_offset]).dump
|
90
|
-
end
|
91
|
-
puts
|
92
|
-
end
|
93
109
|
end
|
94
|
-
|
95
|
-
Innodb::Page::SPECIALIZED_CLASSES[:UNDO_LOG] = Innodb::Page::UndoLog
|
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
|