Class: Innodb::Page

Inherits:
Object
  • Object
show all
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

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",
  },
  UNDO_LOG: {
    value: 2,
    description: "Undo log",
  },
  INODE: {
    value: 3,
    description: "File segment inode",
  },
  IBUF_FREE_LIST: {
    value: 4,
    description: "Insert buffer free list",
  },
  IBUF_BITMAP: {
    value: 5,
    description: "Insert buffer bitmap",
  },
  SYS: {
    value: 6,
    description: "System internal",
  },
  TRX_SYS: {
    value: 7,
    description: "Transaction system header",
  },
  FSP_HDR: {
    value: 8,
    description: "File space header",
  },
  XDES: {
    value: 9,
    description: "Extent descriptor",
  },
  BLOB: {
    value: 10,
    description: "Uncompressed BLOB",
  },
  ZBLOB: {
    value: 11,
    description: "First compressed BLOB",
  },
  ZBLOB2: {
    value: 12,
    description: "Subsequent compressed BLOB",
  },
  UNKNOWN: {
    value: 13,
    description: "Unknown",
  },
  COMPRESSED: {
    value: 14,
    description: "Compressed",
  },
  ENCRYPTED: {
    value: 15,
    description: "Encrypted",
  },
  COMPRESSED_AND_ENCRYPTED: {
    value: 16,
    description: "Compressed and Encrypted",
  },
  ENCRYPTED_RTREE: {
    value: 17,
    description: "Encrypted R-tree",
  },
  SDI_BLOB: {
    value: 18,
    description: "Uncompressed SDI BLOB",
  },
  SDI_ZBLOB: {
    value: 19,
    description: "Compressed SDI BLOB",
  },
  LEGACY_DBLWR: {
    value: 20,
    description: "Legacy doublewrite buffer",
  },
  RSEG_ARRAY: {
    value: 21,
    description: "Rollback Segment Array",
  },
  LOB_INDEX: {
    value: 22,
    description: "Index of uncompressed LOB",
  },
  LOB_DATA: {
    value: 23,
    description: "Data of uncompressed LOB",
  },
  LOB_FIRST: {
    value: 24,
    description: "First page of an uncompressed LOB",
  },
  ZLOB_FIRST: {
    value: 25,
    description: "First page of a compressed LOB",
  },
  ZLOB_DATA: {
    value: 26,
    description: "Data of compressed LOB",
  },
  ZLOB_INDEX: {
    value: 27,
    description: "Index of compressed LOB",
  },
  ZLOB_FRAG: {
    value: 28,
    description: "Fragment of compressed LOB",
  },
  ZLOB_FRAG_ENTRY: {
    value: 29,
    description: "Index of fragment for compressed LOB",
  },
  SDI: {
    value: 17_853,
    description: "Serialized Dictionary Information",
  },
  RTREE: {
    value: 17_854,
    description: "R-tree index",
  },
  INDEX: {
    value: 17_855,
    description: "B+Tree index",
  },
}.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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

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
# 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

Class Attribute Details

.specialized_classesObject (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

#spaceObject (readonly)

Returns the value of attribute space.



112
113
114
# File 'lib/innodb/page.rb', line 112

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.



344
345
346
# File 'lib/innodb/page.rb', line 344

def self.maybe_undefined(page_number)
  page_number unless undefined?(page_number)
end

.page_type_by_value(value) ⇒ Object



348
349
350
# File 'lib/innodb/page.rb', line 348

def self.page_type_by_value(value)
  PAGE_TYPE_BY_VALUE[value] || value
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

Returns:

  • (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.

Returns:

  • (Boolean)


338
339
340
# File 'lib/innodb/page.rb', line 338

def self.undefined?(page_number)
  page_number == UNDEFINED_PAGE_NUMBER
end

Instance Method Details

#checksum_crc32Object

Calculate the checksum of the page using the CRC32c algorithm.



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# 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

#checksum_crc32?Boolean

Returns:

  • (Boolean)


443
444
445
# File 'lib/innodb/page.rb', line 443

def checksum_crc32?
  checksum == checksum_crc32
end

#checksum_innodbObject

Calculate the checksum of the page using InnoDB’s algorithm.



402
403
404
405
406
407
408
409
410
411
412
413
414
415
# 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

#checksum_innodb?Boolean

Returns:

  • (Boolean)


417
418
419
# File 'lib/innodb/page.rb', line 417

def checksum_innodb?
  checksum == checksum_innodb
end

#checksum_invalid?Boolean

Is the page checksum incorrect?

Returns:

  • (Boolean)


453
454
455
# File 'lib/innodb/page.rb', line 453

def checksum_invalid?
  !checksum_valid?
end

#checksum_typeObject



457
458
459
460
461
462
# File 'lib/innodb/page.rb', line 457

def checksum_type
  return :crc32 if checksum_crc32?
  return :innodb if checksum_innodb?

  nil
end

#checksum_valid?Boolean

Is the page checksum correct?

Returns:

  • (Boolean)


448
449
450
# File 'lib/innodb/page.rb', line 448

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?

Returns:

  • (Boolean)


494
495
496
# File 'lib/innodb/page.rb', line 494

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.



139
140
141
142
143
144
145
146
147
148
149
150
151
# 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

#default_page_size?Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/innodb/page.rb', line 119

def default_page_size?
  size == Innodb::Space::DEFAULT_PAGE_SIZE
end

#dumpObject

Dump the contents of a page for debugging purposes.



547
548
549
550
551
552
553
554
555
556
557
558
# 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

#each_page_body_byte_as_uint8(&block) ⇒ Object

Iterate each byte of the page body, except for the FIL header and the FIL trailer.



395
396
397
398
399
# 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

#each_page_header_byte_as_uint8(&block) ⇒ Object

Iterate each byte of the FIL header.



387
388
389
390
391
# 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

#each_region {|Region.new( offset: pos_fil_header, length: size_fil_header, name: :fil_header, info: "FIL Header" )| ... } ⇒ Object

Yields:



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
# 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

#extent_descriptor?Boolean

Is this an extent descriptor page (either FSP_HDR or XDES)?

Returns:

  • (Boolean)


499
500
501
# File 'lib/innodb/page.rb', line 499

def extent_descriptor?
  type == :FSP_HDR || type == :XDES
end

#fil_headerObject

Return the “fil” header from the page, which is common for all page types.



353
354
355
356
357
358
359
360
361
362
363
364
365
366
# 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

#fil_trailerObject

Return the “fil” trailer from the page, which is common for all page types.



369
370
371
372
373
374
375
376
# 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

#in_doublewrite_buffer?Boolean

Is the page in the doublewrite buffer?

Returns:

  • (Boolean)


471
472
473
# File 'lib/innodb/page.rb', line 471

def in_doublewrite_buffer?
  space&.system_space? && space.doublewrite_page?(offset)
end

#inspectObject

Implement a custom inspect method to avoid irb printing the contents of the page buffer, since it’s very large and mostly not interesting.



542
543
544
# File 'lib/innodb/page.rb', line 542

def inspect
  "#<#{self.class} #{inspect_header_fields || '(page header unavailable)'}>"
end

#inspect_header_fieldsObject



523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# 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

#misplaced?Boolean

Is the page misplaced in the wrong file or by offset in the file?

Returns:

  • (Boolean)


488
489
490
# File 'lib/innodb/page.rb', line 488

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?

Returns:

  • (Boolean)


483
484
485
# File 'lib/innodb/page.rb', line 483

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?

Returns:

  • (Boolean)


477
478
479
# File 'lib/innodb/page.rb', line 477

def misplaced_space?
  space && (space_id != space.space_id)
end

#nameObject

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.



126
127
128
129
130
131
132
133
# 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

#pos_fil_headerObject

Return the byte offset of the start of the “fil” header, which is at the beginning of the page. Included here primarily for completeness.



155
156
157
# File 'lib/innodb/page.rb', line 155

def pos_fil_header
  0
end

#pos_fil_trailerObject

Return the byte offset of the start of the “fil” trailer, which is at the end of the page.



180
181
182
# File 'lib/innodb/page.rb', line 180

def pos_fil_trailer
  size - size_fil_trailer
end

#pos_page_bodyObject

Return the position of the “body” of the page, which starts after the FIL header.



191
192
193
# File 'lib/innodb/page.rb', line 191

def pos_page_body
  pos_fil_header + size_fil_header
end

#pos_partial_page_headerObject

The start of the checksummed portion of the file header.



165
166
167
# File 'lib/innodb/page.rb', line 165

def pos_partial_page_header
  pos_fil_header + 4
end

#sizeObject

Return the page size, to eventually be able to deal with non-16kB pages.



115
116
117
# File 'lib/innodb/page.rb', line 115

def size
  @size ||= @buffer.size
end

#size_fil_headerObject

Return the size of the “fil” header, in bytes.



160
161
162
# File 'lib/innodb/page.rb', line 160

def size_fil_header
  4 + 4 + 4 + 4 + 8 + 2 + 8 + 4
end

#size_fil_trailerObject

Return the size of the “fil” trailer, in bytes.



185
186
187
# File 'lib/innodb/page.rb', line 185

def size_fil_trailer
  4 + 4
end

#size_page_bodyObject

Return the size of the page body, excluding the header and trailer.



196
197
198
# File 'lib/innodb/page.rb', line 196

def size_page_body
  size - size_fil_trailer - size_fil_header
end

#size_partial_page_headerObject

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)


174
175
176
# File 'lib/innodb/page.rb', line 174

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?

Returns:

  • (Boolean)


466
467
468
# File 'lib/innodb/page.rb', line 466

def torn?
  fil_header.lsn_low32 != fil_trailer.lsn_low32
end