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.
@@ -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
- def self.get_entry(cursor)
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
@@ -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 -1 if a.size < b.size
106
- return +1 if a.size > b.size
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 linear_search_in_page(page, key)
120
- c = page.record_cursor(page.infimum[:next])
121
- this_rec = c.record
122
- while next_rec = c.record
123
- return this_rec if next_rec == page.supremum
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 = linear_search_in_page(page, key)
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
@@ -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
@@ -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/free_list"
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
- XDES_BITS_PER_PAGE = 2
5
- XDES_BITMAP_SIZE = (64 * XDES_BITS_PER_PAGE) / 8
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
- (32 + 5 * Innodb::FreeList::BASE_NODE_SIZE)
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::FreeList::get_base_node(c),
32
- :free_frag => Innodb::FreeList::get_base_node(c),
33
- :full_frag => Innodb::FreeList::get_base_node(c),
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::FreeList::get_base_node(c),
36
- :free_inodes => Innodb::FreeList::get_base_node(c),
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
- XDES_STATES = {
41
- 1 => :free,
42
- 2 => :free_frag,
43
- 3 => :full_frag,
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 read_xdes(c)
66
+ yield Innodb::Xdes.new(self, c)
61
67
  end
62
68
  end
63
69
 
@@ -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
- :free_list => Innodb::FsegEntry.get_entry(c),
162
- :btree_segment => Innodb::FsegEntry.get_entry(c),
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. (This is mostly unimplemented.)
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 ||= record_describer.cursor_sendable_description(self)
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 c.send(*f)
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 c.send(*f)
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