innodb_ruby 0.9.16 → 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 +5 -5
  2. data/README.md +5 -6
  3. data/bin/innodb_log +13 -18
  4. data/bin/innodb_space +377 -757
  5. data/lib/innodb.rb +4 -5
  6. data/lib/innodb/checksum.rb +26 -24
  7. data/lib/innodb/data_dictionary.rb +490 -550
  8. data/lib/innodb/data_type.rb +362 -326
  9. data/lib/innodb/field.rb +102 -89
  10. data/lib/innodb/fseg_entry.rb +22 -26
  11. data/lib/innodb/history.rb +21 -21
  12. data/lib/innodb/history_list.rb +72 -76
  13. data/lib/innodb/ibuf_bitmap.rb +36 -36
  14. data/lib/innodb/ibuf_index.rb +6 -2
  15. data/lib/innodb/index.rb +245 -276
  16. data/lib/innodb/inode.rb +154 -155
  17. data/lib/innodb/list.rb +191 -183
  18. data/lib/innodb/log.rb +139 -110
  19. data/lib/innodb/log_block.rb +100 -91
  20. data/lib/innodb/log_group.rb +53 -64
  21. data/lib/innodb/log_reader.rb +97 -96
  22. data/lib/innodb/log_record.rb +328 -279
  23. data/lib/innodb/lsn.rb +86 -81
  24. data/lib/innodb/page.rb +417 -414
  25. data/lib/innodb/page/blob.rb +82 -83
  26. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  27. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  28. data/lib/innodb/page/index.rb +964 -943
  29. data/lib/innodb/page/index_compressed.rb +34 -34
  30. data/lib/innodb/page/inode.rb +103 -112
  31. data/lib/innodb/page/sys.rb +13 -15
  32. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  33. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  34. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  35. data/lib/innodb/page/trx_sys.rb +204 -182
  36. data/lib/innodb/page/undo_log.rb +106 -92
  37. data/lib/innodb/record.rb +121 -160
  38. data/lib/innodb/record_describer.rb +66 -68
  39. data/lib/innodb/space.rb +380 -418
  40. data/lib/innodb/stats.rb +33 -35
  41. data/lib/innodb/system.rb +149 -171
  42. data/lib/innodb/undo_log.rb +129 -107
  43. data/lib/innodb/undo_record.rb +255 -247
  44. data/lib/innodb/util/buffer_cursor.rb +81 -79
  45. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  46. data/lib/innodb/version.rb +2 -2
  47. data/lib/innodb/xdes.rb +144 -142
  48. metadata +80 -11
data/lib/innodb/lsn.rb CHANGED
@@ -1,103 +1,108 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  # A Log Sequence Number and its byte offset into the log group.
4
- class Innodb::LSN
5
- # The Log Sequence Number.
6
- attr_reader :lsn_no
7
-
8
- # Alias :lsn_no attribute.
9
- alias_method :no, :lsn_no
4
+ module Innodb
5
+ class LSN
6
+ # The Log Sequence Number.
7
+ attr_reader :lsn_no
8
+
9
+ # Alias :lsn_no attribute.
10
+ alias no lsn_no
11
+
12
+ # Initialize coordinates.
13
+ def initialize(lsn, offset)
14
+ @lsn_no = lsn
15
+ @lsn_offset = offset
16
+ end
10
17
 
11
- # Initialize coordinates.
12
- def initialize(lsn, offset)
13
- @lsn_no = lsn
14
- @lsn_offset = offset
15
- end
18
+ # Place LSN in a new position.
19
+ def reposition(new_lsn_no, group)
20
+ new_offset = offset_of(@lsn_no, @lsn_offset, new_lsn_no, group)
21
+ @lsn_no = new_lsn_no
22
+ @lsn_offset = new_offset
16
23
 
17
- # Place LSN in a new position.
18
- def reposition(new_lsn_no, group)
19
- new_offset = offset_of(@lsn_no, @lsn_offset, new_lsn_no, group)
20
- @lsn_no, @lsn_offset = [new_lsn_no, new_offset]
21
- end
24
+ [@lsn_no, @lsn_offset]
25
+ end
22
26
 
23
- # Advance by a given LSN amount.
24
- def advance(count_lsn_no, group)
25
- new_lsn_no = @lsn_no + count_lsn_no
26
- reposition(new_lsn_no, group)
27
- end
27
+ # Advance by a given LSN amount.
28
+ def advance(count_lsn_no, group)
29
+ new_lsn_no = @lsn_no + count_lsn_no
30
+ reposition(new_lsn_no, group)
31
+ end
28
32
 
29
- # Returns the location coordinates of this LSN.
30
- def location(group)
31
- location_of(@lsn_offset, group)
32
- end
33
+ # Returns the location coordinates of this LSN.
34
+ def location(group)
35
+ location_of(@lsn_offset, group)
36
+ end
33
37
 
34
- # Returns the LSN delta for the given amount of data.
35
- def delta(length)
36
- fragment = (@lsn_no % LOG_BLOCK_SIZE) - LOG_BLOCK_HEADER_SIZE
37
- raise "Invalid fragment #{fragment} for LSN #{@lsn_no}" unless
38
- fragment.between?(0, LOG_BLOCK_DATA_SIZE - 1)
39
- length + (fragment + length) / LOG_BLOCK_DATA_SIZE * LOG_BLOCK_FRAME_SIZE
40
- end
38
+ # Returns the LSN delta for the given amount of data.
39
+ def delta(length)
40
+ fragment = (@lsn_no % LOG_BLOCK_SIZE) - LOG_BLOCK_HEADER_SIZE
41
+ raise "Invalid fragment #{fragment} for LSN #{@lsn_no}" unless fragment.between?(0, LOG_BLOCK_DATA_SIZE - 1)
41
42
 
42
- # Whether LSN might point to log record data.
43
- def record?(group)
44
- data_offset?(@lsn_offset, group)
45
- end
43
+ length + (fragment + length) / LOG_BLOCK_DATA_SIZE * LOG_BLOCK_FRAME_SIZE
44
+ end
46
45
 
47
- private
46
+ # Whether LSN might point to log record data.
47
+ def record?(group)
48
+ data_offset?(@lsn_offset, group)
49
+ end
48
50
 
49
- # Short alias for the size of a log file header.
50
- LOG_HEADER_SIZE = Innodb::Log::LOG_HEADER_SIZE
51
+ private
51
52
 
52
- # Short aliases for the sizes of the subparts of a log block.
53
- LOG_BLOCK_SIZE = Innodb::LogBlock::BLOCK_SIZE
54
- LOG_BLOCK_HEADER_SIZE = Innodb::LogBlock::HEADER_SIZE
55
- LOG_BLOCK_TRAILER_SIZE = Innodb::LogBlock::TRAILER_SIZE
56
- LOG_BLOCK_DATA_SIZE = Innodb::LogBlock::DATA_SIZE
57
- LOG_BLOCK_FRAME_SIZE = LOG_BLOCK_HEADER_SIZE + LOG_BLOCK_TRAILER_SIZE
53
+ # Short alias for the size of a log file header.
54
+ LOG_HEADER_SIZE = Innodb::Log::LOG_HEADER_SIZE
58
55
 
59
- # Returns the coordinates of the given offset.
60
- def location_of(offset, group)
61
- log_no, log_offset = offset.divmod(group.size)
62
- block_no, block_offset = (log_offset - LOG_HEADER_SIZE).divmod(LOG_BLOCK_SIZE)
63
- [log_no, block_no, block_offset]
64
- end
56
+ # Short aliases for the sizes of the subparts of a log block.
57
+ LOG_BLOCK_SIZE = Innodb::LogBlock::BLOCK_SIZE
58
+ LOG_BLOCK_HEADER_SIZE = Innodb::LogBlock::HEADER_SIZE
59
+ LOG_BLOCK_TRAILER_SIZE = Innodb::LogBlock::TRAILER_SIZE
60
+ LOG_BLOCK_DATA_SIZE = Innodb::LogBlock::DATA_SIZE
61
+ LOG_BLOCK_FRAME_SIZE = LOG_BLOCK_HEADER_SIZE + LOG_BLOCK_TRAILER_SIZE
65
62
 
66
- # Returns the offset of the given LSN within a log group.
67
- def offset_of(lsn, offset, new_lsn, group)
68
- log_size = group.log_size
69
- group_capacity = group.capacity
70
-
71
- # Calculate the offset in LSN.
72
- if new_lsn >= lsn
73
- lsn_offset = new_lsn - lsn
74
- else
75
- lsn_offset = lsn - new_lsn
76
- lsn_offset %= group_capacity
77
- lsn_offset = group_capacity - lsn_offset
63
+ # Returns the coordinates of the given offset.
64
+ def location_of(offset, group)
65
+ log_no, log_offset = offset.divmod(group.size)
66
+ block_no, block_offset = (log_offset - LOG_HEADER_SIZE).divmod(LOG_BLOCK_SIZE)
67
+ [log_no, block_no, block_offset]
78
68
  end
79
69
 
80
- # Transpose group size offset to a group capacity offset.
81
- group_offset = offset - (LOG_HEADER_SIZE * (1 + offset / log_size))
70
+ # Returns the offset of the given LSN within a log group.
71
+ def offset_of(lsn, offset, new_lsn, group)
72
+ log_size = group.log_size
73
+ group_capacity = group.capacity
82
74
 
83
- offset = (lsn_offset + group_offset) % group_capacity
75
+ # Calculate the offset in LSN.
76
+ if new_lsn >= lsn
77
+ lsn_offset = new_lsn - lsn
78
+ else
79
+ lsn_offset = lsn - new_lsn
80
+ lsn_offset %= group_capacity
81
+ lsn_offset = group_capacity - lsn_offset
82
+ end
84
83
 
85
- # Transpose group capacity offset to a group size offset.
86
- offset + LOG_HEADER_SIZE * (1 + offset / (log_size - LOG_HEADER_SIZE))
87
- end
84
+ # Transpose group size offset to a group capacity offset.
85
+ group_offset = offset - (LOG_HEADER_SIZE * (1 + offset / log_size))
88
86
 
89
- # Whether offset points to the data area of an existing log block.
90
- def data_offset?(offset, group)
91
- log_offset = offset % group.size
92
- log_no, block_no, block_offset = location_of(offset, group)
87
+ offset = (lsn_offset + group_offset) % group_capacity
93
88
 
94
- status ||= log_no > group.logs
95
- status ||= log_offset <= LOG_HEADER_SIZE
96
- status ||= block_no < 0
97
- status ||= block_no >= group.log(log_no).blocks
98
- status ||= block_offset < Innodb::LogBlock::DATA_OFFSET
99
- status ||= block_offset >= Innodb::LogBlock::TRAILER_OFFSET
89
+ # Transpose group capacity offset to a group size offset.
90
+ offset + LOG_HEADER_SIZE * (1 + offset / (log_size - LOG_HEADER_SIZE))
91
+ end
92
+
93
+ # Whether offset points to the data area of an existing log block.
94
+ def data_offset?(offset, group)
95
+ log_offset = offset % group.size
96
+ log_no, block_no, block_offset = location_of(offset, group)
100
97
 
101
- !status
98
+ status ||= log_no > group.logs
99
+ status ||= log_offset <= LOG_HEADER_SIZE
100
+ status ||= block_no.negative?
101
+ status ||= block_no >= group.log(log_no).blocks
102
+ status ||= block_offset < Innodb::LogBlock::DATA_OFFSET
103
+ status ||= block_offset >= Innodb::LogBlock::TRAILER_OFFSET
104
+
105
+ !status
106
+ end
102
107
  end
103
108
  end
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