innodb_ruby 0.9.16 → 0.11.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.
- checksums.yaml +5 -5
- data/README.md +5 -6
- data/bin/innodb_log +13 -18
- data/bin/innodb_space +377 -757
- data/lib/innodb.rb +4 -5
- data/lib/innodb/checksum.rb +26 -24
- data/lib/innodb/data_dictionary.rb +490 -550
- data/lib/innodb/data_type.rb +362 -326
- data/lib/innodb/field.rb +102 -89
- data/lib/innodb/fseg_entry.rb +22 -26
- data/lib/innodb/history.rb +21 -21
- data/lib/innodb/history_list.rb +72 -76
- data/lib/innodb/ibuf_bitmap.rb +36 -36
- data/lib/innodb/ibuf_index.rb +6 -2
- data/lib/innodb/index.rb +245 -276
- data/lib/innodb/inode.rb +154 -155
- data/lib/innodb/list.rb +191 -183
- data/lib/innodb/log.rb +139 -110
- data/lib/innodb/log_block.rb +100 -91
- data/lib/innodb/log_group.rb +53 -64
- data/lib/innodb/log_reader.rb +97 -96
- data/lib/innodb/log_record.rb +328 -279
- data/lib/innodb/lsn.rb +86 -81
- data/lib/innodb/page.rb +417 -414
- data/lib/innodb/page/blob.rb +82 -83
- data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
- data/lib/innodb/page/ibuf_bitmap.rb +34 -34
- data/lib/innodb/page/index.rb +964 -943
- data/lib/innodb/page/index_compressed.rb +34 -34
- data/lib/innodb/page/inode.rb +103 -112
- data/lib/innodb/page/sys.rb +13 -15
- data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
- data/lib/innodb/page/sys_ibuf_header.rb +45 -42
- data/lib/innodb/page/sys_rseg_header.rb +88 -82
- data/lib/innodb/page/trx_sys.rb +204 -182
- data/lib/innodb/page/undo_log.rb +106 -92
- data/lib/innodb/record.rb +121 -160
- data/lib/innodb/record_describer.rb +66 -68
- data/lib/innodb/space.rb +380 -418
- data/lib/innodb/stats.rb +33 -35
- data/lib/innodb/system.rb +149 -171
- data/lib/innodb/undo_log.rb +129 -107
- data/lib/innodb/undo_record.rb +255 -247
- data/lib/innodb/util/buffer_cursor.rb +81 -79
- data/lib/innodb/util/read_bits_at_offset.rb +2 -1
- data/lib/innodb/version.rb +2 -2
- data/lib/innodb/xdes.rb +144 -142
- metadata +80 -11
data/lib/innodb/field.rb
CHANGED
@@ -1,114 +1,127 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "innodb/data_type"
|
4
4
|
|
5
5
|
# A single field in an InnoDB record (within an INDEX page). This class
|
6
6
|
# provides essential information to parse records, including the length
|
7
7
|
# of the fixed-width and variable-width portion of the field.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
8
|
+
module Innodb
|
9
|
+
class Field
|
10
|
+
ExternReference = Struct.new(
|
11
|
+
:space_id,
|
12
|
+
:page_number,
|
13
|
+
:offset,
|
14
|
+
:length, # rubocop:disable Lint/StructNewOverride
|
15
|
+
keyword_init: true
|
16
|
+
)
|
17
|
+
|
18
|
+
attr_reader :position
|
19
|
+
attr_reader :name
|
20
|
+
attr_reader :data_type
|
21
|
+
attr_reader :nullable
|
22
|
+
|
23
|
+
# Size of a reference to data stored externally to the page.
|
24
|
+
EXTERN_FIELD_SIZE = 20
|
25
|
+
|
26
|
+
def initialize(position, name, type_definition, *properties)
|
27
|
+
@position = position
|
28
|
+
@name = name
|
29
|
+
@nullable = !properties.delete(:NOT_NULL)
|
30
|
+
base_type, modifiers = parse_type_definition(type_definition.to_s)
|
31
|
+
@data_type = Innodb::DataType.new(base_type, modifiers, properties)
|
32
|
+
end
|
21
33
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
34
|
+
# Return whether this field can be NULL.
|
35
|
+
def nullable?
|
36
|
+
@nullable
|
37
|
+
end
|
26
38
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
39
|
+
# Return whether this field is NULL.
|
40
|
+
def null?(record)
|
41
|
+
nullable? && record.header.nulls.include?(@name)
|
42
|
+
end
|
31
43
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
44
|
+
# Return whether a part of this field is stored externally (off-page).
|
45
|
+
def extern?(record)
|
46
|
+
record.header.externs.include?(@name)
|
47
|
+
end
|
36
48
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
def variable?
|
50
|
+
[
|
51
|
+
Innodb::DataType::BlobType,
|
52
|
+
Innodb::DataType::VariableBinaryType,
|
53
|
+
Innodb::DataType::VariableCharacterType,
|
54
|
+
].any? { |c| @data_type.is_a?(c) }
|
55
|
+
end
|
42
56
|
|
43
|
-
|
44
|
-
|
45
|
-
|
57
|
+
def blob?
|
58
|
+
@data_type.is_a?(Innodb::DataType::BlobType)
|
59
|
+
end
|
46
60
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
61
|
+
# Return the actual length of this variable-length field.
|
62
|
+
def length(record)
|
63
|
+
if record.header.lengths.include?(@name)
|
64
|
+
len = record.header.lengths[@name]
|
65
|
+
raise "Fixed-length mismatch" unless variable? || len == @data_type.width
|
66
|
+
else
|
67
|
+
len = @data_type.width
|
68
|
+
end
|
69
|
+
extern?(record) ? len - EXTERN_FIELD_SIZE : len
|
54
70
|
end
|
55
|
-
extern?(record) ? len - EXTERN_FIELD_SIZE : len
|
56
|
-
end
|
57
71
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
72
|
+
# Read an InnoDB encoded data field.
|
73
|
+
def read(cursor, field_length)
|
74
|
+
cursor.name(@data_type.name) { cursor.read_bytes(field_length) }
|
75
|
+
end
|
62
76
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
77
|
+
def value_by_length(cursor, field_length)
|
78
|
+
if @data_type.respond_to?(:read)
|
79
|
+
cursor.name(@data_type.name) { @data_type.read(cursor) }
|
80
|
+
elsif @data_type.respond_to?(:value)
|
81
|
+
@data_type.value(read(cursor, field_length))
|
82
|
+
else
|
83
|
+
read(cursor, field_length)
|
84
|
+
end
|
70
85
|
end
|
71
|
-
end
|
72
86
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
value_by_length(cursor, length(record))
|
77
|
-
end
|
87
|
+
# Read the data value (e.g. encoded in the data).
|
88
|
+
def value(cursor, record)
|
89
|
+
return :NULL if null?(record)
|
78
90
|
|
79
|
-
|
80
|
-
|
81
|
-
return nil if not extern?(record)
|
82
|
-
cursor.name(@name) { read_extern(cursor) }
|
83
|
-
end
|
91
|
+
value_by_length(cursor, length(record))
|
92
|
+
end
|
84
93
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
# address and the length of the externally stored part of the record data.
|
89
|
-
def get_extern_reference(cursor)
|
90
|
-
{
|
91
|
-
:space_id => cursor.name("space_id") { cursor.get_uint32 },
|
92
|
-
:page_number => cursor.name("page_number") { cursor.get_uint32 },
|
93
|
-
:offset => cursor.name("offset") { cursor.get_uint32 },
|
94
|
-
:length => cursor.name("length") { cursor.get_uint64 & 0x3fffffff }
|
95
|
-
}
|
96
|
-
end
|
94
|
+
# Read an InnoDB external pointer field.
|
95
|
+
def extern(cursor, record)
|
96
|
+
return unless extern?(record)
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
end
|
98
|
+
cursor.name(@name) { read_extern(cursor) }
|
99
|
+
end
|
101
100
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
101
|
+
private
|
102
|
+
|
103
|
+
# Return an external reference field. An extern field contains the page
|
104
|
+
# address and the length of the externally stored part of the record data.
|
105
|
+
def read_extern(cursor)
|
106
|
+
cursor.name("extern") do |c|
|
107
|
+
ExternReference.new(
|
108
|
+
space_id: c.name("space_id") { c.read_uint32 },
|
109
|
+
page_number: c.name("page_number") { c.read_uint32 },
|
110
|
+
offset: c.name("offset") { c.read_uint32 },
|
111
|
+
length: c.name("length") { c.read_uint64 & 0x3fffffff }
|
112
|
+
)
|
110
113
|
end
|
111
|
-
|
114
|
+
end
|
115
|
+
|
116
|
+
# Parse a data type definition and extract the base type and any modifiers.
|
117
|
+
def parse_type_definition(type_string)
|
118
|
+
matches = /^([a-zA-Z0-9_]+)(\(([0-9, ]+)\))?$/.match(type_string)
|
119
|
+
return unless matches
|
120
|
+
|
121
|
+
base_type = matches[1].upcase.to_sym
|
122
|
+
return [base_type, []] unless matches[3]
|
123
|
+
|
124
|
+
[base_type, matches[3].sub(/ /, "").split(/,/).map(&:to_i)]
|
112
125
|
end
|
113
126
|
end
|
114
127
|
end
|
data/lib/innodb/fseg_entry.rb
CHANGED
@@ -1,36 +1,32 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# An InnoDB file segment entry, which appears in a few places, such as the
|
4
4
|
# FSEG header of INDEX pages, and in the TRX_SYS pages.
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
module Innodb
|
7
|
+
class FsegEntry
|
8
|
+
# The size (in bytes) of an FSEG entry, which contains a two 32-bit integers
|
9
|
+
# and a 16-bit integer.
|
10
|
+
SIZE = 4 + 4 + 2
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
}
|
18
|
-
:offset => cursor.name("offset") { cursor.get_uint16 },
|
19
|
-
}
|
20
|
-
end
|
21
|
-
|
22
|
-
# Return an INODE entry which represents this file segment.
|
23
|
-
def self.get_inode(space, cursor)
|
24
|
-
address = cursor.name("address") { get_entry_address(cursor) }
|
25
|
-
if address[:offset] == 0
|
26
|
-
return nil
|
12
|
+
# Return the FSEG entry address, which points to an entry on an INODE page.
|
13
|
+
def self.get_entry_address(cursor)
|
14
|
+
{
|
15
|
+
space_id: cursor.name("space_id") { cursor.read_uint32 },
|
16
|
+
page_number: cursor.name("page_number") { Innodb::Page.maybe_undefined(cursor.read_uint32) },
|
17
|
+
offset: cursor.name("offset") { cursor.read_uint16 },
|
18
|
+
}
|
27
19
|
end
|
28
20
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
21
|
+
# Return an INODE entry which represents this file segment.
|
22
|
+
def self.get_inode(space, cursor)
|
23
|
+
address = cursor.name("address") { get_entry_address(cursor) }
|
24
|
+
return nil if address[:offset].zero?
|
33
25
|
|
34
|
-
|
26
|
+
page = space.page(address[:page_number])
|
27
|
+
return nil unless page.type == :INODE
|
28
|
+
|
29
|
+
page.inode_at(page.cursor(address[:offset]))
|
30
|
+
end
|
35
31
|
end
|
36
32
|
end
|
data/lib/innodb/history.rb
CHANGED
@@ -1,30 +1,30 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# The global history of record versions implemented through undo logs.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# A helper to get to the trx_sys page in the Innodb::System.
|
10
|
-
def trx_sys
|
11
|
-
@innodb_system.system_space.trx_sys
|
12
|
-
end
|
4
|
+
module Innodb
|
5
|
+
class History
|
6
|
+
def initialize(innodb_system)
|
7
|
+
@innodb_system = innodb_system
|
8
|
+
end
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
# A helper to get to the trx_sys page in the Innodb::System.
|
11
|
+
def trx_sys
|
12
|
+
@innodb_system.system_space.trx_sys
|
13
|
+
end
|
18
14
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
unless block_given?
|
23
|
-
return enum_for(:each_history_list)
|
15
|
+
# A helper to get to the history_list of a given space_id and page number.
|
16
|
+
def history_list(space_id, page_number)
|
17
|
+
@innodb_system.space(space_id).page(page_number).history_list
|
24
18
|
end
|
25
19
|
|
26
|
-
|
27
|
-
|
20
|
+
# Iterate through all history lists (one per rollback segment, nominally
|
21
|
+
# there are 128 rollback segments).
|
22
|
+
def each_history_list
|
23
|
+
return enum_for(:each_history_list) unless block_given?
|
24
|
+
|
25
|
+
trx_sys.rsegs.each do |slot|
|
26
|
+
yield history_list(slot[:space_id], slot[:page_number])
|
27
|
+
end
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
data/lib/innodb/history_list.rb
CHANGED
@@ -1,106 +1,102 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# A single history list; this is a more intelligent wrapper around the basic
|
4
4
|
# Innodb::List::History which is provided elsewhere.
|
5
|
-
|
6
|
-
|
5
|
+
module Innodb
|
6
|
+
class HistoryList
|
7
|
+
attr_reader :list
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
class UndoRecordCursor
|
14
|
-
def initialize(history, undo_record, direction=:forward)
|
15
|
-
@history = history
|
16
|
-
@undo_record = undo_record
|
9
|
+
# Initialize from a provided Innodb::List::History.
|
10
|
+
def initialize(list)
|
11
|
+
@list = list
|
12
|
+
end
|
17
13
|
|
18
|
-
|
19
|
-
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@
|
14
|
+
class UndoRecordCursor
|
15
|
+
def initialize(history, undo_record, direction = :forward)
|
16
|
+
@history = history
|
17
|
+
@undo_record = undo_record
|
18
|
+
|
19
|
+
# rubocop:disable Style/IfUnlessModifier
|
20
|
+
case undo_record
|
21
|
+
when :min
|
22
|
+
@undo_log_cursor = history.list.list_cursor(:min, direction)
|
23
|
+
if (@undo_log = @undo_log_cursor.node)
|
24
|
+
@undo_record_cursor = @undo_log.undo_record_cursor(:min, direction)
|
25
|
+
end
|
26
|
+
when :max
|
27
|
+
@undo_log_cursor = history.list.list_cursor(:max, direction)
|
28
|
+
if (@undo_log = @undo_log_cursor.node)
|
29
|
+
@undo_record_cursor = @undo_log.undo_record_cursor(:max, direction)
|
30
|
+
end
|
31
|
+
else
|
32
|
+
raise "Not implemented"
|
28
33
|
end
|
29
|
-
|
30
|
-
raise "Not implemented"
|
34
|
+
# rubocop:enable Style/IfUnlessModifier
|
31
35
|
end
|
32
|
-
end
|
33
36
|
|
34
|
-
|
35
|
-
|
36
|
-
return nil
|
37
|
-
end
|
37
|
+
def undo_record
|
38
|
+
return nil unless @undo_record_cursor
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
if (rec = @undo_record_cursor.undo_record)
|
41
|
+
return rec
|
42
|
+
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
case @direction
|
45
|
+
when :forward
|
46
|
+
next_undo_record
|
47
|
+
when :backward
|
48
|
+
prev_undo_record
|
49
|
+
end
|
48
50
|
end
|
49
|
-
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
def next_undo_record
|
57
|
-
if rec = @undo_record_cursor.undo_record
|
58
|
-
return rec
|
52
|
+
def move_cursor(page, undo_record)
|
53
|
+
@undo_log = page
|
54
|
+
@undo_log_cursor = @undo_log.undo_record_cursor(undo_record, @direction)
|
59
55
|
end
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
57
|
+
def next_undo_record
|
58
|
+
if (rec = @undo_record_cursor.undo_record)
|
59
|
+
return rec
|
60
|
+
end
|
65
61
|
|
66
|
-
|
67
|
-
|
62
|
+
if (undo_log = @undo_log_cursor.node)
|
63
|
+
@undo_log = undo_log
|
64
|
+
@undo_record_cursor = @undo_log.undo_record_cursor(:min, @direction)
|
65
|
+
end
|
68
66
|
|
69
|
-
|
70
|
-
if rec = @undo_log_cursor.undo_record
|
71
|
-
return rec
|
67
|
+
@undo_record_cursor.undo_record
|
72
68
|
end
|
73
69
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
70
|
+
def prev_undo_record
|
71
|
+
if (rec = @undo_log_cursor.undo_record)
|
72
|
+
return rec
|
73
|
+
end
|
78
74
|
|
79
|
-
|
80
|
-
|
75
|
+
if (undo_log = @undo_log_cursor.node)
|
76
|
+
@undo_log = undo_log
|
77
|
+
@undo_record_cursor = @undo_log.undo_record_cursor(:max, @direction)
|
78
|
+
end
|
81
79
|
|
82
|
-
|
83
|
-
unless block_given?
|
84
|
-
return enum_for(:each_undo_record)
|
80
|
+
@undo_record_cursor.undo_record
|
85
81
|
end
|
86
82
|
|
87
|
-
|
88
|
-
|
83
|
+
def each_undo_record
|
84
|
+
return enum_for(:each_undo_record) unless block_given?
|
85
|
+
|
86
|
+
while (rec = undo_record)
|
87
|
+
yield rec
|
88
|
+
end
|
89
89
|
end
|
90
90
|
end
|
91
|
-
end
|
92
91
|
|
93
|
-
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
def each_undo_record
|
98
|
-
unless block_given?
|
99
|
-
return enum_for(:each_undo_record)
|
92
|
+
def undo_record_cursor(undo_record = :min, direction = :forward)
|
93
|
+
UndoRecordCursor.new(self, undo_record, direction)
|
100
94
|
end
|
101
95
|
|
102
|
-
|
103
|
-
|
96
|
+
def each_undo_record(&block)
|
97
|
+
return enum_for(:each_undo_record) unless block_given?
|
98
|
+
|
99
|
+
undo_record_cursor.each_undo_record(&block)
|
104
100
|
end
|
105
101
|
end
|
106
102
|
end
|