innodb_ruby 0.9.13 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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