innodb_ruby 0.6.6 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|