innodb_ruby 0.9.13 → 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 +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +14 -19
  4. data/bin/innodb_space +592 -745
  5. data/lib/innodb.rb +5 -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 -325
  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 -275
  16. data/lib/innodb/inode.rb +166 -124
  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 +446 -291
  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 +965 -924
  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 -164
  38. data/lib/innodb/record_describer.rb +66 -68
  39. data/lib/innodb/space.rb +386 -391
  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 +112 -21
@@ -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,495 +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
-
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
173
154
 
174
- # The size (in bytes) of an extent.
175
- def extent_size
176
- pages_per_extent * page_size
177
- 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
+ #
178
182
 
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
184
-
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
183
+ DEFAULT_EXTENT_SIZE / system_page_size
184
+ end
189
185
 
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
186
+ # The size (in bytes) of an extent.
187
+ def extent_size
188
+ pages_per_extent * page_size
189
+ end
194
190
 
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
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
201
196
 
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
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
207
201
 
208
- def data_file_for_offset(offset)
209
- @data_files.each do |file|
210
- return file if offset < file.size
211
- offset -= file.size
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
212
205
  end
213
- nil
214
- end
215
206
 
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
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
+ end
223
213
 
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
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
228
219
 
229
- # Get an Innodb::Page object for a specific page by page number.
230
- def page(page_number)
231
- Innodb::Page.parse(self, page_data(page_number))
232
- end
220
+ def data_file_for_offset(offset)
221
+ @data_files.each do |file|
222
+ return file if offset < file.size
233
223
 
234
- # Determine whether this space looks like a system space. If the initial
235
- # pages in the space match the SYSTEM_SPACE_PAGE_MAP, it is likely to be
236
- # a system space.
237
- def system_space?
238
- SYSTEM_SPACE_PAGE_MAP.each do |page_number, type|
239
- # We can't use page() here, because system_space? need to be used
240
- # in the Innodb::Page::Sys.parse to determine what type of page
241
- # is being looked at. Using page() would cause us to keep recurse
242
- # infinitely. Use Innodb::Page.new instead to load the page as
243
- # simply as possible.
244
- test_page = Innodb::Page.new(self, page_data(page_number))
245
- return false unless test_page.type == type
246
- end
247
- true
248
- end
224
+ offset -= file.size
225
+ end
226
+ nil
227
+ end
249
228
 
250
- # Return the page number for the space's FSP_HDR page.
251
- def page_fsp_hdr
252
- 0
253
- 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
254
232
 
255
- # Get (and cache) the FSP header from the FSP_HDR page.
256
- def fsp
257
- @fsp ||= page(page_fsp_hdr).fsp_header
258
- 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
259
237
 
260
- def space_id
261
- fsp[:space_id]
262
- end
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
263
242
 
264
- # Return the page number for the space's TRX_SYS page.
265
- def page_trx_sys
266
- 5
267
- end
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
247
+ end
268
248
 
269
- # Get the Innodb::Page::TrxSys page for a system space.
270
- def trx_sys
271
- page(page_trx_sys) if system_space?
272
- 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
273
264
 
274
- def rseg_page?(page_number)
275
- if trx_sys
276
- rseg_match = trx_sys.rsegs.select { |rseg|
277
- rseg[:space_id] == 0 && rseg[:page_number] == page_number
278
- }
265
+ # Return the page number for the space's FSP_HDR page.
266
+ def page_fsp_hdr
267
+ 0
268
+ end
279
269
 
280
- ! rseg_match.empty?
270
+ # Get (and cache) the FSP header from the FSP_HDR page.
271
+ def fsp
272
+ @fsp ||= page(page_fsp_hdr).fsp_header
281
273
  end
282
- end
283
274
 
284
- # Return the page number for the space's SYS data dictionary header.
285
- def page_sys_data_dictionary
286
- 7
287
- end
275
+ def space_id
276
+ fsp[:space_id]
277
+ end
288
278
 
289
- # Get the Innodb::Page::SysDataDictionaryHeader page for a system space.
290
- def data_dictionary_page
291
- page(page_sys_data_dictionary) if system_space?
292
- end
279
+ # Return the page number for the space's TRX_SYS page.
280
+ def page_trx_sys
281
+ 5
282
+ end
293
283
 
294
- # Get an Innodb::List object for a specific list by list name.
295
- def list(name)
296
- if xdes_lists.include?(name) || inode_lists.include?(name)
297
- fsp[name]
284
+ # Get the Innodb::Page::TrxSys page for a system space.
285
+ def trx_sys
286
+ page(page_trx_sys) if system_space?
298
287
  end
299
- end
300
288
 
301
- # Get an Innodb::Index object for a specific index by root page number.
302
- def index(root_page_number, record_describer=nil)
303
- Innodb::Index.new(self, root_page_number,
304
- record_describer || @record_describer)
305
- end
289
+ def rseg_page?(page_number)
290
+ return false unless trx_sys
306
291
 
307
- # Iterate through all root page numbers for indexes in the space.
308
- def each_index_root_page_number
309
- unless block_given?
310
- return enum_for(:each_index_root_page_number)
292
+ !trx_sys.rsegs.select { |rseg| rseg.space_id.zero? && rseg.page_number == page_number }.empty?
311
293
  end
312
294
 
313
- if innodb_system
314
- # Retrieve the index root page numbers from the data dictionary.
315
- innodb_system.data_dictionary.each_index_by_space_id(space_id) do |record|
316
- yield record["PAGE_NO"]
317
- end
318
- else
319
- # Guess that the index root pages will be present starting at page 3,
320
- # and walk forward until we find a non-root page. This should work fine
321
- # for IBD files, if they haven't added indexes online.
322
- (3...@pages).each do |page_number|
323
- page = page(page_number)
324
- if page.type == :INDEX && page.root?
325
- yield page_number
326
- end
327
- end
295
+ # Return the page number for the space's SYS data dictionary header.
296
+ def page_sys_data_dictionary
297
+ 7
328
298
  end
329
299
 
330
- nil
331
- end
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?
303
+ end
332
304
 
333
- # Iterate through all indexes in the space.
334
- def each_index
335
- unless block_given?
336
- return enum_for(:each_index)
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)
337
308
  end
338
309
 
339
- each_index_root_page_number do |page_number|
340
- yield index(page_number)
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)
341
313
  end
342
314
 
343
- nil
344
- end
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?
345
318
 
346
- # An array of Innodb::Inode list names.
347
- def inode_lists
348
- [:full_inodes, :free_inodes]
349
- end
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?
331
+ end
332
+ end
350
333
 
351
- # Iterate through Innodb::Inode lists in the space.
352
- def each_inode_list
353
- unless block_given?
354
- return enum_for(:each_inode_list)
334
+ nil
355
335
  end
356
336
 
357
- inode_lists.each do |name|
358
- yield name, list(name)
337
+ # Iterate through all indexes in the space.
338
+ def each_index
339
+ return enum_for(:each_index) unless block_given?
340
+
341
+ each_index_root_page_number do |page_number|
342
+ yield index(page_number)
343
+ end
344
+
345
+ nil
359
346
  end
360
- end
361
347
 
362
- # Iterate through Innodb::Inode objects in the space.
363
- def each_inode
364
- unless block_given?
365
- return enum_for(:each_inode)
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)
354
+ end
366
355
  end
367
356
 
368
- each_inode_list.each do |name, list|
369
- list.each do |page|
370
- page.each_allocated_inode do |inode|
371
- yield inode
357
+ # Iterate through Innodb::Inode objects in the space.
358
+ def each_inode(&block)
359
+ return enum_for(:each_inode) unless block_given?
360
+
361
+ each_inode_list.each do |_name, list|
362
+ list.each do |page|
363
+ page.each_allocated_inode(&block)
372
364
  end
373
365
  end
374
366
  end
375
- end
376
367
 
377
- # Iterate through all pages in a space, returning the page number and an
378
- # Innodb::Page object for each one.
379
- def each_page(start_page=0)
380
- unless block_given?
381
- return enum_for(:each_page, start_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
382
372
  end
383
373
 
384
- (start_page...@pages).each do |page_number|
385
- current_page = page(page_number)
386
- yield page_number, current_page if current_page
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?
378
+
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
387
382
  end
388
- end
389
383
 
390
- # An array of Innodb::Xdes list names.
391
- def xdes_lists
392
- [:free, :free_frag, :full_frag]
393
- end
384
+ # Return true if a page is in the doublewrite buffer.
385
+ def doublewrite_page?(page_number)
386
+ return false unless system_space?
394
387
 
395
- # Iterate through Innodb::Xdes lists in the space.
396
- def each_xdes_list
397
- unless block_given?
398
- return enum_for(:each_xdes_list)
388
+ @doublewrite_pages ||= each_doublewrite_page_number.to_a
389
+ @doublewrite_pages.include?(page_number)
399
390
  end
400
391
 
401
- xdes_lists.each do |name|
402
- yield name, list(name)
403
- end
404
- 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?
405
396
 
406
- # An array of all FSP/XDES page numbers for the space.
407
- def each_xdes_page_number
408
- unless block_given?
409
- return enum_for(:each_xdes_page_number)
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
410
401
  end
411
402
 
412
- 0.step(pages - 1, pages_per_bookkeeping_page).each do |n|
413
- yield n
414
- end
415
- 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?
416
406
 
417
- # Iterate through all FSP_HDR/XDES pages, returning an Innodb::Page object
418
- # for each one.
419
- def each_xdes_page
420
- unless block_given?
421
- return enum_for(:each_xdes_page)
407
+ XDES_LISTS.each do |name|
408
+ yield name, list(name)
409
+ end
422
410
  end
423
411
 
424
- each_xdes_page_number do |page_number|
425
- current_page = page(page_number)
426
- yield current_page if current_page and [:FSP_HDR, :XDES].include?(current_page.type)
427
- end
428
- 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?
429
415
 
430
- # Iterate through all extent descriptors for the space, returning an
431
- # Innodb::Xdes object for each one.
432
- def each_xdes
433
- unless block_given?
434
- return enum_for(:each_xdes)
416
+ 0.step(pages - 1, pages_per_bookkeeping_page).each(&block)
435
417
  end
436
418
 
437
- each_xdes_page do |xdes_page|
438
- xdes_page.each_xdes do |xdes|
439
- # Only return initialized XDES entries; :state will be nil for extents
440
- # that have not been allocated yet.
441
- yield xdes if xdes.xdes[:state]
442
- end
443
- end
444
- end
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?
445
423
 
446
- # Iterate through all pages, yielding the page number, page object,
447
- # and page status.
448
- def each_page_status(start_page=0)
449
- unless block_given?
450
- return enum_for(:each_page_with_status, start_page)
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
451
428
  end
452
429
 
453
- each_xdes do |xdes|
454
- xdes.each_page_status do |page_number, page_status|
455
- next if page_number < start_page
456
- 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?
457
434
 
458
- if this_page = page(page_number)
459
- 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]
460
440
  end
461
441
  end
462
442
  end
463
- end
464
443
 
465
- # A helper to produce a printable page type.
466
- def type_for_page(page, page_status)
467
- page_status[:free] ? "FREE (#{page.type})" : page.type
468
- 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
469
453
 
470
- # Iterate through unique regions in the space by page type. This is useful
471
- # to achieve an overall view of the space.
472
- def each_page_type_region(start_page=0)
473
- unless block_given?
474
- 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
475
459
  end
476
460
 
477
- region = nil
478
- each_page_status(start_page) do |page_number, page, page_status|
479
- page_type = type_for_page(page, page_status)
480
- if region && region[:type] == page_type
481
- region[:end] = page_number
482
- region[:count] += 1
483
- else
484
- yield region if region
485
- region = {
486
- :start => page_number,
487
- :end => page_number,
488
- :type => page_type,
489
- :count => 1,
490
- }
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
491
486
  end
487
+ yield region if region
492
488
  end
493
- yield region if region
494
489
  end
495
490
  end