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