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