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