innodb_ruby 0.9.0 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
- require "innodb/cursor"
4
-
5
3
  # A generic class for any type of page, which handles reading the common
6
4
  # FIL header and trailer, and can handle (via #parse) dispatching to a more
7
5
  # specialized class depending on page type (which comes from the FIL header).
@@ -61,12 +59,26 @@ class Innodb::Page
61
59
  @size ||= @buffer.size
62
60
  end
63
61
 
64
- # If no block is passed, return an Innodb::Cursor object positioned at a
62
+ # Return a simple string to uniquely identify this page within the space.
63
+ # Be careful not to call anything which would instantiate a BufferCursor
64
+ # so that we can use this method in cursor initialization.
65
+ def name
66
+ page_offset = BinData::Uint32be.read(@buffer.slice(4, 4))
67
+ page_type = BinData::Uint16be.read(@buffer.slice(24, 2))
68
+ "%i,%s" % [
69
+ page_offset,
70
+ PAGE_TYPE_BY_VALUE[page_type],
71
+ ]
72
+ end
73
+
74
+ # If no block is passed, return an BufferCursor object positioned at a
65
75
  # specific offset. If a block is passed, create a cursor at the provided
66
76
  # offset and yield it to the provided block one time, and then return the
67
77
  # return value of the block.
68
- def cursor(offset)
69
- new_cursor = Innodb::Cursor.new(@buffer, offset)
78
+ def cursor(buffer_offset)
79
+ new_cursor = BufferCursor.new(@buffer, buffer_offset)
80
+ new_cursor.push_name("space[#{space.name}]")
81
+ new_cursor.push_name("page[#{name}]")
70
82
 
71
83
  if block_given?
72
84
  # Call the block once and return its return value.
@@ -277,6 +289,28 @@ class Innodb::Page
277
289
  checksum != calculate_checksum
278
290
  end
279
291
 
292
+ def each_region
293
+ unless block_given?
294
+ return enum_for(:each_region)
295
+ end
296
+
297
+ yield({
298
+ :offset => pos_fil_header,
299
+ :length => size_fil_header,
300
+ :name => :fil_header,
301
+ :info => "FIL Header",
302
+ })
303
+
304
+ yield({
305
+ :offset => pos_fil_trailer,
306
+ :length => size_fil_trailer,
307
+ :name => :fil_trailer,
308
+ :info => "FIL Trailer",
309
+ })
310
+
311
+ nil
312
+ end
313
+
280
314
  # Implement a custom inspect method to avoid irb printing the contents of
281
315
  # the page buffer, since it's very large and mostly not interesting.
282
316
  def inspect
@@ -41,6 +41,32 @@ class Innodb::Page::Blob < Innodb::Page
41
41
  end
42
42
  end
43
43
 
44
+ def each_region
45
+ unless block_given?
46
+ return enum_for(:each_region)
47
+ end
48
+
49
+ super do |region|
50
+ yield region
51
+ end
52
+
53
+ yield({
54
+ :offset => pos_blob_header,
55
+ :length => size_blob_header,
56
+ :name => :blob_header,
57
+ :info => "Blob Header",
58
+ })
59
+
60
+ yield({
61
+ :offset => pos_blob_data,
62
+ :length => blob_header[:length],
63
+ :name => :blob_data,
64
+ :info => "Blob Data",
65
+ })
66
+
67
+ nil
68
+ end
69
+
44
70
  # Dump the contents of a page for debugging purposes.
45
71
  def dump
46
72
  super
@@ -13,16 +13,12 @@ require "innodb/xdes"
13
13
  # The basic structure of FSP_HDR and XDES pages is: FIL header, FSP header,
14
14
  # an array of 256 XDES entries, empty (unused) space, and FIL trailer.
15
15
  class Innodb::Page::FspHdrXdes < Innodb::Page
16
+ extend ReadBitsAtOffset
17
+
16
18
  # A value added to the adjusted exponent stored in the page size field of
17
19
  # the flags in the FSP header.
18
20
  FLAGS_PAGE_SIZE_ADJUST = 9
19
21
 
20
- # Read a given number of bits from an integer at a specific bit offset. The
21
- # value returned is 0-based so does not need further shifting or adjustment.
22
- def self.read_bits_at_offset(data, bits, offset)
23
- ((data & (((1 << bits) - 1) << offset)) >> offset)
24
- end
25
-
26
22
  # Decode the "flags" field in the FSP header, returning a hash of useful
27
23
  # decoded flags. Unfortunately, InnoDB has a fairly weird and broken
28
24
  # implementation of these flags. The flags are:
@@ -74,6 +70,14 @@ class Innodb::Page::FspHdrXdes < Innodb::Page
74
70
  size / space.pages_per_extent
75
71
  end
76
72
 
73
+ def size_xdes_entry
74
+ @size_xdes_entry ||= Innodb::Xdes.new(self, cursor(pos_xdes_array)).size_entry
75
+ end
76
+
77
+ def size_xdes_array
78
+ entries_in_xdes_array * size_xdes_entry
79
+ end
80
+
77
81
  # Read the FSP (filespace) header, which contains a few counters and flags,
78
82
  # as well as list base nodes for each list maintained in the filespace.
79
83
  def fsp_header
@@ -134,6 +138,34 @@ class Innodb::Page::FspHdrXdes < Innodb::Page
134
138
  end
135
139
  end
136
140
 
141
+ def each_region
142
+ unless block_given?
143
+ return enum_for(:each_region)
144
+ end
145
+
146
+ super do |region|
147
+ yield region
148
+ end
149
+
150
+ yield({
151
+ :offset => pos_fsp_header,
152
+ :length => size_fsp_header,
153
+ :name => :fsp_header,
154
+ :info => "FSP Header",
155
+ })
156
+
157
+ each_xdes do |xdes|
158
+ yield({
159
+ :offset => xdes.offset,
160
+ :length => size_xdes_entry,
161
+ :name => :xdes,
162
+ :info => "Extent Descriptor",
163
+ })
164
+ end
165
+
166
+ nil
167
+ end
168
+
137
169
  # Dump the contents of a page for debugging purposes.
138
170
  def dump
139
171
  super
@@ -31,6 +31,11 @@ class Innodb::Page::Index < Innodb::Page
31
31
  # "compact" record format.
32
32
  RECORD_COMPACT_BITS_SIZE = 3
33
33
 
34
+ # Maximum number of fields.
35
+ RECORD_MAX_N_SYSTEM_FIELDS = 3
36
+ RECORD_MAX_N_FIELDS = 1024 - 1
37
+ RECORD_MAX_N_USER_FIELDS = RECORD_MAX_N_FIELDS - RECORD_MAX_N_SYSTEM_FIELDS * 2
38
+
34
39
  # Page direction values possible in the page_header's :direction field.
35
40
  PAGE_DIRECTION = {
36
41
  1 => :left, # Inserts have been in descending order.
@@ -312,6 +317,8 @@ class Innodb::Page::Index < Innodb::Page
312
317
  when :redundant
313
318
  record_header_redundant_additional(header, cursor)
314
319
  end
320
+
321
+ header[:length] = origin - cursor.position
315
322
  end
316
323
 
317
324
  header
@@ -325,13 +332,13 @@ class Innodb::Page::Index < Innodb::Page
325
332
  # bit vector indicating NULL fields and the length of each
326
333
  # non-NULL variable-length field.
327
334
  if record_format
328
- header[:field_nulls] = cursor.name("field_nulls") {
335
+ header[:nulls] = cursor.name("nulls") {
329
336
  record_header_compact_null_bitmap(cursor)
330
337
  }
331
- header[:field_lengths], header[:field_externs] =
332
- cursor.name("field_lengths_and_externs") {
338
+ header[:lengths], header[:externs] =
339
+ cursor.name("lengths_and_externs") {
333
340
  record_header_compact_variable_lengths_and_externs(cursor,
334
- header[:field_nulls])
341
+ header[:nulls])
335
342
  }
336
343
  end
337
344
  end
@@ -339,39 +346,35 @@ class Innodb::Page::Index < Innodb::Page
339
346
 
340
347
  # Return an array indicating which fields are null.
341
348
  def record_header_compact_null_bitmap(cursor)
342
- fields = (record_format[:key] + record_format[:row])
349
+ fields = record_fields
343
350
 
344
351
  # The number of bits in the bitmap is the number of nullable fields.
345
352
  size = fields.count { |f| f.nullable? }
346
353
 
347
354
  # There is no bitmap if there are no nullable fields.
348
- return nil unless size > 0
349
-
350
- # To simplify later checks, expand bitmap to one for each field.
351
- bitmap = Array.new(fields.last.position + 1, false)
355
+ return [] unless size > 0
352
356
 
353
357
  null_bit_array = cursor.get_bit_array(size).reverse!
354
358
 
355
- # For every nullable field, set whether the field is actually null.
356
- fields.each do |f|
357
- bitmap[f.position] = f.nullable? ? (null_bit_array.shift == 1) : false
359
+ # For every nullable field, select the ones which are actually null.
360
+ fields.inject([]) do |nulls, f|
361
+ nulls << f.name if f.nullable? && (null_bit_array.shift == 1)
362
+ nulls
358
363
  end
359
-
360
- return bitmap
361
364
  end
362
365
 
363
366
  # Return an array containing an array of the length of each variable-length
364
367
  # field and an array indicating which fields are stored externally.
365
- def record_header_compact_variable_lengths_and_externs(cursor, null_bitmap)
368
+ def record_header_compact_variable_lengths_and_externs(cursor, nulls)
366
369
  fields = (record_format[:key] + record_format[:row])
367
370
 
368
- len_array = Array.new(fields.last.position + 1, 0)
369
- ext_array = Array.new(fields.last.position + 1, false)
371
+ lengths = {}
372
+ externs = []
370
373
 
371
374
  # For each non-NULL variable-length field, the record header contains
372
375
  # the length in one or two bytes.
373
376
  fields.each do |f|
374
- next if !f.variable? or (null_bitmap && null_bitmap[f.position])
377
+ next if !f.variable? || nulls.include?(f.name)
375
378
 
376
379
  len = cursor.get_uint8
377
380
  ext = false
@@ -383,18 +386,16 @@ class Innodb::Page::Index < Innodb::Page
383
386
  len = ((len & 0x3f) << 8) + cursor.get_uint8
384
387
  end
385
388
 
386
- len_array[f.position] = len
387
- ext_array[f.position] = ext
389
+ lengths[f.name] = len
390
+ externs << f.name if ext
388
391
  end
389
392
 
390
- return len_array, ext_array
393
+ return lengths, externs
391
394
  end
392
395
 
393
396
  # Read additional header information from a redundant format record header.
394
397
  def record_header_redundant_additional(header, cursor)
395
- header[:field_lengths] = []
396
- header[:field_nulls] = []
397
- header[:field_externs] = []
398
+ lengths, nulls, externs = [], [], []
398
399
 
399
400
  field_offsets = record_header_redundant_field_end_offsets(header, cursor)
400
401
 
@@ -403,17 +404,31 @@ class Innodb::Page::Index < Innodb::Page
403
404
  case header[:offset_size]
404
405
  when 1
405
406
  next_field_offset = (n & RECORD_REDUNDANT_OFF1_OFFSET_MASK)
406
- header[:field_lengths] << (next_field_offset - this_field_offset)
407
- header[:field_nulls] << ((n & RECORD_REDUNDANT_OFF1_NULL_MASK) != 0)
408
- header[:field_externs] << false
407
+ lengths << (next_field_offset - this_field_offset)
408
+ nulls << ((n & RECORD_REDUNDANT_OFF1_NULL_MASK) != 0)
409
+ externs << false
409
410
  when 2
410
411
  next_field_offset = (n & RECORD_REDUNDANT_OFF2_OFFSET_MASK)
411
- header[:field_lengths] << (next_field_offset - this_field_offset)
412
- header[:field_nulls] << ((n & RECORD_REDUNDANT_OFF2_NULL_MASK) != 0)
413
- header[:field_externs] << ((n & RECORD_REDUNDANT_OFF2_EXTERN_MASK) != 0)
412
+ lengths << (next_field_offset - this_field_offset)
413
+ nulls << ((n & RECORD_REDUNDANT_OFF2_NULL_MASK) != 0)
414
+ externs << ((n & RECORD_REDUNDANT_OFF2_EXTERN_MASK) != 0)
414
415
  end
415
416
  this_field_offset = next_field_offset
416
417
  end
418
+
419
+ # If possible, refer to fields by name rather than position for
420
+ # better formatting (i.e. pp).
421
+ if record_format
422
+ header[:lengths], header[:nulls], header[:externs] = {}, [], []
423
+
424
+ record_fields.each do |f|
425
+ header[:lengths][f.name] = lengths[f.position]
426
+ header[:nulls] << f.name if nulls[f.position]
427
+ header[:externs] << f.name if externs[f.position]
428
+ end
429
+ else
430
+ header[:lengths], header[:nulls], header[:externs] = lengths, nulls, externs
431
+ end
417
432
  end
418
433
 
419
434
  # Read field end offsets from the provided cursor for each field as counted
@@ -437,6 +452,7 @@ class Innodb::Page::Index < Innodb::Page
437
452
  :header => header,
438
453
  :next => header[:next],
439
454
  :data => c.name("data") { c.get_bytes(size_mum_record) },
455
+ :length => c.position - offset,
440
456
  })
441
457
  end
442
458
  end
@@ -470,24 +486,28 @@ class Innodb::Page::Index < Innodb::Page
470
486
 
471
487
  # Return a set of field objects that describe the record.
472
488
  def make_record_description
489
+ position = (0..RECORD_MAX_N_FIELDS).each
473
490
  description = record_describer.description
474
-
475
- position = 0
476
- fields = {:type => description[:type], :key => [], :row => []}
491
+ fields = {:type => description[:type], :key => [], :sys => [], :row => []}
477
492
 
478
493
  description[:key].each do |field|
479
- fields[:key] << Innodb::Field.new(position, field[:name], *field[:type])
480
- position += 1
494
+ fields[:key] << Innodb::Field.new(position.next, field[:name], *field[:type])
481
495
  end
482
496
 
483
- if description[:type] == :clustered
484
- # Account for TRX_ID and ROLL_PTR.
485
- position += 2
497
+ # If this is a leaf page of the clustered index, read InnoDB's internal
498
+ # fields, a transaction ID and roll pointer.
499
+ if level == 0 && fields[:type] == :clustered
500
+ [["DB_TRX_ID", :TRX_ID,],["DB_ROLL_PTR", :ROLL_PTR]].each do |name, type|
501
+ fields[:sys] << Innodb::Field.new(position.next, name, type, :NOT_NULL)
502
+ end
486
503
  end
487
504
 
488
- description[:row].each do |field|
489
- fields[:row] << Innodb::Field.new(position, field[:name], *field[:type])
490
- position += 1
505
+ # If this is a leaf page of the clustered index, or any page of a
506
+ # secondary index, read the non-key fields.
507
+ if (level == 0 && fields[:type] == :clustered) || (fields[:type] == :secondary)
508
+ description[:row].each do |field|
509
+ fields[:row] << Innodb::Field.new(position.next, field[:name], *field[:type])
510
+ end
491
511
  end
492
512
 
493
513
  fields
@@ -500,6 +520,13 @@ class Innodb::Page::Index < Innodb::Page
500
520
  end
501
521
  end
502
522
 
523
+ # Returns the (ordered) set of fields that describe records in this page.
524
+ def record_fields
525
+ if record_format
526
+ record_format.values_at(:key, :sys, :row).flatten.sort_by {|f| f.position}
527
+ end
528
+ end
529
+
503
530
  # Parse and return a record at a given offset.
504
531
  def record(offset)
505
532
  return nil unless offset
@@ -520,53 +547,23 @@ class Innodb::Page::Index < Innodb::Page
520
547
  if record_format
521
548
  this_record[:type] = record_format[:type]
522
549
 
523
- # Read the key fields present in all types of pages.
524
- this_record[:key] = []
525
- record_format[:key].each do |f|
526
- c.name("key[#{f.name}]") do
527
- this_record[:key] << {
528
- :name => f.name,
529
- :type => f.data_type.name,
530
- :value => f.value(this_record, c),
531
- :extern => f.extern(this_record, c),
532
- }
533
- end
534
- end
535
-
536
- # If this is a leaf page of the clustered index, read InnoDB's internal
537
- # fields, a transaction ID and roll pointer.
538
- if level == 0 && record_format[:type] == :clustered
539
- this_record[:transaction_id] = c.name("transaction_id") { c.get_hex(6) }
540
- c.name("roll_pointer") do
541
- rseg_id_insert_flag = c.name("rseg_id_insert_flag") { c.get_uint8 }
542
- this_record[:roll_pointer] = {
543
- :is_insert => (rseg_id_insert_flag & 0x80) == 0x80,
544
- :rseg_id => rseg_id_insert_flag & 0x7f,
545
- :undo_log => c.name("undo_log") {
546
- {
547
- :page => c.name("page") { c.get_uint32 },
548
- :offset => c.name("offset") { c.get_uint16 },
549
- }
550
- }
551
- }
552
- end
550
+ # Used to indicate whether a field is part of key/row/sys.
551
+ fmap = [:key, :row, :sys].inject({}) do |h, k|
552
+ this_record[k] = []
553
+ record_format[k].each { |f| h[f.position] = k }
554
+ h
553
555
  end
554
556
 
555
- # If this is a leaf page of the clustered index, or any page of a
556
- # secondary index, read the non-key fields.
557
- if (level == 0 && record_format[:type] == :clustered) ||
558
- (record_format[:type] == :secondary)
559
- # Read the non-key fields.
560
- this_record[:row] = []
561
- record_format[:row].each do |f|
562
- c.name("row[#{f.name}]") do
563
- this_record[:row] << {
564
- :name => f.name,
565
- :type => f.data_type.name,
566
- :value => f.value(this_record, c),
567
- :extern => f.extern(this_record, c),
568
- }
569
- end
557
+ # Read the fields present in this record.
558
+ record_fields.each do |f|
559
+ p = fmap[f.position]
560
+ c.name("#{p.to_s}[#{f.name}]") do
561
+ this_record[p] << {
562
+ :name => f.name,
563
+ :type => f.data_type.name,
564
+ :value => f.value(c, this_record),
565
+ :extern => f.extern(c, this_record),
566
+ }.reject { |k, v| v.nil? }
570
567
  end
571
568
  end
572
569
 
@@ -577,6 +574,18 @@ class Innodb::Page::Index < Innodb::Page
577
574
  this_record[:child_page_number] =
578
575
  c.name("child_page_number") { c.get_uint32 }
579
576
  end
577
+
578
+ this_record[:length] = c.position - offset
579
+
580
+ # Add system field accessors for convenience.
581
+ this_record[:sys].each do |f|
582
+ case f[:name]
583
+ when "DB_TRX_ID"
584
+ this_record[:transaction_id] = f[:value]
585
+ when "DB_ROLL_PTR"
586
+ this_record[:roll_pointer] = f[:value]
587
+ end
588
+ end
580
589
  end
581
590
 
582
591
  Innodb::Record.new(self, this_record)
@@ -649,23 +658,20 @@ class Innodb::Page::Index < Innodb::Page
649
658
  end
650
659
  end
651
660
 
652
- # Return the current record, mostly as a helper.
653
- def current_record
654
- @record
655
- end
656
-
657
661
  # Return the next record, and advance the cursor. Return nil when the
658
662
  # end of records (supremum) is reached.
659
663
  def next_record
660
664
  Innodb::Stats.increment :page_record_cursor_next_record
661
665
 
662
- @record = @page.record(@record.next)
666
+ rec = @page.record(@record.next)
663
667
 
664
- if @record == @page.supremum
668
+ # The garbage record list's end is self-linked, so we must check for
669
+ # both supremum and the current record's offset.
670
+ if rec == @page.supremum || rec.offset == @record.offset
665
671
  # We've reached the end of the linked list at supremum.
666
672
  nil
667
673
  else
668
- @record
674
+ @record = rec
669
675
  end
670
676
  end
671
677
 
@@ -696,7 +702,7 @@ class Innodb::Page::Index < Innodb::Page
696
702
  def record
697
703
  if @initial
698
704
  @initial = false
699
- return current_record
705
+ return @record
700
706
  end
701
707
 
702
708
  case @direction
@@ -885,7 +891,7 @@ class Innodb::Page::Index < Innodb::Page
885
891
  return enum_for(:each_record)
886
892
  end
887
893
 
888
- c = record_cursor(infimum.next)
894
+ c = record_cursor(:min)
889
895
 
890
896
  while rec = c.record
891
897
  yield rec
@@ -930,6 +936,80 @@ class Innodb::Page::Index < Innodb::Page
930
936
  nil
931
937
  end
932
938
 
939
+ def each_region
940
+ unless block_given?
941
+ return enum_for(:each_region)
942
+ end
943
+
944
+ super do |region|
945
+ yield region
946
+ end
947
+
948
+ yield({
949
+ :offset => pos_index_header,
950
+ :length => size_index_header,
951
+ :name => :index_header,
952
+ :info => "Index Header",
953
+ })
954
+
955
+ yield({
956
+ :offset => pos_fseg_header,
957
+ :length => size_fseg_header,
958
+ :name => :fseg_header,
959
+ :info => "File Segment Header",
960
+ })
961
+
962
+ yield({
963
+ :offset => pos_infimum - 5,
964
+ :length => size_mum_record + 5,
965
+ :name => :infimum,
966
+ :info => "Infimum",
967
+ })
968
+
969
+ yield({
970
+ :offset => pos_supremum - 5,
971
+ :length => size_mum_record + 5,
972
+ :name => :supremum,
973
+ :info => "Supremum",
974
+ })
975
+
976
+
977
+ directory_slots.times do |n|
978
+ yield({
979
+ :offset => pos_directory - (n * 2),
980
+ :length => 2,
981
+ :name => :directory,
982
+ :info => "Page Directory",
983
+ })
984
+ end
985
+
986
+ each_garbage_record do |record|
987
+ yield({
988
+ :offset => record.offset - record.header[:length],
989
+ :length => record.length + record.header[:length],
990
+ :name => :garbage,
991
+ :info => "Garbage",
992
+ })
993
+ end
994
+
995
+ each_record do |record|
996
+ yield({
997
+ :offset => record.offset - record.header[:length],
998
+ :length => record.header[:length],
999
+ :name => :record_header,
1000
+ :info => "Record Header",
1001
+ })
1002
+
1003
+ yield({
1004
+ :offset => record.offset,
1005
+ :length => record.length,
1006
+ :name => :record_data,
1007
+ :info => "Record Data",
1008
+ })
1009
+ end
1010
+
1011
+ nil
1012
+ end
933
1013
 
934
1014
  # Dump the contents of a page for debugging purposes.
935
1015
  def dump