innodb_ruby 0.4 → 0.5.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.
- data/bin/innodb_dump_space +177 -21
- data/lib/innodb.rb +4 -0
- data/lib/innodb/cursor.rb +17 -0
- data/lib/innodb/page.rb +70 -245
- data/lib/innodb/space.rb +17 -5
- data/lib/innodb/version.rb +1 -1
- metadata +5 -4
data/bin/innodb_dump_space
CHANGED
@@ -3,31 +3,187 @@
|
|
3
3
|
require "pp"
|
4
4
|
require "innodb"
|
5
5
|
|
6
|
-
|
6
|
+
class EdgesRecordFormatter
|
7
|
+
def format(page)
|
8
|
+
bytes_per_record = (page.record_space / page.page_header[:n_recs])
|
9
|
+
case
|
10
|
+
when [48, 26].include?(bytes_per_record) # Clustered Key
|
11
|
+
{
|
12
|
+
# PRIMARY KEY (source_id, state, position)
|
13
|
+
:type => :clustered,
|
14
|
+
:key => [
|
15
|
+
[:get_uint64], # source_id
|
16
|
+
[:get_i_sint8], # state
|
17
|
+
[:get_i_sint64], # position
|
18
|
+
],
|
19
|
+
:row => [
|
20
|
+
[:get_uint32], # updated_at
|
21
|
+
[:get_uint64], # destination_id
|
22
|
+
[:get_uint8], # count
|
23
|
+
],
|
24
|
+
}
|
25
|
+
when [30, 34].include?(bytes_per_record) # Secondary Key
|
26
|
+
{
|
27
|
+
# INDEX (source_id, destination_id)
|
28
|
+
:type => :secondary,
|
29
|
+
:key => [
|
30
|
+
[:get_uint64], # source_id
|
31
|
+
[:get_uint64], # destination_id
|
32
|
+
],
|
33
|
+
# PKV ([source_id], state, position)
|
34
|
+
:row => [
|
35
|
+
[:get_i_sint8], # state
|
36
|
+
[:get_i_sint64], # position
|
37
|
+
],
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
7
42
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
43
|
+
class PageSplitTestFormatter
|
44
|
+
def format(page)
|
45
|
+
{
|
46
|
+
# PRIMARY KEY (id)
|
47
|
+
:type => :clustered,
|
48
|
+
:key => [
|
49
|
+
[:get_uint64], # id
|
50
|
+
],
|
51
|
+
:row => [],
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def digraph_index(index)
|
57
|
+
puts "digraph btree {"
|
58
|
+
puts " rankdir = LR;"
|
59
|
+
puts " ranksep = 2.0;"
|
60
|
+
index.recurse(
|
61
|
+
lambda do |page, depth|
|
62
|
+
if true #page.level != 0
|
63
|
+
label = "<page>Page %i|(%i records)" % [
|
64
|
+
page.offset,
|
65
|
+
page.page_header[:n_recs],
|
66
|
+
]
|
67
|
+
page.each_child_page do |child_page_number, child_key|
|
68
|
+
label += "|<dir_%i>(%s)" % [
|
69
|
+
child_page_number,
|
70
|
+
child_key.join(", "),
|
71
|
+
]
|
72
|
+
end
|
73
|
+
puts " %spage_%i [ shape = \"record\"; label = \"%s\"; ];" % [
|
74
|
+
" " * depth,
|
75
|
+
page.offset,
|
76
|
+
label,
|
77
|
+
]
|
78
|
+
end
|
79
|
+
end,
|
80
|
+
lambda do |parent_page, child_page, child_key, depth|
|
81
|
+
if true #child_page.level > 0
|
82
|
+
puts " %spage_%i:dir_%i -> page_%i:page:nw;" % [
|
83
|
+
" " * depth,
|
84
|
+
parent_page.offset,
|
85
|
+
child_page.offset,
|
86
|
+
child_page.offset,
|
87
|
+
]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
)
|
91
|
+
puts "}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def recurse_index(index)
|
95
|
+
index.recurse(
|
96
|
+
lambda do |page, depth|
|
97
|
+
puts "%s%s %i: %i records, %i bytes" % [
|
98
|
+
" " * depth,
|
99
|
+
page.level == 0 ? "LEAF" : "NODE",
|
100
|
+
page.offset,
|
101
|
+
page.page_header[:n_recs],
|
102
|
+
page.record_space,
|
103
|
+
]
|
104
|
+
if page.level == 0
|
105
|
+
page.each_record do |record|
|
106
|
+
puts "%sRECORD: (%s) -> (%s)" % [
|
107
|
+
" " * (depth+1),
|
108
|
+
record[:key].join(", "),
|
109
|
+
record[:row].join(", "),
|
110
|
+
]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end,
|
114
|
+
lambda do |parent_page, child_page, child_min_key, depth|
|
115
|
+
puts "%sLINK %i -> %i: %s records >= (%s)" % [
|
116
|
+
" " * depth,
|
117
|
+
parent_page.offset,
|
118
|
+
child_page.offset,
|
119
|
+
child_page.page_header[:n_recs],
|
120
|
+
child_min_key.join(", "),
|
121
|
+
]
|
122
|
+
end
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
def dump_space(space)
|
127
|
+
puts "%-8s%-8s%-8s%-8s%-8s%-8s" % [
|
128
|
+
"page",
|
129
|
+
"index",
|
130
|
+
"level",
|
131
|
+
"data",
|
132
|
+
"free",
|
133
|
+
"records",
|
134
|
+
]
|
135
|
+
|
136
|
+
space.each_page do |page_number, page|
|
137
|
+
case page.type
|
138
|
+
when :INDEX
|
139
|
+
puts "%-8i%-8i%-8i%-8i%-8i%-8i" % [
|
140
|
+
page_number,
|
141
|
+
page.ph[:index_id],
|
142
|
+
page.ph[:level],
|
143
|
+
page.record_space,
|
144
|
+
page.free_space,
|
145
|
+
page.ph[:n_recs],
|
146
|
+
]
|
147
|
+
when :ALLOCATED
|
148
|
+
puts "%-8i%-8i%-8i%-8i%-8i%-8i" % [ page_number, 0, 0, 0, Innodb::Space::PAGE_SIZE, 0 ]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def dump_index_level(index, level)
|
154
|
+
puts "%-8s%-8s%-8s%-8s%-8s%-8s%-8s" % [
|
155
|
+
"page",
|
156
|
+
"index",
|
157
|
+
"level",
|
158
|
+
"data",
|
159
|
+
"free",
|
160
|
+
"records",
|
161
|
+
"min_key",
|
162
|
+
]
|
163
|
+
|
164
|
+
index.each_page_at_level(level) do |page|
|
165
|
+
puts "%-8i%-8i%-8i%-8i%-8i%-8i%s" % [
|
166
|
+
page.offset,
|
21
167
|
page.ph[:index_id],
|
22
168
|
page.ph[:level],
|
23
169
|
page.record_space,
|
170
|
+
page.free_space,
|
171
|
+
page.ph[:n_recs],
|
172
|
+
page.first_record[:key].join("|"),
|
24
173
|
]
|
25
|
-
#page_levels[page.ph[:index_id]] ||= Hash.new(0)
|
26
|
-
#page_levels[page.ph[:index_id]][page.ph[:level]] += 1
|
27
|
-
#records_per_level[page.ph[:level]] ||= Hash.new(0)
|
28
|
-
#records_per_level[page.ph[:level]][page.ph[:n_recs]] += 1
|
29
|
-
end
|
30
|
-
if page.type == :ALLOCATED
|
31
|
-
puts "%-8i%-8i%-8i%-8i" % [ page_number, 0, 0, 0 ]
|
32
174
|
end
|
33
175
|
end
|
176
|
+
|
177
|
+
file, page_number = ARGV.shift(2)
|
178
|
+
|
179
|
+
formatter = EdgesRecordFormatter.new
|
180
|
+
#formatter = PageSplitTestFormatter.new
|
181
|
+
space = Innodb::Space.new(file)
|
182
|
+
space.record_formatter = formatter
|
183
|
+
space.page(page_number).dump
|
184
|
+
#space.each_page { |page_number, page| puts "%6i%20s" % [page_number, page.type] }
|
185
|
+
#dump_space(space)
|
186
|
+
#dump_index_level(Innodb::Index.new(space, page_number), 1)
|
187
|
+
#recurse_index(Innodb::Index.new(space, page_number))
|
188
|
+
#digraph_index(Innodb::Index.new(space, page_number))
|
189
|
+
#Innodb::Index.new(space, page_number).each_record { |record| puts "(#{record[:key].join(", ")}) -> (#{record[:row].join(", ")})" }
|
data/lib/innodb.rb
CHANGED
@@ -3,6 +3,10 @@ module Innodb; end
|
|
3
3
|
|
4
4
|
require "innodb/version"
|
5
5
|
require "innodb/page"
|
6
|
+
require "innodb/page/fsp_hdr_xdes"
|
7
|
+
require "innodb/page/inode"
|
8
|
+
require "innodb/page/index"
|
6
9
|
require "innodb/space"
|
10
|
+
require "innodb/index"
|
7
11
|
require "innodb/log_block"
|
8
12
|
require "innodb/log"
|
data/lib/innodb/cursor.rb
CHANGED
@@ -82,6 +82,11 @@ class Innodb::Cursor
|
|
82
82
|
read_and_advance(length)
|
83
83
|
end
|
84
84
|
|
85
|
+
# Return raw bytes as hex.
|
86
|
+
def get_hex(length)
|
87
|
+
read_and_advance(length).bytes.map { |c| "%02x" % c }.join
|
88
|
+
end
|
89
|
+
|
85
90
|
# Return a big-endian unsigned 8-bit integer.
|
86
91
|
def get_uint8(offset=nil)
|
87
92
|
seek(offset)
|
@@ -145,4 +150,16 @@ class Innodb::Cursor
|
|
145
150
|
raise "Invalid flag #{flag.to_s(16)} seen"
|
146
151
|
end
|
147
152
|
end
|
153
|
+
|
154
|
+
# Return an InnoDB-munged signed 8-bit integer. (This is only implemented
|
155
|
+
# for positive integers at the moment.)
|
156
|
+
def get_i_sint8
|
157
|
+
get_uint8 ^ (1 << 7)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Return an InnoDB-munged signed 64-bit integer. (This is only implemented
|
161
|
+
# for positive integers at the moment.)
|
162
|
+
def get_i_sint64
|
163
|
+
get_uint64 ^ (1 << 63)
|
164
|
+
end
|
148
165
|
end
|
data/lib/innodb/page.rb
CHANGED
@@ -1,36 +1,33 @@
|
|
1
1
|
require "innodb/cursor"
|
2
2
|
|
3
3
|
class Innodb::Page
|
4
|
-
|
5
|
-
PAGE_SIZE = 16384
|
4
|
+
SPECIALIZED_CLASSES = {}
|
6
5
|
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
12 => :ZBLOB2, # Subsequent compressed BLOB page
|
21
|
-
17855 => :INDEX, # B-tree node
|
22
|
-
}
|
6
|
+
# Load a page as a generic page in order to make the "fil" header accessible,
|
7
|
+
# and then attempt to hand off the page to a specialized class to be
|
8
|
+
# re-parsed if possible. If there is no specialized class for this type
|
9
|
+
# of page, return the generic object.
|
10
|
+
def self.parse(buffer)
|
11
|
+
page = Innodb::Page.new(buffer)
|
12
|
+
|
13
|
+
if specialized_class = SPECIALIZED_CLASSES[page.type]
|
14
|
+
page = specialized_class.new(buffer)
|
15
|
+
end
|
16
|
+
|
17
|
+
page
|
18
|
+
end
|
23
19
|
|
24
20
|
# Initialize a page by passing in a 16kB buffer containing the raw page
|
25
21
|
# contents. Currently only 16kB pages are supported.
|
26
22
|
def initialize(buffer)
|
27
|
-
unless buffer.size == PAGE_SIZE
|
28
|
-
raise "Page buffer provided was not #{PAGE_SIZE} bytes"
|
29
|
-
end
|
30
|
-
|
31
23
|
@buffer = buffer
|
32
24
|
end
|
33
25
|
|
26
|
+
# Return the page size, to eventually be able to deal with non-16kB pages.
|
27
|
+
def size
|
28
|
+
@size ||= @buffer.size
|
29
|
+
end
|
30
|
+
|
34
31
|
# A helper function to return bytes from the page buffer based on offset
|
35
32
|
# and length, both in bytes.
|
36
33
|
def data(offset, length)
|
@@ -42,23 +39,59 @@ class Innodb::Page
|
|
42
39
|
Innodb::Cursor.new(self, offset)
|
43
40
|
end
|
44
41
|
|
45
|
-
|
46
|
-
|
42
|
+
# Return the byte offset of the start of the "fil" header, which is at the
|
43
|
+
# beginning of the page. Included here primarily for completeness.
|
44
|
+
def pos_fil_header
|
45
|
+
0
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return the size of the "fil" header, in bytes.
|
49
|
+
def size_fil_header
|
50
|
+
38
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return the byte offset of the start of the "fil" trailer, which is at
|
54
|
+
# the end of the page.
|
55
|
+
def pos_fil_trailer
|
56
|
+
size - size_fil_trailer
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return the size of the "fil" trailer, in bytes.
|
60
|
+
def size_fil_trailer
|
61
|
+
8
|
62
|
+
end
|
63
|
+
|
64
|
+
# InnoDB Page Type constants from include/fil0fil.h.
|
65
|
+
PAGE_TYPE = {
|
66
|
+
0 => :ALLOCATED, # Freshly allocated page
|
67
|
+
2 => :UNDO_LOG, # Undo log page
|
68
|
+
3 => :INODE, # Index node
|
69
|
+
4 => :IBUF_FREE_LIST, # Insert buffer free list
|
70
|
+
5 => :IBUF_BITMAP, # Insert buffer bitmap
|
71
|
+
6 => :SYS, # System page
|
72
|
+
7 => :TRX_SYS, # Transaction system data
|
73
|
+
8 => :FSP_HDR, # File space header
|
74
|
+
9 => :XDES, # Extent descriptor page
|
75
|
+
10 => :BLOB, # Uncompressed BLOB page
|
76
|
+
11 => :ZBLOB, # First compressed BLOB page
|
77
|
+
12 => :ZBLOB2, # Subsequent compressed BLOB page
|
78
|
+
17855 => :INDEX, # B-tree node
|
79
|
+
}
|
47
80
|
|
48
81
|
# A helper to convert "undefined" values stored in previous and next pointers
|
49
82
|
# in the page header to nil.
|
50
|
-
def maybe_undefined(value)
|
83
|
+
def self.maybe_undefined(value)
|
51
84
|
value == 4294967295 ? nil : value
|
52
85
|
end
|
53
86
|
|
54
87
|
# Return the "fil" header from the page, which is common for all page types.
|
55
88
|
def fil_header
|
56
|
-
c = cursor(
|
89
|
+
c = cursor(pos_fil_header)
|
57
90
|
@fil_header ||= {
|
58
91
|
:checksum => c.get_uint32,
|
59
92
|
:offset => c.get_uint32,
|
60
|
-
:prev => maybe_undefined(c.get_uint32),
|
61
|
-
:next => maybe_undefined(c.get_uint32),
|
93
|
+
:prev => Innodb::Page.maybe_undefined(c.get_uint32),
|
94
|
+
:next => Innodb::Page.maybe_undefined(c.get_uint32),
|
62
95
|
:lsn => c.get_uint64,
|
63
96
|
:type => PAGE_TYPE[c.get_uint16],
|
64
97
|
:flush_lsn => c.get_uint64,
|
@@ -73,6 +106,12 @@ class Innodb::Page
|
|
73
106
|
fil_header[:type]
|
74
107
|
end
|
75
108
|
|
109
|
+
# A helper function to return the page offset from the "fil" header, for
|
110
|
+
# easier access.
|
111
|
+
def offset
|
112
|
+
fil_header[:offset]
|
113
|
+
end
|
114
|
+
|
76
115
|
# A helper function to return the page number of the logical previous page
|
77
116
|
# (from the doubly-linked list from page to page) from the "fil" header,
|
78
117
|
# for easier access.
|
@@ -87,227 +126,13 @@ class Innodb::Page
|
|
87
126
|
fil_header[:next]
|
88
127
|
end
|
89
128
|
|
90
|
-
PAGE_HEADER_SIZE = 36
|
91
|
-
PAGE_HEADER_START = FIL_HEADER_START + FIL_HEADER_SIZE
|
92
|
-
|
93
|
-
PAGE_TRAILER_SIZE = 16
|
94
|
-
PAGE_TRAILER_START = PAGE_SIZE - PAGE_TRAILER_SIZE
|
95
|
-
|
96
|
-
FSEG_HEADER_SIZE = 10
|
97
|
-
FSEG_HEADER_START = PAGE_HEADER_START + PAGE_HEADER_SIZE
|
98
|
-
FSEG_HEADER_COUNT = 2
|
99
|
-
|
100
|
-
MUM_RECORD_SIZE = 8
|
101
|
-
|
102
|
-
RECORD_BITS_SIZE = 3
|
103
|
-
RECORD_NEXT_SIZE = 2
|
104
|
-
|
105
|
-
# Page direction values possible in the page_header[:direction] field.
|
106
|
-
PAGE_DIRECTION = {
|
107
|
-
1 => :left,
|
108
|
-
2 => :right,
|
109
|
-
3 => :same_rec,
|
110
|
-
4 => :same_page,
|
111
|
-
5 => :no_direction,
|
112
|
-
}
|
113
|
-
|
114
|
-
# Return the size of the header for each record.
|
115
|
-
def size_record_header
|
116
|
-
case page_header[:format]
|
117
|
-
when :compact
|
118
|
-
RECORD_BITS_SIZE + RECORD_NEXT_SIZE
|
119
|
-
when :redundant
|
120
|
-
RECORD_BITS_SIZE + RECORD_NEXT_SIZE + 1
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# Return the size of a field in the record header for which no description
|
125
|
-
# could be found (but must be skipped anyway).
|
126
|
-
def size_record_undefined
|
127
|
-
case page_header[:format]
|
128
|
-
when :compact
|
129
|
-
0
|
130
|
-
when :redundant
|
131
|
-
1
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# Return the "page" header; currently only "INDEX" pages are supported.
|
136
|
-
def page_header
|
137
|
-
return nil unless type == :INDEX
|
138
|
-
|
139
|
-
c = cursor(PAGE_HEADER_START)
|
140
|
-
@page_header ||= {
|
141
|
-
:n_dir_slots => c.get_uint16,
|
142
|
-
:heap_top => c.get_uint16,
|
143
|
-
:n_heap => ((n_heap = c.get_uint16) & (2**15-1)),
|
144
|
-
:free => c.get_uint16,
|
145
|
-
:garbage => c.get_uint16,
|
146
|
-
:last_insert => c.get_uint16,
|
147
|
-
:direction => PAGE_DIRECTION[c.get_uint16],
|
148
|
-
:n_direction => c.get_uint16,
|
149
|
-
:n_recs => c.get_uint16,
|
150
|
-
:max_trx_id => c.get_uint64,
|
151
|
-
:level => c.get_uint16,
|
152
|
-
:index_id => c.get_uint64,
|
153
|
-
:format => (n_heap & 1<<15) == 0 ? :redundant : :compact,
|
154
|
-
}
|
155
|
-
end
|
156
|
-
alias :ph :page_header
|
157
|
-
|
158
|
-
# Parse and return simple fixed-format system records, such as InnoDB's
|
159
|
-
# internal infimum and supremum records.
|
160
|
-
def system_record(offset)
|
161
|
-
return nil unless type == :INDEX
|
162
|
-
|
163
|
-
c = cursor(offset)
|
164
|
-
c.adjust(-2)
|
165
|
-
{
|
166
|
-
:next => offset + c.get_sint16,
|
167
|
-
:data => c.get_bytes(8),
|
168
|
-
}
|
169
|
-
end
|
170
|
-
|
171
|
-
# Return the byte offset of the start of the "origin" of the infimum record,
|
172
|
-
# which is always the first record in the singly-linked record chain on any
|
173
|
-
# page, and represents a record with a "lower value than any possible user
|
174
|
-
# record". The infimum record immediately follows the page header.
|
175
|
-
def pos_infimum
|
176
|
-
pos_records + size_record_header + size_record_undefined
|
177
|
-
end
|
178
|
-
|
179
|
-
# Return the infimum record on a page.
|
180
|
-
def infimum
|
181
|
-
@infimum ||= system_record(pos_infimum)
|
182
|
-
end
|
183
|
-
|
184
|
-
# Return the byte offset of the start of the "origin" of the supremum record,
|
185
|
-
# which is always the last record in the singly-linked record chain on any
|
186
|
-
# page, and represents a record with a "higher value than any possible user
|
187
|
-
# record". The supremum record immediately follows the infimum record.
|
188
|
-
def pos_supremum
|
189
|
-
pos_infimum + size_record_header + size_record_undefined + MUM_RECORD_SIZE
|
190
|
-
end
|
191
|
-
|
192
|
-
# Return the supremum record on a page.
|
193
|
-
def supremum
|
194
|
-
@supremum ||= system_record(pos_supremum)
|
195
|
-
end
|
196
|
-
|
197
|
-
# Return the byte offset of the start of records within the page (the
|
198
|
-
# position immediately after the page header).
|
199
|
-
def pos_records
|
200
|
-
FIL_HEADER_SIZE +
|
201
|
-
PAGE_HEADER_SIZE +
|
202
|
-
(FSEG_HEADER_COUNT * FSEG_HEADER_SIZE)
|
203
|
-
end
|
204
|
-
|
205
|
-
# Return the byte offset of the start of the user records in a page, which
|
206
|
-
# immediately follows the supremum record.
|
207
|
-
def pos_user_records
|
208
|
-
pos_supremum + size_record_header + size_record_undefined + MUM_RECORD_SIZE
|
209
|
-
end
|
210
|
-
|
211
|
-
# Return the amount of free space in the page.
|
212
|
-
def free_space
|
213
|
-
unused_space = (PAGE_TRAILER_START - page_header[:heap_top])
|
214
|
-
unused_space + page_header[:garbage]
|
215
|
-
end
|
216
|
-
|
217
|
-
# Return the amount of used space in the page.
|
218
|
-
def used_space
|
219
|
-
PAGE_SIZE - free_space
|
220
|
-
end
|
221
|
-
|
222
|
-
# Return the amount of space occupied by records in the page.
|
223
|
-
def record_space
|
224
|
-
used_space - pos_user_records
|
225
|
-
end
|
226
|
-
|
227
|
-
# Return the actual bytes of the portion of the page which is used to
|
228
|
-
# store user records (eliminate the headers and trailer from the page).
|
229
|
-
def record_bytes
|
230
|
-
data(pos_user_records, page_header[:heap_top] - pos_user_records)
|
231
|
-
end
|
232
|
-
|
233
|
-
# Return the header from a record. (This is mostly unimplemented.)
|
234
|
-
def record_header(offset)
|
235
|
-
return nil unless type == :INDEX
|
236
|
-
|
237
|
-
c = cursor(offset).backward
|
238
|
-
case page_header[:format]
|
239
|
-
when :compact
|
240
|
-
header = {}
|
241
|
-
header[:next] = c.get_sint16
|
242
|
-
bits1 = c.get_uint16
|
243
|
-
header[:type] = bits1 & 0x07
|
244
|
-
header[:order] = (bits1 & 0xf8) >> 3
|
245
|
-
bits2 = c.get_uint8
|
246
|
-
header[:n_owned] = bits2 & 0x0f
|
247
|
-
header[:deleted] = (bits2 & 0xf0) >> 4
|
248
|
-
header
|
249
|
-
when :redundant
|
250
|
-
raise "Not implemented"
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
# Return a record. (This is mostly unimplemented.)
|
255
|
-
def record(offset)
|
256
|
-
return nil unless offset
|
257
|
-
return nil unless type == :INDEX
|
258
|
-
return nil if offset == pos_infimum
|
259
|
-
return nil if offset == pos_supremum
|
260
|
-
|
261
|
-
c = cursor(offset).forward
|
262
|
-
# There is a header preceding the row itself, so back up and read it.
|
263
|
-
header = record_header(offset)
|
264
|
-
{
|
265
|
-
:header => header,
|
266
|
-
:next => header[:next] == 0 ? nil : (offset + header[:next]),
|
267
|
-
# These system records may not be present depending on schema.
|
268
|
-
:rec1 => c.get_bytes(6),
|
269
|
-
:rec2 => c.get_bytes(6),
|
270
|
-
:rec3 => c.get_bytes(7),
|
271
|
-
# Read a few bytes just so it can be visually verified.
|
272
|
-
:data => c.get_bytes(8),
|
273
|
-
}
|
274
|
-
end
|
275
|
-
|
276
|
-
# Iterate through all records. (This is mostly unimplemented.)
|
277
|
-
def each_record
|
278
|
-
rec = infimum
|
279
|
-
while rec = record(rec[:next])
|
280
|
-
yield rec
|
281
|
-
end
|
282
|
-
nil
|
283
|
-
end
|
284
|
-
|
285
129
|
# Dump the contents of a page for debugging purposes.
|
286
130
|
def dump
|
287
131
|
puts
|
288
|
-
puts "
|
289
|
-
pp fil_header
|
132
|
+
puts "#{self}:"
|
290
133
|
|
291
134
|
puts
|
292
|
-
puts "
|
293
|
-
pp
|
294
|
-
|
295
|
-
puts
|
296
|
-
puts "free space: #{free_space}"
|
297
|
-
puts "used space: #{used_space}"
|
298
|
-
puts "record space: #{record_space}"
|
299
|
-
|
300
|
-
if type == :INDEX
|
301
|
-
puts
|
302
|
-
puts "system records:"
|
303
|
-
pp infimum
|
304
|
-
pp supremum
|
305
|
-
|
306
|
-
puts
|
307
|
-
puts "records:"
|
308
|
-
each_record do |rec|
|
309
|
-
pp rec
|
310
|
-
end
|
311
|
-
end
|
135
|
+
puts "fil header:"
|
136
|
+
pp fil_header
|
312
137
|
end
|
313
138
|
end
|
data/lib/innodb/space.rb
CHANGED
@@ -1,21 +1,33 @@
|
|
1
1
|
# An InnoDB tablespace file, which can be either a multi-table ibdataN file
|
2
2
|
# or a single-table "innodb_file_per_table" .ibd file.
|
3
3
|
class Innodb::Space
|
4
|
+
attr_accessor :record_formatter
|
5
|
+
|
6
|
+
# Currently only 16kB InnoDB pages are supported.
|
7
|
+
PAGE_SIZE = 16384
|
8
|
+
|
4
9
|
# Open a tablespace file.
|
5
10
|
def initialize(file)
|
6
11
|
@file = File.open(file)
|
7
12
|
@size = @file.stat.size
|
8
|
-
@pages = (@size /
|
13
|
+
@pages = (@size / PAGE_SIZE)
|
14
|
+
@record_formatter = nil
|
9
15
|
end
|
10
16
|
|
11
17
|
# Get an Innodb::Page object for a specific page by page number.
|
12
18
|
def page(page_number)
|
13
|
-
offset = page_number.to_i *
|
19
|
+
offset = page_number.to_i * PAGE_SIZE
|
14
20
|
return nil unless offset < @size
|
15
|
-
return nil unless (offset +
|
21
|
+
return nil unless (offset + PAGE_SIZE) <= @size
|
16
22
|
@file.seek(offset)
|
17
|
-
page_data = @file.read(
|
18
|
-
Innodb::Page.
|
23
|
+
page_data = @file.read(PAGE_SIZE)
|
24
|
+
this_page = Innodb::Page.parse(page_data)
|
25
|
+
|
26
|
+
if this_page.type == :INDEX
|
27
|
+
this_page.record_formatter = @record_formatter
|
28
|
+
end
|
29
|
+
|
30
|
+
this_page
|
19
31
|
end
|
20
32
|
|
21
33
|
# Iterate through all pages in a tablespace, returning the page number
|
data/lib/innodb/version.rb
CHANGED
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: innodb_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
|
8
|
+
- 5
|
9
|
+
- 0
|
10
|
+
version: 0.5.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Jeremy Cole
|
@@ -14,7 +15,7 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2012-11-
|
18
|
+
date: 2012-11-29 00:00:00 Z
|
18
19
|
dependencies: []
|
19
20
|
|
20
21
|
description: Library for parsing InnoDB data files in Ruby
|