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.
- data/bin/innodb_log +100 -34
- data/bin/innodb_space +288 -9
- data/lib/innodb.rb +10 -0
- data/lib/innodb/data_dictionary.rb +8 -8
- data/lib/innodb/data_type.rb +49 -0
- data/lib/innodb/field.rb +21 -11
- data/lib/innodb/history.rb +30 -0
- data/lib/innodb/history_list.rb +106 -0
- data/lib/innodb/index.rb +49 -57
- data/lib/innodb/inode.rb +11 -1
- data/lib/innodb/list.rb +45 -23
- data/lib/innodb/log.rb +22 -11
- data/lib/innodb/log_block.rb +52 -82
- data/lib/innodb/log_group.rb +59 -54
- data/lib/innodb/log_reader.rb +116 -0
- data/lib/innodb/log_record.rb +317 -0
- data/lib/innodb/lsn.rb +103 -0
- data/lib/innodb/page.rb +39 -5
- data/lib/innodb/page/blob.rb +26 -0
- data/lib/innodb/page/fsp_hdr_xdes.rb +38 -6
- data/lib/innodb/page/index.rb +176 -96
- data/lib/innodb/page/inode.rb +33 -1
- data/lib/innodb/page/sys_data_dictionary_header.rb +19 -0
- data/lib/innodb/page/sys_rseg_header.rb +41 -2
- data/lib/innodb/page/trx_sys.rb +69 -1
- data/lib/innodb/record.rb +37 -0
- data/lib/innodb/space.rb +28 -4
- data/lib/innodb/system.rb +4 -0
- data/lib/innodb/undo_log.rb +84 -24
- data/lib/innodb/undo_record.rb +259 -0
- data/lib/innodb/{cursor.rb → util/buffer_cursor.rb} +135 -29
- data/lib/innodb/util/read_bits_at_offset.rb +8 -0
- data/lib/innodb/version.rb +1 -1
- data/lib/innodb/xdes.rb +2 -0
- metadata +10 -3
data/lib/innodb.rb
CHANGED
@@ -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"])
|
data/lib/innodb/data_type.rb
CHANGED
@@ -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)
|
data/lib/innodb/field.rb
CHANGED
@@ -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][:
|
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][:
|
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
|
50
|
-
len = record[:header][:
|
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(
|
59
|
-
cursor.name(@data_type.name) { cursor.get_bytes(
|
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(
|
74
|
+
def value(cursor, record)
|
64
75
|
return :NULL if null?(record)
|
65
|
-
|
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(
|
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-
|
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
|
data/lib/innodb/index.rb
CHANGED
@@ -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 @
|
339
|
-
|
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,
|