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