innodb_ruby 0.9.16 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +5 -6
- data/bin/innodb_log +13 -18
- data/bin/innodb_space +377 -757
- data/lib/innodb.rb +4 -5
- data/lib/innodb/checksum.rb +26 -24
- data/lib/innodb/data_dictionary.rb +490 -550
- data/lib/innodb/data_type.rb +362 -326
- 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 +154 -155
- data/lib/innodb/list.rb +191 -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.rb +417 -414
- 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 +964 -943
- 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/record.rb +121 -160
- data/lib/innodb/record_describer.rb +66 -68
- data/lib/innodb/space.rb +380 -418
- 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
- metadata +80 -11
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
|