innodb_ruby 0.9.14 → 0.12.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 +7 -0
- data/README.md +5 -6
- data/bin/innodb_log +13 -18
- data/bin/innodb_space +654 -778
- data/lib/innodb/checksum.rb +26 -24
- data/lib/innodb/data_dictionary.rb +490 -550
- data/lib/innodb/data_type.rb +362 -325
- 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 +166 -124
- data/lib/innodb/list.rb +196 -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/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 +965 -924
- 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/page.rb +417 -414
- data/lib/innodb/record.rb +121 -164
- data/lib/innodb/record_describer.rb +66 -68
- data/lib/innodb/space.rb +381 -413
- 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
- data/lib/innodb.rb +4 -5
- metadata +100 -25
@@ -1,46 +1,46 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# This is horribly incomplete and broken. InnoDB compression does not
|
4
4
|
# currently work in innodb_ruby. Patches are welcome!
|
5
5
|
# (Hint hint, nudge nudge, Facebook developers!)
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
7
|
+
module Innodb
|
8
|
+
class Page
|
9
|
+
class IndexCompressed < Index
|
10
|
+
specialization_for({ type: :INDEX, compressed: true })
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
# The number of directory slots in use.
|
13
|
+
def directory_slots
|
14
|
+
page_header[:n_heap] - 2
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
if record_format && record_format[:type] == :clustered
|
20
|
-
6 + 7 # Transaction ID + Roll Pointer
|
21
|
-
else
|
22
|
-
0
|
17
|
+
def directory
|
18
|
+
super.map { |n| n & 0x3fff }
|
23
19
|
end
|
24
|
-
else
|
25
|
-
4 # Node pointer for non-leaf pages
|
26
|
-
end
|
27
|
-
end
|
28
20
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
21
|
+
def uncompressed_columns_size
|
22
|
+
if leaf?
|
23
|
+
if record_format && record_format[:type] == :clustered
|
24
|
+
6 + 7 # Transaction ID + Roll Pointer
|
25
|
+
else
|
26
|
+
0
|
27
|
+
end
|
28
|
+
else
|
29
|
+
4 # Node pointer for non-leaf pages
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return the amount of free space in the page.
|
34
|
+
def free_space
|
35
|
+
free_space_start =
|
36
|
+
size - size_fil_trailer - directory_space - (uncompressed_columns_size * (page_header.n_heap - 2))
|
37
|
+
puts "Free space start == %04x" % [offset * size + free_space_start]
|
38
|
+
c = cursor(free_space_start).backward
|
39
|
+
zero_bytes = 0
|
40
|
+
zero_bytes += 1 while c.read_uint8.zero?
|
41
|
+
zero_bytes
|
42
|
+
# page_header[:garbage] + (size - size_fil_trailer - directory_space - page_header[:heap_top])
|
43
|
+
end
|
38
44
|
end
|
39
|
-
zero_bytes
|
40
|
-
#page_header[:garbage] +
|
41
|
-
# (size - size_fil_trailer - directory_space - page_header[:heap_top])
|
42
45
|
end
|
43
46
|
end
|
44
|
-
|
45
|
-
Innodb::Page::SPECIALIZED_CLASSES[{:type => :INDEX, :compressed => true}] = Innodb::Page::Index::Compressed
|
46
|
-
|
data/lib/innodb/page/inode.rb
CHANGED
@@ -1,139 +1,130 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "innodb/list"
|
4
4
|
|
5
5
|
# A specialized class for handling INODE pages, which contain index FSEG (file
|
6
6
|
# segment) information. This allows all extents and individual pages assigned
|
7
7
|
# to each index to be found.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
# Return the byte offset of the Inode array in the page, which immediately
|
21
|
-
# follows the list entry.
|
22
|
-
def pos_inode_array
|
23
|
-
pos_list_entry + size_list_entry
|
24
|
-
end
|
25
|
-
|
26
|
-
# The number of Inode entries that fit on a page.
|
27
|
-
def inodes_per_page
|
28
|
-
(size - pos_inode_array - 10) / Innodb::Inode::SIZE
|
29
|
-
end
|
8
|
+
module Innodb
|
9
|
+
class Page
|
10
|
+
class Inode < Page
|
11
|
+
specialization_for :INODE
|
12
|
+
|
13
|
+
# Return the byte offset of the list node, which immediately follows the
|
14
|
+
# FIL header.
|
15
|
+
def pos_list_entry
|
16
|
+
pos_page_body
|
17
|
+
end
|
30
18
|
|
31
|
-
|
32
|
-
|
33
|
-
|
19
|
+
# Return the size of the list node.
|
20
|
+
def size_list_entry
|
21
|
+
Innodb::List::NODE_SIZE
|
22
|
+
end
|
34
23
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
24
|
+
# Return the byte offset of the Inode array in the page, which immediately
|
25
|
+
# follows the list entry.
|
26
|
+
def pos_inode_array
|
27
|
+
pos_list_entry + size_list_entry
|
28
|
+
end
|
41
29
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
30
|
+
# The number of Inode entries that fit on a page.
|
31
|
+
def inodes_per_page
|
32
|
+
(size - pos_inode_array - 10) / Innodb::Inode::SIZE
|
33
|
+
end
|
47
34
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
list_entry[:next]
|
52
|
-
end
|
35
|
+
def size_inode_array
|
36
|
+
inodes_per_page * Innodb::Inode::SIZE
|
37
|
+
end
|
53
38
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
39
|
+
# Return the list entry.
|
40
|
+
def list_entry
|
41
|
+
cursor(pos_list_entry).name("list") { |c| Innodb::List.get_node(c) }
|
42
|
+
end
|
59
43
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
44
|
+
# Return the "previous" address pointer from the list entry. This is used
|
45
|
+
# by Innodb::List::Inode to iterate through Inode lists.
|
46
|
+
def prev_address
|
47
|
+
list_entry.prev
|
48
|
+
end
|
65
49
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
yield this_inode
|
50
|
+
# Return the "next" address pointer from the list entry. This is used
|
51
|
+
# by Innodb::List::Inode to iterate through Inode lists.
|
52
|
+
def next_address
|
53
|
+
list_entry.next
|
71
54
|
end
|
72
|
-
end
|
73
|
-
end
|
74
55
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
56
|
+
# Read a single Inode entry from the provided byte offset by creating a
|
57
|
+
# cursor and reading the inode using the inode method.
|
58
|
+
def inode_at(cursor)
|
59
|
+
cursor.name("inode[#{cursor.position}]") { |c| Innodb::Inode.new_from_cursor(@space, c) }
|
60
|
+
end
|
80
61
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
62
|
+
# Iterate through all Inodes in the inode array.
|
63
|
+
def each_inode
|
64
|
+
return enum_for(:each_inode) unless block_given?
|
85
65
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
66
|
+
inode_cursor = cursor(pos_inode_array)
|
67
|
+
inodes_per_page.times do |n|
|
68
|
+
inode_cursor.name("inode[#{n}]") do |c|
|
69
|
+
yield Innodb::Inode.new_from_cursor(@space, c)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
90
73
|
|
91
|
-
|
92
|
-
|
93
|
-
|
74
|
+
# Iterate through all allocated inodes in the inode array.
|
75
|
+
def each_allocated_inode
|
76
|
+
return enum_for(:each_allocated_inode) unless block_given?
|
94
77
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
:name => :list_entry,
|
99
|
-
:info => "Inode List Entry",
|
100
|
-
})
|
101
|
-
|
102
|
-
each_inode do |inode|
|
103
|
-
if inode.allocated?
|
104
|
-
yield({
|
105
|
-
:offset => inode.offset,
|
106
|
-
:length => Innodb::Inode::SIZE,
|
107
|
-
:name => :inode_used,
|
108
|
-
:info => "Inode (used)",
|
109
|
-
})
|
110
|
-
else
|
111
|
-
yield({
|
112
|
-
:offset => inode.offset,
|
113
|
-
:length => Innodb::Inode::SIZE,
|
114
|
-
:name => :inode_free,
|
115
|
-
:info => "Inode (free)",
|
116
|
-
})
|
78
|
+
each_inode do |this_inode|
|
79
|
+
yield this_inode if this_inode.allocated?
|
80
|
+
end
|
117
81
|
end
|
118
|
-
end
|
119
82
|
|
120
|
-
|
121
|
-
|
83
|
+
def each_region(&block)
|
84
|
+
return enum_for(:each_region) unless block_given?
|
85
|
+
|
86
|
+
super(&block)
|
87
|
+
|
88
|
+
yield Region.new(
|
89
|
+
offset: pos_list_entry,
|
90
|
+
length: size_list_entry,
|
91
|
+
name: :list_entry,
|
92
|
+
info: "Inode List Entry"
|
93
|
+
)
|
94
|
+
|
95
|
+
each_inode do |inode|
|
96
|
+
if inode.allocated?
|
97
|
+
yield Region.new(
|
98
|
+
offset: inode.offset,
|
99
|
+
length: Innodb::Inode::SIZE,
|
100
|
+
name: :inode_used,
|
101
|
+
info: "Inode (used)"
|
102
|
+
)
|
103
|
+
else
|
104
|
+
yield Region.new(
|
105
|
+
offset: inode.offset,
|
106
|
+
length: Innodb::Inode::SIZE,
|
107
|
+
name: :inode_free,
|
108
|
+
info: "Inode (free)"
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
nil
|
114
|
+
end
|
122
115
|
|
123
|
-
|
124
|
-
|
125
|
-
|
116
|
+
# Dump the contents of a page for debugging purposes.
|
117
|
+
def dump
|
118
|
+
super
|
126
119
|
|
127
|
-
|
128
|
-
|
129
|
-
|
120
|
+
puts "list entry:"
|
121
|
+
pp list_entry
|
122
|
+
puts
|
130
123
|
|
131
|
-
|
132
|
-
|
133
|
-
|
124
|
+
puts "inodes:"
|
125
|
+
each_inode(&:dump)
|
126
|
+
puts
|
127
|
+
end
|
134
128
|
end
|
135
|
-
puts
|
136
129
|
end
|
137
130
|
end
|
138
|
-
|
139
|
-
Innodb::Page::SPECIALIZED_CLASSES[:INODE] = Innodb::Page::Inode
|
data/lib/innodb/page/sys.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "innodb/page/sys_rseg_header"
|
4
4
|
require "innodb/page/sys_data_dictionary_header"
|
@@ -7,20 +7,18 @@ require "innodb/page/sys_ibuf_header"
|
|
7
7
|
# Another layer of indirection for pages of type SYS, as they have multiple
|
8
8
|
# uses within InnoDB. We'll override the self.handle method and check the
|
9
9
|
# page offset to decide which type of SYS page this is.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
module Innodb
|
11
|
+
class Page
|
12
|
+
class Sys < Page
|
13
|
+
specialization_for :SYS
|
14
|
+
|
15
|
+
def self.handle(page, space, buffer, page_number = nil)
|
16
|
+
return Innodb::Page::SysIbufHeader.new(space, buffer, page_number) if page.offset == 3
|
17
|
+
return Innodb::Page::SysDataDictionaryHeader.new(space, buffer, page_number) if page.offset == 7
|
18
|
+
return Innodb::Page::SysRsegHeader.new(space, buffer, page_number) if space.rseg_page?(page.offset)
|
19
|
+
|
20
|
+
page
|
21
|
+
end
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
26
|
-
Innodb::Page::SPECIALIZED_CLASSES[:SYS] = Innodb::Page::Sys
|
@@ -1,70 +1,92 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module Innodb
|
4
|
+
class Page
|
5
|
+
class SysDataDictionaryHeader < Page
|
6
|
+
Header = Struct.new(
|
7
|
+
:max_row_id,
|
8
|
+
:max_table_id,
|
9
|
+
:max_index_id,
|
10
|
+
:max_space_id,
|
11
|
+
:unused_mix_id_low,
|
12
|
+
:indexes,
|
13
|
+
:unused_space,
|
14
|
+
:fseg,
|
15
|
+
keyword_init: true
|
16
|
+
)
|
8
17
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
18
|
+
# The position of the data dictionary header within the page.
|
19
|
+
def pos_data_dictionary_header
|
20
|
+
pos_page_body
|
21
|
+
end
|
13
22
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
:max_row_id => c.name("max_row_id") { c.get_uint64 },
|
19
|
-
:max_table_id => c.name("max_table_id") { c.get_uint64 },
|
20
|
-
:max_index_id => c.name("max_index_id") { c.get_uint64 },
|
21
|
-
:max_space_id => c.name("max_space_id") { c.get_uint32 },
|
22
|
-
:unused_mix_id_low => c.name("unused_mix_id_low") { c.get_uint32 },
|
23
|
-
:indexes => c.name("indexes") {{
|
24
|
-
:SYS_TABLES => c.name("SYS_TABLES") {{
|
25
|
-
:PRIMARY => c.name("PRIMARY") { c.get_uint32 },
|
26
|
-
:ID => c.name("ID") { c.get_uint32 },
|
27
|
-
}},
|
28
|
-
:SYS_COLUMNS => c.name("SYS_COLUMNS") {{
|
29
|
-
:PRIMARY => c.name("PRIMARY") { c.get_uint32 },
|
30
|
-
}},
|
31
|
-
:SYS_INDEXES => c.name("SYS_INDEXES") {{
|
32
|
-
:PRIMARY => c.name("PRIMARY") { c.get_uint32 },
|
33
|
-
}},
|
34
|
-
:SYS_FIELDS => c.name("SYS_FIELDS") {{
|
35
|
-
:PRIMARY => c.name("PRIMARY") { c.get_uint32 },
|
36
|
-
}}
|
37
|
-
}},
|
38
|
-
:unused_space => c.name("unused_space") { c.get_bytes(4) },
|
39
|
-
:fseg => c.name("fseg") { Innodb::FsegEntry.get_inode(@space, c) },
|
40
|
-
}
|
41
|
-
end
|
42
|
-
end
|
23
|
+
# The size of the data dictionary header.
|
24
|
+
def size_data_dictionary_header
|
25
|
+
((8 * 3) + (4 * 7) + 4 + Innodb::FsegEntry::SIZE)
|
26
|
+
end
|
43
27
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
28
|
+
# Parse the data dictionary header from the page.
|
29
|
+
def data_dictionary_header
|
30
|
+
cursor(pos_data_dictionary_header).name("data_dictionary_header") do |c|
|
31
|
+
Header.new(
|
32
|
+
max_row_id: c.name("max_row_id") { c.read_uint64 },
|
33
|
+
max_table_id: c.name("max_table_id") { c.read_uint64 },
|
34
|
+
max_index_id: c.name("max_index_id") { c.read_uint64 },
|
35
|
+
max_space_id: c.name("max_space_id") { c.read_uint32 },
|
36
|
+
unused_mix_id_low: c.name("unused_mix_id_low") { c.read_uint32 },
|
37
|
+
indexes: c.name("indexes") do
|
38
|
+
{
|
39
|
+
SYS_TABLES: c.name("SYS_TABLES") do
|
40
|
+
{
|
41
|
+
PRIMARY: c.name("PRIMARY") { c.read_uint32 },
|
42
|
+
ID: c.name("ID") { c.read_uint32 },
|
43
|
+
}
|
44
|
+
end,
|
45
|
+
SYS_COLUMNS: c.name("SYS_COLUMNS") do
|
46
|
+
{
|
47
|
+
PRIMARY: c.name("PRIMARY") { c.read_uint32 },
|
48
|
+
}
|
49
|
+
end,
|
50
|
+
SYS_INDEXES: c.name("SYS_INDEXES") do
|
51
|
+
{
|
52
|
+
PRIMARY: c.name("PRIMARY") { c.read_uint32 },
|
53
|
+
}
|
54
|
+
end,
|
55
|
+
SYS_FIELDS: c.name("SYS_FIELDS") do
|
56
|
+
{
|
57
|
+
PRIMARY: c.name("PRIMARY") { c.read_uint32 },
|
58
|
+
}
|
59
|
+
end,
|
60
|
+
}
|
61
|
+
end,
|
62
|
+
unused_space: c.name("unused_space") { c.read_bytes(4) },
|
63
|
+
fseg: c.name("fseg") { Innodb::FsegEntry.get_inode(@space, c) }
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
48
67
|
|
49
|
-
|
50
|
-
|
51
|
-
end
|
68
|
+
def each_region(&block)
|
69
|
+
return enum_for(:each_region) unless block_given?
|
52
70
|
|
53
|
-
|
54
|
-
:offset => pos_data_dictionary_header,
|
55
|
-
:length => size_data_dictionary_header,
|
56
|
-
:name => :data_dictionary_header,
|
57
|
-
:info => "Data Dictionary Header",
|
58
|
-
})
|
71
|
+
super(&block)
|
59
72
|
|
60
|
-
|
61
|
-
|
73
|
+
yield Region.new(
|
74
|
+
offset: pos_data_dictionary_header,
|
75
|
+
length: size_data_dictionary_header,
|
76
|
+
name: :data_dictionary_header,
|
77
|
+
info: "Data Dictionary Header"
|
78
|
+
)
|
62
79
|
|
63
|
-
|
64
|
-
|
80
|
+
nil
|
81
|
+
end
|
65
82
|
|
66
|
-
|
67
|
-
|
68
|
-
|
83
|
+
def dump
|
84
|
+
super
|
85
|
+
|
86
|
+
puts
|
87
|
+
puts "data_dictionary header:"
|
88
|
+
pp data_dictionary_header
|
89
|
+
end
|
90
|
+
end
|
69
91
|
end
|
70
92
|
end
|