innodb_ruby 0.9.13 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +14 -19
  4. data/bin/innodb_space +592 -745
  5. data/lib/innodb.rb +5 -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 -325
  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 -275
  16. data/lib/innodb/inode.rb +166 -124
  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 +446 -291
  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 +965 -924
  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 -164
  38. data/lib/innodb/record_describer.rb +66 -68
  39. data/lib/innodb/space.rb +386 -391
  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 +112 -21
@@ -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