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.
@@ -0,0 +1,259 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ # A single undo log record.
4
+ class Innodb::UndoRecord
5
+ attr_reader :undo_page
6
+ attr_reader :position
7
+
8
+ attr_accessor :undo_log
9
+ attr_accessor :index_page
10
+
11
+ def initialize(undo_page, position)
12
+ @undo_page = undo_page
13
+ @position = position
14
+
15
+ @undo_log = nil
16
+ @index_page = nil
17
+ end
18
+
19
+ def new_subordinate(undo_page, position)
20
+ new_undo_record = self.class.new(undo_page, position)
21
+ new_undo_record.undo_log = undo_log
22
+ new_undo_record.index_page = index_page
23
+
24
+ new_undo_record
25
+ end
26
+
27
+ # The header really starts 2 bytes before the undo record position, as the
28
+ # pointer to the previous record is written there.
29
+ def pos_header
30
+ @position - 2
31
+ end
32
+
33
+ # The size of the header.
34
+ def size_header
35
+ 2 + 2 + 1
36
+ end
37
+
38
+ def pos_record
39
+ pos_header + size_header
40
+ end
41
+
42
+ # Return a BufferCursor starting before the header.
43
+ def cursor(position)
44
+ new_cursor = @undo_page.cursor(position)
45
+ if @undo_log
46
+ new_cursor.push_name("undo_log[#{@undo_log.position}]")
47
+ end
48
+ new_cursor.push_name("undo_record[#{@position}]")
49
+ new_cursor
50
+ end
51
+
52
+ # Possible undo record types.
53
+ TYPE = {
54
+ 11 => :insert,
55
+ 12 => :update_existing,
56
+ 13 => :update_deleted,
57
+ 14 => :delete,
58
+ }
59
+
60
+ TYPE_MASK = 0x0f
61
+ COMPILATION_INFO_MASK = 0x70
62
+ COMPILATION_INFO_SHIFT = 4
63
+ COMPILATION_INFO_NO_ORDER_CHANGE_BV = 1
64
+ COMPILATION_INFO_NO_SIZE_CHANGE_BV = 2
65
+ EXTERN_FLAG = 0x80
66
+
67
+ def header
68
+ @header ||= cursor(pos_header).name("header") do |c|
69
+ header = {
70
+ :prev => c.name("prev") { c.get_uint16 },
71
+ :next => c.name("next") { c.get_uint16 },
72
+ }
73
+
74
+ info = c.name("info") { c.get_uint8 }
75
+ cmpl = (info & COMPILATION_INFO_MASK) >> COMPILATION_INFO_SHIFT
76
+ header[:type] = TYPE[info & TYPE_MASK]
77
+ header[:extern_flag] = (info & EXTERN_FLAG) != 0
78
+ header[:info] = {
79
+ :order_may_change => (cmpl & COMPILATION_INFO_NO_ORDER_CHANGE_BV) == 0,
80
+ :size_may_change => (cmpl & COMPILATION_INFO_NO_SIZE_CHANGE_BV) == 0,
81
+ }
82
+
83
+ header
84
+ end
85
+ end
86
+
87
+ def type
88
+ header[:type]
89
+ end
90
+
91
+ def has_previous_version?
92
+ [:update_existing, :update_deleted, :delete].include?(type)
93
+ end
94
+
95
+ def get(prev_or_next)
96
+ if header[prev_or_next] != 0
97
+ new_undo_record = new_subordinate(@undo_page, header[prev_or_next])
98
+ if new_undo_record.type
99
+ new_undo_record
100
+ end
101
+ end
102
+ end
103
+
104
+ def prev
105
+ get(:prev)
106
+ end
107
+
108
+ def next
109
+ get(:next)
110
+ end
111
+
112
+ def record_size
113
+ header[:next] - @position - size_header
114
+ end
115
+
116
+ def read_record
117
+ cursor(pos_record).name("record") do |c|
118
+ this_record = {
119
+ :page => undo_page.offset,
120
+ :offset => position,
121
+ :header => header,
122
+ :undo_no => c.name("undo_no") { c.get_imc_uint64 },
123
+ :table_id => c.name("table_id") { c.get_imc_uint64 },
124
+ }
125
+
126
+ if has_previous_version?
127
+ this_record[:info_bits] = c.name("info_bits") { c.get_uint8 }
128
+ this_record[:trx_id] = c.name("trx_id") { c.get_ic_uint64 }
129
+ this_record[:roll_ptr] = c.name("roll_ptr") {
130
+ Innodb::DataType::RollPointerType.parse_roll_pointer(c.get_ic_uint64)
131
+ }
132
+ end
133
+
134
+ if index_page
135
+ read_record_fields(this_record, c)
136
+ else
137
+ # Slurp up the remaining data as a string.
138
+ this_record[:data] = c.get_bytes(header[:next] - c.position - 2)
139
+ end
140
+
141
+ this_record
142
+ end
143
+ end
144
+
145
+ def read_record_fields(this_record, c)
146
+ this_record[:key] = []
147
+ index_page.record_format[:key].each do |field|
148
+ this_record[:key][field.position] = {
149
+ :name => field.name,
150
+ :type => field.data_type.name,
151
+ :value => c.name(field.name) {
152
+ field_length = c.name("field_length") { c.get_ic_uint32 }
153
+ field.value_by_length(c, field_length)
154
+ }
155
+ }
156
+ end
157
+
158
+ if has_previous_version?
159
+ field_count = c.name("field_count") { c.get_ic_uint32 }
160
+ this_record[:row] = Array.new(index_page.record_format[:row].size)
161
+ field_count.times do
162
+ field_number = c.name("field_number[#{field_count}]") { c.get_ic_uint32 }
163
+ field = nil
164
+ field_index = nil
165
+ index_page.record_format[:row].each_with_index do |candidate_field, index|
166
+ if candidate_field.position == field_number
167
+ field = candidate_field
168
+ field_index = index
169
+ end
170
+ end
171
+ raise "Unknown field #{field_number}" unless field
172
+ this_record[:row][field_index] = {
173
+ :name => field.name,
174
+ :type => field.data_type.name,
175
+ :value => c.name(field.name) {
176
+ field_length = c.name("field_length") { c.get_ic_uint32 }
177
+ field.value_by_length(c, field_length)
178
+ }
179
+ }
180
+ end
181
+ end
182
+ end
183
+
184
+ def undo_record
185
+ @undo_record ||= read_record
186
+ end
187
+
188
+ def undo_no
189
+ undo_record[:undo_no]
190
+ end
191
+
192
+ def table_id
193
+ undo_record[:table_id]
194
+ end
195
+
196
+ def trx_id
197
+ undo_record[:trx_id]
198
+ end
199
+
200
+ def roll_ptr
201
+ undo_record[:roll_ptr]
202
+ end
203
+
204
+ def key
205
+ undo_record[:key]
206
+ end
207
+
208
+ def key_string
209
+ key && key.map { |r| "%s=%s" % [r[:name], r[:value].inspect] }.join(", ")
210
+ end
211
+
212
+ def row
213
+ undo_record[:row]
214
+ end
215
+
216
+ def row_string
217
+ row && row.select { |r| !r.nil? }.map { |r| r && "%s=%s" % [r[:name], r[:value].inspect] }.join(", ")
218
+ end
219
+
220
+ def string
221
+ "(%s) → (%s)" % [key_string, row_string]
222
+ end
223
+
224
+ # Find the previous row version by following the roll_ptr from one undo
225
+ # record to the next (backwards through the record version history). Since
226
+ # we are operating without the benefit of knowing about active transactions
227
+ # and without protection from purge, check that everything looks sane before
228
+ # returning it.
229
+ def prev_by_history
230
+ unless has_previous_version?
231
+ # This undo record type has no previous version information.
232
+ return nil
233
+ end
234
+
235
+ undo_log = roll_ptr[:undo_log]
236
+ older_undo_page = @undo_page.space.page(undo_log[:page])
237
+
238
+ unless older_undo_page and older_undo_page.is_a?(Innodb::Page::UndoLog)
239
+ # The page was probably re-used for something else.
240
+ return nil
241
+ end
242
+
243
+ older_undo_record = new_subordinate(older_undo_page,
244
+ undo_log[:offset])
245
+
246
+ unless older_undo_record and table_id == older_undo_record.table_id
247
+ # The record space was probably re-used for something else.
248
+ return nil
249
+ end
250
+
251
+ unless older_undo_record.trx_id.nil? or trx_id >= older_undo_record.trx_id
252
+ # The trx_id should not be newer; but may be absent (for insert).
253
+ return nil
254
+ end
255
+
256
+ older_undo_record
257
+ end
258
+
259
+ end
@@ -2,8 +2,11 @@
2
2
 
3
3
  require "bindata"
4
4
 
5
- # A cursor to walk through InnoDB data structures to read fields.
6
- class Innodb::Cursor
5
+ # A cursor to walk through data structures to read fields. The cursor can move
6
+ # forwards, backwards, is seekable, and supports peeking without moving the
7
+ # cursor. The BinData module is used for interpreting bytes as desired.
8
+ class BufferCursor
9
+ VERSION = "0.9.0"
7
10
 
8
11
  # An entry in a stack of cursors. The cursor position, direction, and
9
12
  # name array are each attributes of the current cursor stack and are
@@ -21,16 +24,24 @@ class Innodb::Cursor
21
24
  @name = name || []
22
25
  end
23
26
 
27
+ def inspect
28
+ "<%s direction=%s position=%s>" % [
29
+ self.class.name,
30
+ @direction.inspect,
31
+ @position,
32
+ ]
33
+ end
34
+
24
35
  def dup
25
36
  StackEntry.new(cursor, position, direction, name.dup)
26
37
  end
27
38
  end
28
39
 
29
- @@tracing = false
40
+ @@global_tracing = false
30
41
 
31
- # Enable tracing for all Innodb::Cursor objects.
42
+ # Enable tracing for all BufferCursor objects globally.
32
43
  def self.trace!(arg=true)
33
- @@tracing = arg
44
+ @@global_tracing = arg
34
45
  end
35
46
 
36
47
  # Initialize a cursor within a buffer at the given position.
@@ -38,7 +49,22 @@ class Innodb::Cursor
38
49
  @buffer = buffer
39
50
  @stack = [ StackEntry.new(self, position) ]
40
51
 
52
+ trace false
41
53
  trace_with :print_trace
54
+ trace_to STDOUT
55
+ end
56
+
57
+ def inspect
58
+ "<%s size=%i current=%s>" % [
59
+ self.class.name,
60
+ @buffer.size,
61
+ current.inspect,
62
+ ]
63
+ end
64
+
65
+ def trace(arg=true)
66
+ @instance_tracing = arg
67
+ self
42
68
  end
43
69
 
44
70
  # Print a trace output for this cursor. The method is passed a cursor object,
@@ -46,7 +72,7 @@ class Innodb::Cursor
46
72
  def print_trace(cursor, position, bytes, name)
47
73
  slice_size = 16
48
74
  bytes.each_slice(slice_size).each_with_index do |slice_bytes, slice_count|
49
- puts "%06i %s %-32s %s" % [
75
+ @trace_io.puts "%06i %s %-32s %s" % [
50
76
  position + (slice_count * slice_size),
51
77
  direction == :backward ? "←" : "→",
52
78
  slice_bytes.map { |n| "%02x" % n }.join,
@@ -55,6 +81,11 @@ class Innodb::Cursor
55
81
  end
56
82
  end
57
83
 
84
+ def trace_to(file)
85
+ @trace_io = file
86
+ self
87
+ end
88
+
58
89
  # Set a Proc or method on self to trace with.
59
90
  def trace_with(arg=nil)
60
91
  if arg.nil?
@@ -66,11 +97,16 @@ class Innodb::Cursor
66
97
  else
67
98
  raise "Don't know how to trace with #{arg}"
68
99
  end
100
+ self
101
+ end
102
+
103
+ def tracing_enabled?
104
+ (@@global_tracing or @instance_tracing) && @trace_proc
69
105
  end
70
106
 
71
107
  # Generate a trace record from the current cursor.
72
- def trace(position, bytes, name)
73
- @trace_proc.call(self, position, bytes, name) if @@tracing && @trace_proc
108
+ def record_trace(position, bytes, name)
109
+ @trace_proc.call(self, position, bytes, name) if tracing_enabled?
74
110
  end
75
111
 
76
112
  # The current cursor object; the top of the stack.
@@ -78,6 +114,14 @@ class Innodb::Cursor
78
114
  @stack.last
79
115
  end
80
116
 
117
+ def push_name(name_arg)
118
+ current.name.push name_arg
119
+ end
120
+
121
+ def pop_name
122
+ current.name.pop
123
+ end
124
+
81
125
  # Set the field name.
82
126
  def name(name_arg=nil)
83
127
  if name_arg.nil?
@@ -170,7 +214,7 @@ class Innodb::Cursor
170
214
  data = @buffer.slice(current.position, length)
171
215
  end
172
216
 
173
- trace(cursor_start, data.bytes, current.name)
217
+ record_trace(cursor_start, data.bytes, current.name)
174
218
  data
175
219
  end
176
220
 
@@ -179,6 +223,12 @@ class Innodb::Cursor
179
223
  read_and_advance(length)
180
224
  end
181
225
 
226
+ # Return a null-terminated string.
227
+ def get_string(length)
228
+ BinData::Stringz.read(read_and_advance(length))
229
+ end
230
+
231
+ # Iterate through length bytes returning each as an unsigned 8-bit integer.
182
232
  def each_byte_as_uint8(length)
183
233
  unless block_given?
184
234
  return enum_for(:each_byte_as_uint8, length)
@@ -261,33 +311,89 @@ class Innodb::Cursor
261
311
  when 8
262
312
  get_uint64
263
313
  else
264
- raise "Not implemented"
314
+ raise "Integer size #{size} not implemented"
265
315
  end
266
316
  end
267
317
 
318
+ # Read an array of count unsigned integers given their size in bytes.
268
319
  def get_uint_array_by_size(size, count)
269
320
  (0...count).to_a.inject([]) { |a, n| a << get_uint_by_size(size); a }
270
321
  end
271
322
 
272
- # Read an InnoDB-compressed unsigned 32-bit integer.
273
- def get_ic_uint32
274
- flag = peek { name("ic_uint32") { get_uint8 } }
275
-
276
- case
277
- when flag < 0x80
278
- name("uint8") { get_uint8 }
279
- when flag < 0xc0
280
- name("uint16") { get_uint16 } & 0x7fff
281
- when flag < 0xe0
282
- name("uint24") { get_uint24 } & 0x3fffff
283
- when flag < 0xf0
284
- name("uint32") { get_uint32 } & 0x1fffffff
285
- when flag == 0xf0
286
- adjust(+1) # Skip the flag.
287
- name("uint32+1") { get_uint32 }
288
- else
289
- raise "Invalid flag #{flag.to_s} seen"
290
- end
323
+ # Read an InnoDB-compressed unsigned 32-bit integer (1-5 bytes).
324
+ #
325
+ # The first byte makes up part of the value stored as well as indicating
326
+ # the number of bytes stored, maximally an additional 4 bytes after the
327
+ # flag for integers >= 0xf0000000.
328
+ #
329
+ # Optionally accept a flag (first byte) if it has already been read (as is
330
+ # the case in get_imc_uint64).
331
+ def get_ic_uint32(flag=nil)
332
+ name("ic_uint32") {
333
+ if !flag
334
+ flag = peek { name("uint8_or_flag") { get_uint8 } }
335
+ end
336
+
337
+ case
338
+ when flag < 0x80
339
+ adjust(+1)
340
+ flag
341
+ when flag < 0xc0
342
+ name("uint16") { get_uint16 } & 0x7fff
343
+ when flag < 0xe0
344
+ name("uint24") { get_uint24 } & 0x3fffff
345
+ when flag < 0xf0
346
+ name("uint32") { get_uint32 } & 0x1fffffff
347
+ when flag == 0xf0
348
+ adjust(+1) # Skip the flag byte.
349
+ name("uint32+1") { get_uint32 }
350
+ else
351
+ raise "Invalid flag #{flag.to_s} seen"
352
+ end
353
+ }
354
+ end
355
+
356
+ # Read an InnoDB-compressed unsigned 64-bit integer (5-9 bytes).
357
+ #
358
+ # The high 32 bits are stored as an InnoDB-compressed unsigned 32-bit
359
+ # integer (1-5 bytes) while the low 32 bits are stored as a standard
360
+ # big-endian 32-bit integer (4 bytes). This makes a combined size of
361
+ # between 5 and 9 bytes.
362
+ def get_ic_uint64
363
+ name("ic_uint64") {
364
+ high = name("high") { get_ic_uint32 }
365
+ low = name("low") { name("uint32") { get_uint32 } }
366
+
367
+ (high << 32) | low
368
+ }
369
+ end
370
+
371
+ # Read an InnoDB-"much compressed" unsigned 64-bit integer (1-11 bytes).
372
+ #
373
+ # If the first byte is 0xff, this indicates that the high 32 bits are
374
+ # stored immediately afterwards as an InnoDB-compressed 32-bit unsigned
375
+ # integer. If it is any other value it represents the first byte (which
376
+ # is also a flag) of the low 32 bits of the value, also as an InnoDB-
377
+ # compressed 32-bit unsigned integer. This makes for a combined size
378
+ # of between 1 and 11 bytes.
379
+ def get_imc_uint64
380
+ name("imc_uint64") {
381
+ high = 0
382
+ flag = peek { name("uint8_or_flag") { get_uint8 } }
383
+
384
+ if flag == 0xff
385
+ # The high 32-bits are stored first as an ic_uint32.
386
+ adjust(+1) # Skip the flag byte.
387
+ high = name("high") { get_ic_uint32 }
388
+ flag = nil
389
+ end
390
+
391
+ # The low 32-bits are stored as an ic_uint32; pass the flag we already
392
+ # read, so we don't have to read it again.
393
+ low = name("low") { get_ic_uint32(flag) }
394
+
395
+ (high << 32) | low
396
+ }
291
397
  end
292
398
 
293
399
  # Read an array of 1-bit integers.