innodb_ruby 0.7.11 → 0.7.12
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.
- 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
|