innodb_ruby 0.9.14 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +13 -18
  4. data/bin/innodb_space +654 -778
  5. data/lib/innodb/checksum.rb +26 -24
  6. data/lib/innodb/data_dictionary.rb +490 -550
  7. data/lib/innodb/data_type.rb +362 -325
  8. data/lib/innodb/field.rb +102 -89
  9. data/lib/innodb/fseg_entry.rb +22 -26
  10. data/lib/innodb/history.rb +21 -21
  11. data/lib/innodb/history_list.rb +72 -76
  12. data/lib/innodb/ibuf_bitmap.rb +36 -36
  13. data/lib/innodb/ibuf_index.rb +6 -2
  14. data/lib/innodb/index.rb +245 -276
  15. data/lib/innodb/inode.rb +166 -124
  16. data/lib/innodb/list.rb +196 -183
  17. data/lib/innodb/log.rb +139 -110
  18. data/lib/innodb/log_block.rb +100 -91
  19. data/lib/innodb/log_group.rb +53 -64
  20. data/lib/innodb/log_reader.rb +97 -96
  21. data/lib/innodb/log_record.rb +328 -279
  22. data/lib/innodb/lsn.rb +86 -81
  23. data/lib/innodb/page/blob.rb +82 -83
  24. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  25. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  26. data/lib/innodb/page/index.rb +965 -924
  27. data/lib/innodb/page/index_compressed.rb +34 -34
  28. data/lib/innodb/page/inode.rb +103 -112
  29. data/lib/innodb/page/sys.rb +13 -15
  30. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  31. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  32. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  33. data/lib/innodb/page/trx_sys.rb +204 -182
  34. data/lib/innodb/page/undo_log.rb +106 -92
  35. data/lib/innodb/page.rb +417 -414
  36. data/lib/innodb/record.rb +121 -164
  37. data/lib/innodb/record_describer.rb +66 -68
  38. data/lib/innodb/space.rb +381 -413
  39. data/lib/innodb/stats.rb +33 -35
  40. data/lib/innodb/system.rb +149 -171
  41. data/lib/innodb/undo_log.rb +129 -107
  42. data/lib/innodb/undo_record.rb +255 -247
  43. data/lib/innodb/util/buffer_cursor.rb +81 -79
  44. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  45. data/lib/innodb/version.rb +2 -2
  46. data/lib/innodb/xdes.rb +144 -142
  47. data/lib/innodb.rb +4 -5
  48. metadata +100 -25
@@ -1,49 +1,49 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
- class Innodb::IbufBitmap
4
- BITS_PER_PAGE = 4
3
+ module Innodb
4
+ class IbufBitmap
5
+ PageStatus = Struct.new(:free, :buffered, :ibuf, keyword_init: true)
5
6
 
6
- BITMAP_BV_FREE = 1 + 2
7
- BITMAP_BV_BUFFERED = 4
8
- BITMAP_BV_IBUF = 8
7
+ BITS_PER_PAGE = 4
9
8
 
10
- BITMAP_BV_ALL =
11
- BITMAP_BV_FREE |
12
- BITMAP_BV_BUFFERED |
13
- BITMAP_BV_IBUF
9
+ BITMAP_BV_FREE = 1 + 2
10
+ BITMAP_BV_BUFFERED = 4
11
+ BITMAP_BV_IBUF = 8
14
12
 
15
- def initialize(page, cursor)
16
- @page = page
17
- @bitmap = read_bitmap(page, cursor)
18
- end
13
+ BITMAP_BV_ALL =
14
+ BITMAP_BV_FREE |
15
+ BITMAP_BV_BUFFERED |
16
+ BITMAP_BV_IBUF
19
17
 
20
- def size_bitmap
21
- (@page.space.pages_per_bookkeeping_page * BITS_PER_PAGE) / 8
22
- end
18
+ def initialize(page, cursor)
19
+ @page = page
20
+ @bitmap = read_bitmap(cursor)
21
+ end
23
22
 
24
- def read_bitmap(page, cursor)
25
- cursor.name("ibuf_bitmap") do |c|
26
- c.get_bytes(size_bitmap)
23
+ def size_bitmap
24
+ (@page.space.pages_per_bookkeeping_page * BITS_PER_PAGE) / 8
27
25
  end
28
- end
29
26
 
30
- def each_page_status
31
- unless block_given?
32
- return enum_for(:each_page_status)
27
+ def read_bitmap(cursor)
28
+ cursor.name("ibuf_bitmap") { |c| c.read_bytes(size_bitmap) }
33
29
  end
34
30
 
35
- bitmap = @bitmap.enum_for(:each_byte)
36
-
37
- bitmap.each_with_index do |byte, byte_index|
38
- (0..1).each do |page_offset|
39
- page_number = (byte_index * 2) + page_offset
40
- page_bits = ((byte >> (page_offset * BITS_PER_PAGE)) & BITMAP_BV_ALL)
41
- page_status = {
42
- :free => (page_bits & BITMAP_BV_FREE),
43
- :buffered => (page_bits & BITMAP_BV_BUFFERED != 0),
44
- :ibuf => (page_bits & BITMAP_BV_IBUF != 0),
45
- }
46
- yield page_number, page_status
31
+ def each_page_status
32
+ return enum_for(:each_page_status) unless block_given?
33
+
34
+ bitmap = @bitmap.enum_for(:each_byte)
35
+
36
+ bitmap.each_with_index do |byte, byte_index|
37
+ (0..1).each do |page_offset|
38
+ page_number = (byte_index * 2) + page_offset
39
+ page_bits = ((byte >> (page_offset * BITS_PER_PAGE)) & BITMAP_BV_ALL)
40
+ page_status = PageStatus.new(
41
+ free: (page_bits & BITMAP_BV_FREE),
42
+ buffered: (page_bits & BITMAP_BV_BUFFERED != 0),
43
+ ibuf: (page_bits & BITMAP_BV_IBUF != 0)
44
+ )
45
+ yield page_number, page_status
46
+ end
47
47
  end
48
48
  end
49
49
  end
@@ -1,3 +1,7 @@
1
- class Innodb::IbufIndex
2
- INDEX_ID = 0xFFFFFFFF00000000
1
+ # frozen_string_literal: true
2
+
3
+ module Innodb
4
+ class IbufIndex
5
+ INDEX_ID = 0xFFFFFFFF00000000
6
+ end
3
7
  end
data/lib/innodb/index.rb CHANGED
@@ -1,362 +1,331 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  # An InnoDB index B-tree, given an Innodb::Space and a root page number.
4
- class Innodb::Index
5
- attr_reader :root
6
- attr_reader :space
7
- attr_accessor :record_describer
4
+ module Innodb
5
+ class Index
6
+ attr_reader :root
7
+ attr_reader :space
8
+ attr_accessor :record_describer
8
9
 
9
- def initialize(space, root_page_number, record_describer=nil)
10
- @space = space
11
- @record_describer = record_describer || space.record_describer
10
+ FSEG_LIST_NAMES = %i[
11
+ internal
12
+ leaf
13
+ ].freeze
12
14
 
13
- @root = page(root_page_number)
15
+ def initialize(space, root_page_number, record_describer = nil)
16
+ @space = space
17
+ @record_describer = record_describer || space.record_describer
14
18
 
15
- unless @root
16
- raise "Page #{root_page_number} couldn't be read"
17
- end
19
+ @root = page(root_page_number)
18
20
 
19
- # The root page should be an index page.
20
- unless @root.type == :INDEX
21
- raise "Page #{root_page_number} is a #{@root.type} page, not an INDEX page"
22
- end
21
+ raise "Page #{root_page_number} couldn't be read" unless @root
23
22
 
24
- # The root page should be the only page at its level.
25
- unless @root.prev.nil? && @root.next.nil?
26
- raise "Page #{root_page_number} is a node page, but not appear to be the root; it has previous page and next page pointers"
27
- end
28
- end
23
+ # The root page should be an index page.
24
+ raise "Page #{root_page_number} is a #{@root.type} page, not an INDEX page" unless @root.type == :INDEX
29
25
 
30
- def page(page_number)
31
- page = @space.page(page_number) or
32
- raise "Page #{page_number} couldn't be read"
33
- page.record_describer = @record_describer
34
- page
35
- end
26
+ # The root page should be the only page at its level.
27
+ raise "Page #{root_page_number} does not appear to be an index root" if @root.prev || @root.next
28
+ end
36
29
 
37
- # A helper function to access the index ID in the page header.
38
- def id
39
- @root.page_header[:index_id]
40
- end
30
+ def page(page_number)
31
+ page = @space.page(page_number)
32
+ raise "Page #{page_number} couldn't be read" unless page
41
33
 
42
- # Return the type of node that the given page represents in the index tree.
43
- def node_type(page)
44
- if @root.offset == page.offset
45
- :root
46
- elsif page.level == 0
47
- :leaf
48
- else
49
- :internal
34
+ page.record_describer = @record_describer
35
+ page
50
36
  end
51
- end
52
37
 
53
- # Internal method used by recurse.
54
- def _recurse(parent_page, page_proc, link_proc, depth=0)
55
- if page_proc && parent_page.type == :INDEX
56
- page_proc.call(parent_page, depth)
38
+ # A helper function to access the index ID in the page header.
39
+ def id
40
+ @root.page_header.index_id
57
41
  end
58
42
 
59
- parent_page.each_child_page do |child_page_number, child_min_key|
60
- child_page = page(child_page_number)
61
- child_page.record_describer = record_describer
62
- if child_page.type == :INDEX
63
- if link_proc
64
- link_proc.call(parent_page, child_page, child_min_key, depth+1)
65
- end
66
- _recurse(child_page, page_proc, link_proc, depth+1)
43
+ # Return the type of node that the given page represents in the index tree.
44
+ def node_type(page)
45
+ if @root.offset == page.offset
46
+ :root
47
+ elsif page.level.zero?
48
+ :leaf
49
+ else
50
+ :internal
67
51
  end
68
52
  end
69
- end
70
53
 
71
- # Walk an index tree depth-first, calling procs for each page and link
72
- # in the tree.
73
- def recurse(page_proc, link_proc)
74
- _recurse(@root, page_proc, link_proc)
75
- end
54
+ # Internal method used by recurse.
55
+ def _recurse(parent_page, page_proc, link_proc, depth = 0)
56
+ page_proc.call(parent_page, depth) if page_proc && parent_page.type == :INDEX
76
57
 
77
- # Return the first leaf page in the index by walking down the left side
78
- # of the B-tree until a page at the given level is encountered.
79
- def min_page_at_level(level)
80
- page = @root
81
- record = @root.min_record
82
- while record && page.level > level
83
- page = page(record.child_page_number)
84
- record = page.min_record
85
- end
86
- page if page.level == level
87
- end
58
+ parent_page.each_child_page do |child_page_number, child_min_key|
59
+ child_page = page(child_page_number)
60
+ child_page.record_describer = record_describer
61
+ next unless child_page.type == :INDEX
88
62
 
89
- # Return the minimum record in the index.
90
- def min_record
91
- if min_page = min_page_at_level(0)
92
- min_page.min_record
63
+ link_proc&.call(parent_page, child_page, child_min_key, depth + 1)
64
+ _recurse(child_page, page_proc, link_proc, depth + 1)
65
+ end
93
66
  end
94
- end
95
67
 
96
- # Return the last leaf page in the index by walking down the right side
97
- # of the B-tree until a page at the given level is encountered.
98
- def max_page_at_level(level)
99
- page = @root
100
- record = @root.max_record
101
- while record && page.level > level
102
- page = page(record.child_page_number)
103
- record = page.max_record
68
+ # Walk an index tree depth-first, calling procs for each page and link
69
+ # in the tree.
70
+ def recurse(page_proc, link_proc)
71
+ _recurse(@root, page_proc, link_proc)
104
72
  end
105
- page if page.level == level
106
- end
107
73
 
108
- # Return the maximum record in the index.
109
- def max_record
110
- if max_page = max_page_at_level(0)
111
- max_page.max_record
74
+ # Return the first leaf page in the index by walking down the left side
75
+ # of the B-tree until a page at the given level is encountered.
76
+ def min_page_at_level(level)
77
+ page = @root
78
+ record = @root.min_record
79
+ while record && page.level > level
80
+ page = page(record.child_page_number)
81
+ record = page.min_record
82
+ end
83
+ page if page.level == level
112
84
  end
113
- end
114
85
 
115
- # Return the file segment with the given name from the fseg header.
116
- def fseg(name)
117
- @root.fseg_header[name]
118
- end
119
-
120
- def field_names
121
- record_describer.field_names
122
- end
123
-
124
- # Iterate through all file segments in the index.
125
- def each_fseg
126
- unless block_given?
127
- return enum_for(:each_fseg)
86
+ # Return the minimum record in the index.
87
+ def min_record
88
+ min_page_at_level(0)&.min_record
128
89
  end
129
90
 
130
- [:internal, :leaf].each do |fseg_name|
131
- yield fseg_name, fseg(fseg_name)
91
+ # Return the last leaf page in the index by walking down the right side
92
+ # of the B-tree until a page at the given level is encountered.
93
+ def max_page_at_level(level)
94
+ page = @root
95
+ record = @root.max_record
96
+ while record && page.level > level
97
+ page = page(record.child_page_number)
98
+ record = page.max_record
99
+ end
100
+ page if page.level == level
132
101
  end
133
- end
134
102
 
135
- # Iterate through all lists in a given fseg.
136
- def each_fseg_list(fseg)
137
- unless block_given?
138
- return enum_for(:each_fseg_list, fseg)
103
+ # Return the maximum record in the index.
104
+ def max_record
105
+ max_page_at_level(0)&.max_record
139
106
  end
140
107
 
141
- fseg.each_list do |list_name, list|
142
- yield list_name, list
108
+ # Return the file segment with the given name from the fseg header.
109
+ def fseg(name)
110
+ @root.fseg_header[name]
143
111
  end
144
- end
145
112
 
146
- # Iterate through all frag pages in a given fseg.
147
- def each_fseg_frag_page(fseg)
148
- unless block_given?
149
- return enum_for(:each_fseg_frag_page, fseg)
113
+ def field_names
114
+ record_describer.field_names
150
115
  end
151
116
 
152
- fseg.frag_array_pages.each do |page_number|
153
- yield page_number, page(page_number)
154
- end
155
- end
117
+ # Iterate through all file segments in the index.
118
+ def each_fseg
119
+ return enum_for(:each_fseg) unless block_given?
156
120
 
157
- # Iterate through all pages at this level starting with the provided page.
158
- def each_page_from(page)
159
- unless block_given?
160
- return enum_for(:each_page_from, page)
121
+ FSEG_LIST_NAMES.each do |fseg_name|
122
+ yield fseg_name, fseg(fseg_name)
123
+ end
161
124
  end
162
125
 
163
- while page && page.type == :INDEX
164
- yield page
165
- break unless page.next
166
- page = page(page.next)
167
- end
168
- end
126
+ # Iterate through all lists in a given fseg.
127
+ def each_fseg_list(fseg, &block)
128
+ return enum_for(:each_fseg_list, fseg) unless block_given?
169
129
 
170
- # Iterate through all pages at the given level by finding the first page
171
- # and following the next pointers in each page.
172
- def each_page_at_level(level)
173
- unless block_given?
174
- return enum_for(:each_page_at_level, level)
130
+ fseg.each_list(&block)
175
131
  end
176
132
 
177
- each_page_from(min_page_at_level(level)) { |page| yield page }
178
- end
179
-
180
- # Iterate through all records on all leaf pages in ascending order.
181
- def each_record
182
- unless block_given?
183
- return enum_for(:each_record)
184
- end
133
+ # Iterate through all frag pages in a given fseg.
134
+ def each_fseg_frag_page(fseg)
135
+ return enum_for(:each_fseg_frag_page, fseg) unless block_given?
185
136
 
186
- each_page_at_level(0) do |page|
187
- page.each_record do |record|
188
- yield record
137
+ fseg.frag_array_pages.each do |page_number|
138
+ yield page_number, page(page_number)
189
139
  end
190
140
  end
191
- end
192
141
 
193
- # Search for a record within the entire index, walking down the non-leaf
194
- # pages until a leaf page is found, and then verifying that the record
195
- # returned on the leaf page is an exact match for the key. If a matching
196
- # record is not found, nil is returned (either because linear_search_in_page
197
- # returns nil breaking the loop, or because compare_key returns non-zero).
198
- def linear_search(key)
199
- Innodb::Stats.increment :linear_search
200
-
201
- page = @root
202
-
203
- if Innodb.debug?
204
- puts "linear_search: root=%i, level=%i, key=(%s)" % [
205
- page.offset,
206
- page.level,
207
- key.join(", "),
208
- ]
209
- end
142
+ # Iterate through all pages at this level starting with the provided page.
143
+ def each_page_from(page)
144
+ return enum_for(:each_page_from, page) unless block_given?
210
145
 
211
- while rec =
212
- page.linear_search_from_cursor(page.record_cursor(page.infimum.next), key)
213
- if page.level > 0
214
- # If we haven't reached a leaf page yet, move down the tree and search
215
- # again using linear search.
216
- page = page(rec.child_page_number)
217
- else
218
- # We're on a leaf page, so return the page and record if there is a
219
- # match. If there is no match, break the loop and cause nil to be
220
- # returned.
221
- return rec if rec.compare_key(key) == 0
222
- break
146
+ while page && page.type == :INDEX
147
+ yield page
148
+ break unless page.next
149
+
150
+ page = page(page.next)
223
151
  end
224
152
  end
225
- end
226
153
 
227
- # Search for a record within the entire index like linear_search, but use
228
- # the page directory to search while making as few record comparisons as
229
- # possible. If a matching record is not found, nil is returned.
230
- def binary_search(key)
231
- Innodb::Stats.increment :binary_search
154
+ # Iterate through all pages at the given level by finding the first page
155
+ # and following the next pointers in each page.
156
+ def each_page_at_level(level, &block)
157
+ return enum_for(:each_page_at_level, level) unless block_given?
232
158
 
233
- page = @root
234
-
235
- if Innodb.debug?
236
- puts "binary_search: root=%i, level=%i, key=(%s)" % [
237
- page.offset,
238
- page.level,
239
- key.join(", "),
240
- ]
159
+ each_page_from(min_page_at_level(level), &block)
241
160
  end
242
161
 
243
- # Remove supremum from the page directory, since nothing can be scanned
244
- # linearly from there anyway.
245
- while rec = page.binary_search_by_directory(page.directory[0...-1], key)
246
- if page.level > 0
247
- # If we haven't reached a leaf page yet, move down the tree and search
248
- # again using binary search.
249
- page = page(rec.child_page_number)
250
- else
251
- # We're on a leaf page, so return the page and record if there is a
252
- # match. If there is no match, break the loop and cause nil to be
253
- # returned.
254
- return rec if rec.compare_key(key) == 0
255
- break
256
- end
257
- end
258
- end
162
+ # Iterate through all records on all leaf pages in ascending order.
163
+ def each_record(&block)
164
+ return enum_for(:each_record) unless block_given?
259
165
 
260
- # A cursor to walk the index (cursor) forwards or backward starting with
261
- # a given record, or the minimum (:min) or maximum (:max) record in the
262
- # index.
263
- class IndexCursor
264
- def initialize(index, record, direction)
265
- Innodb::Stats.increment :index_cursor_create
266
- @index = index
267
- @direction = direction
268
- case record
269
- when :min
270
- # Start at the minimum record on the minimum page in the index.
271
- @page = index.min_page_at_level(0)
272
- @page_cursor = @page.record_cursor(:min, direction)
273
- when :max
274
- # Start at the maximum record on the maximum page in the index.
275
- @page = index.max_page_at_level(0)
276
- @page_cursor = @page.record_cursor(:max, direction)
277
- else
278
- # Start at the record provided.
279
- @page = record.page
280
- @page_cursor = @page.record_cursor(record.offset, direction)
166
+ each_page_at_level(0) do |page|
167
+ page.each_record(&block)
281
168
  end
282
169
  end
283
170
 
284
- # Return the next record in the order defined when the cursor was created.
285
- def record
286
- if rec = @page_cursor.record
287
- return rec
171
+ # Search for a record within the entire index, walking down the non-leaf
172
+ # pages until a leaf page is found, and then verifying that the record
173
+ # returned on the leaf page is an exact match for the key. If a matching
174
+ # record is not found, nil is returned (either because linear_search_in_page
175
+ # returns nil breaking the loop, or because compare_key returns non-zero).
176
+ def linear_search(key)
177
+ Innodb::Stats.increment :linear_search
178
+
179
+ page = @root
180
+
181
+ if Innodb.debug?
182
+ puts "linear_search: root=%i, level=%i, key=(%s)" % [
183
+ page.offset,
184
+ page.level,
185
+ key.join(", "),
186
+ ]
288
187
  end
289
188
 
290
- case @direction
291
- when :forward
292
- next_record
293
- when :backward
294
- prev_record
189
+ while (rec = page.linear_search_from_cursor(page.record_cursor(page.infimum.next), key))
190
+ if page.level.positive?
191
+ # If we haven't reached a leaf page yet, move down the tree and search
192
+ # again using linear search.
193
+ page = page(rec.child_page_number)
194
+ else
195
+ # We're on a leaf page, so return the page and record if there is a
196
+ # match. If there is no match, break the loop and cause nil to be
197
+ # returned.
198
+ return rec if rec.compare_key(key).zero?
199
+
200
+ break
201
+ end
295
202
  end
296
203
  end
297
204
 
298
- # Iterate through all records in the cursor.
299
- def each_record
300
- unless block_given?
301
- return enum_for(:each_record)
205
+ # Search for a record within the entire index like linear_search, but use
206
+ # the page directory to search while making as few record comparisons as
207
+ # possible. If a matching record is not found, nil is returned.
208
+ def binary_search(key)
209
+ Innodb::Stats.increment :binary_search
210
+
211
+ page = @root
212
+
213
+ if Innodb.debug?
214
+ puts "binary_search: root=%i, level=%i, key=(%s)" % [
215
+ page.offset,
216
+ page.level,
217
+ key.join(", "),
218
+ ]
302
219
  end
303
220
 
304
- while rec = record
305
- yield rec
221
+ # Remove supremum from the page directory, since nothing can be scanned
222
+ # linearly from there anyway.
223
+ while (rec = page.binary_search_by_directory(page.directory[0...-1], key))
224
+ if page.level.positive?
225
+ # If we haven't reached a leaf page yet, move down the tree and search
226
+ # again using binary search.
227
+ page = page(rec.child_page_number)
228
+ else
229
+ # We're on a leaf page, so return the page and record if there is a
230
+ # match. If there is no match, break the loop and cause nil to be
231
+ # returned.
232
+ return rec if rec.compare_key(key).zero?
233
+
234
+ break
235
+ end
306
236
  end
307
237
  end
308
238
 
309
- private
310
-
311
- # Move the cursor to a new starting position in a given page.
312
- def move_cursor(page, record)
313
- unless @page = @index.page(page)
314
- raise "Failed to load page"
239
+ # A cursor to walk the index (cursor) forwards or backward starting with
240
+ # a given record, or the minimum (:min) or maximum (:max) record in the
241
+ # index.
242
+ class IndexCursor
243
+ def initialize(index, record, direction)
244
+ Innodb::Stats.increment :index_cursor_create
245
+ @index = index
246
+ @direction = direction
247
+ case record
248
+ when :min
249
+ # Start at the minimum record on the minimum page in the index.
250
+ @page = index.min_page_at_level(0)
251
+ @page_cursor = @page.record_cursor(:min, direction)
252
+ when :max
253
+ # Start at the maximum record on the maximum page in the index.
254
+ @page = index.max_page_at_level(0)
255
+ @page_cursor = @page.record_cursor(:max, direction)
256
+ else
257
+ # Start at the record provided.
258
+ @page = record.page
259
+ @page_cursor = @page.record_cursor(record.offset, direction)
260
+ end
315
261
  end
316
262
 
317
- unless @page_cursor = @page.record_cursor(record, @direction)
318
- raise "Failed to position cursor"
263
+ # Return the next record in the order defined when the cursor was created.
264
+ def record
265
+ if (rec = @page_cursor.record)
266
+ return rec
267
+ end
268
+
269
+ case @direction
270
+ when :forward
271
+ next_record
272
+ when :backward
273
+ prev_record
274
+ end
319
275
  end
320
- end
321
276
 
322
- # Move to the next record in the forward direction and return it.
323
- def next_record
324
- Innodb::Stats.increment :index_cursor_next_record
277
+ # Iterate through all records in the cursor.
278
+ def each_record
279
+ return enum_for(:each_record) unless block_given?
325
280
 
326
- if rec = @page_cursor.record
327
- return rec
281
+ while (rec = record)
282
+ yield rec
283
+ end
328
284
  end
329
285
 
330
- unless next_page = @page.next
331
- return nil
286
+ private
287
+
288
+ # Move the cursor to a new starting position in a given page.
289
+ def move_cursor(page, record)
290
+ raise "Failed to load page" unless (@page = @index.page(page))
291
+ raise "Failed to position cursor" unless (@page_cursor = @page.record_cursor(record, @direction))
332
292
  end
333
293
 
334
- move_cursor(next_page, :min)
294
+ # Move to the next record in the forward direction and return it.
295
+ def next_record
296
+ Innodb::Stats.increment :index_cursor_next_record
335
297
 
336
- @page_cursor.record
337
- end
298
+ if (rec = @page_cursor.record)
299
+ return rec
300
+ end
338
301
 
339
- # Move to the previous record in the backward direction and return it.
340
- def prev_record
341
- Innodb::Stats.increment :index_cursor_prev_record
302
+ return unless (next_page = @page.next)
342
303
 
343
- if rec = @page_cursor.record
344
- return rec
345
- end
304
+ move_cursor(next_page, :min)
346
305
 
347
- unless prev_page = @page.prev
348
- return nil
306
+ @page_cursor.record
349
307
  end
350
308
 
351
- move_cursor(prev_page, :max)
309
+ # Move to the previous record in the backward direction and return it.
310
+ def prev_record
311
+ Innodb::Stats.increment :index_cursor_prev_record
312
+
313
+ if (rec = @page_cursor.record)
314
+ return rec
315
+ end
316
+
317
+ return unless (prev_page = @page.prev)
352
318
 
353
- @page_cursor.record
319
+ move_cursor(prev_page, :max)
320
+
321
+ @page_cursor.record
322
+ end
354
323
  end
355
- end
356
324
 
357
- # Return an IndexCursor starting at the given record (an Innodb::Record,
358
- # :min, or :max) and cursor in the direction given (:forward or :backward).
359
- def cursor(record=:min, direction=:forward)
360
- IndexCursor.new(self, record, direction)
325
+ # Return an IndexCursor starting at the given record (an Innodb::Record,
326
+ # :min, or :max) and cursor in the direction given (:forward or :backward).
327
+ def cursor(record = :min, direction = :forward)
328
+ IndexCursor.new(self, record, direction)
329
+ end
361
330
  end
362
331
  end