innodb_ruby 0.7.12 → 0.8.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_space +15 -8
- data/lib/innodb.rb +2 -1
- data/lib/innodb/data_dictionary.rb +53 -0
- data/lib/innodb/index.rb +10 -10
- data/lib/innodb/page.rb +68 -14
- data/lib/innodb/page/index.rb +39 -26
- data/lib/innodb/page/sys_data_dictionary_header.rb +30 -29
- data/lib/innodb/record_describer.rb +111 -2
- data/lib/innodb/space.rb +19 -4
- data/lib/innodb/version.rb +1 -1
- metadata +3 -2
data/bin/innodb_space
CHANGED
@@ -262,7 +262,12 @@ def page_account(space, page_number)
|
|
262
262
|
end
|
263
263
|
|
264
264
|
page = space.page(page_number)
|
265
|
-
|
265
|
+
page_type = Innodb::Page::PAGE_TYPE[page.type]
|
266
|
+
puts " Page type is %s (%s, %s)." % [
|
267
|
+
page.type,
|
268
|
+
page_type[:description],
|
269
|
+
page_type[:usage],
|
270
|
+
]
|
266
271
|
|
267
272
|
xdes = space.xdes_for_page(page_number)
|
268
273
|
puts " Extent descriptor for pages %d-%d is at page %d, offset %d." % [
|
@@ -354,7 +359,7 @@ def page_directory_summary(page)
|
|
354
359
|
record = page.record(offset)
|
355
360
|
key = if [:conventional, :node_pointer].include? record[:header][:type]
|
356
361
|
if record[:key]
|
357
|
-
"(%s)" % record[:key].join(", ")
|
362
|
+
"(%s)" % record[:key].map { |r| "%s=%s" % [r[:name], r[:value]] }.join(", ")
|
358
363
|
end
|
359
364
|
end
|
360
365
|
puts "%-8i%-8i%-14s%-8i%s" % [
|
@@ -409,8 +414,8 @@ def index_recurse(index)
|
|
409
414
|
page.each_record do |record|
|
410
415
|
puts "%sRECORD: (%s) -> (%s)" % [
|
411
416
|
" " * (depth+1),
|
412
|
-
record[:key].join(", "),
|
413
|
-
record[:row].join(", "),
|
417
|
+
record[:key].map { |r| "%s=%s" % [r[:name], r[:value]] }.join(", "),
|
418
|
+
record[:row].map { |r| "%s=%s" % [r[:name], r[:value]] }.join(", "),
|
414
419
|
]
|
415
420
|
end
|
416
421
|
end
|
@@ -418,7 +423,7 @@ def index_recurse(index)
|
|
418
423
|
lambda do |parent_page, child_page, child_min_key, depth|
|
419
424
|
puts "%sNODE POINTER RECORD >= (%s) -> #%i" % [
|
420
425
|
" " * depth,
|
421
|
-
child_min_key.join(", "),
|
426
|
+
child_min_key.map { |r| "%s=%s" % [r[:name], r[:value]] }.join(", "),
|
422
427
|
child_page.offset,
|
423
428
|
]
|
424
429
|
end
|
@@ -479,7 +484,7 @@ def index_level_summary(index, levels)
|
|
479
484
|
page.record_space,
|
480
485
|
page.free_space,
|
481
486
|
page.records,
|
482
|
-
page.first_record[:key].join("|"),
|
487
|
+
page.first_record[:key].map { |r| r[:value] }.join("|"),
|
483
488
|
]
|
484
489
|
end
|
485
490
|
end
|
@@ -667,9 +672,11 @@ end
|
|
667
672
|
space = Innodb::Space.new(@options.file, @options.page_size)
|
668
673
|
|
669
674
|
if @options.describer
|
670
|
-
|
671
|
-
|
675
|
+
describer = eval(@options.describer)
|
676
|
+
unless describer
|
677
|
+
describer = Innodb::RecordDescriber.const_get(@options.describer)
|
672
678
|
end
|
679
|
+
space.record_describer = describer.new
|
673
680
|
end
|
674
681
|
|
675
682
|
if ARGV.empty?
|
data/lib/innodb.rb
CHANGED
@@ -6,6 +6,8 @@ require "enumerator"
|
|
6
6
|
|
7
7
|
require "innodb/version"
|
8
8
|
require "innodb/checksum"
|
9
|
+
require "innodb/record_describer"
|
10
|
+
require "innodb/data_dictionary"
|
9
11
|
require "innodb/page"
|
10
12
|
require "innodb/page/blob"
|
11
13
|
require "innodb/page/fsp_hdr_xdes"
|
@@ -14,7 +16,6 @@ require "innodb/page/index"
|
|
14
16
|
require "innodb/page/trx_sys"
|
15
17
|
require "innodb/page/sys"
|
16
18
|
require "innodb/page/undo_log"
|
17
|
-
require "innodb/record_describer"
|
18
19
|
require "innodb/field"
|
19
20
|
require "innodb/space"
|
20
21
|
require "innodb/inode"
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Innodb::DataDictionary
|
2
|
+
# A record describer for SYS_TABLES clustered records.
|
3
|
+
class SYS_TABLES_PRIMARY < Innodb::RecordDescriber
|
4
|
+
type :clustered
|
5
|
+
key "NAME", "VARCHAR(100)", :NOT_NULL
|
6
|
+
row "ID", :BIGINT, :UNSIGNED, :NOT_NULL
|
7
|
+
row "N_COLS", :INT, :UNSIGNED, :NOT_NULL
|
8
|
+
row "TYPE", :INT, :UNSIGNED, :NOT_NULL
|
9
|
+
row "MIX_ID", :BIGINT, :UNSIGNED, :NOT_NULL
|
10
|
+
row "MIX_LEN", :INT, :UNSIGNED, :NOT_NULL
|
11
|
+
row "CLUSTER_NAME", "VARCHAR(100)", :NOT_NULL
|
12
|
+
row "SPACE", :INT, :UNSIGNED, :NOT_NULL
|
13
|
+
end
|
14
|
+
|
15
|
+
# A record describer for SYS_TABLES secondary key on ID.
|
16
|
+
class SYS_TABLES_ID < Innodb::RecordDescriber
|
17
|
+
type :secondary
|
18
|
+
key "ID", :BIGINT, :UNSIGNED, :NOT_NULL
|
19
|
+
row "NAME", "VARCHAR(100)", :NOT_NULL
|
20
|
+
end
|
21
|
+
|
22
|
+
# A record describer for SYS_COLUMNS clustered records.
|
23
|
+
class SYS_COLUMNS_PRIMARY < Innodb::RecordDescriber
|
24
|
+
type :clustered
|
25
|
+
key "TABLE_ID", :BIGINT, :UNSIGNED, :NOT_NULL
|
26
|
+
key "POS", :INT, :UNSIGNED, :NOT_NULL
|
27
|
+
row "NAME", "VARCHAR(100)", :NOT_NULL
|
28
|
+
row "MTYPE", :INT, :UNSIGNED, :NOT_NULL
|
29
|
+
row "PRTYPE", :INT, :UNSIGNED, :NOT_NULL
|
30
|
+
row "LEN", :INT, :UNSIGNED, :NOT_NULL
|
31
|
+
row "PREC", :INT, :UNSIGNED, :NOT_NULL
|
32
|
+
end
|
33
|
+
|
34
|
+
# A record describer for SYS_INDEXES clustered records.
|
35
|
+
class SYS_INDEXES_PRIMARY < Innodb::RecordDescriber
|
36
|
+
type :clustered
|
37
|
+
key "TABLE_ID", :BIGINT, :UNSIGNED, :NOT_NULL
|
38
|
+
key "ID", :BIGINT, :UNSIGNED, :NOT_NULL
|
39
|
+
row "NAME", "VARCHAR(100)", :NOT_NULL
|
40
|
+
row "N_FIELDS", :INT, :UNSIGNED, :NOT_NULL
|
41
|
+
row "TYPE", :INT, :UNSIGNED, :NOT_NULL
|
42
|
+
row "SPACE", :INT, :UNSIGNED, :NOT_NULL
|
43
|
+
row "PAGE_NO", :INT, :UNSIGNED, :NOT_NULL
|
44
|
+
end
|
45
|
+
|
46
|
+
# A record describer for SYS_FIELDS clustered records.
|
47
|
+
class SYS_FIELDS_PRIMARY < Innodb::RecordDescriber
|
48
|
+
type :clustered
|
49
|
+
key "INDEX_ID", :BIGINT, :UNSIGNED, :NOT_NULL
|
50
|
+
key "POS", :INT, :UNSIGNED, :NOT_NULL
|
51
|
+
row "COL_NAME", "VARCHAR(100)", :NOT_NULL
|
52
|
+
end
|
53
|
+
end
|
data/lib/innodb/index.rb
CHANGED
@@ -201,7 +201,7 @@ class Innodb::Index
|
|
201
201
|
puts "linear_search_from_cursor: page=%i, level=%i, start=(%s)" % [
|
202
202
|
page.offset,
|
203
203
|
page.level,
|
204
|
-
this_rec && this_rec[:key].join(", "),
|
204
|
+
this_rec && this_rec[:key].map { |r| r[:value] }.join(", "),
|
205
205
|
]
|
206
206
|
end
|
207
207
|
|
@@ -214,19 +214,19 @@ class Innodb::Index
|
|
214
214
|
puts "linear_search_from_cursor: page=%i, level=%i, current=(%s)" % [
|
215
215
|
page.offset,
|
216
216
|
page.level,
|
217
|
-
this_rec && this_rec[:key].join(", "),
|
217
|
+
this_rec && this_rec[:key].map { |r| r[:value] }.join(", "),
|
218
218
|
]
|
219
219
|
end
|
220
220
|
|
221
221
|
# If we reach supremum, return the last non-system record we got.
|
222
222
|
return this_rec if next_rec[:header][:type] == :supremum
|
223
223
|
|
224
|
-
if compare_key(key, this_rec[:key]) < 0
|
224
|
+
if compare_key(key, this_rec[:key][:value]) < 0
|
225
225
|
return this_rec
|
226
226
|
end
|
227
227
|
|
228
|
-
if (compare_key(key, this_rec[:key]) >= 0) &&
|
229
|
-
(compare_key(key, next_rec[:key]) < 0)
|
228
|
+
if (compare_key(key, this_rec[:key][:value]) >= 0) &&
|
229
|
+
(compare_key(key, next_rec[:key][:value]) < 0)
|
230
230
|
# The desired key is either an exact match for this_rec or is greater
|
231
231
|
# than it but less than next_rec. If this is a non-leaf page, that
|
232
232
|
# will mean that the record will fall on the leaf page this node
|
@@ -263,7 +263,7 @@ class Innodb::Index
|
|
263
263
|
page.level,
|
264
264
|
dir.size,
|
265
265
|
mid,
|
266
|
-
rec[:key] && rec[:key].join(", "),
|
266
|
+
rec[:key] && rec[:key].map { |r| r[:value] }.join(", "),
|
267
267
|
]
|
268
268
|
end
|
269
269
|
|
@@ -276,7 +276,7 @@ class Innodb::Index
|
|
276
276
|
end
|
277
277
|
|
278
278
|
# Compare the desired key to the mid-point record's key.
|
279
|
-
case compare_key(key, rec[:key])
|
279
|
+
case compare_key(key, rec[:key][:value])
|
280
280
|
when 0
|
281
281
|
# An exact match for the key was found. Return the record.
|
282
282
|
@stats[:binary_search_by_directory_exact_match] += 1
|
@@ -292,7 +292,7 @@ class Innodb::Index
|
|
292
292
|
binary_search_by_directory(page, dir[mid...dir.size], key)
|
293
293
|
else
|
294
294
|
next_rec = page.record(dir[mid+1])
|
295
|
-
next_key = next_rec && compare_key(key, next_rec[:key])
|
295
|
+
next_key = next_rec && compare_key(key, next_rec[:key][:value])
|
296
296
|
if dir.size == 1 || next_key == -1 || next_key == 0
|
297
297
|
# This is the last entry remaining from the directory, or our key is
|
298
298
|
# greater than rec and less than rec+1's key. Use linear search to
|
@@ -350,7 +350,7 @@ class Innodb::Index
|
|
350
350
|
# We're on a leaf page, so return the page and record if there is a
|
351
351
|
# match. If there is no match, break the loop and cause nil to be
|
352
352
|
# returned.
|
353
|
-
return page, rec if compare_key(key, rec[:key]) == 0
|
353
|
+
return page, rec if compare_key(key, rec[:key][:value]) == 0
|
354
354
|
break
|
355
355
|
end
|
356
356
|
end
|
@@ -383,7 +383,7 @@ class Innodb::Index
|
|
383
383
|
# We're on a leaf page, so return the page and record if there is a
|
384
384
|
# match. If there is no match, break the loop and cause nil to be
|
385
385
|
# returned.
|
386
|
-
return page, rec if compare_key(key, rec[:key]) == 0
|
386
|
+
return page, rec if compare_key(key, rec[:key][:value]) == 0
|
387
387
|
break
|
388
388
|
end
|
389
389
|
end
|
data/lib/innodb/page.rb
CHANGED
@@ -112,21 +112,75 @@ class Innodb::Page
|
|
112
112
|
|
113
113
|
# InnoDB Page Type constants from include/fil0fil.h.
|
114
114
|
PAGE_TYPE = {
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
115
|
+
:ALLOCATED => {
|
116
|
+
:value => 0,
|
117
|
+
:description => "Freshly allocated",
|
118
|
+
:usage => "page type field has not been initialized",
|
119
|
+
},
|
120
|
+
:UNDO_LOG => {
|
121
|
+
:value => 2,
|
122
|
+
:description => "Undo log",
|
123
|
+
:usage => "stores previous values of modified records",
|
124
|
+
},
|
125
|
+
:INODE => {
|
126
|
+
:value => 3,
|
127
|
+
:description => "File segment inode",
|
128
|
+
:usage => "bookkeeping for file segments",
|
129
|
+
},
|
130
|
+
:IBUF_FREE_LIST => {
|
131
|
+
:value => 4,
|
132
|
+
:description => "Insert buffer free list",
|
133
|
+
:usage => "bookkeeping for insert buffer free space management",
|
134
|
+
},
|
135
|
+
:IBUF_BITMAP => {
|
136
|
+
:value => 5,
|
137
|
+
:description => "Insert buffer bitmap",
|
138
|
+
:usage => "bookkeeping for insert buffer writes to be merged",
|
139
|
+
},
|
140
|
+
:SYS => {
|
141
|
+
:value => 6,
|
142
|
+
:description => "System internal",
|
143
|
+
:usage => "used for various purposes in the system tablespace",
|
144
|
+
},
|
145
|
+
:TRX_SYS => {
|
146
|
+
:value => 7,
|
147
|
+
:description => "Transaction system header",
|
148
|
+
:usage => "bookkeeping for the transaction system in system tablespace",
|
149
|
+
},
|
150
|
+
:FSP_HDR => {
|
151
|
+
:value => 8,
|
152
|
+
:description => "File space header",
|
153
|
+
:usage => "header page (page 0) for each tablespace file",
|
154
|
+
},
|
155
|
+
:XDES => {
|
156
|
+
:value => 9,
|
157
|
+
:description => "Extent descriptor",
|
158
|
+
:usage => "header page for subsequent blocks of 16,384 pages",
|
159
|
+
},
|
160
|
+
:BLOB => {
|
161
|
+
:value => 10,
|
162
|
+
:description => "Uncompressed BLOB",
|
163
|
+
:usage => "externally-stored uncompressed BLOB column data",
|
164
|
+
},
|
165
|
+
:ZBLOB => {
|
166
|
+
:value => 11,
|
167
|
+
:description => "First compressed BLOB",
|
168
|
+
:usage => "externally-stored compressed BLOB column data, first page",
|
169
|
+
},
|
170
|
+
:ZBLOB2 => {
|
171
|
+
:value => 12,
|
172
|
+
:description => "Subsequent compressed BLOB",
|
173
|
+
:usage => "externally-stored compressed BLOB column data, subsequent page",
|
174
|
+
},
|
175
|
+
:INDEX => {
|
176
|
+
:value => 17855,
|
177
|
+
:description => "B+Tree index",
|
178
|
+
:usage => "table and index data stored in B+Tree structure",
|
179
|
+
},
|
128
180
|
}
|
129
181
|
|
182
|
+
PAGE_TYPE_BY_VALUE = PAGE_TYPE.inject({}) { |h, (k, v)| h[v[:value]] = k; h }
|
183
|
+
|
130
184
|
# A helper to convert "undefined" values stored in previous and next pointers
|
131
185
|
# in the page header to nil.
|
132
186
|
def self.maybe_undefined(value)
|
@@ -146,7 +200,7 @@ class Innodb::Page
|
|
146
200
|
Innodb::Page.maybe_undefined(c.get_uint32)
|
147
201
|
},
|
148
202
|
:lsn => c.name("lsn") { c.get_uint64 },
|
149
|
-
:type => c.name("type") {
|
203
|
+
:type => c.name("type") { PAGE_TYPE_BY_VALUE[c.get_uint16] },
|
150
204
|
:flush_lsn => c.name("flush_lsn") { c.get_uint64 },
|
151
205
|
:space_id => c.name("space_id") { c.get_uint32 },
|
152
206
|
}
|
data/lib/innodb/page/index.rb
CHANGED
@@ -335,22 +335,22 @@ class Innodb::Page::Index < Innodb::Page
|
|
335
335
|
|
336
336
|
# Return an array indicating which fields are null.
|
337
337
|
def record_header_compact_null_bitmap(cursor)
|
338
|
-
|
338
|
+
columns = (record_format[:key] + record_format[:row])
|
339
339
|
|
340
340
|
# The number of bits in the bitmap is the number of nullable fields.
|
341
|
-
size =
|
341
|
+
size = columns.count { |c| c[:field].type.nullable? }
|
342
342
|
|
343
343
|
# There is no bitmap if there are no nullable fields.
|
344
344
|
return nil unless size > 0
|
345
345
|
|
346
346
|
# To simplify later checks, expand bitmap to one for each field.
|
347
|
-
bitmap = Array.new(
|
347
|
+
bitmap = Array.new(columns.last[:field].position + 1, false)
|
348
348
|
|
349
349
|
null_bit_array = cursor.get_bit_array(size).reverse!
|
350
350
|
|
351
351
|
# For every nullable field, set whether the field is actually null.
|
352
|
-
|
353
|
-
bitmap[
|
352
|
+
columns.each do |c|
|
353
|
+
bitmap[c[:field].position] = c[:field].type.nullable? ? (null_bit_array.shift == 1) : false
|
354
354
|
end
|
355
355
|
|
356
356
|
return bitmap
|
@@ -359,14 +359,15 @@ class Innodb::Page::Index < Innodb::Page
|
|
359
359
|
# Return an array containing an array of the length of each variable-length
|
360
360
|
# field and an array indicating which fields are stored externally.
|
361
361
|
def record_header_compact_variable_lengths_and_externs(cursor, null_bitmap)
|
362
|
-
|
362
|
+
columns = (record_format[:key] + record_format[:row])
|
363
363
|
|
364
|
-
len_array = Array.new(
|
365
|
-
ext_array = Array.new(
|
364
|
+
len_array = Array.new(columns.last[:field].position + 1, 0)
|
365
|
+
ext_array = Array.new(columns.last[:field].position + 1, false)
|
366
366
|
|
367
367
|
# For each non-NULL variable-length field, the record header contains
|
368
368
|
# the length in one or two bytes.
|
369
|
-
|
369
|
+
columns.each do |c|
|
370
|
+
f = c[:field]
|
370
371
|
next if !f.type.variable? or (null_bitmap && null_bitmap[f.position])
|
371
372
|
|
372
373
|
len = cursor.get_uint8
|
@@ -449,21 +450,29 @@ class Innodb::Page::Index < Innodb::Page
|
|
449
450
|
|
450
451
|
# Return a set of field objects that describe the record.
|
451
452
|
def make_record_description
|
452
|
-
description = record_describer.
|
453
|
+
description = record_describer.description
|
453
454
|
|
454
455
|
position = 0
|
455
456
|
fields = {:type => description[:type], :key => [], :row => []}
|
456
457
|
|
457
|
-
description[:key].each do |
|
458
|
-
fields[:key] <<
|
458
|
+
description[:key].each do |field|
|
459
|
+
fields[:key] << {
|
460
|
+
:name => field[:name],
|
461
|
+
:field => Innodb::Field.new(position, *field[:type]),
|
462
|
+
}
|
459
463
|
position += 1
|
460
464
|
end
|
461
465
|
|
462
|
-
|
463
|
-
|
466
|
+
if description[:type] == :clustered
|
467
|
+
# Account for TRX_ID and ROLL_PTR.
|
468
|
+
position += 2
|
469
|
+
end
|
464
470
|
|
465
|
-
description[:row].each do |
|
466
|
-
fields[:row] <<
|
471
|
+
description[:row].each do |field|
|
472
|
+
fields[:row] << {
|
473
|
+
:name => field[:name],
|
474
|
+
:field => Innodb::Field.new(position, *field[:type]),
|
475
|
+
}
|
467
476
|
position += 1
|
468
477
|
end
|
469
478
|
|
@@ -499,11 +508,13 @@ class Innodb::Page::Index < Innodb::Page
|
|
499
508
|
|
500
509
|
# Read the key fields present in all types of pages.
|
501
510
|
this_record[:key] = []
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
511
|
+
record_format[:key].each do |column|
|
512
|
+
c.name("key[#{column[:name]}]") do
|
513
|
+
this_record[:key] << {
|
514
|
+
:name => column[:name],
|
515
|
+
:value => column[:field].read(this_record, c),
|
516
|
+
:extern => column[:field].read_extern(this_record, c),
|
517
|
+
}
|
507
518
|
end
|
508
519
|
end
|
509
520
|
|
@@ -532,11 +543,13 @@ class Innodb::Page::Index < Innodb::Page
|
|
532
543
|
(record_format[:type] == :secondary)
|
533
544
|
# Read the non-key fields.
|
534
545
|
this_record[:row] = []
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
546
|
+
record_format[:row].each do |column|
|
547
|
+
c.name("row[#{column[:name]}]") do
|
548
|
+
this_record[:row] << {
|
549
|
+
:name => column[:name],
|
550
|
+
:value => column[:field].read(this_record, c),
|
551
|
+
:extern => column[:field].read_extern(this_record, c),
|
552
|
+
}
|
540
553
|
end
|
541
554
|
end
|
542
555
|
end
|
@@ -1,5 +1,18 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require "innodb/data_dictionary"
|
4
|
+
|
2
5
|
class Innodb::Page::SysDataDictionaryHeader < Innodb::Page
|
6
|
+
RECORD_DESCRIBERS = {
|
7
|
+
:SYS_TABLES => {
|
8
|
+
:PRIMARY => Innodb::DataDictionary::SYS_TABLES_PRIMARY,
|
9
|
+
:ID => Innodb::DataDictionary::SYS_TABLES_ID
|
10
|
+
},
|
11
|
+
:SYS_COLUMNS => { :PRIMARY => Innodb::DataDictionary::SYS_COLUMNS_PRIMARY },
|
12
|
+
:SYS_INDEXES => { :PRIMARY => Innodb::DataDictionary::SYS_INDEXES_PRIMARY },
|
13
|
+
:SYS_FIELDS => { :PRIMARY => Innodb::DataDictionary::SYS_FIELDS_PRIMARY },
|
14
|
+
}
|
15
|
+
|
3
16
|
# The position of the data dictionary header within the page.
|
4
17
|
def pos_data_dictionary_header
|
5
18
|
pos_fil_header + size_fil_header
|
@@ -40,6 +53,22 @@ class Innodb::Page::SysDataDictionaryHeader < Innodb::Page
|
|
40
53
|
end
|
41
54
|
end
|
42
55
|
|
56
|
+
def index(table_name, index_name)
|
57
|
+
unless table_entry = data_dictionary_header[:indexes][table_name]
|
58
|
+
raise "Unknown data dictionary table #{table_name}"
|
59
|
+
end
|
60
|
+
|
61
|
+
unless index_root_page = table_entry[index_name]
|
62
|
+
raise "Unknown data dictionary index #{table_name}.#{index_name}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# If we have a record describer for this index, load it.
|
66
|
+
record_describer = RECORD_DESCRIBERS[table_name] &&
|
67
|
+
RECORD_DESCRIBERS[table_name][index_name]
|
68
|
+
|
69
|
+
@space.index(index_root_page, record_describer.new)
|
70
|
+
end
|
71
|
+
|
43
72
|
# Iterate through all indexes in the data dictionary, yielding the table
|
44
73
|
# name, index name, and the index itself as an Innodb::Index.
|
45
74
|
def each_index
|
@@ -49,7 +78,7 @@ class Innodb::Page::SysDataDictionaryHeader < Innodb::Page
|
|
49
78
|
|
50
79
|
data_dictionary_header[:indexes].each do |table_name, indexes|
|
51
80
|
indexes.each do |index_name, root_page_number|
|
52
|
-
yield table_name, index_name,
|
81
|
+
yield table_name, index_name, index(table_name, index_name)
|
53
82
|
end
|
54
83
|
end
|
55
84
|
end
|
@@ -61,32 +90,4 @@ class Innodb::Page::SysDataDictionaryHeader < Innodb::Page
|
|
61
90
|
puts "data_dictionary header:"
|
62
91
|
pp data_dictionary_header
|
63
92
|
end
|
64
|
-
|
65
|
-
# A record describer for SYS_TABLES clustered records.
|
66
|
-
class SYS_TABLES_PRIMARY
|
67
|
-
def self.cursor_sendable_description(page)
|
68
|
-
{
|
69
|
-
:type => :clustered,
|
70
|
-
:key => [
|
71
|
-
["VARCHAR(100)", :NOT_NULL], # NAME
|
72
|
-
],
|
73
|
-
:row => [
|
74
|
-
[:BIGINT, :UNSIGNED, :NOT_NULL], # ID
|
75
|
-
[:INT, :UNSIGNED, :NOT_NULL], # N_COLS
|
76
|
-
[:INT, :UNSIGNED, :NOT_NULL], # TYPE
|
77
|
-
[:BIGINT, :UNSIGNED, :NOT_NULL], # MIX_ID
|
78
|
-
[:INT, :UNSIGNED, :NOT_NULL], # MIX_LEN
|
79
|
-
["VARCHAR(100)"], # CLUSTER_NAME
|
80
|
-
[:INT, :UNSIGNED, :NOT_NULL], # SPACE
|
81
|
-
]
|
82
|
-
}
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
RECORD_DESCRIBERS = {
|
87
|
-
:SYS_TABLES => { :PRIMARY => SYS_TABLES_PRIMARY, :ID => nil },
|
88
|
-
:SYS_COLUMNS => { :PRIMARY => nil },
|
89
|
-
:SYS_INDEXES => { :PRIMARY => nil },
|
90
|
-
:SYS_FIELDS => { :PRIMARY => nil },
|
91
|
-
}
|
92
93
|
end
|
@@ -1,3 +1,112 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
#
|
4
|
+
# A class to describe record layouts for InnoDB indexes. Designed to be usable
|
5
|
+
# in two different ways: statically built and dynamically built. Note that in
|
6
|
+
# both cases, the order that statements are encountered is critical. Columns
|
7
|
+
# must be added to the key and row structures in the correct order.
|
8
|
+
#
|
9
|
+
# STATIC USAGE
|
10
|
+
#
|
11
|
+
# Static building is useful for building a custom describer for any index
|
12
|
+
# and looks like the following:
|
13
|
+
#
|
14
|
+
# To describe the SQL syntax:
|
15
|
+
#
|
16
|
+
# CREATE TABLE my_table (
|
17
|
+
# id BIGINT NOT NULL,
|
18
|
+
# name VARCHAR(100) NOT NULL,
|
19
|
+
# age INT UNSIGNED,
|
20
|
+
# PRIMARY KEY (id)
|
21
|
+
# );
|
22
|
+
#
|
23
|
+
# The clustered key would require a class like:
|
24
|
+
#
|
25
|
+
# class MyTableClusteredDescriber < Innodb::RecordDescriber
|
26
|
+
# type :clustered
|
27
|
+
# key "id", :BIGINT, :UNSIGNED, :NOT_NULL
|
28
|
+
# row "name", "VARCHAR(100)", :NOT_NULL
|
29
|
+
# row "age", :INT, :UNSIGNED
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# It can then be instantiated as usual:
|
33
|
+
#
|
34
|
+
# my_table_clustered = MyTableClusteredDescriber.new
|
35
|
+
#
|
36
|
+
# All statically-defined type, key, and row information will be copied into
|
37
|
+
# the instance when it is initialized. Once initialized, the instance can
|
38
|
+
# be additionally used dynamically, as per below. (A dynamic class is just
|
39
|
+
# the same as a static class that is empty.)
|
40
|
+
#
|
41
|
+
# Note that since InnoDB works in terms of *indexes* individually, a new class
|
42
|
+
# must be created for each index.
|
43
|
+
#
|
44
|
+
# DYNAMIC USAGE
|
45
|
+
#
|
46
|
+
# If a record describer needs to be built based on runtime information, such
|
47
|
+
# as index descriptions from a live data dictionary, instances can be built
|
48
|
+
# dynamically. For the same table above, this would require:
|
49
|
+
#
|
50
|
+
# my_table_clustered = Innodb::RecordDescriber.new
|
51
|
+
# my_table_clustered.type = :clustered
|
52
|
+
# my_table_clustered.key "id", :BIGINT, :UNSIGNED, :NOT_NULL
|
53
|
+
# my_table_clustered.row "name", "VARCHAR(100)", :NOT_NULL
|
54
|
+
# my_table_clustered.row "age", :INT, :UNSIGNED
|
55
|
+
#
|
56
|
+
|
57
|
+
class Innodb::RecordDescriber
|
58
|
+
# Internal method to initialize the class's instance variable on access.
|
59
|
+
def self.static_description
|
60
|
+
@static_description ||= {
|
61
|
+
:type => nil,
|
62
|
+
:key => [],
|
63
|
+
:row => []
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
# A 'type' method to be used from the DSL.
|
68
|
+
def self.type(type)
|
69
|
+
static_description[:type] = type
|
70
|
+
end
|
71
|
+
|
72
|
+
# An internal method wrapped with 'key' and 'row' helpers.
|
73
|
+
def self.add_static_field(group, name, type)
|
74
|
+
static_description[group] << { :name => name, :type => type }
|
75
|
+
end
|
76
|
+
|
77
|
+
# A 'key' method to be used from the DSL.
|
78
|
+
def self.key(name, *type)
|
79
|
+
add_static_field :key, name, type
|
80
|
+
end
|
81
|
+
|
82
|
+
# A 'row' method to be used from the DSL.
|
83
|
+
def self.row(name, *type)
|
84
|
+
add_static_field :row, name, type
|
85
|
+
end
|
86
|
+
|
87
|
+
attr_accessor :description
|
88
|
+
|
89
|
+
def initialize
|
90
|
+
@description = self.class.static_description.dup
|
91
|
+
end
|
92
|
+
|
93
|
+
# Set the type of this record (:clustered or :secondary).
|
94
|
+
def type(type)
|
95
|
+
description[:type] = type
|
96
|
+
end
|
97
|
+
|
98
|
+
# An internal method wrapped with 'key' and 'row' helpers.
|
99
|
+
def add_field(group, name, type)
|
100
|
+
description[group] << { :name => name, :type => type }
|
101
|
+
end
|
102
|
+
|
103
|
+
# Add a key column to the record description.
|
104
|
+
def key(name, *type)
|
105
|
+
add_field :key, name, type
|
106
|
+
end
|
107
|
+
|
108
|
+
# Add a row (non-key) column to the record description.
|
109
|
+
def row(name, *type)
|
110
|
+
add_field :row, name, type
|
111
|
+
end
|
112
|
+
end
|
data/lib/innodb/space.rb
CHANGED
@@ -57,7 +57,7 @@ class Innodb::Space
|
|
57
57
|
# to offset 0 and page type :FSP_HDR (8).
|
58
58
|
page_offset = BinData::Uint32be.read(read_at_offset(4, 4))
|
59
59
|
page_type = BinData::Uint16be.read(read_at_offset(24, 2))
|
60
|
-
unless page_offset == 0 && Innodb::Page::
|
60
|
+
unless page_offset == 0 && Innodb::Page::PAGE_TYPE_BY_VALUE[page_type] == :FSP_HDR
|
61
61
|
raise "Something is very wrong; Page 0 does not seem to be type FSP_HDR"
|
62
62
|
end
|
63
63
|
|
@@ -165,14 +165,24 @@ class Innodb::Space
|
|
165
165
|
true
|
166
166
|
end
|
167
167
|
|
168
|
+
# Return the page number for the space's FSP_HDR page.
|
169
|
+
def page_fsp_hdr
|
170
|
+
0
|
171
|
+
end
|
172
|
+
|
168
173
|
# Get (and cache) the FSP header from the FSP_HDR page.
|
169
174
|
def fsp
|
170
|
-
@fsp ||= page(
|
175
|
+
@fsp ||= page(page_fsp_hdr).fsp_header
|
176
|
+
end
|
177
|
+
|
178
|
+
# Return the page number for the space's TRX_SYS page.
|
179
|
+
def page_trx_sys
|
180
|
+
5
|
171
181
|
end
|
172
182
|
|
173
183
|
# Get the Innodb::Page::TrxSys page for a system space.
|
174
184
|
def trx_sys
|
175
|
-
page(
|
185
|
+
page(page_trx_sys) if system_space?
|
176
186
|
end
|
177
187
|
|
178
188
|
def rseg_page?(page_number)
|
@@ -184,9 +194,14 @@ class Innodb::Space
|
|
184
194
|
end
|
185
195
|
end
|
186
196
|
|
197
|
+
# Return the page number for the space's SYS data dictionary header.
|
198
|
+
def page_sys_data_dictionary
|
199
|
+
7
|
200
|
+
end
|
201
|
+
|
187
202
|
# Get the Innodb::Page::SysDataDictionaryHeader page for a system space.
|
188
203
|
def data_dictionary
|
189
|
-
page(
|
204
|
+
page(page_sys_data_dictionary) if system_space?
|
190
205
|
end
|
191
206
|
|
192
207
|
# Get an Innodb::List object for a specific list by list name.
|
data/lib/innodb/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: innodb_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bindata
|
@@ -39,6 +39,7 @@ files:
|
|
39
39
|
- lib/innodb.rb
|
40
40
|
- lib/innodb/checksum.rb
|
41
41
|
- lib/innodb/cursor.rb
|
42
|
+
- lib/innodb/data_dictionary.rb
|
42
43
|
- lib/innodb/field.rb
|
43
44
|
- lib/innodb/field_type.rb
|
44
45
|
- lib/innodb/fseg_entry.rb
|