innodb_ruby 0.9.14 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +13 -18
  4. data/bin/innodb_space +654 -778
  5. data/lib/innodb/checksum.rb +26 -24
  6. data/lib/innodb/data_dictionary.rb +490 -550
  7. data/lib/innodb/data_type.rb +362 -325
  8. data/lib/innodb/field.rb +102 -89
  9. data/lib/innodb/fseg_entry.rb +22 -26
  10. data/lib/innodb/history.rb +21 -21
  11. data/lib/innodb/history_list.rb +72 -76
  12. data/lib/innodb/ibuf_bitmap.rb +36 -36
  13. data/lib/innodb/ibuf_index.rb +6 -2
  14. data/lib/innodb/index.rb +245 -276
  15. data/lib/innodb/inode.rb +166 -124
  16. data/lib/innodb/list.rb +196 -183
  17. data/lib/innodb/log.rb +139 -110
  18. data/lib/innodb/log_block.rb +100 -91
  19. data/lib/innodb/log_group.rb +53 -64
  20. data/lib/innodb/log_reader.rb +97 -96
  21. data/lib/innodb/log_record.rb +328 -279
  22. data/lib/innodb/lsn.rb +86 -81
  23. data/lib/innodb/page/blob.rb +82 -83
  24. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  25. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  26. data/lib/innodb/page/index.rb +965 -924
  27. data/lib/innodb/page/index_compressed.rb +34 -34
  28. data/lib/innodb/page/inode.rb +103 -112
  29. data/lib/innodb/page/sys.rb +13 -15
  30. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  31. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  32. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  33. data/lib/innodb/page/trx_sys.rb +204 -182
  34. data/lib/innodb/page/undo_log.rb +106 -92
  35. data/lib/innodb/page.rb +417 -414
  36. data/lib/innodb/record.rb +121 -164
  37. data/lib/innodb/record_describer.rb +66 -68
  38. data/lib/innodb/space.rb +381 -413
  39. data/lib/innodb/stats.rb +33 -35
  40. data/lib/innodb/system.rb +149 -171
  41. data/lib/innodb/undo_log.rb +129 -107
  42. data/lib/innodb/undo_record.rb +255 -247
  43. data/lib/innodb/util/buffer_cursor.rb +81 -79
  44. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  45. data/lib/innodb/version.rb +2 -2
  46. data/lib/innodb/xdes.rb +144 -142
  47. data/lib/innodb.rb +4 -5
  48. metadata +100 -25
@@ -1,95 +1,109 @@
1
- # -*- encoding : utf-8 -*-
2
-
3
- class Innodb::Page::UndoLog < Innodb::Page
4
- def pos_undo_page_header
5
- pos_page_body
6
- end
7
-
8
- def size_undo_page_header
9
- 2 + 2 + 2 + Innodb::List::NODE_SIZE
10
- end
11
-
12
- def pos_undo_segment_header
13
- pos_undo_page_header + size_undo_page_header
14
- end
15
-
16
- def size_undo_segment_header
17
- 2 + 2 + Innodb::FsegEntry::SIZE + Innodb::List::BASE_NODE_SIZE
18
- end
19
-
20
- def pos_undo_logs
21
- pos_undo_segment_header + size_undo_segment_header
22
- end
23
-
24
- UNDO_PAGE_TYPES = {
25
- 1 => :insert,
26
- 2 => :update,
27
- }
28
-
29
- UNDO_SEGMENT_STATES = {
30
- 1 => :active,
31
- 2 => :cached,
32
- 3 => :to_free,
33
- 4 => :to_purge,
34
- 5 => :prepared,
35
- }
36
-
37
- def undo_page_header
38
- @undo_page_header ||=
39
- cursor(pos_undo_page_header).name("undo_page_header") do |c|
40
- {
41
- :type => c.name("type") { UNDO_PAGE_TYPES[c.get_uint16] },
42
- :latest_log_record_offset => c.name("latest_log_record_offset") { c.get_uint16 },
43
- :free_offset => c.name("free_offset") { c.get_uint16 },
44
- :page_list_node => c.name("page_list") { Innodb::List.get_node(c) },
45
- }
1
+ # frozen_string_literal: true
2
+
3
+ module Innodb
4
+ class Page
5
+ class UndoLog < Page
6
+ specialization_for :UNDO_LOG
7
+
8
+ PageHeader = Struct.new(
9
+ :type,
10
+ :latest_log_record_offset,
11
+ :free_offset,
12
+ :page_list_node,
13
+ keyword_init: true
14
+ )
15
+
16
+ SegmentHeader = Struct.new(
17
+ :state,
18
+ :last_log_offset,
19
+ :fseg,
20
+ :page_list,
21
+ keyword_init: true
22
+ )
23
+
24
+ def pos_undo_page_header
25
+ pos_page_body
26
+ end
27
+
28
+ def size_undo_page_header
29
+ 2 + 2 + 2 + Innodb::List::NODE_SIZE
30
+ end
31
+
32
+ def pos_undo_segment_header
33
+ pos_undo_page_header + size_undo_page_header
34
+ end
35
+
36
+ def size_undo_segment_header
37
+ 2 + 2 + Innodb::FsegEntry::SIZE + Innodb::List::BASE_NODE_SIZE
38
+ end
39
+
40
+ def pos_undo_logs
41
+ pos_undo_segment_header + size_undo_segment_header
42
+ end
43
+
44
+ UNDO_PAGE_TYPES = {
45
+ 1 => :insert,
46
+ 2 => :update,
47
+ }.freeze
48
+
49
+ UNDO_SEGMENT_STATES = {
50
+ 1 => :active,
51
+ 2 => :cached,
52
+ 3 => :to_free,
53
+ 4 => :to_purge,
54
+ 5 => :prepared,
55
+ }.freeze
56
+
57
+ def undo_page_header
58
+ @undo_page_header ||= cursor(pos_undo_page_header).name("undo_page_header") do |c|
59
+ PageHeader.new(
60
+ type: c.name("type") { UNDO_PAGE_TYPES[c.read_uint16] },
61
+ latest_log_record_offset: c.name("latest_log_record_offset") { c.read_uint16 },
62
+ free_offset: c.name("free_offset") { c.read_uint16 },
63
+ page_list_node: c.name("page_list") { Innodb::List.get_node(c) }
64
+ )
65
+ end
66
+ end
67
+
68
+ def prev_address
69
+ undo_page_header[:page_list_node][:prev]
70
+ end
71
+
72
+ def next_address
73
+ undo_page_header[:page_list_node][:next]
74
+ end
75
+
76
+ def undo_segment_header
77
+ @undo_segment_header ||= cursor(pos_undo_segment_header).name("undo_segment_header") do |c|
78
+ SegmentHeader.new(
79
+ state: c.name("state") { UNDO_SEGMENT_STATES[c.read_uint16] },
80
+ last_log_offset: c.name("last_log_offset") { c.read_uint16 },
81
+ fseg: c.name("fseg") { Innodb::FsegEntry.get_inode(@space, c) },
82
+ page_list: c.name("page_list") { Innodb::List::UndoPage.new(@space, Innodb::List.get_base_node(c)) }
83
+ )
84
+ end
85
+ end
86
+
87
+ def undo_log(pos)
88
+ Innodb::UndoLog.new(self, pos)
89
+ end
90
+
91
+ # Dump the contents of a page for debugging purposes.
92
+ def dump
93
+ super
94
+
95
+ puts "undo page header:"
96
+ pp undo_page_header
97
+ puts
98
+
99
+ puts "undo segment header:"
100
+ pp undo_segment_header
101
+ puts
102
+
103
+ puts "last undo log:"
104
+ undo_log(undo_segment_header[:last_log_offset]).dump unless undo_segment_header[:last_log_offset].zero?
105
+ puts
106
+ end
46
107
  end
47
108
  end
48
-
49
- def prev_address
50
- undo_page_header[:page_list_node][:prev]
51
- end
52
-
53
- def next_address
54
- undo_page_header[:page_list_node][:next]
55
- end
56
-
57
- def undo_segment_header
58
- @undo_segment_header ||=
59
- cursor(pos_undo_segment_header).name("undo_segment_header") do |c|
60
- {
61
- :state => c.name("state") { UNDO_SEGMENT_STATES[c.get_uint16] },
62
- :last_log_offset => c.name("last_log_offset") { c.get_uint16 },
63
- :fseg => c.name("fseg") { Innodb::FsegEntry.get_inode(@space, c) },
64
- :page_list => c.name("page_list") {
65
- Innodb::List::UndoPage.new(@space, Innodb::List.get_base_node(c))
66
- },
67
- }
68
- end
69
- end
70
-
71
- def undo_log(pos)
72
- Innodb::UndoLog.new(self, pos)
73
- end
74
-
75
- # Dump the contents of a page for debugging purposes.
76
- def dump
77
- super
78
-
79
- puts "undo page header:"
80
- pp undo_page_header
81
- puts
82
-
83
- puts "undo segment header:"
84
- pp undo_segment_header
85
- puts
86
-
87
- puts "last undo log:"
88
- if undo_segment_header[:last_log_offset] != 0
89
- undo_log(undo_segment_header[:last_log_offset]).dump
90
- end
91
- puts
92
- end
93
109
  end
94
-
95
- Innodb::Page::SPECIALIZED_CLASSES[:UNDO_LOG] = Innodb::Page::UndoLog
data/lib/innodb/page.rb CHANGED
@@ -1,493 +1,496 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
2
4
 
3
5
  # A generic class for any type of page, which handles reading the common
4
6
  # FIL header and trailer, and can handle (via #parse) dispatching to a more
5
7
  # specialized class depending on page type (which comes from the FIL header).
6
8
  # A page being handled by Innodb::Page indicates that its type is not currently
7
9
  # handled by any more specialized class.
8
- class Innodb::Page
9
- # A hash of page types to specialized classes to handle them. Normally
10
- # subclasses will register themselves in this list.
11
- SPECIALIZED_CLASSES = {}
12
-
13
- # Load a page as a generic page in order to make the "fil" header accessible,
14
- # and then attempt to hand off the page to a specialized class to be
15
- # re-parsed if possible. If there is no specialized class for this type
16
- # of page, return the generic object.
17
- #
18
- # This could be optimized to reach into the page buffer and efficiently
19
- # extract the page type in order to avoid throwing away a generic
20
- # Innodb::Page object when parsing every specialized page, but this is
21
- # a bit cleaner, and we're not particularly performance sensitive.
22
- def self.parse(space, buffer, page_number=nil)
23
- # Create a page object as a generic page.
24
- page = Innodb::Page.new(space, buffer, page_number)
25
-
26
- # If there is a specialized class available for this page type, re-create
27
- # the page object using that specialized class.
28
- if specialized_class = SPECIALIZED_CLASSES[page.type]
29
- page = specialized_class.handle(page, space, buffer, page_number)
30
- end
31
-
32
- page
33
- end
34
-
35
- # Allow the specialized class to do something that isn't 'new' with this page.
36
- def self.handle(page, space, buffer, page_number=nil)
37
- self.new(space, buffer, page_number)
38
- end
39
-
40
- # Initialize a page by passing in a buffer containing the raw page contents.
41
- # The buffer size should match the space's page size.
42
- def initialize(space, buffer, page_number=nil)
43
- unless space && buffer
44
- raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})"
10
+ module Innodb
11
+ class Page
12
+ extend Forwardable
13
+
14
+ Address = Struct.new(
15
+ :page,
16
+ :offset,
17
+ keyword_init: true
18
+ )
19
+
20
+ FilHeader = Struct.new(
21
+ :checksum,
22
+ :offset,
23
+ :prev,
24
+ :next,
25
+ :lsn,
26
+ :type,
27
+ :flush_lsn,
28
+ :space_id,
29
+ keyword_init: true
30
+ )
31
+
32
+ class FilHeader
33
+ def lsn_low32
34
+ lsn & 0xffffffff
35
+ end
45
36
  end
46
37
 
47
- unless space.page_size == buffer.size
48
- raise "Buffer size #{buffer.size} is different than space page size"
38
+ FilTrailer = Struct.new(
39
+ :checksum,
40
+ :lsn_low32,
41
+ keyword_init: true
42
+ )
43
+
44
+ Region = Struct.new(
45
+ :offset,
46
+ :length, # rubocop:disable Lint/StructNewOverride
47
+ :name,
48
+ :info,
49
+ keyword_init: true
50
+ )
51
+
52
+ # A hash of page types to specialized classes to handle them. Normally
53
+ # subclasses will register themselves in this list.
54
+ @specialized_classes = {}
55
+
56
+ class << self
57
+ attr_reader :specialized_classes
49
58
  end
50
59
 
51
- @space = space
52
- @buffer = buffer
53
- @page_number = page_number
54
- end
55
-
56
- attr_reader :space
57
-
58
- # Return the page size, to eventually be able to deal with non-16kB pages.
59
- def size
60
- @size ||= @buffer.size
61
- end
62
-
63
- # Return a simple string to uniquely identify this page within the space.
64
- # Be careful not to call anything which would instantiate a BufferCursor
65
- # so that we can use this method in cursor initialization.
66
- def name
67
- page_offset = BinData::Uint32be.read(@buffer.slice(4, 4))
68
- page_type = BinData::Uint16be.read(@buffer.slice(24, 2))
69
- "%i,%s" % [
70
- page_offset,
71
- PAGE_TYPE_BY_VALUE[page_type],
72
- ]
73
- end
74
-
75
- # If no block is passed, return an BufferCursor object positioned at a
76
- # specific offset. If a block is passed, create a cursor at the provided
77
- # offset and yield it to the provided block one time, and then return the
78
- # return value of the block.
79
- def cursor(buffer_offset)
80
- new_cursor = BufferCursor.new(@buffer, buffer_offset)
81
- new_cursor.push_name("space[#{space.name}]")
82
- new_cursor.push_name("page[#{name}]")
83
-
84
- if block_given?
85
- # Call the block once and return its return value.
86
- yield new_cursor
87
- else
88
- # Return the cursor itself.
89
- new_cursor
60
+ def self.register_specialization(page_type, specialized_class)
61
+ @specialized_classes[page_type] = specialized_class
90
62
  end
91
- end
92
63
 
93
- # Return the byte offset of the start of the "fil" header, which is at the
94
- # beginning of the page. Included here primarily for completeness.
95
- def pos_fil_header
96
- 0
97
- end
64
+ def self.specialization_for(page_type)
65
+ # This needs to intentionally use Innodb::Page because we need to register
66
+ # in the class instance variable in *that* class.
67
+ Innodb::Page.register_specialization(page_type, self)
68
+ end
98
69
 
99
- # Return the size of the "fil" header, in bytes.
100
- def size_fil_header
101
- 4 + 4 + 4 + 4 + 8 + 2 + 8 + 4
102
- end
70
+ def self.specialization_for?(page_type)
71
+ Innodb::Page.specialized_classes.include?(page_type)
72
+ end
103
73
 
104
- # The start of the checksummed portion of the file header.
105
- def pos_partial_page_header
106
- pos_fil_header + 4
107
- end
74
+ # Load a page as a generic page in order to make the "fil" header accessible,
75
+ # and then attempt to hand off the page to a specialized class to be
76
+ # re-parsed if possible. If there is no specialized class for this type
77
+ # of page, return the generic object.
78
+ #
79
+ # This could be optimized to reach into the page buffer and efficiently
80
+ # extract the page type in order to avoid throwing away a generic
81
+ # Innodb::Page object when parsing every specialized page, but this is
82
+ # a bit cleaner, and we're not particularly performance sensitive.
83
+ def self.parse(space, buffer, page_number = nil)
84
+ # Create a page object as a generic page.
85
+ page = Innodb::Page.new(space, buffer, page_number)
86
+
87
+ # If there is a specialized class available for this page type, re-create
88
+ # the page object using that specialized class.
89
+ if (specialized_class = specialized_classes[page.type])
90
+ page = specialized_class.handle(page, space, buffer, page_number)
91
+ end
108
92
 
109
- # The size of the portion of the fil header that is included in the
110
- # checksum. Exclude the following:
111
- # :checksum (offset 4, size 4)
112
- # :flush_lsn (offset 26, size 8)
113
- # :space_id (offset 34, size 4)
114
- def size_partial_page_header
115
- size_fil_header - 4 - 8 - 4
116
- end
93
+ page
94
+ end
117
95
 
118
- # Return the byte offset of the start of the "fil" trailer, which is at
119
- # the end of the page.
120
- def pos_fil_trailer
121
- size - size_fil_trailer
122
- end
96
+ # Allow the specialized class to do something that isn't 'new' with this page.
97
+ def self.handle(_page, space, buffer, page_number = nil)
98
+ new(space, buffer, page_number)
99
+ end
123
100
 
124
- # Return the size of the "fil" trailer, in bytes.
125
- def size_fil_trailer
126
- 4 + 4
127
- end
101
+ # Initialize a page by passing in a buffer containing the raw page contents.
102
+ # The buffer size should match the space's page size.
103
+ def initialize(space, buffer, page_number = nil)
104
+ unless space && buffer
105
+ raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})"
106
+ end
128
107
 
129
- # Return the position of the "body" of the page, which starts after the FIL
130
- # header.
131
- def pos_page_body
132
- pos_fil_header + size_fil_header
133
- end
108
+ raise "Buffer size #{buffer.size} is different than space page size" unless space.page_size == buffer.size
134
109
 
135
- # Return the size of the page body, excluding the header and trailer.
136
- def size_page_body
137
- size - size_fil_trailer - size_fil_header
138
- end
110
+ @space = space
111
+ @buffer = buffer
112
+ @page_number = page_number
113
+ end
139
114
 
140
- # InnoDB Page Type constants from include/fil0fil.h.
141
- PAGE_TYPE = {
142
- :ALLOCATED => {
143
- :value => 0,
144
- :description => "Freshly allocated",
145
- :usage => "page type field has not been initialized",
146
- },
147
- :UNDO_LOG => {
148
- :value => 2,
149
- :description => "Undo log",
150
- :usage => "stores previous values of modified records",
151
- },
152
- :INODE => {
153
- :value => 3,
154
- :description => "File segment inode",
155
- :usage => "bookkeeping for file segments",
156
- },
157
- :IBUF_FREE_LIST => {
158
- :value => 4,
159
- :description => "Insert buffer free list",
160
- :usage => "bookkeeping for insert buffer free space management",
161
- },
162
- :IBUF_BITMAP => {
163
- :value => 5,
164
- :description => "Insert buffer bitmap",
165
- :usage => "bookkeeping for insert buffer writes to be merged",
166
- },
167
- :SYS => {
168
- :value => 6,
169
- :description => "System internal",
170
- :usage => "used for various purposes in the system tablespace",
171
- },
172
- :TRX_SYS => {
173
- :value => 7,
174
- :description => "Transaction system header",
175
- :usage => "bookkeeping for the transaction system in system tablespace",
176
- },
177
- :FSP_HDR => {
178
- :value => 8,
179
- :description => "File space header",
180
- :usage => "header page (page 0) for each tablespace file",
181
- },
182
- :XDES => {
183
- :value => 9,
184
- :description => "Extent descriptor",
185
- :usage => "header page for subsequent blocks of 16,384 pages",
186
- },
187
- :BLOB => {
188
- :value => 10,
189
- :description => "Uncompressed BLOB",
190
- :usage => "externally-stored uncompressed BLOB column data",
191
- },
192
- :ZBLOB => {
193
- :value => 11,
194
- :description => "First compressed BLOB",
195
- :usage => "externally-stored compressed BLOB column data, first page",
196
- },
197
- :ZBLOB2 => {
198
- :value => 12,
199
- :description => "Subsequent compressed BLOB",
200
- :usage => "externally-stored compressed BLOB column data, subsequent page",
201
- },
202
- :INDEX => {
203
- :value => 17855,
204
- :description => "B+Tree index",
205
- :usage => "table and index data stored in B+Tree structure",
206
- },
207
- }
208
-
209
- PAGE_TYPE_BY_VALUE = PAGE_TYPE.inject({}) { |h, (k, v)| h[v[:value]] = k; h }
210
-
211
- # A helper to convert "undefined" values stored in previous and next pointers
212
- # in the page header to nil.
213
- def self.maybe_undefined(value)
214
- value == 4294967295 ? nil : value
215
- end
115
+ attr_reader :space
216
116
 
217
- # Return the "fil" header from the page, which is common for all page types.
218
- def fil_header
219
- @fil_header ||= cursor(pos_fil_header).name("fil_header") do |c|
220
- {
221
- :checksum => c.name("checksum") { c.get_uint32 },
222
- :offset => c.name("offset") { c.get_uint32 },
223
- :prev => c.name("prev") {
224
- Innodb::Page.maybe_undefined(c.get_uint32)
225
- },
226
- :next => c.name("next") {
227
- Innodb::Page.maybe_undefined(c.get_uint32)
228
- },
229
- :lsn => c.name("lsn") { c.get_uint64 },
230
- :type => c.name("type") { PAGE_TYPE_BY_VALUE[c.get_uint16] },
231
- :flush_lsn => c.name("flush_lsn") { c.get_uint64 },
232
- :space_id => c.name("space_id") { c.get_uint32 },
233
- }
117
+ # Return the page size, to eventually be able to deal with non-16kB pages.
118
+ def size
119
+ @size ||= @buffer.size
234
120
  end
235
- end
236
121
 
237
- # Return the "fil" trailer from the page, which is common for all page types.
238
- def fil_trailer
239
- @fil_trailer ||= cursor(pos_fil_trailer).name("fil_trailer") do |c|
240
- {
241
- :checksum => c.name("checksum") { c.get_uint32 },
242
- :lsn_low32 => c.name("lsn_low32") { c.get_uint32 },
243
- }
122
+ def default_page_size?
123
+ size == Innodb::Space::DEFAULT_PAGE_SIZE
244
124
  end
245
- end
246
-
247
- # A helper function to return the checksum from the "fil" header, for easier
248
- # access.
249
- def checksum
250
- fil_header[:checksum]
251
- end
252
125
 
253
- # A helper function to return the checksum from the "fil" trailer, for easier
254
- # access.
255
- def checksum_trailer
256
- fil_trailer[:checksum]
257
- end
126
+ # Return a simple string to uniquely identify this page within the space.
127
+ # Be careful not to call anything which would instantiate a BufferCursor
128
+ # so that we can use this method in cursor initialization.
129
+ def name
130
+ page_offset = BinData::Uint32be.read(@buffer.slice(4, 4))
131
+ page_type = BinData::Uint16be.read(@buffer.slice(24, 2))
132
+ "%i,%s" % [
133
+ page_offset,
134
+ PAGE_TYPE_BY_VALUE[page_type],
135
+ ]
136
+ end
258
137
 
259
- # A helper function to return the page offset from the "fil" header, for
260
- # easier access.
261
- def offset
262
- fil_header[:offset]
263
- end
138
+ # If no block is passed, return an BufferCursor object positioned at a
139
+ # specific offset. If a block is passed, create a cursor at the provided
140
+ # offset and yield it to the provided block one time, and then return the
141
+ # return value of the block.
142
+ def cursor(buffer_offset)
143
+ new_cursor = BufferCursor.new(@buffer, buffer_offset)
144
+ new_cursor.push_name("space[#{space.name}]")
145
+ new_cursor.push_name("page[#{name}]")
146
+
147
+ if block_given?
148
+ # Call the block once and return its return value.
149
+ yield new_cursor
150
+ else
151
+ # Return the cursor itself.
152
+ new_cursor
153
+ end
154
+ end
264
155
 
265
- # A helper function to return the page number of the logical previous page
266
- # (from the doubly-linked list from page to page) from the "fil" header,
267
- # for easier access.
268
- def prev
269
- fil_header[:prev]
270
- end
156
+ # Return the byte offset of the start of the "fil" header, which is at the
157
+ # beginning of the page. Included here primarily for completeness.
158
+ def pos_fil_header
159
+ 0
160
+ end
271
161
 
272
- # A helper function to return the page number of the logical next page
273
- # (from the doubly-linked list from page to page) from the "fil" header,
274
- # for easier access.
275
- def next
276
- fil_header[:next]
277
- end
162
+ # Return the size of the "fil" header, in bytes.
163
+ def size_fil_header
164
+ 4 + 4 + 4 + 4 + 8 + 2 + 8 + 4
165
+ end
278
166
 
279
- # A helper function to return the LSN from the page header, for easier access.
280
- def lsn
281
- fil_header[:lsn]
282
- end
167
+ # The start of the checksummed portion of the file header.
168
+ def pos_partial_page_header
169
+ pos_fil_header + 4
170
+ end
283
171
 
284
- # A helper function to return the low 32 bits of the LSN from the page header
285
- # for use in comparing to the low 32 bits stored in the trailer.
286
- def lsn_low32_header
287
- fil_header[:lsn] & 0xffffffff
288
- end
172
+ # The size of the portion of the fil header that is included in the
173
+ # checksum. Exclude the following:
174
+ # :checksum (offset 4, size 4)
175
+ # :flush_lsn (offset 26, size 8)
176
+ # :space_id (offset 34, size 4)
177
+ def size_partial_page_header
178
+ size_fil_header - 4 - 8 - 4
179
+ end
289
180
 
290
- # A helper function to return the low 32 bits of the LSN as stored in the page
291
- # trailer.
292
- def lsn_low32_trailer
293
- fil_trailer[:lsn_low32]
294
- end
181
+ # Return the byte offset of the start of the "fil" trailer, which is at
182
+ # the end of the page.
183
+ def pos_fil_trailer
184
+ size - size_fil_trailer
185
+ end
295
186
 
296
- # A helper function to return the page type from the "fil" header, for easier
297
- # access.
298
- def type
299
- fil_header[:type]
300
- end
187
+ # Return the size of the "fil" trailer, in bytes.
188
+ def size_fil_trailer
189
+ 4 + 4
190
+ end
301
191
 
302
- # A helper function to return the space ID from the "fil" header, for easier
303
- # access.
304
- def space_id
305
- fil_header[:space_id]
306
- end
192
+ # Return the position of the "body" of the page, which starts after the FIL
193
+ # header.
194
+ def pos_page_body
195
+ pos_fil_header + size_fil_header
196
+ end
307
197
 
308
- # Iterate each byte of the FIL header.
309
- def each_page_header_byte_as_uint8
310
- unless block_given?
311
- return enum_for(:each_page_header_byte_as_uint8)
198
+ # Return the size of the page body, excluding the header and trailer.
199
+ def size_page_body
200
+ size - size_fil_trailer - size_fil_header
312
201
  end
313
202
 
314
- cursor(pos_partial_page_header).
315
- each_byte_as_uint8(size_partial_page_header) do |byte|
316
- yield byte
203
+ # InnoDB Page Type constants from include/fil0fil.h.
204
+ PAGE_TYPE = {
205
+ ALLOCATED: {
206
+ value: 0,
207
+ description: "Freshly allocated",
208
+ usage: "page type field has not been initialized",
209
+ },
210
+ UNDO_LOG: {
211
+ value: 2,
212
+ description: "Undo log",
213
+ usage: "stores previous values of modified records",
214
+ },
215
+ INODE: {
216
+ value: 3,
217
+ description: "File segment inode",
218
+ usage: "bookkeeping for file segments",
219
+ },
220
+ IBUF_FREE_LIST: {
221
+ value: 4,
222
+ description: "Insert buffer free list",
223
+ usage: "bookkeeping for insert buffer free space management",
224
+ },
225
+ IBUF_BITMAP: {
226
+ value: 5,
227
+ description: "Insert buffer bitmap",
228
+ usage: "bookkeeping for insert buffer writes to be merged",
229
+ },
230
+ SYS: {
231
+ value: 6,
232
+ description: "System internal",
233
+ usage: "used for various purposes in the system tablespace",
234
+ },
235
+ TRX_SYS: {
236
+ value: 7,
237
+ description: "Transaction system header",
238
+ usage: "bookkeeping for the transaction system in system tablespace",
239
+ },
240
+ FSP_HDR: {
241
+ value: 8,
242
+ description: "File space header",
243
+ usage: "header page (page 0) for each tablespace file",
244
+ },
245
+ XDES: {
246
+ value: 9,
247
+ description: "Extent descriptor",
248
+ usage: "header page for subsequent blocks of 16,384 pages",
249
+ },
250
+ BLOB: {
251
+ value: 10,
252
+ description: "Uncompressed BLOB",
253
+ usage: "externally-stored uncompressed BLOB column data",
254
+ },
255
+ ZBLOB: {
256
+ value: 11,
257
+ description: "First compressed BLOB",
258
+ usage: "externally-stored compressed BLOB column data, first page",
259
+ },
260
+ ZBLOB2: {
261
+ value: 12,
262
+ description: "Subsequent compressed BLOB",
263
+ usage: "externally-stored compressed BLOB column data, subsequent page",
264
+ },
265
+ INDEX: {
266
+ value: 17_855,
267
+ description: "B+Tree index",
268
+ usage: "table and index data stored in B+Tree structure",
269
+ },
270
+ }.freeze
271
+
272
+ PAGE_TYPE_BY_VALUE = PAGE_TYPE.each_with_object({}) { |(k, v), h| h[v[:value]] = k }
273
+
274
+ # A page number representing "undefined" values, (4294967295).
275
+ UNDEFINED_PAGE_NUMBER = 2**32 - 1
276
+
277
+ # A helper to check if a page number is the undefined page number.
278
+ def self.undefined?(page_number)
279
+ page_number == UNDEFINED_PAGE_NUMBER
317
280
  end
318
- end
319
281
 
320
- # Iterate each byte of the page body, except for the FIL header and
321
- # the FIL trailer.
322
- def each_page_body_byte_as_uint8
323
- unless block_given?
324
- return enum_for(:each_page_body_byte_as_uint8)
282
+ # A helper to convert "undefined" values stored in previous and next pointers
283
+ # in the page header to nil.
284
+ def self.maybe_undefined(page_number)
285
+ page_number unless undefined?(page_number)
325
286
  end
326
287
 
327
- cursor(pos_page_body).
328
- each_byte_as_uint8(size_page_body) do |byte|
329
- yield byte
288
+ # Return the "fil" header from the page, which is common for all page types.
289
+ def fil_header
290
+ @fil_header ||= cursor(pos_fil_header).name("fil_header") do |c|
291
+ FilHeader.new(
292
+ checksum: c.name("checksum") { c.read_uint32 },
293
+ offset: c.name("offset") { c.read_uint32 },
294
+ prev: c.name("prev") { Innodb::Page.maybe_undefined(c.read_uint32) },
295
+ next: c.name("next") { Innodb::Page.maybe_undefined(c.read_uint32) },
296
+ lsn: c.name("lsn") { c.read_uint64 },
297
+ type: c.name("type") { PAGE_TYPE_BY_VALUE[c.read_uint16] },
298
+ flush_lsn: c.name("flush_lsn") { c.read_uint64 },
299
+ space_id: c.name("space_id") { c.read_uint32 }
300
+ )
301
+ end
330
302
  end
331
- end
332
303
 
333
- # Calculate the checksum of the page using InnoDB's algorithm.
334
- def checksum_innodb
335
- unless size == 16384
336
- raise "Checksum calculation is only supported for 16 KiB pages"
304
+ # Return the "fil" trailer from the page, which is common for all page types.
305
+ def fil_trailer
306
+ @fil_trailer ||= cursor(pos_fil_trailer).name("fil_trailer") do |c|
307
+ FilTrailer.new(
308
+ checksum: c.name("checksum") { c.read_uint32 },
309
+ lsn_low32: c.name("lsn_low32") { c.read_uint32 }
310
+ )
311
+ end
337
312
  end
338
313
 
339
- @checksum_innodb ||= begin
340
- # Calculate the InnoDB checksum of the page header.
341
- c_partial_header = Innodb::Checksum.fold_enumerator(each_page_header_byte_as_uint8)
314
+ def_delegator :fil_header, :checksum
315
+ def_delegator :fil_header, :offset
316
+ def_delegator :fil_header, :prev
317
+ def_delegator :fil_header, :next
318
+ def_delegator :fil_header, :lsn
319
+ def_delegator :fil_header, :type
320
+ def_delegator :fil_header, :space_id
342
321
 
343
- # Calculate the InnoDB checksum of the page body.
344
- c_page_body = Innodb::Checksum.fold_enumerator(each_page_body_byte_as_uint8)
322
+ # Iterate each byte of the FIL header.
323
+ def each_page_header_byte_as_uint8(&block)
324
+ return enum_for(:each_page_header_byte_as_uint8) unless block_given?
345
325
 
346
- # Add the two checksums together, and mask the result back to 32 bits.
347
- (c_partial_header + c_page_body) & Innodb::Checksum::MAX
326
+ cursor(pos_partial_page_header).each_byte_as_uint8(size_partial_page_header, &block)
348
327
  end
349
- end
350
328
 
351
- def checksum_innodb?
352
- checksum == checksum_innodb
353
- end
329
+ # Iterate each byte of the page body, except for the FIL header and
330
+ # the FIL trailer.
331
+ def each_page_body_byte_as_uint8(&block)
332
+ return enum_for(:each_page_body_byte_as_uint8) unless block_given?
354
333
 
355
- # Calculate the checksum of the page using the CRC32c algorithm.
356
- def checksum_crc32
357
- unless size == 16384
358
- raise "Checksum calculation is only supported for 16 KiB pages"
334
+ cursor(pos_page_body).each_byte_as_uint8(size_page_body, &block)
359
335
  end
360
336
 
361
- @checksum_crc32 ||= begin
362
- # Calculate the CRC32c of the page header.
363
- crc_partial_header = Digest::CRC32c.new
364
- each_page_header_byte_as_uint8 do |byte|
365
- crc_partial_header << byte.chr
337
+ # Calculate the checksum of the page using InnoDB's algorithm.
338
+ def checksum_innodb
339
+ raise "Checksum calculation is only supported for 16 KiB pages" unless default_page_size?
340
+
341
+ @checksum_innodb ||= begin
342
+ # Calculate the InnoDB checksum of the page header.
343
+ c_partial_header = Innodb::Checksum.fold_enumerator(each_page_header_byte_as_uint8)
344
+
345
+ # Calculate the InnoDB checksum of the page body.
346
+ c_page_body = Innodb::Checksum.fold_enumerator(each_page_body_byte_as_uint8)
347
+
348
+ # Add the two checksums together, and mask the result back to 32 bits.
349
+ (c_partial_header + c_page_body) & Innodb::Checksum::MAX
366
350
  end
351
+ end
352
+
353
+ def checksum_innodb?
354
+ checksum == checksum_innodb
355
+ end
367
356
 
368
- # Calculate the CRC32c of the page body.
369
- crc_page_body = Digest::CRC32c.new
370
- each_page_body_byte_as_uint8 do |byte|
371
- crc_page_body << byte.chr
357
+ # Calculate the checksum of the page using the CRC32c algorithm.
358
+ def checksum_crc32
359
+ raise "Checksum calculation is only supported for 16 KiB pages" unless default_page_size?
360
+
361
+ @checksum_crc32 ||= begin
362
+ # Calculate the CRC32c of the page header.
363
+ crc_partial_header = Digest::CRC32c.new
364
+ each_page_header_byte_as_uint8 do |byte|
365
+ crc_partial_header << byte.chr
366
+ end
367
+
368
+ # Calculate the CRC32c of the page body.
369
+ crc_page_body = Digest::CRC32c.new
370
+ each_page_body_byte_as_uint8 do |byte|
371
+ crc_page_body << byte.chr
372
+ end
373
+
374
+ # Bitwise XOR the two checksums together.
375
+ crc_partial_header.checksum ^ crc_page_body.checksum
372
376
  end
377
+ end
373
378
 
374
- # Bitwise XOR the two checksums together.
375
- crc_partial_header.checksum ^ crc_page_body.checksum
379
+ def checksum_crc32?
380
+ checksum == checksum_crc32
376
381
  end
377
- end
378
382
 
379
- def checksum_crc32?
380
- checksum == checksum_crc32
381
- end
383
+ # Is the page checksum correct?
384
+ def checksum_valid?
385
+ checksum_crc32? || checksum_innodb?
386
+ end
382
387
 
383
- # Is the page checksum correct?
384
- def checksum_valid?
385
- checksum_crc32? || checksum_innodb?
386
- end
388
+ # Is the page checksum incorrect?
389
+ def checksum_invalid?
390
+ !checksum_valid?
391
+ end
387
392
 
388
- # Is the page checksum incorrect?
389
- def checksum_invalid?
390
- !checksum_valid?
391
- end
393
+ def checksum_type
394
+ return :crc32 if checksum_crc32?
395
+ return :innodb if checksum_innodb?
392
396
 
393
- def checksum_type
394
- case
395
- when checksum_crc32?
396
- :crc32
397
- when checksum_innodb?
398
- :innodb
397
+ nil
399
398
  end
400
- end
401
399
 
402
- # Is the LSN stored in the header different from the one stored in the
403
- # trailer?
404
- def torn?
405
- lsn_low32_header != lsn_low32_trailer
406
- end
400
+ # Is the LSN stored in the header different from the one stored in the
401
+ # trailer?
402
+ def torn?
403
+ fil_header.lsn_low32 != fil_trailer.lsn_low32
404
+ end
407
405
 
408
- # Is the page in the doublewrite buffer?
409
- def in_doublewrite_buffer?
410
- space && space.system_space? && space.doublewrite_page?(offset)
411
- end
406
+ # Is the page in the doublewrite buffer?
407
+ def in_doublewrite_buffer?
408
+ space&.system_space? && space&.doublewrite_page?(offset)
409
+ end
412
410
 
413
- # Is the space ID stored in the header different from that of the space
414
- # provided when initializing this page?
415
- def misplaced_space?
416
- space && (space_id != space.space_id)
417
- end
411
+ # Is the space ID stored in the header different from that of the space
412
+ # provided when initializing this page?
413
+ def misplaced_space?
414
+ space && (space_id != space.space_id)
415
+ end
418
416
 
419
- # Is the page number stored in the header different from the page number
420
- # which was supposed to be read?
421
- def misplaced_offset?
422
- offset != @page_number
423
- end
417
+ # Is the page number stored in the header different from the page number
418
+ # which was supposed to be read?
419
+ def misplaced_offset?
420
+ offset != @page_number
421
+ end
424
422
 
425
- # Is the page misplaced in the wrong file or by offset in the file?
426
- def misplaced?
427
- !in_doublewrite_buffer? && (misplaced_space? || misplaced_offset?)
428
- end
423
+ # Is the page misplaced in the wrong file or by offset in the file?
424
+ def misplaced?
425
+ !in_doublewrite_buffer? && (misplaced_space? || misplaced_offset?)
426
+ end
429
427
 
430
- # Is the page corrupt, either due to data corruption, tearing, or in the
431
- # wrong place?
432
- def corrupt?
433
- checksum_invalid? || torn? || misplaced?
434
- end
428
+ # Is the page corrupt, either due to data corruption, tearing, or in the
429
+ # wrong place?
430
+ def corrupt?
431
+ checksum_invalid? || torn? || misplaced?
432
+ end
435
433
 
436
- def each_region
437
- unless block_given?
438
- return enum_for(:each_region)
434
+ # Is this an extent descriptor page (either FSP_HDR or XDES)?
435
+ def extent_descriptor?
436
+ type == :FSP_HDR || type == :XDES
439
437
  end
440
438
 
441
- yield({
442
- :offset => pos_fil_header,
443
- :length => size_fil_header,
444
- :name => :fil_header,
445
- :info => "FIL Header",
446
- })
439
+ def each_region
440
+ return enum_for(:each_region) unless block_given?
447
441
 
448
- yield({
449
- :offset => pos_fil_trailer,
450
- :length => size_fil_trailer,
451
- :name => :fil_trailer,
452
- :info => "FIL Trailer",
453
- })
442
+ yield Region.new(
443
+ offset: pos_fil_header,
444
+ length: size_fil_header,
445
+ name: :fil_header,
446
+ info: "FIL Header"
447
+ )
454
448
 
455
- nil
456
- end
449
+ yield Region.new(
450
+ offset: pos_fil_trailer,
451
+ length: size_fil_trailer,
452
+ name: :fil_trailer,
453
+ info: "FIL Trailer"
454
+ )
457
455
 
458
- # Implement a custom inspect method to avoid irb printing the contents of
459
- # the page buffer, since it's very large and mostly not interesting.
460
- def inspect
461
- if fil_header
462
- "#<%s: size=%i, space_id=%i, offset=%i, type=%s, prev=%s, next=%s, checksum_valid?=%s (%s), torn?=%s, misplaced?=%s>" % [
463
- self.class,
464
- size,
465
- fil_header[:space_id],
466
- fil_header[:offset],
467
- fil_header[:type],
468
- fil_header[:prev] || "nil",
469
- fil_header[:next] || "nil",
470
- checksum_valid?,
471
- checksum_type ? checksum_type : "unknown",
472
- torn?,
473
- misplaced?,
474
- ]
475
- else
476
- "#<#{self.class}>"
456
+ nil
477
457
  end
478
- end
479
458
 
480
- # Dump the contents of a page for debugging purposes.
481
- def dump
482
- puts "#{self}:"
483
- puts
459
+ def inspect_header_fields
460
+ return nil unless fil_header
461
+
462
+ %i[
463
+ size
464
+ space_id
465
+ offset
466
+ type
467
+ prev
468
+ next
469
+ checksum_valid?
470
+ checksum_type
471
+ torn?
472
+ misplaced?
473
+ ].map { |m| "#{m}=#{send(m).inspect}" }.join(", ")
474
+ end
475
+
476
+ # Implement a custom inspect method to avoid irb printing the contents of
477
+ # the page buffer, since it's very large and mostly not interesting.
478
+ def inspect
479
+ "#<#{self.class} #{inspect_header_fields || '(page header unavailable)'}>"
480
+ end
484
481
 
485
- puts "fil header:"
486
- pp fil_header
487
- puts
482
+ # Dump the contents of a page for debugging purposes.
483
+ def dump
484
+ puts "#{self}:"
485
+ puts
488
486
 
489
- puts "fil trailer:"
490
- pp fil_trailer
491
- puts
487
+ puts "fil header:"
488
+ pp fil_header
489
+ puts
490
+
491
+ puts "fil trailer:"
492
+ pp fil_trailer
493
+ puts
494
+ end
492
495
  end
493
496
  end