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.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +5 -6
  3. data/bin/innodb_log +13 -18
  4. data/bin/innodb_space +377 -757
  5. data/lib/innodb.rb +4 -5
  6. data/lib/innodb/checksum.rb +26 -24
  7. data/lib/innodb/data_dictionary.rb +490 -550
  8. data/lib/innodb/data_type.rb +362 -326
  9. data/lib/innodb/field.rb +102 -89
  10. data/lib/innodb/fseg_entry.rb +22 -26
  11. data/lib/innodb/history.rb +21 -21
  12. data/lib/innodb/history_list.rb +72 -76
  13. data/lib/innodb/ibuf_bitmap.rb +36 -36
  14. data/lib/innodb/ibuf_index.rb +6 -2
  15. data/lib/innodb/index.rb +245 -276
  16. data/lib/innodb/inode.rb +154 -155
  17. data/lib/innodb/list.rb +191 -183
  18. data/lib/innodb/log.rb +139 -110
  19. data/lib/innodb/log_block.rb +100 -91
  20. data/lib/innodb/log_group.rb +53 -64
  21. data/lib/innodb/log_reader.rb +97 -96
  22. data/lib/innodb/log_record.rb +328 -279
  23. data/lib/innodb/lsn.rb +86 -81
  24. data/lib/innodb/page.rb +417 -414
  25. data/lib/innodb/page/blob.rb +82 -83
  26. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  27. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  28. data/lib/innodb/page/index.rb +964 -943
  29. data/lib/innodb/page/index_compressed.rb +34 -34
  30. data/lib/innodb/page/inode.rb +103 -112
  31. data/lib/innodb/page/sys.rb +13 -15
  32. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  33. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  34. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  35. data/lib/innodb/page/trx_sys.rb +204 -182
  36. data/lib/innodb/page/undo_log.rb +106 -92
  37. data/lib/innodb/record.rb +121 -160
  38. data/lib/innodb/record_describer.rb +66 -68
  39. data/lib/innodb/space.rb +380 -418
  40. data/lib/innodb/stats.rb +33 -35
  41. data/lib/innodb/system.rb +149 -171
  42. data/lib/innodb/undo_log.rb +129 -107
  43. data/lib/innodb/undo_record.rb +255 -247
  44. data/lib/innodb/util/buffer_cursor.rb +81 -79
  45. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  46. data/lib/innodb/version.rb +2 -2
  47. data/lib/innodb/xdes.rb +144 -142
  48. metadata +80 -11
data/lib/innodb/inode.rb CHANGED
@@ -1,187 +1,186 @@
1
- # -*- encoding : utf-8 -*-
2
-
3
- class Innodb::Inode
4
- # The number of "slots" (each representing one page) in the fragment array
5
- # within each Inode entry.
6
- FRAG_ARRAY_N_SLOTS = 32 # FSP_EXTENT_SIZE / 2
7
-
8
- # The size (in bytes) of each slot in the fragment array.
9
- FRAG_SLOT_SIZE = 4
10
-
11
- # A magic number which helps determine if an Inode structure is in use
12
- # and populated with valid data.
13
- MAGIC_N_VALUE = 97937874
14
-
15
- # The size (in bytes) of an Inode entry.
16
- SIZE = (16 + (3 * Innodb::List::BASE_NODE_SIZE) +
17
- (FRAG_ARRAY_N_SLOTS * FRAG_SLOT_SIZE))
18
-
19
- # Read an array of page numbers (32-bit integers, which may be nil) from
20
- # the provided cursor.
21
- def self.page_number_array(size, cursor)
22
- size.times.map do |n|
23
- cursor.name("page[#{n}]") do |c|
24
- Innodb::Page.maybe_undefined(c.get_uint32)
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Innodb
6
+ class Inode
7
+ extend Forwardable
8
+
9
+ Header = Struct.new(
10
+ :offset,
11
+ :fseg_id,
12
+ :not_full_n_used,
13
+ :free,
14
+ :not_full,
15
+ :full,
16
+ :magic_n,
17
+ :frag_array,
18
+ keyword_init: true
19
+ )
20
+
21
+ # The number of "slots" (each representing one page) in the fragment array
22
+ # within each Inode entry.
23
+ FRAG_ARRAY_N_SLOTS = 32 # FSP_EXTENT_SIZE / 2
24
+
25
+ # The size (in bytes) of each slot in the fragment array.
26
+ FRAG_SLOT_SIZE = 4
27
+
28
+ # A magic number which helps determine if an Inode structure is in use
29
+ # and populated with valid data.
30
+ MAGIC_N_VALUE = 97_937_874
31
+
32
+ # The size (in bytes) of an Inode entry.
33
+ SIZE = (16 + (3 * Innodb::List::BASE_NODE_SIZE) +
34
+ (FRAG_ARRAY_N_SLOTS * FRAG_SLOT_SIZE))
35
+
36
+ LISTS = %i[
37
+ free
38
+ not_full
39
+ full
40
+ ].freeze
41
+
42
+ # Read an array of page numbers (32-bit integers, which may be nil) from
43
+ # the provided cursor.
44
+ def self.page_number_array(size, cursor)
45
+ size.times.map do |n|
46
+ cursor.name("page[#{n}]") do |c|
47
+ Innodb::Page.maybe_undefined(c.read_uint32)
48
+ end
25
49
  end
26
50
  end
27
- end
28
51
 
29
- # Construct a new Inode by reading an FSEG header from a cursor.
30
- def self.new_from_cursor(space, cursor)
31
- data = {
32
- :offset => cursor.position,
33
- :fseg_id => cursor.name("fseg_id") {
34
- cursor.get_uint64
35
- },
36
- :not_full_n_used => cursor.name("not_full_n_used") {
37
- cursor.get_uint32
38
- },
39
- :free => cursor.name("list[free]") {
40
- Innodb::List::Xdes.new(space, Innodb::List.get_base_node(cursor))
41
- },
42
- :not_full => cursor.name("list[not_full]") {
43
- Innodb::List::Xdes.new(space, Innodb::List.get_base_node(cursor))
44
- },
45
- :full => cursor.name("list[full]") {
46
- Innodb::List::Xdes.new(space, Innodb::List.get_base_node(cursor))
47
- },
48
- :magic_n => cursor.name("magic_n") {
49
- cursor.get_uint32
50
- },
51
- :frag_array => cursor.name("frag_array") {
52
- page_number_array(FRAG_ARRAY_N_SLOTS, cursor)
53
- },
54
- }
55
-
56
- Innodb::Inode.new(space, data)
57
- end
58
-
59
- attr_accessor :space
52
+ # Construct a new Inode by reading an FSEG header from a cursor.
53
+ def self.new_from_cursor(space, cursor)
54
+ Innodb::Inode.new(
55
+ space,
56
+ Header.new(
57
+ offset: cursor.position,
58
+ fseg_id: cursor.name("fseg_id") { cursor.read_uint64 },
59
+ not_full_n_used: cursor.name("not_full_n_used") { cursor.read_uint32 },
60
+ free: cursor.name("list[free]") { Innodb::List::Xdes.new(space, Innodb::List.get_base_node(cursor)) },
61
+ not_full: cursor.name("list[not_full]") { Innodb::List::Xdes.new(space, Innodb::List.get_base_node(cursor)) },
62
+ full: cursor.name("list[full]") { Innodb::List::Xdes.new(space, Innodb::List.get_base_node(cursor)) },
63
+ magic_n: cursor.name("magic_n") { cursor.read_uint32 },
64
+ frag_array: cursor.name("frag_array") { page_number_array(FRAG_ARRAY_N_SLOTS, cursor) }
65
+ )
66
+ )
67
+ end
60
68
 
61
- def initialize(space, data)
62
- @space = space
63
- @data = data
64
- end
69
+ attr_accessor :space
70
+ attr_accessor :header
65
71
 
66
- def offset; @data[:offset]; end
67
- def fseg_id; @data[:fseg_id]; end
68
- def not_full_n_used; @data[:not_full_n_used]; end
69
- def free; @data[:free]; end
70
- def not_full; @data[:not_full]; end
71
- def full; @data[:full]; end
72
- def magic_n; @data[:magic_n]; end
73
- def frag_array; @data[:frag_array]; end
74
-
75
- def inspect
76
- "<%s space=%s, fseg=%i>" % [
77
- self.class.name,
78
- space.inspect,
79
- fseg_id,
80
- ]
81
- end
72
+ def initialize(space, header)
73
+ @space = space
74
+ @header = header
75
+ end
82
76
 
83
- # Helper method to determine if an Inode is in use. Inodes that are not in
84
- # use have an fseg_id of 0.
85
- def allocated?
86
- fseg_id != 0
87
- end
77
+ def_delegator :header, :offset
78
+ def_delegator :header, :fseg_id
79
+ def_delegator :header, :not_full_n_used
80
+ def_delegator :header, :free
81
+ def_delegator :header, :not_full
82
+ def_delegator :header, :full
83
+ def_delegator :header, :magic_n
84
+ def_delegator :header, :frag_array
85
+
86
+ def inspect
87
+ "<%s space=%s, fseg=%i>" % [
88
+ self.class.name,
89
+ space.inspect,
90
+ fseg_id,
91
+ ]
92
+ end
88
93
 
89
- # Helper method to return an array of only non-nil fragment pages.
90
- def frag_array_pages
91
- frag_array.select { |n| ! n.nil? }
92
- end
94
+ # Helper method to determine if an Inode is in use. Inodes that are not in
95
+ # use have an fseg_id of 0.
96
+ def allocated?
97
+ fseg_id != 0
98
+ end
93
99
 
94
- # Helper method to count non-nil fragment pages.
95
- def frag_array_n_used
96
- frag_array.inject(0) { |n, i| n += 1 if i; n }
97
- end
100
+ # Helper method to return an array of only non-nil fragment pages.
101
+ def frag_array_pages
102
+ frag_array.reject(&:nil?)
103
+ end
98
104
 
99
- # Calculate the total number of pages in use (not free) within this fseg.
100
- def used_pages
101
- frag_array_n_used + not_full_n_used +
102
- (full.length * @space.pages_per_extent)
103
- end
105
+ # Helper method to count non-nil fragment pages.
106
+ def frag_array_n_used
107
+ frag_array_pages.count
108
+ end
104
109
 
105
- # Calculate the total number of pages within this fseg.
106
- def total_pages
107
- frag_array_n_used +
108
- (free.length * @space.pages_per_extent) +
109
- (not_full.length * @space.pages_per_extent) +
110
- (full.length * @space.pages_per_extent)
111
- end
110
+ # Calculate the total number of pages in use (not free) within this fseg.
111
+ def used_pages
112
+ frag_array_n_used + not_full_n_used +
113
+ (full.length * @space.pages_per_extent)
114
+ end
112
115
 
113
- # Calculate the fill factor of this fseg, in percent.
114
- def fill_factor
115
- total_pages > 0 ? 100.0 * (used_pages.to_f / total_pages.to_f) : 0.0
116
- end
116
+ # Calculate the total number of pages within this fseg.
117
+ def total_pages
118
+ frag_array_n_used +
119
+ (free.length * @space.pages_per_extent) +
120
+ (not_full.length * @space.pages_per_extent) +
121
+ (full.length * @space.pages_per_extent)
122
+ end
117
123
 
118
- # Return an array of lists within an fseg.
119
- def lists
120
- [:free, :not_full, :full]
121
- end
124
+ # Calculate the fill factor of this fseg, in percent.
125
+ def fill_factor
126
+ total_pages.positive? ? 100.0 * (used_pages.to_f / total_pages) : 0.0
127
+ end
122
128
 
123
- # Return a list from the fseg, given its name as a symbol.
124
- def list(name)
125
- @data[name] if lists.include? name
126
- end
129
+ # Return a list from the fseg, given its name as a symbol.
130
+ def list(name)
131
+ return unless LISTS.include?(name)
127
132
 
128
- # Iterate through all lists, yielding the list name and the list itself.
129
- def each_list
130
- unless block_given?
131
- return enum_for(:each_list)
133
+ header[name]
132
134
  end
133
135
 
134
- lists.each do |name|
135
- yield name, list(name)
136
- end
136
+ # Iterate through all lists, yielding the list name and the list itself.
137
+ def each_list
138
+ return enum_for(:each_list) unless block_given?
137
139
 
138
- nil
139
- end
140
+ LISTS.each do |name|
141
+ yield name, list(name)
142
+ end
140
143
 
141
- # Iterate through the fragment array followed by all lists, yielding the
142
- # page number. This allows a convenient way to identify all pages that are
143
- # part of this inode.
144
- def each_page_number
145
- unless block_given?
146
- return enum_for(:each_page_number)
144
+ nil
147
145
  end
148
146
 
149
- frag_array_pages.each do |page_number|
150
- yield page_number
151
- end
147
+ # Iterate through the fragment array followed by all lists, yielding the
148
+ # page number. This allows a convenient way to identify all pages that are
149
+ # part of this inode.
150
+ def each_page_number(&block)
151
+ return enum_for(:each_page_number) unless block_given?
152
+
153
+ frag_array_pages.each(&block)
152
154
 
153
- each_list do |fseg_name, fseg_list|
154
- fseg_list.each do |xdes|
155
- xdes.each_page_status do |page_number|
156
- yield page_number
155
+ each_list do |_fseg_name, fseg_list|
156
+ fseg_list.each do |xdes|
157
+ xdes.each_page_status(&block)
157
158
  end
158
159
  end
160
+
161
+ nil
159
162
  end
160
163
 
161
- nil
162
- end
164
+ # Iterate through the page as associated with this inode using the
165
+ # each_page_number method, and yield the page number and page.
166
+ def each_page
167
+ return enum_for(:each_page) unless block_given?
163
168
 
164
- # Iterate through the page as associated with this inode using the
165
- # each_page_number method, and yield the page number and page.
166
- def each_page
167
- unless block_given?
168
- return enum_for(:each_page)
169
- end
169
+ each_page_number do |page_number|
170
+ yield page_number, space.page(page_number)
171
+ end
170
172
 
171
- each_page_number do |page_number|
172
- yield page_number, space.page(page_number)
173
+ nil
173
174
  end
174
175
 
175
- nil
176
- end
177
-
178
- # Compare one Innodb::Inode to another.
179
- def ==(other)
180
- fseg_id == other.fseg_id if other
181
- end
176
+ # Compare one Innodb::Inode to another.
177
+ def ==(other)
178
+ fseg_id == other.fseg_id if other
179
+ end
182
180
 
183
- # Dump a summary of this object for debugging purposes.
184
- def dump
185
- pp @data
181
+ # Dump a summary of this object for debugging purposes.
182
+ def dump
183
+ pp header
184
+ end
186
185
  end
187
186
  end
data/lib/innodb/list.rb CHANGED
@@ -1,233 +1,241 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  # An abstract InnoDB "free list" or FLST (renamed to just "list" here as it
4
4
  # frequently is used for structures that aren't free lists). This class must
5
5
  # be sub-classed to provide an appropriate #object_from_address method.
6
6
 
7
- class Innodb::List
8
- # An "address", which consists of a page number and byte offset within the
9
- # page. This points to the list "node" pointers (prev and next) of the
10
- # node, not necessarily the node object.
11
- ADDRESS_SIZE = 4 + 2
12
-
13
- # Read a node address from a cursor. Return nil if the address is an end
14
- # or "NULL" pointer (the page number is UINT32_MAX), or the address if
15
- # valid.
16
- def self.get_address(cursor)
17
- page = cursor.name("page") {
18
- Innodb::Page.maybe_undefined(cursor.get_uint32)
19
- }
20
- offset = cursor.name("offset") { cursor.get_uint16 }
21
- if page
22
- {
23
- :page => page,
24
- :offset => offset,
25
- }
7
+ module Innodb
8
+ class List
9
+ BaseNode = Struct.new(
10
+ :length, # rubocop:disable Lint/StructNewOverride
11
+ :first, # rubocop:disable Lint/StructNewOverride
12
+ :last,
13
+ keyword_init: true
14
+ )
15
+
16
+ Node = Struct.new(
17
+ :prev,
18
+ :next,
19
+ keyword_init: true
20
+ )
21
+
22
+ # An "address", which consists of a page number and byte offset within the
23
+ # page. This points to the list "node" pointers (prev and next) of the
24
+ # node, not necessarily the node object.
25
+ ADDRESS_SIZE = 4 + 2
26
+
27
+ # Read a node address from a cursor. Return nil if the address is an end
28
+ # or "NULL" pointer (the page number is UINT32_MAX), or the address if
29
+ # valid.
30
+ def self.get_address(cursor)
31
+ page = cursor.name("page") { Innodb::Page.maybe_undefined(cursor.read_uint32) }
32
+ offset = cursor.name("offset") { cursor.read_uint16 }
33
+
34
+ Innodb::Page::Address.new(page: page, offset: offset) if page
26
35
  end
27
- end
28
-
29
- # A list node consists of two addresses: the "previous" node address, and
30
- # the "next" node address.
31
- NODE_SIZE = 2 * ADDRESS_SIZE
32
-
33
- # Read a node, consisting of two consecutive addresses (:prev and :next)
34
- # from a cursor. Either address may be nil, indicating the end of the
35
- # linked list.
36
- def self.get_node(cursor)
37
- {
38
- :prev => cursor.name("prev") { get_address(cursor) },
39
- :next => cursor.name("next") { get_address(cursor) },
40
- }
41
- end
42
36
 
43
- # A list base node consists of a list length followed by two addresses:
44
- # the "first" node address, and the "last" node address.
45
- BASE_NODE_SIZE = 4 + (2 * ADDRESS_SIZE)
46
-
47
- # Read a base node, consisting of a list length followed by two addresses
48
- # (:first and :last) from a cursor. Either address may be nil. An empty list
49
- # has a :length of 0 and :first and :last are nil. A list with only a single
50
- # item will have a :length of 1 and :first and :last will point to the same
51
- # address.
52
- def self.get_base_node(cursor)
53
- {
54
- :length => cursor.name("length") { cursor.get_uint32 },
55
- :first => cursor.name("first") { get_address(cursor) },
56
- :last => cursor.name("last") { get_address(cursor) },
57
- }
58
- end
37
+ # A list node consists of two addresses: the "previous" node address, and
38
+ # the "next" node address.
39
+ NODE_SIZE = 2 * ADDRESS_SIZE
40
+
41
+ # Read a node, consisting of two consecutive addresses (:prev and :next)
42
+ # from a cursor. Either address may be nil, indicating the end of the
43
+ # linked list.
44
+ def self.get_node(cursor)
45
+ Node.new(
46
+ prev: cursor.name("prev") { get_address(cursor) },
47
+ next: cursor.name("next") { get_address(cursor) }
48
+ )
49
+ end
59
50
 
60
- def initialize(space, base)
61
- @space = space
62
- @base = base
63
- end
51
+ # A list base node consists of a list length followed by two addresses:
52
+ # the "first" node address, and the "last" node address.
53
+ BASE_NODE_SIZE = 4 + (2 * ADDRESS_SIZE)
54
+
55
+ # Read a base node, consisting of a list length followed by two addresses
56
+ # (:first and :last) from a cursor. Either address may be nil. An empty list
57
+ # has a :length of 0 and :first and :last are nil. A list with only a single
58
+ # item will have a :length of 1 and :first and :last will point to the same
59
+ # address.
60
+ def self.get_base_node(cursor)
61
+ BaseNode.new(
62
+ length: cursor.name("length") { cursor.read_uint32 },
63
+ first: cursor.name("first") { get_address(cursor) },
64
+ last: cursor.name("last") { get_address(cursor) }
65
+ )
66
+ end
64
67
 
65
- attr_reader :space
66
- attr_reader :base
68
+ attr_reader :space
69
+ attr_reader :base
67
70
 
68
- # Abstract #object_from_address method which must be implemented by
69
- # sub-classes in order to return a useful object given an object address.
70
- def object_from_address(address)
71
- raise "#{self.class} must implement object_from_address"
72
- end
71
+ def initialize(space, base)
72
+ @space = space
73
+ @base = base
74
+ end
73
75
 
74
- # Return the object pointed to by the "previous" address pointer of the
75
- # provided object.
76
- def prev(object)
77
- unless object.respond_to? :prev_address
78
- raise "Class #{object.class} does not respond to prev_address"
76
+ # Abstract #object_from_address method which must be implemented by
77
+ # sub-classes in order to return a useful object given an object address.
78
+ def object_from_address(_address)
79
+ raise "#{self.class} must implement object_from_address"
79
80
  end
80
81
 
81
- object_from_address(object.prev_address)
82
- end
82
+ # Return the object pointed to by the "previous" address pointer of the
83
+ # provided object.
84
+ def prev(object)
85
+ raise "Class #{object.class} does not respond to prev_address" unless object.respond_to?(:prev_address)
83
86
 
84
- # Return the object pointed to by the "next" address pointer of the
85
- # provided object.
86
- def next(object)
87
- unless object.respond_to? :next_address
88
- raise "Class #{object.class} does not respond to next_address"
87
+ object_from_address(object.prev_address)
89
88
  end
90
89
 
91
- object_from_address(object.next_address)
92
- end
90
+ # Return the object pointed to by the "next" address pointer of the
91
+ # provided object.
92
+ def next(object)
93
+ raise "Class #{object.class} does not respond to next_address" unless object.respond_to?(:next_address)
93
94
 
94
- # Return the number of items in the list.
95
- def length
96
- @base[:length]
97
- end
95
+ object_from_address(object.next_address)
96
+ end
98
97
 
99
- # Return the first object in the list using the list base node "first"
100
- # address pointer.
101
- def first
102
- object_from_address(@base[:first])
103
- end
98
+ # Return the number of items in the list.
99
+ def length
100
+ @base.length
101
+ end
104
102
 
105
- # Return the first object in the list using the list base node "last"
106
- # address pointer.
107
- def last
108
- object_from_address(@base[:last])
109
- end
103
+ # Return the first object in the list using the list base node "first"
104
+ # address pointer.
105
+ def first
106
+ object_from_address(@base.first)
107
+ end
110
108
 
111
- # Return a list cursor for the list.
112
- def list_cursor(node=:min, direction=:forward)
113
- ListCursor.new(self, node, direction)
114
- end
109
+ # Return the first object in the list using the list base node "last"
110
+ # address pointer.
111
+ def last
112
+ object_from_address(@base.last)
113
+ end
115
114
 
116
- # Return whether the given item is present in the list. This depends on the
117
- # item and the items in the list implementing some sufficient == method.
118
- # This is implemented rather inefficiently by constructing an array and
119
- # leaning on Array#include? to do the real work.
120
- def include?(item)
121
- each.to_a.include? item
122
- end
115
+ # Return a list cursor for the list.
116
+ def list_cursor(node = :min, direction = :forward)
117
+ ListCursor.new(self, node, direction)
118
+ end
123
119
 
124
- # Iterate through all nodes in the list.
125
- def each
126
- unless block_given?
127
- return enum_for(:each)
120
+ # Return whether the given item is present in the list. This depends on the
121
+ # item and the items in the list implementing some sufficient == method.
122
+ # This is implemented rather inefficiently by constructing an array and
123
+ # leaning on Array#include? to do the real work.
124
+ def include?(item)
125
+ each.to_a.include?(item)
128
126
  end
129
127
 
130
- list_cursor.each_node do |node|
131
- yield node
128
+ # Iterate through all nodes in the list.
129
+ def each(&block)
130
+ return enum_for(:each) unless block_given?
131
+
132
+ list_cursor.each_node(&block)
132
133
  end
133
- end
134
134
 
135
- # A list iteration cursor used primarily by the Innodb::List #cursor method
136
- # implicitly. Keeps its own state for iterating through lists efficiently.
137
- class ListCursor
138
- def initialize(list, node=:min, direction=:forward)
139
- @initial = true
140
- @list = list
141
- @direction = direction
142
-
143
- case node
144
- when :min
145
- @node = @list.first
146
- when :max
147
- @node = @list.last
148
- else
149
- @node = node
135
+ # A list iteration cursor used primarily by the Innodb::List #cursor method
136
+ # implicitly. Keeps its own state for iterating through lists efficiently.
137
+ class ListCursor
138
+ def initialize(list, node = :min, direction = :forward)
139
+ @initial = true
140
+ @list = list
141
+ @direction = direction
142
+ @node = initial_node(node)
150
143
  end
151
- end
152
144
 
153
- def node
154
- if @initial
155
- @initial = false
156
- return @node
145
+ def initial_node(node)
146
+ case node
147
+ when :min
148
+ @list.first
149
+ when :max
150
+ @list.last
151
+ else
152
+ node
153
+ end
157
154
  end
158
155
 
159
- case @direction
160
- when :forward
161
- next_node
162
- when :backward
163
- prev_node
156
+ def node
157
+ if @initial
158
+ @initial = false
159
+ return @node
160
+ end
161
+
162
+ case @direction
163
+ when :forward
164
+ next_node
165
+ when :backward
166
+ prev_node
167
+ end
164
168
  end
165
- end
166
169
 
167
- # Return the previous entry from the current position, and advance the
168
- # cursor position to the returned entry. If the cursor is currently nil,
169
- # return the last entry in the list and adjust the cursor position to
170
- # that entry.
171
- def prev_node
172
- if node = @list.prev(@node)
173
- @node = node
170
+ def goto_node(node)
171
+ @node = node if node
174
172
  end
175
- end
176
173
 
177
- # Return the next entry from the current position, and advance the
178
- # cursor position to the returned entry. If the cursor is currently nil,
179
- # return the first entry in the list and adjust the cursor position to
180
- # that entry.
181
- def next_node
182
- if node = @list.next(@node)
183
- @node = node
174
+ # Return the previous entry from the current position, and advance the
175
+ # cursor position to the returned entry. If the cursor is currently nil,
176
+ # return the last entry in the list and adjust the cursor position to
177
+ # that entry.
178
+ def prev_node
179
+ goto_node(@list.prev(@node))
184
180
  end
185
- end
186
181
 
187
- def each_node
188
- unless block_given?
189
- return enum_for(:each_node)
182
+ # Return the next entry from the current position, and advance the
183
+ # cursor position to the returned entry. If the cursor is currently nil,
184
+ # return the first entry in the list and adjust the cursor position to
185
+ # that entry.
186
+ def next_node
187
+ goto_node(@list.next(@node))
190
188
  end
191
189
 
192
- while n = node
193
- yield n
190
+ def each_node
191
+ return enum_for(:each_node) unless block_given?
192
+
193
+ while (n = node)
194
+ yield n
195
+ end
194
196
  end
195
197
  end
196
- end
197
- end
198
198
 
199
- # A list of extent descriptor entries. Objects returned by list methods
200
- # will be Innodb::Xdes objects.
201
- class Innodb::List::Xdes < Innodb::List
202
- def object_from_address(address)
203
- if address && page = @space.page(address[:page])
204
- Innodb::Xdes.new(page, page.cursor(address[:offset] - 8))
199
+ # A list of extent descriptor entries. Objects returned by list methods
200
+ # will be Innodb::Xdes objects.
201
+ class Xdes < Innodb::List
202
+ def object_from_address(address)
203
+ return unless address
204
+
205
+ page = @space.page(address.page)
206
+ return unless page
207
+
208
+ Innodb::Xdes.new(page, page.cursor(address.offset - 8))
209
+ end
205
210
  end
206
- end
207
- end
208
211
 
209
- # A list of Inode pages. Objects returned by list methods will be
210
- # Innodb::Page::Inode objects.
211
- class Innodb::List::Inode < Innodb::List
212
- def object_from_address(address)
213
- if address && page = @space.page(address[:page])
214
- page
212
+ # A list of Inode pages. Objects returned by list methods will be
213
+ # Innodb::Page::Inode objects.
214
+ class Inode < Innodb::List
215
+ def object_from_address(address)
216
+ return unless address
217
+
218
+ @space.page(address.page)
219
+ end
215
220
  end
216
- end
217
- end
218
221
 
219
- class Innodb::List::UndoPage < Innodb::List
220
- def object_from_address(address)
221
- if address && page = @space.page(address[:page])
222
- page
222
+ class UndoPage < Innodb::List
223
+ def object_from_address(address)
224
+ return unless address
225
+
226
+ @space.page(address.page)
227
+ end
223
228
  end
224
- end
225
- end
226
229
 
227
- class Innodb::List::History < Innodb::List
228
- def object_from_address(address)
229
- if address && page = @space.page(address[:page])
230
- Innodb::UndoLog.new(page, address[:offset] - 34)
230
+ class History < Innodb::List
231
+ def object_from_address(address)
232
+ return unless address
233
+
234
+ page = @space.page(address.page)
235
+ return unless page
236
+
237
+ Innodb::UndoLog.new(page, address.offset - 34)
238
+ end
231
239
  end
232
240
  end
233
241
  end