innodb_ruby 0.9.14 → 0.12.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.
- checksums.yaml +7 -0
- data/README.md +5 -6
- data/bin/innodb_log +13 -18
- data/bin/innodb_space +654 -778
- data/lib/innodb/checksum.rb +26 -24
- data/lib/innodb/data_dictionary.rb +490 -550
- data/lib/innodb/data_type.rb +362 -325
- data/lib/innodb/field.rb +102 -89
- data/lib/innodb/fseg_entry.rb +22 -26
- data/lib/innodb/history.rb +21 -21
- data/lib/innodb/history_list.rb +72 -76
- data/lib/innodb/ibuf_bitmap.rb +36 -36
- data/lib/innodb/ibuf_index.rb +6 -2
- data/lib/innodb/index.rb +245 -276
- data/lib/innodb/inode.rb +166 -124
- data/lib/innodb/list.rb +196 -183
- data/lib/innodb/log.rb +139 -110
- data/lib/innodb/log_block.rb +100 -91
- data/lib/innodb/log_group.rb +53 -64
- data/lib/innodb/log_reader.rb +97 -96
- data/lib/innodb/log_record.rb +328 -279
- data/lib/innodb/lsn.rb +86 -81
- data/lib/innodb/page/blob.rb +82 -83
- data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
- data/lib/innodb/page/ibuf_bitmap.rb +34 -34
- data/lib/innodb/page/index.rb +965 -924
- data/lib/innodb/page/index_compressed.rb +34 -34
- data/lib/innodb/page/inode.rb +103 -112
- data/lib/innodb/page/sys.rb +13 -15
- data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
- data/lib/innodb/page/sys_ibuf_header.rb +45 -42
- data/lib/innodb/page/sys_rseg_header.rb +88 -82
- data/lib/innodb/page/trx_sys.rb +204 -182
- data/lib/innodb/page/undo_log.rb +106 -92
- data/lib/innodb/page.rb +417 -414
- data/lib/innodb/record.rb +121 -164
- data/lib/innodb/record_describer.rb +66 -68
- data/lib/innodb/space.rb +381 -413
- data/lib/innodb/stats.rb +33 -35
- data/lib/innodb/system.rb +149 -171
- data/lib/innodb/undo_log.rb +129 -107
- data/lib/innodb/undo_record.rb +255 -247
- data/lib/innodb/util/buffer_cursor.rb +81 -79
- data/lib/innodb/util/read_bits_at_offset.rb +2 -1
- data/lib/innodb/version.rb +2 -2
- data/lib/innodb/xdes.rb +144 -142
- data/lib/innodb.rb +4 -5
- metadata +100 -25
data/lib/innodb/log.rb
CHANGED
@@ -1,126 +1,155 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# An InnoDB transaction log file.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# Number of blocks in the log file header.
|
14
|
-
LOG_HEADER_BLOCKS = LOG_HEADER_BLOCK_MAP.size
|
15
|
-
|
16
|
-
# The size in bytes of the log file header.
|
17
|
-
LOG_HEADER_SIZE = LOG_HEADER_BLOCKS * Innodb::LogBlock::BLOCK_SIZE
|
18
|
-
|
19
|
-
# Maximum number of log group checkpoints.
|
20
|
-
LOG_CHECKPOINT_GROUPS = 32
|
21
|
-
|
22
|
-
# Open a log file.
|
23
|
-
def initialize(filename)
|
24
|
-
@file = File.open(filename)
|
25
|
-
@size = @file.stat.size
|
26
|
-
@blocks = (@size / Innodb::LogBlock::BLOCK_SIZE) - LOG_HEADER_BLOCKS
|
27
|
-
@capacity = @blocks * Innodb::LogBlock::BLOCK_SIZE
|
28
|
-
end
|
4
|
+
module Innodb
|
5
|
+
class Log
|
6
|
+
Header = Struct.new(
|
7
|
+
:group_id,
|
8
|
+
:start_lsn,
|
9
|
+
:file_no,
|
10
|
+
:created_by,
|
11
|
+
keyword_init: true
|
12
|
+
)
|
29
13
|
|
30
|
-
|
31
|
-
|
14
|
+
Checkpoint = Struct.new(
|
15
|
+
:number,
|
16
|
+
:lsn,
|
17
|
+
:lsn_offset,
|
18
|
+
:buffer_size,
|
19
|
+
:archived_lsn,
|
20
|
+
:group_array,
|
21
|
+
:checksum_1,
|
22
|
+
:checksum_2,
|
23
|
+
:fsp_free_limit,
|
24
|
+
:fsp_magic,
|
25
|
+
keyword_init: true
|
26
|
+
)
|
32
27
|
|
33
|
-
|
34
|
-
|
28
|
+
CheckpointGroup = Struct.new(
|
29
|
+
:archived_file_no,
|
30
|
+
:archived_offset,
|
31
|
+
keyword_init: true
|
32
|
+
)
|
35
33
|
|
36
|
-
|
37
|
-
|
34
|
+
CheckpointSet = Struct.new(
|
35
|
+
:checkpoint_1,
|
36
|
+
:checkpoint_2,
|
37
|
+
keyword_init: true
|
38
|
+
)
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
# A map of the name and position of the blocks that form the log header.
|
41
|
+
LOG_HEADER_BLOCK_MAP = {
|
42
|
+
LOG_FILE_HEADER: 0,
|
43
|
+
LOG_CHECKPOINT_1: 1,
|
44
|
+
EMPTY: 2,
|
45
|
+
LOG_CHECKPOINT_2: 3,
|
46
|
+
}.freeze
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
BufferCursor.new(block_data(offset), 0)
|
49
|
-
end
|
48
|
+
# Number of blocks in the log file header.
|
49
|
+
LOG_HEADER_BLOCKS = LOG_HEADER_BLOCK_MAP.size
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
51
|
+
# The size in bytes of the log file header.
|
52
|
+
LOG_HEADER_SIZE = LOG_HEADER_BLOCKS * Innodb::LogBlock::BLOCK_SIZE
|
53
|
+
|
54
|
+
# Maximum number of log group checkpoints.
|
55
|
+
LOG_CHECKPOINT_GROUPS = 32
|
56
|
+
|
57
|
+
# Open a log file.
|
58
|
+
def initialize(filename)
|
59
|
+
@file = File.open(filename)
|
60
|
+
@size = @file.stat.size
|
61
|
+
@blocks = (@size / Innodb::LogBlock::BLOCK_SIZE) - LOG_HEADER_BLOCKS
|
62
|
+
@capacity = @blocks * Innodb::LogBlock::BLOCK_SIZE
|
61
63
|
end
|
62
|
-
end
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
# Log archive related fields (e.g. group_array) are not currently in
|
67
|
-
# use or even read by InnoDB. However, for the sake of completeness,
|
68
|
-
# they are included.
|
69
|
-
{
|
70
|
-
:number => c.name("number") { c.get_uint64 },
|
71
|
-
:lsn => c.name("lsn") { c.get_uint64 },
|
72
|
-
:lsn_offset => c.name("lsn_offset") { c.get_uint32 },
|
73
|
-
:buffer_size => c.name("buffer_size") { c.get_uint32 },
|
74
|
-
:archived_lsn => c.name("archived_lsn") { c.get_uint64 },
|
75
|
-
:group_array =>
|
76
|
-
(0 .. LOG_CHECKPOINT_GROUPS - 1).map do |n|
|
77
|
-
c.name("group_array[#{n}]") do
|
78
|
-
{
|
79
|
-
:archived_file_no => c.name("archived_file_no") { c.get_uint32 },
|
80
|
-
:archived_offset => c.name("archived_offset") { c.get_uint32 },
|
81
|
-
}
|
82
|
-
end
|
83
|
-
end,
|
84
|
-
:checksum_1 => c.name("checksum_1") { c.get_uint32 },
|
85
|
-
:checksum_2 => c.name("checksum_2") { c.get_uint32 },
|
86
|
-
:fsp_free_limit => c.name("fsp_free_limit") { c.get_uint32 },
|
87
|
-
:fsp_magic => c.name("fsp_magic") { c.get_uint32 },
|
88
|
-
}
|
89
|
-
end
|
65
|
+
# The size (in bytes) of the log.
|
66
|
+
attr_reader :size
|
90
67
|
|
91
|
-
|
92
|
-
|
93
|
-
offset1 = LOG_HEADER_BLOCK_MAP[:LOG_CHECKPOINT_1] * Innodb::LogBlock::BLOCK_SIZE
|
94
|
-
offset2 = LOG_HEADER_BLOCK_MAP[:LOG_CHECKPOINT_2] * Innodb::LogBlock::BLOCK_SIZE
|
95
|
-
@checkpoint ||=
|
96
|
-
{
|
97
|
-
:checkpoint_1 => block_cursor(offset1).name("checkpoint_1") do |cursor|
|
98
|
-
cp = read_checkpoint(cursor)
|
99
|
-
cp.delete(:group_array)
|
100
|
-
cp
|
101
|
-
end,
|
102
|
-
:checkpoint_2 => block_cursor(offset2).name("checkpoint_2") do |cursor|
|
103
|
-
cp = read_checkpoint(cursor)
|
104
|
-
cp.delete(:group_array)
|
105
|
-
cp
|
106
|
-
end
|
107
|
-
}
|
108
|
-
end
|
68
|
+
# The log capacity (in bytes).
|
69
|
+
attr_reader :capacity
|
109
70
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
71
|
+
# The number of blocks in the log.
|
72
|
+
attr_reader :blocks
|
73
|
+
|
74
|
+
# Get the raw byte buffer for a specific block by block offset.
|
75
|
+
def block_data(offset)
|
76
|
+
raise "Invalid block offset" unless (offset % Innodb::LogBlock::BLOCK_SIZE).zero?
|
77
|
+
|
78
|
+
@file.sysseek(offset)
|
79
|
+
@file.sysread(Innodb::LogBlock::BLOCK_SIZE)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Get a cursor to a block in a given offset of the log.
|
83
|
+
def block_cursor(offset)
|
84
|
+
BufferCursor.new(block_data(offset), 0)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return the log header.
|
88
|
+
def header
|
89
|
+
offset = LOG_HEADER_BLOCK_MAP[:LOG_FILE_HEADER] * Innodb::LogBlock::BLOCK_SIZE
|
90
|
+
@header ||= block_cursor(offset).name("header") do |c|
|
91
|
+
Header.new(
|
92
|
+
group_id: c.name("group_id") { c.read_uint32 },
|
93
|
+
start_lsn: c.name("start_lsn") { c.read_uint64 },
|
94
|
+
file_no: c.name("file_no") { c.read_uint32 },
|
95
|
+
created_by: c.name("created_by") { c.read_string(32) }
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Read a log checkpoint from the given cursor.
|
101
|
+
def read_checkpoint(cursor)
|
102
|
+
# Log archive related fields (e.g. group_array) are not currently in
|
103
|
+
# use or even read by InnoDB. However, for the sake of completeness,
|
104
|
+
# they are included.
|
105
|
+
Checkpoint.new(
|
106
|
+
number: cursor.name("number") { cursor.read_uint64 },
|
107
|
+
lsn: cursor.name("lsn") { cursor.read_uint64 },
|
108
|
+
lsn_offset: cursor.name("lsn_offset") { cursor.read_uint32 },
|
109
|
+
buffer_size: cursor.name("buffer_size") { cursor.read_uint32 },
|
110
|
+
archived_lsn: cursor.name("archived_lsn") { cursor.read_uint64 },
|
111
|
+
group_array:
|
112
|
+
(0..(LOG_CHECKPOINT_GROUPS - 1)).map do |n|
|
113
|
+
cursor.name("group_array[#{n}]") do
|
114
|
+
CheckpointGroup.new(
|
115
|
+
archived_file_no: cursor.name("archived_file_no") { cursor.read_uint32 },
|
116
|
+
archived_offset: cursor.name("archived_offset") { cursor.read_uint32 }
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end,
|
120
|
+
checksum_1: cursor.name("checksum_1") { cursor.read_uint32 },
|
121
|
+
checksum_2: cursor.name("checksum_2") { cursor.read_uint32 },
|
122
|
+
fsp_free_limit: cursor.name("fsp_free_limit") { cursor.read_uint32 },
|
123
|
+
fsp_magic: cursor.name("fsp_magic") { cursor.read_uint32 }
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Return the log checkpoints.
|
128
|
+
def checkpoint
|
129
|
+
offset1 = LOG_HEADER_BLOCK_MAP[:LOG_CHECKPOINT_1] * Innodb::LogBlock::BLOCK_SIZE
|
130
|
+
offset2 = LOG_HEADER_BLOCK_MAP[:LOG_CHECKPOINT_2] * Innodb::LogBlock::BLOCK_SIZE
|
131
|
+
@checkpoint ||= CheckpointSet.new(
|
132
|
+
checkpoint_1: block_cursor(offset1).name("checkpoint_1") { |c| read_checkpoint(c) },
|
133
|
+
checkpoint_2: block_cursor(offset2).name("checkpoint_2") { |c| read_checkpoint(c) }
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return a log block with a given block index as an InnoDB::LogBlock object.
|
138
|
+
# Blocks are indexed after the log file header, starting from 0.
|
139
|
+
def block(block_index)
|
140
|
+
return nil unless block_index.between?(0, @blocks - 1)
|
141
|
+
|
142
|
+
offset = (LOG_HEADER_BLOCKS + block_index.to_i) * Innodb::LogBlock::BLOCK_SIZE
|
143
|
+
Innodb::LogBlock.new(block_data(offset))
|
144
|
+
end
|
117
145
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
146
|
+
# Iterate through all log blocks, returning the block index and an
|
147
|
+
# InnoDB::LogBlock object for each block.
|
148
|
+
def each_block
|
149
|
+
(0...@blocks).each do |block_index|
|
150
|
+
current_block = block(block_index)
|
151
|
+
yield block_index, current_block if current_block
|
152
|
+
end
|
124
153
|
end
|
125
154
|
end
|
126
155
|
end
|
data/lib/innodb/log_block.rb
CHANGED
@@ -1,120 +1,129 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
2
4
|
|
3
5
|
# An InnoDB transaction log block.
|
4
|
-
|
5
|
-
|
6
|
-
|
6
|
+
module Innodb
|
7
|
+
class LogBlock
|
8
|
+
extend Forwardable
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
+
Header = Struct.new(
|
11
|
+
:flush,
|
12
|
+
:block_number,
|
13
|
+
:data_length,
|
14
|
+
:first_rec_group,
|
15
|
+
:checkpoint_no,
|
16
|
+
keyword_init: true
|
17
|
+
)
|
10
18
|
|
11
|
-
|
12
|
-
|
19
|
+
Trailer = Struct.new(
|
20
|
+
:checksum,
|
21
|
+
keyword_init: true
|
22
|
+
)
|
13
23
|
|
14
|
-
|
15
|
-
|
24
|
+
# Log blocks are fixed-length at 512 bytes in InnoDB.
|
25
|
+
BLOCK_SIZE = 512
|
16
26
|
|
17
|
-
|
18
|
-
|
27
|
+
# Offset of the header within the log block.
|
28
|
+
HEADER_OFFSET = 0
|
19
29
|
|
20
|
-
|
21
|
-
|
30
|
+
# The size of the block header.
|
31
|
+
HEADER_SIZE = 4 + 2 + 2 + 4
|
22
32
|
|
23
|
-
|
24
|
-
|
33
|
+
# Offset of the trailer within ths log block.
|
34
|
+
TRAILER_OFFSET = BLOCK_SIZE - 4
|
25
35
|
|
26
|
-
|
27
|
-
|
36
|
+
# The size of the block trailer.
|
37
|
+
TRAILER_SIZE = 4
|
28
38
|
|
29
|
-
|
30
|
-
|
31
|
-
def initialize(buffer)
|
32
|
-
unless buffer.size == BLOCK_SIZE
|
33
|
-
raise "Log block buffer provided was not #{BLOCK_SIZE} bytes"
|
34
|
-
end
|
39
|
+
# Offset of the start of data in the block.
|
40
|
+
DATA_OFFSET = HEADER_SIZE
|
35
41
|
|
36
|
-
|
37
|
-
|
42
|
+
# Size of the space available for log records.
|
43
|
+
DATA_SIZE = BLOCK_SIZE - HEADER_SIZE - TRAILER_SIZE
|
38
44
|
|
39
|
-
|
40
|
-
|
41
|
-
BufferCursor.new(@buffer, offset)
|
42
|
-
end
|
45
|
+
# Mask used to get the flush bit in the header.
|
46
|
+
HEADER_FLUSH_BIT_MASK = 0x80000000
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
{
|
48
|
-
:flush => c.name("flush") {
|
49
|
-
c.peek { (c.get_uint32 & HEADER_FLUSH_BIT_MASK) > 0 }
|
50
|
-
},
|
51
|
-
:block_number => c.name("block_number") {
|
52
|
-
c.get_uint32 & ~HEADER_FLUSH_BIT_MASK
|
53
|
-
},
|
54
|
-
:data_length => c.name("data_length") { c.get_uint16 },
|
55
|
-
:first_rec_group => c.name("first_rec_group") { c.get_uint16 },
|
56
|
-
:checkpoint_no => c.name("checkpoint_no") { c.get_uint32 },
|
57
|
-
}
|
58
|
-
end
|
59
|
-
end
|
48
|
+
# Initialize a log block by passing in a 512-byte buffer containing the raw
|
49
|
+
# log block contents.
|
50
|
+
def initialize(buffer)
|
51
|
+
raise "Log block buffer provided was not #{BLOCK_SIZE} bytes" unless buffer.size == BLOCK_SIZE
|
60
52
|
|
61
|
-
|
62
|
-
|
63
|
-
def data(offset = DATA_OFFSET)
|
64
|
-
length = header[:data_length]
|
53
|
+
@buffer = buffer
|
54
|
+
end
|
65
55
|
|
66
|
-
|
67
|
-
|
56
|
+
# Return an BufferCursor object positioned at a specific offset.
|
57
|
+
def cursor(offset)
|
58
|
+
BufferCursor.new(@buffer, offset)
|
68
59
|
end
|
69
60
|
|
70
|
-
|
71
|
-
|
61
|
+
# Return the log block header.
|
62
|
+
def header
|
63
|
+
@header ||= cursor(HEADER_OFFSET).name("header") do |c|
|
64
|
+
Header.new(
|
65
|
+
flush: c.name("flush") { c.peek { (c.read_uint32 & HEADER_FLUSH_BIT_MASK).positive? } },
|
66
|
+
block_number: c.name("block_number") { c.read_uint32 & ~HEADER_FLUSH_BIT_MASK },
|
67
|
+
data_length: c.name("data_length") { c.read_uint16 },
|
68
|
+
first_rec_group: c.name("first_rec_group") { c.read_uint16 },
|
69
|
+
checkpoint_no: c.name("checkpoint_no") { c.read_uint32 }
|
70
|
+
)
|
71
|
+
end
|
72
72
|
end
|
73
73
|
|
74
|
-
|
75
|
-
|
74
|
+
def_delegator :header, :flush
|
75
|
+
def_delegator :header, :block_number
|
76
|
+
def_delegator :header, :data_length
|
77
|
+
def_delegator :header, :first_rec_group
|
78
|
+
def_delegator :header, :checkpoint_no
|
79
|
+
|
80
|
+
# Return a slice of actual block data (that is, excluding header and
|
81
|
+
# trailer) starting at the given offset.
|
82
|
+
def data(offset = DATA_OFFSET)
|
83
|
+
length = data_length
|
84
|
+
length -= TRAILER_SIZE if length == BLOCK_SIZE
|
76
85
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
{
|
81
|
-
:checksum => c.name("checksum") { c.get_uint32 },
|
82
|
-
}
|
86
|
+
raise "Invalid block data offset" if offset < DATA_OFFSET || offset > length
|
87
|
+
|
88
|
+
@buffer.slice(offset, length - offset)
|
83
89
|
end
|
84
|
-
end
|
85
90
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
+
# Return the log block trailer.
|
92
|
+
def trailer
|
93
|
+
@trailer ||= cursor(TRAILER_OFFSET).name("trailer") do |c|
|
94
|
+
Trailer.new(checksum: c.name("checksum") { c.read_uint32 })
|
95
|
+
end
|
96
|
+
end
|
91
97
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
98
|
+
def_delegator :trailer, :checksum
|
99
|
+
|
100
|
+
# Calculate the checksum of the block using InnoDB's log block
|
101
|
+
# checksum algorithm.
|
102
|
+
def calculate_checksum
|
103
|
+
csum = 1
|
104
|
+
shift = (0..24).cycle
|
105
|
+
cursor(0).each_byte_as_uint8(TRAILER_OFFSET) do |b|
|
106
|
+
csum &= 0x7fffffff
|
107
|
+
csum += b + (b << shift.next)
|
108
|
+
end
|
109
|
+
csum
|
100
110
|
end
|
101
|
-
cksum
|
102
|
-
end
|
103
111
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
112
|
+
# Is the block corrupt? Calculate the checksum of the block and compare to
|
113
|
+
# the stored checksum; return true or false.
|
114
|
+
def corrupt?
|
115
|
+
checksum != calculate_checksum
|
116
|
+
end
|
109
117
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
118
|
+
# Dump the contents of a log block for debugging purposes.
|
119
|
+
def dump
|
120
|
+
puts
|
121
|
+
puts "header:"
|
122
|
+
pp header
|
115
123
|
|
116
|
-
|
117
|
-
|
118
|
-
|
124
|
+
puts
|
125
|
+
puts "trailer:"
|
126
|
+
pp trailer
|
127
|
+
end
|
119
128
|
end
|
120
129
|
end
|