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
@@ -1,4 +1,4 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  #
4
4
  # A class to describe record layouts for InnoDB indexes. Designed to be usable
@@ -54,87 +54,85 @@
54
54
  # my_table_clustered.row "age", :INT, :UNSIGNED
55
55
  #
56
56
 
57
- class Innodb::RecordDescriber
58
- # Internal method to initialize the class's instance variable on access.
59
- def self.static_description
60
- @static_description ||= {
61
- :type => nil,
62
- :key => [],
63
- :row => []
64
- }
65
- end
57
+ module Innodb
58
+ class RecordDescriber
59
+ # Internal method to initialize the class's instance variable on access.
60
+ def self.static_description
61
+ @static_description ||= { type: nil, key: [], row: [] }
62
+ end
66
63
 
67
- # A 'type' method to be used from the DSL.
68
- def self.type(type)
69
- static_description[:type] = type
70
- end
64
+ # A 'type' method to be used from the DSL.
65
+ def self.type(type)
66
+ static_description[:type] = type
67
+ end
71
68
 
72
- # An internal method wrapped with 'key' and 'row' helpers.
73
- def self.add_static_field(group, name, type)
74
- static_description[group] << { :name => name, :type => type }
75
- end
69
+ # An internal method wrapped with 'key' and 'row' helpers.
70
+ def self.add_static_field(group, name, type)
71
+ static_description[group] << { name: name, type: type }
72
+ end
76
73
 
77
- # A 'key' method to be used from the DSL.
78
- def self.key(name, *type)
79
- add_static_field :key, name, type
80
- end
74
+ # A 'key' method to be used from the DSL.
75
+ def self.key(name, *type)
76
+ add_static_field :key, name, type
77
+ end
81
78
 
82
- # A 'row' method to be used from the DSL.
83
- def self.row(name, *type)
84
- add_static_field :row, name, type
85
- end
79
+ # A 'row' method to be used from the DSL.
80
+ def self.row(name, *type)
81
+ add_static_field :row, name, type
82
+ end
86
83
 
87
- attr_accessor :description
84
+ attr_accessor :description
88
85
 
89
- def initialize
90
- @description = self.class.static_description.dup
91
- @description[:key] = @description[:key].dup
92
- @description[:row] = @description[:row].dup
93
- end
86
+ def initialize
87
+ @description = self.class.static_description.dup
88
+ @description[:key] = @description[:key].dup
89
+ @description[:row] = @description[:row].dup
90
+ end
94
91
 
95
- # Set the type of this record (:clustered or :secondary).
96
- def type(type)
97
- description[:type] = type
98
- end
92
+ # Set the type of this record (:clustered or :secondary).
93
+ def type(type)
94
+ description[:type] = type
95
+ end
99
96
 
100
- # An internal method wrapped with 'key' and 'row' helpers.
101
- def add_field(group, name, type)
102
- description[group] << { :name => name, :type => type }
103
- end
97
+ # An internal method wrapped with 'key' and 'row' helpers.
98
+ def add_field(group, name, type)
99
+ description[group] << { name: name, type: type }
100
+ end
104
101
 
105
- # Add a key column to the record description.
106
- def key(name, *type)
107
- add_field :key, name, type
108
- end
102
+ # Add a key column to the record description.
103
+ def key(name, *type)
104
+ add_field :key, name, type
105
+ end
109
106
 
110
- # Add a row (non-key) column to the record description.
111
- def row(name, *type)
112
- add_field :row, name, type
113
- end
107
+ # Add a row (non-key) column to the record description.
108
+ def row(name, *type)
109
+ add_field :row, name, type
110
+ end
114
111
 
115
- def field_names
116
- names = []
117
- [:key, :row].each do |group|
118
- names += description[group].map { |n| n[:name] }
112
+ def field_names
113
+ names = []
114
+ %i[key row].each do |group|
115
+ names += description[group].map { |n| n[:name] }
116
+ end
117
+ names
119
118
  end
120
- names
121
- end
122
119
 
123
- def generate_class(name="Describer_#{object_id}")
124
- str = "class #{name}\n"
125
- str << " type %s\n" % [
126
- description[:type].inspect
127
- ]
128
- [:key, :row].each do |group|
129
- description[group].each do |item|
130
- str << " %s %s, %s\n" % [
131
- group,
132
- item[:name].inspect,
133
- item[:type].map { |s| s.inspect }.join(", "),
134
- ]
120
+ def generate_class(name = "Describer_#{object_id}")
121
+ str = "class #{name}\n".dup
122
+ str << " type %s\n" % [
123
+ description[:type].inspect,
124
+ ]
125
+ %i[key row].each do |group|
126
+ description[group].each do |item|
127
+ str << " %s %s, %s\n" % [
128
+ group,
129
+ item[:name].inspect,
130
+ item[:type].map(&:inspect).join(", "),
131
+ ]
132
+ end
135
133
  end
134
+ str << "end\n"
135
+ str
136
136
  end
137
- str << "end\n"
138
- str
139
137
  end
140
138
  end
data/lib/innodb/space.rb CHANGED
@@ -1,528 +1,490 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  # An InnoDB space file, which can be either a multi-table ibdataN file
4
4
  # or a single-table "innodb_file_per_table" .ibd file.
5
- class Innodb::Space
6
- # InnoDB's default page size is 16KiB.
7
- DEFAULT_PAGE_SIZE = 16384
8
-
9
- # A map of InnoDB system space fixed-allocation pages. This can be used to
10
- # check whether a space is a system space or not, as non-system spaces will
11
- # not match this pattern.
12
- SYSTEM_SPACE_PAGE_MAP = {
13
- 0 => :FSP_HDR,
14
- 1 => :IBUF_BITMAP,
15
- 2 => :INODE,
16
- 3 => :SYS,
17
- 4 => :INDEX,
18
- 5 => :TRX_SYS,
19
- 6 => :SYS,
20
- 7 => :SYS,
21
- }
22
-
23
- class DataFile
24
- attr_reader :file
25
- attr_reader :size
26
- attr_reader :offset
5
+ module Innodb
6
+ class Space
7
+ # InnoDB's default page size is 16KiB.
8
+ DEFAULT_PAGE_SIZE = 16 * 1024
9
+
10
+ # The default extent size is 1 MiB defined originally as 64 pages.
11
+ DEFAULT_EXTENT_SIZE = 64 * DEFAULT_PAGE_SIZE
12
+
13
+ # A map of InnoDB system space fixed-allocation pages. This can be used to
14
+ # check whether a space is a system space or not, as non-system spaces will
15
+ # not match this pattern.
16
+ SYSTEM_SPACE_PAGE_MAP = {
17
+ 0 => :FSP_HDR,
18
+ 1 => :IBUF_BITMAP,
19
+ 2 => :INODE,
20
+ 3 => :SYS,
21
+ 4 => :INDEX,
22
+ 5 => :TRX_SYS,
23
+ 6 => :SYS,
24
+ 7 => :SYS,
25
+ }.freeze
26
+
27
+ XDES_LISTS = %i[
28
+ free
29
+ free_frag
30
+ full_frag
31
+ ].freeze
32
+
33
+ # An array of Innodb::Inode list names.
34
+ INODE_LISTS = %i[
35
+ full_inodes
36
+ free_inodes
37
+ ].freeze
38
+
39
+ class DataFile
40
+ attr_reader :file
41
+ attr_reader :size
42
+ attr_reader :offset
43
+
44
+ def initialize(filename, offset)
45
+ @file = File.open(filename)
46
+ @size = @file.stat.size
47
+ @offset = offset
48
+ end
27
49
 
28
- def initialize(filename, offset)
29
- @file = File.open(filename)
30
- @size = @file.stat.size
31
- @offset = offset
32
- end
50
+ def name
51
+ prefix = ""
52
+ prefix = "#{File.basename(File.dirname(file.path))}/" if File.extname(file.path) == ".ibd"
33
53
 
34
- def name
35
- prefix = ""
36
- if File.extname(file.path) == ".ibd"
37
- prefix = File.basename(File.dirname(file.path)) + "/"
54
+ prefix + File.basename(file.path)
38
55
  end
39
-
40
- prefix + File.basename(file.path)
41
56
  end
42
- end
43
-
44
- # Open a space file, optionally providing the page size to use. Pages
45
- # that aren't 16 KiB may not be supported well.
46
- def initialize(filenames)
47
- filenames = [filenames] unless filenames.is_a?(Array)
48
57
 
49
- @data_files = []
50
- @size = 0
51
- filenames.each do |filename|
52
- file = DataFile.new(filename, @size)
53
- @size += file.size
54
- @data_files << file
55
- end
58
+ # Open a space file, optionally providing the page size to use. Pages
59
+ # that aren't 16 KiB may not be supported well.
60
+ def initialize(filenames)
61
+ filenames = [filenames] unless filenames.is_a?(Array)
56
62
 
57
- @system_page_size = fsp_flags[:system_page_size]
58
- @page_size = fsp_flags[:page_size]
59
- @compressed = fsp_flags[:compressed]
63
+ @data_files = []
64
+ @size = 0
65
+ filenames.each do |filename|
66
+ file = DataFile.new(filename, @size)
67
+ @size += file.size
68
+ @data_files << file
69
+ end
60
70
 
61
- @pages = (@size / @page_size)
62
- @innodb_system = nil
63
- @record_describer = nil
64
- end
71
+ @system_page_size = fsp_flags.system_page_size
72
+ @page_size = fsp_flags.page_size
73
+ @compressed = fsp_flags.compressed
65
74
 
66
- # The Innodb::System to which this space belongs, if any.
67
- attr_accessor :innodb_system
75
+ @pages = (@size / @page_size)
76
+ @innodb_system = nil
77
+ @record_describer = nil
78
+ end
68
79
 
69
- # An object which can be used to describe records found in pages within
70
- # this space.
71
- attr_accessor :record_describer
80
+ # The Innodb::System to which this space belongs, if any.
81
+ attr_accessor :innodb_system
72
82
 
73
- # The system default page size (in bytes), equivalent to UNIV_PAGE_SIZE.
74
- attr_reader :system_page_size
83
+ # An object which can be used to describe records found in pages within
84
+ # this space.
85
+ attr_accessor :record_describer
75
86
 
76
- # The size (in bytes) of each page in the space.
77
- attr_reader :page_size
87
+ # The system default page size (in bytes), equivalent to UNIV_PAGE_SIZE.
88
+ attr_reader :system_page_size
78
89
 
79
- # The size (in bytes) of the space
80
- attr_reader :size
90
+ # The size (in bytes) of each page in the space.
91
+ attr_reader :page_size
81
92
 
82
- # The number of pages in the space.
83
- attr_reader :pages
93
+ # The size (in bytes) of the space
94
+ attr_reader :size
84
95
 
85
- # Return a string which can uniquely identify this space. Be careful not
86
- # to do anything which could instantiate a BufferCursor so that we can use
87
- # this method in cursor initialization.
88
- def name
89
- @name ||= @data_files.map { |f| f.name }.join(",")
90
- end
96
+ # The number of pages in the space.
97
+ attr_reader :pages
91
98
 
92
- def inspect
93
- "<%s file=%s, page_size=%i, pages=%i>" % [
94
- self.class.name,
95
- name.inspect,
96
- page_size,
97
- pages,
98
- ]
99
- end
99
+ # Return a string which can uniquely identify this space. Be careful not
100
+ # to do anything which could instantiate a BufferCursor so that we can use
101
+ # this method in cursor initialization.
102
+ def name
103
+ @name ||= @data_files.map(&:name).join(",")
104
+ end
100
105
 
101
- # Read the FSP header "flags" field by byte offset within the space file.
102
- # This is useful in order to initialize the page size, as we can't properly
103
- # read the FSP_HDR page before we know its size.
104
- def raw_fsp_header_flags
105
- # A simple sanity check. The FIL header should be initialized in page 0,
106
- # to offset 0 and page type :FSP_HDR (8).
107
- page_offset = BinData::Uint32be.read(read_at_offset(4, 4)).to_i
108
- page_type = BinData::Uint16be.read(read_at_offset(24, 2)).to_i
109
- unless page_offset == 0 && Innodb::Page::PAGE_TYPE_BY_VALUE[page_type] == :FSP_HDR
110
- raise "Something is very wrong; Page 0 does not seem to be type FSP_HDR; got page type %i but expected %i" % [
111
- page_type,
112
- Innodb::Page::PAGE_TYPE[:FSP_HDR][:value],
106
+ def inspect
107
+ "<%s file=%s, page_size=%i, pages=%i>" % [
108
+ self.class.name,
109
+ name.inspect,
110
+ page_size,
111
+ pages,
113
112
  ]
114
113
  end
115
114
 
116
- # Another sanity check. The Space ID should be the same in both the FIL
117
- # and FSP headers.
118
- fil_space = BinData::Uint32be.read(read_at_offset(34, 4)).to_i
119
- fsp_space = BinData::Uint32be.read(read_at_offset(38, 4)).to_i
120
- unless fil_space == fsp_space
121
- raise "Something is very wrong; FIL and FSP header Space IDs don't match: FIL is %i but FSP is %i" % [
122
- fil_space,
123
- fsp_space,
124
- ]
115
+ # Read the FSP header "flags" field by byte offset within the space file.
116
+ # This is useful in order to initialize the page size, as we can't properly
117
+ # read the FSP_HDR page before we know its size.
118
+ def raw_fsp_header_flags
119
+ # A simple sanity check. The FIL header should be initialized in page 0,
120
+ # to offset 0 and page type :FSP_HDR (8).
121
+ page_offset = BinData::Uint32be.read(read_at_offset(4, 4)).to_i
122
+ page_type = BinData::Uint16be.read(read_at_offset(24, 2)).to_i
123
+ unless page_offset.zero? && Innodb::Page::PAGE_TYPE_BY_VALUE[page_type] == :FSP_HDR
124
+ raise "Something is very wrong; Page 0 does not seem to be type FSP_HDR; got page type %i but expected %i" % [
125
+ page_type,
126
+ Innodb::Page::PAGE_TYPE[:FSP_HDR][:value],
127
+ ]
128
+ end
129
+
130
+ # Another sanity check. The Space ID should be the same in both the FIL
131
+ # and FSP headers.
132
+ fil_space = BinData::Uint32be.read(read_at_offset(34, 4)).to_i
133
+ fsp_space = BinData::Uint32be.read(read_at_offset(38, 4)).to_i
134
+ unless fil_space == fsp_space
135
+ raise "Something is very wrong; FIL and FSP header Space IDs do not match: FIL is %i but FSP is %i" % [
136
+ fil_space,
137
+ fsp_space,
138
+ ]
139
+ end
140
+
141
+ # Well, we're as sure as we can be. Read the flags field and decode it.
142
+ flags_value = BinData::Uint32be.read(read_at_offset(54, 4))
143
+ Innodb::Page::FspHdrXdes.decode_flags(flags_value)
125
144
  end
126
145
 
127
- # Well, we're as sure as we can be. Read the flags field and decode it.
128
- flags_value = BinData::Uint32be.read(read_at_offset(54, 4))
129
- Innodb::Page::FspHdrXdes.decode_flags(flags_value)
130
- end
146
+ # The FSP header flags, decoded. If the page size has not been initialized,
147
+ # reach into the raw bytes of the FSP_HDR page and attempt to decode the
148
+ # flags field that way.
149
+ def fsp_flags
150
+ return fsp.flags if @page_size
131
151
 
132
- # The FSP header flags, decoded. If the page size has not been initialized,
133
- # reach into the raw bytes of the FSP_HDR page and attempt to decode the
134
- # flags field that way.
135
- def fsp_flags
136
- if @page_size
137
- return fsp[:flags]
138
- else
139
152
  raw_fsp_header_flags
140
153
  end
141
- end
142
154
 
143
- # The number of pages per extent.
144
- def pages_per_extent
145
- # Note that uncompressed tables and compressed tables using the same page
146
- # size will have a different number of pages per "extent" because InnoDB
147
- # compression uses the FSP_EXTENT_SIZE define (which is then based on the
148
- # UNIV_PAGE_SIZE define, which may be based on the innodb_page_size system
149
- # variable) for compressed tables rather than something based on the actual
150
- # compressed page size.
151
- #
152
- # For this reason, an "extent" differs in size as follows (the maximum page
153
- # size supported for compressed tables is the innodb_page_size):
154
- #
155
- # innodb_page_size | innodb compression |
156
- # page size | extent size | pages | page size | extent size | pages |
157
- # 16384 | 1 MiB | 64 | 16384 | 1 MiB | 64 |
158
- # | 8192 | 512 KiB | 64 |
159
- # | 4096 | 256 KiB | 64 |
160
- # | 2048 | 128 KiB | 64 |
161
- # | 1024 | 64 KiB | 64 |
162
- # 8192 | 1 MiB | 128 | 8192 | 1 MiB | 128 |
163
- # | 4096 | 512 KiB | 128 |
164
- # | 2048 | 256 KiB | 128 |
165
- # | 1024 | 128 KiB | 128 |
166
- # 4096 | 1 MiB | 256 | 4096 | 1 MiB | 256 |
167
- # | 2048 | 512 KiB | 256 |
168
- # | 1024 | 256 KiB | 256 |
169
- #
170
-
171
- 1048576 / system_page_size
172
- end
155
+ # The number of pages per extent.
156
+ def pages_per_extent
157
+ # Note that uncompressed tables and compressed tables using the same page
158
+ # size will have a different number of pages per "extent" because InnoDB
159
+ # compression uses the FSP_EXTENT_SIZE define (which is then based on the
160
+ # UNIV_PAGE_SIZE define, which may be based on the innodb_page_size system
161
+ # variable) for compressed tables rather than something based on the actual
162
+ # compressed page size.
163
+ #
164
+ # For this reason, an "extent" differs in size as follows (the maximum page
165
+ # size supported for compressed tables is the innodb_page_size):
166
+ #
167
+ # innodb_page_size | innodb compression |
168
+ # page size | extent size | pages | page size | extent size | pages |
169
+ # 16384 | 1 MiB | 64 | 16384 | 1 MiB | 64 |
170
+ # | 8192 | 512 KiB | 64 |
171
+ # | 4096 | 256 KiB | 64 |
172
+ # | 2048 | 128 KiB | 64 |
173
+ # | 1024 | 64 KiB | 64 |
174
+ # 8192 | 1 MiB | 128 | 8192 | 1 MiB | 128 |
175
+ # | 4096 | 512 KiB | 128 |
176
+ # | 2048 | 256 KiB | 128 |
177
+ # | 1024 | 128 KiB | 128 |
178
+ # 4096 | 1 MiB | 256 | 4096 | 1 MiB | 256 |
179
+ # | 2048 | 512 KiB | 256 |
180
+ # | 1024 | 256 KiB | 256 |
181
+ #
173
182
 
174
- # The size (in bytes) of an extent.
175
- def extent_size
176
- pages_per_extent * page_size
177
- end
178
-
179
- # The number of pages per FSP_HDR/XDES/IBUF_BITMAP page. This is crudely
180
- # mapped to the page size, and works for pages down to 1KiB.
181
- def pages_per_bookkeeping_page
182
- page_size
183
- end
183
+ DEFAULT_EXTENT_SIZE / system_page_size
184
+ end
184
185
 
185
- # The FSP_HDR/XDES page which will contain the XDES entry for a given page.
186
- def xdes_page_for_page(page_number)
187
- page_number - (page_number % pages_per_bookkeeping_page)
188
- end
186
+ # The size (in bytes) of an extent.
187
+ def extent_size
188
+ pages_per_extent * page_size
189
+ end
189
190
 
190
- # The IBUF_BITMAP page which will contain the bitmap entry for a given page.
191
- def ibuf_bitmap_page_for_page(page_number)
192
- page_number - (page_number % pages_per_bookkeeping_page) + 1
193
- end
191
+ # The number of pages per FSP_HDR/XDES/IBUF_BITMAP page. This is crudely
192
+ # mapped to the page size, and works for pages down to 1KiB.
193
+ def pages_per_bookkeeping_page
194
+ page_size
195
+ end
194
196
 
195
- # The XDES entry offset for a given page within its FSP_HDR/XDES page's
196
- # XDES array.
197
- def xdes_entry_for_page(page_number)
198
- relative_page_number = page_number - xdes_page_for_page(page_number)
199
- relative_page_number / pages_per_extent
200
- end
197
+ # The FSP_HDR/XDES page which will contain the XDES entry for a given page.
198
+ def xdes_page_for_page(page_number)
199
+ page_number - (page_number % pages_per_bookkeeping_page)
200
+ end
201
201
 
202
- # Return the Innodb::Xdes entry which represents a given page.
203
- def xdes_for_page(page_number)
204
- xdes_array = page(xdes_page_for_page(page_number)).each_xdes.to_a
205
- xdes_array[xdes_entry_for_page(page_number)]
206
- end
202
+ # The IBUF_BITMAP page which will contain the bitmap entry for a given page.
203
+ def ibuf_bitmap_page_for_page(page_number)
204
+ page_number - (page_number % pages_per_bookkeeping_page) + 1
205
+ end
207
206
 
208
- def data_file_for_offset(offset)
209
- @data_files.each do |file|
210
- return file if offset < file.size
211
- offset -= file.size
207
+ # The XDES entry offset for a given page within its FSP_HDR/XDES page's
208
+ # XDES array.
209
+ def xdes_entry_for_page(page_number)
210
+ relative_page_number = page_number - xdes_page_for_page(page_number)
211
+ relative_page_number / pages_per_extent
212
212
  end
213
- nil
214
- end
215
213
 
216
- # Get the raw byte buffer of size bytes at offset in the file.
217
- def read_at_offset(offset, size)
218
- return nil unless offset < @size && (offset + size) <= @size
219
- data_file = data_file_for_offset(offset)
220
- data_file.file.seek(offset - data_file.offset)
221
- data_file.file.read(size)
222
- end
214
+ # Return the Innodb::Xdes entry which represents a given page.
215
+ def xdes_for_page(page_number)
216
+ xdes_array = page(xdes_page_for_page(page_number)).each_xdes.to_a
217
+ xdes_array[xdes_entry_for_page(page_number)]
218
+ end
223
219
 
224
- # Get the raw byte buffer for a specific page by page number.
225
- def page_data(page_number)
226
- read_at_offset(page_number * page_size, page_size)
227
- end
220
+ def data_file_for_offset(offset)
221
+ @data_files.each do |file|
222
+ return file if offset < file.size
228
223
 
229
- # Get an Innodb::Page object for a specific page by page number.
230
- def page(page_number)
231
- data = page_data(page_number)
232
- if data
233
- Innodb::Page.parse(self, data, page_number)
234
- else
224
+ offset -= file.size
225
+ end
235
226
  nil
236
227
  end
237
- end
238
228
 
239
- # Determine whether this space looks like a system space. If the initial
240
- # pages in the space match the SYSTEM_SPACE_PAGE_MAP, it is likely to be
241
- # a system space.
242
- def system_space?
243
- SYSTEM_SPACE_PAGE_MAP.each do |page_number, type|
244
- # We can't use page() here, because system_space? need to be used
245
- # in the Innodb::Page::Sys.parse to determine what type of page
246
- # is being looked at. Using page() would cause us to keep recurse
247
- # infinitely. Use Innodb::Page.new instead to load the page as
248
- # simply as possible.
249
- test_page = Innodb::Page.new(self, page_data(page_number))
250
- return false unless test_page.type == type
251
- end
252
- true
253
- end
254
-
255
- # Return the page number for the space's FSP_HDR page.
256
- def page_fsp_hdr
257
- 0
258
- end
259
-
260
- # Get (and cache) the FSP header from the FSP_HDR page.
261
- def fsp
262
- @fsp ||= page(page_fsp_hdr).fsp_header
263
- end
264
-
265
- def space_id
266
- fsp[:space_id]
267
- end
229
+ # Get the raw byte buffer of size bytes at offset in the file.
230
+ def read_at_offset(offset, size)
231
+ return nil unless offset < @size && (offset + size) <= @size
268
232
 
269
- # Return the page number for the space's TRX_SYS page.
270
- def page_trx_sys
271
- 5
272
- end
273
-
274
- # Get the Innodb::Page::TrxSys page for a system space.
275
- def trx_sys
276
- page(page_trx_sys) if system_space?
277
- end
233
+ data_file = data_file_for_offset(offset)
234
+ data_file.file.seek(offset - data_file.offset)
235
+ data_file.file.read(size)
236
+ end
278
237
 
279
- def rseg_page?(page_number)
280
- if trx_sys
281
- rseg_match = trx_sys.rsegs.select { |rseg|
282
- rseg[:space_id] == 0 && rseg[:page_number] == page_number
283
- }
238
+ # Get the raw byte buffer for a specific page by page number.
239
+ def page_data(page_number)
240
+ read_at_offset(page_number * page_size, page_size)
241
+ end
284
242
 
285
- ! rseg_match.empty?
243
+ # Get an Innodb::Page object for a specific page by page number.
244
+ def page(page_number)
245
+ data = page_data(page_number)
246
+ Innodb::Page.parse(self, data, page_number) if data
286
247
  end
287
- end
288
248
 
289
- # Return the page number for the space's SYS data dictionary header.
290
- def page_sys_data_dictionary
291
- 7
292
- end
249
+ # Determine whether this space looks like a system space. If the initial
250
+ # pages in the space match the SYSTEM_SPACE_PAGE_MAP, it is likely to be
251
+ # a system space.
252
+ def system_space?
253
+ SYSTEM_SPACE_PAGE_MAP.each do |page_number, type|
254
+ # We can't use page() here, because system_space? need to be used
255
+ # in the Innodb::Page::Sys.parse to determine what type of page
256
+ # is being looked at. Using page() would cause us to keep recurse
257
+ # infinitely. Use Innodb::Page.new instead to load the page as
258
+ # simply as possible.
259
+ test_page = Innodb::Page.new(self, page_data(page_number))
260
+ return false unless test_page.type == type
261
+ end
262
+ true
263
+ end
293
264
 
294
- # Get the Innodb::Page::SysDataDictionaryHeader page for a system space.
295
- def data_dictionary_page
296
- page(page_sys_data_dictionary) if system_space?
297
- end
265
+ # Return the page number for the space's FSP_HDR page.
266
+ def page_fsp_hdr
267
+ 0
268
+ end
298
269
 
299
- # Get an Innodb::List object for a specific list by list name.
300
- def list(name)
301
- if xdes_lists.include?(name) || inode_lists.include?(name)
302
- fsp[name]
270
+ # Get (and cache) the FSP header from the FSP_HDR page.
271
+ def fsp
272
+ @fsp ||= page(page_fsp_hdr).fsp_header
303
273
  end
304
- end
305
274
 
306
- # Get an Innodb::Index object for a specific index by root page number.
307
- def index(root_page_number, record_describer=nil)
308
- Innodb::Index.new(self, root_page_number,
309
- record_describer || @record_describer)
310
- end
275
+ def space_id
276
+ fsp[:space_id]
277
+ end
311
278
 
312
- # Iterate through all root page numbers for indexes in the space.
313
- def each_index_root_page_number
314
- unless block_given?
315
- return enum_for(:each_index_root_page_number)
279
+ # Return the page number for the space's TRX_SYS page.
280
+ def page_trx_sys
281
+ 5
316
282
  end
317
283
 
318
- if innodb_system
319
- # Retrieve the index root page numbers from the data dictionary.
320
- innodb_system.data_dictionary.each_index_by_space_id(space_id) do |record|
321
- yield record["PAGE_NO"]
322
- end
323
- else
324
- # Guess that the index root pages will be present starting at page 3,
325
- # and walk forward until we find a non-root page. This should work fine
326
- # for IBD files, if they haven't added indexes online.
327
- (3...@pages).each do |page_number|
328
- page = page(page_number)
329
- if page.type == :INDEX && page.root?
330
- yield page_number
331
- end
332
- end
284
+ # Get the Innodb::Page::TrxSys page for a system space.
285
+ def trx_sys
286
+ page(page_trx_sys) if system_space?
333
287
  end
334
288
 
335
- nil
336
- end
289
+ def rseg_page?(page_number)
290
+ return false unless trx_sys
337
291
 
338
- # Iterate through all indexes in the space.
339
- def each_index
340
- unless block_given?
341
- return enum_for(:each_index)
292
+ !trx_sys.rsegs.select { |rseg| rseg.space_id.zero? && rseg.page_number == page_number }.empty?
342
293
  end
343
294
 
344
- each_index_root_page_number do |page_number|
345
- yield index(page_number)
295
+ # Return the page number for the space's SYS data dictionary header.
296
+ def page_sys_data_dictionary
297
+ 7
346
298
  end
347
299
 
348
- nil
349
- end
350
-
351
- # An array of Innodb::Inode list names.
352
- def inode_lists
353
- [:full_inodes, :free_inodes]
354
- end
355
-
356
- # Iterate through Innodb::Inode lists in the space.
357
- def each_inode_list
358
- unless block_given?
359
- return enum_for(:each_inode_list)
300
+ # Get the Innodb::Page::SysDataDictionaryHeader page for a system space.
301
+ def data_dictionary_page
302
+ page(page_sys_data_dictionary) if system_space?
360
303
  end
361
304
 
362
- inode_lists.each do |name|
363
- yield name, list(name)
305
+ # Get an Innodb::List object for a specific list by list name.
306
+ def list(name)
307
+ fsp[name] if XDES_LISTS.include?(name) || INODE_LISTS.include?(name)
364
308
  end
365
- end
366
309
 
367
- # Iterate through Innodb::Inode objects in the space.
368
- def each_inode
369
- unless block_given?
370
- return enum_for(:each_inode)
310
+ # Get an Innodb::Index object for a specific index by root page number.
311
+ def index(root_page_number, record_describer = nil)
312
+ Innodb::Index.new(self, root_page_number, record_describer || @record_describer)
371
313
  end
372
314
 
373
- each_inode_list.each do |name, list|
374
- list.each do |page|
375
- page.each_allocated_inode do |inode|
376
- yield inode
315
+ # Iterate through all root page numbers for indexes in the space.
316
+ def each_index_root_page_number
317
+ return enum_for(:each_index_root_page_number) unless block_given?
318
+
319
+ if innodb_system
320
+ # Retrieve the index root page numbers from the data dictionary.
321
+ innodb_system.data_dictionary.each_index_by_space_id(space_id) do |record|
322
+ yield record["PAGE_NO"]
323
+ end
324
+ else
325
+ # Guess that the index root pages will be present starting at page 3,
326
+ # and walk forward until we find a non-root page. This should work fine
327
+ # for IBD files, if they haven't added indexes online.
328
+ (3...@pages).each do |page_number|
329
+ page = page(page_number)
330
+ yield page_number if page.type == :INDEX && page.root?
377
331
  end
378
332
  end
333
+
334
+ nil
379
335
  end
380
- end
381
336
 
382
- # Return an Inode by fseg_id. Iterates through the inode list, but it
383
- # normally is fairly small, so should be relatively efficient.
384
- def inode(fseg_id)
385
- each_inode.select { |inode| inode.fseg_id == fseg_id }.first
386
- end
337
+ # Iterate through all indexes in the space.
338
+ def each_index
339
+ return enum_for(:each_index) unless block_given?
387
340
 
388
- # Iterate through the page numbers in the doublewrite buffer.
389
- def each_doublewrite_page_number
390
- return nil unless system_space?
341
+ each_index_root_page_number do |page_number|
342
+ yield index(page_number)
343
+ end
391
344
 
392
- unless block_given?
393
- return enum_for(:each_doublewrite_page_number)
345
+ nil
394
346
  end
395
347
 
396
- trx_sys.doublewrite[:page_info][0][:page_number].each do |start_page|
397
- (start_page...(start_page+pages_per_extent)).each do |page_number|
398
- yield page_number
348
+ # Iterate through Innodb::Inode lists in the space.
349
+ def each_inode_list
350
+ return enum_for(:each_inode_list) unless block_given?
351
+
352
+ INODE_LISTS.each do |name|
353
+ yield name, list(name)
399
354
  end
400
355
  end
401
- end
402
356
 
403
- # Return true if a page is in the doublewrite buffer.
404
- def doublewrite_page?(page_number)
405
- return false unless system_space?
406
- @doublewrite_pages ||= each_doublewrite_page_number.to_a
407
- @doublewrite_pages.include?(page_number)
408
- end
357
+ # Iterate through Innodb::Inode objects in the space.
358
+ def each_inode(&block)
359
+ return enum_for(:each_inode) unless block_given?
409
360
 
410
- # Iterate through all pages in a space, returning the page number and an
411
- # Innodb::Page object for each one.
412
- def each_page(start_page=0)
413
- unless block_given?
414
- return enum_for(:each_page, start_page)
361
+ each_inode_list.each do |_name, list|
362
+ list.each do |page|
363
+ page.each_allocated_inode(&block)
364
+ end
365
+ end
415
366
  end
416
367
 
417
- (start_page...@pages).each do |page_number|
418
- current_page = page(page_number)
419
- yield page_number, current_page if current_page
368
+ # Return an Inode by fseg_id. Iterates through the inode list, but it
369
+ # normally is fairly small, so should be relatively efficient.
370
+ def inode(fseg_id)
371
+ each_inode.select { |inode| inode.fseg_id == fseg_id }.first
420
372
  end
421
- end
422
373
 
423
- # An array of Innodb::Xdes list names.
424
- def xdes_lists
425
- [:free, :free_frag, :full_frag]
426
- end
374
+ # Iterate through the page numbers in the doublewrite buffer.
375
+ def each_doublewrite_page_number(&block)
376
+ return nil unless system_space?
377
+ return enum_for(:each_doublewrite_page_number) unless block_given?
427
378
 
428
- # Iterate through Innodb::Xdes lists in the space.
429
- def each_xdes_list
430
- unless block_given?
431
- return enum_for(:each_xdes_list)
379
+ trx_sys.doublewrite[:page_info][0][:page_number].each do |start_page|
380
+ (start_page...(start_page + pages_per_extent)).each(&block)
381
+ end
432
382
  end
433
383
 
434
- xdes_lists.each do |name|
435
- yield name, list(name)
436
- end
437
- end
384
+ # Return true if a page is in the doublewrite buffer.
385
+ def doublewrite_page?(page_number)
386
+ return false unless system_space?
438
387
 
439
- # An array of all FSP/XDES page numbers for the space.
440
- def each_xdes_page_number
441
- unless block_given?
442
- return enum_for(:each_xdes_page_number)
388
+ @doublewrite_pages ||= each_doublewrite_page_number.to_a
389
+ @doublewrite_pages.include?(page_number)
443
390
  end
444
391
 
445
- 0.step(pages - 1, pages_per_bookkeeping_page).each do |n|
446
- yield n
447
- end
448
- end
392
+ # Iterate through all pages in a space, returning the page number and an
393
+ # Innodb::Page object for each one.
394
+ def each_page(start_page = 0)
395
+ return enum_for(:each_page, start_page) unless block_given?
449
396
 
450
- # Iterate through all FSP_HDR/XDES pages, returning an Innodb::Page object
451
- # for each one.
452
- def each_xdes_page
453
- unless block_given?
454
- return enum_for(:each_xdes_page)
397
+ (start_page...@pages).each do |page_number|
398
+ current_page = page(page_number)
399
+ yield page_number, current_page if current_page
400
+ end
455
401
  end
456
402
 
457
- each_xdes_page_number do |page_number|
458
- current_page = page(page_number)
459
- yield current_page if current_page and [:FSP_HDR, :XDES].include?(current_page.type)
460
- end
461
- end
403
+ # Iterate through Innodb::Xdes lists in the space.
404
+ def each_xdes_list
405
+ return enum_for(:each_xdes_list) unless block_given?
462
406
 
463
- # Iterate through all extent descriptors for the space, returning an
464
- # Innodb::Xdes object for each one.
465
- def each_xdes
466
- unless block_given?
467
- return enum_for(:each_xdes)
407
+ XDES_LISTS.each do |name|
408
+ yield name, list(name)
409
+ end
468
410
  end
469
411
 
470
- each_xdes_page do |xdes_page|
471
- xdes_page.each_xdes do |xdes|
472
- # Only return initialized XDES entries; :state will be nil for extents
473
- # that have not been allocated yet.
474
- yield xdes if xdes.xdes[:state]
475
- end
412
+ # An array of all FSP/XDES page numbers for the space.
413
+ def each_xdes_page_number(&block)
414
+ return enum_for(:each_xdes_page_number) unless block_given?
415
+
416
+ 0.step(pages - 1, pages_per_bookkeeping_page).each(&block)
476
417
  end
477
- end
478
418
 
479
- # Iterate through all pages, yielding the page number, page object,
480
- # and page status.
481
- def each_page_status(start_page=0)
482
- unless block_given?
483
- return enum_for(:each_page_with_status, start_page)
419
+ # Iterate through all extent descriptor pages, returning an Innodb::Page object
420
+ # for each one.
421
+ def each_xdes_page
422
+ return enum_for(:each_xdes_page) unless block_given?
423
+
424
+ each_xdes_page_number do |page_number|
425
+ current_page = page(page_number)
426
+ yield current_page if current_page&.extent_descriptor?
427
+ end
484
428
  end
485
429
 
486
- each_xdes do |xdes|
487
- xdes.each_page_status do |page_number, page_status|
488
- next if page_number < start_page
489
- next if page_number >= @pages
430
+ # Iterate through all extent descriptors for the space, returning an
431
+ # Innodb::Xdes object for each one.
432
+ def each_xdes
433
+ return enum_for(:each_xdes) unless block_given?
490
434
 
491
- if this_page = page(page_number)
492
- yield page_number, this_page, page_status
435
+ each_xdes_page do |xdes_page|
436
+ xdes_page.each_xdes do |xdes|
437
+ # Only return initialized XDES entries; :state will be nil for extents
438
+ # that have not been allocated yet.
439
+ yield xdes if xdes.xdes[:state]
493
440
  end
494
441
  end
495
442
  end
496
- end
497
443
 
498
- # A helper to produce a printable page type.
499
- def type_for_page(page, page_status)
500
- page_status[:free] ? "FREE (#{page.type})" : page.type
501
- end
444
+ # Iterate through all pages, yielding the page number, page object,
445
+ # and page status.
446
+ def each_page_status(start_page = 0)
447
+ return enum_for(:each_page_status, start_page) unless block_given?
448
+
449
+ each_xdes do |xdes|
450
+ xdes.each_page_status do |page_number, page_status|
451
+ next if page_number < start_page
452
+ next if page_number >= @pages
502
453
 
503
- # Iterate through unique regions in the space by page type. This is useful
504
- # to achieve an overall view of the space.
505
- def each_page_type_region(start_page=0)
506
- unless block_given?
507
- return enum_for(:each_page_type_region, start_page)
454
+ if (this_page = page(page_number))
455
+ yield page_number, this_page, page_status
456
+ end
457
+ end
458
+ end
508
459
  end
509
460
 
510
- region = nil
511
- each_page_status(start_page) do |page_number, page, page_status|
512
- page_type = type_for_page(page, page_status)
513
- if region && region[:type] == page_type
514
- region[:end] = page_number
515
- region[:count] += 1
516
- else
517
- yield region if region
518
- region = {
519
- :start => page_number,
520
- :end => page_number,
521
- :type => page_type,
522
- :count => 1,
523
- }
461
+ # A helper to produce a printable page type.
462
+ def type_for_page(page, page_status)
463
+ page_status[:free] ? "FREE (#{page.type})" : page.type
464
+ end
465
+
466
+ # Iterate through unique regions in the space by page type. This is useful
467
+ # to achieve an overall view of the space.
468
+ def each_page_type_region(start_page = 0)
469
+ return enum_for(:each_page_type_region, start_page) unless block_given?
470
+
471
+ region = nil
472
+ each_page_status(start_page) do |page_number, page, page_status|
473
+ page_type = type_for_page(page, page_status)
474
+ if region && region[:type] == page_type
475
+ region[:end] = page_number
476
+ region[:count] += 1
477
+ else
478
+ yield region if region
479
+ region = {
480
+ start: page_number,
481
+ end: page_number,
482
+ type: page_type,
483
+ count: 1,
484
+ }
485
+ end
524
486
  end
487
+ yield region if region
525
488
  end
526
- yield region if region
527
489
  end
528
490
  end