innodb_ruby 0.9.14 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +5 -6
- data/bin/innodb_log +13 -18
- data/bin/innodb_space +654 -778
- data/lib/innodb/checksum.rb +26 -24
- data/lib/innodb/data_dictionary.rb +490 -550
- data/lib/innodb/data_type.rb +362 -325
- 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 +166 -124
- data/lib/innodb/list.rb +196 -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/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 +965 -924
- 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/page.rb +417 -414
- data/lib/innodb/record.rb +121 -164
- data/lib/innodb/record_describer.rb +66 -68
- data/lib/innodb/space.rb +381 -413
- 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
- data/lib/innodb.rb +4 -5
- metadata +100 -25
@@ -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,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
|
-
|
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
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
end
|
48
|
+
def deleted?
|
49
|
+
(info_flags & RECORD_INFO_DELETED_FLAG) != 0
|
50
|
+
end
|
51
|
+
end
|
163
52
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
182
|
+
# The size of the "fseg" header.
|
183
|
+
def size_fseg_header
|
184
|
+
2 * Innodb::FsegEntry::SIZE
|
185
|
+
end
|
185
186
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
210
|
+
# The size of the data from the supremum or infimum records.
|
211
|
+
def size_mum_record
|
212
|
+
8
|
213
|
+
end
|
201
214
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
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
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
263
|
+
# The number of directory slots in use.
|
264
|
+
def directory_slots
|
265
|
+
page_header[:n_dir_slots]
|
266
|
+
end
|
260
267
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
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
|
-
|
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
|
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
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
-
#
|
311
|
-
|
312
|
-
|
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
|
-
|
327
|
-
|
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
|
-
|
330
|
-
|
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
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
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
|
-
|
353
|
-
|
354
|
-
|
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
|
-
|
357
|
-
|
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
|
-
|
360
|
-
|
335
|
+
# A helper function to identify leaf index pages.
|
336
|
+
def leaf?
|
337
|
+
level.zero?
|
338
|
+
end
|
361
339
|
|
362
|
-
|
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
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
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
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
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
|
-
|
377
|
-
|
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
|
-
|
380
|
-
|
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
|
-
|
385
|
-
|
402
|
+
header
|
403
|
+
end
|
386
404
|
|
387
|
-
#
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
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
|
-
|
395
|
-
|
396
|
-
|
421
|
+
# Return an array indicating which fields are null.
|
422
|
+
def record_header_compact_null_bitmap(cursor)
|
423
|
+
fields = record_fields
|
397
424
|
|
398
|
-
|
399
|
-
|
425
|
+
# The number of bits in the bitmap is the number of nullable fields.
|
426
|
+
size = fields.count(&:nullable?)
|
400
427
|
|
401
|
-
|
402
|
-
|
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
|
-
|
425
|
-
|
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
|
-
|
430
|
-
|
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
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
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
|
-
|
451
|
-
|
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
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
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
|
-
|
471
|
-
|
472
|
-
@supremum ||= system_record(pos_supremum)
|
473
|
-
end
|
451
|
+
len = cursor.read_uint8
|
452
|
+
ext = false
|
474
453
|
|
475
|
-
|
476
|
-
|
477
|
-
|
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
|
-
|
480
|
-
|
461
|
+
lengths[f.name] = len
|
462
|
+
externs << f.name if ext
|
463
|
+
end
|
481
464
|
|
482
|
-
|
483
|
-
|
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
|
-
|
490
|
-
|
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
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
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
|
-
|
499
|
-
|
500
|
-
|
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
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
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
|
-
|
511
|
-
|
512
|
-
|
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
|
-
|
519
|
-
|
543
|
+
# Return the supremum record on a page.
|
544
|
+
def supremum
|
545
|
+
@supremum ||= system_record(pos_supremum)
|
546
|
+
end
|
520
547
|
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
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
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
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
|
-
|
536
|
-
|
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
|
-
#
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
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
|
576
|
-
#
|
577
|
-
if
|
578
|
-
|
579
|
-
|
580
|
-
|
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
|
-
|
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
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
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
|
-
|
597
|
-
|
598
|
-
|
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
|
-
|
601
|
-
|
602
|
-
|
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
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
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
|
-
|
612
|
-
|
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
|
-
|
615
|
-
|
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
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
end
|
689
|
+
while (rec = search_cursor.record)
|
690
|
+
slot = record_directory_slot(rec)
|
691
|
+
return slot if slot
|
692
|
+
end
|
625
693
|
|
626
|
-
|
627
|
-
|
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
|
-
|
635
|
-
|
636
|
-
end
|
697
|
+
def each_directory_offset
|
698
|
+
return enum_for(:each_directory_offset) unless block_given?
|
637
699
|
|
638
|
-
|
639
|
-
|
640
|
-
|
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
|
-
|
645
|
-
|
705
|
+
def each_directory_record
|
706
|
+
return enum_for(:each_directory_record) unless block_given?
|
646
707
|
|
647
|
-
|
648
|
-
|
649
|
-
|
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
|
-
|
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
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
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
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
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
|
-
|
689
|
-
|
690
|
-
|
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
|
-
|
693
|
-
raise "Couldn't position search cursor"
|
694
|
-
end
|
741
|
+
rec = @page.record(@record.next)
|
695
742
|
|
696
|
-
|
697
|
-
|
698
|
-
if rec == @page.
|
699
|
-
|
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
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
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
|
-
|
714
|
-
|
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
|
-
|
722
|
-
|
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
|
-
|
728
|
-
|
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
|
-
|
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
|
-
|
739
|
-
|
740
|
-
|
741
|
-
end
|
742
|
-
end
|
769
|
+
return @record = rec
|
770
|
+
end
|
771
|
+
end
|
743
772
|
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
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
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
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
|
-
|
765
|
-
|
766
|
-
|
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
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
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
|
-
#
|
796
|
-
|
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
|
-
|
799
|
-
|
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
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
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
|
-
|
812
|
-
|
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
|
-
|
815
|
-
|
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
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
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
|
-
|
845
|
-
|
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
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
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
|
-
|
900
|
-
|
901
|
-
unless block_given?
|
902
|
-
return enum_for(:each_record)
|
903
|
-
end
|
874
|
+
this_rec
|
875
|
+
end
|
904
876
|
|
905
|
-
|
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
|
-
|
908
|
-
|
909
|
-
|
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
|
-
|
912
|
-
|
956
|
+
# Iterate through all records.
|
957
|
+
def each_record
|
958
|
+
return enum_for(:each_record) unless block_given?
|
913
959
|
|
914
|
-
|
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
|
-
|
921
|
-
|
922
|
-
|
962
|
+
while (rec = c.record)
|
963
|
+
yield rec
|
964
|
+
end
|
923
965
|
|
924
|
-
|
966
|
+
nil
|
967
|
+
end
|
925
968
|
|
926
|
-
|
927
|
-
|
928
|
-
|
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
|
-
|
931
|
-
end
|
974
|
+
c = record_cursor(garbage_offset)
|
932
975
|
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
def each_child_page
|
937
|
-
return nil if level == 0
|
976
|
+
while (rec = c.record)
|
977
|
+
yield rec
|
978
|
+
end
|
938
979
|
|
939
|
-
|
940
|
-
|
941
|
-
end
|
980
|
+
nil
|
981
|
+
end
|
942
982
|
|
943
|
-
|
944
|
-
|
945
|
-
|
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
|
-
|
948
|
-
end
|
989
|
+
return enum_for(:each_child_page) unless block_given?
|
949
990
|
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
end
|
991
|
+
each_record do |rec|
|
992
|
+
yield rec.child_page_number, rec.key
|
993
|
+
end
|
954
994
|
|
955
|
-
|
956
|
-
|
957
|
-
end
|
995
|
+
nil
|
996
|
+
end
|
958
997
|
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
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
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
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
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
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
|
-
|
1022
|
-
|
1065
|
+
nil
|
1066
|
+
end
|
1023
1067
|
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
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
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
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
|