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