innodb_ruby 0.8.8 → 0.9.0

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.
@@ -8,30 +8,14 @@ class Innodb::LogBlock
8
8
  # Log blocks are fixed-length at 512 bytes in InnoDB.
9
9
  BLOCK_SIZE = 512
10
10
 
11
- HEADER_SIZE = 12
12
- HEADER_START = 0
11
+ # Offset of the header within the log block.
12
+ HEADER_OFFSET = 0
13
13
 
14
- TRAILER_SIZE = 4
15
- TRAILER_START = BLOCK_SIZE - TRAILER_SIZE
14
+ # Offset of the trailer within ths log block.
15
+ TRAILER_OFFSET = BLOCK_SIZE - 4
16
16
 
17
- RECORD_START = HEADER_START + HEADER_SIZE
18
-
19
- # Header:
20
- #define LOG_BLOCK_HDR_NO 0 /* block number which must be > 0 and
21
- #define LOG_BLOCK_HDR_DATA_LEN 4 /* number of bytes of log written to
22
- #define LOG_BLOCK_FIRST_REC_GROUP 6 /* offset of the first start of an
23
- #define LOG_BLOCK_CHECKPOINT_NO 8 /* 4 lower bytes of the value of
24
-
25
- # Trailer:
26
- #define LOG_BLOCK_CHECKSUM 0 /* 4 byte checksum of the log block
27
-
28
- #/* Offsets for a checkpoint field */
29
- #define LOG_CHECKPOINT_NO 0
30
- #define LOG_CHECKPOINT_LSN 8
31
- #define LOG_CHECKPOINT_OFFSET 16
32
- #define LOG_CHECKPOINT_LOG_BUF_SIZE 20
33
- #define LOG_CHECKPOINT_ARCHIVED_LSN 24
34
- #define LOG_CHECKPOINT_GROUP_ARRAY 32
17
+ # Mask used to get the flush bit in the header.
18
+ HEADER_FLUSH_BIT_MASK = 0x80000000
35
19
 
36
20
  # Initialize a log block by passing in a 512-byte buffer containing the raw
37
21
  # log block contents.
@@ -43,36 +27,33 @@ class Innodb::LogBlock
43
27
  @buffer = buffer
44
28
  end
45
29
 
46
- # A helper function to return bytes from the log block buffer based on offset
47
- # and length, both in bytes.
48
- def data(offset, length)
49
- @buffer[offset...(offset + length)]
50
- end
51
-
52
30
  # Return an Innodb::Cursor object positioned at a specific offset.
53
31
  def cursor(offset)
54
- Innodb::Cursor.new(self, offset)
32
+ Innodb::Cursor.new(@buffer, offset)
55
33
  end
56
34
 
57
35
  # Return the log block header.
58
36
  def header
59
- @header ||= begin
60
- c = cursor(HEADER_START)
37
+ @header ||= cursor(HEADER_OFFSET).name("header") do |c|
61
38
  {
62
- :block => c.get_uint32 & (2**31-1),
63
- :data_length => c.get_uint16,
64
- :first_rec_group => c.get_uint16,
65
- :checkpoint_no => c.get_uint32,
39
+ :flush => c.name("flush") {
40
+ c.peek { (c.get_uint32 & HEADER_FLUSH_BIT_MASK) > 0 }
41
+ },
42
+ :block_number => c.name("block_number") {
43
+ c.get_uint32 & ~HEADER_FLUSH_BIT_MASK
44
+ },
45
+ :data_length => c.name("data_length") { c.get_uint16 },
46
+ :first_rec_group => c.name("first_rec_group") { c.get_uint16 },
47
+ :checkpoint_no => c.name("checkpoint_no") { c.get_uint32 },
66
48
  }
67
49
  end
68
50
  end
69
51
 
70
52
  # Return the log block trailer.
71
53
  def trailer
72
- @trailer ||= begin
73
- c = cursor(TRAILER_START)
54
+ @trailer ||= cursor(TRAILER_OFFSET).name("trailer") do |c|
74
55
  {
75
- :checksum => c.get_uint32,
56
+ :checksum => c.name("checksum") { c.get_uint32 },
76
57
  }
77
58
  end
78
59
  end
@@ -127,41 +108,26 @@ class Innodb::LogBlock
127
108
  51 => :ZIP_PAGE_COMPRESS,
128
109
  }
129
110
 
130
- # Return the log record contents. (This is mostly unimplemented.)
131
- def record_content(record_type, offset)
132
- c = cursor(offset)
133
- case record_type
134
- when :MLOG_1BYTE
135
- c.get_uint8
136
- when :MLOG_2BYTE
137
- c.get_uint16
138
- when :MLOG_4BYTE
139
- c.get_uint32
140
- when :MLOG_8BYTE
141
- c.get_uint64
142
- when :UNDO_INSERT
143
- when :COMP_REC_INSERT
144
- end
145
- end
146
-
147
111
  SINGLE_RECORD_MASK = 0x80
148
112
  RECORD_TYPE_MASK = 0x7f
149
113
 
150
- # Return the log record. (This is mostly unimplemented.)
151
- def record
152
- @record ||= begin
153
- if header[:first_rec_group] != 0
154
- c = cursor(header[:first_rec_group])
155
- type_and_flag = c.get_uint8
156
- type = type_and_flag & RECORD_TYPE_MASK
157
- type = RECORD_TYPES[type] || type
158
- single_record = (type_and_flag & SINGLE_RECORD_MASK) > 0
114
+ # Return a preamble of the first record in this block.
115
+ def first_record_preamble
116
+ return nil unless header[:first_rec_group] > 0
117
+ cursor(header[:first_rec_group]).name("header") do |c|
118
+ type_and_flag = c.name("type") { c.get_uint8 }
119
+ type = type_and_flag & RECORD_TYPE_MASK
120
+ type = RECORD_TYPES[type] || type
121
+ single_record = (type_and_flag & SINGLE_RECORD_MASK) > 0
122
+ case type
123
+ when :MULTI_REC_END, :DUMMY_RECORD
124
+ { :type => type }
125
+ else
159
126
  {
160
127
  :type => type,
161
128
  :single_record => single_record,
162
- :content => record_content(type, c.position),
163
- :space => c.get_ic_uint32,
164
- :page_number => c.get_ic_uint32,
129
+ :space => c.name("space") { c.get_ic_uint32 },
130
+ :page_number => c.name("page_number") { c.get_ic_uint32 },
165
131
  }
166
132
  end
167
133
  end
@@ -179,6 +145,6 @@ class Innodb::LogBlock
179
145
 
180
146
  puts
181
147
  puts "record:"
182
- pp record
148
+ pp first_record_preamble
183
149
  end
184
150
  end
data/lib/innodb/page.rb CHANGED
@@ -61,18 +61,12 @@ class Innodb::Page
61
61
  @size ||= @buffer.size
62
62
  end
63
63
 
64
- # A helper function to return bytes from the page buffer based on offset
65
- # and length, both in bytes.
66
- def data(offset, length)
67
- @buffer[offset...(offset + length)]
68
- end
69
-
70
64
  # If no block is passed, return an Innodb::Cursor object positioned at a
71
65
  # specific offset. If a block is passed, create a cursor at the provided
72
66
  # offset and yield it to the provided block one time, and then return the
73
67
  # return value of the block.
74
68
  def cursor(offset)
75
- new_cursor = Innodb::Cursor.new(self, offset)
69
+ new_cursor = Innodb::Cursor.new(@buffer, offset)
76
70
 
77
71
  if block_given?
78
72
  # Call the block once and return its return value.
@@ -11,8 +11,6 @@ require "innodb/fseg_entry"
11
11
  # (the actual data) which grow ascending by offset, free space, the page
12
12
  # directory which grows descending by offset, and the FIL trailer.
13
13
  class Innodb::Page::Index < Innodb::Page
14
- attr_accessor :record_describer
15
-
16
14
  # The size (in bytes) of the "next" pointer in each record header.
17
15
  RECORD_NEXT_SIZE = 2
18
16
 
@@ -228,6 +226,11 @@ class Innodb::Page::Index < Innodb::Page
228
226
  end
229
227
  end
230
228
 
229
+ # A helper function to return the index id.
230
+ def index_id
231
+ page_header && page_header[:index_id]
232
+ end
233
+
231
234
  # A helper function to return the page level from the "page" header, for
232
235
  # easier access.
233
236
  def level
@@ -429,7 +432,7 @@ class Innodb::Page::Index < Innodb::Page
429
432
  def system_record(offset)
430
433
  cursor(offset).name("record[#{offset}]") do |c|
431
434
  header = c.peek { record_header(c) }
432
- Innodb::Record.new({
435
+ Innodb::Record.new(self, {
433
436
  :offset => offset,
434
437
  :header => header,
435
438
  :next => header[:next],
@@ -448,6 +451,23 @@ class Innodb::Page::Index < Innodb::Page
448
451
  @supremum ||= system_record(pos_supremum)
449
452
  end
450
453
 
454
+ def record_describer=(o)
455
+ @record_describer = o
456
+ end
457
+
458
+ def record_describer
459
+ return @record_describer if @record_describer
460
+
461
+ if space and space.innodb_system and index_id
462
+ @record_describer =
463
+ space.innodb_system.data_dictionary.record_describer_by_index_id(index_id)
464
+ elsif space
465
+ @record_describer = space.record_describer
466
+ end
467
+
468
+ @record_describer
469
+ end
470
+
451
471
  # Return a set of field objects that describe the record.
452
472
  def make_record_description
453
473
  description = record_describer.description
@@ -559,48 +579,304 @@ class Innodb::Page::Index < Innodb::Page
559
579
  end
560
580
  end
561
581
 
562
- Innodb::Record.new(this_record)
582
+ Innodb::Record.new(self, this_record)
583
+ end
584
+ end
585
+
586
+ # Return an array of row offsets for all entries in the page directory.
587
+ def directory
588
+ return @directory if @directory
589
+
590
+ @directory = []
591
+ cursor(pos_directory).backward.name("page_directory") do |c|
592
+ directory_slots.times do |n|
593
+ @directory.push c.name("slot[#{n}]") { c.get_uint16 }
594
+ end
595
+ end
596
+
597
+ @directory
598
+ end
599
+
600
+ # Return the slot number of the provided offset in the page directory, or nil
601
+ # if the offset is not present in the page directory.
602
+ def offset_is_directory_slot?(offset)
603
+ directory.index(offset)
604
+ end
605
+
606
+ # Return the slot number of the provided record in the page directory, or nil
607
+ # if the record is not present in the page directory.
608
+ def record_is_directory_slot?(this_record)
609
+ offset_is_directory_slot?(this_record.offset)
610
+ end
611
+
612
+ # Return the slot number for the page directory entry which "owns" the
613
+ # provided record. This will be either the record itself, or the nearest
614
+ # record with an entry in the directory and a value greater than the record.
615
+ def directory_slot_for_record(this_record)
616
+ if slot = record_is_directory_slot?(this_record)
617
+ return slot
563
618
  end
619
+
620
+ unless search_cursor = record_cursor(this_record.next)
621
+ raise "Couldn't position cursor"
622
+ end
623
+
624
+ while rec = search_cursor.record
625
+ if slot = record_is_directory_slot?(rec)
626
+ return slot
627
+ end
628
+ end
629
+
630
+ return record_is_directory_slot?(supremum)
564
631
  end
565
632
 
566
633
  # A class for cursoring through records starting from an arbitrary point.
567
634
  class RecordCursor
568
- def initialize(page, offset)
569
- @page = page
570
- @offset = offset
635
+ def initialize(page, offset, direction)
636
+ Innodb::Stats.increment :page_record_cursor_create
637
+
638
+ @initial = true
639
+ @page = page
640
+ @direction = direction
641
+ case offset
642
+ when :min
643
+ @record = @page.min_record
644
+ when :max
645
+ @record = @page.max_record
646
+ else
647
+ # Offset is a byte offset of a record (hopefully).
648
+ @record = @page.record(offset)
649
+ end
650
+ end
651
+
652
+ # Return the current record, mostly as a helper.
653
+ def current_record
654
+ @record
571
655
  end
572
656
 
573
657
  # Return the next record, and advance the cursor. Return nil when the
574
- # end of records is reached.
575
- def record
576
- return nil unless @offset
658
+ # end of records (supremum) is reached.
659
+ def next_record
660
+ Innodb::Stats.increment :page_record_cursor_next_record
577
661
 
578
- record = @page.record(@offset)
662
+ @record = @page.record(@record.next)
579
663
 
580
- if record == @page.supremum
664
+ if @record == @page.supremum
581
665
  # We've reached the end of the linked list at supremum.
582
- @offset = nil
583
- elsif record.next == @offset
584
- # The record links to itself; go ahead and return it (once), but set
585
- # the next offset to nil to end the loop.
586
- @offset = nil
587
- record
666
+ nil
588
667
  else
589
- @offset = record.next
590
- record
668
+ @record
669
+ end
670
+ end
671
+
672
+ # Return the previous record, and advance the cursor. Return nil when the
673
+ # end of records (infimum) is reached.
674
+ def prev_record
675
+ Innodb::Stats.increment :page_record_cursor_prev_record
676
+
677
+ unless slot = @page.directory_slot_for_record(@record)
678
+ raise "Couldn't find slot for record"
679
+ end
680
+
681
+ unless search_cursor = @page.record_cursor(@page.directory[slot-1])
682
+ raise "Couldn't position search cursor"
683
+ end
684
+
685
+ while rec = search_cursor.record and rec.offset != @record.offset
686
+ if rec.next == @record.offset
687
+ if rec == @page.infimum
688
+ return nil
689
+ end
690
+ return @record = rec
691
+ end
692
+ end
693
+ end
694
+
695
+ # Return the next record in the order defined when the cursor was created.
696
+ def record
697
+ if @initial
698
+ @initial = false
699
+ return current_record
700
+ end
701
+
702
+ case @direction
703
+ when :forward
704
+ next_record
705
+ when :backward
706
+ prev_record
707
+ end
708
+ end
709
+
710
+ # Iterate through all records in the cursor.
711
+ def each_record
712
+ unless block_given?
713
+ return enum_for(:each_record)
714
+ end
715
+
716
+ while rec = record
717
+ yield rec
591
718
  end
592
719
  end
593
720
  end
594
721
 
595
722
  # Return a RecordCursor starting at offset.
596
- def record_cursor(offset)
597
- RecordCursor.new(self, offset)
723
+ def record_cursor(offset=:min, direction=:forward)
724
+ RecordCursor.new(self, offset, direction)
598
725
  end
599
726
 
600
- # Return the first record on this page.
601
- def first_record
602
- first = record(infimum.next)
603
- first if first != supremum
727
+ # Return the minimum record on this page.
728
+ def min_record
729
+ min = record(infimum.next)
730
+ min if min != supremum
731
+ end
732
+
733
+ # Return the maximum record on this page.
734
+ def max_record
735
+ # Since the records are only singly-linked in the forward direction, in
736
+ # order to do find the last record, we must create a cursor and walk
737
+ # backwards one step.
738
+ unless max_cursor = record_cursor(supremum.offset, :backward)
739
+ raise "Couldn't position cursor"
740
+ end
741
+ # Note the deliberate use of prev_record rather than record; we want
742
+ # to skip over supremum itself.
743
+ max = max_cursor.prev_record
744
+ max if max != infimum
745
+ end
746
+
747
+ # Search for a record within a single page, and return either a perfect
748
+ # match for the key, or the last record closest to they key but not greater
749
+ # than the key. (If an exact match is desired, compare_key must be used to
750
+ # check if the returned record matches. This makes the function useful for
751
+ # search in both leaf and non-leaf pages.)
752
+ def linear_search_from_cursor(search_cursor, key)
753
+ Innodb::Stats.increment :linear_search_from_cursor
754
+
755
+ this_rec = search_cursor.record
756
+
757
+ if Innodb.debug?
758
+ puts "linear_search_from_cursor: page=%i, level=%i, start=(%s)" % [
759
+ offset,
760
+ level,
761
+ this_rec && this_rec.key_string,
762
+ ]
763
+ end
764
+
765
+ # Iterate through all records until finding either a matching record or
766
+ # one whose key is greater than the desired key.
767
+ while this_rec && next_rec = search_cursor.record
768
+ Innodb::Stats.increment :linear_search_from_cursor_record_scans
769
+
770
+ if Innodb.debug?
771
+ puts "linear_search_from_cursor: page=%i, level=%i, current=(%s)" % [
772
+ offset,
773
+ level,
774
+ this_rec && this_rec.key_string,
775
+ ]
776
+ end
777
+
778
+ # If we reach supremum, return the last non-system record we got.
779
+ return this_rec if next_rec.header[:type] == :supremum
780
+
781
+ if this_rec.compare_key(key) < 0
782
+ return this_rec
783
+ end
784
+
785
+ if (this_rec.compare_key(key) >= 0) &&
786
+ (next_rec.compare_key(key) < 0)
787
+ # The desired key is either an exact match for this_rec or is greater
788
+ # than it but less than next_rec. If this is a non-leaf page, that
789
+ # will mean that the record will fall on the leaf page this node
790
+ # pointer record points to, if it exists at all.
791
+ return this_rec
792
+ end
793
+
794
+ this_rec = next_rec
795
+ end
796
+
797
+ this_rec
798
+ end
799
+
800
+ # Search or a record within a single page using the page directory to limit
801
+ # the number of record comparisons required. Once the last page directory
802
+ # entry closest to but not greater than the key is found, fall back to
803
+ # linear search using linear_search_from_cursor to find the closest record
804
+ # whose key is not greater than the desired key. (If an exact match is
805
+ # desired, the returned record must be checked in the same way as the above
806
+ # linear_search_from_cursor function.)
807
+ def binary_search_by_directory(dir, key)
808
+ Innodb::Stats.increment :binary_search_by_directory
809
+
810
+ return nil if dir.empty?
811
+
812
+ # Split the directory at the mid-point (using integer math, so the division
813
+ # is rounding down). Retrieve the record that sits at the mid-point.
814
+ mid = ((dir.size-1) / 2)
815
+ rec = record(dir[mid])
816
+
817
+ if Innodb.debug?
818
+ puts "binary_search_by_directory: page=%i, level=%i, dir.size=%i, dir[%i]=(%s)" % [
819
+ offset,
820
+ level,
821
+ dir.size,
822
+ mid,
823
+ rec.key_string,
824
+ ]
825
+ end
826
+
827
+ # The mid-point record was the infimum record, which is not comparable with
828
+ # compare_key, so we need to just linear scan from here. If the mid-point
829
+ # is the beginning of the page there can't be many records left to check
830
+ # anyway.
831
+ if rec.header[:type] == :infimum
832
+ return linear_search_from_cursor(record_cursor(rec.next), key)
833
+ end
834
+
835
+ # Compare the desired key to the mid-point record's key.
836
+ case rec.compare_key(key)
837
+ when 0
838
+ # An exact match for the key was found. Return the record.
839
+ Innodb::Stats.increment :binary_search_by_directory_exact_match
840
+ rec
841
+ when +1
842
+ # The mid-point record's key is less than the desired key.
843
+ if dir.size > 2
844
+ # There are more entries remaining from the directory, recurse again
845
+ # using binary search on the right half of the directory, which
846
+ # represents values greater than or equal to the mid-point record's
847
+ # key.
848
+ Innodb::Stats.increment :binary_search_by_directory_recurse_right
849
+ binary_search_by_directory(dir[mid...dir.size], key)
850
+ else
851
+ next_rec = record(dir[mid+1])
852
+ next_key = next_rec && next_rec.compare_key(key)
853
+ if dir.size == 1 || next_key == -1 || next_key == 0
854
+ # This is the last entry remaining from the directory, or our key is
855
+ # greater than rec and less than rec+1's key. Use linear search to
856
+ # find the record starting at rec.
857
+ Innodb::Stats.increment :binary_search_by_directory_linear_search
858
+ linear_search_from_cursor(record_cursor(rec.offset), key)
859
+ elsif next_key == +1
860
+ Innodb::Stats.increment :binary_search_by_directory_linear_search
861
+ linear_search_from_cursor(record_cursor(next_rec.offset), key)
862
+ else
863
+ nil
864
+ end
865
+ end
866
+ when -1
867
+ # The mid-point record's key is greater than the desired key.
868
+ if dir.size == 1
869
+ # If this is the last entry remaining from the directory, we didn't
870
+ # find anything workable.
871
+ Innodb::Stats.increment :binary_search_by_directory_empty_result
872
+ nil
873
+ else
874
+ # Recurse on the left half of the directory, which represents values
875
+ # less than the mid-point record's key.
876
+ Innodb::Stats.increment :binary_search_by_directory_recurse_left
877
+ binary_search_by_directory(dir[0...mid], key)
878
+ end
879
+ end
604
880
  end
605
881
 
606
882
  # Iterate through all records.
@@ -618,6 +894,7 @@ class Innodb::Page::Index < Innodb::Page
618
894
  nil
619
895
  end
620
896
 
897
+ # Iterate through all records in the garbage list.
621
898
  def each_garbage_record
622
899
  unless block_given?
623
900
  return enum_for(:each_garbage_record)
@@ -653,19 +930,6 @@ class Innodb::Page::Index < Innodb::Page
653
930
  nil
654
931
  end
655
932
 
656
- # Return an array of row offsets for all entries in the page directory.
657
- def directory
658
- return @directory if @directory
659
-
660
- @directory = []
661
- cursor(pos_directory).backward.name("page_directory") do |c|
662
- directory_slots.times do |n|
663
- @directory.push c.name("slot[#{n}]") { c.get_uint16 }
664
- end
665
- end
666
-
667
- @directory
668
- end
669
933
 
670
934
  # Dump the contents of a page for debugging purposes.
671
935
  def dump