innodb_ruby 0.9.14 → 0.12.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 +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +13 -18
  4. data/bin/innodb_space +654 -778
  5. data/lib/innodb/checksum.rb +26 -24
  6. data/lib/innodb/data_dictionary.rb +490 -550
  7. data/lib/innodb/data_type.rb +362 -325
  8. data/lib/innodb/field.rb +102 -89
  9. data/lib/innodb/fseg_entry.rb +22 -26
  10. data/lib/innodb/history.rb +21 -21
  11. data/lib/innodb/history_list.rb +72 -76
  12. data/lib/innodb/ibuf_bitmap.rb +36 -36
  13. data/lib/innodb/ibuf_index.rb +6 -2
  14. data/lib/innodb/index.rb +245 -276
  15. data/lib/innodb/inode.rb +166 -124
  16. data/lib/innodb/list.rb +196 -183
  17. data/lib/innodb/log.rb +139 -110
  18. data/lib/innodb/log_block.rb +100 -91
  19. data/lib/innodb/log_group.rb +53 -64
  20. data/lib/innodb/log_reader.rb +97 -96
  21. data/lib/innodb/log_record.rb +328 -279
  22. data/lib/innodb/lsn.rb +86 -81
  23. data/lib/innodb/page/blob.rb +82 -83
  24. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  25. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  26. data/lib/innodb/page/index.rb +965 -924
  27. data/lib/innodb/page/index_compressed.rb +34 -34
  28. data/lib/innodb/page/inode.rb +103 -112
  29. data/lib/innodb/page/sys.rb +13 -15
  30. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  31. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  32. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  33. data/lib/innodb/page/trx_sys.rb +204 -182
  34. data/lib/innodb/page/undo_log.rb +106 -92
  35. data/lib/innodb/page.rb +417 -414
  36. data/lib/innodb/record.rb +121 -164
  37. data/lib/innodb/record_describer.rb +66 -68
  38. data/lib/innodb/space.rb +381 -413
  39. data/lib/innodb/stats.rb +33 -35
  40. data/lib/innodb/system.rb +149 -171
  41. data/lib/innodb/undo_log.rb +129 -107
  42. data/lib/innodb/undo_record.rb +255 -247
  43. data/lib/innodb/util/buffer_cursor.rb +81 -79
  44. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  45. data/lib/innodb/version.rb +2 -2
  46. data/lib/innodb/xdes.rb +144 -142
  47. data/lib/innodb.rb +4 -5
  48. metadata +100 -25
@@ -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,1065 +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
89
-
90
- # The size of the "fseg" header.
91
- def size_fseg_header
92
- 2 * Innodb::FsegEntry::SIZE
93
- end
94
-
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
104
-
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
117
-
118
- # The size of the data from the supremum or infimum records.
119
- def size_mum_record
120
- 8
121
- end
122
-
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
132
-
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
143
-
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
151
-
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
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
157
47
 
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
48
+ def deleted?
49
+ (info_flags & RECORD_INFO_DELETED_FLAG) != 0
50
+ end
51
+ end
163
52
 
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
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
170
170
 
171
- # The number of directory slots in use.
172
- def directory_slots
173
- page_header[:n_dir_slots]
174
- 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
175
175
 
176
- # The amount of space consumed by the page directory.
177
- def directory_space
178
- directory_slots * PAGE_DIR_SLOT_SIZE
179
- 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
180
181
 
181
- # The amount of space consumed by the trailers in the page.
182
- def trailer_space
183
- size_fil_trailer
184
- end
182
+ # The size of the "fseg" header.
183
+ def size_fseg_header
184
+ 2 * Innodb::FsegEntry::SIZE
185
+ end
185
186
 
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
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
191
196
 
192
- # Return the amount of used space in the page.
193
- def used_space
194
- size - free_space
195
- 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
196
209
 
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
210
+ # The size of the data from the supremum or infimum records.
211
+ def size_mum_record
212
+ 8
213
+ end
201
214
 
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
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
207
224
 
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
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
233
235
 
234
- # A helper function to return the index id.
235
- def index_id
236
- page_header && page_header[:index_id]
237
- 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
238
243
 
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
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
244
249
 
245
- # A helper function to return the number of records.
246
- def records
247
- page_header && page_header[:n_recs]
248
- 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
249
255
 
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
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
255
262
 
256
- # A helper function to identify leaf index pages.
257
- def leaf?
258
- level == 0
259
- end
263
+ # The number of directory slots in use.
264
+ def directory_slots
265
+ page_header[:n_dir_slots]
266
+ end
260
267
 
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
268
+ # The amount of space consumed by the page directory.
269
+ def directory_space
270
+ directory_slots * PAGE_DIR_SLOT_SIZE
271
+ end
265
272
 
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
273
+ # The amount of space consumed by the trailers in the page.
274
+ def trailer_space
275
+ size_fil_trailer
276
+ end
279
277
 
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
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])
308
282
  end
309
283
 
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)
284
+ # Return the amount of used space in the page.
285
+ def used_space
286
+ size - free_space
324
287
  end
325
288
 
326
- header[:length] = origin - cursor.position
327
- 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
328
293
 
329
- header
330
- 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
331
298
 
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
- }
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
348
322
  end
349
- end
350
- end
351
323
 
352
- # Return an array indicating which fields are null.
353
- def record_header_compact_null_bitmap(cursor)
354
- fields = record_fields
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
355
328
 
356
- # The number of bits in the bitmap is the number of nullable fields.
357
- size = fields.count { |f| f.nullable? }
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
358
334
 
359
- # There is no bitmap if there are no nullable fields.
360
- return [] unless size > 0
335
+ # A helper function to identify leaf index pages.
336
+ def leaf?
337
+ level.zero?
338
+ end
361
339
 
362
- null_bit_array = cursor.get_bit_array(size).reverse!
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
343
+ end
363
344
 
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
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
353
+ end
370
354
 
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])
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
375
384
 
376
- lengths = {}
377
- externs = []
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
378
398
 
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)
399
+ header.length = origin - cursor.position
400
+ end
383
401
 
384
- len = cursor.get_uint8
385
- ext = false
402
+ header
403
+ end
386
404
 
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
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
392
419
  end
393
420
 
394
- lengths[f.name] = len
395
- externs << f.name if ext
396
- end
421
+ # Return an array indicating which fields are null.
422
+ def record_header_compact_null_bitmap(cursor)
423
+ fields = record_fields
397
424
 
398
- return lengths, externs
399
- end
425
+ # The number of bits in the bitmap is the number of nullable fields.
426
+ size = fields.count(&:nullable?)
400
427
 
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)
420
- end
421
- this_field_offset = next_field_offset
422
- end
428
+ # There is no bitmap if there are no nullable fields.
429
+ return [] unless size.positive?
423
430
 
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] = {}, [], []
431
+ # TODO: This is really ugly.
432
+ null_bit_array = cursor.read_bit_array(size).reverse!
428
433
 
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]
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)
433
436
  end
434
- else
435
- header[:lengths], header[:nulls], header[:externs] = lengths, nulls, externs
436
- end
437
- end
438
437
 
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
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])
449
442
 
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
443
+ lengths = {}
444
+ externs = []
464
445
 
465
- # Return the infimum record on a page.
466
- def infimum
467
- @infimum ||= system_record(pos_infimum)
468
- end
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)
469
450
 
470
- # Return the supremum record on a page.
471
- def supremum
472
- @supremum ||= system_record(pos_supremum)
473
- end
451
+ len = cursor.read_uint8
452
+ ext = false
474
453
 
475
- def record_describer=(o)
476
- @record_describer = o
477
- 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
478
460
 
479
- def record_describer
480
- return @record_describer if @record_describer
461
+ lengths[f.name] = len
462
+ externs << f.name if ext
463
+ end
481
464
 
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
465
+ [lengths, externs]
466
+ end
488
467
 
489
- @record_describer
490
- end
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
491
492
 
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 => []}
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
510
+ end
497
511
 
498
- description[:key].each do |field|
499
- fields[:key] << Innodb::Field.new(position.next, field[:name], *field[:type])
500
- 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
501
519
 
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)
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
507
536
  end
508
- end
509
537
 
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])
538
+ # Return the infimum record on a page.
539
+ def infimum
540
+ @infimum ||= system_record(pos_infimum)
515
541
  end
516
- end
517
542
 
518
- fields
519
- end
543
+ # Return the supremum record on a page.
544
+ def supremum
545
+ @supremum ||= system_record(pos_supremum)
546
+ end
520
547
 
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
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
527
555
 
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
556
+ def record_describer
557
+ @record_describer ||= make_record_describer
558
+ end
559
+
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: [] }
534
565
 
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
566
+ description[:key].each do |field|
567
+ fields[:key] << Innodb::Field.new(position.next, field[:name], *field[:type])
560
568
  end
561
569
 
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? }
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)
572
575
  end
573
576
  end
574
577
 
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 }
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
581
584
  end
582
585
 
583
- this_record[:length] = c.position - offset
586
+ fields
587
+ end
588
+
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
592
+ end
584
593
 
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]
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
598
+
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
592
654
  end
655
+
656
+ Innodb::Record.new(self, this_record)
593
657
  end
594
658
  end
595
659
 
596
- Innodb::Record.new(self, this_record)
597
- end
598
- end
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 } }
664
+ end
665
+ end
599
666
 
600
- # Return an array of row offsets for all entries in the page directory.
601
- def directory
602
- return @directory if @directory
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
603
672
 
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 }
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)
608
677
  end
609
- end
610
678
 
611
- @directory
612
- 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
613
685
 
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
686
+ search_cursor = record_cursor(this_record.next)
687
+ raise "Could not position cursor" unless search_cursor
619
688
 
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
689
+ while (rec = search_cursor.record)
690
+ slot = record_directory_slot(rec)
691
+ return slot if slot
692
+ end
625
693
 
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
694
+ record_directory_slot(supremum)
695
+ end
633
696
 
634
- unless search_cursor = record_cursor(this_record.next)
635
- raise "Couldn't position cursor"
636
- end
697
+ def each_directory_offset
698
+ return enum_for(:each_directory_offset) unless block_given?
637
699
 
638
- while rec = search_cursor.record
639
- if slot = record_is_directory_slot?(rec)
640
- return slot
700
+ directory.each do |offset|
701
+ yield offset unless [pos_infimum, pos_supremum].include?(offset)
702
+ end
641
703
  end
642
- end
643
704
 
644
- return record_is_directory_slot?(supremum)
645
- end
705
+ def each_directory_record
706
+ return enum_for(:each_directory_record) unless block_given?
646
707
 
647
- # A class for cursoring through records starting from an arbitrary point.
648
- class RecordCursor
649
- def initialize(page, offset, direction)
650
- Innodb::Stats.increment :page_record_cursor_create
651
-
652
- @initial = true
653
- @page = page
654
- @direction = direction
655
- case offset
656
- when :min
657
- @record = @page.min_record
658
- when :max
659
- @record = @page.max_record
660
- else
661
- # Offset is a byte offset of a record (hopefully).
662
- @record = @page.record(offset)
708
+ each_directory_offset do |offset|
709
+ yield record(offset)
710
+ end
663
711
  end
664
- end
665
-
666
- # Return the next record, and advance the cursor. Return nil when the
667
- # end of records (supremum) is reached.
668
- def next_record
669
- Innodb::Stats.increment :page_record_cursor_next_record
670
712
 
671
- rec = @page.record(@record.next)
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
672
717
 
673
- # The garbage record list's end is self-linked, so we must check for
674
- # both supremum and the current record's offset.
675
- if rec == @page.supremum || rec.offset == @record.offset
676
- # We've reached the end of the linked list at supremum.
677
- nil
678
- else
679
- @record = rec
680
- end
681
- end
718
+ @initial = true
719
+ @page = page
720
+ @direction = direction
721
+ @record = initial_record(offset)
722
+ end
682
723
 
683
- # Return the previous record, and advance the cursor. Return nil when the
684
- # end of records (infimum) is reached.
685
- def prev_record
686
- Innodb::Stats.increment :page_record_cursor_prev_record
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
687
735
 
688
- unless slot = @page.directory_slot_for_record(@record)
689
- raise "Couldn't find slot for record"
690
- 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
691
740
 
692
- unless search_cursor = @page.record_cursor(@page.directory[slot-1])
693
- raise "Couldn't position search cursor"
694
- end
741
+ rec = @page.record(@record.next)
695
742
 
696
- while rec = search_cursor.record and rec.offset != @record.offset
697
- if rec.next == @record.offset
698
- if rec == @page.infimum
699
- return nil
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
700
750
  end
701
- return @record = rec
702
751
  end
703
- end
704
- end
705
752
 
706
- # Return the next record in the order defined when the cursor was created.
707
- def record
708
- if @initial
709
- @initial = false
710
- return @record
711
- 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
712
757
 
713
- case @direction
714
- when :forward
715
- next_record
716
- when :backward
717
- prev_record
718
- end
719
- end
758
+ slot = @page.directory_slot_for_record(@record)
759
+ raise "Could not find slot for record" unless slot
720
760
 
721
- # Iterate through all records in the cursor.
722
- def each_record
723
- unless block_given?
724
- return enum_for(:each_record)
725
- end
761
+ search_cursor = @page.record_cursor(@page.directory[slot - 1])
762
+ raise "Could not position search cursor" unless search_cursor
726
763
 
727
- while rec = record
728
- yield rec
729
- end
730
- end
731
- end
764
+ while (rec = search_cursor.record) && rec.offset != @record.offset
765
+ next unless rec.next == @record.offset
732
766
 
733
- # Return a RecordCursor starting at offset.
734
- def record_cursor(offset=:min, direction=:forward)
735
- RecordCursor.new(self, offset, direction)
736
- end
767
+ return if rec == @page.infimum
737
768
 
738
- def record_if_exists(offset)
739
- each_record do |rec|
740
- return rec if rec.offset == offset
741
- end
742
- end
769
+ return @record = rec
770
+ end
771
+ end
743
772
 
744
- # Return the minimum record on this page.
745
- def min_record
746
- min = record(infimum.next)
747
- min if min != supremum
748
- 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
749
779
 
750
- # Return the maximum record on this page.
751
- def max_record
752
- # Since the records are only singly-linked in the forward direction, in
753
- # order to do find the last record, we must create a cursor and walk
754
- # backwards one step.
755
- unless max_cursor = record_cursor(supremum.offset, :backward)
756
- raise "Couldn't position cursor"
757
- end
758
- # Note the deliberate use of prev_record rather than record; we want
759
- # to skip over supremum itself.
760
- max = max_cursor.prev_record
761
- max if max != infimum
762
- end
780
+ case @direction
781
+ when :forward
782
+ next_record
783
+ when :backward
784
+ prev_record
785
+ end
786
+ end
763
787
 
764
- # Search for a record within a single page, and return either a perfect
765
- # match for the key, or the last record closest to they key but not greater
766
- # than the key. (If an exact match is desired, compare_key must be used to
767
- # check if the returned record matches. This makes the function useful for
768
- # search in both leaf and non-leaf pages.)
769
- def linear_search_from_cursor(search_cursor, key)
770
- Innodb::Stats.increment :linear_search_from_cursor
771
-
772
- this_rec = search_cursor.record
773
-
774
- if Innodb.debug?
775
- puts "linear_search_from_cursor: page=%i, level=%i, start=(%s)" % [
776
- offset,
777
- level,
778
- this_rec && this_rec.key_string,
779
- ]
780
- end
788
+ # Iterate through all records in the cursor.
789
+ def each_record
790
+ return enum_for(:each_record) unless block_given?
781
791
 
782
- # Iterate through all records until finding either a matching record or
783
- # one whose key is greater than the desired key.
784
- while this_rec && next_rec = search_cursor.record
785
- Innodb::Stats.increment :linear_search_from_cursor_record_scans
786
-
787
- if Innodb.debug?
788
- puts "linear_search_from_cursor: page=%i, level=%i, current=(%s)" % [
789
- offset,
790
- level,
791
- this_rec && this_rec.key_string,
792
- ]
792
+ while (rec = record)
793
+ yield rec
794
+ end
795
+ end
793
796
  end
794
797
 
795
- # If we reach supremum, return the last non-system record we got.
796
- return this_rec if next_rec.header[:type] == :supremum
798
+ # Return a RecordCursor starting at offset.
799
+ def record_cursor(offset = :min, direction = :forward)
800
+ RecordCursor.new(self, offset, direction)
801
+ end
797
802
 
798
- if this_rec.compare_key(key) < 0
799
- return this_rec
803
+ def record_if_exists(offset)
804
+ each_record do |rec|
805
+ return rec if rec.offset == offset
806
+ end
800
807
  end
801
808
 
802
- if (this_rec.compare_key(key) >= 0) &&
803
- (next_rec.compare_key(key) < 0)
804
- # The desired key is either an exact match for this_rec or is greater
805
- # than it but less than next_rec. If this is a non-leaf page, that
806
- # will mean that the record will fall on the leaf page this node
807
- # pointer record points to, if it exists at all.
808
- return this_rec
809
+ # Return the minimum record on this page.
810
+ def min_record
811
+ min = record(infimum.next)
812
+ min if min != supremum
809
813
  end
810
814
 
811
- this_rec = next_rec
812
- end
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
827
+ end
813
828
 
814
- this_rec
815
- 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
816
846
 
817
- # Search or a record within a single page using the page directory to limit
818
- # the number of record comparisons required. Once the last page directory
819
- # entry closest to but not greater than the key is found, fall back to
820
- # linear search using linear_search_from_cursor to find the closest record
821
- # whose key is not greater than the desired key. (If an exact match is
822
- # desired, the returned record must be checked in the same way as the above
823
- # linear_search_from_cursor function.)
824
- def binary_search_by_directory(dir, key)
825
- Innodb::Stats.increment :binary_search_by_directory
826
-
827
- return nil if dir.empty?
828
-
829
- # Split the directory at the mid-point (using integer math, so the division
830
- # is rounding down). Retrieve the record that sits at the mid-point.
831
- mid = ((dir.size-1) / 2)
832
- rec = record(dir[mid])
833
-
834
- if Innodb.debug?
835
- puts "binary_search_by_directory: page=%i, level=%i, dir.size=%i, dir[%i]=(%s)" % [
836
- offset,
837
- level,
838
- dir.size,
839
- mid,
840
- rec.key_string,
841
- ]
842
- 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
843
859
 
844
- # The mid-point record was the infimum record, which is not comparable with
845
- # compare_key, so we need to just linear scan from here. If the mid-point
846
- # is the beginning of the page there can't be many records left to check
847
- # anyway.
848
- if rec.header[:type] == :infimum
849
- return linear_search_from_cursor(record_cursor(rec.next), key)
850
- end
860
+ # If we reach supremum, return the last non-system record we got.
861
+ return this_rec if next_rec.header.type == :supremum
851
862
 
852
- # Compare the desired key to the mid-point record's key.
853
- case rec.compare_key(key)
854
- when 0
855
- # An exact match for the key was found. Return the record.
856
- Innodb::Stats.increment :binary_search_by_directory_exact_match
857
- rec
858
- when +1
859
- # The mid-point record's key is less than the desired key.
860
- if dir.size > 2
861
- # There are more entries remaining from the directory, recurse again
862
- # using binary search on the right half of the directory, which
863
- # represents values greater than or equal to the mid-point record's
864
- # key.
865
- Innodb::Stats.increment :binary_search_by_directory_recurse_right
866
- binary_search_by_directory(dir[mid...dir.size], key)
867
- else
868
- next_rec = record(dir[mid+1])
869
- next_key = next_rec && next_rec.compare_key(key)
870
- if dir.size == 1 || next_key == -1 || next_key == 0
871
- # This is the last entry remaining from the directory, or our key is
872
- # greater than rec and less than rec+1's key. Use linear search to
873
- # find the record starting at rec.
874
- Innodb::Stats.increment :binary_search_by_directory_linear_search
875
- linear_search_from_cursor(record_cursor(rec.offset), key)
876
- elsif next_key == +1
877
- Innodb::Stats.increment :binary_search_by_directory_linear_search
878
- linear_search_from_cursor(record_cursor(next_rec.offset), key)
879
- else
880
- nil
863
+ return this_rec if this_rec.compare_key(key).negative?
864
+
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?
870
+
871
+ this_rec = next_rec
881
872
  end
882
- end
883
- when -1
884
- # The mid-point record's key is greater than the desired key.
885
- if dir.size == 1
886
- # If this is the last entry remaining from the directory, we didn't
887
- # find anything workable.
888
- Innodb::Stats.increment :binary_search_by_directory_empty_result
889
- nil
890
- else
891
- # Recurse on the left half of the directory, which represents values
892
- # less than the mid-point record's key.
893
- Innodb::Stats.increment :binary_search_by_directory_recurse_left
894
- binary_search_by_directory(dir[0...mid], key)
895
- end
896
- end
897
- end
898
873
 
899
- # Iterate through all records.
900
- def each_record
901
- unless block_given?
902
- return enum_for(:each_record)
903
- end
874
+ this_rec
875
+ end
904
876
 
905
- c = record_cursor(:min)
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
906
904
 
907
- while rec = c.record
908
- yield rec
909
- end
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
953
+ end
954
+ end
910
955
 
911
- nil
912
- end
956
+ # Iterate through all records.
957
+ def each_record
958
+ return enum_for(:each_record) unless block_given?
913
959
 
914
- # Iterate through all records in the garbage list.
915
- def each_garbage_record
916
- unless block_given?
917
- return enum_for(:each_garbage_record)
918
- end
960
+ c = record_cursor(:min)
919
961
 
920
- if garbage_offset == 0
921
- return nil
922
- end
962
+ while (rec = c.record)
963
+ yield rec
964
+ end
923
965
 
924
- c = record_cursor(garbage_offset)
966
+ nil
967
+ end
925
968
 
926
- while rec = c.record
927
- yield rec
928
- 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?
929
973
 
930
- nil
931
- end
974
+ c = record_cursor(garbage_offset)
932
975
 
933
- # Iterate through all child pages of a node (non-leaf) page, which are
934
- # stored as records with the child page number as the last field in the
935
- # record.
936
- def each_child_page
937
- return nil if level == 0
976
+ while (rec = c.record)
977
+ yield rec
978
+ end
938
979
 
939
- unless block_given?
940
- return enum_for(:each_child_page)
941
- end
980
+ nil
981
+ end
942
982
 
943
- each_record do |rec|
944
- yield rec.child_page_number, rec.key
945
- 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?
946
988
 
947
- nil
948
- end
989
+ return enum_for(:each_child_page) unless block_given?
949
990
 
950
- def each_region
951
- unless block_given?
952
- return enum_for(:each_region)
953
- end
991
+ each_record do |rec|
992
+ yield rec.child_page_number, rec.key
993
+ end
954
994
 
955
- super do |region|
956
- yield region
957
- end
995
+ nil
996
+ end
958
997
 
959
- yield({
960
- :offset => pos_index_header,
961
- :length => size_index_header,
962
- :name => :index_header,
963
- :info => "Index Header",
964
- })
965
-
966
- yield({
967
- :offset => pos_fseg_header,
968
- :length => size_fseg_header,
969
- :name => :fseg_header,
970
- :info => "File Segment Header",
971
- })
972
-
973
- yield({
974
- :offset => pos_infimum - 5,
975
- :length => size_mum_record + 5,
976
- :name => :infimum,
977
- :info => "Infimum",
978
- })
979
-
980
- yield({
981
- :offset => pos_supremum - 5,
982
- :length => size_mum_record + 5,
983
- :name => :supremum,
984
- :info => "Supremum",
985
- })
986
-
987
- directory_slots.times do |n|
988
- yield({
989
- :offset => pos_directory - (n * 2),
990
- :length => 2,
991
- :name => :directory,
992
- :info => "Page Directory",
993
- })
994
- 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
995
1039
 
996
- each_garbage_record do |record|
997
- yield({
998
- :offset => record.offset - record.header[:length],
999
- :length => record.length + record.header[:length],
1000
- :name => :garbage,
1001
- :info => "Garbage",
1002
- })
1003
- 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
1004
1048
 
1005
- each_record do |record|
1006
- yield({
1007
- :offset => record.offset - record.header[:length],
1008
- :length => record.header[:length],
1009
- :name => :record_header,
1010
- :info => "Record Header",
1011
- })
1012
-
1013
- yield({
1014
- :offset => record.offset,
1015
- :length => record.length || 1,
1016
- :name => :record_data,
1017
- :info => "Record Data",
1018
- })
1019
- 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
1020
1064
 
1021
- nil
1022
- end
1065
+ nil
1066
+ end
1023
1067
 
1024
- # Dump the contents of a page for debugging purposes.
1025
- def dump
1026
- super
1027
-
1028
- puts "page header:"
1029
- pp page_header
1030
- puts
1031
-
1032
- puts "fseg header:"
1033
- pp fseg_header
1034
- puts
1035
-
1036
- puts "sizes:"
1037
- puts " %-15s%5i" % [ "header", header_space ]
1038
- puts " %-15s%5i" % [ "trailer", trailer_space ]
1039
- puts " %-15s%5i" % [ "directory", directory_space ]
1040
- puts " %-15s%5i" % [ "free", free_space ]
1041
- puts " %-15s%5i" % [ "used", used_space ]
1042
- puts " %-15s%5i" % [ "record", record_space ]
1043
- puts " %-15s%5.2f" % [
1044
- "per record",
1045
- (page_header[:n_recs] > 0) ? (record_space / page_header[:n_recs]) : 0
1046
- ]
1047
- puts
1048
-
1049
- puts "page directory:"
1050
- pp directory
1051
- puts
1052
-
1053
- puts "system records:"
1054
- pp infimum.record
1055
- pp supremum.record
1056
- puts
1057
-
1058
- puts "garbage records:"
1059
- each_garbage_record do |rec|
1060
- pp rec.record
1061
- puts
1062
- end
1063
- 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
1064
1105
 
1065
- puts "records:"
1066
- each_record do |rec|
1067
- pp rec.record
1068
- puts
1106
+ puts "records:"
1107
+ each_record do |rec|
1108
+ pp rec.record
1109
+ puts
1110
+ end
1111
+ puts
1112
+ end
1069
1113
  end
1070
- puts
1071
1114
  end
1072
1115
  end
1073
-
1074
- Innodb::Page::SPECIALIZED_CLASSES[:INDEX] = Innodb::Page::Index