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