innodb_ruby 0.8.8 → 0.9.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/lib/innodb.rb CHANGED
@@ -2,11 +2,22 @@
2
2
 
3
3
  # A set of classes for parsing and working with InnoDB data files.
4
4
 
5
- module Innodb; end
5
+ module Innodb
6
+ @@debug = false
7
+
8
+ def self.debug?
9
+ @@debug == true
10
+ end
11
+
12
+ def self.debug=(value)
13
+ @@debug = value
14
+ end
15
+ end
6
16
 
7
17
  require "enumerator"
8
18
 
9
19
  require "innodb/version"
20
+ require "innodb/stats"
10
21
  require "innodb/checksum"
11
22
  require "innodb/record_describer"
12
23
  require "innodb/data_dictionary"
data/lib/innodb/cursor.rb CHANGED
@@ -163,11 +163,11 @@ class Innodb::Cursor
163
163
  cursor_start = current.position
164
164
  case current.direction
165
165
  when :forward
166
- data = @buffer.data(current.position, length)
166
+ data = @buffer.slice(current.position, length)
167
167
  adjust(length)
168
168
  when :backward
169
169
  adjust(-length)
170
- data = @buffer.data(current.position, length)
170
+ data = @buffer.slice(current.position, length)
171
171
  end
172
172
 
173
173
  trace(cursor_start, data.bytes, current.name)
@@ -1,5 +1,7 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
+ # A class representing InnoDB's data dictionary, which contains metadata about
4
+ # tables, columns, and indexes.
3
5
  class Innodb::DataDictionary
4
6
  # A record describer for SYS_TABLES clustered records.
5
7
  class SYS_TABLES_PRIMARY < Innodb::RecordDescriber
@@ -52,4 +54,593 @@ class Innodb::DataDictionary
52
54
  key "POS", :INT, :UNSIGNED, :NOT_NULL
53
55
  row "COL_NAME", "VARCHAR(100)", :NOT_NULL
54
56
  end
57
+
58
+ # A hash of hashes of table name and index name to describer
59
+ # class.
60
+ DATA_DICTIONARY_RECORD_DESCRIBERS = {
61
+ :SYS_TABLES => {
62
+ :PRIMARY => SYS_TABLES_PRIMARY,
63
+ :ID => SYS_TABLES_ID
64
+ },
65
+ :SYS_COLUMNS => { :PRIMARY => SYS_COLUMNS_PRIMARY },
66
+ :SYS_INDEXES => { :PRIMARY => SYS_INDEXES_PRIMARY },
67
+ :SYS_FIELDS => { :PRIMARY => SYS_FIELDS_PRIMARY },
68
+ }
69
+
70
+ # A hash of MySQL's internal type system to the stored
71
+ # values for those types, and the "external" SQL type.
72
+ MYSQL_TYPE = {
73
+ # :DECIMAL => { :value => 0, :type => :DECIMAL },
74
+ :TINY => { :value => 1, :type => :TINYINT },
75
+ :SHORT => { :value => 2, :type => :SMALLINT },
76
+ :LONG => { :value => 3, :type => :INT },
77
+ :FLOAT => { :value => 4, :type => :FLOAT },
78
+ :DOUBLE => { :value => 5, :type => :DOUBLE },
79
+ # :NULL => { :value => 6, :type => nil },
80
+ :TIMESTAMP => { :value => 7, :type => :TIMESTAMP },
81
+ :LONGLONG => { :value => 8, :type => :BIGINT },
82
+ :INT24 => { :value => 9, :type => :MEDIUMINT },
83
+ # :DATE => { :value => 10, :type => :DATE },
84
+ :TIME => { :value => 11, :type => :TIME },
85
+ :DATETIME => { :value => 12, :type => :DATETIME },
86
+ :YEAR => { :value => 13, :type => :YEAR },
87
+ :NEWDATE => { :value => 14, :type => :DATE },
88
+ :VARCHAR => { :value => 15, :type => :VARCHAR },
89
+ :BIT => { :value => 16, :type => :BIT },
90
+ :NEWDECIMAL => { :value => 246, :type => :CHAR },
91
+ # :ENUM => { :value => 247, :type => :ENUM },
92
+ # :SET => { :value => 248, :type => :SET },
93
+ :TINY_BLOB => { :value => 249, :type => :TINYBLOB },
94
+ :MEDIUM_BLOB => { :value => 250, :type => :MEDIUMBLOB },
95
+ :LONG_BLOB => { :value => 251, :type => :LONGBLOB },
96
+ :BLOB => { :value => 252, :type => :BLOB },
97
+ # :VAR_STRING => { :value => 253, :type => :VARCHAR },
98
+ :STRING => { :value => 254, :type => :CHAR },
99
+ :GEOMETRY => { :value => 255, :type => :GEOMETRY },
100
+ }
101
+
102
+ # A hash of MYSQL_TYPE keys by value :value key.
103
+ MYSQL_TYPE_BY_VALUE = MYSQL_TYPE.inject({}) { |h, (k, v)| h[v[:value]] = k; h }
104
+
105
+ # A hash of InnoDB's internal type system to the values
106
+ # stored for each type.
107
+ COLUMN_MTYPE = {
108
+ :VARCHAR => 1,
109
+ :CHAR => 2,
110
+ :FIXBINARY => 3,
111
+ :BINARY => 4,
112
+ :BLOB => 5,
113
+ :INT => 6,
114
+ :SYS_CHILD => 7,
115
+ :SYS => 8,
116
+ :FLOAT => 9,
117
+ :DOUBLE => 10,
118
+ :DECIMAL => 11,
119
+ :VARMYSQL => 12,
120
+ :MYSQL => 13,
121
+ }
122
+
123
+ # A hash of COLUMN_MTYPE keys by value.
124
+ COLUMN_MTYPE_BY_VALUE = COLUMN_MTYPE.inject({}) { |h, (k, v)| h[v] = k; h }
125
+
126
+ # A hash of InnoDB "precise type" bitwise flags.
127
+ COLUMN_PRTYPE_FLAG = {
128
+ :NOT_NULL => 256,
129
+ :UNSIGNED => 512,
130
+ :BINARY => 1024,
131
+ :LONG_TRUE_VARCHAR => 4096,
132
+ }
133
+
134
+ # A hash of COLUMN_PRTYPE keys by value.
135
+ COLUMN_PRTYPE_FLAG_BY_VALUE = COLUMN_PRTYPE_FLAG.inject({}) { |h, (k, v)| h[v] = k; h }
136
+
137
+ # The bitmask to extract the MySQL internal type
138
+ # from the InnoDB "precise type".
139
+ COLUMN_PRTYPE_MYSQL_TYPE_MASK = 0xFF
140
+
141
+ # A hash of InnoDB's index type flags.
142
+ INDEX_TYPE_FLAG = {
143
+ :CLUSTERED => 1,
144
+ :UNIQUE => 2,
145
+ :UNIVERSAL => 4,
146
+ :IBUF => 8,
147
+ :CORRUPT => 16,
148
+ }
149
+
150
+ # A hash of INDEX_TYPE_FLAG keys by value.
151
+ INDEX_TYPE_FLAG_BY_VALUE = INDEX_TYPE_FLAG.inject({}) { |h, (k, v)| h[v] = k; h }
152
+
153
+ # Return the "external" SQL type string (such as "VARCHAR" or
154
+ # "INT") given the stored mtype and prtype from the InnoDB
155
+ # data dictionary. Note that not all types are extractable
156
+ # into fully defined SQL types due to the lossy nature of
157
+ # the MySQL-to-InnoDB interface regarding types.
158
+ def self.mtype_prtype_to_type_string(mtype, prtype, len, prec)
159
+ mysql_type = prtype & COLUMN_PRTYPE_MYSQL_TYPE_MASK
160
+ internal_type = MYSQL_TYPE_BY_VALUE[mysql_type]
161
+ external_type = MYSQL_TYPE[internal_type][:type]
162
+
163
+ case external_type
164
+ when :VARCHAR
165
+ # One-argument: length.
166
+ "%s(%i)" % [external_type, len]
167
+ when :FLOAT, :DOUBLE
168
+ # Two-argument: length and precision.
169
+ "%s(%i,%i)" % [external_type, len, prec]
170
+ when :CHAR
171
+ if COLUMN_MTYPE_BY_VALUE[mtype] == :MYSQL
172
+ # When the mtype is :MYSQL, the column is actually
173
+ # stored as VARCHAR despite being a CHAR. This is
174
+ # done for CHAR columns having multi-byte character
175
+ # sets in order to limit size. Note that such data
176
+ # are still space-padded to at least len.
177
+ "VARCHAR(%i)" % [len]
178
+ else
179
+ "CHAR(%i)" % [len]
180
+ end
181
+ when :DECIMAL
182
+ # The DECIMAL type is designated as DECIMAL(M,D)
183
+ # however the M and D definitions are not stored
184
+ # in the InnoDB data dictionary. We need to define
185
+ # the column as something which will extract the
186
+ # raw bytes in order to read the column, but we
187
+ # can't figure out the right decimal type. The
188
+ # len stored here is actually the on-disk storage
189
+ # size.
190
+ "CHAR(%i)" % [len]
191
+ else
192
+ external_type
193
+ end
194
+ end
195
+
196
+ # Return a full data type given an mtype and prtype, such
197
+ # as ["VARCHAR(10)", :NOT_NULL] or [:INT, :UNSIGNED].
198
+ def self.mtype_prtype_to_data_type(mtype, prtype, len, prec)
199
+ data_type = []
200
+
201
+ if type = mtype_prtype_to_type_string(mtype, prtype, len, prec)
202
+ data_type << type
203
+ else
204
+ raise "Unsupported type (mtype #{mtype}, prtype #{prtype})"
205
+ end
206
+
207
+ if prtype & COLUMN_PRTYPE_FLAG[:NOT_NULL] != 0
208
+ data_type << :NOT_NULL
209
+ end
210
+
211
+ if prtype & COLUMN_PRTYPE_FLAG[:UNSIGNED] != 0
212
+ data_type << :UNSIGNED
213
+ end
214
+
215
+ data_type
216
+ end
217
+
218
+ attr_reader :system_space
219
+
220
+ def initialize(system_space)
221
+ @system_space = system_space
222
+ end
223
+
224
+ # A helper method to reach inside the system space and retrieve
225
+ # the data dictionary index locations from the data dictionary
226
+ # header.
227
+ def data_dictionary_indexes
228
+ system_space.data_dictionary_page.data_dictionary_header[:indexes]
229
+ end
230
+
231
+ # Return an Innodb::Index object initialized to the
232
+ # internal data dictionary index with an appropriate
233
+ # record describer so that records can be recursed.
234
+ def data_dictionary_index(table_name, index_name)
235
+ unless table_entry = data_dictionary_indexes[table_name]
236
+ raise "Unknown data dictionary table #{table_name}"
237
+ end
238
+
239
+ unless index_root_page = table_entry[index_name]
240
+ raise "Unknown data dictionary index #{table_name}.#{index_name}"
241
+ end
242
+
243
+ # If we have a record describer for this index, load it.
244
+ record_describer = DATA_DICTIONARY_RECORD_DESCRIBERS[table_name] &&
245
+ DATA_DICTIONARY_RECORD_DESCRIBERS[table_name][index_name]
246
+
247
+ system_space.index(index_root_page, record_describer.new)
248
+ end
249
+
250
+ # Iterate through all data dictionary indexes, yielding the
251
+ # table name, index name, and root page number.
252
+ def each_data_dictionary_index_root_page_number
253
+ unless block_given?
254
+ return enum_for(:each_data_dictionary_index_root_page_number)
255
+ end
256
+
257
+ data_dictionary_indexes.each do |table_name, indexes|
258
+ indexes.each do |index_name, root_page_number|
259
+ yield table_name, index_name, root_page_number
260
+ end
261
+ end
262
+
263
+ nil
264
+ end
265
+
266
+ # Iterate through all data dictionary indexes, yielding the table
267
+ # name, index name, and the index itself as an Innodb::Index.
268
+ def each_data_dictionary_index
269
+ unless block_given?
270
+ return enum_for(:each_data_dictionary_index)
271
+ end
272
+
273
+ data_dictionary_indexes.each do |table_name, indexes|
274
+ indexes.each do |index_name, root_page_number|
275
+ yield table_name, index_name,
276
+ data_dictionary_index(table_name, index_name)
277
+ end
278
+ end
279
+
280
+ nil
281
+ end
282
+
283
+ # Iterate through records from a data dictionary index yielding each record
284
+ # as a Innodb::Record object.
285
+ def each_record_from_data_dictionary_index(table, index)
286
+ unless block_given?
287
+ return enum_for(:each_index, table, index)
288
+ end
289
+
290
+ data_dictionary_index(table, index).each_record do |record|
291
+ yield record
292
+ end
293
+
294
+ nil
295
+ end
296
+
297
+ # Iterate through the records in the SYS_TABLES data dictionary table.
298
+ def each_table
299
+ unless block_given?
300
+ return enum_for(:each_table)
301
+ end
302
+
303
+ each_record_from_data_dictionary_index(:SYS_TABLES, :PRIMARY) do |record|
304
+ yield record.fields
305
+ end
306
+
307
+ nil
308
+ end
309
+
310
+ # Iterate through the records in the SYS_COLUMNS data dictionary table.
311
+ def each_column
312
+ unless block_given?
313
+ return enum_for(:each_column)
314
+ end
315
+
316
+ each_record_from_data_dictionary_index(:SYS_COLUMNS, :PRIMARY) do |record|
317
+ yield record.fields
318
+ end
319
+
320
+ nil
321
+ end
322
+
323
+ # Iterate through the records in the SYS_INDEXES dictionary table.
324
+ def each_index
325
+ unless block_given?
326
+ return enum_for(:each_index)
327
+ end
328
+
329
+ each_record_from_data_dictionary_index(:SYS_INDEXES, :PRIMARY) do |record|
330
+ yield record.fields
331
+ end
332
+
333
+ nil
334
+ end
335
+
336
+ # Iterate through the records in the SYS_FIELDS data dictionary table.
337
+ def each_field
338
+ unless block_given?
339
+ return enum_for(:each_field)
340
+ end
341
+
342
+ each_record_from_data_dictionary_index(:SYS_FIELDS, :PRIMARY) do |record|
343
+ yield record.fields
344
+ end
345
+
346
+ nil
347
+ end
348
+
349
+ # A helper to iterate the method provided and return the first record
350
+ # where the record's field matches the provided value.
351
+ def object_by_field(method, field, value)
352
+ send(method).select { |o| o[field] == value }.first
353
+ end
354
+
355
+ # A helper to iterate the method provided and return the first record
356
+ # where the record's fields f1 and f2 match the provided values v1 and v2.
357
+ def object_by_two_fields(method, f1, v1, f2, v2)
358
+ send(method).select { |o| o[f1] == v1 && o[f2] == v2 }.first
359
+ end
360
+
361
+ # Lookup a table by table ID.
362
+ def table_by_id(table_id)
363
+ object_by_field(:each_table,
364
+ "ID", table_id)
365
+ end
366
+
367
+ # Lookup a table by table name.
368
+ def table_by_name(table_name)
369
+ object_by_field(:each_table,
370
+ "NAME", table_name)
371
+ end
372
+
373
+ # Lookup a table by space ID.
374
+ def table_by_space_id(space_id)
375
+ object_by_field(:each_table,
376
+ "SPACE", space_id)
377
+ end
378
+
379
+ # Lookup a column by table name and column name.
380
+ def column_by_name(table_name, column_name)
381
+ unless table = table_by_name(table_name)
382
+ return nil
383
+ end
384
+
385
+ object_by_two_fields(:each_column,
386
+ "TABLE_ID", table["ID"],
387
+ "NAME", column_name)
388
+ end
389
+
390
+ # Lookup an index by index ID.
391
+ def index_by_id(index_id)
392
+ object_by_field(:each_index,
393
+ "ID", index_id)
394
+ end
395
+
396
+ # Lookup an index by table name and index name.
397
+ def index_by_name(table_name, index_name)
398
+ unless table = table_by_name(table_name)
399
+ return nil
400
+ end
401
+
402
+ object_by_two_fields(:each_index,
403
+ "TABLE_ID", table["ID"],
404
+ "NAME", index_name)
405
+ end
406
+
407
+ # Iterate through indexes by space ID.
408
+ def each_index_by_space_id(space_id)
409
+ unless block_given?
410
+ return enum_for(:each_index_by_space_id, space_id)
411
+ end
412
+
413
+ each_index do |record|
414
+ yield record if record["SPACE"] == space_id
415
+ end
416
+
417
+ nil
418
+ end
419
+
420
+ # Iterate through all indexes in a table by table ID.
421
+ def each_index_by_table_id(table_id)
422
+ unless block_given?
423
+ return enum_for(:each_index_by_table_id, table_id)
424
+ end
425
+
426
+ each_index do |record|
427
+ yield record if record["TABLE_ID"] == table_id
428
+ end
429
+
430
+ nil
431
+ end
432
+
433
+ # Iterate through all indexes in a table by table name.
434
+ def each_index_by_table_name(table_name)
435
+ unless block_given?
436
+ return enum_for(:each_index_by_table_name, table_name)
437
+ end
438
+
439
+ unless table = table_by_name(table_name)
440
+ raise "Table not found"
441
+ end
442
+
443
+ each_index_by_table_id(table["ID"]) do |record|
444
+ yield record
445
+ end
446
+
447
+ nil
448
+ end
449
+
450
+ # Iterate through all fields in an index by index ID.
451
+ def each_field_by_index_id(index_id)
452
+ unless block_given?
453
+ return enum_for(:each_field_by_index_id, index_id)
454
+ end
455
+
456
+ each_field do |record|
457
+ yield record if record["INDEX_ID"] == index_id
458
+ end
459
+
460
+ nil
461
+ end
462
+
463
+ # Iterate through all fields in an index by index name.
464
+ def each_field_by_index_name(table_name, index_name)
465
+ unless block_given?
466
+ return enum_for(:each_field_by_name, table_name, index_name)
467
+ end
468
+
469
+ unless index = index_by_name(table_name, index_name)
470
+ raise "Index not found"
471
+ end
472
+
473
+ each_field_by_index_id(index["ID"]) do |record|
474
+ yield record
475
+ end
476
+
477
+ nil
478
+ end
479
+
480
+ # Iterate through all columns in a table by table ID.
481
+ def each_column_by_table_id(table_id)
482
+ unless block_given?
483
+ return enum_for(:each_column_by_table_id, table_id)
484
+ end
485
+
486
+ each_column do |record|
487
+ yield record if record["TABLE_ID"] == table_id
488
+ end
489
+
490
+ nil
491
+ end
492
+
493
+ # Iterate through all columns in a table by table name.
494
+ def each_column_by_table_name(table_name)
495
+ unless block_given?
496
+ return enum_for(:each_column_by_table_name, table_name)
497
+ end
498
+
499
+ unless table = table_by_name(table_name)
500
+ raise "Table not found"
501
+ end
502
+
503
+ each_column_by_table_id(table["ID"]) do |record|
504
+ yield record
505
+ end
506
+
507
+ nil
508
+ end
509
+
510
+ # Iterate through all columns in an index by table name and index name.
511
+ def each_column_in_index_by_name(table_name, index_name)
512
+ unless block_given?
513
+ return enum_for(:each_column_in_index_by_name, table_name, index_name)
514
+ end
515
+
516
+ each_field_by_index_name(table_name, index_name) do |record|
517
+ yield column_by_name(table_name, record["COL_NAME"])
518
+ end
519
+
520
+ nil
521
+ end
522
+
523
+ # Iterate through all columns not in an index by table name and index name.
524
+ # This is useful when building index descriptions for secondary indexes.
525
+ def each_column_not_in_index_by_name(table_name, index_name)
526
+ unless block_given?
527
+ return enum_for(:each_column_not_in_index_by_name, table_name, index_name)
528
+ end
529
+
530
+ columns_in_index = {}
531
+ each_column_in_index_by_name(table_name, index_name) do |record|
532
+ columns_in_index[record["NAME"]] = 1
533
+ end
534
+
535
+ each_column_by_table_name(table_name) do |record|
536
+ yield record unless columns_in_index.include?(record["NAME"])
537
+ end
538
+
539
+ nil
540
+ end
541
+
542
+ # Return the name of the clustered index (usually "PRIMARY", but not always)
543
+ # for a given table name.
544
+ def clustered_index_name_by_table_name(table_name)
545
+ unless table_record = table_by_name(table_name)
546
+ raise "Table not found"
547
+ end
548
+
549
+ if index_record = object_by_two_fields(:each_index,
550
+ "TABLE_ID", table_record["ID"],
551
+ "TYPE", 3)
552
+ index_record["NAME"]
553
+ end
554
+ end
555
+
556
+ # Produce a Innodb::RecordDescriber-compatible column description
557
+ # given a type (:key, :row) and data dictionary SYS_COLUMNS record.
558
+ def _make_column_description(type, record)
559
+ {
560
+ :type => type,
561
+ :name => record["NAME"],
562
+ :description => self.class.mtype_prtype_to_data_type(
563
+ record["MTYPE"],
564
+ record["PRTYPE"],
565
+ record["LEN"],
566
+ record["PREC"]
567
+ )
568
+ }
569
+ end
570
+
571
+ # Iterate through Innodb::RecordDescriber-compatible column descriptions
572
+ # for a given index by table name and index name.
573
+ def each_column_description_by_index_name(table_name, index_name)
574
+ unless block_given?
575
+ return enum_for(:each_column_description_by_index_name, table_name, index_name)
576
+ end
577
+
578
+ unless index = index_by_name(table_name, index_name)
579
+ raise "Index not found"
580
+ end
581
+
582
+ columns_in_index = {}
583
+ each_column_in_index_by_name(table_name, index_name) do |record|
584
+ columns_in_index[record["NAME"]] = 1
585
+ yield _make_column_description(:key, record)
586
+ end
587
+
588
+ if index["TYPE"] & INDEX_TYPE_FLAG[:CLUSTERED] != 0
589
+ each_column_by_table_name(table_name) do |record|
590
+ unless columns_in_index.include?(record["NAME"])
591
+ yield _make_column_description(:row, record)
592
+ end
593
+ end
594
+ else
595
+ clustered_index_name = clustered_index_name_by_table_name(table_name)
596
+
597
+ each_column_in_index_by_name(table_name, clustered_index_name) do |record|
598
+ yield _make_column_description(:row, record)
599
+ end
600
+ end
601
+
602
+ nil
603
+ end
604
+
605
+ # Return an Innodb::RecordDescriber object describing records for a given
606
+ # index by table name and index name.
607
+ def record_describer_by_index_name(table_name, index_name)
608
+ unless index = index_by_name(table_name, index_name)
609
+ raise "Index not found"
610
+ end
611
+
612
+ describer = Innodb::RecordDescriber.new
613
+
614
+ if index["TYPE"] & INDEX_TYPE_FLAG[:CLUSTERED] != 0
615
+ describer.type :clustered
616
+ else
617
+ describer.type :secondary
618
+ end
619
+
620
+ each_column_description_by_index_name(table_name, index_name) do |column|
621
+ case column[:type]
622
+ when :key
623
+ describer.key column[:name], *column[:description]
624
+ when :row
625
+ describer.row column[:name], *column[:description]
626
+ end
627
+ end
628
+
629
+ describer
630
+ end
631
+
632
+ # Return an Innodb::RecordDescriber object describing the records
633
+ # in a given index by index ID.
634
+ def record_describer_by_index_id(index_id)
635
+ unless index = index_by_id(index_id)
636
+ raise "Index not found"
637
+ end
638
+
639
+ unless table = table_by_id(index["TABLE_ID"])
640
+ raise "Table not found"
641
+ end
642
+
643
+ record_describer_by_index_name(table["NAME"], index["NAME"])
644
+ end
645
+
55
646
  end