class Innodb::Page
Constants
- Address
- FilHeader
- FilTrailer
- PAGE_TYPE
InnoDB
Page
Type constants from include/fil0fil.h.- PAGE_TYPE_BY_VALUE
- Region
- UNDEFINED_PAGE_NUMBER
A page number representing “undefined” values, (4294967295).
Attributes
Public Class Methods
Allow the specialized class to do something that isn’t ‘new’ with this page.
# File lib/innodb/page.rb, line 97 def self.handle(_page, space, buffer, page_number = nil) new(space, buffer, page_number) end
A helper to convert “undefined” values stored in previous and next pointers in the page header to nil.
# File lib/innodb/page.rb, line 344 def self.maybe_undefined(page_number) page_number unless undefined?(page_number) end
Initialize a page by passing in a buffer containing the raw page contents. The buffer size should match the space’s page size.
# File lib/innodb/page.rb, line 103 def initialize(space, buffer, page_number = nil) raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})" unless buffer raise "Buffer size #{buffer.size} is different than space page size" if space && space.page_size != buffer.size @space = space @buffer = buffer @page_number = page_number end
# File lib/innodb/page.rb, line 348 def self.page_type_by_value(value) PAGE_TYPE_BY_VALUE[value] || value end
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.
# 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
# File lib/innodb/page.rb, line 60 def self.register_specialization(page_type, specialized_class) @specialized_classes[page_type] = specialized_class end
# 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
# File lib/innodb/page.rb, line 70 def self.specialization_for?(page_type) Innodb::Page.specialized_classes.include?(page_type) end
A helper to check if a page number is the undefined page number.
# File lib/innodb/page.rb, line 338 def self.undefined?(page_number) page_number == UNDEFINED_PAGE_NUMBER end
Public Instance Methods
Calculate the checksum of the page using the CRC32c algorithm.
# File lib/innodb/page.rb, line 422 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
# File lib/innodb/page.rb, line 443 def checksum_crc32? checksum == checksum_crc32 end
Calculate the checksum of the page using InnoDB’s algorithm.
# File lib/innodb/page.rb, line 402 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
# File lib/innodb/page.rb, line 417 def checksum_innodb? checksum == checksum_innodb end
Is the page checksum incorrect?
# File lib/innodb/page.rb, line 453 def checksum_invalid? !checksum_valid? end
# File lib/innodb/page.rb, line 457 def checksum_type return :crc32 if checksum_crc32? return :innodb if checksum_innodb? nil end
Is the page checksum correct?
# File lib/innodb/page.rb, line 448 def checksum_valid? checksum_crc32? || checksum_innodb? end
Is the page corrupt, either due to data corruption, tearing, or in the wrong place?
# File lib/innodb/page.rb, line 494 def corrupt? checksum_invalid? || torn? || misplaced? end
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.
# File lib/innodb/page.rb, line 139 def cursor(buffer_offset) new_cursor = BufferCursor.new(@buffer, buffer_offset) new_cursor.push_name("space[#{space&.name || 'unknown'}]") 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
# File lib/innodb/page.rb, line 119 def default_page_size? size == Innodb::Space::DEFAULT_PAGE_SIZE end
Dump the contents of a page for debugging purposes.
# File lib/innodb/page.rb, line 547 def dump puts "#{self}:" puts puts "fil header:" pp fil_header puts puts "fil trailer:" pp fil_trailer puts end
Iterate each byte of the page body, except for the FIL header and the FIL trailer.
# File lib/innodb/page.rb, line 395 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
Iterate each byte of the FIL header.
# File lib/innodb/page.rb, line 387 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
# File lib/innodb/page.rb, line 503 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
Is this an extent descriptor page (either FSP_HDR or XDES)?
# File lib/innodb/page.rb, line 499 def extent_descriptor? type == :FSP_HDR || type == :XDES end
Return the “fil” header from the page, which is common for all page types.
# File lib/innodb/page.rb, line 353 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") { Innodb::Page.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
Return the “fil” trailer from the page, which is common for all page types.
# File lib/innodb/page.rb, line 369 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
Is the page in the doublewrite buffer?
# File lib/innodb/page.rb, line 471 def in_doublewrite_buffer? space&.system_space? && space.doublewrite_page?(offset) end
Implement a custom inspect method to avoid irb printing the contents of the page buffer, since it’s very large and mostly not interesting.
# File lib/innodb/page.rb, line 542 def inspect "#<#{self.class} #{inspect_header_fields || '(page header unavailable)'}>" end
# File lib/innodb/page.rb, line 523 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
Is the page misplaced in the wrong file or by offset in the file?
# File lib/innodb/page.rb, line 488 def misplaced? !in_doublewrite_buffer? && (misplaced_space? || misplaced_offset?) end
Is the page number stored in the header different from the page number which was supposed to be read?
# File lib/innodb/page.rb, line 483 def misplaced_offset? offset != @page_number end
Is the space ID stored in the header different from that of the space provided when initializing this page?
# File lib/innodb/page.rb, line 477 def misplaced_space? space && (space_id != space.space_id) end
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.
# File lib/innodb/page.rb, line 126 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
Return the byte offset of the start of the “fil” header, which is at the beginning of the page. Included here primarily for completeness.
# File lib/innodb/page.rb, line 155 def pos_fil_header 0 end
Return the byte offset of the start of the “fil” trailer, which is at the end of the page.
# File lib/innodb/page.rb, line 180 def pos_fil_trailer size - size_fil_trailer end
Return the position of the “body” of the page, which starts after the FIL header.
# File lib/innodb/page.rb, line 191 def pos_page_body pos_fil_header + size_fil_header end
The start of the checksummed portion of the file header.
# File lib/innodb/page.rb, line 165 def pos_partial_page_header pos_fil_header + 4 end
Return the page size, to eventually be able to deal with non-16kB pages.
# File lib/innodb/page.rb, line 115 def size @size ||= @buffer.size end
Return the size of the “fil” header, in bytes.
# File lib/innodb/page.rb, line 160 def size_fil_header 4 + 4 + 4 + 4 + 8 + 2 + 8 + 4 end
Return the size of the “fil” trailer, in bytes.
# File lib/innodb/page.rb, line 185 def size_fil_trailer 4 + 4 end
Return the size of the page body, excluding the header and trailer.
# File lib/innodb/page.rb, line 196 def size_page_body size - size_fil_trailer - size_fil_header end
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)
# File lib/innodb/page.rb, line 174 def size_partial_page_header size_fil_header - 4 - 8 - 4 end
Is the LSN
stored in the header different from the one stored in the trailer?
# File lib/innodb/page.rb, line 466 def torn? fil_header.lsn_low32 != fil_trailer.lsn_low32 end