innodb_ruby 0.7.11 → 0.7.12
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/innodb_space +162 -25
- data/lib/innodb.rb +8 -1
- data/lib/innodb/checksum.rb +30 -0
- data/lib/innodb/cursor.rb +165 -37
- data/lib/innodb/field.rb +31 -57
- data/lib/innodb/field_type.rb +124 -0
- data/lib/innodb/fseg_entry.rb +9 -6
- data/lib/innodb/index.rb +23 -12
- data/lib/innodb/inode.rb +133 -0
- data/lib/innodb/list.rb +44 -12
- data/lib/innodb/log.rb +1 -0
- data/lib/innodb/log_block.rb +2 -1
- data/lib/innodb/page.rb +93 -17
- data/lib/innodb/page/blob.rb +60 -0
- data/lib/innodb/page/fsp_hdr_xdes.rb +34 -24
- data/lib/innodb/page/index.rb +364 -190
- data/lib/innodb/page/inode.rb +20 -51
- data/lib/innodb/page/sys.rb +22 -0
- data/lib/innodb/page/sys_data_dictionary_header.rb +92 -0
- data/lib/innodb/page/sys_rseg_header.rb +59 -0
- data/lib/innodb/page/trx_sys.rb +72 -29
- data/lib/innodb/page/undo_log.rb +95 -0
- data/lib/innodb/record_describer.rb +2 -1
- data/lib/innodb/space.rb +162 -17
- data/lib/innodb/undo_log.rb +73 -0
- data/lib/innodb/version.rb +2 -1
- data/lib/innodb/xdes.rb +44 -11
- metadata +19 -6
data/lib/innodb/list.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
# An abstract InnoDB "free list" or FLST (renamed to just "list" here as it
|
2
3
|
# frequently is used for structures that aren't free lists). This class must
|
3
4
|
# be sub-classed to provide an appropriate #object_from_address method.
|
@@ -11,8 +12,10 @@ class Innodb::List
|
|
11
12
|
# or "NULL" pointer (the page number is UINT32_MAX), or the address if
|
12
13
|
# valid.
|
13
14
|
def self.get_address(cursor)
|
14
|
-
page =
|
15
|
-
|
15
|
+
page = cursor.name("page") {
|
16
|
+
Innodb::Page.maybe_undefined(cursor.get_uint32)
|
17
|
+
}
|
18
|
+
offset = cursor.name("offset") { cursor.get_uint16 }
|
16
19
|
if page
|
17
20
|
{
|
18
21
|
:page => page,
|
@@ -30,8 +33,8 @@ class Innodb::List
|
|
30
33
|
# linked list.
|
31
34
|
def self.get_node(cursor)
|
32
35
|
{
|
33
|
-
:prev => get_address(cursor),
|
34
|
-
:next => get_address(cursor),
|
36
|
+
:prev => cursor.name("prev") { get_address(cursor) },
|
37
|
+
:next => cursor.name("next") { get_address(cursor) },
|
35
38
|
}
|
36
39
|
end
|
37
40
|
|
@@ -46,9 +49,9 @@ class Innodb::List
|
|
46
49
|
# address.
|
47
50
|
def self.get_base_node(cursor)
|
48
51
|
{
|
49
|
-
:length => cursor.get_uint32,
|
50
|
-
:first => get_address(cursor),
|
51
|
-
:last => get_address(cursor),
|
52
|
+
:length => cursor.name("length") { cursor.get_uint32 },
|
53
|
+
:first => cursor.name("first") { get_address(cursor) },
|
54
|
+
:last => cursor.name("last") { get_address(cursor) },
|
52
55
|
}
|
53
56
|
end
|
54
57
|
|
@@ -86,6 +89,11 @@ class Innodb::List
|
|
86
89
|
object_from_address(object.next_address)
|
87
90
|
end
|
88
91
|
|
92
|
+
# Return the number of items in the list.
|
93
|
+
def length
|
94
|
+
@base[:length]
|
95
|
+
end
|
96
|
+
|
89
97
|
# Return the first object in the list using the list base node "first"
|
90
98
|
# address pointer.
|
91
99
|
def first
|
@@ -99,8 +107,16 @@ class Innodb::List
|
|
99
107
|
end
|
100
108
|
|
101
109
|
# Return a list cursor for the list.
|
102
|
-
def
|
103
|
-
|
110
|
+
def list_cursor(node=nil)
|
111
|
+
ListCursor.new(self, node)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Return whether the given item is present in the list. This depends on the
|
115
|
+
# item and the items in the list implementing some sufficient == method.
|
116
|
+
# This is implemented rather inefficiently by constructing an array and
|
117
|
+
# leaning on Array#include? to do the real work.
|
118
|
+
def include?(item)
|
119
|
+
each.to_a.include? item
|
104
120
|
end
|
105
121
|
|
106
122
|
# Iterate through all nodes in the list.
|
@@ -109,7 +125,7 @@ class Innodb::List
|
|
109
125
|
return enum_for(:each)
|
110
126
|
end
|
111
127
|
|
112
|
-
c =
|
128
|
+
c = list_cursor
|
113
129
|
while e = c.next
|
114
130
|
yield e
|
115
131
|
end
|
@@ -117,7 +133,7 @@ class Innodb::List
|
|
117
133
|
|
118
134
|
# A list iteration cursor used primarily by the Innodb::List #cursor method
|
119
135
|
# implicitly. Keeps its own state for iterating through lists efficiently.
|
120
|
-
class
|
136
|
+
class ListCursor
|
121
137
|
def initialize(list, node=nil)
|
122
138
|
@list = list
|
123
139
|
@cursor = node
|
@@ -174,4 +190,20 @@ class Innodb::List::Inode < Innodb::List
|
|
174
190
|
page
|
175
191
|
end
|
176
192
|
end
|
177
|
-
end
|
193
|
+
end
|
194
|
+
|
195
|
+
class Innodb::List::UndoPage < Innodb::List
|
196
|
+
def object_from_address(address)
|
197
|
+
if address && page = @space.page(address[:page])
|
198
|
+
page
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class Innodb::List::History < Innodb::List
|
204
|
+
def object_from_address(address)
|
205
|
+
if address && page = @space.page(address[:page])
|
206
|
+
Innodb::UndoLog.new(page, address[:offset] - 34)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
data/lib/innodb/log.rb
CHANGED
data/lib/innodb/log_block.rb
CHANGED
data/lib/innodb/page.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
require "innodb/cursor"
|
2
3
|
|
3
4
|
# A generic class for any type of page, which handles reading the common
|
@@ -26,15 +27,24 @@ class Innodb::Page
|
|
26
27
|
# If there is a specialized class available for this page type, re-create
|
27
28
|
# the page object using that specialized class.
|
28
29
|
if specialized_class = SPECIALIZED_CLASSES[page.type]
|
29
|
-
page = specialized_class.
|
30
|
+
page = specialized_class.handle(page, space, buffer)
|
30
31
|
end
|
31
32
|
|
32
33
|
page
|
33
34
|
end
|
34
35
|
|
36
|
+
# Allow the specialized class to do something that isn't 'new' with this page.
|
37
|
+
def self.handle(page, space, buffer)
|
38
|
+
self.new(space, buffer)
|
39
|
+
end
|
40
|
+
|
35
41
|
# Initialize a page by passing in a buffer containing the raw page contents.
|
36
42
|
# The buffer size should match the space's page size.
|
37
43
|
def initialize(space, buffer)
|
44
|
+
unless space && buffer
|
45
|
+
raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})"
|
46
|
+
end
|
47
|
+
|
38
48
|
unless space.page_size == buffer.size
|
39
49
|
raise "Buffer size #{buffer.size} is different than space page size"
|
40
50
|
end
|
@@ -56,9 +66,20 @@ class Innodb::Page
|
|
56
66
|
@buffer[offset...(offset + length)]
|
57
67
|
end
|
58
68
|
|
59
|
-
#
|
69
|
+
# If no block is passed, return an Innodb::Cursor object positioned at a
|
70
|
+
# specific offset. If a block is passed, create a cursor at the provided
|
71
|
+
# offset and yield it to the provided block one time, and then return the
|
72
|
+
# return value of the block.
|
60
73
|
def cursor(offset)
|
61
|
-
Innodb::Cursor.new(self, offset)
|
74
|
+
new_cursor = Innodb::Cursor.new(self, offset)
|
75
|
+
|
76
|
+
if block_given?
|
77
|
+
# Call the block once and return its return value.
|
78
|
+
yield new_cursor
|
79
|
+
else
|
80
|
+
# Return the cursor itself.
|
81
|
+
new_cursor
|
82
|
+
end
|
62
83
|
end
|
63
84
|
|
64
85
|
# Return the byte offset of the start of the "fil" header, which is at the
|
@@ -83,6 +104,12 @@ class Innodb::Page
|
|
83
104
|
4 + 4
|
84
105
|
end
|
85
106
|
|
107
|
+
# Return the position of the "body" of the page, which starts after the FIL
|
108
|
+
# header.
|
109
|
+
def pos_page_body
|
110
|
+
pos_fil_header + size_fil_header
|
111
|
+
end
|
112
|
+
|
86
113
|
# InnoDB Page Type constants from include/fil0fil.h.
|
87
114
|
PAGE_TYPE = {
|
88
115
|
0 => :ALLOCATED, # Freshly allocated page
|
@@ -108,23 +135,28 @@ class Innodb::Page
|
|
108
135
|
|
109
136
|
# Return the "fil" header from the page, which is common for all page types.
|
110
137
|
def fil_header
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
138
|
+
@fil_header ||= cursor(pos_fil_header).name("fil") do |c|
|
139
|
+
{
|
140
|
+
:checksum => c.name("checksum") { c.get_uint32 },
|
141
|
+
:offset => c.name("offset") { c.get_uint32 },
|
142
|
+
:prev => c.name("prev") {
|
143
|
+
Innodb::Page.maybe_undefined(c.get_uint32)
|
144
|
+
},
|
145
|
+
:next => c.name("next") {
|
146
|
+
Innodb::Page.maybe_undefined(c.get_uint32)
|
147
|
+
},
|
148
|
+
:lsn => c.name("lsn") { c.get_uint64 },
|
149
|
+
:type => c.name("type") { PAGE_TYPE[c.get_uint16] },
|
150
|
+
:flush_lsn => c.name("flush_lsn") { c.get_uint64 },
|
151
|
+
:space_id => c.name("space_id") { c.get_uint32 },
|
152
|
+
}
|
153
|
+
end
|
122
154
|
end
|
123
155
|
|
124
|
-
# A helper function to return the
|
156
|
+
# A helper function to return the checksum from the "fil" header, for easier
|
125
157
|
# access.
|
126
|
-
def
|
127
|
-
fil_header[:
|
158
|
+
def checksum
|
159
|
+
fil_header[:checksum]
|
128
160
|
end
|
129
161
|
|
130
162
|
# A helper function to return the page offset from the "fil" header, for
|
@@ -152,6 +184,50 @@ class Innodb::Page
|
|
152
184
|
fil_header[:lsn]
|
153
185
|
end
|
154
186
|
|
187
|
+
# A helper function to return the page type from the "fil" header, for easier
|
188
|
+
# access.
|
189
|
+
def type
|
190
|
+
fil_header[:type]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Calculate the checksum of the page using InnoDB's algorithm. Two sections
|
194
|
+
# of the page are checksummed separately, and then added together to produce
|
195
|
+
# the final checksum.
|
196
|
+
def calculate_checksum
|
197
|
+
unless size == 16384
|
198
|
+
raise "Checksum calculation is only supported for 16 KiB pages"
|
199
|
+
end
|
200
|
+
|
201
|
+
# Calculate the checksum of the FIL header, except for the following:
|
202
|
+
# :checksum (offset 4, size 4)
|
203
|
+
# :flush_lsn (offset 26, size 8)
|
204
|
+
# :space_id (offset 34, size 4)
|
205
|
+
c_partial_header =
|
206
|
+
Innodb::Checksum.fold_enumerator(
|
207
|
+
cursor(pos_fil_header + 4).each_byte_as_uint8(
|
208
|
+
size_fil_header - 4 - 8 - 4
|
209
|
+
)
|
210
|
+
)
|
211
|
+
|
212
|
+
# Calculate the checksum of the page body, except for the FIL header and
|
213
|
+
# the FIL trailer.
|
214
|
+
c_page_body =
|
215
|
+
Innodb::Checksum.fold_enumerator(
|
216
|
+
cursor(pos_page_body).each_byte_as_uint8(
|
217
|
+
size - size_fil_trailer - size_fil_header
|
218
|
+
)
|
219
|
+
)
|
220
|
+
|
221
|
+
# Add the two checksums together, and mask the result back to 32 bits.
|
222
|
+
(c_partial_header + c_page_body) & Innodb::Checksum::MAX
|
223
|
+
end
|
224
|
+
|
225
|
+
# Is the page corrupt? Calculate the checksum of the page and compare to
|
226
|
+
# the stored checksum; return true or false.
|
227
|
+
def corrupt?
|
228
|
+
checksum != calculate_checksum
|
229
|
+
end
|
230
|
+
|
155
231
|
# Implement a custom inspect method to avoid irb printing the contents of
|
156
232
|
# the page buffer, since it's very large and mostly not interesting.
|
157
233
|
def inspect
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Innodb::Page::Blob < Innodb::Page
|
4
|
+
def pos_blob_header
|
5
|
+
pos_fil_header + size_fil_header
|
6
|
+
end
|
7
|
+
|
8
|
+
def size_blob_header
|
9
|
+
4 + 4
|
10
|
+
end
|
11
|
+
|
12
|
+
def pos_blob_data
|
13
|
+
pos_blob_header + size_blob_header
|
14
|
+
end
|
15
|
+
|
16
|
+
def blob_header
|
17
|
+
cursor(pos_blob_header).name("blob_header") do |c|
|
18
|
+
{
|
19
|
+
:length => c.name("length") { c.get_uint32 },
|
20
|
+
:next => c.name("next") { Innodb::Page.maybe_undefined(c.get_uint32) },
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def blob_data
|
26
|
+
cursor(pos_blob_data).name("blob_data") do |c|
|
27
|
+
c.get_bytes(blob_header[:length])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def dump_hex(string)
|
32
|
+
slice_size = 16
|
33
|
+
bytes = string.split("").map { |s| s.ord }
|
34
|
+
string.split("").each_slice(slice_size).each_with_index do |slice_bytes, slice_count|
|
35
|
+
puts "%08i %-23s %-23s |%-16s|" % [
|
36
|
+
(slice_count * slice_size),
|
37
|
+
slice_bytes[0..8].map { |n| "%02x" % n.ord }.join(" "),
|
38
|
+
slice_bytes[8..16].map { |n| "%02x" % n.ord }.join(" "),
|
39
|
+
slice_bytes.join(""),
|
40
|
+
]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Dump the contents of a page for debugging purposes.
|
45
|
+
def dump
|
46
|
+
super
|
47
|
+
|
48
|
+
puts "blob header:"
|
49
|
+
pp blob_header
|
50
|
+
puts
|
51
|
+
|
52
|
+
puts "blob data:"
|
53
|
+
dump_hex(blob_data)
|
54
|
+
puts
|
55
|
+
|
56
|
+
puts
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Innodb::Page::SPECIALIZED_CLASSES[:BLOB] = Innodb::Page::Blob
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
require "innodb/list"
|
2
3
|
require "innodb/xdes"
|
3
4
|
|
@@ -75,26 +76,34 @@ class Innodb::Page::FspHdrXdes < Innodb::Page
|
|
75
76
|
# Read the FSP (filespace) header, which contains a few counters and flags,
|
76
77
|
# as well as list base nodes for each list maintained in the filespace.
|
77
78
|
def fsp_header
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
79
|
+
@fsp_header ||= cursor(pos_fsp_header).name("fsp") do |c|
|
80
|
+
{
|
81
|
+
:space_id => c.name("space_id") { c.get_uint32 },
|
82
|
+
:unused => c.name("unused") { c.get_uint32 },
|
83
|
+
:size => c.name("size") { c.get_uint32 },
|
84
|
+
:free_limit => c.name("free_limit") { c.get_uint32 },
|
85
|
+
:flags => c.name("flags") {
|
86
|
+
self.class.decode_flags(c.get_uint32)
|
87
|
+
},
|
88
|
+
:frag_n_used => c.name("frag_n_used") { c.get_uint32 },
|
89
|
+
:free => c.name("list[free]") {
|
90
|
+
Innodb::List::Xdes.new(@space, Innodb::List.get_base_node(c))
|
91
|
+
},
|
92
|
+
:free_frag => c.name("list[free_frag]") {
|
93
|
+
Innodb::List::Xdes.new(@space, Innodb::List.get_base_node(c))
|
94
|
+
},
|
95
|
+
:full_frag => c.name("list[full_frag]") {
|
96
|
+
Innodb::List::Xdes.new(@space, Innodb::List.get_base_node(c))
|
97
|
+
},
|
98
|
+
:first_unused_seg => c.name("first_unused_seg") { c.get_uint64 },
|
99
|
+
:full_inodes => c.name("list[full_inodes]") {
|
100
|
+
Innodb::List::Inode.new(@space, Innodb::List.get_base_node(c))
|
101
|
+
},
|
102
|
+
:free_inodes => c.name("list[free_inodes]") {
|
103
|
+
Innodb::List::Inode.new(@space, Innodb::List.get_base_node(c))
|
104
|
+
},
|
105
|
+
}
|
106
|
+
end
|
98
107
|
end
|
99
108
|
|
100
109
|
# Iterate through all lists in the file space.
|
@@ -117,9 +126,10 @@ class Innodb::Page::FspHdrXdes < Innodb::Page
|
|
117
126
|
return enum_for(:each_xdes)
|
118
127
|
end
|
119
128
|
|
120
|
-
|
121
|
-
|
122
|
-
|
129
|
+
cursor(pos_xdes_array).name("xdes_array") do |c|
|
130
|
+
entries_in_xdes_array.times do |n|
|
131
|
+
yield Innodb::Xdes.new(self, c)
|
132
|
+
end
|
123
133
|
end
|
124
134
|
end
|
125
135
|
|
@@ -140,4 +150,4 @@ class Innodb::Page::FspHdrXdes < Innodb::Page
|
|
140
150
|
end
|
141
151
|
|
142
152
|
Innodb::Page::SPECIALIZED_CLASSES[:FSP_HDR] = Innodb::Page::FspHdrXdes
|
143
|
-
Innodb::Page::SPECIALIZED_CLASSES[:XDES] = Innodb::Page::FspHdrXdes
|
153
|
+
Innodb::Page::SPECIALIZED_CLASSES[:XDES] = Innodb::Page::FspHdrXdes
|
data/lib/innodb/page/index.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
1
2
|
require "innodb/fseg_entry"
|
2
3
|
|
3
4
|
# A specialized class for handling INDEX pages, which contain a portion of
|
@@ -11,6 +12,60 @@ require "innodb/fseg_entry"
|
|
11
12
|
class Innodb::Page::Index < Innodb::Page
|
12
13
|
attr_accessor :record_describer
|
13
14
|
|
15
|
+
# The size (in bytes) of the "next" pointer in each record header.
|
16
|
+
RECORD_NEXT_SIZE = 2
|
17
|
+
|
18
|
+
# The size (in bytes) of the bit-packed fields in each record header for
|
19
|
+
# "redundant" record format.
|
20
|
+
RECORD_REDUNDANT_BITS_SIZE = 4
|
21
|
+
|
22
|
+
# Masks for 1-byte record end-offsets within "redundant" records.
|
23
|
+
RECORD_REDUNDANT_OFF1_OFFSET_MASK = 0x7f
|
24
|
+
RECORD_REDUNDANT_OFF1_NULL_MASK = 0x80
|
25
|
+
|
26
|
+
# Masks for 2-byte record end-offsets within "redundant" records.
|
27
|
+
RECORD_REDUNDANT_OFF2_OFFSET_MASK = 0x3fff
|
28
|
+
RECORD_REDUNDANT_OFF2_NULL_MASK = 0x8000
|
29
|
+
RECORD_REDUNDANT_OFF2_EXTERN_MASK = 0x4000
|
30
|
+
|
31
|
+
# The size (in bytes) of the bit-packed fields in each record header for
|
32
|
+
# "compact" record format.
|
33
|
+
RECORD_COMPACT_BITS_SIZE = 3
|
34
|
+
|
35
|
+
# Page direction values possible in the page_header's :direction field.
|
36
|
+
PAGE_DIRECTION = {
|
37
|
+
1 => :left, # Inserts have been in descending order.
|
38
|
+
2 => :right, # Inserts have been in ascending order.
|
39
|
+
3 => :same_rec, # Unused by InnoDB.
|
40
|
+
4 => :same_page, # Unused by InnoDB.
|
41
|
+
5 => :no_direction, # Inserts have been in random order.
|
42
|
+
}
|
43
|
+
|
44
|
+
# Record types used in the :type field of the record header.
|
45
|
+
RECORD_TYPES = {
|
46
|
+
0 => :conventional, # A normal user record in a leaf page.
|
47
|
+
1 => :node_pointer, # A node pointer in a non-leaf page.
|
48
|
+
2 => :infimum, # The system "infimum" record.
|
49
|
+
3 => :supremum, # The system "supremum" record.
|
50
|
+
}
|
51
|
+
|
52
|
+
# This record is the minimum record at this level of the B-tree.
|
53
|
+
RECORD_INFO_MIN_REC_FLAG = 1
|
54
|
+
|
55
|
+
# This record has been marked as deleted.
|
56
|
+
RECORD_INFO_DELETED_FLAG = 2
|
57
|
+
|
58
|
+
# The size (in bytes) of the record pointers in each page directory slot.
|
59
|
+
PAGE_DIR_SLOT_SIZE = 2
|
60
|
+
|
61
|
+
# The minimum number of records "owned" by each record with an entry in
|
62
|
+
# the page directory.
|
63
|
+
PAGE_DIR_SLOT_MIN_N_OWNED = 4
|
64
|
+
|
65
|
+
# The maximum number of records "owned" by each record with an entry in
|
66
|
+
# the page directory.
|
67
|
+
PAGE_DIR_SLOT_MAX_N_OWNED = 8
|
68
|
+
|
14
69
|
# Return the byte offset of the start of the "index" page header, which
|
15
70
|
# immediately follows the "fil" header.
|
16
71
|
def pos_index_header
|
@@ -19,7 +74,7 @@ class Innodb::Page::Index < Innodb::Page
|
|
19
74
|
|
20
75
|
# The size of the "index" header.
|
21
76
|
def size_index_header
|
22
|
-
|
77
|
+
2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 8 + 2 + 8
|
23
78
|
end
|
24
79
|
|
25
80
|
# Return the byte offset of the start of the "fseg" header, which immediately
|
@@ -33,10 +88,27 @@ class Innodb::Page::Index < Innodb::Page
|
|
33
88
|
2 * Innodb::FsegEntry::SIZE
|
34
89
|
end
|
35
90
|
|
36
|
-
# Return the
|
37
|
-
|
38
|
-
|
39
|
-
|
91
|
+
# Return the size of the header for each record.
|
92
|
+
def size_record_header
|
93
|
+
case page_header[:format]
|
94
|
+
when :compact
|
95
|
+
RECORD_NEXT_SIZE + RECORD_COMPACT_BITS_SIZE
|
96
|
+
when :redundant
|
97
|
+
RECORD_NEXT_SIZE + RECORD_REDUNDANT_BITS_SIZE
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# The size of the additional data structures in the header of the system
|
102
|
+
# records, which is just 1 byte in redundant format to store the offset
|
103
|
+
# of the end of the field. This is needed specifically here since we need
|
104
|
+
# to be able to calculate the fixed positions of these system records.
|
105
|
+
def size_mum_record_header_additional
|
106
|
+
case page_header[:format]
|
107
|
+
when :compact
|
108
|
+
0 # No additional data is stored in compact format.
|
109
|
+
when :redundant
|
110
|
+
1 # A 1-byte offset for 1 field is stored in redundant format.
|
111
|
+
end
|
40
112
|
end
|
41
113
|
|
42
114
|
# The size of the data from the supremum or infimum records.
|
@@ -49,7 +121,9 @@ class Innodb::Page::Index < Innodb::Page
|
|
49
121
|
# page, and represents a record with a "lower value than any possible user
|
50
122
|
# record". The infimum record immediately follows the page header.
|
51
123
|
def pos_infimum
|
52
|
-
pos_records +
|
124
|
+
pos_records +
|
125
|
+
size_record_header +
|
126
|
+
size_mum_record_header_additional
|
53
127
|
end
|
54
128
|
|
55
129
|
# Return the byte offset of the start of the "origin" of the supremum record,
|
@@ -57,7 +131,18 @@ class Innodb::Page::Index < Innodb::Page
|
|
57
131
|
# page, and represents a record with a "higher value than any possible user
|
58
132
|
# record". The supremum record immediately follows the infimum record.
|
59
133
|
def pos_supremum
|
60
|
-
pos_infimum +
|
134
|
+
pos_infimum +
|
135
|
+
size_record_header +
|
136
|
+
size_mum_record_header_additional +
|
137
|
+
size_mum_record
|
138
|
+
end
|
139
|
+
|
140
|
+
# Return the byte offset of the start of records within the page (the
|
141
|
+
# position immediately after the page header).
|
142
|
+
def pos_records
|
143
|
+
size_fil_header +
|
144
|
+
size_index_header +
|
145
|
+
size_fseg_header
|
61
146
|
end
|
62
147
|
|
63
148
|
# Return the byte offset of the start of the user records in a page, which
|
@@ -96,7 +181,7 @@ class Innodb::Page::Index < Innodb::Page
|
|
96
181
|
|
97
182
|
# Return the amount of free space in the page.
|
98
183
|
def free_space
|
99
|
-
page_header[:
|
184
|
+
page_header[:garbage_size] +
|
100
185
|
(size - size_fil_trailer - directory_space - page_header[:heap_top])
|
101
186
|
end
|
102
187
|
|
@@ -116,33 +201,30 @@ class Innodb::Page::Index < Innodb::Page
|
|
116
201
|
data(pos_user_records, page_header[:heap_top] - pos_user_records)
|
117
202
|
end
|
118
203
|
|
119
|
-
# Page direction values possible in the page_header's :direction field.
|
120
|
-
PAGE_DIRECTION = {
|
121
|
-
1 => :left, # Inserts have been in descending order.
|
122
|
-
2 => :right, # Inserts have been in ascending order.
|
123
|
-
3 => :same_rec, # Unused by InnoDB.
|
124
|
-
4 => :same_page, # Unused by InnoDB.
|
125
|
-
5 => :no_direction, # Inserts have been in random order.
|
126
|
-
}
|
127
|
-
|
128
204
|
# Return the "index" header.
|
129
205
|
def page_header
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
206
|
+
@page_header ||= cursor(pos_index_header).name("index") do |c|
|
207
|
+
index = {
|
208
|
+
:n_dir_slots => c.name("n_dir_slots") { c.get_uint16 },
|
209
|
+
:heap_top => c.name("heap_top") { c.get_uint16 },
|
210
|
+
:n_heap_format => c.name("n_heap_format") { c.get_uint16 },
|
211
|
+
:garbage_offset => c.name("garbage_offset") { c.get_uint16 },
|
212
|
+
:garbage_size => c.name("garbage_size") { c.get_uint16 },
|
213
|
+
:last_insert_offset => c.name("last_insert_offset") { c.get_uint16 },
|
214
|
+
:direction => c.name("direction") { PAGE_DIRECTION[c.get_uint16] },
|
215
|
+
:n_direction => c.name("n_direction") { c.get_uint16 },
|
216
|
+
:n_recs => c.name("n_recs") { c.get_uint16 },
|
217
|
+
:max_trx_id => c.name("max_trx_id") { c.get_uint64 },
|
218
|
+
:level => c.name("level") { c.get_uint16 },
|
219
|
+
:index_id => c.name("index_id") { c.get_uint64 },
|
220
|
+
}
|
221
|
+
index[:n_heap] = index[:n_heap_format] & (2**15-1)
|
222
|
+
index[:format] = (index[:n_heap_format] & 1<<15) == 0 ?
|
223
|
+
:redundant : :compact
|
224
|
+
index.delete :n_heap_format
|
225
|
+
|
226
|
+
index
|
227
|
+
end
|
146
228
|
end
|
147
229
|
|
148
230
|
# A helper function to return the page level from the "page" header, for
|
@@ -162,156 +244,197 @@ class Innodb::Page::Index < Innodb::Page
|
|
162
244
|
self.prev.nil? && self.next.nil?
|
163
245
|
end
|
164
246
|
|
165
|
-
#
|
166
|
-
def
|
167
|
-
|
168
|
-
@fseg_header ||= {
|
169
|
-
:leaf => Innodb::FsegEntry.get_inode(@space, c),
|
170
|
-
:internal => Innodb::FsegEntry.get_inode(@space, c),
|
171
|
-
}
|
172
|
-
end
|
173
|
-
|
174
|
-
# The size (in bytes) of the bit-packed fields in each record header.
|
175
|
-
RECORD_BITS_SIZE = 3
|
176
|
-
|
177
|
-
# The size (in bytes) of the "next" pointer in each record header.
|
178
|
-
RECORD_NEXT_SIZE = 2
|
179
|
-
|
180
|
-
# The size (in bytes) of the record pointers in each page directory slot.
|
181
|
-
PAGE_DIR_SLOT_SIZE = 2
|
182
|
-
|
183
|
-
# The minimum number of records "owned" by each record with an entry in
|
184
|
-
# the page directory.
|
185
|
-
PAGE_DIR_SLOT_MIN_N_OWNED = 4
|
186
|
-
|
187
|
-
# The maximum number of records "owned" by each record with an entry in
|
188
|
-
# the page directory.
|
189
|
-
PAGE_DIR_SLOT_MAX_N_OWNED = 8
|
190
|
-
|
191
|
-
# Return the size of the header for each record.
|
192
|
-
def size_record_header
|
193
|
-
case page_header[:format]
|
194
|
-
when :compact
|
195
|
-
RECORD_BITS_SIZE + RECORD_NEXT_SIZE
|
196
|
-
when :redundant
|
197
|
-
RECORD_BITS_SIZE + RECORD_NEXT_SIZE + 1
|
198
|
-
end
|
247
|
+
# A helper function to return the offset to the first free record.
|
248
|
+
def garbage_offset
|
249
|
+
page_header && page_header[:garbage_offset]
|
199
250
|
end
|
200
251
|
|
201
|
-
# Return the
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
252
|
+
# Return the "fseg" header.
|
253
|
+
def fseg_header
|
254
|
+
@fseg_header ||= cursor(pos_fseg_header).name("fseg") do |c|
|
255
|
+
{
|
256
|
+
:leaf => c.name("fseg[leaf]") {
|
257
|
+
Innodb::FsegEntry.get_inode(@space, c)
|
258
|
+
},
|
259
|
+
:internal => c.name("fseg[internal]") {
|
260
|
+
Innodb::FsegEntry.get_inode(@space, c)
|
261
|
+
},
|
262
|
+
}
|
209
263
|
end
|
210
264
|
end
|
211
265
|
|
212
|
-
# Record types used in the :type field of the record header.
|
213
|
-
RECORD_TYPES = {
|
214
|
-
0 => :conventional, # A normal user record in a leaf page.
|
215
|
-
1 => :node_pointer, # A node pointer in a non-leaf page.
|
216
|
-
2 => :infimum, # The system "infimum" record.
|
217
|
-
3 => :supremum, # The system "supremum" record.
|
218
|
-
}
|
219
|
-
|
220
|
-
# This record is the minimum record at this level of the B-tree.
|
221
|
-
RECORD_INFO_MIN_REC_FLAG = 1
|
222
|
-
|
223
|
-
# This record has been marked as deleted.
|
224
|
-
RECORD_INFO_DELETED_FLAG = 2
|
225
|
-
|
226
266
|
# Return the header from a record.
|
227
|
-
def record_header(
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
267
|
+
def record_header(cursor)
|
268
|
+
origin = cursor.position
|
269
|
+
header = {}
|
270
|
+
cursor.backward.name("header") do |c|
|
271
|
+
case page_header[:format]
|
272
|
+
when :compact
|
273
|
+
# The "next" pointer is a relative offset from the current record.
|
274
|
+
header[:next] = c.name("next") { origin + c.get_sint16 }
|
275
|
+
|
276
|
+
# Fields packed in a 16-bit integer (LSB first):
|
277
|
+
# 3 bits for type
|
278
|
+
# 13 bits for heap_number
|
279
|
+
bits1 = c.name("bits1") { c.get_uint16 }
|
280
|
+
header[:type] = RECORD_TYPES[bits1 & 0x07]
|
281
|
+
header[:heap_number] = (bits1 & 0xf8) >> 3
|
282
|
+
when :redundant
|
283
|
+
# The "next" pointer is an absolute offset within the page.
|
284
|
+
header[:next] = c.name("next") { c.get_uint16 }
|
285
|
+
|
286
|
+
# Fields packed in a 24-bit integer (LSB first):
|
287
|
+
# 1 bit for offset_size (0 = 2 bytes, 1 = 1 byte)
|
288
|
+
# 10 bits for n_fields
|
289
|
+
# 13 bits for heap_number
|
290
|
+
bits1 = c.name("bits1") { c.get_uint24 }
|
291
|
+
header[:offset_size] = (bits1 & 1) == 0 ? 2 : 1
|
292
|
+
header[:n_fields] = (bits1 & (((1 << 10) - 1) << 1)) >> 1
|
293
|
+
header[:heap_number] = (bits1 & (((1 << 13) - 1) << 11)) >> 11
|
294
|
+
end
|
295
|
+
|
296
|
+
# Fields packed in an 8-bit integer (LSB first):
|
297
|
+
# 4 bits for n_owned
|
298
|
+
# 4 bits for flags
|
299
|
+
bits2 = c.name("bits2") { c.get_uint8 }
|
237
300
|
header[:n_owned] = bits2 & 0x0f
|
238
301
|
info = (bits2 & 0xf0) >> 4
|
239
302
|
header[:min_rec] = (info & RECORD_INFO_MIN_REC_FLAG) != 0
|
240
303
|
header[:deleted] = (info & RECORD_INFO_DELETED_FLAG) != 0
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
304
|
+
|
305
|
+
case page_header[:format]
|
306
|
+
when :compact
|
307
|
+
record_header_compact_additional(header, cursor)
|
308
|
+
when :redundant
|
309
|
+
record_header_redundant_additional(header, cursor)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
header
|
314
|
+
end
|
315
|
+
|
316
|
+
# Read additional header information from a compact format record header.
|
317
|
+
def record_header_compact_additional(header, cursor)
|
318
|
+
case header[:type]
|
319
|
+
when :conventional, :node_pointer
|
320
|
+
# The variable-length part of the record header contains a
|
321
|
+
# bit vector indicating NULL fields and the length of each
|
322
|
+
# non-NULL variable-length field.
|
323
|
+
if record_format
|
324
|
+
header[:field_nulls] = cursor.name("field_nulls") {
|
325
|
+
record_header_compact_null_bitmap(cursor)
|
326
|
+
}
|
327
|
+
header[:field_lengths], header[:field_externs] =
|
328
|
+
cursor.name("field_lengths_and_externs") {
|
329
|
+
record_header_compact_variable_lengths_and_externs(cursor,
|
330
|
+
header[:field_nulls])
|
331
|
+
}
|
250
332
|
end
|
251
|
-
header
|
252
|
-
when :redundant
|
253
|
-
raise "Not implemented"
|
254
333
|
end
|
255
334
|
end
|
256
335
|
|
257
336
|
# Return an array indicating which fields are null.
|
258
|
-
def
|
337
|
+
def record_header_compact_null_bitmap(cursor)
|
259
338
|
fields = (record_format[:key] + record_format[:row])
|
260
339
|
|
261
340
|
# The number of bits in the bitmap is the number of nullable fields.
|
262
|
-
size = fields.count
|
341
|
+
size = fields.count { |f| f.type.nullable? }
|
263
342
|
|
264
343
|
# There is no bitmap if there are no nullable fields.
|
265
344
|
return nil unless size > 0
|
266
345
|
|
267
346
|
# To simplify later checks, expand bitmap to one for each field.
|
268
|
-
bitmap = Array.new(fields.
|
347
|
+
bitmap = Array.new(fields.last.position + 1, false)
|
269
348
|
|
270
349
|
null_bit_array = cursor.get_bit_array(size).reverse!
|
271
350
|
|
272
351
|
# For every nullable field, set whether the field is actually null.
|
273
352
|
fields.each do |f|
|
274
|
-
bitmap[f.position] = f.nullable ? (null_bit_array.shift == 1) : false
|
353
|
+
bitmap[f.position] = f.type.nullable? ? (null_bit_array.shift == 1) : false
|
275
354
|
end
|
276
355
|
|
277
356
|
return bitmap
|
278
357
|
end
|
279
358
|
|
280
|
-
# Return an array containing the length of each variable-length
|
281
|
-
|
359
|
+
# Return an array containing an array of the length of each variable-length
|
360
|
+
# field and an array indicating which fields are stored externally.
|
361
|
+
def record_header_compact_variable_lengths_and_externs(cursor, null_bitmap)
|
282
362
|
fields = (record_format[:key] + record_format[:row])
|
283
363
|
|
284
|
-
len_array = Array.new(fields.
|
364
|
+
len_array = Array.new(fields.last.position + 1, 0)
|
365
|
+
ext_array = Array.new(fields.last.position + 1, false)
|
285
366
|
|
286
367
|
# For each non-NULL variable-length field, the record header contains
|
287
368
|
# the length in one or two bytes.
|
288
369
|
fields.each do |f|
|
289
|
-
next if f.
|
370
|
+
next if !f.type.variable? or (null_bitmap && null_bitmap[f.position])
|
290
371
|
|
291
372
|
len = cursor.get_uint8
|
373
|
+
ext = false
|
292
374
|
|
293
375
|
# Two bytes are used only if the length exceeds 127 bytes and the
|
294
|
-
# maximum length exceeds 255 bytes.
|
295
|
-
if len > 127
|
376
|
+
# maximum length exceeds 255 bytes (or the field is a BLOB type).
|
377
|
+
if len > 127 && (f.type.blob? || f.type.length > 255)
|
378
|
+
ext = (0x40 & len) != 0
|
296
379
|
len = ((len & 0x3f) << 8) + cursor.get_uint8
|
297
380
|
end
|
298
381
|
|
299
382
|
len_array[f.position] = len
|
383
|
+
ext_array[f.position] = ext
|
300
384
|
end
|
301
385
|
|
302
|
-
return len_array
|
386
|
+
return len_array, ext_array
|
387
|
+
end
|
388
|
+
|
389
|
+
# Read additional header information from a redundant format record header.
|
390
|
+
def record_header_redundant_additional(header, cursor)
|
391
|
+
header[:field_lengths] = []
|
392
|
+
header[:field_nulls] = []
|
393
|
+
header[:field_externs] = []
|
394
|
+
|
395
|
+
field_offsets = record_header_redundant_field_end_offsets(header, cursor)
|
396
|
+
|
397
|
+
this_field_offset = 0
|
398
|
+
field_offsets.each do |n|
|
399
|
+
case header[:offset_size]
|
400
|
+
when 1
|
401
|
+
next_field_offset = (n & RECORD_REDUNDANT_OFF1_OFFSET_MASK)
|
402
|
+
header[:field_lengths] << (next_field_offset - this_field_offset)
|
403
|
+
header[:field_nulls] << ((n & RECORD_REDUNDANT_OFF1_NULL_MASK) != 0)
|
404
|
+
header[:field_externs] << false
|
405
|
+
when 2
|
406
|
+
next_field_offset = (n & RECORD_REDUNDANT_OFF2_OFFSET_MASK)
|
407
|
+
header[:field_lengths] << (next_field_offset - this_field_offset)
|
408
|
+
header[:field_nulls] << ((n & RECORD_REDUNDANT_OFF2_NULL_MASK) != 0)
|
409
|
+
header[:field_externs] << ((n & RECORD_REDUNDANT_OFF2_EXTERN_MASK) != 0)
|
410
|
+
end
|
411
|
+
this_field_offset = next_field_offset
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
# Read field end offsets from the provided cursor for each field as counted
|
416
|
+
# by n_fields.
|
417
|
+
def record_header_redundant_field_end_offsets(header, cursor)
|
418
|
+
(0...header[:n_fields]).to_a.inject([]) do |offsets, n|
|
419
|
+
cursor.name("field_end_offset[#{n}]") {
|
420
|
+
offsets << cursor.get_uint_by_size(header[:offset_size])
|
421
|
+
}
|
422
|
+
offsets
|
423
|
+
end
|
303
424
|
end
|
304
425
|
|
305
426
|
# Parse and return simple fixed-format system records, such as InnoDB's
|
306
427
|
# internal infimum and supremum records.
|
307
428
|
def system_record(offset)
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
429
|
+
cursor(offset).name("record[#{offset}]") do |c|
|
430
|
+
header = c.peek { record_header(c) }
|
431
|
+
{
|
432
|
+
:offset => offset,
|
433
|
+
:header => header,
|
434
|
+
:next => header[:next],
|
435
|
+
:data => c.name("data") { c.get_bytes(size_mum_record) },
|
436
|
+
}
|
437
|
+
end
|
315
438
|
end
|
316
439
|
|
317
440
|
# Return the infimum record on a page.
|
@@ -328,18 +451,23 @@ class Innodb::Page::Index < Innodb::Page
|
|
328
451
|
def make_record_description
|
329
452
|
description = record_describer.cursor_sendable_description(self)
|
330
453
|
|
331
|
-
|
454
|
+
position = 0
|
455
|
+
fields = {:type => description[:type], :key => [], :row => []}
|
332
456
|
|
333
|
-
|
334
|
-
fields << Innodb::Field.new(
|
457
|
+
description[:key].each do |d|
|
458
|
+
fields[:key] << Innodb::Field.new(position, *d)
|
459
|
+
position += 1
|
335
460
|
end
|
336
461
|
|
337
|
-
|
462
|
+
# Account for TRX_ID and ROLL_PTR.
|
463
|
+
position += 2
|
338
464
|
|
339
|
-
description[:
|
340
|
-
|
465
|
+
description[:row].each do |d|
|
466
|
+
fields[:row] << Innodb::Field.new(position, *d)
|
467
|
+
position += 1
|
468
|
+
end
|
341
469
|
|
342
|
-
|
470
|
+
fields
|
343
471
|
end
|
344
472
|
|
345
473
|
# Return (and cache) the record format provided by an external class.
|
@@ -355,62 +483,75 @@ class Innodb::Page::Index < Innodb::Page
|
|
355
483
|
return infimum if offset == pos_infimum
|
356
484
|
return supremum if offset == pos_supremum
|
357
485
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
486
|
+
cursor(offset).forward.name("record[#{offset}]") do |c|
|
487
|
+
# There is a header preceding the row itself, so back up and read it.
|
488
|
+
header = c.peek { record_header(c) }
|
489
|
+
|
490
|
+
this_record = {
|
491
|
+
:format => page_header[:format],
|
492
|
+
:offset => offset,
|
493
|
+
:header => header,
|
494
|
+
:next => header[:next] == 0 ? nil : (header[:next]),
|
495
|
+
}
|
496
|
+
|
497
|
+
if record_format
|
498
|
+
this_record[:type] = record_format[:type]
|
499
|
+
|
500
|
+
# Read the key fields present in all types of pages.
|
501
|
+
this_record[:key] = []
|
502
|
+
this_record[:key_ext] = []
|
503
|
+
c.name("key") do
|
504
|
+
record_format[:key].each do |f|
|
505
|
+
this_record[:key].push f.read(this_record, c)
|
506
|
+
this_record[:key_ext].push f.read_extern(this_record, c)
|
507
|
+
end
|
508
|
+
end
|
372
509
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
510
|
+
# If this is a leaf page of the clustered index, read InnoDB's internal
|
511
|
+
# fields, a transaction ID and roll pointer.
|
512
|
+
if level == 0 && record_format[:type] == :clustered
|
513
|
+
this_record[:transaction_id] = c.name("transaction_id") { c.get_hex(6) }
|
514
|
+
c.name("roll_pointer") do
|
515
|
+
rseg_id_insert_flag = c.name("rseg_id_insert_flag") { c.get_uint8 }
|
516
|
+
this_record[:roll_pointer] = {
|
517
|
+
:is_insert => (rseg_id_insert_flag & 0x80) == 0x80,
|
518
|
+
:rseg_id => rseg_id_insert_flag & 0x7f,
|
519
|
+
:undo_log => c.name("undo_log") {
|
520
|
+
{
|
521
|
+
:page => c.name("page") { c.get_uint32 },
|
522
|
+
:offset => c.name("offset") { c.get_uint16 },
|
523
|
+
}
|
524
|
+
}
|
525
|
+
}
|
526
|
+
end
|
527
|
+
end
|
378
528
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
:
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
529
|
+
# If this is a leaf page of the clustered index, or any page of a
|
530
|
+
# secondary index, read the non-key fields.
|
531
|
+
if (level == 0 && record_format[:type] == :clustered) ||
|
532
|
+
(record_format[:type] == :secondary)
|
533
|
+
# Read the non-key fields.
|
534
|
+
this_record[:row] = []
|
535
|
+
this_record[:row_ext] = []
|
536
|
+
c.name("row") do
|
537
|
+
record_format[:row].each do |f|
|
538
|
+
this_record[:row].push f.read(this_record, c)
|
539
|
+
this_record[:row_ext].push f.read_extern(this_record, c)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
393
543
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
record_format[:row].each do |f|
|
401
|
-
this_record[:row].push f.read(this_record, c)
|
544
|
+
# If this is a node (non-leaf) page, it will have a child page number
|
545
|
+
# (or "node pointer") stored as the last field.
|
546
|
+
if level > 0
|
547
|
+
# Read the node pointer in a node (non-leaf) page.
|
548
|
+
this_record[:child_page_number] =
|
549
|
+
c.name("child_page_number") { c.get_uint32 }
|
402
550
|
end
|
403
551
|
end
|
404
552
|
|
405
|
-
|
406
|
-
# (or "node pointer") stored as the last field.
|
407
|
-
if level > 0
|
408
|
-
# Read the node pointer in a node (non-leaf) page.
|
409
|
-
this_record[:child_page_number] = c.get_uint32
|
410
|
-
end
|
553
|
+
this_record
|
411
554
|
end
|
412
|
-
|
413
|
-
this_record
|
414
555
|
end
|
415
556
|
|
416
557
|
# A class for cursoring through records starting from an arbitrary point.
|
@@ -428,7 +569,13 @@ class Innodb::Page::Index < Innodb::Page
|
|
428
569
|
record = @page.record(@offset)
|
429
570
|
|
430
571
|
if record == @page.supremum
|
572
|
+
# We've reached the end of the linked list at supremum.
|
573
|
+
@offset = nil
|
574
|
+
elsif record[:next] == @offset
|
575
|
+
# The record links to itself; go ahead and return it (once), but set
|
576
|
+
# the next offset to nil to end the loop.
|
431
577
|
@offset = nil
|
578
|
+
record
|
432
579
|
else
|
433
580
|
@offset = record[:next]
|
434
581
|
record
|
@@ -462,6 +609,24 @@ class Innodb::Page::Index < Innodb::Page
|
|
462
609
|
nil
|
463
610
|
end
|
464
611
|
|
612
|
+
def each_garbage_record
|
613
|
+
unless block_given?
|
614
|
+
return enum_for(:each_garbage_record)
|
615
|
+
end
|
616
|
+
|
617
|
+
if garbage_offset == 0
|
618
|
+
return nil
|
619
|
+
end
|
620
|
+
|
621
|
+
c = record_cursor(garbage_offset)
|
622
|
+
|
623
|
+
while rec = c.record
|
624
|
+
yield rec
|
625
|
+
end
|
626
|
+
|
627
|
+
nil
|
628
|
+
end
|
629
|
+
|
465
630
|
# Iterate through all child pages of a node (non-leaf) page, which are
|
466
631
|
# stored as records with the child page number as the last field in the
|
467
632
|
# record.
|
@@ -484,9 +649,10 @@ class Innodb::Page::Index < Innodb::Page
|
|
484
649
|
return @directory if @directory
|
485
650
|
|
486
651
|
@directory = []
|
487
|
-
|
488
|
-
|
489
|
-
|
652
|
+
cursor(pos_directory).backward.name("page_directory") do |c|
|
653
|
+
directory_slots.times do |n|
|
654
|
+
@directory.push c.name("slot[#{n}]") { c.get_uint16 }
|
655
|
+
end
|
490
656
|
end
|
491
657
|
|
492
658
|
@directory
|
@@ -517,18 +683,26 @@ class Innodb::Page::Index < Innodb::Page
|
|
517
683
|
]
|
518
684
|
puts
|
519
685
|
|
686
|
+
puts "page directory:"
|
687
|
+
pp directory
|
688
|
+
puts
|
689
|
+
|
520
690
|
puts "system records:"
|
521
691
|
pp infimum
|
522
692
|
pp supremum
|
523
693
|
puts
|
524
694
|
|
525
|
-
puts "
|
526
|
-
|
695
|
+
puts "garbage records:"
|
696
|
+
each_garbage_record do |rec|
|
697
|
+
pp rec
|
698
|
+
puts
|
699
|
+
end
|
527
700
|
puts
|
528
701
|
|
529
702
|
puts "records:"
|
530
703
|
each_record do |rec|
|
531
704
|
pp rec
|
705
|
+
puts
|
532
706
|
end
|
533
707
|
puts
|
534
708
|
end
|