innodb_ruby 0.6.6 → 0.7.1
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/README.md +12 -0
- data/bin/innodb_space +157 -1
- data/lib/innodb.rb +2 -0
- data/lib/innodb/cursor.rb +93 -31
- data/lib/innodb/field.rb +66 -0
- data/lib/innodb/fseg_entry.rb +13 -1
- data/lib/innodb/index.rb +106 -8
- data/lib/innodb/list.rb +109 -0
- data/lib/innodb/page.rb +26 -4
- data/lib/innodb/page/fsp_hdr_xdes.rb +35 -29
- data/lib/innodb/page/index.rb +103 -7
- data/lib/innodb/page/inode.rb +46 -9
- data/lib/innodb/page/trx_sys.rb +36 -10
- data/lib/innodb/space.rb +6 -1
- data/lib/innodb/version.rb +1 -1
- data/lib/innodb/xdes.rb +39 -0
- metadata +26 -8
- data/lib/innodb/free_list.rb +0 -27
data/lib/innodb/fseg_entry.rb
CHANGED
@@ -1,11 +1,23 @@
|
|
1
|
+
# An InnoDB file segment entry, which appears in a few places, such as the
|
2
|
+
# FSEG header of INDEX pages, and in the TRX_SYS pages.
|
1
3
|
class Innodb::FsegEntry
|
2
4
|
SIZE = 4 + 4 + 2
|
3
5
|
|
4
|
-
|
6
|
+
# Return the FSEG entry address, which points to an entry on an INODE page.
|
7
|
+
def self.get_entry_address(cursor)
|
5
8
|
{
|
6
9
|
:space_id => cursor.get_uint32,
|
7
10
|
:page_number => cursor.get_uint32,
|
8
11
|
:offset => cursor.get_uint16,
|
9
12
|
}
|
10
13
|
end
|
14
|
+
|
15
|
+
# Return an INODE entry which represents this file segment.
|
16
|
+
def self.get_inode(space, cursor)
|
17
|
+
address = get_entry_address(cursor)
|
18
|
+
page = space.page(address[:page_number])
|
19
|
+
if page.type == :INODE
|
20
|
+
page.inode_at(address[:offset])
|
21
|
+
end
|
22
|
+
end
|
11
23
|
end
|
data/lib/innodb/index.rb
CHANGED
@@ -102,12 +102,15 @@ class Innodb::Index
|
|
102
102
|
# -1 = a is less than b
|
103
103
|
# +1 = a is greater than b
|
104
104
|
def compare_key(a, b)
|
105
|
-
return
|
106
|
-
return
|
105
|
+
return 0 if a.nil? && b.nil?
|
106
|
+
return -1 if a.nil? || (!b.nil? && a.size < b.size)
|
107
|
+
return +1 if b.nil? || (!a.nil? && a.size > b.size)
|
108
|
+
|
107
109
|
a.each_index do |i|
|
108
110
|
return -1 if a[i] < b[i]
|
109
111
|
return +1 if a[i] > b[i]
|
110
112
|
end
|
113
|
+
|
111
114
|
return 0
|
112
115
|
end
|
113
116
|
|
@@ -116,20 +119,87 @@ class Innodb::Index
|
|
116
119
|
# than the key. (If an exact match is desired, compare_key must be used to
|
117
120
|
# check if the returned record matches. This makes the function useful for
|
118
121
|
# search in both leaf and non-leaf pages.)
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
122
|
+
def linear_search_from_cursor(cursor, key)
|
123
|
+
this_rec = cursor.record
|
124
|
+
|
125
|
+
# Iterate through all records until finding either a matching record or
|
126
|
+
# one whose key is greater than the desired key.
|
127
|
+
while this_rec && next_rec = cursor.record
|
128
|
+
# If we reach supremum, return the last non-system record we got.
|
129
|
+
return this_rec if next_rec[:header][:type] == :supremum
|
130
|
+
|
124
131
|
if (compare_key(key, this_rec[:key]) >= 0) &&
|
125
132
|
(compare_key(key, next_rec[:key]) < 0)
|
133
|
+
# The desired key is either an exact match for this_rec or is greater
|
134
|
+
# than it but less than next_rec. If this is a non-leaf page, that
|
135
|
+
# will mean that the record will fall on the leaf page this node
|
136
|
+
# pointer record points to, if it exists at all.
|
126
137
|
return this_rec
|
127
138
|
end
|
139
|
+
|
128
140
|
this_rec = next_rec
|
129
141
|
end
|
142
|
+
|
130
143
|
this_rec
|
131
144
|
end
|
132
145
|
|
146
|
+
# Search or a record within a single page using the page directory to limit
|
147
|
+
# the number of record comparisons required. Once the last page directory
|
148
|
+
# entry closest to but not greater than the key is found, fall back to
|
149
|
+
# linear search using linear_search_from_cursor to find the closest record
|
150
|
+
# whose key is not greater than the desired key. (If an exact match is
|
151
|
+
# desired, the returned record must be checked in the same way as the above
|
152
|
+
# linear_search_from_cursor function.)
|
153
|
+
def binary_search_by_directory(page, dir, key)
|
154
|
+
return nil if dir.empty?
|
155
|
+
|
156
|
+
# Split the directory at the mid-point (using integer math, so the division
|
157
|
+
# is rounding down). Retrieve the record that sits at the mid-point.
|
158
|
+
mid = dir.size / 2
|
159
|
+
rec = page.record(dir[mid])
|
160
|
+
|
161
|
+
# The mid-point record was the infimum record, which is not comparable with
|
162
|
+
# compare_key, so we need to just linear scan from here. If the mid-point
|
163
|
+
# is the beginning of the page there can't be many records left to check
|
164
|
+
# anyway.
|
165
|
+
if rec[:header][:type] == :infimum
|
166
|
+
return linear_search_from_cursor(page.record_cursor(rec[:next]), key)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Compare the desired key to the mid-point record's key.
|
170
|
+
case compare_key(key, rec[:key])
|
171
|
+
when 0
|
172
|
+
# An exact match for the key was found. Return the record.
|
173
|
+
rec
|
174
|
+
when +1
|
175
|
+
# The mid-point record's key is less than the desired key.
|
176
|
+
if dir.size == 1
|
177
|
+
# This is the last entry remaining from the directory, use linear
|
178
|
+
# search to find the record. We already know that there wasn't an
|
179
|
+
# exact match, so skip the current record and start cursoring from
|
180
|
+
# the next record.
|
181
|
+
linear_search_from_cursor(page.record_cursor(rec[:next]), key)
|
182
|
+
else
|
183
|
+
# There are more entries remaining from the directory, recurse again
|
184
|
+
# using binary search on the right half of the directory, which
|
185
|
+
# represents values greater than or equal to the mid-point record's
|
186
|
+
# key.
|
187
|
+
binary_search_by_directory(page, dir[mid...dir.size], key)
|
188
|
+
end
|
189
|
+
when -1
|
190
|
+
# The mid-point record's key is greater than the desired key.
|
191
|
+
if dir.size == 1
|
192
|
+
# If this is the last entry remaining from the directory, we didn't
|
193
|
+
# find anything workable.
|
194
|
+
nil
|
195
|
+
else
|
196
|
+
# Recurse on the left half of the directory, which represents values
|
197
|
+
# less than the mid-point record's key.
|
198
|
+
binary_search_by_directory(page, dir[0...mid], key)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
133
203
|
# Search for a record within the entire index, walking down the non-leaf
|
134
204
|
# pages until a leaf page is found, and then verifying that the record
|
135
205
|
# returned on the leaf page is an exact match for the key. If a matching
|
@@ -138,13 +208,41 @@ class Innodb::Index
|
|
138
208
|
def linear_search(key)
|
139
209
|
page = @root
|
140
210
|
|
141
|
-
while rec =
|
211
|
+
while rec =
|
212
|
+
linear_search_from_cursor(page.record_cursor(page.infimum[:next]), key)
|
142
213
|
if page.level > 0
|
214
|
+
# If we haven't reached a leaf page yet, move down the tree and search
|
215
|
+
# again using linear search.
|
143
216
|
page = @space.page(rec[:child_page_number])
|
144
217
|
else
|
218
|
+
# We're on a leaf page, so return the page and record if there is a
|
219
|
+
# match. If there is no match, break the loop and cause nil to be
|
220
|
+
# returned.
|
145
221
|
return page, rec if compare_key(key, rec[:key]) == 0
|
146
222
|
break
|
147
223
|
end
|
148
224
|
end
|
149
225
|
end
|
226
|
+
|
227
|
+
# Search for a record within the entire index like linear_search, but use
|
228
|
+
# the page directory to search while making as few record comparisons as
|
229
|
+
# possible. If a matching record is not found, nil is returned.
|
230
|
+
def binary_search(key)
|
231
|
+
page = @root
|
232
|
+
|
233
|
+
while rec = binary_search_by_directory(page, page.directory.dup, key)
|
234
|
+
if page.level > 0
|
235
|
+
# If we haven't reached a leaf page yet, move down the tree and search
|
236
|
+
# again using binary search.
|
237
|
+
page = @space.page(rec[:child_page_number])
|
238
|
+
else
|
239
|
+
# We're on a leaf page, so return the page and record if there is a
|
240
|
+
# match. If there is no match, break the loop and cause nil to be
|
241
|
+
# returned.
|
242
|
+
return page, rec if compare_key(key, rec[:key]) == 0
|
243
|
+
break
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
150
248
|
end
|
data/lib/innodb/list.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
class Innodb::List
|
2
|
+
FIL_ADDR_SIZE = 4 + 2
|
3
|
+
NODE_SIZE = 2 * FIL_ADDR_SIZE
|
4
|
+
BASE_NODE_SIZE = 4 + (2 * FIL_ADDR_SIZE)
|
5
|
+
|
6
|
+
def self.get_address(cursor)
|
7
|
+
page = Innodb::Page.maybe_undefined(cursor.get_uint32)
|
8
|
+
offset = cursor.get_uint16
|
9
|
+
if page
|
10
|
+
{
|
11
|
+
:page => page,
|
12
|
+
:offset => offset,
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.get_node(cursor)
|
18
|
+
{
|
19
|
+
:prev => get_address(cursor),
|
20
|
+
:next => get_address(cursor),
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.get_base_node(cursor)
|
25
|
+
{
|
26
|
+
:length => cursor.get_uint32,
|
27
|
+
:first => get_address(cursor),
|
28
|
+
:last => get_address(cursor),
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(space, base)
|
33
|
+
@space = space
|
34
|
+
@base = base
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :space
|
38
|
+
attr_reader :base
|
39
|
+
|
40
|
+
def prev(object)
|
41
|
+
object_from_address(object.prev_address)
|
42
|
+
end
|
43
|
+
|
44
|
+
def next(object)
|
45
|
+
object_from_address(object.next_address)
|
46
|
+
end
|
47
|
+
|
48
|
+
def first
|
49
|
+
object_from_address(@base[:first])
|
50
|
+
end
|
51
|
+
|
52
|
+
def last
|
53
|
+
object_from_address(@base[:last])
|
54
|
+
end
|
55
|
+
|
56
|
+
def cursor(node=nil)
|
57
|
+
Cursor.new(self, node)
|
58
|
+
end
|
59
|
+
|
60
|
+
def each
|
61
|
+
c = cursor
|
62
|
+
while e = c.next
|
63
|
+
yield e
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Cursor
|
68
|
+
def initialize(list, node=nil)
|
69
|
+
@list = list
|
70
|
+
@cursor = node
|
71
|
+
end
|
72
|
+
|
73
|
+
def reset
|
74
|
+
@cursor = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def prev
|
78
|
+
if @cursor
|
79
|
+
@cursor = @list.prev(@cursor)
|
80
|
+
else
|
81
|
+
@cursor = @list.last
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def next
|
86
|
+
if @cursor
|
87
|
+
@cursor = @list.next(@cursor)
|
88
|
+
else
|
89
|
+
@cursor = @list.first
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Innodb::List::Xdes < Innodb::List
|
96
|
+
def object_from_address(address)
|
97
|
+
if address && page = @space.page(address[:page])
|
98
|
+
Innodb::Xdes.new(page, page.cursor(address[:offset] - 8))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Innodb::List::Inode < Innodb::List
|
104
|
+
def object_from_address(address)
|
105
|
+
if address && page = @space.page(address[:page])
|
106
|
+
page
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/innodb/page.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
require "innodb/cursor"
|
2
2
|
|
3
|
+
# A generic class for any type of page, which handles reading the common
|
4
|
+
# FIL header and trailer, and can handle (via #parse) dispatching to a more
|
5
|
+
# specialized class depending on page type (which comes from the FIL header).
|
6
|
+
# A page being handled by Innodb::Page indicates that its type is not currently
|
7
|
+
# handled by any more specialized class.
|
3
8
|
class Innodb::Page
|
4
9
|
SPECIALIZED_CLASSES = {}
|
5
10
|
|
@@ -7,11 +12,11 @@ class Innodb::Page
|
|
7
12
|
# and then attempt to hand off the page to a specialized class to be
|
8
13
|
# re-parsed if possible. If there is no specialized class for this type
|
9
14
|
# of page, return the generic object.
|
10
|
-
def self.parse(buffer)
|
11
|
-
page = Innodb::Page.new(buffer)
|
15
|
+
def self.parse(space, buffer)
|
16
|
+
page = Innodb::Page.new(space, buffer)
|
12
17
|
|
13
18
|
if specialized_class = SPECIALIZED_CLASSES[page.type]
|
14
|
-
page = specialized_class.new(buffer)
|
19
|
+
page = specialized_class.new(space, buffer)
|
15
20
|
end
|
16
21
|
|
17
22
|
page
|
@@ -19,7 +24,8 @@ class Innodb::Page
|
|
19
24
|
|
20
25
|
# Initialize a page by passing in a 16kB buffer containing the raw page
|
21
26
|
# contents. Currently only 16kB pages are supported.
|
22
|
-
def initialize(buffer)
|
27
|
+
def initialize(space, buffer)
|
28
|
+
@space = space
|
23
29
|
@buffer = buffer
|
24
30
|
end
|
25
31
|
|
@@ -130,6 +136,22 @@ class Innodb::Page
|
|
130
136
|
fil_header[:lsn]
|
131
137
|
end
|
132
138
|
|
139
|
+
def inspect
|
140
|
+
if fil_header
|
141
|
+
"#<%s: size=%i, space_id=%i, offset=%i, type=%s, prev=%i, next=%i>" % [
|
142
|
+
self.class,
|
143
|
+
size,
|
144
|
+
fil_header[:space_id],
|
145
|
+
fil_header[:offset],
|
146
|
+
fil_header[:type],
|
147
|
+
fil_header[:prev],
|
148
|
+
fil_header[:next],
|
149
|
+
]
|
150
|
+
else
|
151
|
+
"#<#{self.class}>"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
133
155
|
# Dump the contents of a page for debugging purposes.
|
134
156
|
def dump
|
135
157
|
puts "#{self}:"
|
@@ -1,24 +1,38 @@
|
|
1
|
-
require "innodb/
|
1
|
+
require "innodb/list"
|
2
|
+
require "innodb/xdes"
|
2
3
|
|
4
|
+
# A specialized class for FSP_HDR (filespace header) and XDES (extent
|
5
|
+
# descriptor) page types. Each tablespace always has an FSP_HDR page as
|
6
|
+
# its first page (page 0), and has repeating XDES pages every 16,384 pages
|
7
|
+
# after that (page 16384, 32768, ...). The FSP_HDR and XDES page structure
|
8
|
+
# is completely identical, with the exception that the FSP header structure
|
9
|
+
# is zero-filled on XDES pages, but populated on FSP_HDR pages.
|
10
|
+
#
|
11
|
+
# The basic structure of FSP_HDR and XDES pages is: FIL header, FSP header,
|
12
|
+
# an array of 256 XDES entries, empty (unused) space, and FIL trailer.
|
3
13
|
class Innodb::Page::FspHdrXdes < Innodb::Page
|
4
|
-
|
5
|
-
|
6
|
-
XDES_SIZE = 8 + Innodb::FreeList::NODE_SIZE + 4 + XDES_BITMAP_SIZE
|
7
|
-
|
14
|
+
# This is actually defined as page size divided by extent size, which is
|
15
|
+
# 16384 / 64 = 256.
|
8
16
|
XDES_N_ARRAY_ENTRIES = 256
|
9
17
|
|
18
|
+
# The FSP header immediately follows the FIL header.
|
10
19
|
def pos_fsp_header
|
11
20
|
pos_fil_header + size_fil_header
|
12
21
|
end
|
13
22
|
|
23
|
+
# The FSP header contains six 32-bit integers, one 64-bit integer, and 5
|
24
|
+
# list base nodes.
|
14
25
|
def size_fsp_header
|
15
|
-
(
|
26
|
+
((4 * 6) + (1 * 8) + (5 * Innodb::List::BASE_NODE_SIZE))
|
16
27
|
end
|
17
28
|
|
29
|
+
# The XDES entry array immediately follows the FSP header.
|
18
30
|
def pos_xdes_array
|
19
31
|
pos_fsp_header + size_fsp_header
|
20
32
|
end
|
21
33
|
|
34
|
+
# Read the FSP (filespace) header, which contains a few counters and flags,
|
35
|
+
# as well as list base nodes for each list maintained in the filespace.
|
22
36
|
def fsp_header
|
23
37
|
c = cursor(pos_fsp_header)
|
24
38
|
@fsp_header ||= {
|
@@ -28,36 +42,28 @@ class Innodb::Page::FspHdrXdes < Innodb::Page
|
|
28
42
|
:free_limit => c.get_uint32,
|
29
43
|
:flags => c.get_uint32,
|
30
44
|
:frag_n_used => c.get_uint32,
|
31
|
-
:free => Innodb::
|
32
|
-
|
33
|
-
:
|
45
|
+
:free => Innodb::List::Xdes.new(@space,
|
46
|
+
Innodb::List.get_base_node(c)),
|
47
|
+
:free_frag => Innodb::List::Xdes.new(@space,
|
48
|
+
Innodb::List.get_base_node(c)),
|
49
|
+
:full_frag => Innodb::List::Xdes.new(@space,
|
50
|
+
Innodb::List.get_base_node(c)),
|
34
51
|
:first_unused_seg => c.get_uint64,
|
35
|
-
:full_inodes => Innodb::
|
36
|
-
|
52
|
+
:full_inodes => Innodb::List::Inode.new(@space,
|
53
|
+
Innodb::List.get_base_node(c)),
|
54
|
+
:free_inodes => Innodb::List::Inode.new(@space,
|
55
|
+
Innodb::List.get_base_node(c)),
|
37
56
|
}
|
38
57
|
end
|
39
58
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
4 => :fseg,
|
45
|
-
}
|
46
|
-
|
47
|
-
def read_xdes(cursor)
|
48
|
-
{
|
49
|
-
:xdes_id => cursor.get_uint64,
|
50
|
-
:position => cursor.position,
|
51
|
-
:free_list => Innodb::FreeList::get_node(cursor),
|
52
|
-
:state => XDES_STATES[cursor.get_uint32],
|
53
|
-
:bitmap => cursor.get_hex(XDES_BITMAP_SIZE),
|
54
|
-
}
|
55
|
-
end
|
56
|
-
|
59
|
+
# Iterate through all XDES entries in order. This is useful for debugging,
|
60
|
+
# but each of these entries is actually a node in some other list. The state
|
61
|
+
# field in the XDES entry indicates which type of list it is present in,
|
62
|
+
# although not necessarily which list (e.g. :fseg).
|
57
63
|
def each_xdes
|
58
64
|
c = cursor(pos_xdes_array)
|
59
65
|
XDES_N_ARRAY_ENTRIES.times do
|
60
|
-
yield
|
66
|
+
yield Innodb::Xdes.new(self, c)
|
61
67
|
end
|
62
68
|
end
|
63
69
|
|
data/lib/innodb/page/index.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
require "innodb/fseg_entry"
|
2
2
|
|
3
|
+
# A specialized class for handling INDEX pages, which contain a portion of
|
4
|
+
# the data from exactly one B+tree. These are typically the most common type
|
5
|
+
# of page in any database.
|
6
|
+
#
|
7
|
+
# The basic structure of an INDEX page is: FIL header, INDEX header, FSEG
|
8
|
+
# header, fixed-width system records (infimum and supremum), user records
|
9
|
+
# (the actual data) which grow ascending by offset, free space, the page
|
10
|
+
# directory which grows descending by offset, and the FIL trailer.
|
3
11
|
class Innodb::Page::Index < Innodb::Page
|
4
12
|
attr_accessor :record_describer
|
5
13
|
|
@@ -158,16 +166,26 @@ class Innodb::Page::Index < Innodb::Page
|
|
158
166
|
def fseg_header
|
159
167
|
c = cursor(pos_fseg_header)
|
160
168
|
@fseg_header ||= {
|
161
|
-
:
|
162
|
-
:
|
169
|
+
:leaf => Innodb::FsegEntry.get_inode(@space, c),
|
170
|
+
:internal => Innodb::FsegEntry.get_inode(@space, c),
|
163
171
|
}
|
164
172
|
end
|
165
173
|
|
174
|
+
# The size (in bytes) of the bit-packed fields in each record header.
|
166
175
|
RECORD_BITS_SIZE = 3
|
176
|
+
|
177
|
+
# The size (in bytes) of the "next" pointer in each record header.
|
167
178
|
RECORD_NEXT_SIZE = 2
|
168
179
|
|
180
|
+
# The size (in bytes) of the record pointers in each page directory slot.
|
169
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.
|
170
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.
|
171
189
|
PAGE_DIR_SLOT_MAX_N_OWNED = 8
|
172
190
|
|
173
191
|
# Return the size of the header for each record.
|
@@ -205,7 +223,7 @@ class Innodb::Page::Index < Innodb::Page
|
|
205
223
|
# This record has been marked as deleted.
|
206
224
|
RECORD_INFO_DELETED_FLAG = 2
|
207
225
|
|
208
|
-
# Return the header from a record.
|
226
|
+
# Return the header from a record.
|
209
227
|
def record_header(offset)
|
210
228
|
return nil unless type == :INDEX
|
211
229
|
|
@@ -222,12 +240,70 @@ class Innodb::Page::Index < Innodb::Page
|
|
222
240
|
info = (bits2 & 0xf0) >> 4
|
223
241
|
header[:min_rec] = (info & RECORD_INFO_MIN_REC_FLAG) != 0
|
224
242
|
header[:deleted] = (info & RECORD_INFO_DELETED_FLAG) != 0
|
243
|
+
case header[:type]
|
244
|
+
when :conventional, :node_pointer:
|
245
|
+
# The variable-length part of the record header contains a
|
246
|
+
# bit vector indicating NULL fields and the length of each
|
247
|
+
# non-NULL variable-length field.
|
248
|
+
if record_format
|
249
|
+
header[:null_bitmap] = nbmap = record_null_bitmap(c)
|
250
|
+
header[:variable_length] = record_variable_length(c, nbmap)
|
251
|
+
end
|
252
|
+
end
|
225
253
|
header
|
226
254
|
when :redundant
|
227
255
|
raise "Not implemented"
|
228
256
|
end
|
229
257
|
end
|
230
258
|
|
259
|
+
# Return an array indicating which fields are null.
|
260
|
+
def record_null_bitmap(cursor)
|
261
|
+
fields = (record_format[:key] + record_format[:row])
|
262
|
+
|
263
|
+
# The number of bits in the bitmap is the number of nullable fields.
|
264
|
+
size = fields.count do |f| f.nullable end
|
265
|
+
|
266
|
+
# There is no bitmap if there are no nullable fields.
|
267
|
+
return nil unless size > 0
|
268
|
+
|
269
|
+
# To simplify later checks, expand bitmap to one for each field.
|
270
|
+
bitmap = Array.new(fields.size, false)
|
271
|
+
|
272
|
+
null_bit_array = cursor.get_bit_array(size).reverse!
|
273
|
+
|
274
|
+
# For every nullable field, set whether the field is actually null.
|
275
|
+
fields.each do |f|
|
276
|
+
bitmap[f.position] = f.nullable ? (null_bit_array.shift == 1) : false
|
277
|
+
end
|
278
|
+
|
279
|
+
return bitmap
|
280
|
+
end
|
281
|
+
|
282
|
+
# Return an array containing the length of each variable-length field.
|
283
|
+
def record_variable_length(cursor, null_bitmap)
|
284
|
+
fields = (record_format[:key] + record_format[:row])
|
285
|
+
|
286
|
+
len_array = Array.new(fields.size, 0)
|
287
|
+
|
288
|
+
# For each non-NULL variable-length field, the record header contains
|
289
|
+
# the length in one or two bytes.
|
290
|
+
fields.each do |f|
|
291
|
+
next if f.fixed_len > 0 or null_bitmap[f.position]
|
292
|
+
|
293
|
+
len = cursor.get_uint8
|
294
|
+
|
295
|
+
# Two bytes are used only if the length exceeds 127 bytes and the
|
296
|
+
# maximum length exceeds 255 bytes.
|
297
|
+
if len > 127 and f.variable_len > 255
|
298
|
+
len = ((len & 0x3f) << 8) + cursor.get_uint8
|
299
|
+
end
|
300
|
+
|
301
|
+
len_array[f.position] = len
|
302
|
+
end
|
303
|
+
|
304
|
+
return len_array
|
305
|
+
end
|
306
|
+
|
231
307
|
# Parse and return simple fixed-format system records, such as InnoDB's
|
232
308
|
# internal infimum and supremum records.
|
233
309
|
def system_record(offset)
|
@@ -235,6 +311,7 @@ class Innodb::Page::Index < Innodb::Page
|
|
235
311
|
|
236
312
|
header = record_header(offset)
|
237
313
|
{
|
314
|
+
:offset => offset,
|
238
315
|
:header => header,
|
239
316
|
:next => offset + header[:next],
|
240
317
|
:data => cursor(offset).get_bytes(size_mum_record),
|
@@ -251,10 +328,28 @@ class Innodb::Page::Index < Innodb::Page
|
|
251
328
|
@supremum ||= system_record(pos_supremum)
|
252
329
|
end
|
253
330
|
|
331
|
+
# Return a set of field objects that describe the record.
|
332
|
+
def make_record_description
|
333
|
+
description = record_describer.cursor_sendable_description(self)
|
334
|
+
|
335
|
+
fields = []
|
336
|
+
|
337
|
+
(description[:key] + description[:row]).each_with_index do |d, p|
|
338
|
+
fields << Innodb::Field.new(p, *d)
|
339
|
+
end
|
340
|
+
|
341
|
+
n = description[:key].size
|
342
|
+
|
343
|
+
description[:key] = fields.slice(0 .. n-1)
|
344
|
+
description[:row] = fields.slice(n .. -1)
|
345
|
+
|
346
|
+
return description
|
347
|
+
end
|
348
|
+
|
254
349
|
# Return (and cache) the record format provided by an external class.
|
255
350
|
def record_format
|
256
351
|
if record_describer
|
257
|
-
@record_format ||=
|
352
|
+
@record_format ||= make_record_description()
|
258
353
|
end
|
259
354
|
end
|
260
355
|
|
@@ -271,6 +366,7 @@ class Innodb::Page::Index < Innodb::Page
|
|
271
366
|
header = record_header(offset)
|
272
367
|
|
273
368
|
this_record = {
|
369
|
+
:format => page_header[:format],
|
274
370
|
:offset => offset,
|
275
371
|
:header => header,
|
276
372
|
:next => header[:next] == 0 ? nil : (offset + header[:next]),
|
@@ -282,7 +378,7 @@ class Innodb::Page::Index < Innodb::Page
|
|
282
378
|
# Read the key fields present in all types of pages.
|
283
379
|
this_record[:key] = []
|
284
380
|
record_format[:key].each do |f|
|
285
|
-
this_record[:key].push
|
381
|
+
this_record[:key].push f.read(this_record, c)
|
286
382
|
end
|
287
383
|
|
288
384
|
# If this is a leaf page of the clustered index, read InnoDB's internal
|
@@ -299,7 +395,7 @@ class Innodb::Page::Index < Innodb::Page
|
|
299
395
|
# Read the non-key fields.
|
300
396
|
this_record[:row] = []
|
301
397
|
record_format[:row].each do |f|
|
302
|
-
this_record[:row].push
|
398
|
+
this_record[:row].push f.read(this_record, c)
|
303
399
|
end
|
304
400
|
end
|
305
401
|
|
@@ -427,4 +523,4 @@ class Innodb::Page::Index < Innodb::Page
|
|
427
523
|
end
|
428
524
|
end
|
429
525
|
|
430
|
-
Innodb::Page::SPECIALIZED_CLASSES[:INDEX] = Innodb::Page::Index
|
526
|
+
Innodb::Page::SPECIALIZED_CLASSES[:INDEX] = Innodb::Page::Index
|