innodb_ruby 0.9.0 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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