innodb_ruby 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ class Innodb::FreeList
2
+ FIL_ADDR_SIZE = 4 + 2
3
+ NODE_SIZE = 2 * FIL_ADDR_SIZE
4
+ BASE_NODE_SIZE = 4 + (2 * FIL_ADDR_SIZE)
5
+
6
+ def self.get_address(cursor)
7
+ {
8
+ :page => Innodb::Page.maybe_undefined(cursor.get_uint32),
9
+ :offset => cursor.get_uint16,
10
+ }
11
+ end
12
+
13
+ def self.get_node(cursor)
14
+ {
15
+ :prev => Innodb::FreeList::get_address(cursor),
16
+ :next => Innodb::FreeList::get_address(cursor),
17
+ }
18
+ end
19
+
20
+ def self.get_base_node(cursor)
21
+ {
22
+ :length => cursor.get_uint32,
23
+ :first => Innodb::FreeList::get_address(cursor),
24
+ :last => Innodb::FreeList::get_address(cursor),
25
+ }
26
+ end
27
+ end
@@ -0,0 +1,85 @@
1
+ # An InnoDB index B-tree, given an Innodb::Space and a root page number.
2
+ class Innodb::Index
3
+ def initialize(space, root_page_number)
4
+ @space = space
5
+ @root = @space.page(root_page_number)
6
+
7
+ unless @root
8
+ raise "Page #{root_page_number} couldn't be read"
9
+ end
10
+
11
+ # The root page should be an index page.
12
+ unless @root.type == :INDEX
13
+ raise "Page #{root_page_number} is a #{@root.type} page, not an INDEX page"
14
+ end
15
+
16
+ # The root page should not be a leaf page.
17
+ unless @root.level > 0
18
+ raise "Page #{root_page_number} is a leaf page"
19
+ end
20
+
21
+ # The root page should be the only page at its level.
22
+ unless @root.prev.nil? && @root.next.nil?
23
+ raise "Page #{root_page_number} is a node page, but not appear to be the root; it has previous page and next page pointers"
24
+ end
25
+ end
26
+
27
+ # Internal method used by recurse.
28
+ def _recurse(parent_page, page_proc, link_proc, depth=0)
29
+ if page_proc && parent_page.type == :INDEX
30
+ page_proc.call(parent_page, depth)
31
+ end
32
+
33
+ parent_page.each_child_page do |child_page_number, child_min_key|
34
+ child_page = @space.page(child_page_number)
35
+ child_page.record_formatter = @space.record_formatter
36
+ if child_page.type == :INDEX
37
+ if link_proc
38
+ link_proc.call(parent_page, child_page, child_min_key, depth+1)
39
+ end
40
+ _recurse(child_page, page_proc, link_proc, depth+1)
41
+ end
42
+ end
43
+ end
44
+
45
+ # Walk an index tree depth-first, calling procs for each page and link
46
+ # in the tree.
47
+ def recurse(page_proc, link_proc)
48
+ _recurse(@root, page_proc, link_proc)
49
+ end
50
+
51
+ # Return the first leaf page in the index by walking down the left side
52
+ # of the B-tree until a page at the given level is encountered.
53
+ def first_page_at_level(level)
54
+ page = @root
55
+ record = @root.first_record
56
+ while record && page.level > level
57
+ page = @space.page(record[:child_page_number])
58
+ record = page.first_record
59
+ end
60
+ page if page.level == level
61
+ end
62
+
63
+ # Iterate through all pages at this level starting with the provided page.
64
+ def each_page_from(page)
65
+ while page && page.type == :INDEX
66
+ yield page
67
+ page = @space.page(page.next)
68
+ end
69
+ end
70
+
71
+ # Iterate through all pages at the given level by finding the first page
72
+ # and following the next pointers in each page.
73
+ def each_page_at_level(level)
74
+ each_page_from(first_page_at_level(level)) { |page| yield page }
75
+ end
76
+
77
+ # Iterate through all records on all leaf pages in ascending order.
78
+ def each_record
79
+ each_leaf_page do |page|
80
+ page.each_record do |record|
81
+ yield record
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,78 @@
1
+ require "innodb/free_list"
2
+
3
+ class Innodb::Page::FspHdrXdes < Innodb::Page
4
+ XDES_BITS_PER_PAGE = 2
5
+ XDES_BITMAP_SIZE = (64 * XDES_BITS_PER_PAGE) / 8
6
+ XDES_SIZE = 8 + Innodb::FreeList::NODE_SIZE + 4 + XDES_BITMAP_SIZE
7
+
8
+ XDES_N_ARRAY_ENTRIES = 10
9
+
10
+ def pos_fsp_header
11
+ pos_fil_header + size_fil_header
12
+ end
13
+
14
+ def size_fsp_header
15
+ (32 + 5 * Innodb::FreeList::BASE_NODE_SIZE)
16
+ end
17
+
18
+ def pos_xdes_array
19
+ pos_fsp_header + size_fsp_header
20
+ end
21
+
22
+ def fsp_header
23
+ c = cursor(pos_fsp_header)
24
+ @fsp_header ||= {
25
+ :space_id => c.get_uint32,
26
+ :unused => c.get_uint32,
27
+ :size => c.get_uint32,
28
+ :free_limit => c.get_uint32,
29
+ :flags => c.get_uint32,
30
+ :frag_n_used => c.get_uint32,
31
+ :free_frag => Innodb::FreeList::get_base_node(c),
32
+ :full_frag => Innodb::FreeList::get_base_node(c),
33
+ :first_unused_seg => c.get_uint64,
34
+ :full_inodes => Innodb::FreeList::get_base_node(c),
35
+ :free_inodes => Innodb::FreeList::get_base_node(c),
36
+ }
37
+ end
38
+
39
+ XDES_STATES = {
40
+ 1 => :free,
41
+ 2 => :free_frag,
42
+ 3 => :full_frag,
43
+ 4 => :fseg,
44
+ }
45
+
46
+ def read_xdes(cursor)
47
+ {
48
+ :xdes_id => cursor.get_uint64,
49
+ :free_list => Innodb::FreeList::get_node(cursor),
50
+ :state => XDES_STATES[cursor.get_uint32],
51
+ :bitmap => cursor.get_bytes(XDES_BITMAP_SIZE),
52
+ }
53
+ end
54
+
55
+ def each_xdes
56
+ c = cursor(pos_xdes_array)
57
+ XDES_N_ARRAY_ENTRIES.times do
58
+ yield read_xdes(c)
59
+ end
60
+ end
61
+
62
+ def dump
63
+ super
64
+
65
+ puts
66
+ puts "fsp header:"
67
+ pp fsp_header
68
+
69
+ puts
70
+ puts "xdes entries:"
71
+ each_xdes do |xdes|
72
+ pp xdes
73
+ end
74
+ end
75
+ end
76
+
77
+ Innodb::Page::SPECIALIZED_CLASSES[:FSP_HDR] = Innodb::Page::FspHdrXdes
78
+ Innodb::Page::SPECIALIZED_CLASSES[:XDES] = Innodb::Page::FspHdrXdes
@@ -0,0 +1,351 @@
1
+ class Innodb::Page::Index < Innodb::Page
2
+ attr_accessor :record_formatter
3
+
4
+ # Return the byte offset of the start of the "index" page header, which
5
+ # immediately follows the "fil" header.
6
+ def pos_index_header
7
+ pos_fil_header + size_fil_header
8
+ end
9
+
10
+ # The size of the "index" header.
11
+ def size_index_header
12
+ 36
13
+ end
14
+
15
+ # Return the byte offset of the start of the "fseg" header, which immediately
16
+ # follows the "index" header.
17
+ def pos_fseg_header
18
+ pos_index_header + size_index_header
19
+ end
20
+
21
+ # The size of the "fseg" header.
22
+ def size_fseg_header
23
+ 2 * 10
24
+ end
25
+
26
+ # Return the byte offset of the start of records within the page (the
27
+ # position immediately after the page header).
28
+ def pos_records
29
+ size_fil_header + size_index_header + size_fseg_header
30
+ end
31
+
32
+ # The size of the data from the supremum or infimum records.
33
+ def size_mum_record
34
+ 8
35
+ end
36
+
37
+ # Return the byte offset of the start of the "origin" of the infimum record,
38
+ # which is always the first record in the singly-linked record chain on any
39
+ # page, and represents a record with a "lower value than any possible user
40
+ # record". The infimum record immediately follows the page header.
41
+ def pos_infimum
42
+ pos_records + size_record_header + size_record_undefined
43
+ end
44
+
45
+ # Return the byte offset of the start of the "origin" of the supremum record,
46
+ # which is always the last record in the singly-linked record chain on any
47
+ # page, and represents a record with a "higher value than any possible user
48
+ # record". The supremum record immediately follows the infimum record.
49
+ def pos_supremum
50
+ pos_infimum + size_record_header + size_record_undefined + size_mum_record
51
+ end
52
+
53
+ # Return the byte offset of the start of the user records in a page, which
54
+ # immediately follows the supremum record.
55
+ def pos_user_records
56
+ pos_supremum + size_mum_record
57
+ end
58
+
59
+ # The position of the page directory, which starts at the "fil" trailer and
60
+ # grows backwards from there.
61
+ def pos_directory
62
+ pos_fil_trailer
63
+ end
64
+
65
+ # The amount of space consumed by the page header.
66
+ def header_space
67
+ # The end of the supremum system record is the beginning of the space
68
+ # available for user records.
69
+ pos_user_records
70
+ end
71
+
72
+ # The amount of space consumed by the page directory.
73
+ def directory_space
74
+ page_header[:n_dir_slots] * PAGE_DIR_SLOT_SIZE
75
+ end
76
+
77
+ # The amount of space consumed by the trailers in the page.
78
+ def trailer_space
79
+ size_fil_trailer
80
+ end
81
+
82
+ # Return the amount of free space in the page.
83
+ def free_space
84
+ page_header[:garbage] +
85
+ (size - size_fil_trailer - directory_space - page_header[:heap_top])
86
+ end
87
+
88
+ # Return the amount of used space in the page.
89
+ def used_space
90
+ size - free_space
91
+ end
92
+
93
+ # Return the amount of space occupied by records in the page.
94
+ def record_space
95
+ used_space - header_space - directory_space - trailer_space
96
+ end
97
+
98
+ # Return the actual bytes of the portion of the page which is used to
99
+ # store user records (eliminate the headers and trailer from the page).
100
+ def record_bytes
101
+ data(pos_user_records, page_header[:heap_top] - pos_user_records)
102
+ end
103
+
104
+ # Page direction values possible in the page_header[:direction] field.
105
+ PAGE_DIRECTION = {
106
+ 1 => :left,
107
+ 2 => :right,
108
+ 3 => :same_rec,
109
+ 4 => :same_page,
110
+ 5 => :no_direction,
111
+ }
112
+
113
+ # Return the "index" header.
114
+ def page_header
115
+ return nil unless type == :INDEX
116
+
117
+ c = cursor(pos_index_header)
118
+ @page_header ||= {
119
+ :n_dir_slots => c.get_uint16,
120
+ :heap_top => c.get_uint16,
121
+ :n_heap => ((n_heap = c.get_uint16) & (2**15-1)),
122
+ :free => c.get_uint16,
123
+ :garbage => c.get_uint16,
124
+ :last_insert => c.get_uint16,
125
+ :direction => PAGE_DIRECTION[c.get_uint16],
126
+ :n_direction => c.get_uint16,
127
+ :n_recs => c.get_uint16,
128
+ :max_trx_id => c.get_uint64,
129
+ :level => c.get_uint16,
130
+ :index_id => c.get_uint64,
131
+ :format => (n_heap & 1<<15) == 0 ? :redundant : :compact,
132
+ }
133
+ end
134
+ alias :ph :page_header
135
+
136
+ # A helper function to return the page level from the "page" header, for
137
+ # easier access.
138
+ def level
139
+ page_header && page_header[:level]
140
+ end
141
+
142
+ RECORD_BITS_SIZE = 3
143
+ RECORD_NEXT_SIZE = 2
144
+
145
+ PAGE_DIR_SLOT_SIZE = 2
146
+ PAGE_DIR_SLOT_MIN_N_OWNED = 4
147
+ PAGE_DIR_SLOT_MAX_N_OWNED = 8
148
+
149
+ # Return the size of the header for each record.
150
+ def size_record_header
151
+ case page_header[:format]
152
+ when :compact
153
+ RECORD_BITS_SIZE + RECORD_NEXT_SIZE
154
+ when :redundant
155
+ RECORD_BITS_SIZE + RECORD_NEXT_SIZE + 1
156
+ end
157
+ end
158
+
159
+ # Return the size of a field in the record header for which no description
160
+ # could be found (but must be skipped anyway).
161
+ def size_record_undefined
162
+ case page_header[:format]
163
+ when :compact
164
+ 0
165
+ when :redundant
166
+ 1
167
+ end
168
+ end
169
+
170
+ # Record types used in the :type field of the record header.
171
+ RECORD_TYPES = {
172
+ 0 => :conventional,
173
+ 1 => :node_pointer,
174
+ 2 => :infimum,
175
+ 3 => :supremum,
176
+ }
177
+
178
+ # This record is the minimum record at this level of the B-tree.
179
+ RECORD_INFO_MIN_REC_FLAG = 1
180
+
181
+ # This record has been marked as deleted.
182
+ RECORD_INFO_DELETED_FLAG = 2
183
+
184
+ # Return the header from a record. (This is mostly unimplemented.)
185
+ def record_header(offset)
186
+ return nil unless type == :INDEX
187
+
188
+ c = cursor(offset).backward
189
+ case page_header[:format]
190
+ when :compact
191
+ header = {}
192
+ header[:next] = c.get_sint16
193
+ bits1 = c.get_uint16
194
+ header[:type] = RECORD_TYPES[bits1 & 0x07]
195
+ header[:order] = (bits1 & 0xf8) >> 3
196
+ bits2 = c.get_uint8
197
+ header[:n_owned] = bits2 & 0x0f
198
+ info = (bits2 & 0xf0) >> 4
199
+ header[:min_rec] = (info & RECORD_INFO_MIN_REC_FLAG) != 0
200
+ header[:deleted] = (info & RECORD_INFO_DELETED_FLAG) != 0
201
+ header
202
+ when :redundant
203
+ raise "Not implemented"
204
+ end
205
+ end
206
+
207
+ # Parse and return simple fixed-format system records, such as InnoDB's
208
+ # internal infimum and supremum records.
209
+ def system_record(offset)
210
+ return nil unless type == :INDEX
211
+
212
+ header = record_header(offset)
213
+ {
214
+ :header => header,
215
+ :next => offset + header[:next],
216
+ :data => cursor(offset).get_bytes(size_mum_record),
217
+ }
218
+ end
219
+
220
+ # Return the infimum record on a page.
221
+ def infimum
222
+ @infimum ||= system_record(pos_infimum)
223
+ end
224
+
225
+ # Return the supremum record on a page.
226
+ def supremum
227
+ @supremum ||= system_record(pos_supremum)
228
+ end
229
+
230
+ # Return (and cache) the record format provided by an external class.
231
+ def record_format
232
+ if record_formatter
233
+ @record_format ||= record_formatter.format(self)
234
+ end
235
+ end
236
+
237
+ # Parse and return a record at a given offset.
238
+ def record(offset)
239
+ return nil unless offset
240
+ return nil unless type == :INDEX
241
+ return nil if offset == pos_infimum
242
+ return nil if offset == pos_supremum
243
+
244
+ c = cursor(offset).forward
245
+
246
+ # There is a header preceding the row itself, so back up and read it.
247
+ header = record_header(offset)
248
+
249
+ this_record = {
250
+ :header => header,
251
+ :next => header[:next] == 0 ? nil : (offset + header[:next]),
252
+ }
253
+
254
+ if record_format
255
+ this_record[:type] = record_format[:type]
256
+
257
+ # Read the key fields present in all types of pages.
258
+ this_record[:key] = []
259
+ record_format[:key].each do |f|
260
+ this_record[:key].push c.send(*f)
261
+ end
262
+
263
+ # If this is a leaf page of the clustered index, read InnoDB's internal
264
+ # fields, a transaction ID and roll pointer.
265
+ if level == 0 && record_format[:type] == :clustered
266
+ this_record[:transaction_id] = c.get_hex(6)
267
+ this_record[:roll_pointer] = c.get_hex(7)
268
+ end
269
+
270
+ # If this is a leaf page of the clustered index, or any page of a
271
+ # secondary index, read the non-key fields.
272
+ if (level == 0 && record_format[:type] == :clustered) ||
273
+ (record_format[:type] == :secondary)
274
+ # Read the non-key fields.
275
+ this_record[:row] = []
276
+ record_format[:row].each do |f|
277
+ this_record[:row].push c.send(*f)
278
+ end
279
+ end
280
+
281
+ # If this is a node (non-leaf) page, it will have a child page number
282
+ # (or "node pointer") stored as the last field.
283
+ if level > 0
284
+ # Read the node pointer in a node (non-leaf) page.
285
+ this_record[:child_page_number] = c.get_uint32
286
+ end
287
+ end
288
+
289
+ this_record
290
+ end
291
+
292
+ # Return the first record on this page.
293
+ def first_record
294
+ record(infimum[:next])
295
+ end
296
+
297
+ # Iterate through all records. (This is mostly unimplemented.)
298
+ def each_record
299
+ rec = infimum
300
+ while rec = record(rec[:next])
301
+ yield rec
302
+ end
303
+ nil
304
+ end
305
+
306
+ # Iterate through all child pages of a node (non-leaf) page, which are
307
+ # stored as records with the child page number as the last field in the
308
+ # record.
309
+ def each_child_page
310
+ return nil if level == 0
311
+ each_record do |rec|
312
+ yield rec[:child_page_number], rec[:key]
313
+ end
314
+ nil
315
+ end
316
+
317
+ # Dump the contents of a page for debugging purposes.
318
+ def dump
319
+ super
320
+
321
+ puts
322
+ puts "page header:"
323
+ pp page_header
324
+
325
+ puts
326
+ puts "sizes:"
327
+ puts " %-15s%5i" % [ "header", header_space ]
328
+ puts " %-15s%5i" % [ "trailer", trailer_space ]
329
+ puts " %-15s%5i" % [ "directory", directory_space ]
330
+ puts " %-15s%5i" % [ "free", free_space ]
331
+ puts " %-15s%5i" % [ "used", used_space ]
332
+ puts " %-15s%5i" % [ "record", record_space ]
333
+ puts " %-15s%5.2f" % [
334
+ "per record",
335
+ (page_header[:n_recs] > 0) ? (record_space / page_header[:n_recs]) : 0
336
+ ]
337
+
338
+ puts
339
+ puts "system records:"
340
+ pp infimum
341
+ pp supremum
342
+
343
+ puts
344
+ puts "records:"
345
+ each_record do |rec|
346
+ pp rec
347
+ end
348
+ end
349
+ end
350
+
351
+ Innodb::Page::SPECIALIZED_CLASSES[:INDEX] = Innodb::Page::Index
@@ -0,0 +1,44 @@
1
+ require "innodb/free_list"
2
+
3
+ class Innodb::Page::Inode < Innodb::Page
4
+ FRAG_ARRAY_N_SLOTS = 32 # FSP_EXTENT_SIZE / 2
5
+ FRAG_SLOT_SIZE = 4
6
+
7
+ MAGIC_N_VALUE = 97937874
8
+
9
+ def pos_inode_header
10
+ pos_fil_header + size_fil_header + Innodb::FreeList::NODE_SIZE
11
+ end
12
+
13
+ def size_inode_header
14
+ (16 + (3 * Innodb::FreeList::BASE_NODE_SIZE) +
15
+ (FRAG_ARRAY_N_SLOTS * FRAG_SLOT_SIZE))
16
+ end
17
+
18
+ def uint32_array(size, cursor)
19
+ size.times.map { |n| cursor.get_uint32 }
20
+ end
21
+
22
+ def inode_header
23
+ c = cursor(pos_inode_header)
24
+ {
25
+ :fseg_id => c.get_uint64,
26
+ :not_full_n_used => c.get_uint32,
27
+ :free => Innodb::FreeList.get_base_node(c),
28
+ :not_full => Innodb::FreeList.get_base_node(c),
29
+ :full => Innodb::FreeList.get_base_node(c),
30
+ :magic_n => c.get_uint32,
31
+ :frag_array => uint32_array(FRAG_ARRAY_N_SLOTS, c),
32
+ }
33
+ end
34
+
35
+ def dump
36
+ super
37
+
38
+ puts
39
+ puts "inode header:"
40
+ pp inode_header
41
+ end
42
+ end
43
+
44
+ Innodb::Page::SPECIALIZED_CLASSES[:INODE] = Innodb::Page::Inode
@@ -1,3 +1,3 @@
1
1
  module Innodb
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.1"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: innodb_ruby
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
4
+ hash: 9
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 5
9
- - 0
10
- version: 0.5.0
9
+ - 1
10
+ version: 0.5.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jeremy Cole
@@ -30,9 +30,14 @@ extra_rdoc_files: []
30
30
  files:
31
31
  - lib/innodb.rb
32
32
  - lib/innodb/cursor.rb
33
+ - lib/innodb/free_list.rb
34
+ - lib/innodb/index.rb
33
35
  - lib/innodb/log.rb
34
36
  - lib/innodb/log_block.rb
35
37
  - lib/innodb/page.rb
38
+ - lib/innodb/page/fsp_hdr_xdes.rb
39
+ - lib/innodb/page/index.rb
40
+ - lib/innodb/page/inode.rb
36
41
  - lib/innodb/space.rb
37
42
  - lib/innodb/version.rb
38
43
  - bin/innodb_dump_log