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
@@ -0,0 +1,116 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
|
5
|
+
# Representation of the log group as a seekable stream of log records.
|
6
|
+
class Innodb::LogReader
|
7
|
+
|
8
|
+
# Whether to checksum blocks.
|
9
|
+
attr_accessor :checksum
|
10
|
+
|
11
|
+
def initialize(lsn, group)
|
12
|
+
@group = group
|
13
|
+
@context = OpenStruct.new(:buffer => String.new,
|
14
|
+
:buffer_lsn => lsn.dup, :record_lsn => lsn.dup)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Seek to record starting position.
|
18
|
+
def seek(lsn_no)
|
19
|
+
check_lsn_no(lsn_no)
|
20
|
+
@context.buffer = String.new
|
21
|
+
@context.buffer_lsn.reposition(lsn_no, @group)
|
22
|
+
@context.record_lsn = @context.buffer_lsn.dup
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the current LSN starting position.
|
27
|
+
def tell
|
28
|
+
@context.record_lsn.no
|
29
|
+
end
|
30
|
+
|
31
|
+
# Read a record.
|
32
|
+
def record
|
33
|
+
cursor = BufferCursor.new(self, 0)
|
34
|
+
record = Innodb::LogRecord.new
|
35
|
+
record.read(cursor)
|
36
|
+
record.lsn = reposition(cursor.position)
|
37
|
+
record
|
38
|
+
end
|
39
|
+
|
40
|
+
# Call the given block once for each record in the log until the
|
41
|
+
# end of the log (or a corrupted block) is reached. If the follow
|
42
|
+
# argument is true, retry.
|
43
|
+
def each_record(follow, wait=0.5)
|
44
|
+
begin
|
45
|
+
loop { yield record }
|
46
|
+
rescue EOFError, ChecksumError
|
47
|
+
sleep(wait) and retry if follow
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Read a slice of log data (that is, log data used for records).
|
52
|
+
def slice(position, length)
|
53
|
+
buffer = @context.buffer
|
54
|
+
length = position + length
|
55
|
+
|
56
|
+
if length > buffer.size
|
57
|
+
preload(length)
|
58
|
+
end
|
59
|
+
|
60
|
+
buffer.slice(position, length - position)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Checksum failed exception.
|
64
|
+
class ChecksumError < RuntimeError
|
65
|
+
end
|
66
|
+
|
67
|
+
# EOF reached exception.
|
68
|
+
class EOFError < EOFError
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Check if LSN points to where records may be located.
|
74
|
+
def check_lsn_no(lsn_no)
|
75
|
+
lsn = @context.record_lsn.dup
|
76
|
+
lsn.reposition(lsn_no, @group)
|
77
|
+
raise "LSN #{lsn_no} is out of bounds" unless lsn.record?(@group)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Reposition to the beginning of the next record.
|
81
|
+
def reposition(length)
|
82
|
+
start_lsn_no = @context.record_lsn.no
|
83
|
+
delta_lsn_no = @context.record_lsn.delta(length)
|
84
|
+
@context.record_lsn.advance(delta_lsn_no, @group)
|
85
|
+
@context.buffer.slice!(0, length)
|
86
|
+
[start_lsn_no, start_lsn_no + delta_lsn_no]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Reads the log block at the given LSN position.
|
90
|
+
def get_block(lsn)
|
91
|
+
log_no, block_no, block_offset = lsn.location(@group)
|
92
|
+
[@group.log(log_no).block(block_no), block_offset]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Preload the log buffer with enough data to satisfy the requested amount.
|
96
|
+
def preload(size)
|
97
|
+
buffer = @context.buffer
|
98
|
+
buffer_lsn = @context.buffer_lsn
|
99
|
+
|
100
|
+
# If reading for the first time, offset points to the start of the
|
101
|
+
# record (somewhere in the block). Otherwise, the block is read as
|
102
|
+
# a whole and offset points to the start of the next block to read.
|
103
|
+
while buffer.size < size
|
104
|
+
block, offset = get_block(buffer_lsn)
|
105
|
+
break if checksum && corrupt = block.corrupt?
|
106
|
+
data = offset == 0 ? block.data : block.data(offset)
|
107
|
+
data_length = block.header[:data_length]
|
108
|
+
buffer << data
|
109
|
+
buffer_lsn.advance(data_length - offset, @group)
|
110
|
+
break if data_length < Innodb::LogBlock::BLOCK_SIZE
|
111
|
+
end
|
112
|
+
|
113
|
+
raise ChecksumError, "Block is corrupted" if corrupt
|
114
|
+
raise EOFError, "End of log reached" if buffer.size < size
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# An InnoDB transaction log block.
|
4
|
+
class Innodb::LogRecord
|
5
|
+
# Start and end LSNs for this record.
|
6
|
+
attr_accessor :lsn
|
7
|
+
|
8
|
+
# The size (in bytes) of the record.
|
9
|
+
attr_reader :size
|
10
|
+
|
11
|
+
attr_reader :preamble
|
12
|
+
|
13
|
+
attr_reader :payload
|
14
|
+
|
15
|
+
# InnoDB log record types.
|
16
|
+
RECORD_TYPES =
|
17
|
+
{
|
18
|
+
1 => :MLOG_1BYTE, 2 => :MLOG_2BYTE,
|
19
|
+
4 => :MLOG_4BYTE, 8 => :MLOG_8BYTE,
|
20
|
+
9 => :REC_INSERT, 10 => :REC_CLUST_DELETE_MARK,
|
21
|
+
11 => :REC_SEC_DELETE_MARK, 13 => :REC_UPDATE_IN_PLACE,
|
22
|
+
14 => :REC_DELETE, 15 => :LIST_END_DELETE,
|
23
|
+
16 => :LIST_START_DELETE, 17 => :LIST_END_COPY_CREATED,
|
24
|
+
18 => :PAGE_REORGANIZE, 19 => :PAGE_CREATE,
|
25
|
+
20 => :UNDO_INSERT, 21 => :UNDO_ERASE_END,
|
26
|
+
22 => :UNDO_INIT, 23 => :UNDO_HDR_DISCARD,
|
27
|
+
24 => :UNDO_HDR_REUSE, 25 => :UNDO_HDR_CREATE,
|
28
|
+
26 => :REC_MIN_MARK, 27 => :IBUF_BITMAP_INIT,
|
29
|
+
28 => :LSN, 29 => :INIT_FILE_PAGE,
|
30
|
+
30 => :WRITE_STRING, 31 => :MULTI_REC_END,
|
31
|
+
32 => :DUMMY_RECORD, 33 => :FILE_CREATE,
|
32
|
+
34 => :FILE_RENAME, 35 => :FILE_DELETE,
|
33
|
+
36 => :COMP_REC_MIN_MARK, 37 => :COMP_PAGE_CREATE,
|
34
|
+
38 => :COMP_REC_INSERT, 39 => :COMP_REC_CLUST_DELETE_MARK,
|
35
|
+
40 => :COMP_REC_SEC_DELETE_MARK, 41 => :COMP_REC_UPDATE_IN_PLACE,
|
36
|
+
42 => :COMP_REC_DELETE, 43 => :COMP_LIST_END_DELETE,
|
37
|
+
44 => :COMP_LIST_START_DELETE, 45 => :COMP_LIST_END_COPY_CREATE,
|
38
|
+
46 => :COMP_PAGE_REORGANIZE, 47 => :FILE_CREATE2,
|
39
|
+
48 => :ZIP_WRITE_NODE_PTR, 49 => :ZIP_WRITE_BLOB_PTR,
|
40
|
+
50 => :ZIP_WRITE_HEADER, 51 => :ZIP_PAGE_COMPRESS,
|
41
|
+
}
|
42
|
+
|
43
|
+
# Types of undo log segments.
|
44
|
+
UNDO_TYPES = { 1 => :UNDO_INSERT, 2 => :UNDO_UPDATE }
|
45
|
+
|
46
|
+
def read(cursor)
|
47
|
+
origin = cursor.position
|
48
|
+
@preamble = read_preamble(cursor)
|
49
|
+
@payload = read_payload(@preamble[:type], cursor)
|
50
|
+
@size = cursor.position - origin
|
51
|
+
end
|
52
|
+
|
53
|
+
# Dump the contents of the record.
|
54
|
+
def dump
|
55
|
+
pp({:lsn => lsn, :size => size, :content => @preamble.merge(@payload)})
|
56
|
+
end
|
57
|
+
|
58
|
+
# Single record flag is masked in the record type.
|
59
|
+
SINGLE_RECORD_MASK = 0x80
|
60
|
+
RECORD_TYPE_MASK = 0x7f
|
61
|
+
|
62
|
+
# Return a preamble of the first record in this block.
|
63
|
+
def read_preamble(c)
|
64
|
+
type_and_flag = c.name("type") { c.get_uint8 }
|
65
|
+
type = type_and_flag & RECORD_TYPE_MASK
|
66
|
+
type = RECORD_TYPES[type] || type
|
67
|
+
# Whether this is a single record for a single page.
|
68
|
+
single_record = (type_and_flag & SINGLE_RECORD_MASK) > 0
|
69
|
+
case type
|
70
|
+
when :MULTI_REC_END, :DUMMY_RECORD
|
71
|
+
{ :type => type }
|
72
|
+
else
|
73
|
+
{
|
74
|
+
:type => type,
|
75
|
+
:single_record => single_record,
|
76
|
+
:space => c.name("space") { c.get_ic_uint32 },
|
77
|
+
:page_number => c.name("page_number") { c.get_ic_uint32 },
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Read the index part of a log record for a compact record insert.
|
83
|
+
# Ref. mlog_parse_index
|
84
|
+
def read_index(c)
|
85
|
+
n_cols = c.name("n_cols") { c.get_uint16 }
|
86
|
+
n_uniq = c.name("n_uniq") { c.get_uint16 }
|
87
|
+
cols = n_cols.times.collect do
|
88
|
+
info = c.name("field_info") { c.get_uint16 }
|
89
|
+
{
|
90
|
+
:mtype => ((info + 1) & 0x7fff) <= 1 ? :BINARY : :FIXBINARY,
|
91
|
+
:prtype => (info & 0x8000) != 0 ? :NOT_NULL : nil,
|
92
|
+
:length => info & 0x7fff
|
93
|
+
}
|
94
|
+
end
|
95
|
+
{
|
96
|
+
:n_cols => n_cols,
|
97
|
+
:n_uniq => n_uniq,
|
98
|
+
:cols => cols,
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
# Flag of whether an insert log record contains info and status.
|
103
|
+
INFO_AND_STATUS_MASK = 0x1
|
104
|
+
|
105
|
+
# Read the insert record into page part of a insert log.
|
106
|
+
# Ref. page_cur_parse_insert_rec
|
107
|
+
def read_insert_record(c)
|
108
|
+
page_offset = c.name("page_offset") { c.get_uint16 }
|
109
|
+
end_seg_len = c.name("end_seg_len") { c.get_ic_uint32 }
|
110
|
+
|
111
|
+
if (end_seg_len & INFO_AND_STATUS_MASK) != 0
|
112
|
+
info_and_status_bits = c.get_uint8
|
113
|
+
origin_offset = c.get_ic_uint32
|
114
|
+
mismatch_index = c.get_ic_uint32
|
115
|
+
end
|
116
|
+
|
117
|
+
{
|
118
|
+
:page_offset => page_offset,
|
119
|
+
:end_seg_len => end_seg_len >> 1,
|
120
|
+
:info_and_status_bits => info_and_status_bits,
|
121
|
+
:origin_offset => origin_offset,
|
122
|
+
:mismatch_index => mismatch_index,
|
123
|
+
:record => c.name("record") { c.get_bytes(end_seg_len >> 1) },
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
# Read the log record for an in-place update.
|
128
|
+
# Ref. btr_cur_parse_update_in_place
|
129
|
+
def read_update_in_place_record(c)
|
130
|
+
{
|
131
|
+
:flags => c.name("flags") { c.get_uint8 },
|
132
|
+
:sys_fields => read_sys_fields(c),
|
133
|
+
:rec_offset => c.name("rec_offset") { c.get_uint16 },
|
134
|
+
:update_index => read_update_index(c),
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
LENGTH_NULL = 0xFFFFFFFF
|
139
|
+
|
140
|
+
# Read the update vector for an update log record.
|
141
|
+
# Ref. row_upd_index_parse
|
142
|
+
def read_update_index(c)
|
143
|
+
info_bits = c.name("info_bits") { c.get_uint8 }
|
144
|
+
n_fields = c.name("n_fields") { c.get_ic_uint32 }
|
145
|
+
fields = n_fields.times.collect do
|
146
|
+
{
|
147
|
+
:field_no => c.name("field_no") { c.get_ic_uint32 },
|
148
|
+
:len => len = c.name("len") { c.get_ic_uint32 },
|
149
|
+
:data => c.name("data") { len != LENGTH_NULL ? c.get_bytes(len) : :NULL },
|
150
|
+
}
|
151
|
+
end
|
152
|
+
{
|
153
|
+
:info_bits => info_bits,
|
154
|
+
:n_fields => n_fields,
|
155
|
+
:fields => fields,
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
# Read system fields values in a log record.
|
160
|
+
# Ref. row_upd_parse_sys_vals
|
161
|
+
def read_sys_fields(c)
|
162
|
+
{
|
163
|
+
:trx_id_pos => c.name("trx_id_pos") { c.get_ic_uint32 },
|
164
|
+
:roll_ptr => c.name("roll_ptr") { c.get_bytes(7) },
|
165
|
+
:trx_id => c.name("trx_id") { c.get_ic_uint64 },
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
# Read the log record for delete marking or unmarking of a clustered
|
170
|
+
# index record.
|
171
|
+
# Ref. btr_cur_parse_del_mark_set_clust_rec
|
172
|
+
def read_clust_delete_mark(c)
|
173
|
+
{
|
174
|
+
:flags => c.name("flags") { c.get_uint8 },
|
175
|
+
:value => c.name("value") { c.get_uint8 },
|
176
|
+
:sys_fields => c.name("sys_fields") { read_sys_fields(c) },
|
177
|
+
:offset => c.name("offset") { c.get_uint16 },
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
def read_payload(type, c)
|
182
|
+
case type
|
183
|
+
when :MLOG_1BYTE, :MLOG_2BYTE, :MLOG_4BYTE
|
184
|
+
{
|
185
|
+
:page_offset => c.name("page_offset") { c.get_uint16 },
|
186
|
+
:value => c.name("value") { c.get_ic_uint32 }
|
187
|
+
}
|
188
|
+
when :MLOG_8BYTE
|
189
|
+
{
|
190
|
+
:offset => c.name("offset") { c.get_uint16 },
|
191
|
+
:value => c.name("value") { c.get_ic_uint64 }
|
192
|
+
}
|
193
|
+
when :UNDO_HDR_CREATE, :UNDO_HDR_REUSE
|
194
|
+
{
|
195
|
+
:trx_id => c.name("trx_id") { c.get_ic_uint64 }
|
196
|
+
}
|
197
|
+
when :UNDO_INSERT
|
198
|
+
{
|
199
|
+
:length => len = c.name("length") { c.get_uint16 },
|
200
|
+
:value => c.name("value") { c.get_bytes(len) }
|
201
|
+
}
|
202
|
+
when :REC_INSERT
|
203
|
+
{
|
204
|
+
:record => c.name("record") { read_insert_record(c) }
|
205
|
+
}
|
206
|
+
when :COMP_REC_INSERT
|
207
|
+
{
|
208
|
+
:index => c.name("index") { read_index(c) },
|
209
|
+
:record => c.name("record") { read_insert_record(c) }
|
210
|
+
}
|
211
|
+
when :COMP_REC_UPDATE_IN_PLACE
|
212
|
+
{
|
213
|
+
:index => c.name("index") { read_index(c) },
|
214
|
+
:record => c.name("record") { read_update_in_place_record(c) }
|
215
|
+
}
|
216
|
+
when :REC_UPDATE_IN_PLACE
|
217
|
+
{
|
218
|
+
:record => c.name("record") { read_update_in_place_record(c) }
|
219
|
+
}
|
220
|
+
when :WRITE_STRING
|
221
|
+
{
|
222
|
+
:offset => c.name("offset") { c.get_uint16 },
|
223
|
+
:length => length = c.name("length") { c.get_uint16 },
|
224
|
+
:value => c.name("value") { c.get_bytes(length) },
|
225
|
+
}
|
226
|
+
when :UNDO_INIT
|
227
|
+
{
|
228
|
+
:type => c.name("type") { UNDO_TYPES[c.get_ic_uint32] }
|
229
|
+
}
|
230
|
+
when :FILE_CREATE, :FILE_DELETE
|
231
|
+
{
|
232
|
+
:name_len => len = c.name("name_len") { c.get_uint16 },
|
233
|
+
:name => c.name("name") { c.get_bytes(len) },
|
234
|
+
}
|
235
|
+
when :FILE_CREATE2
|
236
|
+
{
|
237
|
+
:flags => c.name("flags") { c.get_uint32 },
|
238
|
+
:name_len => len = c.name("name_len") { c.get_uint16 },
|
239
|
+
:name => c.name("name") { c.get_bytes(len) },
|
240
|
+
}
|
241
|
+
when :FILE_RENAME
|
242
|
+
{
|
243
|
+
:old => {
|
244
|
+
:name_len => len = c.name("name_len") { c.get_uint16 },
|
245
|
+
:name => c.name("name") { c.get_bytes(len) },
|
246
|
+
},
|
247
|
+
:new => {
|
248
|
+
:name_len => len = c.name("name_len") { c.get_uint16 },
|
249
|
+
:name => c.name("name") { c.get_bytes(len) },
|
250
|
+
}
|
251
|
+
}
|
252
|
+
when :COMP_REC_CLUST_DELETE_MARK
|
253
|
+
{
|
254
|
+
:index => c.name("index") { read_index(c) },
|
255
|
+
:record => c.name("record") { read_clust_delete_mark(c) }
|
256
|
+
}
|
257
|
+
when :REC_CLUST_DELETE_MARK
|
258
|
+
{
|
259
|
+
:record => c.name("record") { read_clust_delete_mark(c) }
|
260
|
+
}
|
261
|
+
when :COMP_REC_SEC_DELETE_MARK
|
262
|
+
{
|
263
|
+
:index => c.name("index") { read_index(c) },
|
264
|
+
:value => c.name("value") { c.get_uint8 },
|
265
|
+
:offset => c.name("offset") { c.get_uint16 },
|
266
|
+
}
|
267
|
+
when :REC_SEC_DELETE_MARK
|
268
|
+
{
|
269
|
+
:value => c.name("value") { c.get_uint8 },
|
270
|
+
:offset => c.name("offset") { c.get_uint16 },
|
271
|
+
}
|
272
|
+
when :REC_DELETE
|
273
|
+
{
|
274
|
+
:offset => c.name("offset") { c.get_uint16 },
|
275
|
+
}
|
276
|
+
when :COMP_REC_DELETE
|
277
|
+
{
|
278
|
+
:index => c.name("index") { read_index(c) },
|
279
|
+
:offset => c.name("offset") { c.get_uint16 },
|
280
|
+
}
|
281
|
+
when :REC_MIN_MARK, :COMP_REC_MIN_MARK
|
282
|
+
{
|
283
|
+
:offset => c.name("offset") { c.get_uint16 },
|
284
|
+
}
|
285
|
+
when :LIST_START_DELETE, :LIST_END_DELETE
|
286
|
+
{
|
287
|
+
:offset => c.name("offset") { c.get_uint16 },
|
288
|
+
}
|
289
|
+
when :COMP_LIST_START_DELETE, :COMP_LIST_END_DELETE
|
290
|
+
{
|
291
|
+
:index => c.name("index") { read_index(c) },
|
292
|
+
:offset => c.name("offset") { c.get_uint16 },
|
293
|
+
}
|
294
|
+
when :LIST_END_COPY_CREATED
|
295
|
+
{
|
296
|
+
:length => len = c.name("length") { c.get_uint32 },
|
297
|
+
:data => c.name("data") { c.get_bytes(len) }
|
298
|
+
}
|
299
|
+
when :COMP_LIST_END_COPY_CREATE
|
300
|
+
{
|
301
|
+
:index => c.name("index") { read_index(c) },
|
302
|
+
:length => len = c.name("length") { c.get_uint32 },
|
303
|
+
:data => c.name("data") { c.get_bytes(len) }
|
304
|
+
}
|
305
|
+
when :COMP_PAGE_REORGANIZE
|
306
|
+
{
|
307
|
+
:index => c.name("index") { read_index(c) },
|
308
|
+
}
|
309
|
+
when :DUMMY_RECORD, :MULTI_REC_END, :INIT_FILE_PAGE,
|
310
|
+
:IBUF_BITMAP_INIT, :PAGE_CREATE, :COMP_PAGE_CREATE,
|
311
|
+
:PAGE_REORGANIZE, :UNDO_ERASE_END, :UNDO_HDR_DISCARD
|
312
|
+
{}
|
313
|
+
else
|
314
|
+
raise "Unsupported log record type: #{type.to_s}"
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
data/lib/innodb/lsn.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# A Log Sequence Number and its byte offset into the log group.
|
4
|
+
class Innodb::LSN
|
5
|
+
# The Log Sequence Number.
|
6
|
+
attr_reader :lsn_no
|
7
|
+
|
8
|
+
# Alias :lsn_no attribute.
|
9
|
+
alias_method :no, :lsn_no
|
10
|
+
|
11
|
+
# Initialize coordinates.
|
12
|
+
def initialize(lsn, offset)
|
13
|
+
@lsn_no = lsn
|
14
|
+
@lsn_offset = offset
|
15
|
+
end
|
16
|
+
|
17
|
+
# Place LSN in a new position.
|
18
|
+
def reposition(new_lsn_no, group)
|
19
|
+
new_offset = offset_of(@lsn_no, @lsn_offset, new_lsn_no, group)
|
20
|
+
@lsn_no, @lsn_offset = [new_lsn_no, new_offset]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Advance by a given LSN amount.
|
24
|
+
def advance(count_lsn_no, group)
|
25
|
+
new_lsn_no = @lsn_no + count_lsn_no
|
26
|
+
reposition(new_lsn_no, group)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the location coordinates of this LSN.
|
30
|
+
def location(group)
|
31
|
+
location_of(@lsn_offset, group)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the LSN delta for the given amount of data.
|
35
|
+
def delta(length)
|
36
|
+
fragment = (@lsn_no % LOG_BLOCK_SIZE) - LOG_BLOCK_HEADER_SIZE
|
37
|
+
raise "Invalid fragment #{fragment} for LSN #{@lsn_no}" unless
|
38
|
+
fragment.between?(0, LOG_BLOCK_DATA_SIZE - 1)
|
39
|
+
length + (fragment + length) / LOG_BLOCK_DATA_SIZE * LOG_BLOCK_FRAME_SIZE
|
40
|
+
end
|
41
|
+
|
42
|
+
# Whether LSN might point to log record data.
|
43
|
+
def record?(group)
|
44
|
+
data_offset?(@lsn_offset, group)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Short alias for the size of a log file header.
|
50
|
+
LOG_HEADER_SIZE = Innodb::Log::LOG_HEADER_SIZE
|
51
|
+
|
52
|
+
# Short aliases for the sizes of the subparts of a log block.
|
53
|
+
LOG_BLOCK_SIZE = Innodb::LogBlock::BLOCK_SIZE
|
54
|
+
LOG_BLOCK_HEADER_SIZE = Innodb::LogBlock::HEADER_SIZE
|
55
|
+
LOG_BLOCK_TRAILER_SIZE = Innodb::LogBlock::TRAILER_SIZE
|
56
|
+
LOG_BLOCK_DATA_SIZE = Innodb::LogBlock::DATA_SIZE
|
57
|
+
LOG_BLOCK_FRAME_SIZE = LOG_BLOCK_HEADER_SIZE + LOG_BLOCK_TRAILER_SIZE
|
58
|
+
|
59
|
+
# Returns the coordinates of the given offset.
|
60
|
+
def location_of(offset, group)
|
61
|
+
log_no, log_offset = offset.divmod(group.size)
|
62
|
+
block_no, block_offset = (log_offset - LOG_HEADER_SIZE).divmod(LOG_BLOCK_SIZE)
|
63
|
+
[log_no, block_no, block_offset]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the offset of the given LSN within a log group.
|
67
|
+
def offset_of(lsn, offset, new_lsn, group)
|
68
|
+
log_size = group.log_size
|
69
|
+
group_capacity = group.capacity
|
70
|
+
|
71
|
+
# Calculate the offset in LSN.
|
72
|
+
if new_lsn >= lsn
|
73
|
+
lsn_offset = new_lsn - lsn
|
74
|
+
else
|
75
|
+
lsn_offset = lsn - new_lsn
|
76
|
+
lsn_offset %= group_capacity
|
77
|
+
lsn_offset = group_capacity - lsn_offset
|
78
|
+
end
|
79
|
+
|
80
|
+
# Transpose group size offset to a group capacity offset.
|
81
|
+
group_offset = offset - (LOG_HEADER_SIZE * (1 + offset / log_size))
|
82
|
+
|
83
|
+
offset = (lsn_offset + group_offset) % group_capacity
|
84
|
+
|
85
|
+
# Transpose group capacity offset to a group size offset.
|
86
|
+
offset + LOG_HEADER_SIZE * (1 + offset / (log_size - LOG_HEADER_SIZE))
|
87
|
+
end
|
88
|
+
|
89
|
+
# Whether offset points to the data area of an existing log block.
|
90
|
+
def data_offset?(offset, group)
|
91
|
+
log_offset = offset % group.size
|
92
|
+
log_no, block_no, block_offset = location_of(offset, group)
|
93
|
+
|
94
|
+
status ||= log_no > group.logs
|
95
|
+
status ||= log_offset <= LOG_HEADER_SIZE
|
96
|
+
status ||= block_no < 0
|
97
|
+
status ||= block_no >= group.log(log_no).blocks
|
98
|
+
status ||= block_offset < Innodb::LogBlock::DATA_OFFSET
|
99
|
+
status ||= block_offset >= Innodb::LogBlock::TRAILER_OFFSET
|
100
|
+
|
101
|
+
!status
|
102
|
+
end
|
103
|
+
end
|