Class: Innodb::Page

Inherits:
Object
  • Object
show all
Defined in:
lib/innodb/page.rb

Overview

A generic class for any type of page, which handles reading the common FIL header and trailer, and can handle (via #parse) dispatching to a more specialized class depending on page type (which comes from the FIL header). A page being handled by Innodb::Page indicates that its type is not currently handled by any more specialized class.

Defined Under Namespace

Classes: Blob, FspHdrXdes, IbufBitmap, Index, Inode, Sys, SysDataDictionaryHeader, SysIbufHeader, SysRsegHeader, TrxSys, UndoLog

Constant Summary collapse

SPECIALIZED_CLASSES =

A hash of page types to specialized classes to handle them. Normally subclasses will register themselves in this list.

{}
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 => 17855,
    :description => "B+Tree index",
    :usage => "table and index data stored in B+Tree structure",
  },
}
PAGE_TYPE_BY_VALUE =
PAGE_TYPE.inject({}) { |h, (k, v)| h[v[:value]] = k; h }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(space, buffer) ⇒ Page

Initialize a page by passing in a buffer containing the raw page contents. The buffer size should match the space’s page size.



42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/innodb/page.rb', line 42

def initialize(space, buffer)
  unless space && buffer
    raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})"
  end

  unless space.page_size == buffer.size
    raise "Buffer size #{buffer.size} is different than space page size"
  end

  @space  = space
  @buffer = buffer
end

Instance Attribute Details

#spaceObject (readonly)

Returns the value of attribute space.



55
56
57
# File 'lib/innodb/page.rb', line 55

def space
  @space
end

Class Method Details

.handle(page, space, buffer) ⇒ Object

Allow the specialized class to do something that isn’t ‘new’ with this page.



36
37
38
# File 'lib/innodb/page.rb', line 36

def self.handle(page, space, buffer)
  self.new(space, buffer)
end

.maybe_undefined(value) ⇒ Object

A helper to convert “undefined” values stored in previous and next pointers in the page header to nil.



193
194
195
# File 'lib/innodb/page.rb', line 193

def self.maybe_undefined(value)
  value == 4294967295 ? nil : value
end

.parse(space, buffer) ⇒ 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.



22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/innodb/page.rb', line 22

def self.parse(space, buffer)
  # Create a page object as a generic page.
  page = Innodb::Page.new(space, buffer)

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

  page
end

Instance Method Details

#calculate_checksumObject

Calculate the checksum of the page using InnoDB’s algorithm. Two sections of the page are checksummed separately, and then added together to produce the final checksum.



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/innodb/page.rb', line 257

def calculate_checksum
  unless size == 16384
    raise "Checksum calculation is only supported for 16 KiB pages"
  end

  # Calculate the checksum of the FIL header, except for the following:
  #   :checksum   (offset 4, size 4)
  #   :flush_lsn  (offset 26, size 8)
  #   :space_id   (offset 34, size 4)
  c_partial_header =
    Innodb::Checksum.fold_enumerator(
      cursor(pos_fil_header + 4).each_byte_as_uint8(
        size_fil_header - 4 - 8 - 4
      )
    )

  # Calculate the checksum of the page body, except for the FIL header and
  # the FIL trailer.
  c_page_body =
    Innodb::Checksum.fold_enumerator(
      cursor(pos_page_body).each_byte_as_uint8(
        size - size_fil_trailer - size_fil_header
      )
    )

  # Add the two checksums together, and mask the result back to 32 bits.
  (c_partial_header + c_page_body) & Innodb::Checksum::MAX
end

#checksumObject

A helper function to return the checksum from the “fil” header, for easier access.



219
220
221
# File 'lib/innodb/page.rb', line 219

def checksum
  fil_header[:checksum]
end

#corrupt?Boolean

Is the page corrupt? Calculate the checksum of the page and compare to the stored checksum; return true or false.

Returns:

  • (Boolean)


288
289
290
# File 'lib/innodb/page.rb', line 288

def corrupt?
  checksum != calculate_checksum
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.



78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/innodb/page.rb', line 78

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

#dumpObject

Dump the contents of a page for debugging purposes.



333
334
335
336
337
338
339
340
# File 'lib/innodb/page.rb', line 333

def dump
  puts "#{self}:"
  puts

  puts "fil header:"
  pp fil_header
  puts
end

#each_region {|{ :offset => pos_fil_header, :length => size_fil_header, :name => :fil_header, :info => "FIL Header", }| ... } ⇒ Object

Yields:



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/innodb/page.rb', line 292

def each_region
  unless block_given?
    return enum_for(:each_region)
  end

  yield({
    :offset => pos_fil_header,
    :length => size_fil_header,
    :name   => :fil_header,
    :info   => "FIL Header",
  })

  yield({
    :offset => pos_fil_trailer,
    :length => size_fil_trailer,
    :name   => :fil_trailer,
    :info   => "FIL Trailer",
  })

  nil
end

#fil_headerObject

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



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/innodb/page.rb', line 198

def fil_header
  @fil_header ||= cursor(pos_fil_header).name("fil") do |c|
    {
      :checksum   => c.name("checksum") { c.get_uint32 },
      :offset     => c.name("offset") { c.get_uint32 },
      :prev       => c.name("prev") {
        Innodb::Page.maybe_undefined(c.get_uint32)
      },
      :next       => c.name("next") {
        Innodb::Page.maybe_undefined(c.get_uint32)
      },
      :lsn        => c.name("lsn") { c.get_uint64 },
      :type       => c.name("type") { PAGE_TYPE_BY_VALUE[c.get_uint16] },
      :flush_lsn  => c.name("flush_lsn") { c.get_uint64 },
      :space_id   => c.name("space_id") { c.get_uint32 },
    }
  end
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.



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/innodb/page.rb', line 316

def inspect
  if fil_header
    "#<%s: size=%i, space_id=%i, offset=%i, type=%s, prev=%s, next=%s>" % [
      self.class,
      size,
      fil_header[:space_id],
      fil_header[:offset],
      fil_header[:type],
      fil_header[:prev] || "nil",
      fil_header[:next] || "nil",
    ]
  else
    "#<#{self.class}>"
  end
end

#lsnObject

A helper function to return the LSN, for easier access.



244
245
246
# File 'lib/innodb/page.rb', line 244

def lsn
  fil_header[:lsn]
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.



65
66
67
68
69
70
71
72
# File 'lib/innodb/page.rb', line 65

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

#nextObject

A helper function to return the page number of the logical next page (from the doubly-linked list from page to page) from the “fil” header, for easier access.



239
240
241
# File 'lib/innodb/page.rb', line 239

def next
  fil_header[:next]
end

#offsetObject

A helper function to return the page offset from the “fil” header, for easier access.



225
226
227
# File 'lib/innodb/page.rb', line 225

def offset
  fil_header[:offset]
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.



94
95
96
# File 'lib/innodb/page.rb', line 94

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.



105
106
107
# File 'lib/innodb/page.rb', line 105

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.



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

def pos_page_body
  pos_fil_header + size_fil_header
end

#prevObject

A helper function to return the page number of the logical previous page (from the doubly-linked list from page to page) from the “fil” header, for easier access.



232
233
234
# File 'lib/innodb/page.rb', line 232

def prev
  fil_header[:prev]
end

#sizeObject

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



58
59
60
# File 'lib/innodb/page.rb', line 58

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

#size_fil_headerObject

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



99
100
101
# File 'lib/innodb/page.rb', line 99

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.



110
111
112
# File 'lib/innodb/page.rb', line 110

def size_fil_trailer
  4 + 4
end

#typeObject

A helper function to return the page type from the “fil” header, for easier access.



250
251
252
# File 'lib/innodb/page.rb', line 250

def type
  fil_header[:type]
end