innodb_ruby 0.9.0 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,7 +14,10 @@ module Innodb
14
14
  end
15
15
  end
16
16
 
17
+ require "pp"
17
18
  require "enumerator"
19
+ require "innodb/util/buffer_cursor"
20
+ require "innodb/util/read_bits_at_offset"
18
21
 
19
22
  require "innodb/version"
20
23
  require "innodb/stats"
@@ -33,9 +36,16 @@ require "innodb/record"
33
36
  require "innodb/field"
34
37
  require "innodb/space"
35
38
  require "innodb/system"
39
+ require "innodb/history"
40
+ require "innodb/history_list"
36
41
  require "innodb/inode"
37
42
  require "innodb/index"
43
+ require "innodb/log_record"
38
44
  require "innodb/log_block"
39
45
  require "innodb/log"
46
+ require "innodb/lsn"
47
+ require "innodb/log_group"
48
+ require "innodb/log_reader"
40
49
  require "innodb/undo_log"
50
+ require "innodb/undo_record"
41
51
  require "innodb/xdes"
@@ -437,7 +437,7 @@ class Innodb::DataDictionary
437
437
  end
438
438
 
439
439
  unless table = table_by_name(table_name)
440
- raise "Table not found"
440
+ raise "Table #{table_name} not found"
441
441
  end
442
442
 
443
443
  each_index_by_table_id(table["ID"]) do |record|
@@ -467,7 +467,7 @@ class Innodb::DataDictionary
467
467
  end
468
468
 
469
469
  unless index = index_by_name(table_name, index_name)
470
- raise "Index not found"
470
+ raise "Index #{index_name} for table #{table_name} not found"
471
471
  end
472
472
 
473
473
  each_field_by_index_id(index["ID"]) do |record|
@@ -497,7 +497,7 @@ class Innodb::DataDictionary
497
497
  end
498
498
 
499
499
  unless table = table_by_name(table_name)
500
- raise "Table not found"
500
+ raise "Table #{table_name} not found"
501
501
  end
502
502
 
503
503
  each_column_by_table_id(table["ID"]) do |record|
@@ -543,7 +543,7 @@ class Innodb::DataDictionary
543
543
  # for a given table name.
544
544
  def clustered_index_name_by_table_name(table_name)
545
545
  unless table_record = table_by_name(table_name)
546
- raise "Table not found"
546
+ raise "Table #{table_name} not found"
547
547
  end
548
548
 
549
549
  if index_record = object_by_two_fields(:each_index,
@@ -576,7 +576,7 @@ class Innodb::DataDictionary
576
576
  end
577
577
 
578
578
  unless index = index_by_name(table_name, index_name)
579
- raise "Index not found"
579
+ raise "Index #{index_name} for table #{table_name} not found"
580
580
  end
581
581
 
582
582
  columns_in_index = {}
@@ -606,7 +606,7 @@ class Innodb::DataDictionary
606
606
  # index by table name and index name.
607
607
  def record_describer_by_index_name(table_name, index_name)
608
608
  unless index = index_by_name(table_name, index_name)
609
- raise "Index not found"
609
+ raise "Index #{index_name} for table #{table_name} not found"
610
610
  end
611
611
 
612
612
  describer = Innodb::RecordDescriber.new
@@ -633,11 +633,11 @@ class Innodb::DataDictionary
633
633
  # in a given index by index ID.
634
634
  def record_describer_by_index_id(index_id)
635
635
  unless index = index_by_id(index_id)
636
- raise "Index not found"
636
+ raise "Index #{index_id} not found"
637
637
  end
638
638
 
639
639
  unless table = table_by_id(index["TABLE_ID"])
640
- raise "Table not found"
640
+ raise "Table #{INDEX["TABLE_ID"]} not found"
641
641
  end
642
642
 
643
643
  record_describer_by_index_name(table["NAME"], index["NAME"])
@@ -317,6 +317,53 @@ class Innodb::DataType
317
317
  end
318
318
  end
319
319
 
320
+ #
321
+ # Data types for InnoDB system columns.
322
+ #
323
+
324
+ # Transaction ID.
325
+ class TransactionIdType
326
+ attr_reader :name, :width
327
+
328
+ def initialize(base_type, modifiers, properties)
329
+ @width = 6
330
+ @name = Innodb::DataType.make_name(base_type, modifiers, properties)
331
+ end
332
+
333
+ def read(c)
334
+ c.name("transaction_id") { c.get_hex(6) }
335
+ end
336
+ end
337
+
338
+ # Rollback data pointer.
339
+ class RollPointerType
340
+ extend ReadBitsAtOffset
341
+
342
+ attr_reader :name, :width
343
+
344
+ def initialize(base_type, modifiers, properties)
345
+ @width = 7
346
+ @name = Innodb::DataType.make_name(base_type, modifiers, properties)
347
+ end
348
+
349
+ def self.parse_roll_pointer(roll_ptr)
350
+ {
351
+ :is_insert => read_bits_at_offset(roll_ptr, 1, 55) == 1,
352
+ :rseg_id => read_bits_at_offset(roll_ptr, 7, 48),
353
+ :undo_log => {
354
+ :page => read_bits_at_offset(roll_ptr, 32, 16),
355
+ :offset => read_bits_at_offset(roll_ptr, 16, 0),
356
+ }
357
+ }
358
+ end
359
+
360
+ def value(data)
361
+ roll_ptr = BinData::Uint56be.read(data)
362
+ self.class.parse_roll_pointer(roll_ptr)
363
+ end
364
+
365
+ end
366
+
320
367
  # Maps base type to data type class.
321
368
  TYPES = {
322
369
  :BIT => BitType,
@@ -349,6 +396,8 @@ class Innodb::DataType
349
396
  :DATE => DateType,
350
397
  :DATETIME => DatetimeType,
351
398
  :TIMESTAMP => TimestampType,
399
+ :TRX_ID => TransactionIdType,
400
+ :ROLL_PTR => RollPointerType,
352
401
  }
353
402
 
354
403
  def self.make_name(base_type, modifiers, properties)
@@ -26,12 +26,12 @@ class Innodb::Field
26
26
 
27
27
  # Return whether this field is NULL.
28
28
  def null?(record)
29
- nullable? && record[:header][:field_nulls][position]
29
+ nullable? && record[:header][:nulls].include?(@name)
30
30
  end
31
31
 
32
32
  # Return whether a part of this field is stored externally (off-page).
33
33
  def extern?(record)
34
- record[:header][:field_externs][position]
34
+ record[:header][:externs].include?(@name)
35
35
  end
36
36
 
37
37
  def variable?
@@ -46,8 +46,9 @@ class Innodb::Field
46
46
 
47
47
  # Return the actual length of this variable-length field.
48
48
  def length(record)
49
- if variable?
50
- len = record[:header][:field_lengths][position]
49
+ if record[:header][:lengths].include?(@name)
50
+ len = record[:header][:lengths][@name]
51
+ raise "Fixed-length mismatch" unless variable? || len == @data_type.width
51
52
  else
52
53
  len = @data_type.width
53
54
  end
@@ -55,19 +56,28 @@ class Innodb::Field
55
56
  end
56
57
 
57
58
  # Read an InnoDB encoded data field.
58
- def read(record, cursor)
59
- cursor.name(@data_type.name) { cursor.get_bytes(length(record)) }
59
+ def read(cursor, field_length)
60
+ cursor.name(@data_type.name) { cursor.get_bytes(field_length) }
61
+ end
62
+
63
+ def value_by_length(cursor, field_length)
64
+ if @data_type.respond_to?(:read)
65
+ cursor.name(@data_type.name) { @data_type.read(cursor) }
66
+ elsif @data_type.respond_to?(:value)
67
+ @data_type.value(read(cursor, field_length))
68
+ else
69
+ read(cursor, field_length)
70
+ end
60
71
  end
61
72
 
62
73
  # Read the data value (e.g. encoded in the data).
63
- def value(record, cursor)
74
+ def value(cursor, record)
64
75
  return :NULL if null?(record)
65
- data = read(record, cursor)
66
- @data_type.respond_to?(:value) ? @data_type.value(data) : data
76
+ value_by_length(cursor, length(record))
67
77
  end
68
78
 
69
79
  # Read an InnoDB external pointer field.
70
- def extern(record, cursor)
80
+ def extern(cursor, record)
71
81
  return nil if not extern?(record)
72
82
  cursor.name(@name) { read_extern(cursor) }
73
83
  end
@@ -91,7 +101,7 @@ class Innodb::Field
91
101
 
92
102
  # Parse a data type definition and extract the base type and any modifiers.
93
103
  def parse_type_definition(type_string)
94
- if matches = /^([a-zA-Z0-9]+)(\(([0-9, ]+)\))?$/.match(type_string)
104
+ if matches = /^([a-zA-Z0-9_]+)(\(([0-9, ]+)\))?$/.match(type_string)
95
105
  base_type = matches[1].upcase.to_sym
96
106
  if matches[3]
97
107
  modifiers = matches[3].sub(/[ ]/, "").split(/,/).map { |s| s.to_i }
@@ -0,0 +1,30 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ # The global history of record versions implemented through undo logs.
4
+ class Innodb::History
5
+ def initialize(innodb_system)
6
+ @innodb_system = innodb_system
7
+ end
8
+
9
+ # A helper to get to the trx_sys page in the Innodb::System.
10
+ def trx_sys
11
+ @innodb_system.system_space.trx_sys
12
+ end
13
+
14
+ # A helper to get to the history_list of a given space_id and page number.
15
+ def history_list(space_id, page_number)
16
+ @innodb_system.space(space_id).page(page_number).history_list
17
+ end
18
+
19
+ # Iterate through all history lists (one per rollback segment, nominally
20
+ # there are 128 rollback segments).
21
+ def each_history_list
22
+ unless block_given?
23
+ return enum_for(:each_history_list)
24
+ end
25
+
26
+ trx_sys.rsegs.each do |slot|
27
+ yield history_list(slot[:space_id], slot[:page_number])
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,106 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ # A single history list; this is a more intelligent wrapper around the basic
4
+ # Innodb::List::History which is provided elsewhere.
5
+ class Innodb::HistoryList
6
+ attr_reader :list
7
+
8
+ # Initialize from a provided Innodb::List::History.
9
+ def initialize(list)
10
+ @list = list
11
+ end
12
+
13
+ class UndoRecordCursor
14
+ def initialize(history, undo_record, direction=:forward)
15
+ @history = history
16
+ @undo_record = undo_record
17
+
18
+ case undo_record
19
+ when :min
20
+ @undo_log_cursor = history.list.list_cursor(:min, direction)
21
+ if @undo_log = @undo_log_cursor.node
22
+ @undo_record_cursor = @undo_log.undo_record_cursor(:min, direction)
23
+ end
24
+ when :max
25
+ @undo_log_cursor = history.list.list_cursor(:max, direction)
26
+ if @undo_log = @undo_log_cursor.node
27
+ @undo_record_cursor = @undo_log.undo_record_cursor(:max, direction)
28
+ end
29
+ else
30
+ raise "Not implemented"
31
+ end
32
+ end
33
+
34
+ def undo_record
35
+ unless @undo_record_cursor
36
+ return nil
37
+ end
38
+
39
+ if rec = @undo_record_cursor.undo_record
40
+ return rec
41
+ end
42
+
43
+ case @direction
44
+ when :forward
45
+ next_undo_record
46
+ when :backward
47
+ prev_undo_record
48
+ end
49
+ end
50
+
51
+ def move_cursor(page, undo_record)
52
+ @undo_log = page
53
+ @undo_log_cursor = @undo_log.undo_record_cursor(undo_record, @direction)
54
+ end
55
+
56
+ def next_undo_record
57
+ if rec = @undo_record_cursor.undo_record
58
+ return rec
59
+ end
60
+
61
+ if undo_log = @undo_log_cursor.node
62
+ @undo_log = undo_log
63
+ @undo_record_cursor = @undo_log.undo_record_cursor(:min, @direction)
64
+ end
65
+
66
+ @undo_record_cursor.undo_record
67
+ end
68
+
69
+ def prev_undo_record
70
+ if rec = @undo_log_cursor.undo_record
71
+ return rec
72
+ end
73
+
74
+ if undo_log = @undo_log_cursor.node
75
+ @undo_log = undo_log
76
+ @undo_record_cursor = @undo_log.undo_record_cursor(:max, @direction)
77
+ end
78
+
79
+ @undo_record_cursor.undo_record
80
+ end
81
+
82
+ def each_undo_record
83
+ unless block_given?
84
+ return enum_for(:each_undo_record)
85
+ end
86
+
87
+ while rec = undo_record
88
+ yield rec
89
+ end
90
+ end
91
+ end
92
+
93
+ def undo_record_cursor(undo_record=:min, direction=:forward)
94
+ UndoRecordCursor.new(self, undo_record, direction)
95
+ end
96
+
97
+ def each_undo_record
98
+ unless block_given?
99
+ return enum_for(:each_undo_record)
100
+ end
101
+
102
+ undo_record_cursor.each_undo_record do |rec|
103
+ yield rec
104
+ end
105
+ end
106
+ end
@@ -261,7 +261,6 @@ class Innodb::Index
261
261
  class IndexCursor
262
262
  def initialize(index, record, direction)
263
263
  Innodb::Stats.increment :index_cursor_create
264
- @initial = true
265
264
  @index = index
266
265
  @direction = direction
267
266
  case record
@@ -278,66 +277,12 @@ class Innodb::Index
278
277
  @page = record.page
279
278
  @page_cursor = @page.record_cursor(record.offset, direction)
280
279
  end
281
- @record = @page_cursor.record
282
- end
283
-
284
- # Return the current record, mostly as a helper.
285
- def current_record
286
- @record
287
- end
288
-
289
- # Move to the next record in the forward direction and return it.
290
- def next_record
291
- Innodb::Stats.increment :index_cursor_next_record
292
-
293
- while true
294
- if rec = @page_cursor.record
295
- return rec
296
- end
297
-
298
- unless next_page = @page.next
299
- return nil
300
- end
301
-
302
- unless @page = @index.page(next_page)
303
- raise "Failed to load next page"
304
- end
305
-
306
- unless @page_cursor = @page.record_cursor(:min, @direction)
307
- raise "Failed to position cursor"
308
- end
309
- end
310
- end
311
-
312
- # Move to the previous record in the backward direction and return it.
313
- def prev_record
314
- Innodb::Stats.increment :index_cursor_prev_record
315
-
316
- while true
317
- if rec = @page_cursor.record
318
- return rec
319
- end
320
-
321
- unless prev_page = @page.prev
322
- return nil
323
- end
324
-
325
- unless @page = @index.page(prev_page)
326
- raise "Failed to load prev page"
327
- end
328
-
329
- unless @page_cursor = @page.record_cursor(:max, @direction)
330
- raise "Failed to position cursor"
331
- end
332
- end
333
- raise "Not implemented"
334
280
  end
335
281
 
336
282
  # Return the next record in the order defined when the cursor was created.
337
283
  def record
338
- if @initial
339
- @initial = false
340
- return current_record
284
+ if rec = @page_cursor.record
285
+ return rec
341
286
  end
342
287
 
343
288
  case @direction
@@ -358,6 +303,53 @@ class Innodb::Index
358
303
  yield rec
359
304
  end
360
305
  end
306
+
307
+ private
308
+
309
+ # Move the cursor to a new starting position in a given page.
310
+ def move_cursor(page, record)
311
+ unless @page = @index.page(page)
312
+ raise "Failed to load page"
313
+ end
314
+
315
+ unless @page_cursor = @page.record_cursor(record, @direction)
316
+ raise "Failed to position cursor"
317
+ end
318
+ end
319
+
320
+ # Move to the next record in the forward direction and return it.
321
+ def next_record
322
+ Innodb::Stats.increment :index_cursor_next_record
323
+
324
+ if rec = @page_cursor.record
325
+ return rec
326
+ end
327
+
328
+ unless next_page = @page.next
329
+ return nil
330
+ end
331
+
332
+ move_cursor(next_page, :min)
333
+
334
+ @page_cursor.record
335
+ end
336
+
337
+ # Move to the previous record in the backward direction and return it.
338
+ def prev_record
339
+ Innodb::Stats.increment :index_cursor_prev_record
340
+
341
+ if rec = @page_cursor.record
342
+ return rec
343
+ end
344
+
345
+ unless prev_page = @page.prev
346
+ return nil
347
+ end
348
+
349
+ move_cursor(prev_page, :max)
350
+
351
+ @page_cursor.record
352
+ end
361
353
  end
362
354
 
363
355
  # Return an IndexCursor starting at the given record (an Innodb::Record,