Class: Innodb::Page::Index
- Inherits:
-
Innodb::Page
- Object
- Innodb::Page
- Innodb::Page::Index
- Extended by:
- Forwardable
- Defined in:
- lib/innodb/page/index.rb
Direct Known Subclasses
Defined Under Namespace
Classes: FieldDescriptor, FsegHeader, PageHeader, RecordCursor, RecordHeader, SystemRecord, UserRecord
Constant Summary collapse
- RECORD_NEXT_SIZE =
The size (in bytes) of the “next” pointer in each record header.
2
- RECORD_REDUNDANT_BITS_SIZE =
The size (in bytes) of the bit-packed fields in each record header for “redundant” record format.
4
- RECORD_REDUNDANT_OFF1_OFFSET_MASK =
Masks for 1-byte record end-offsets within “redundant” records.
0x7f
- RECORD_REDUNDANT_OFF1_NULL_MASK =
0x80
- RECORD_REDUNDANT_OFF2_OFFSET_MASK =
Masks for 2-byte record end-offsets within “redundant” records.
0x3fff
- RECORD_REDUNDANT_OFF2_NULL_MASK =
0x8000
- RECORD_REDUNDANT_OFF2_EXTERN_MASK =
0x4000
- RECORD_COMPACT_BITS_SIZE =
The size (in bytes) of the bit-packed fields in each record header for “compact” record format.
3
- RECORD_MAX_N_SYSTEM_FIELDS =
Maximum number of fields.
3
- RECORD_MAX_N_FIELDS =
1024 - 1
- RECORD_MAX_N_USER_FIELDS =
RECORD_MAX_N_FIELDS - (RECORD_MAX_N_SYSTEM_FIELDS * 2)
- PAGE_DIRECTION =
Page direction values possible in the page_header’s :direction field.
{ 1 => :left, # Inserts have been in descending order. 2 => :right, # Inserts have been in ascending order. 3 => :same_rec, # Unused by InnoDB. 4 => :same_page, # Unused by InnoDB. 5 => :no_direction, # Inserts have been in random order. }.freeze
- RECORD_TYPES =
Record types used in the :type field of the record header.
{ 0 => :conventional, # A normal user record in a leaf page. 1 => :node_pointer, # A node pointer in a non-leaf page. 2 => :infimum, # The system "infimum" record. 3 => :supremum, # The system "supremum" record. }.freeze
- PAGE_DIR_SLOT_SIZE =
The size (in bytes) of the record pointers in each page directory slot.
2
- PAGE_DIR_SLOT_MIN_N_OWNED =
The minimum number of records “owned” by each record with an entry in the page directory.
4
- PAGE_DIR_SLOT_MAX_N_OWNED =
The maximum number of records “owned” by each record with an entry in the page directory.
8
Constants inherited from Innodb::Page
PAGE_TYPE, PAGE_TYPE_BY_VALUE, UNDEFINED_PAGE_NUMBER
Instance Attribute Summary collapse
Attributes inherited from Innodb::Page
Instance Method Summary collapse
-
#binary_search_by_directory(dir, key) ⇒ Object
Search or a record within a single page using the page directory to limit the number of record comparisons required.
-
#directory ⇒ Object
Return an array of row offsets for all entries in the page directory.
-
#directory_slot_for_record(this_record) ⇒ Object
Return the slot number for the page directory entry which “owns” the provided record.
-
#directory_slots ⇒ Object
The number of directory slots in use.
-
#directory_space ⇒ Object
The amount of space consumed by the page directory.
-
#dump ⇒ Object
Dump the contents of a page for debugging purposes.
-
#each_child_page ⇒ Object
Iterate through all child pages of a node (non-leaf) page, which are stored as records with the child page number as the last field in the record.
- #each_directory_offset ⇒ Object
- #each_directory_record ⇒ Object
-
#each_garbage_record ⇒ Object
Iterate through all records in the garbage list.
-
#each_record ⇒ Object
Iterate through all records.
- #each_region {|Region.new( offset: pos_index_header, length: size_index_header, name: :index_header, info: "Index Header" )| ... } ⇒ Object
-
#free_space ⇒ Object
Return the amount of free space in the page.
-
#fseg_header ⇒ Object
Return the “fseg” header.
-
#header_space ⇒ Object
The amount of space consumed by the page header.
-
#ibuf_index? ⇒ Boolean
A helper to determine if an this page is part of an insert buffer index.
-
#infimum ⇒ Object
Return the infimum record on a page.
-
#leaf? ⇒ Boolean
A helper function to identify leaf index pages.
-
#linear_search_from_cursor(search_cursor, key) ⇒ Object
Search for a record within a single page, and return either a perfect match for the key, or the last record closest to they key but not greater than the key.
- #make_record_describer ⇒ Object
-
#make_record_description ⇒ Object
Return a set of field objects that describe the record.
-
#max_record ⇒ Object
Return the maximum record on this page.
-
#min_record ⇒ Object
Return the minimum record on this page.
-
#offset_directory_slot(offset) ⇒ Object
Return the slot number of the provided offset in the page directory, or nil if the offset is not present in the page directory.
-
#page_header ⇒ Object
Return the “index” header.
-
#pos_directory ⇒ Object
The position of the page directory, which starts at the “fil” trailer and grows backwards from there.
-
#pos_fseg_header ⇒ Object
Return the byte offset of the start of the “fseg” header, which immediately follows the “index” header.
-
#pos_index_header ⇒ Object
Return the byte offset of the start of the “index” page header, which immediately follows the “fil” header.
-
#pos_infimum ⇒ Object
Return the byte offset of the start of the “origin” of the infimum record, which is always the first record in the singly-linked record chain on any page, and represents a record with a “lower value than any possible user record”.
-
#pos_records ⇒ Object
Return the byte offset of the start of records within the page (the position immediately after the page header).
-
#pos_supremum ⇒ Object
Return the byte offset of the start of the “origin” of the supremum record, which is always the last record in the singly-linked record chain on any page, and represents a record with a “higher value than any possible user record”.
-
#pos_user_records ⇒ Object
Return the byte offset of the start of the user records in a page, which immediately follows the supremum record.
-
#record(offset) ⇒ Object
Parse and return a record at a given offset.
-
#record_cursor(offset = :min, direction = :forward) ⇒ Object
Return a RecordCursor starting at offset.
-
#record_directory_slot(this_record) ⇒ Object
Return the slot number of the provided record in the page directory, or nil if the record is not present in the page directory.
-
#record_fields ⇒ Object
Returns the (ordered) set of fields that describe records in this page.
-
#record_format ⇒ Object
Return (and cache) the record format provided by an external class.
-
#record_header(cursor) ⇒ Object
Return the header from a record.
-
#record_header_compact_additional(header, cursor) ⇒ Object
Read additional header information from a compact format record header.
-
#record_header_compact_null_bitmap(cursor) ⇒ Object
Return an array indicating which fields are null.
-
#record_header_compact_variable_lengths_and_externs(cursor, nulls) ⇒ Object
Return an array containing an array of the length of each variable-length field and an array indicating which fields are stored externally.
-
#record_header_redundant_additional(header, cursor) ⇒ Object
Read additional header information from a redundant format record header.
-
#record_header_redundant_field_end_offsets(header, cursor) ⇒ Object
Read field end offsets from the provided cursor for each field as counted by n_fields.
- #record_if_exists(offset) ⇒ Object
-
#record_space ⇒ Object
Return the amount of space occupied by records in the page.
-
#root? ⇒ Boolean
A helper function to identify root index pages; they must be the only pages at their level.
-
#size_fseg_header ⇒ Object
The size of the “fseg” header.
-
#size_index_header ⇒ Object
The size of the “index” header.
-
#size_mum_record ⇒ Object
The size of the data from the supremum or infimum records.
-
#size_mum_record_header_additional ⇒ Object
The size of the additional data structures in the header of the system records, which is just 1 byte in redundant format to store the offset of the end of the field.
-
#size_record_header ⇒ Object
Return the size of the header for each record.
-
#space_per_record ⇒ Object
A helper to calculate the amount of space consumed per record.
-
#supremum ⇒ Object
Return the supremum record on a page.
-
#system_record(offset) ⇒ Object
Parse and return simple fixed-format system records, such as InnoDB’s internal infimum and supremum records.
-
#trailer_space ⇒ Object
The amount of space consumed by the trailers in the page.
-
#used_space ⇒ Object
Return the amount of used space in the page.
Methods inherited from Innodb::Page
#checksum_crc32, #checksum_crc32?, #checksum_innodb, #checksum_innodb?, #checksum_invalid?, #checksum_type, #checksum_valid?, #corrupt?, #cursor, #default_page_size?, #each_page_body_byte_as_uint8, #each_page_header_byte_as_uint8, #extent_descriptor?, #fil_header, #fil_trailer, handle, #in_doublewrite_buffer?, #initialize, #inspect, #inspect_header_fields, maybe_undefined, #misplaced?, #misplaced_offset?, #misplaced_space?, #name, page_type_by_value, parse, #pos_fil_header, #pos_fil_trailer, #pos_page_body, #pos_partial_page_header, register_specialization, #size, #size_fil_header, #size_fil_trailer, #size_page_body, #size_partial_page_header, specialization_for, specialization_for?, #torn?, undefined?
Constructor Details
This class inherits a constructor from Innodb::Page
Instance Attribute Details
#record_describer ⇒ Object
556 557 558 |
# File 'lib/innodb/page/index.rb', line 556 def record_describer @record_describer ||= make_record_describer end |
Instance Method Details
#binary_search_by_directory(dir, key) ⇒ Object
Search or a record within a single page using the page directory to limit the number of record comparisons required. Once the last page directory entry closest to but not greater than the key is found, fall back to linear search using linear_search_from_cursor to find the closest record whose key is not greater than the desired key. (If an exact match is desired, the returned record must be checked in the same way as the above linear_search_from_cursor function.)
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 |
# File 'lib/innodb/page/index.rb', line 884 def binary_search_by_directory(dir, key) Innodb::Stats.increment :binary_search_by_directory return if dir.empty? # Split the directory at the mid-point (using integer math, so the division # is rounding down). Retrieve the record that sits at the mid-point. mid = ((dir.size - 1) / 2) rec = record(dir[mid]) return unless rec if Innodb.debug? puts "binary_search_by_directory: page=%i, level=%i, dir.size=%i, dir[%i]=(%s)" % [ offset, level, dir.size, mid, rec.key_string, ] end # The mid-point record was the infimum record, which is not comparable with # compare_key, so we need to just linear scan from here. If the mid-point # is the beginning of the page there can't be many records left to check # anyway. return linear_search_from_cursor(record_cursor(rec.next), key) if rec.header.type == :infimum # Compare the desired key to the mid-point record's key. case rec.compare_key(key) when 0 # An exact match for the key was found. Return the record. Innodb::Stats.increment :binary_search_by_directory_exact_match rec when +1 # The mid-point record's key is less than the desired key. if dir.size > 2 # There are more entries remaining from the directory, recurse again # using binary search on the right half of the directory, which # represents values greater than or equal to the mid-point record's # key. Innodb::Stats.increment :binary_search_by_directory_recurse_right binary_search_by_directory(dir[mid...dir.size], key) else next_rec = record(dir[mid + 1]) next_key = next_rec&.compare_key(key) if dir.size == 1 || next_key == -1 || next_key.zero? # This is the last entry remaining from the directory, or our key is # greater than rec and less than rec+1's key. Use linear search to # find the record starting at rec. Innodb::Stats.increment :binary_search_by_directory_linear_search linear_search_from_cursor(record_cursor(rec.offset), key) elsif next_key == +1 Innodb::Stats.increment :binary_search_by_directory_linear_search linear_search_from_cursor(record_cursor(next_rec.offset), key) end end when -1 # The mid-point record's key is greater than the desired key. if dir.size == 1 # If this is the last entry remaining from the directory, we didn't # find anything workable. Innodb::Stats.increment :binary_search_by_directory_empty_result nil else # Recurse on the left half of the directory, which represents values # less than the mid-point record's key. Innodb::Stats.increment :binary_search_by_directory_recurse_left binary_search_by_directory(dir[0...mid], key) end end end |
#directory ⇒ Object
Return an array of row offsets for all entries in the page directory.
661 662 663 664 665 |
# File 'lib/innodb/page/index.rb', line 661 def directory @directory ||= cursor(pos_directory).backward.name("page_directory") do |c| directory_slots.times.map { |n| c.name("slot[#{n}]") { c.read_uint16 } } end end |
#directory_slot_for_record(this_record) ⇒ Object
Return the slot number for the page directory entry which “owns” the provided record. This will be either the record itself, or the nearest record with an entry in the directory and a value greater than the record.
682 683 684 685 686 687 688 689 690 691 692 693 694 695 |
# File 'lib/innodb/page/index.rb', line 682 def directory_slot_for_record(this_record) slot = record_directory_slot(this_record) return slot if slot search_cursor = record_cursor(this_record.next) raise "Could not position cursor" unless search_cursor while (rec = search_cursor.record) slot = record_directory_slot(rec) return slot if slot end record_directory_slot(supremum) end |
#directory_slots ⇒ Object
The number of directory slots in use.
264 265 266 |
# File 'lib/innodb/page/index.rb', line 264 def directory_slots page_header[:n_dir_slots] end |
#directory_space ⇒ Object
The amount of space consumed by the page directory.
269 270 271 |
# File 'lib/innodb/page/index.rb', line 269 def directory_space directory_slots * PAGE_DIR_SLOT_SIZE end |
#dump ⇒ Object
Dump the contents of a page for debugging purposes.
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 |
# File 'lib/innodb/page/index.rb', line 1069 def dump super puts "page header:" pp page_header puts puts "fseg header:" pp fseg_header puts puts "sizes:" puts " %-15s%5i" % ["header", header_space] puts " %-15s%5i" % ["trailer", trailer_space] puts " %-15s%5i" % ["directory", directory_space] puts " %-15s%5i" % ["free", free_space] puts " %-15s%5i" % ["used", used_space] puts " %-15s%5i" % ["record", record_space] puts " %-15s%5.2f" % ["per record", space_per_record] puts puts "page directory:" pp directory puts puts "system records:" pp infimum.record pp supremum.record puts if ibuf_index? puts "(records not dumped due to this being an insert buffer index)" elsif !record_describer puts "(records not dumped due to missing record describer or data dictionary)" else puts "garbage records:" each_garbage_record do |rec| pp rec.record puts end puts puts "records:" each_record do |rec| pp rec.record puts end end puts end |
#each_child_page ⇒ Object
Iterate through all child pages of a node (non-leaf) page, which are stored as records with the child page number as the last field in the record.
986 987 988 989 990 991 992 993 994 995 996 |
# File 'lib/innodb/page/index.rb', line 986 def each_child_page return if leaf? return enum_for(:each_child_page) unless block_given? each_record do |rec| yield rec.child_page_number, rec.key end nil end |
#each_directory_offset ⇒ Object
697 698 699 700 701 702 703 |
# File 'lib/innodb/page/index.rb', line 697 def each_directory_offset return enum_for(:each_directory_offset) unless block_given? directory.each do |offset| yield offset unless [pos_infimum, pos_supremum].include?(offset) end end |
#each_directory_record ⇒ Object
705 706 707 708 709 710 711 |
# File 'lib/innodb/page/index.rb', line 705 def each_directory_record return enum_for(:each_directory_record) unless block_given? each_directory_offset do |offset| yield record(offset) end end |
#each_garbage_record ⇒ Object
Iterate through all records in the garbage list.
970 971 972 973 974 975 976 977 978 979 980 981 |
# File 'lib/innodb/page/index.rb', line 970 def each_garbage_record return enum_for(:each_garbage_record) unless block_given? return if garbage_offset.zero? c = record_cursor(garbage_offset) while (rec = c.record) yield rec end nil end |
#each_record ⇒ Object
Iterate through all records.
957 958 959 960 961 962 963 964 965 966 967 |
# File 'lib/innodb/page/index.rb', line 957 def each_record return enum_for(:each_record) unless block_given? c = record_cursor(:min) while (rec = c.record) yield rec end nil end |
#each_region {|Region.new( offset: pos_index_header, length: size_index_header, name: :index_header, info: "Index Header" )| ... } ⇒ Object
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 |
# File 'lib/innodb/page/index.rb', line 998 def each_region(&block) return enum_for(:each_region) unless block_given? super yield Region.new( offset: pos_index_header, length: size_index_header, name: :index_header, info: "Index Header" ) yield Region.new( offset: pos_fseg_header, length: size_fseg_header, name: :fseg_header, info: "File Segment Header" ) yield Region.new( offset: pos_infimum - 5, length: size_mum_record + 5, name: :infimum, info: "Infimum" ) yield Region.new( offset: pos_supremum - 5, length: size_mum_record + 5, name: :supremum, info: "Supremum" ) directory_slots.times do |n| yield Region.new( offset: pos_directory - (n * 2), length: 2, name: :directory, info: "Page Directory" ) end each_garbage_record do |record| yield Region.new( offset: record.offset - record.header.length, length: record.length + record.header.length, name: :garbage, info: "Garbage" ) end each_record do |record| yield Region.new( offset: record.offset - record.header.length, length: record.header.length, name: :record_header, info: "Record Header" ) yield Region.new( offset: record.offset, length: record.length || 1, name: :record_data, info: "Record Data" ) end nil end |
#free_space ⇒ Object
Return the amount of free space in the page.
279 280 281 282 |
# File 'lib/innodb/page/index.rb', line 279 def free_space page_header[:garbage_size] + (size - size_fil_trailer - directory_space - page_header[:heap_top]) end |
#fseg_header ⇒ Object
Return the “fseg” header.
346 347 348 349 350 351 352 353 |
# File 'lib/innodb/page/index.rb', line 346 def fseg_header @fseg_header ||= cursor(pos_fseg_header).name("fseg") do |c| FsegHeader.new( leaf: c.name("fseg[leaf]") { Innodb::FsegEntry.get_inode(@space, c) }, internal: c.name("fseg[internal]") { Innodb::FsegEntry.get_inode(@space, c) } ) end end |
#header_space ⇒ Object
The amount of space consumed by the page header.
257 258 259 260 261 |
# File 'lib/innodb/page/index.rb', line 257 def header_space # The end of the supremum system record is the beginning of the space # available for user records. pos_user_records end |
#ibuf_index? ⇒ Boolean
A helper to determine if an this page is part of an insert buffer index.
341 342 343 |
# File 'lib/innodb/page/index.rb', line 341 def ibuf_index? index_id == Innodb::IbufIndex::INDEX_ID end |
#infimum ⇒ Object
Return the infimum record on a page.
539 540 541 |
# File 'lib/innodb/page/index.rb', line 539 def infimum @infimum ||= system_record(pos_infimum) end |
#leaf? ⇒ Boolean
A helper function to identify leaf index pages.
336 337 338 |
# File 'lib/innodb/page/index.rb', line 336 def leaf? level.zero? end |
#linear_search_from_cursor(search_cursor, key) ⇒ Object
Search for a record within a single page, and return either a perfect match for the key, or the last record closest to they key but not greater than the key. (If an exact match is desired, compare_key must be used to check if the returned record matches. This makes the function useful for search in both leaf and non-leaf pages.)
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 |
# File 'lib/innodb/page/index.rb', line 834 def linear_search_from_cursor(search_cursor, key) Innodb::Stats.increment :linear_search_from_cursor this_rec = search_cursor.record if Innodb.debug? puts "linear_search_from_cursor: page=%i, level=%i, start=(%s)" % [ offset, level, this_rec && this_rec.key_string, ] end # Iterate through all records until finding either a matching record or # one whose key is greater than the desired key. while this_rec && (next_rec = search_cursor.record) Innodb::Stats.increment :linear_search_from_cursor_record_scans if Innodb.debug? puts "linear_search_from_cursor: page=%i, level=%i, current=(%s)" % [ offset, level, this_rec.key_string, ] end # If we reach supremum, return the last non-system record we got. return this_rec if next_rec.header.type == :supremum return this_rec if this_rec.compare_key(key).negative? # The desired key is either an exact match for this_rec or is greater # than it but less than next_rec. If this is a non-leaf page, that # will mean that the record will fall on the leaf page this node # pointer record points to, if it exists at all. return this_rec if !this_rec.compare_key(key).negative? && next_rec.compare_key(key).negative? this_rec = next_rec end this_rec end |
#make_record_describer ⇒ Object
548 549 550 551 552 553 554 |
# File 'lib/innodb/page/index.rb', line 548 def make_record_describer if space&.innodb_system&.data_dictionary&.found? && index_id && !ibuf_index? @record_describer = space.innodb_system.data_dictionary.record_describer_by_index_id(index_id) elsif space @record_describer = space.record_describer end end |
#make_record_description ⇒ Object
Return a set of field objects that describe the record.
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 |
# File 'lib/innodb/page/index.rb', line 561 def make_record_description position = (0..RECORD_MAX_N_FIELDS).each description = record_describer.description fields = { type: description[:type], key: [], sys: [], row: [] } description[:key].each do |field| fields[:key] << Innodb::Field.new(position.next, field[:name], *field[:type]) end # If this is a leaf page of the clustered index, read InnoDB's internal # fields, a transaction ID and roll pointer. if leaf? && fields[:type] == :clustered [["DB_TRX_ID", :TRX_ID], ["DB_ROLL_PTR", :ROLL_PTR]].each do |name, type| fields[:sys] << Innodb::Field.new(position.next, name, type, :NOT_NULL) end end # If this is a leaf page of the clustered index, or any page of a # secondary index, read the non-key fields. if (leaf? && fields[:type] == :clustered) || (fields[:type] == :secondary) description[:row].each do |field| fields[:row] << Innodb::Field.new(position.next, field[:name], *field[:type]) end end fields end |
#max_record ⇒ Object
Return the maximum record on this page.
816 817 818 819 820 821 822 823 824 825 826 827 |
# File 'lib/innodb/page/index.rb', line 816 def max_record # Since the records are only singly-linked in the forward direction, in # order to do find the last record, we must create a cursor and walk # backwards one step. max_cursor = record_cursor(supremum.offset, :backward) raise "Could not position cursor" unless max_cursor # Note the deliberate use of prev_record rather than record; we want # to skip over supremum itself. max = max_cursor.prev_record max if max != infimum end |
#min_record ⇒ Object
Return the minimum record on this page.
810 811 812 813 |
# File 'lib/innodb/page/index.rb', line 810 def min_record min = record(infimum.next) min if min != supremum end |
#offset_directory_slot(offset) ⇒ Object
Return the slot number of the provided offset in the page directory, or nil if the offset is not present in the page directory.
669 670 671 |
# File 'lib/innodb/page/index.rb', line 669 def offset_directory_slot(offset) directory.index(offset) end |
#page_header ⇒ Object
Return the “index” header.
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/innodb/page/index.rb', line 300 def page_header @page_header ||= cursor(pos_index_header).name("index") do |c| index = PageHeader.new( n_dir_slots: c.name("n_dir_slots") { c.read_uint16 }, heap_top: c.name("heap_top") { c.read_uint16 }, n_heap_format: c.name("n_heap_format") { c.read_uint16 }, garbage_offset: c.name("garbage_offset") { c.read_uint16 }, garbage_size: c.name("garbage_size") { c.read_uint16 }, last_insert_offset: c.name("last_insert_offset") { c.read_uint16 }, direction: c.name("direction") { PAGE_DIRECTION[c.read_uint16] }, n_direction: c.name("n_direction") { c.read_uint16 }, n_recs: c.name("n_recs") { c.read_uint16 }, max_trx_id: c.name("max_trx_id") { c.read_uint64 }, level: c.name("level") { c.read_uint16 }, index_id: c.name("index_id") { c.read_uint64 } ) index.n_heap = index.n_heap_format & ((2**15) - 1) index.format = (index.n_heap_format & (1 << 15)).zero? ? :redundant : :compact index end end |
#pos_directory ⇒ Object
The position of the page directory, which starts at the “fil” trailer and grows backwards from there.
252 253 254 |
# File 'lib/innodb/page/index.rb', line 252 def pos_directory pos_fil_trailer end |
#pos_fseg_header ⇒ Object
Return the byte offset of the start of the “fseg” header, which immediately follows the “index” header.
178 179 180 |
# File 'lib/innodb/page/index.rb', line 178 def pos_fseg_header pos_index_header + size_index_header end |
#pos_index_header ⇒ Object
Return the byte offset of the start of the “index” page header, which immediately follows the “fil” header.
167 168 169 |
# File 'lib/innodb/page/index.rb', line 167 def pos_index_header pos_page_body end |
#pos_infimum ⇒ Object
Return the byte offset of the start of the “origin” of the infimum record, which is always the first record in the singly-linked record chain on any page, and represents a record with a “lower value than any possible user record”. The infimum record immediately follows the page header.
219 220 221 222 223 |
# File 'lib/innodb/page/index.rb', line 219 def pos_infimum pos_records + size_record_header + size_mum_record_header_additional end |
#pos_records ⇒ Object
Return the byte offset of the start of records within the page (the position immediately after the page header).
238 239 240 241 242 |
# File 'lib/innodb/page/index.rb', line 238 def pos_records size_fil_header + size_index_header + size_fseg_header end |
#pos_supremum ⇒ Object
Return the byte offset of the start of the “origin” of the supremum record, which is always the last record in the singly-linked record chain on any page, and represents a record with a “higher value than any possible user record”. The supremum record immediately follows the infimum record.
229 230 231 232 233 234 |
# File 'lib/innodb/page/index.rb', line 229 def pos_supremum pos_infimum + size_record_header + size_mum_record_header_additional + size_mum_record end |
#pos_user_records ⇒ Object
Return the byte offset of the start of the user records in a page, which immediately follows the supremum record.
246 247 248 |
# File 'lib/innodb/page/index.rb', line 246 def pos_user_records pos_supremum + size_mum_record end |
#record(offset) ⇒ Object
Parse and return a record at a given offset.
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 |
# File 'lib/innodb/page/index.rb', line 600 def record(offset) return nil unless offset return infimum if offset == pos_infimum return supremum if offset == pos_supremum cursor(offset).forward.name("record[#{offset}]") do |c| # There is a header preceding the row itself, so back up and read it. header = c.peek { record_header(c) } this_record = UserRecord.new( format: page_header.format, offset: offset, header: header, next: header.next.zero? ? nil : header.next ) if record_format this_record.type = record_format[:type] # Used to indicate whether a field is part of key/row/sys. # TODO: There's probably a better way to do this. fmap = %i[key row sys].each_with_object({}) do |k, h| this_record[k] = [] record_format[k].each { |f| h[f.position] = k } end # Read the fields present in this record. record_fields.each do |f| p = fmap[f.position] c.name("#{p}[#{f.name}]") do this_record[p] << FieldDescriptor.new( name: f.name, type: f.data_type.name, value: f.value(c, this_record), extern: f.extern(c, this_record) ) end end # If this is a node (non-leaf) page, it will have a child page number # (or "node pointer") stored as the last field. this_record.child_page_number = c.name("child_page_number") { c.read_uint32 } unless leaf? this_record.length = c.position - offset # Add system field accessors for convenience. this_record.sys.each do |f| case f[:name] when "DB_TRX_ID" this_record.transaction_id = f[:value] when "DB_ROLL_PTR" this_record.roll_pointer = f[:value] end end end Innodb::Record.new(self, this_record) end end |
#record_cursor(offset = :min, direction = :forward) ⇒ Object
Return a RecordCursor starting at offset.
799 800 801 |
# File 'lib/innodb/page/index.rb', line 799 def record_cursor(offset = :min, direction = :forward) RecordCursor.new(self, offset, direction) end |
#record_directory_slot(this_record) ⇒ Object
Return the slot number of the provided record in the page directory, or nil if the record is not present in the page directory.
675 676 677 |
# File 'lib/innodb/page/index.rb', line 675 def record_directory_slot(this_record) offset_directory_slot(this_record.offset) end |
#record_fields ⇒ Object
Returns the (ordered) set of fields that describe records in this page.
595 596 597 |
# File 'lib/innodb/page/index.rb', line 595 def record_fields record_format.values_at(:key, :sys, :row).flatten.sort_by(&:position) if record_format end |
#record_format ⇒ Object
Return (and cache) the record format provided by an external class.
590 591 592 |
# File 'lib/innodb/page/index.rb', line 590 def record_format @record_format ||= make_record_description if record_describer end |
#record_header(cursor) ⇒ Object
Return the header from a record.
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 |
# File 'lib/innodb/page/index.rb', line 356 def record_header(cursor) origin = cursor.position header = RecordHeader.new cursor.backward.name("header") do |c| case page_header.format when :compact # The "next" pointer is a relative offset from the current record. header.next = c.name("next") { origin + c.read_sint16 } # Fields packed in a 16-bit integer (LSB first): # 3 bits for type # 13 bits for heap_number bits1 = c.name("bits1") { c.read_uint16 } header.type = RECORD_TYPES[bits1 & 0x07] header.heap_number = (bits1 & 0xfff8) >> 3 when :redundant # The "next" pointer is an absolute offset within the page. header.next = c.name("next") { c.read_uint16 } # Fields packed in a 24-bit integer (LSB first): # 1 bit for offset_size (0 = 2 bytes, 1 = 1 byte) # 10 bits for n_fields # 13 bits for heap_number bits1 = c.name("bits1") { c.read_uint24 } header.offset_size = (bits1 & 1).zero? ? 2 : 1 header.n_fields = (bits1 & (((1 << 10) - 1) << 1)) >> 1 header.heap_number = (bits1 & (((1 << 13) - 1) << 11)) >> 11 end # Fields packed in an 8-bit integer (LSB first): # 4 bits for n_owned # 4 bits for flags bits2 = c.name("bits2") { c.read_uint8 } header.n_owned = bits2 & 0x0f header.info_flags = (bits2 & 0xf0) >> 4 case page_header.format when :compact record_header_compact_additional(header, cursor) when :redundant record_header_redundant_additional(header, cursor) end header.length = origin - cursor.position end header end |
#record_header_compact_additional(header, cursor) ⇒ Object
Read additional header information from a compact format record header.
406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/innodb/page/index.rb', line 406 def record_header_compact_additional(header, cursor) case header.type when :conventional, :node_pointer # The variable-length part of the record header contains a # bit vector indicating NULL fields and the length of each # non-NULL variable-length field. if record_format header.nulls = cursor.name("nulls") { record_header_compact_null_bitmap(cursor) } header.lengths, header.externs = cursor.name("lengths_and_externs") do record_header_compact_variable_lengths_and_externs(cursor, header.nulls) end end end end |
#record_header_compact_null_bitmap(cursor) ⇒ Object
Return an array indicating which fields are null.
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 |
# File 'lib/innodb/page/index.rb', line 422 def record_header_compact_null_bitmap(cursor) fields = record_fields # The number of bits in the bitmap is the number of nullable fields. size = fields.count(&:nullable?) # There is no bitmap if there are no nullable fields. return [] unless size.positive? # TODO: This is really ugly. null_bit_array = cursor.read_bit_array(size).reverse! # For every nullable field, select the ones which are actually null. fields.select { |f| f.nullable? && (null_bit_array.shift == 1) }.map(&:name) end |
#record_header_compact_variable_lengths_and_externs(cursor, nulls) ⇒ Object
Return an array containing an array of the length of each variable-length field and an array indicating which fields are stored externally.
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 |
# File 'lib/innodb/page/index.rb', line 440 def record_header_compact_variable_lengths_and_externs(cursor, nulls) fields = (record_format[:key] + record_format[:row]) lengths = {} externs = [] # For each non-NULL variable-length field, the record header contains # the length in one or two bytes. fields.each do |f| next if !f.variable? || nulls.include?(f.name) len = cursor.read_uint8 ext = false # Two bytes are used only if the length exceeds 127 bytes and the # maximum length exceeds 255 bytes (or the field is a BLOB type). if len > 127 && (f.blob? || f.data_type.width > 255) ext = (0x40 & len) != 0 len = ((len & 0x3f) << 8) + cursor.read_uint8 end lengths[f.name] = len externs << f.name if ext end [lengths, externs] end |
#record_header_redundant_additional(header, cursor) ⇒ Object
Read additional header information from a redundant format record header.
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 |
# File 'lib/innodb/page/index.rb', line 469 def record_header_redundant_additional(header, cursor) lengths = [] nulls = [] externs = [] field_offsets = record_header_redundant_field_end_offsets(header, cursor) this_field_offset = 0 field_offsets.each do |n| case header.offset_size when 1 next_field_offset = (n & RECORD_REDUNDANT_OFF1_OFFSET_MASK) lengths << (next_field_offset - this_field_offset) nulls << ((n & RECORD_REDUNDANT_OFF1_NULL_MASK) != 0) externs << false when 2 next_field_offset = (n & RECORD_REDUNDANT_OFF2_OFFSET_MASK) lengths << (next_field_offset - this_field_offset) nulls << ((n & RECORD_REDUNDANT_OFF2_NULL_MASK) != 0) externs << ((n & RECORD_REDUNDANT_OFF2_EXTERN_MASK) != 0) end this_field_offset = next_field_offset end # If possible, refer to fields by name rather than position for # better formatting (i.e. pp). if record_format header.lengths = {} header.nulls = [] header.externs = [] record_fields.each do |f| header.lengths[f.name] = lengths[f.position] header.nulls << f.name if nulls[f.position] header.externs << f.name if externs[f.position] end else header.lengths = lengths header.nulls = nulls header.externs = externs end end |
#record_header_redundant_field_end_offsets(header, cursor) ⇒ Object
Read field end offsets from the provided cursor for each field as counted by n_fields.
514 515 516 517 518 |
# File 'lib/innodb/page/index.rb', line 514 def record_header_redundant_field_end_offsets(header, cursor) header.n_fields.times.map do |n| cursor.name("field_end_offset[#{n}]") { cursor.read_uint_by_size(header.offset_size) } end end |
#record_if_exists(offset) ⇒ Object
803 804 805 806 807 |
# File 'lib/innodb/page/index.rb', line 803 def record_if_exists(offset) each_record do |rec| return rec if rec.offset == offset end end |
#record_space ⇒ Object
Return the amount of space occupied by records in the page.
290 291 292 |
# File 'lib/innodb/page/index.rb', line 290 def record_space used_space - header_space - directory_space - trailer_space end |
#root? ⇒ Boolean
A helper function to identify root index pages; they must be the only pages at their level.
331 332 333 |
# File 'lib/innodb/page/index.rb', line 331 def root? prev.nil? && self.next.nil? end |
#size_fseg_header ⇒ Object
The size of the “fseg” header.
183 184 185 |
# File 'lib/innodb/page/index.rb', line 183 def size_fseg_header 2 * Innodb::FsegEntry::SIZE end |
#size_index_header ⇒ Object
The size of the “index” header.
172 173 174 |
# File 'lib/innodb/page/index.rb', line 172 def size_index_header 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 8 + 2 + 8 end |
#size_mum_record ⇒ Object
The size of the data from the supremum or infimum records.
211 212 213 |
# File 'lib/innodb/page/index.rb', line 211 def size_mum_record 8 end |
#size_mum_record_header_additional ⇒ Object
The size of the additional data structures in the header of the system records, which is just 1 byte in redundant format to store the offset of the end of the field. This is needed specifically here since we need to be able to calculate the fixed positions of these system records.
201 202 203 204 205 206 207 208 |
# File 'lib/innodb/page/index.rb', line 201 def size_mum_record_header_additional case page_header[:format] when :compact 0 # No additional data is stored in compact format. when :redundant 1 # A 1-byte offset for 1 field is stored in redundant format. end end |
#size_record_header ⇒ Object
Return the size of the header for each record.
188 189 190 191 192 193 194 195 |
# File 'lib/innodb/page/index.rb', line 188 def size_record_header case page_header[:format] when :compact RECORD_NEXT_SIZE + RECORD_COMPACT_BITS_SIZE when :redundant RECORD_NEXT_SIZE + RECORD_REDUNDANT_BITS_SIZE end end |
#space_per_record ⇒ Object
A helper to calculate the amount of space consumed per record.
295 296 297 |
# File 'lib/innodb/page/index.rb', line 295 def space_per_record page_header.n_recs.positive? ? (record_space.to_f / page_header.n_recs) : 0 end |
#supremum ⇒ Object
Return the supremum record on a page.
544 545 546 |
# File 'lib/innodb/page/index.rb', line 544 def supremum @supremum ||= system_record(pos_supremum) end |
#system_record(offset) ⇒ Object
Parse and return simple fixed-format system records, such as InnoDB’s internal infimum and supremum records.
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 |
# File 'lib/innodb/page/index.rb', line 522 def system_record(offset) cursor(offset).name("record[#{offset}]") do |c| header = c.peek { record_header(c) } Innodb::Record.new( self, SystemRecord.new( offset: offset, header: header, next: header.next, data: c.name("data") { c.read_bytes(size_mum_record) }, length: c.position - offset ) ) end end |
#trailer_space ⇒ Object
The amount of space consumed by the trailers in the page.
274 275 276 |
# File 'lib/innodb/page/index.rb', line 274 def trailer_space size_fil_trailer end |
#used_space ⇒ Object
Return the amount of used space in the page.
285 286 287 |
# File 'lib/innodb/page/index.rb', line 285 def used_space size - free_space end |