innodb_ruby 0.5.0 → 0.5.1

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.
@@ -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