Class: Innodb::Page
- Inherits:
-
Object
- Object
- Innodb::Page
- Extended by:
- Forwardable
- Defined in:
- lib/innodb/page.rb,
lib/innodb/page/sys.rb,
lib/innodb/page/blob.rb,
lib/innodb/page/index.rb,
lib/innodb/page/inode.rb,
lib/innodb/page/trx_sys.rb,
lib/innodb/page/undo_log.rb,
lib/innodb/page/ibuf_bitmap.rb,
lib/innodb/page/fsp_hdr_xdes.rb,
lib/innodb/page/sys_ibuf_header.rb,
lib/innodb/page/sys_rseg_header.rb,
lib/innodb/page/index_compressed.rb,
lib/innodb/page/sys_data_dictionary_header.rb
Direct Known Subclasses
Blob, FspHdrXdes, IbufBitmap, Index, Inode, Sys, SysDataDictionaryHeader, SysIbufHeader, SysRsegHeader, TrxSys, UndoLog
Defined Under Namespace
Classes: Address, Blob, FilHeader, FilTrailer, FspHdrXdes, IbufBitmap, Index, IndexCompressed, Inode, Region, Sys, SysDataDictionaryHeader, SysIbufHeader, SysRsegHeader, TrxSys, UndoLog
Constant Summary collapse
- PAGE_TYPE =
InnoDB Page Type constants from include/fil0fil.h.
{ ALLOCATED: { value: 0, description: "Freshly allocated", usage: "page type field has not been initialized", }, UNDO_LOG: { value: 2, description: "Undo log", usage: "stores previous values of modified records", }, INODE: { value: 3, description: "File segment inode", usage: "bookkeeping for file segments", }, IBUF_FREE_LIST: { value: 4, description: "Insert buffer free list", usage: "bookkeeping for insert buffer free space management", }, IBUF_BITMAP: { value: 5, description: "Insert buffer bitmap", usage: "bookkeeping for insert buffer writes to be merged", }, SYS: { value: 6, description: "System internal", usage: "used for various purposes in the system tablespace", }, TRX_SYS: { value: 7, description: "Transaction system header", usage: "bookkeeping for the transaction system in system tablespace", }, FSP_HDR: { value: 8, description: "File space header", usage: "header page (page 0) for each tablespace file", }, XDES: { value: 9, description: "Extent descriptor", usage: "header page for subsequent blocks of 16,384 pages", }, BLOB: { value: 10, description: "Uncompressed BLOB", usage: "externally-stored uncompressed BLOB column data", }, ZBLOB: { value: 11, description: "First compressed BLOB", usage: "externally-stored compressed BLOB column data, first page", }, ZBLOB2: { value: 12, description: "Subsequent compressed BLOB", usage: "externally-stored compressed BLOB column data, subsequent page", }, INDEX: { value: 17_855, description: "B+Tree index", usage: "table and index data stored in B+Tree structure", }, }.freeze
- PAGE_TYPE_BY_VALUE =
PAGE_TYPE.each_with_object({}) { |(k, v), h| h[v[:value]] = k }
- UNDEFINED_PAGE_NUMBER =
A page number representing “undefined” values, (4294967295).
2**32 - 1
Class Attribute Summary collapse
-
.specialized_classes ⇒ Object
readonly
Returns the value of attribute specialized_classes.
Instance Attribute Summary collapse
-
#space ⇒ Object
readonly
Returns the value of attribute space.
Class Method Summary collapse
-
.handle(_page, space, buffer, page_number = nil) ⇒ Object
Allow the specialized class to do something that isn’t ‘new’ with this page.
-
.maybe_undefined(page_number) ⇒ Object
A helper to convert “undefined” values stored in previous and next pointers in the page header to nil.
-
.parse(space, buffer, page_number = nil) ⇒ Object
Load a page as a generic page in order to make the “fil” header accessible, and then attempt to hand off the page to a specialized class to be re-parsed if possible.
- .register_specialization(page_type, specialized_class) ⇒ Object
- .specialization_for(page_type) ⇒ Object
- .specialization_for?(page_type) ⇒ Boolean
-
.undefined?(page_number) ⇒ Boolean
A helper to check if a page number is the undefined page number.
Instance Method Summary collapse
-
#checksum_crc32 ⇒ Object
Calculate the checksum of the page using the CRC32c algorithm.
- #checksum_crc32? ⇒ Boolean
-
#checksum_innodb ⇒ Object
Calculate the checksum of the page using InnoDB’s algorithm.
- #checksum_innodb? ⇒ Boolean
-
#checksum_invalid? ⇒ Boolean
Is the page checksum incorrect?.
- #checksum_type ⇒ Object
-
#checksum_valid? ⇒ Boolean
Is the page checksum correct?.
-
#corrupt? ⇒ Boolean
Is the page corrupt, either due to data corruption, tearing, or in the wrong place?.
-
#cursor(buffer_offset) ⇒ Object
If no block is passed, return an BufferCursor object positioned at a specific offset.
- #default_page_size? ⇒ Boolean
-
#dump ⇒ Object
Dump the contents of a page for debugging purposes.
-
#each_page_body_byte_as_uint8(&block) ⇒ Object
Iterate each byte of the page body, except for the FIL header and the FIL trailer.
-
#each_page_header_byte_as_uint8(&block) ⇒ Object
Iterate each byte of the FIL header.
- #each_region {|Region.new( offset: pos_fil_header, length: size_fil_header, name: :fil_header, info: "FIL Header" )| ... } ⇒ Object
-
#extent_descriptor? ⇒ Boolean
Is this an extent descriptor page (either FSP_HDR or XDES)?.
-
#fil_header ⇒ Object
Return the “fil” header from the page, which is common for all page types.
-
#fil_trailer ⇒ Object
Return the “fil” trailer from the page, which is common for all page types.
-
#in_doublewrite_buffer? ⇒ Boolean
Is the page in the doublewrite buffer?.
-
#initialize(space, buffer, page_number = nil) ⇒ Page
constructor
Initialize a page by passing in a buffer containing the raw page contents.
-
#inspect ⇒ Object
Implement a custom inspect method to avoid irb printing the contents of the page buffer, since it’s very large and mostly not interesting.
- #inspect_header_fields ⇒ Object
-
#misplaced? ⇒ Boolean
Is the page misplaced in the wrong file or by offset in the file?.
-
#misplaced_offset? ⇒ Boolean
Is the page number stored in the header different from the page number which was supposed to be read?.
-
#misplaced_space? ⇒ Boolean
Is the space ID stored in the header different from that of the space provided when initializing this page?.
-
#name ⇒ Object
Return a simple string to uniquely identify this page within the space.
-
#pos_fil_header ⇒ Object
Return the byte offset of the start of the “fil” header, which is at the beginning of the page.
-
#pos_fil_trailer ⇒ Object
Return the byte offset of the start of the “fil” trailer, which is at the end of the page.
-
#pos_page_body ⇒ Object
Return the position of the “body” of the page, which starts after the FIL header.
-
#pos_partial_page_header ⇒ Object
The start of the checksummed portion of the file header.
-
#size ⇒ Object
Return the page size, to eventually be able to deal with non-16kB pages.
-
#size_fil_header ⇒ Object
Return the size of the “fil” header, in bytes.
-
#size_fil_trailer ⇒ Object
Return the size of the “fil” trailer, in bytes.
-
#size_page_body ⇒ Object
Return the size of the page body, excluding the header and trailer.
-
#size_partial_page_header ⇒ Object
The size of the portion of the fil header that is included in the checksum.
-
#torn? ⇒ Boolean
Is the LSN stored in the header different from the one stored in the trailer?.
Constructor Details
#initialize(space, buffer, page_number = nil) ⇒ Page
Initialize a page by passing in a buffer containing the raw page contents. The buffer size should match the space’s page size.
103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/innodb/page.rb', line 103 def initialize(space, buffer, page_number = nil) unless space && buffer raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})" end raise "Buffer size #{buffer.size} is different than space page size" unless space.page_size == buffer.size @space = space @buffer = buffer @page_number = page_number end |
Class Attribute Details
.specialized_classes ⇒ Object (readonly)
Returns the value of attribute specialized_classes.
57 58 59 |
# File 'lib/innodb/page.rb', line 57 def specialized_classes @specialized_classes end |
Instance Attribute Details
#space ⇒ Object (readonly)
Returns the value of attribute space.
115 116 117 |
# File 'lib/innodb/page.rb', line 115 def space @space end |
Class Method Details
.handle(_page, space, buffer, page_number = nil) ⇒ Object
Allow the specialized class to do something that isn’t ‘new’ with this page.
97 98 99 |
# File 'lib/innodb/page.rb', line 97 def self.handle(_page, space, buffer, page_number = nil) new(space, buffer, page_number) end |
.maybe_undefined(page_number) ⇒ Object
A helper to convert “undefined” values stored in previous and next pointers in the page header to nil.
284 285 286 |
# File 'lib/innodb/page.rb', line 284 def self.maybe_undefined(page_number) page_number unless undefined?(page_number) end |
.parse(space, buffer, page_number = nil) ⇒ Object
Load a page as a generic page in order to make the “fil” header accessible, and then attempt to hand off the page to a specialized class to be re-parsed if possible. If there is no specialized class for this type of page, return the generic object.
This could be optimized to reach into the page buffer and efficiently extract the page type in order to avoid throwing away a generic Innodb::Page object when parsing every specialized page, but this is a bit cleaner, and we’re not particularly performance sensitive.
83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/innodb/page.rb', line 83 def self.parse(space, buffer, page_number = nil) # Create a page object as a generic page. page = Innodb::Page.new(space, buffer, page_number) # If there is a specialized class available for this page type, re-create # the page object using that specialized class. if (specialized_class = specialized_classes[page.type]) page = specialized_class.handle(page, space, buffer, page_number) end page end |
.register_specialization(page_type, specialized_class) ⇒ Object
60 61 62 |
# File 'lib/innodb/page.rb', line 60 def self.register_specialization(page_type, specialized_class) @specialized_classes[page_type] = specialized_class end |
.specialization_for(page_type) ⇒ Object
64 65 66 67 68 |
# File 'lib/innodb/page.rb', line 64 def self.specialization_for(page_type) # This needs to intentionally use Innodb::Page because we need to register # in the class instance variable in *that* class. Innodb::Page.register_specialization(page_type, self) end |
.specialization_for?(page_type) ⇒ Boolean
70 71 72 |
# File 'lib/innodb/page.rb', line 70 def self.specialization_for?(page_type) Innodb::Page.specialized_classes.include?(page_type) end |
.undefined?(page_number) ⇒ Boolean
A helper to check if a page number is the undefined page number.
278 279 280 |
# File 'lib/innodb/page.rb', line 278 def self.undefined?(page_number) page_number == UNDEFINED_PAGE_NUMBER end |
Instance Method Details
#checksum_crc32 ⇒ Object
Calculate the checksum of the page using the CRC32c algorithm.
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/innodb/page.rb', line 358 def checksum_crc32 raise "Checksum calculation is only supported for 16 KiB pages" unless default_page_size? @checksum_crc32 ||= begin # Calculate the CRC32c of the page header. crc_partial_header = Digest::CRC32c.new each_page_header_byte_as_uint8 do |byte| crc_partial_header << byte.chr end # Calculate the CRC32c of the page body. crc_page_body = Digest::CRC32c.new each_page_body_byte_as_uint8 do |byte| crc_page_body << byte.chr end # Bitwise XOR the two checksums together. crc_partial_header.checksum ^ crc_page_body.checksum end end |
#checksum_crc32? ⇒ Boolean
379 380 381 |
# File 'lib/innodb/page.rb', line 379 def checksum_crc32? checksum == checksum_crc32 end |
#checksum_innodb ⇒ Object
Calculate the checksum of the page using InnoDB’s algorithm.
338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/innodb/page.rb', line 338 def checksum_innodb raise "Checksum calculation is only supported for 16 KiB pages" unless default_page_size? @checksum_innodb ||= begin # Calculate the InnoDB checksum of the page header. c_partial_header = Innodb::Checksum.fold_enumerator(each_page_header_byte_as_uint8) # Calculate the InnoDB checksum of the page body. c_page_body = Innodb::Checksum.fold_enumerator(each_page_body_byte_as_uint8) # Add the two checksums together, and mask the result back to 32 bits. (c_partial_header + c_page_body) & Innodb::Checksum::MAX end end |
#checksum_innodb? ⇒ Boolean
353 354 355 |
# File 'lib/innodb/page.rb', line 353 def checksum_innodb? checksum == checksum_innodb end |
#checksum_invalid? ⇒ Boolean
Is the page checksum incorrect?
389 390 391 |
# File 'lib/innodb/page.rb', line 389 def checksum_invalid? !checksum_valid? end |
#checksum_type ⇒ Object
393 394 395 396 397 398 |
# File 'lib/innodb/page.rb', line 393 def checksum_type return :crc32 if checksum_crc32? return :innodb if checksum_innodb? nil end |
#checksum_valid? ⇒ Boolean
Is the page checksum correct?
384 385 386 |
# File 'lib/innodb/page.rb', line 384 def checksum_valid? checksum_crc32? || checksum_innodb? end |
#corrupt? ⇒ Boolean
Is the page corrupt, either due to data corruption, tearing, or in the wrong place?
430 431 432 |
# File 'lib/innodb/page.rb', line 430 def corrupt? checksum_invalid? || torn? || misplaced? end |
#cursor(buffer_offset) ⇒ Object
If no block is passed, return an BufferCursor object positioned at a specific offset. If a block is passed, create a cursor at the provided offset and yield it to the provided block one time, and then return the return value of the block.
142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/innodb/page.rb', line 142 def cursor(buffer_offset) new_cursor = BufferCursor.new(@buffer, buffer_offset) new_cursor.push_name("space[#{space.name}]") new_cursor.push_name("page[#{name}]") if block_given? # Call the block once and return its return value. yield new_cursor else # Return the cursor itself. new_cursor end end |
#default_page_size? ⇒ Boolean
122 123 124 |
# File 'lib/innodb/page.rb', line 122 def default_page_size? size == Innodb::Space::DEFAULT_PAGE_SIZE end |
#dump ⇒ Object
Dump the contents of a page for debugging purposes.
483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/innodb/page.rb', line 483 def dump puts "#{self}:" puts puts "fil header:" pp fil_header puts puts "fil trailer:" pp fil_trailer puts end |
#each_page_body_byte_as_uint8(&block) ⇒ Object
Iterate each byte of the page body, except for the FIL header and the FIL trailer.
331 332 333 334 335 |
# File 'lib/innodb/page.rb', line 331 def each_page_body_byte_as_uint8(&block) return enum_for(:each_page_body_byte_as_uint8) unless block_given? cursor(pos_page_body).each_byte_as_uint8(size_page_body, &block) end |
#each_page_header_byte_as_uint8(&block) ⇒ Object
Iterate each byte of the FIL header.
323 324 325 326 327 |
# File 'lib/innodb/page.rb', line 323 def each_page_header_byte_as_uint8(&block) return enum_for(:each_page_header_byte_as_uint8) unless block_given? cursor(pos_partial_page_header).each_byte_as_uint8(size_partial_page_header, &block) end |
#each_region {|Region.new( offset: pos_fil_header, length: size_fil_header, name: :fil_header, info: "FIL Header" )| ... } ⇒ Object
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 |
# File 'lib/innodb/page.rb', line 439 def each_region return enum_for(:each_region) unless block_given? yield Region.new( offset: pos_fil_header, length: size_fil_header, name: :fil_header, info: "FIL Header" ) yield Region.new( offset: pos_fil_trailer, length: size_fil_trailer, name: :fil_trailer, info: "FIL Trailer" ) nil end |
#extent_descriptor? ⇒ Boolean
Is this an extent descriptor page (either FSP_HDR or XDES)?
435 436 437 |
# File 'lib/innodb/page.rb', line 435 def extent_descriptor? type == :FSP_HDR || type == :XDES end |
#fil_header ⇒ Object
Return the “fil” header from the page, which is common for all page types.
289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/innodb/page.rb', line 289 def fil_header @fil_header ||= cursor(pos_fil_header).name("fil_header") do |c| FilHeader.new( checksum: c.name("checksum") { c.read_uint32 }, offset: c.name("offset") { c.read_uint32 }, prev: c.name("prev") { Innodb::Page.maybe_undefined(c.read_uint32) }, next: c.name("next") { Innodb::Page.maybe_undefined(c.read_uint32) }, lsn: c.name("lsn") { c.read_uint64 }, type: c.name("type") { PAGE_TYPE_BY_VALUE[c.read_uint16] }, flush_lsn: c.name("flush_lsn") { c.read_uint64 }, space_id: c.name("space_id") { c.read_uint32 } ) end end |
#fil_trailer ⇒ Object
Return the “fil” trailer from the page, which is common for all page types.
305 306 307 308 309 310 311 312 |
# File 'lib/innodb/page.rb', line 305 def fil_trailer @fil_trailer ||= cursor(pos_fil_trailer).name("fil_trailer") do |c| FilTrailer.new( checksum: c.name("checksum") { c.read_uint32 }, lsn_low32: c.name("lsn_low32") { c.read_uint32 } ) end end |
#in_doublewrite_buffer? ⇒ Boolean
Is the page in the doublewrite buffer?
407 408 409 |
# File 'lib/innodb/page.rb', line 407 def in_doublewrite_buffer? space&.system_space? && space&.doublewrite_page?(offset) end |
#inspect ⇒ Object
Implement a custom inspect method to avoid irb printing the contents of the page buffer, since it’s very large and mostly not interesting.
478 479 480 |
# File 'lib/innodb/page.rb', line 478 def inspect "#<#{self.class} #{inspect_header_fields || '(page header unavailable)'}>" end |
#inspect_header_fields ⇒ Object
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'lib/innodb/page.rb', line 459 def inspect_header_fields return nil unless fil_header %i[ size space_id offset type prev next checksum_valid? checksum_type torn? misplaced? ].map { |m| "#{m}=#{send(m).inspect}" }.join(", ") end |
#misplaced? ⇒ Boolean
Is the page misplaced in the wrong file or by offset in the file?
424 425 426 |
# File 'lib/innodb/page.rb', line 424 def misplaced? !in_doublewrite_buffer? && (misplaced_space? || misplaced_offset?) end |
#misplaced_offset? ⇒ Boolean
Is the page number stored in the header different from the page number which was supposed to be read?
419 420 421 |
# File 'lib/innodb/page.rb', line 419 def misplaced_offset? offset != @page_number end |
#misplaced_space? ⇒ Boolean
Is the space ID stored in the header different from that of the space provided when initializing this page?
413 414 415 |
# File 'lib/innodb/page.rb', line 413 def misplaced_space? space && (space_id != space.space_id) end |
#name ⇒ Object
Return a simple string to uniquely identify this page within the space. Be careful not to call anything which would instantiate a BufferCursor so that we can use this method in cursor initialization.
129 130 131 132 133 134 135 136 |
# File 'lib/innodb/page.rb', line 129 def name page_offset = BinData::Uint32be.read(@buffer.slice(4, 4)) page_type = BinData::Uint16be.read(@buffer.slice(24, 2)) "%i,%s" % [ page_offset, PAGE_TYPE_BY_VALUE[page_type], ] end |
#pos_fil_header ⇒ Object
Return the byte offset of the start of the “fil” header, which is at the beginning of the page. Included here primarily for completeness.
158 159 160 |
# File 'lib/innodb/page.rb', line 158 def pos_fil_header 0 end |
#pos_fil_trailer ⇒ Object
Return the byte offset of the start of the “fil” trailer, which is at the end of the page.
183 184 185 |
# File 'lib/innodb/page.rb', line 183 def pos_fil_trailer size - size_fil_trailer end |
#pos_page_body ⇒ Object
Return the position of the “body” of the page, which starts after the FIL header.
194 195 196 |
# File 'lib/innodb/page.rb', line 194 def pos_page_body pos_fil_header + size_fil_header end |
#pos_partial_page_header ⇒ Object
The start of the checksummed portion of the file header.
168 169 170 |
# File 'lib/innodb/page.rb', line 168 def pos_partial_page_header pos_fil_header + 4 end |
#size ⇒ Object
Return the page size, to eventually be able to deal with non-16kB pages.
118 119 120 |
# File 'lib/innodb/page.rb', line 118 def size @size ||= @buffer.size end |
#size_fil_header ⇒ Object
Return the size of the “fil” header, in bytes.
163 164 165 |
# File 'lib/innodb/page.rb', line 163 def size_fil_header 4 + 4 + 4 + 4 + 8 + 2 + 8 + 4 end |
#size_fil_trailer ⇒ Object
Return the size of the “fil” trailer, in bytes.
188 189 190 |
# File 'lib/innodb/page.rb', line 188 def size_fil_trailer 4 + 4 end |
#size_page_body ⇒ Object
Return the size of the page body, excluding the header and trailer.
199 200 201 |
# File 'lib/innodb/page.rb', line 199 def size_page_body size - size_fil_trailer - size_fil_header end |
#size_partial_page_header ⇒ Object
The size of the portion of the fil header that is included in the checksum. Exclude the following:
:checksum (offset 4, size 4)
:flush_lsn (offset 26, size 8)
:space_id (offset 34, size 4)
177 178 179 |
# File 'lib/innodb/page.rb', line 177 def size_partial_page_header size_fil_header - 4 - 8 - 4 end |
#torn? ⇒ Boolean
Is the LSN stored in the header different from the one stored in the trailer?
402 403 404 |
# File 'lib/innodb/page.rb', line 402 def torn? fil_header.lsn_low32 != fil_trailer.lsn_low32 end |