innodb_ruby 0.9.16 → 0.11.0

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.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +5 -6
  3. data/bin/innodb_log +13 -18
  4. data/bin/innodb_space +377 -757
  5. data/lib/innodb.rb +4 -5
  6. data/lib/innodb/checksum.rb +26 -24
  7. data/lib/innodb/data_dictionary.rb +490 -550
  8. data/lib/innodb/data_type.rb +362 -326
  9. data/lib/innodb/field.rb +102 -89
  10. data/lib/innodb/fseg_entry.rb +22 -26
  11. data/lib/innodb/history.rb +21 -21
  12. data/lib/innodb/history_list.rb +72 -76
  13. data/lib/innodb/ibuf_bitmap.rb +36 -36
  14. data/lib/innodb/ibuf_index.rb +6 -2
  15. data/lib/innodb/index.rb +245 -276
  16. data/lib/innodb/inode.rb +154 -155
  17. data/lib/innodb/list.rb +191 -183
  18. data/lib/innodb/log.rb +139 -110
  19. data/lib/innodb/log_block.rb +100 -91
  20. data/lib/innodb/log_group.rb +53 -64
  21. data/lib/innodb/log_reader.rb +97 -96
  22. data/lib/innodb/log_record.rb +328 -279
  23. data/lib/innodb/lsn.rb +86 -81
  24. data/lib/innodb/page.rb +417 -414
  25. data/lib/innodb/page/blob.rb +82 -83
  26. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  27. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  28. data/lib/innodb/page/index.rb +964 -943
  29. data/lib/innodb/page/index_compressed.rb +34 -34
  30. data/lib/innodb/page/inode.rb +103 -112
  31. data/lib/innodb/page/sys.rb +13 -15
  32. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  33. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  34. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  35. data/lib/innodb/page/trx_sys.rb +204 -182
  36. data/lib/innodb/page/undo_log.rb +106 -92
  37. data/lib/innodb/record.rb +121 -160
  38. data/lib/innodb/record_describer.rb +66 -68
  39. data/lib/innodb/space.rb +380 -418
  40. data/lib/innodb/stats.rb +33 -35
  41. data/lib/innodb/system.rb +149 -171
  42. data/lib/innodb/undo_log.rb +129 -107
  43. data/lib/innodb/undo_record.rb +255 -247
  44. data/lib/innodb/util/buffer_cursor.rb +81 -79
  45. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  46. data/lib/innodb/version.rb +2 -2
  47. data/lib/innodb/xdes.rb +144 -142
  48. metadata +80 -11
@@ -1,47 +1,47 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
- class Innodb::Page::IbufBitmap < Innodb::Page
4
- extend ReadBitsAtOffset
3
+ module Innodb
4
+ class Page
5
+ class IbufBitmap < Page
6
+ extend ReadBitsAtOffset
5
7
 
6
- def pos_ibuf_bitmap
7
- pos_page_body
8
- end
8
+ specialization_for :IBUF_BITMAP
9
9
 
10
- def size_ibuf_bitmap
11
- (Innodb::IbufBitmap::BITS_PER_PAGE * space.pages_per_bookkeeping_page) / 8
12
- end
10
+ def pos_ibuf_bitmap
11
+ pos_page_body
12
+ end
13
13
 
14
- def ibuf_bitmap
15
- Innodb::IbufBitmap.new(self, cursor(pos_ibuf_bitmap))
16
- end
14
+ def size_ibuf_bitmap
15
+ (Innodb::IbufBitmap::BITS_PER_PAGE * space.pages_per_bookkeeping_page) / 8
16
+ end
17
17
 
18
- def each_region
19
- unless block_given?
20
- return enum_for(:each_region)
21
- end
18
+ def ibuf_bitmap
19
+ Innodb::IbufBitmap.new(self, cursor(pos_ibuf_bitmap))
20
+ end
22
21
 
23
- super do |region|
24
- yield region
25
- end
22
+ def each_region(&block)
23
+ return enum_for(:each_region) unless block_given?
26
24
 
27
- yield({
28
- :offset => pos_ibuf_bitmap,
29
- :length => size_ibuf_bitmap,
30
- :name => :ibuf_bitmap,
31
- :info => "Insert Buffer Bitmap",
32
- })
25
+ super(&block)
33
26
 
34
- nil
35
- end
27
+ yield Region.new(
28
+ offset: pos_ibuf_bitmap,
29
+ length: size_ibuf_bitmap,
30
+ name: :ibuf_bitmap,
31
+ info: "Insert Buffer Bitmap"
32
+ )
36
33
 
37
- def dump
38
- super
34
+ nil
35
+ end
39
36
 
40
- puts "ibuf bitmap:"
41
- ibuf_bitmap.each_page_status do |page_number, page_status|
42
- puts " Page %i: %s" % [page_number, page_status.inspect]
37
+ def dump
38
+ super
39
+
40
+ puts "ibuf bitmap:"
41
+ ibuf_bitmap.each_page_status do |page_number, page_status|
42
+ puts " Page %i: %s" % [page_number, page_status.inspect]
43
+ end
44
+ end
43
45
  end
44
46
  end
45
47
  end
46
-
47
- Innodb::Page::SPECIALIZED_CLASSES[:IBUF_BITMAP] = Innodb::Page::IbufBitmap
@@ -1,4 +1,6 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
2
4
 
3
5
  require "innodb/fseg_entry"
4
6
 
@@ -10,1085 +12,1104 @@ require "innodb/fseg_entry"
10
12
  # header, fixed-width system records (infimum and supremum), user records
11
13
  # (the actual data) which grow ascending by offset, free space, the page
12
14
  # directory which grows descending by offset, and the FIL trailer.
13
- class Innodb::Page::Index < Innodb::Page
14
- # The size (in bytes) of the "next" pointer in each record header.
15
- RECORD_NEXT_SIZE = 2
16
-
17
- # The size (in bytes) of the bit-packed fields in each record header for
18
- # "redundant" record format.
19
- RECORD_REDUNDANT_BITS_SIZE = 4
20
-
21
- # Masks for 1-byte record end-offsets within "redundant" records.
22
- RECORD_REDUNDANT_OFF1_OFFSET_MASK = 0x7f
23
- RECORD_REDUNDANT_OFF1_NULL_MASK = 0x80
24
-
25
- # Masks for 2-byte record end-offsets within "redundant" records.
26
- RECORD_REDUNDANT_OFF2_OFFSET_MASK = 0x3fff
27
- RECORD_REDUNDANT_OFF2_NULL_MASK = 0x8000
28
- RECORD_REDUNDANT_OFF2_EXTERN_MASK = 0x4000
29
-
30
- # The size (in bytes) of the bit-packed fields in each record header for
31
- # "compact" record format.
32
- RECORD_COMPACT_BITS_SIZE = 3
33
-
34
- # Maximum number of fields.
35
- RECORD_MAX_N_SYSTEM_FIELDS = 3
36
- RECORD_MAX_N_FIELDS = 1024 - 1
37
- RECORD_MAX_N_USER_FIELDS = RECORD_MAX_N_FIELDS - RECORD_MAX_N_SYSTEM_FIELDS * 2
38
-
39
- # Page direction values possible in the page_header's :direction field.
40
- PAGE_DIRECTION = {
41
- 1 => :left, # Inserts have been in descending order.
42
- 2 => :right, # Inserts have been in ascending order.
43
- 3 => :same_rec, # Unused by InnoDB.
44
- 4 => :same_page, # Unused by InnoDB.
45
- 5 => :no_direction, # Inserts have been in random order.
46
- }
47
-
48
- # Record types used in the :type field of the record header.
49
- RECORD_TYPES = {
50
- 0 => :conventional, # A normal user record in a leaf page.
51
- 1 => :node_pointer, # A node pointer in a non-leaf page.
52
- 2 => :infimum, # The system "infimum" record.
53
- 3 => :supremum, # The system "supremum" record.
54
- }
55
-
56
- # This record is the minimum record at this level of the B-tree.
57
- RECORD_INFO_MIN_REC_FLAG = 1
58
-
59
- # This record has been marked as deleted.
60
- RECORD_INFO_DELETED_FLAG = 2
61
-
62
- # The size (in bytes) of the record pointers in each page directory slot.
63
- PAGE_DIR_SLOT_SIZE = 2
64
-
65
- # The minimum number of records "owned" by each record with an entry in
66
- # the page directory.
67
- PAGE_DIR_SLOT_MIN_N_OWNED = 4
68
-
69
- # The maximum number of records "owned" by each record with an entry in
70
- # the page directory.
71
- PAGE_DIR_SLOT_MAX_N_OWNED = 8
72
-
73
- # Return the byte offset of the start of the "index" page header, which
74
- # immediately follows the "fil" header.
75
- def pos_index_header
76
- pos_page_body
77
- end
78
-
79
- # The size of the "index" header.
80
- def size_index_header
81
- 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 8 + 2 + 8
82
- end
83
-
84
- # Return the byte offset of the start of the "fseg" header, which immediately
85
- # follows the "index" header.
86
- def pos_fseg_header
87
- pos_index_header + size_index_header
88
- end
15
+ module Innodb
16
+ class Page
17
+ class Index < Page
18
+ extend Forwardable
19
+
20
+ specialization_for :INDEX
21
+
22
+ RecordHeader = Struct.new(
23
+ :length, # rubocop:disable Lint/StructNewOverride
24
+ :next,
25
+ :type,
26
+ :heap_number,
27
+ :n_owned,
28
+ :info_flags,
29
+ :offset_size,
30
+ :n_fields,
31
+ :nulls,
32
+ :lengths,
33
+ :externs,
34
+ keyword_init: true
35
+ )
36
+
37
+ class RecordHeader
38
+ # This record is the minimum record at this level of the B-tree.
39
+ RECORD_INFO_MIN_REC_FLAG = 1
40
+
41
+ # This record has been marked as deleted.
42
+ RECORD_INFO_DELETED_FLAG = 2
43
+
44
+ def min_rec?
45
+ (info_flags & RECORD_INFO_MIN_REC_FLAG) != 0
46
+ end
89
47
 
90
- # The size of the "fseg" header.
91
- def size_fseg_header
92
- 2 * Innodb::FsegEntry::SIZE
93
- end
48
+ def deleted?
49
+ (info_flags & RECORD_INFO_DELETED_FLAG) != 0
50
+ end
51
+ end
94
52
 
95
- # Return the size of the header for each record.
96
- def size_record_header
97
- case page_header[:format]
98
- when :compact
99
- RECORD_NEXT_SIZE + RECORD_COMPACT_BITS_SIZE
100
- when :redundant
101
- RECORD_NEXT_SIZE + RECORD_REDUNDANT_BITS_SIZE
102
- end
103
- end
53
+ SystemRecord = Struct.new(
54
+ :offset,
55
+ :header,
56
+ :next,
57
+ :data,
58
+ :length, # rubocop:disable Lint/StructNewOverride
59
+ keyword_init: true
60
+ )
61
+
62
+ UserRecord = Struct.new(
63
+ :type,
64
+ :format,
65
+ :offset,
66
+ :header,
67
+ :next,
68
+ :key,
69
+ :row,
70
+ :sys,
71
+ :child_page_number,
72
+ :transaction_id,
73
+ :roll_pointer,
74
+ :length, # rubocop:disable Lint/StructNewOverride
75
+ keyword_init: true
76
+ )
77
+
78
+ FieldDescriptor = Struct.new(
79
+ :name,
80
+ :type,
81
+ :value,
82
+ :extern,
83
+ keyword_init: true
84
+ )
85
+
86
+ FsegHeader = Struct.new(
87
+ :leaf,
88
+ :internal,
89
+ keyword_init: true
90
+ )
91
+
92
+ PageHeader = Struct.new(
93
+ :n_dir_slots,
94
+ :heap_top,
95
+ :n_heap_format,
96
+ :n_heap,
97
+ :format,
98
+ :garbage_offset,
99
+ :garbage_size,
100
+ :last_insert_offset,
101
+ :direction,
102
+ :n_direction,
103
+ :n_recs,
104
+ :max_trx_id,
105
+ :level,
106
+ :index_id,
107
+ keyword_init: true
108
+ )
109
+
110
+ # The size (in bytes) of the "next" pointer in each record header.
111
+ RECORD_NEXT_SIZE = 2
112
+
113
+ # The size (in bytes) of the bit-packed fields in each record header for
114
+ # "redundant" record format.
115
+ RECORD_REDUNDANT_BITS_SIZE = 4
116
+
117
+ # Masks for 1-byte record end-offsets within "redundant" records.
118
+ RECORD_REDUNDANT_OFF1_OFFSET_MASK = 0x7f
119
+ RECORD_REDUNDANT_OFF1_NULL_MASK = 0x80
120
+
121
+ # Masks for 2-byte record end-offsets within "redundant" records.
122
+ RECORD_REDUNDANT_OFF2_OFFSET_MASK = 0x3fff
123
+ RECORD_REDUNDANT_OFF2_NULL_MASK = 0x8000
124
+ RECORD_REDUNDANT_OFF2_EXTERN_MASK = 0x4000
125
+
126
+ # The size (in bytes) of the bit-packed fields in each record header for
127
+ # "compact" record format.
128
+ RECORD_COMPACT_BITS_SIZE = 3
129
+
130
+ # Maximum number of fields.
131
+ RECORD_MAX_N_SYSTEM_FIELDS = 3
132
+ RECORD_MAX_N_FIELDS = 1024 - 1
133
+ RECORD_MAX_N_USER_FIELDS = RECORD_MAX_N_FIELDS - RECORD_MAX_N_SYSTEM_FIELDS * 2
134
+
135
+ # Page direction values possible in the page_header's :direction field.
136
+ PAGE_DIRECTION = {
137
+ 1 => :left, # Inserts have been in descending order.
138
+ 2 => :right, # Inserts have been in ascending order.
139
+ 3 => :same_rec, # Unused by InnoDB.
140
+ 4 => :same_page, # Unused by InnoDB.
141
+ 5 => :no_direction, # Inserts have been in random order.
142
+ }.freeze
143
+
144
+ # Record types used in the :type field of the record header.
145
+ RECORD_TYPES = {
146
+ 0 => :conventional, # A normal user record in a leaf page.
147
+ 1 => :node_pointer, # A node pointer in a non-leaf page.
148
+ 2 => :infimum, # The system "infimum" record.
149
+ 3 => :supremum, # The system "supremum" record.
150
+ }.freeze
151
+
152
+ # The size (in bytes) of the record pointers in each page directory slot.
153
+ PAGE_DIR_SLOT_SIZE = 2
154
+
155
+ # The minimum number of records "owned" by each record with an entry in
156
+ # the page directory.
157
+ PAGE_DIR_SLOT_MIN_N_OWNED = 4
158
+
159
+ # The maximum number of records "owned" by each record with an entry in
160
+ # the page directory.
161
+ PAGE_DIR_SLOT_MAX_N_OWNED = 8
162
+
163
+ attr_writer :record_describer
164
+
165
+ # Return the byte offset of the start of the "index" page header, which
166
+ # immediately follows the "fil" header.
167
+ def pos_index_header
168
+ pos_page_body
169
+ end
104
170
 
105
- # The size of the additional data structures in the header of the system
106
- # records, which is just 1 byte in redundant format to store the offset
107
- # of the end of the field. This is needed specifically here since we need
108
- # to be able to calculate the fixed positions of these system records.
109
- def size_mum_record_header_additional
110
- case page_header[:format]
111
- when :compact
112
- 0 # No additional data is stored in compact format.
113
- when :redundant
114
- 1 # A 1-byte offset for 1 field is stored in redundant format.
115
- end
116
- end
171
+ # The size of the "index" header.
172
+ def size_index_header
173
+ 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 8 + 2 + 8
174
+ end
117
175
 
118
- # The size of the data from the supremum or infimum records.
119
- def size_mum_record
120
- 8
121
- end
176
+ # Return the byte offset of the start of the "fseg" header, which immediately
177
+ # follows the "index" header.
178
+ def pos_fseg_header
179
+ pos_index_header + size_index_header
180
+ end
122
181
 
123
- # Return the byte offset of the start of the "origin" of the infimum record,
124
- # which is always the first record in the singly-linked record chain on any
125
- # page, and represents a record with a "lower value than any possible user
126
- # record". The infimum record immediately follows the page header.
127
- def pos_infimum
128
- pos_records +
129
- size_record_header +
130
- size_mum_record_header_additional
131
- end
182
+ # The size of the "fseg" header.
183
+ def size_fseg_header
184
+ 2 * Innodb::FsegEntry::SIZE
185
+ end
132
186
 
133
- # Return the byte offset of the start of the "origin" of the supremum record,
134
- # which is always the last record in the singly-linked record chain on any
135
- # page, and represents a record with a "higher value than any possible user
136
- # record". The supremum record immediately follows the infimum record.
137
- def pos_supremum
138
- pos_infimum +
139
- size_record_header +
140
- size_mum_record_header_additional +
141
- size_mum_record
142
- end
187
+ # Return the size of the header for each record.
188
+ def size_record_header
189
+ case page_header[:format]
190
+ when :compact
191
+ RECORD_NEXT_SIZE + RECORD_COMPACT_BITS_SIZE
192
+ when :redundant
193
+ RECORD_NEXT_SIZE + RECORD_REDUNDANT_BITS_SIZE
194
+ end
195
+ end
143
196
 
144
- # Return the byte offset of the start of records within the page (the
145
- # position immediately after the page header).
146
- def pos_records
147
- size_fil_header +
148
- size_index_header +
149
- size_fseg_header
150
- end
197
+ # The size of the additional data structures in the header of the system
198
+ # records, which is just 1 byte in redundant format to store the offset
199
+ # of the end of the field. This is needed specifically here since we need
200
+ # to be able to calculate the fixed positions of these system records.
201
+ def size_mum_record_header_additional
202
+ case page_header[:format]
203
+ when :compact
204
+ 0 # No additional data is stored in compact format.
205
+ when :redundant
206
+ 1 # A 1-byte offset for 1 field is stored in redundant format.
207
+ end
208
+ end
151
209
 
152
- # Return the byte offset of the start of the user records in a page, which
153
- # immediately follows the supremum record.
154
- def pos_user_records
155
- pos_supremum + size_mum_record
156
- end
210
+ # The size of the data from the supremum or infimum records.
211
+ def size_mum_record
212
+ 8
213
+ end
157
214
 
158
- # The position of the page directory, which starts at the "fil" trailer and
159
- # grows backwards from there.
160
- def pos_directory
161
- pos_fil_trailer
162
- end
215
+ # Return the byte offset of the start of the "origin" of the infimum record,
216
+ # which is always the first record in the singly-linked record chain on any
217
+ # page, and represents a record with a "lower value than any possible user
218
+ # record". The infimum record immediately follows the page header.
219
+ def pos_infimum
220
+ pos_records +
221
+ size_record_header +
222
+ size_mum_record_header_additional
223
+ end
163
224
 
164
- # The amount of space consumed by the page header.
165
- def header_space
166
- # The end of the supremum system record is the beginning of the space
167
- # available for user records.
168
- pos_user_records
169
- end
225
+ # Return the byte offset of the start of the "origin" of the supremum record,
226
+ # which is always the last record in the singly-linked record chain on any
227
+ # page, and represents a record with a "higher value than any possible user
228
+ # record". The supremum record immediately follows the infimum record.
229
+ def pos_supremum
230
+ pos_infimum +
231
+ size_record_header +
232
+ size_mum_record_header_additional +
233
+ size_mum_record
234
+ end
170
235
 
171
- # The number of directory slots in use.
172
- def directory_slots
173
- page_header[:n_dir_slots]
174
- end
236
+ # Return the byte offset of the start of records within the page (the
237
+ # position immediately after the page header).
238
+ def pos_records
239
+ size_fil_header +
240
+ size_index_header +
241
+ size_fseg_header
242
+ end
175
243
 
176
- # The amount of space consumed by the page directory.
177
- def directory_space
178
- directory_slots * PAGE_DIR_SLOT_SIZE
179
- end
244
+ # Return the byte offset of the start of the user records in a page, which
245
+ # immediately follows the supremum record.
246
+ def pos_user_records
247
+ pos_supremum + size_mum_record
248
+ end
180
249
 
181
- # The amount of space consumed by the trailers in the page.
182
- def trailer_space
183
- size_fil_trailer
184
- end
250
+ # The position of the page directory, which starts at the "fil" trailer and
251
+ # grows backwards from there.
252
+ def pos_directory
253
+ pos_fil_trailer
254
+ end
185
255
 
186
- # Return the amount of free space in the page.
187
- def free_space
188
- page_header[:garbage_size] +
189
- (size - size_fil_trailer - directory_space - page_header[:heap_top])
190
- end
256
+ # The amount of space consumed by the page header.
257
+ def header_space
258
+ # The end of the supremum system record is the beginning of the space
259
+ # available for user records.
260
+ pos_user_records
261
+ end
191
262
 
192
- # Return the amount of used space in the page.
193
- def used_space
194
- size - free_space
195
- end
263
+ # The number of directory slots in use.
264
+ def directory_slots
265
+ page_header[:n_dir_slots]
266
+ end
196
267
 
197
- # Return the amount of space occupied by records in the page.
198
- def record_space
199
- used_space - header_space - directory_space - trailer_space
200
- end
268
+ # The amount of space consumed by the page directory.
269
+ def directory_space
270
+ directory_slots * PAGE_DIR_SLOT_SIZE
271
+ end
201
272
 
202
- # Return the actual bytes of the portion of the page which is used to
203
- # store user records (eliminate the headers and trailer from the page).
204
- def record_bytes
205
- data(pos_user_records, page_header[:heap_top] - pos_user_records)
206
- end
273
+ # The amount of space consumed by the trailers in the page.
274
+ def trailer_space
275
+ size_fil_trailer
276
+ end
207
277
 
208
- # Return the "index" header.
209
- def page_header
210
- @page_header ||= cursor(pos_index_header).name("index") do |c|
211
- index = {
212
- :n_dir_slots => c.name("n_dir_slots") { c.get_uint16 },
213
- :heap_top => c.name("heap_top") { c.get_uint16 },
214
- :n_heap_format => c.name("n_heap_format") { c.get_uint16 },
215
- :garbage_offset => c.name("garbage_offset") { c.get_uint16 },
216
- :garbage_size => c.name("garbage_size") { c.get_uint16 },
217
- :last_insert_offset => c.name("last_insert_offset") { c.get_uint16 },
218
- :direction => c.name("direction") { PAGE_DIRECTION[c.get_uint16] },
219
- :n_direction => c.name("n_direction") { c.get_uint16 },
220
- :n_recs => c.name("n_recs") { c.get_uint16 },
221
- :max_trx_id => c.name("max_trx_id") { c.get_uint64 },
222
- :level => c.name("level") { c.get_uint16 },
223
- :index_id => c.name("index_id") { c.get_uint64 },
224
- }
225
- index[:n_heap] = index[:n_heap_format] & (2**15-1)
226
- index[:format] = (index[:n_heap_format] & 1<<15) == 0 ?
227
- :redundant : :compact
228
- index.delete :n_heap_format
229
-
230
- index
231
- end
232
- end
278
+ # Return the amount of free space in the page.
279
+ def free_space
280
+ page_header[:garbage_size] +
281
+ (size - size_fil_trailer - directory_space - page_header[:heap_top])
282
+ end
233
283
 
234
- # A helper function to return the index id.
235
- def index_id
236
- page_header && page_header[:index_id]
237
- end
284
+ # Return the amount of used space in the page.
285
+ def used_space
286
+ size - free_space
287
+ end
238
288
 
239
- # A helper function to return the page level from the "page" header, for
240
- # easier access.
241
- def level
242
- page_header && page_header[:level]
243
- end
289
+ # Return the amount of space occupied by records in the page.
290
+ def record_space
291
+ used_space - header_space - directory_space - trailer_space
292
+ end
244
293
 
245
- # A helper function to return the number of records.
246
- def records
247
- page_header && page_header[:n_recs]
248
- end
294
+ # A helper to calculate the amount of space consumed per record.
295
+ def space_per_record
296
+ page_header.n_recs.positive? ? (record_space.to_f / page_header.n_recs) : 0
297
+ end
249
298
 
250
- # A helper function to identify root index pages; they must be the only pages
251
- # at their level.
252
- def root?
253
- self.prev.nil? && self.next.nil?
254
- end
299
+ # Return the "index" header.
300
+ def page_header
301
+ @page_header ||= cursor(pos_index_header).name("index") do |c|
302
+ index = PageHeader.new(
303
+ n_dir_slots: c.name("n_dir_slots") { c.read_uint16 },
304
+ heap_top: c.name("heap_top") { c.read_uint16 },
305
+ n_heap_format: c.name("n_heap_format") { c.read_uint16 },
306
+ garbage_offset: c.name("garbage_offset") { c.read_uint16 },
307
+ garbage_size: c.name("garbage_size") { c.read_uint16 },
308
+ last_insert_offset: c.name("last_insert_offset") { c.read_uint16 },
309
+ direction: c.name("direction") { PAGE_DIRECTION[c.read_uint16] },
310
+ n_direction: c.name("n_direction") { c.read_uint16 },
311
+ n_recs: c.name("n_recs") { c.read_uint16 },
312
+ max_trx_id: c.name("max_trx_id") { c.read_uint64 },
313
+ level: c.name("level") { c.read_uint16 },
314
+ index_id: c.name("index_id") { c.read_uint64 }
315
+ )
316
+
317
+ index.n_heap = index.n_heap_format & (2**15 - 1)
318
+ index.format = (index.n_heap_format & 1 << 15).zero? ? :redundant : :compact
319
+
320
+ index
321
+ end
322
+ end
255
323
 
256
- # A helper function to identify leaf index pages.
257
- def leaf?
258
- level == 0
259
- end
324
+ def_delegator :page_header, :index_id
325
+ def_delegator :page_header, :level
326
+ def_delegator :page_header, :n_recs, :records
327
+ def_delegator :page_header, :garbage_offset
260
328
 
261
- # A helper function to return the offset to the first free record.
262
- def garbage_offset
263
- page_header && page_header[:garbage_offset]
264
- end
329
+ # A helper function to identify root index pages; they must be the only pages
330
+ # at their level.
331
+ def root?
332
+ prev.nil? && self.next.nil?
333
+ end
265
334
 
266
- # Return the "fseg" header.
267
- def fseg_header
268
- @fseg_header ||= cursor(pos_fseg_header).name("fseg") do |c|
269
- {
270
- :leaf => c.name("fseg[leaf]") {
271
- Innodb::FsegEntry.get_inode(@space, c)
272
- },
273
- :internal => c.name("fseg[internal]") {
274
- Innodb::FsegEntry.get_inode(@space, c)
275
- },
276
- }
277
- end
278
- end
335
+ # A helper function to identify leaf index pages.
336
+ def leaf?
337
+ level.zero?
338
+ end
279
339
 
280
- # Return the header from a record.
281
- def record_header(cursor)
282
- origin = cursor.position
283
- header = {}
284
- cursor.backward.name("header") do |c|
285
- case page_header[:format]
286
- when :compact
287
- # The "next" pointer is a relative offset from the current record.
288
- header[:next] = c.name("next") { origin + c.get_sint16 }
289
-
290
- # Fields packed in a 16-bit integer (LSB first):
291
- # 3 bits for type
292
- # 13 bits for heap_number
293
- bits1 = c.name("bits1") { c.get_uint16 }
294
- header[:type] = RECORD_TYPES[bits1 & 0x07]
295
- header[:heap_number] = (bits1 & 0xfff8) >> 3
296
- when :redundant
297
- # The "next" pointer is an absolute offset within the page.
298
- header[:next] = c.name("next") { c.get_uint16 }
299
-
300
- # Fields packed in a 24-bit integer (LSB first):
301
- # 1 bit for offset_size (0 = 2 bytes, 1 = 1 byte)
302
- # 10 bits for n_fields
303
- # 13 bits for heap_number
304
- bits1 = c.name("bits1") { c.get_uint24 }
305
- header[:offset_size] = (bits1 & 1) == 0 ? 2 : 1
306
- header[:n_fields] = (bits1 & (((1 << 10) - 1) << 1)) >> 1
307
- header[:heap_number] = (bits1 & (((1 << 13) - 1) << 11)) >> 11
340
+ # A helper to determine if an this page is part of an insert buffer index.
341
+ def ibuf_index?
342
+ index_id == Innodb::IbufIndex::INDEX_ID
308
343
  end
309
344
 
310
- # Fields packed in an 8-bit integer (LSB first):
311
- # 4 bits for n_owned
312
- # 4 bits for flags
313
- bits2 = c.name("bits2") { c.get_uint8 }
314
- header[:n_owned] = bits2 & 0x0f
315
- info = (bits2 & 0xf0) >> 4
316
- header[:min_rec] = (info & RECORD_INFO_MIN_REC_FLAG) != 0
317
- header[:deleted] = (info & RECORD_INFO_DELETED_FLAG) != 0
318
-
319
- case page_header[:format]
320
- when :compact
321
- record_header_compact_additional(header, cursor)
322
- when :redundant
323
- record_header_redundant_additional(header, cursor)
345
+ # Return the "fseg" header.
346
+ def fseg_header
347
+ @fseg_header ||= cursor(pos_fseg_header).name("fseg") do |c|
348
+ FsegHeader.new(
349
+ leaf: c.name("fseg[leaf]") { Innodb::FsegEntry.get_inode(@space, c) },
350
+ internal: c.name("fseg[internal]") { Innodb::FsegEntry.get_inode(@space, c) }
351
+ )
352
+ end
324
353
  end
325
354
 
326
- header[:length] = origin - cursor.position
327
- end
355
+ # Return the header from a record.
356
+ def record_header(cursor)
357
+ origin = cursor.position
358
+ header = RecordHeader.new
359
+ cursor.backward.name("header") do |c|
360
+ case page_header.format
361
+ when :compact
362
+ # The "next" pointer is a relative offset from the current record.
363
+ header.next = c.name("next") { origin + c.read_sint16 }
364
+
365
+ # Fields packed in a 16-bit integer (LSB first):
366
+ # 3 bits for type
367
+ # 13 bits for heap_number
368
+ bits1 = c.name("bits1") { c.read_uint16 }
369
+ header.type = RECORD_TYPES[bits1 & 0x07]
370
+ header.heap_number = (bits1 & 0xfff8) >> 3
371
+ when :redundant
372
+ # The "next" pointer is an absolute offset within the page.
373
+ header.next = c.name("next") { c.read_uint16 }
374
+
375
+ # Fields packed in a 24-bit integer (LSB first):
376
+ # 1 bit for offset_size (0 = 2 bytes, 1 = 1 byte)
377
+ # 10 bits for n_fields
378
+ # 13 bits for heap_number
379
+ bits1 = c.name("bits1") { c.read_uint24 }
380
+ header.offset_size = (bits1 & 1).zero? ? 2 : 1
381
+ header.n_fields = (bits1 & (((1 << 10) - 1) << 1)) >> 1
382
+ header.heap_number = (bits1 & (((1 << 13) - 1) << 11)) >> 11
383
+ end
328
384
 
329
- header
330
- end
385
+ # Fields packed in an 8-bit integer (LSB first):
386
+ # 4 bits for n_owned
387
+ # 4 bits for flags
388
+ bits2 = c.name("bits2") { c.read_uint8 }
389
+ header.n_owned = bits2 & 0x0f
390
+ header.info_flags = (bits2 & 0xf0) >> 4
391
+
392
+ case page_header.format
393
+ when :compact
394
+ record_header_compact_additional(header, cursor)
395
+ when :redundant
396
+ record_header_redundant_additional(header, cursor)
397
+ end
398
+
399
+ header.length = origin - cursor.position
400
+ end
331
401
 
332
- # Read additional header information from a compact format record header.
333
- def record_header_compact_additional(header, cursor)
334
- case header[:type]
335
- when :conventional, :node_pointer
336
- # The variable-length part of the record header contains a
337
- # bit vector indicating NULL fields and the length of each
338
- # non-NULL variable-length field.
339
- if record_format
340
- header[:nulls] = cursor.name("nulls") {
341
- record_header_compact_null_bitmap(cursor)
342
- }
343
- header[:lengths], header[:externs] =
344
- cursor.name("lengths_and_externs") {
345
- record_header_compact_variable_lengths_and_externs(cursor,
346
- header[:nulls])
347
- }
402
+ header
348
403
  end
349
- end
350
- end
351
404
 
352
- # Return an array indicating which fields are null.
353
- def record_header_compact_null_bitmap(cursor)
354
- fields = record_fields
405
+ # Read additional header information from a compact format record header.
406
+ def record_header_compact_additional(header, cursor)
407
+ case header.type
408
+ when :conventional, :node_pointer
409
+ # The variable-length part of the record header contains a
410
+ # bit vector indicating NULL fields and the length of each
411
+ # non-NULL variable-length field.
412
+ if record_format
413
+ header.nulls = cursor.name("nulls") { record_header_compact_null_bitmap(cursor) }
414
+ header.lengths, header.externs = cursor.name("lengths_and_externs") do
415
+ record_header_compact_variable_lengths_and_externs(cursor, header.nulls)
416
+ end
417
+ end
418
+ end
419
+ end
355
420
 
356
- # The number of bits in the bitmap is the number of nullable fields.
357
- size = fields.count { |f| f.nullable? }
421
+ # Return an array indicating which fields are null.
422
+ def record_header_compact_null_bitmap(cursor)
423
+ fields = record_fields
358
424
 
359
- # There is no bitmap if there are no nullable fields.
360
- return [] unless size > 0
425
+ # The number of bits in the bitmap is the number of nullable fields.
426
+ size = fields.count(&:nullable?)
361
427
 
362
- null_bit_array = cursor.get_bit_array(size).reverse!
428
+ # There is no bitmap if there are no nullable fields.
429
+ return [] unless size.positive?
363
430
 
364
- # For every nullable field, select the ones which are actually null.
365
- fields.inject([]) do |nulls, f|
366
- nulls << f.name if f.nullable? && (null_bit_array.shift == 1)
367
- nulls
368
- end
369
- end
431
+ # TODO: This is really ugly.
432
+ null_bit_array = cursor.read_bit_array(size).reverse!
370
433
 
371
- # Return an array containing an array of the length of each variable-length
372
- # field and an array indicating which fields are stored externally.
373
- def record_header_compact_variable_lengths_and_externs(cursor, nulls)
374
- fields = (record_format[:key] + record_format[:row])
434
+ # For every nullable field, select the ones which are actually null.
435
+ fields.select { |f| f.nullable? && (null_bit_array.shift == 1) }.map(&:name)
436
+ end
375
437
 
376
- lengths = {}
377
- externs = []
438
+ # Return an array containing an array of the length of each variable-length
439
+ # field and an array indicating which fields are stored externally.
440
+ def record_header_compact_variable_lengths_and_externs(cursor, nulls)
441
+ fields = (record_format[:key] + record_format[:row])
378
442
 
379
- # For each non-NULL variable-length field, the record header contains
380
- # the length in one or two bytes.
381
- fields.each do |f|
382
- next if !f.variable? || nulls.include?(f.name)
443
+ lengths = {}
444
+ externs = []
383
445
 
384
- len = cursor.get_uint8
385
- ext = false
446
+ # For each non-NULL variable-length field, the record header contains
447
+ # the length in one or two bytes.
448
+ fields.each do |f|
449
+ next if !f.variable? || nulls.include?(f.name)
386
450
 
387
- # Two bytes are used only if the length exceeds 127 bytes and the
388
- # maximum length exceeds 255 bytes (or the field is a BLOB type).
389
- if len > 127 && (f.blob? || f.data_type.width > 255)
390
- ext = (0x40 & len) != 0
391
- len = ((len & 0x3f) << 8) + cursor.get_uint8
392
- end
451
+ len = cursor.read_uint8
452
+ ext = false
393
453
 
394
- lengths[f.name] = len
395
- externs << f.name if ext
396
- end
454
+ # Two bytes are used only if the length exceeds 127 bytes and the
455
+ # maximum length exceeds 255 bytes (or the field is a BLOB type).
456
+ if len > 127 && (f.blob? || f.data_type.width > 255)
457
+ ext = (0x40 & len) != 0
458
+ len = ((len & 0x3f) << 8) + cursor.read_uint8
459
+ end
397
460
 
398
- return lengths, externs
399
- end
461
+ lengths[f.name] = len
462
+ externs << f.name if ext
463
+ end
400
464
 
401
- # Read additional header information from a redundant format record header.
402
- def record_header_redundant_additional(header, cursor)
403
- lengths, nulls, externs = [], [], []
404
-
405
- field_offsets = record_header_redundant_field_end_offsets(header, cursor)
406
-
407
- this_field_offset = 0
408
- field_offsets.each do |n|
409
- case header[:offset_size]
410
- when 1
411
- next_field_offset = (n & RECORD_REDUNDANT_OFF1_OFFSET_MASK)
412
- lengths << (next_field_offset - this_field_offset)
413
- nulls << ((n & RECORD_REDUNDANT_OFF1_NULL_MASK) != 0)
414
- externs << false
415
- when 2
416
- next_field_offset = (n & RECORD_REDUNDANT_OFF2_OFFSET_MASK)
417
- lengths << (next_field_offset - this_field_offset)
418
- nulls << ((n & RECORD_REDUNDANT_OFF2_NULL_MASK) != 0)
419
- externs << ((n & RECORD_REDUNDANT_OFF2_EXTERN_MASK) != 0)
465
+ [lengths, externs]
420
466
  end
421
- this_field_offset = next_field_offset
422
- end
423
467
 
424
- # If possible, refer to fields by name rather than position for
425
- # better formatting (i.e. pp).
426
- if record_format
427
- header[:lengths], header[:nulls], header[:externs] = {}, [], []
468
+ # Read additional header information from a redundant format record header.
469
+ def record_header_redundant_additional(header, cursor)
470
+ lengths = []
471
+ nulls = []
472
+ externs = []
473
+
474
+ field_offsets = record_header_redundant_field_end_offsets(header, cursor)
475
+
476
+ this_field_offset = 0
477
+ field_offsets.each do |n|
478
+ case header.offset_size
479
+ when 1
480
+ next_field_offset = (n & RECORD_REDUNDANT_OFF1_OFFSET_MASK)
481
+ lengths << (next_field_offset - this_field_offset)
482
+ nulls << ((n & RECORD_REDUNDANT_OFF1_NULL_MASK) != 0)
483
+ externs << false
484
+ when 2
485
+ next_field_offset = (n & RECORD_REDUNDANT_OFF2_OFFSET_MASK)
486
+ lengths << (next_field_offset - this_field_offset)
487
+ nulls << ((n & RECORD_REDUNDANT_OFF2_NULL_MASK) != 0)
488
+ externs << ((n & RECORD_REDUNDANT_OFF2_EXTERN_MASK) != 0)
489
+ end
490
+ this_field_offset = next_field_offset
491
+ end
428
492
 
429
- record_fields.each do |f|
430
- header[:lengths][f.name] = lengths[f.position]
431
- header[:nulls] << f.name if nulls[f.position]
432
- header[:externs] << f.name if externs[f.position]
493
+ # If possible, refer to fields by name rather than position for
494
+ # better formatting (i.e. pp).
495
+ if record_format
496
+ header.lengths = {}
497
+ header.nulls = []
498
+ header.externs = []
499
+
500
+ record_fields.each do |f|
501
+ header.lengths[f.name] = lengths[f.position]
502
+ header.nulls << f.name if nulls[f.position]
503
+ header.externs << f.name if externs[f.position]
504
+ end
505
+ else
506
+ header.lengths = lengths
507
+ header.nulls = nulls
508
+ header.externs = externs
509
+ end
433
510
  end
434
- else
435
- header[:lengths], header[:nulls], header[:externs] = lengths, nulls, externs
436
- end
437
- end
438
511
 
439
- # Read field end offsets from the provided cursor for each field as counted
440
- # by n_fields.
441
- def record_header_redundant_field_end_offsets(header, cursor)
442
- (0...header[:n_fields]).to_a.inject([]) do |offsets, n|
443
- cursor.name("field_end_offset[#{n}]") {
444
- offsets << cursor.get_uint_by_size(header[:offset_size])
445
- }
446
- offsets
447
- end
448
- end
512
+ # Read field end offsets from the provided cursor for each field as counted
513
+ # by n_fields.
514
+ def record_header_redundant_field_end_offsets(header, cursor)
515
+ header.n_fields.times.map do |n|
516
+ cursor.name("field_end_offset[#{n}]") { cursor.read_uint_by_size(header.offset_size) }
517
+ end
518
+ end
449
519
 
450
- # Parse and return simple fixed-format system records, such as InnoDB's
451
- # internal infimum and supremum records.
452
- def system_record(offset)
453
- cursor(offset).name("record[#{offset}]") do |c|
454
- header = c.peek { record_header(c) }
455
- Innodb::Record.new(self, {
456
- :offset => offset,
457
- :header => header,
458
- :next => header[:next],
459
- :data => c.name("data") { c.get_bytes(size_mum_record) },
460
- :length => c.position - offset,
461
- })
462
- end
463
- end
520
+ # Parse and return simple fixed-format system records, such as InnoDB's
521
+ # internal infimum and supremum records.
522
+ def system_record(offset)
523
+ cursor(offset).name("record[#{offset}]") do |c|
524
+ header = c.peek { record_header(c) }
525
+ Innodb::Record.new(
526
+ self,
527
+ SystemRecord.new(
528
+ offset: offset,
529
+ header: header,
530
+ next: header.next,
531
+ data: c.name("data") { c.read_bytes(size_mum_record) },
532
+ length: c.position - offset
533
+ )
534
+ )
535
+ end
536
+ end
464
537
 
465
- # Return the infimum record on a page.
466
- def infimum
467
- @infimum ||= system_record(pos_infimum)
468
- end
538
+ # Return the infimum record on a page.
539
+ def infimum
540
+ @infimum ||= system_record(pos_infimum)
541
+ end
469
542
 
470
- # Return the supremum record on a page.
471
- def supremum
472
- @supremum ||= system_record(pos_supremum)
473
- end
543
+ # Return the supremum record on a page.
544
+ def supremum
545
+ @supremum ||= system_record(pos_supremum)
546
+ end
474
547
 
475
- def record_describer=(o)
476
- @record_describer = o
477
- end
548
+ def make_record_describer
549
+ if space&.innodb_system && index_id
550
+ @record_describer = space.innodb_system.data_dictionary.record_describer_by_index_id(index_id)
551
+ elsif space
552
+ @record_describer = space.record_describer
553
+ end
554
+ end
478
555
 
479
- def record_describer
480
- return @record_describer if @record_describer
556
+ def record_describer
557
+ @record_describer ||= make_record_describer
558
+ end
481
559
 
482
- if space and space.innodb_system and index_id
483
- @record_describer =
484
- space.innodb_system.data_dictionary.record_describer_by_index_id(index_id)
485
- elsif space
486
- @record_describer = space.record_describer
487
- end
560
+ # Return a set of field objects that describe the record.
561
+ def make_record_description
562
+ position = (0..RECORD_MAX_N_FIELDS).each
563
+ description = record_describer.description
564
+ fields = { type: description[:type], key: [], sys: [], row: [] }
488
565
 
489
- @record_describer
490
- end
566
+ description[:key].each do |field|
567
+ fields[:key] << Innodb::Field.new(position.next, field[:name], *field[:type])
568
+ end
491
569
 
492
- # Return a set of field objects that describe the record.
493
- def make_record_description
494
- position = (0..RECORD_MAX_N_FIELDS).each
495
- description = record_describer.description
496
- fields = {:type => description[:type], :key => [], :sys => [], :row => []}
570
+ # If this is a leaf page of the clustered index, read InnoDB's internal
571
+ # fields, a transaction ID and roll pointer.
572
+ if leaf? && fields[:type] == :clustered
573
+ [["DB_TRX_ID", :TRX_ID], ["DB_ROLL_PTR", :ROLL_PTR]].each do |name, type|
574
+ fields[:sys] << Innodb::Field.new(position.next, name, type, :NOT_NULL)
575
+ end
576
+ end
497
577
 
498
- description[:key].each do |field|
499
- fields[:key] << Innodb::Field.new(position.next, field[:name], *field[:type])
500
- end
578
+ # If this is a leaf page of the clustered index, or any page of a
579
+ # secondary index, read the non-key fields.
580
+ if (leaf? && fields[:type] == :clustered) || (fields[:type] == :secondary)
581
+ description[:row].each do |field|
582
+ fields[:row] << Innodb::Field.new(position.next, field[:name], *field[:type])
583
+ end
584
+ end
501
585
 
502
- # If this is a leaf page of the clustered index, read InnoDB's internal
503
- # fields, a transaction ID and roll pointer.
504
- if level == 0 && fields[:type] == :clustered
505
- [["DB_TRX_ID", :TRX_ID,],["DB_ROLL_PTR", :ROLL_PTR]].each do |name, type|
506
- fields[:sys] << Innodb::Field.new(position.next, name, type, :NOT_NULL)
586
+ fields
507
587
  end
508
- end
509
588
 
510
- # If this is a leaf page of the clustered index, or any page of a
511
- # secondary index, read the non-key fields.
512
- if (level == 0 && fields[:type] == :clustered) || (fields[:type] == :secondary)
513
- description[:row].each do |field|
514
- fields[:row] << Innodb::Field.new(position.next, field[:name], *field[:type])
589
+ # Return (and cache) the record format provided by an external class.
590
+ def record_format
591
+ @record_format ||= make_record_description if record_describer
515
592
  end
516
- end
517
-
518
- fields
519
- end
520
593
 
521
- # Return (and cache) the record format provided by an external class.
522
- def record_format
523
- if record_describer
524
- @record_format ||= make_record_description()
525
- end
526
- end
527
-
528
- # Returns the (ordered) set of fields that describe records in this page.
529
- def record_fields
530
- if record_format
531
- record_format.values_at(:key, :sys, :row).flatten.sort_by {|f| f.position}
532
- end
533
- end
534
-
535
- # Parse and return a record at a given offset.
536
- def record(offset)
537
- return nil unless offset
538
- return infimum if offset == pos_infimum
539
- return supremum if offset == pos_supremum
540
-
541
- cursor(offset).forward.name("record[#{offset}]") do |c|
542
- # There is a header preceding the row itself, so back up and read it.
543
- header = c.peek { record_header(c) }
544
-
545
- this_record = {
546
- :format => page_header[:format],
547
- :offset => offset,
548
- :header => header,
549
- :next => header[:next] == 0 ? nil : (header[:next]),
550
- }
551
-
552
- if record_format
553
- this_record[:type] = record_format[:type]
554
-
555
- # Used to indicate whether a field is part of key/row/sys.
556
- fmap = [:key, :row, :sys].inject({}) do |h, k|
557
- this_record[k] = []
558
- record_format[k].each { |f| h[f.position] = k }
559
- h
560
- end
594
+ # Returns the (ordered) set of fields that describe records in this page.
595
+ def record_fields
596
+ record_format.values_at(:key, :sys, :row).flatten.sort_by(&:position) if record_format
597
+ end
561
598
 
562
- # Read the fields present in this record.
563
- record_fields.each do |f|
564
- p = fmap[f.position]
565
- c.name("#{p.to_s}[#{f.name}]") do
566
- this_record[p] << {
567
- :name => f.name,
568
- :type => f.data_type.name,
569
- :value => f.value(c, this_record),
570
- :extern => f.extern(c, this_record),
571
- }.reject { |k, v| v.nil? }
599
+ # Parse and return a record at a given offset.
600
+ def record(offset)
601
+ return nil unless offset
602
+ return infimum if offset == pos_infimum
603
+ return supremum if offset == pos_supremum
604
+
605
+ cursor(offset).forward.name("record[#{offset}]") do |c|
606
+ # There is a header preceding the row itself, so back up and read it.
607
+ header = c.peek { record_header(c) }
608
+
609
+ this_record = UserRecord.new(
610
+ format: page_header.format,
611
+ offset: offset,
612
+ header: header,
613
+ next: header.next.zero? ? nil : header.next
614
+ )
615
+
616
+ if record_format
617
+ this_record.type = record_format[:type]
618
+
619
+ # Used to indicate whether a field is part of key/row/sys.
620
+ # TODO: There's probably a better way to do this.
621
+ fmap = %i[key row sys].each_with_object({}) do |k, h|
622
+ this_record[k] = []
623
+ record_format[k].each { |f| h[f.position] = k }
624
+ end
625
+
626
+ # Read the fields present in this record.
627
+ record_fields.each do |f|
628
+ p = fmap[f.position]
629
+ c.name("#{p}[#{f.name}]") do
630
+ this_record[p] << FieldDescriptor.new(
631
+ name: f.name,
632
+ type: f.data_type.name,
633
+ value: f.value(c, this_record),
634
+ extern: f.extern(c, this_record)
635
+ )
636
+ end
637
+ end
638
+
639
+ # If this is a node (non-leaf) page, it will have a child page number
640
+ # (or "node pointer") stored as the last field.
641
+ this_record.child_page_number = c.name("child_page_number") { c.read_uint32 } unless leaf?
642
+
643
+ this_record.length = c.position - offset
644
+
645
+ # Add system field accessors for convenience.
646
+ this_record.sys.each do |f|
647
+ case f[:name]
648
+ when "DB_TRX_ID"
649
+ this_record.transaction_id = f[:value]
650
+ when "DB_ROLL_PTR"
651
+ this_record.roll_pointer = f[:value]
652
+ end
653
+ end
572
654
  end
655
+
656
+ Innodb::Record.new(self, this_record)
573
657
  end
658
+ end
574
659
 
575
- # If this is a node (non-leaf) page, it will have a child page number
576
- # (or "node pointer") stored as the last field.
577
- if level > 0
578
- # Read the node pointer in a node (non-leaf) page.
579
- this_record[:child_page_number] =
580
- c.name("child_page_number") { c.get_uint32 }
660
+ # Return an array of row offsets for all entries in the page directory.
661
+ def directory
662
+ @directory ||= cursor(pos_directory).backward.name("page_directory") do |c|
663
+ directory_slots.times.map { |n| c.name("slot[#{n}]") { c.read_uint16 } }
581
664
  end
665
+ end
582
666
 
583
- this_record[:length] = c.position - offset
667
+ # Return the slot number of the provided offset in the page directory, or nil
668
+ # if the offset is not present in the page directory.
669
+ def offset_directory_slot(offset)
670
+ directory.index(offset)
671
+ end
584
672
 
585
- # Add system field accessors for convenience.
586
- this_record[:sys].each do |f|
587
- case f[:name]
588
- when "DB_TRX_ID"
589
- this_record[:transaction_id] = f[:value]
590
- when "DB_ROLL_PTR"
591
- this_record[:roll_pointer] = f[:value]
592
- end
593
- end
673
+ # Return the slot number of the provided record in the page directory, or nil
674
+ # if the record is not present in the page directory.
675
+ def record_directory_slot(this_record)
676
+ offset_directory_slot(this_record.offset)
594
677
  end
595
678
 
596
- Innodb::Record.new(self, this_record)
597
- end
598
- end
679
+ # Return the slot number for the page directory entry which "owns" the
680
+ # provided record. This will be either the record itself, or the nearest
681
+ # record with an entry in the directory and a value greater than the record.
682
+ def directory_slot_for_record(this_record)
683
+ slot = record_directory_slot(this_record)
684
+ return slot if slot
599
685
 
600
- # Return an array of row offsets for all entries in the page directory.
601
- def directory
602
- return @directory if @directory
686
+ search_cursor = record_cursor(this_record.next)
687
+ raise "Could not position cursor" unless search_cursor
603
688
 
604
- @directory = []
605
- cursor(pos_directory).backward.name("page_directory") do |c|
606
- directory_slots.times do |n|
607
- @directory.push c.name("slot[#{n}]") { c.get_uint16 }
689
+ while (rec = search_cursor.record)
690
+ slot = record_directory_slot(rec)
691
+ return slot if slot
692
+ end
693
+
694
+ record_directory_slot(supremum)
608
695
  end
609
- end
610
696
 
611
- @directory
612
- end
697
+ def each_directory_offset
698
+ return enum_for(:each_directory_offset) unless block_given?
613
699
 
614
- # Return the slot number of the provided offset in the page directory, or nil
615
- # if the offset is not present in the page directory.
616
- def offset_is_directory_slot?(offset)
617
- directory.index(offset)
618
- end
700
+ directory.each do |offset|
701
+ yield offset unless [pos_infimum, pos_supremum].include?(offset)
702
+ end
703
+ end
619
704
 
620
- # Return the slot number of the provided record in the page directory, or nil
621
- # if the record is not present in the page directory.
622
- def record_is_directory_slot?(this_record)
623
- offset_is_directory_slot?(this_record.offset)
624
- end
705
+ def each_directory_record
706
+ return enum_for(:each_directory_record) unless block_given?
625
707
 
626
- # Return the slot number for the page directory entry which "owns" the
627
- # provided record. This will be either the record itself, or the nearest
628
- # record with an entry in the directory and a value greater than the record.
629
- def directory_slot_for_record(this_record)
630
- if slot = record_is_directory_slot?(this_record)
631
- return slot
632
- end
708
+ each_directory_offset do |offset|
709
+ yield record(offset)
710
+ end
711
+ end
633
712
 
634
- unless search_cursor = record_cursor(this_record.next)
635
- raise "Couldn't position cursor"
636
- end
713
+ # A class for cursoring through records starting from an arbitrary point.
714
+ class RecordCursor
715
+ def initialize(page, offset, direction)
716
+ Innodb::Stats.increment :page_record_cursor_create
637
717
 
638
- while rec = search_cursor.record
639
- if slot = record_is_directory_slot?(rec)
640
- return slot
641
- end
642
- end
718
+ @initial = true
719
+ @page = page
720
+ @direction = direction
721
+ @record = initial_record(offset)
722
+ end
643
723
 
644
- return record_is_directory_slot?(supremum)
645
- end
724
+ def initial_record(offset)
725
+ case offset
726
+ when :min
727
+ @page.min_record
728
+ when :max
729
+ @page.max_record
730
+ else
731
+ # Offset is a byte offset of a record (hopefully).
732
+ @page.record(offset)
733
+ end
734
+ end
646
735
 
647
- def each_directory_offset
648
- unless block_given?
649
- return enum_for(:each_directory_offset)
650
- end
736
+ # Return the next record, and advance the cursor. Return nil when the
737
+ # end of records (supremum) is reached.
738
+ def next_record
739
+ Innodb::Stats.increment :page_record_cursor_next_record
651
740
 
652
- directory.each do |offset|
653
- yield offset unless [pos_infimum, pos_supremum].include?(offset)
654
- end
655
- end
741
+ rec = @page.record(@record.next)
656
742
 
657
- def each_directory_record
658
- unless block_given?
659
- return enum_for(:each_directory_record)
660
- end
743
+ # The garbage record list's end is self-linked, so we must check for
744
+ # both supremum and the current record's offset.
745
+ if rec == @page.supremum || rec.offset == @record.offset
746
+ # We've reached the end of the linked list at supremum.
747
+ nil
748
+ else
749
+ @record = rec
750
+ end
751
+ end
661
752
 
662
- each_directory_offset do |offset|
663
- yield record(offset)
664
- end
665
- end
753
+ # Return the previous record, and advance the cursor. Return nil when the
754
+ # end of records (infimum) is reached.
755
+ def prev_record
756
+ Innodb::Stats.increment :page_record_cursor_prev_record
666
757
 
667
- # A class for cursoring through records starting from an arbitrary point.
668
- class RecordCursor
669
- def initialize(page, offset, direction)
670
- Innodb::Stats.increment :page_record_cursor_create
671
-
672
- @initial = true
673
- @page = page
674
- @direction = direction
675
- case offset
676
- when :min
677
- @record = @page.min_record
678
- when :max
679
- @record = @page.max_record
680
- else
681
- # Offset is a byte offset of a record (hopefully).
682
- @record = @page.record(offset)
683
- end
684
- end
758
+ slot = @page.directory_slot_for_record(@record)
759
+ raise "Could not find slot for record" unless slot
685
760
 
686
- # Return the next record, and advance the cursor. Return nil when the
687
- # end of records (supremum) is reached.
688
- def next_record
689
- Innodb::Stats.increment :page_record_cursor_next_record
761
+ search_cursor = @page.record_cursor(@page.directory[slot - 1])
762
+ raise "Could not position search cursor" unless search_cursor
690
763
 
691
- rec = @page.record(@record.next)
764
+ while (rec = search_cursor.record) && rec.offset != @record.offset
765
+ next unless rec.next == @record.offset
692
766
 
693
- # The garbage record list's end is self-linked, so we must check for
694
- # both supremum and the current record's offset.
695
- if rec == @page.supremum || rec.offset == @record.offset
696
- # We've reached the end of the linked list at supremum.
697
- nil
698
- else
699
- @record = rec
700
- end
701
- end
767
+ return if rec == @page.infimum
702
768
 
703
- # Return the previous record, and advance the cursor. Return nil when the
704
- # end of records (infimum) is reached.
705
- def prev_record
706
- Innodb::Stats.increment :page_record_cursor_prev_record
769
+ return @record = rec
770
+ end
771
+ end
707
772
 
708
- unless slot = @page.directory_slot_for_record(@record)
709
- raise "Couldn't find slot for record"
710
- end
773
+ # Return the next record in the order defined when the cursor was created.
774
+ def record
775
+ if @initial
776
+ @initial = false
777
+ return @record
778
+ end
711
779
 
712
- unless search_cursor = @page.record_cursor(@page.directory[slot-1])
713
- raise "Couldn't position search cursor"
714
- end
780
+ case @direction
781
+ when :forward
782
+ next_record
783
+ when :backward
784
+ prev_record
785
+ end
786
+ end
787
+
788
+ # Iterate through all records in the cursor.
789
+ def each_record
790
+ return enum_for(:each_record) unless block_given?
715
791
 
716
- while rec = search_cursor.record and rec.offset != @record.offset
717
- if rec.next == @record.offset
718
- if rec == @page.infimum
719
- return nil
792
+ while (rec = record)
793
+ yield rec
720
794
  end
721
- return @record = rec
722
795
  end
723
796
  end
724
- end
725
797
 
726
- # Return the next record in the order defined when the cursor was created.
727
- def record
728
- if @initial
729
- @initial = false
730
- return @record
798
+ # Return a RecordCursor starting at offset.
799
+ def record_cursor(offset = :min, direction = :forward)
800
+ RecordCursor.new(self, offset, direction)
731
801
  end
732
802
 
733
- case @direction
734
- when :forward
735
- next_record
736
- when :backward
737
- prev_record
803
+ def record_if_exists(offset)
804
+ each_record do |rec|
805
+ return rec if rec.offset == offset
806
+ end
738
807
  end
739
- end
740
808
 
741
- # Iterate through all records in the cursor.
742
- def each_record
743
- unless block_given?
744
- return enum_for(:each_record)
809
+ # Return the minimum record on this page.
810
+ def min_record
811
+ min = record(infimum.next)
812
+ min if min != supremum
745
813
  end
746
814
 
747
- while rec = record
748
- yield rec
815
+ # Return the maximum record on this page.
816
+ def max_record
817
+ # Since the records are only singly-linked in the forward direction, in
818
+ # order to do find the last record, we must create a cursor and walk
819
+ # backwards one step.
820
+ max_cursor = record_cursor(supremum.offset, :backward)
821
+ raise "Could not position cursor" unless max_cursor
822
+
823
+ # Note the deliberate use of prev_record rather than record; we want
824
+ # to skip over supremum itself.
825
+ max = max_cursor.prev_record
826
+ max if max != infimum
749
827
  end
750
- end
751
- end
752
-
753
- # Return a RecordCursor starting at offset.
754
- def record_cursor(offset=:min, direction=:forward)
755
- RecordCursor.new(self, offset, direction)
756
- end
757
828
 
758
- def record_if_exists(offset)
759
- each_record do |rec|
760
- return rec if rec.offset == offset
761
- end
762
- end
763
-
764
- # Return the minimum record on this page.
765
- def min_record
766
- min = record(infimum.next)
767
- min if min != supremum
768
- end
829
+ # Search for a record within a single page, and return either a perfect
830
+ # match for the key, or the last record closest to they key but not greater
831
+ # than the key. (If an exact match is desired, compare_key must be used to
832
+ # check if the returned record matches. This makes the function useful for
833
+ # search in both leaf and non-leaf pages.)
834
+ def linear_search_from_cursor(search_cursor, key)
835
+ Innodb::Stats.increment :linear_search_from_cursor
836
+
837
+ this_rec = search_cursor.record
838
+
839
+ if Innodb.debug?
840
+ puts "linear_search_from_cursor: page=%i, level=%i, start=(%s)" % [
841
+ offset,
842
+ level,
843
+ this_rec && this_rec.key_string,
844
+ ]
845
+ end
769
846
 
770
- # Return the maximum record on this page.
771
- def max_record
772
- # Since the records are only singly-linked in the forward direction, in
773
- # order to do find the last record, we must create a cursor and walk
774
- # backwards one step.
775
- unless max_cursor = record_cursor(supremum.offset, :backward)
776
- raise "Couldn't position cursor"
777
- end
778
- # Note the deliberate use of prev_record rather than record; we want
779
- # to skip over supremum itself.
780
- max = max_cursor.prev_record
781
- max if max != infimum
782
- end
847
+ # Iterate through all records until finding either a matching record or
848
+ # one whose key is greater than the desired key.
849
+ while this_rec && (next_rec = search_cursor.record)
850
+ Innodb::Stats.increment :linear_search_from_cursor_record_scans
851
+
852
+ if Innodb.debug?
853
+ puts "linear_search_from_cursor: page=%i, level=%i, current=(%s)" % [
854
+ offset,
855
+ level,
856
+ this_rec.key_string,
857
+ ]
858
+ end
783
859
 
784
- # Search for a record within a single page, and return either a perfect
785
- # match for the key, or the last record closest to they key but not greater
786
- # than the key. (If an exact match is desired, compare_key must be used to
787
- # check if the returned record matches. This makes the function useful for
788
- # search in both leaf and non-leaf pages.)
789
- def linear_search_from_cursor(search_cursor, key)
790
- Innodb::Stats.increment :linear_search_from_cursor
791
-
792
- this_rec = search_cursor.record
793
-
794
- if Innodb.debug?
795
- puts "linear_search_from_cursor: page=%i, level=%i, start=(%s)" % [
796
- offset,
797
- level,
798
- this_rec && this_rec.key_string,
799
- ]
800
- end
860
+ # If we reach supremum, return the last non-system record we got.
861
+ return this_rec if next_rec.header.type == :supremum
801
862
 
802
- # Iterate through all records until finding either a matching record or
803
- # one whose key is greater than the desired key.
804
- while this_rec && next_rec = search_cursor.record
805
- Innodb::Stats.increment :linear_search_from_cursor_record_scans
806
-
807
- if Innodb.debug?
808
- puts "linear_search_from_cursor: page=%i, level=%i, current=(%s)" % [
809
- offset,
810
- level,
811
- this_rec && this_rec.key_string,
812
- ]
813
- end
863
+ return this_rec if this_rec.compare_key(key).negative?
814
864
 
815
- # If we reach supremum, return the last non-system record we got.
816
- return this_rec if next_rec.header[:type] == :supremum
865
+ # The desired key is either an exact match for this_rec or is greater
866
+ # than it but less than next_rec. If this is a non-leaf page, that
867
+ # will mean that the record will fall on the leaf page this node
868
+ # pointer record points to, if it exists at all.
869
+ return this_rec if !this_rec.compare_key(key).negative? && next_rec.compare_key(key).negative?
817
870
 
818
- if this_rec.compare_key(key) < 0
819
- return this_rec
820
- end
871
+ this_rec = next_rec
872
+ end
821
873
 
822
- if (this_rec.compare_key(key) >= 0) &&
823
- (next_rec.compare_key(key) < 0)
824
- # The desired key is either an exact match for this_rec or is greater
825
- # than it but less than next_rec. If this is a non-leaf page, that
826
- # will mean that the record will fall on the leaf page this node
827
- # pointer record points to, if it exists at all.
828
- return this_rec
874
+ this_rec
829
875
  end
830
876
 
831
- this_rec = next_rec
832
- end
833
-
834
- this_rec
835
- end
836
-
837
- # Search or a record within a single page using the page directory to limit
838
- # the number of record comparisons required. Once the last page directory
839
- # entry closest to but not greater than the key is found, fall back to
840
- # linear search using linear_search_from_cursor to find the closest record
841
- # whose key is not greater than the desired key. (If an exact match is
842
- # desired, the returned record must be checked in the same way as the above
843
- # linear_search_from_cursor function.)
844
- def binary_search_by_directory(dir, key)
845
- Innodb::Stats.increment :binary_search_by_directory
846
-
847
- return nil if dir.empty?
848
-
849
- # Split the directory at the mid-point (using integer math, so the division
850
- # is rounding down). Retrieve the record that sits at the mid-point.
851
- mid = ((dir.size-1) / 2)
852
- rec = record(dir[mid])
853
-
854
- if Innodb.debug?
855
- puts "binary_search_by_directory: page=%i, level=%i, dir.size=%i, dir[%i]=(%s)" % [
856
- offset,
857
- level,
858
- dir.size,
859
- mid,
860
- rec.key_string,
861
- ]
862
- end
863
-
864
- # The mid-point record was the infimum record, which is not comparable with
865
- # compare_key, so we need to just linear scan from here. If the mid-point
866
- # is the beginning of the page there can't be many records left to check
867
- # anyway.
868
- if rec.header[:type] == :infimum
869
- return linear_search_from_cursor(record_cursor(rec.next), key)
870
- end
877
+ # Search or a record within a single page using the page directory to limit
878
+ # the number of record comparisons required. Once the last page directory
879
+ # entry closest to but not greater than the key is found, fall back to
880
+ # linear search using linear_search_from_cursor to find the closest record
881
+ # whose key is not greater than the desired key. (If an exact match is
882
+ # desired, the returned record must be checked in the same way as the above
883
+ # linear_search_from_cursor function.)
884
+ def binary_search_by_directory(dir, key)
885
+ Innodb::Stats.increment :binary_search_by_directory
886
+
887
+ return if dir.empty?
888
+
889
+ # Split the directory at the mid-point (using integer math, so the division
890
+ # is rounding down). Retrieve the record that sits at the mid-point.
891
+ mid = ((dir.size - 1) / 2)
892
+ rec = record(dir[mid])
893
+ return unless rec
894
+
895
+ if Innodb.debug?
896
+ puts "binary_search_by_directory: page=%i, level=%i, dir.size=%i, dir[%i]=(%s)" % [
897
+ offset,
898
+ level,
899
+ dir.size,
900
+ mid,
901
+ rec.key_string,
902
+ ]
903
+ end
871
904
 
872
- # Compare the desired key to the mid-point record's key.
873
- case rec.compare_key(key)
874
- when 0
875
- # An exact match for the key was found. Return the record.
876
- Innodb::Stats.increment :binary_search_by_directory_exact_match
877
- rec
878
- when +1
879
- # The mid-point record's key is less than the desired key.
880
- if dir.size > 2
881
- # There are more entries remaining from the directory, recurse again
882
- # using binary search on the right half of the directory, which
883
- # represents values greater than or equal to the mid-point record's
884
- # key.
885
- Innodb::Stats.increment :binary_search_by_directory_recurse_right
886
- binary_search_by_directory(dir[mid...dir.size], key)
887
- else
888
- next_rec = record(dir[mid+1])
889
- next_key = next_rec && next_rec.compare_key(key)
890
- if dir.size == 1 || next_key == -1 || next_key == 0
891
- # This is the last entry remaining from the directory, or our key is
892
- # greater than rec and less than rec+1's key. Use linear search to
893
- # find the record starting at rec.
894
- Innodb::Stats.increment :binary_search_by_directory_linear_search
895
- linear_search_from_cursor(record_cursor(rec.offset), key)
896
- elsif next_key == +1
897
- Innodb::Stats.increment :binary_search_by_directory_linear_search
898
- linear_search_from_cursor(record_cursor(next_rec.offset), key)
899
- else
900
- nil
905
+ # The mid-point record was the infimum record, which is not comparable with
906
+ # compare_key, so we need to just linear scan from here. If the mid-point
907
+ # is the beginning of the page there can't be many records left to check
908
+ # anyway.
909
+ return linear_search_from_cursor(record_cursor(rec.next), key) if rec.header.type == :infimum
910
+
911
+ # Compare the desired key to the mid-point record's key.
912
+ case rec.compare_key(key)
913
+ when 0
914
+ # An exact match for the key was found. Return the record.
915
+ Innodb::Stats.increment :binary_search_by_directory_exact_match
916
+ rec
917
+ when +1
918
+ # The mid-point record's key is less than the desired key.
919
+ if dir.size > 2
920
+ # There are more entries remaining from the directory, recurse again
921
+ # using binary search on the right half of the directory, which
922
+ # represents values greater than or equal to the mid-point record's
923
+ # key.
924
+ Innodb::Stats.increment :binary_search_by_directory_recurse_right
925
+ binary_search_by_directory(dir[mid...dir.size], key)
926
+ else
927
+ next_rec = record(dir[mid + 1])
928
+ next_key = next_rec&.compare_key(key)
929
+ if dir.size == 1 || next_key == -1 || next_key.zero?
930
+ # This is the last entry remaining from the directory, or our key is
931
+ # greater than rec and less than rec+1's key. Use linear search to
932
+ # find the record starting at rec.
933
+ Innodb::Stats.increment :binary_search_by_directory_linear_search
934
+ linear_search_from_cursor(record_cursor(rec.offset), key)
935
+ elsif next_key == +1
936
+ Innodb::Stats.increment :binary_search_by_directory_linear_search
937
+ linear_search_from_cursor(record_cursor(next_rec.offset), key)
938
+ end
939
+ end
940
+ when -1
941
+ # The mid-point record's key is greater than the desired key.
942
+ if dir.size == 1
943
+ # If this is the last entry remaining from the directory, we didn't
944
+ # find anything workable.
945
+ Innodb::Stats.increment :binary_search_by_directory_empty_result
946
+ nil
947
+ else
948
+ # Recurse on the left half of the directory, which represents values
949
+ # less than the mid-point record's key.
950
+ Innodb::Stats.increment :binary_search_by_directory_recurse_left
951
+ binary_search_by_directory(dir[0...mid], key)
952
+ end
901
953
  end
902
954
  end
903
- when -1
904
- # The mid-point record's key is greater than the desired key.
905
- if dir.size == 1
906
- # If this is the last entry remaining from the directory, we didn't
907
- # find anything workable.
908
- Innodb::Stats.increment :binary_search_by_directory_empty_result
909
- nil
910
- else
911
- # Recurse on the left half of the directory, which represents values
912
- # less than the mid-point record's key.
913
- Innodb::Stats.increment :binary_search_by_directory_recurse_left
914
- binary_search_by_directory(dir[0...mid], key)
915
- end
916
- end
917
- end
918
955
 
919
- # Iterate through all records.
920
- def each_record
921
- unless block_given?
922
- return enum_for(:each_record)
923
- end
956
+ # Iterate through all records.
957
+ def each_record
958
+ return enum_for(:each_record) unless block_given?
924
959
 
925
- c = record_cursor(:min)
960
+ c = record_cursor(:min)
926
961
 
927
- while rec = c.record
928
- yield rec
929
- end
930
-
931
- nil
932
- end
933
-
934
- # Iterate through all records in the garbage list.
935
- def each_garbage_record
936
- unless block_given?
937
- return enum_for(:each_garbage_record)
938
- end
939
-
940
- if garbage_offset == 0
941
- return nil
942
- end
962
+ while (rec = c.record)
963
+ yield rec
964
+ end
943
965
 
944
- c = record_cursor(garbage_offset)
966
+ nil
967
+ end
945
968
 
946
- while rec = c.record
947
- yield rec
948
- end
969
+ # Iterate through all records in the garbage list.
970
+ def each_garbage_record
971
+ return enum_for(:each_garbage_record) unless block_given?
972
+ return if garbage_offset.zero?
949
973
 
950
- nil
951
- end
974
+ c = record_cursor(garbage_offset)
952
975
 
953
- # Iterate through all child pages of a node (non-leaf) page, which are
954
- # stored as records with the child page number as the last field in the
955
- # record.
956
- def each_child_page
957
- return nil if level == 0
976
+ while (rec = c.record)
977
+ yield rec
978
+ end
958
979
 
959
- unless block_given?
960
- return enum_for(:each_child_page)
961
- end
980
+ nil
981
+ end
962
982
 
963
- each_record do |rec|
964
- yield rec.child_page_number, rec.key
965
- end
983
+ # Iterate through all child pages of a node (non-leaf) page, which are
984
+ # stored as records with the child page number as the last field in the
985
+ # record.
986
+ def each_child_page
987
+ return if leaf?
966
988
 
967
- nil
968
- end
989
+ return enum_for(:each_child_page) unless block_given?
969
990
 
970
- def each_region
971
- unless block_given?
972
- return enum_for(:each_region)
973
- end
991
+ each_record do |rec|
992
+ yield rec.child_page_number, rec.key
993
+ end
974
994
 
975
- super do |region|
976
- yield region
977
- end
995
+ nil
996
+ end
978
997
 
979
- yield({
980
- :offset => pos_index_header,
981
- :length => size_index_header,
982
- :name => :index_header,
983
- :info => "Index Header",
984
- })
985
-
986
- yield({
987
- :offset => pos_fseg_header,
988
- :length => size_fseg_header,
989
- :name => :fseg_header,
990
- :info => "File Segment Header",
991
- })
992
-
993
- yield({
994
- :offset => pos_infimum - 5,
995
- :length => size_mum_record + 5,
996
- :name => :infimum,
997
- :info => "Infimum",
998
- })
999
-
1000
- yield({
1001
- :offset => pos_supremum - 5,
1002
- :length => size_mum_record + 5,
1003
- :name => :supremum,
1004
- :info => "Supremum",
1005
- })
1006
-
1007
- directory_slots.times do |n|
1008
- yield({
1009
- :offset => pos_directory - (n * 2),
1010
- :length => 2,
1011
- :name => :directory,
1012
- :info => "Page Directory",
1013
- })
1014
- end
998
+ def each_region(&block)
999
+ return enum_for(:each_region) unless block_given?
1000
+
1001
+ super(&block)
1002
+
1003
+ yield Region.new(
1004
+ offset: pos_index_header,
1005
+ length: size_index_header,
1006
+ name: :index_header,
1007
+ info: "Index Header"
1008
+ )
1009
+
1010
+ yield Region.new(
1011
+ offset: pos_fseg_header,
1012
+ length: size_fseg_header,
1013
+ name: :fseg_header,
1014
+ info: "File Segment Header"
1015
+ )
1016
+
1017
+ yield Region.new(
1018
+ offset: pos_infimum - 5,
1019
+ length: size_mum_record + 5,
1020
+ name: :infimum,
1021
+ info: "Infimum"
1022
+ )
1023
+
1024
+ yield Region.new(
1025
+ offset: pos_supremum - 5,
1026
+ length: size_mum_record + 5,
1027
+ name: :supremum,
1028
+ info: "Supremum"
1029
+ )
1030
+
1031
+ directory_slots.times do |n|
1032
+ yield Region.new(
1033
+ offset: pos_directory - (n * 2),
1034
+ length: 2,
1035
+ name: :directory,
1036
+ info: "Page Directory"
1037
+ )
1038
+ end
1015
1039
 
1016
- each_garbage_record do |record|
1017
- yield({
1018
- :offset => record.offset - record.header[:length],
1019
- :length => record.length + record.header[:length],
1020
- :name => :garbage,
1021
- :info => "Garbage",
1022
- })
1023
- end
1040
+ each_garbage_record do |record|
1041
+ yield Region.new(
1042
+ offset: record.offset - record.header.length,
1043
+ length: record.length + record.header.length,
1044
+ name: :garbage,
1045
+ info: "Garbage"
1046
+ )
1047
+ end
1024
1048
 
1025
- each_record do |record|
1026
- yield({
1027
- :offset => record.offset - record.header[:length],
1028
- :length => record.header[:length],
1029
- :name => :record_header,
1030
- :info => "Record Header",
1031
- })
1032
-
1033
- yield({
1034
- :offset => record.offset,
1035
- :length => record.length || 1,
1036
- :name => :record_data,
1037
- :info => "Record Data",
1038
- })
1039
- end
1049
+ each_record do |record|
1050
+ yield Region.new(
1051
+ offset: record.offset - record.header.length,
1052
+ length: record.header.length,
1053
+ name: :record_header,
1054
+ info: "Record Header"
1055
+ )
1056
+
1057
+ yield Region.new(
1058
+ offset: record.offset,
1059
+ length: record.length || 1,
1060
+ name: :record_data,
1061
+ info: "Record Data"
1062
+ )
1063
+ end
1040
1064
 
1041
- nil
1042
- end
1065
+ nil
1066
+ end
1043
1067
 
1044
- # Dump the contents of a page for debugging purposes.
1045
- def dump
1046
- super
1047
-
1048
- puts "page header:"
1049
- pp page_header
1050
- puts
1051
-
1052
- puts "fseg header:"
1053
- pp fseg_header
1054
- puts
1055
-
1056
- puts "sizes:"
1057
- puts " %-15s%5i" % [ "header", header_space ]
1058
- puts " %-15s%5i" % [ "trailer", trailer_space ]
1059
- puts " %-15s%5i" % [ "directory", directory_space ]
1060
- puts " %-15s%5i" % [ "free", free_space ]
1061
- puts " %-15s%5i" % [ "used", used_space ]
1062
- puts " %-15s%5i" % [ "record", record_space ]
1063
- puts " %-15s%5.2f" % [
1064
- "per record",
1065
- (page_header[:n_recs] > 0) ? (record_space / page_header[:n_recs]) : 0
1066
- ]
1067
- puts
1068
-
1069
- puts "page directory:"
1070
- pp directory
1071
- puts
1072
-
1073
- puts "system records:"
1074
- pp infimum.record
1075
- pp supremum.record
1076
- puts
1077
-
1078
- puts "garbage records:"
1079
- each_garbage_record do |rec|
1080
- pp rec.record
1081
- puts
1082
- end
1083
- puts
1068
+ # Dump the contents of a page for debugging purposes.
1069
+ def dump
1070
+ super
1071
+
1072
+ puts "page header:"
1073
+ pp page_header
1074
+ puts
1075
+
1076
+ puts "fseg header:"
1077
+ pp fseg_header
1078
+ puts
1079
+
1080
+ puts "sizes:"
1081
+ puts " %-15s%5i" % ["header", header_space]
1082
+ puts " %-15s%5i" % ["trailer", trailer_space]
1083
+ puts " %-15s%5i" % ["directory", directory_space]
1084
+ puts " %-15s%5i" % ["free", free_space]
1085
+ puts " %-15s%5i" % ["used", used_space]
1086
+ puts " %-15s%5i" % ["record", record_space]
1087
+ puts " %-15s%5.2f" % ["per record", space_per_record]
1088
+ puts
1089
+
1090
+ puts "page directory:"
1091
+ pp directory
1092
+ puts
1093
+
1094
+ puts "system records:"
1095
+ pp infimum.record
1096
+ pp supremum.record
1097
+ puts
1098
+
1099
+ puts "garbage records:"
1100
+ each_garbage_record do |rec|
1101
+ pp rec.record
1102
+ puts
1103
+ end
1104
+ puts
1084
1105
 
1085
- puts "records:"
1086
- each_record do |rec|
1087
- pp rec.record
1088
- puts
1106
+ puts "records:"
1107
+ each_record do |rec|
1108
+ pp rec.record
1109
+ puts
1110
+ end
1111
+ puts
1112
+ end
1089
1113
  end
1090
- puts
1091
1114
  end
1092
1115
  end
1093
-
1094
- Innodb::Page::SPECIALIZED_CLASSES[:INDEX] = Innodb::Page::Index