innodb_ruby 0.9.0 → 0.9.5

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