Class: Innodb::Cursor

Inherits:
Object
  • Object
show all
Defined in:
lib/innodb/cursor.rb

Overview

A cursor to walk through InnoDB data structures to read fields.

Defined Under Namespace

Classes: StackEntry

Constant Summary collapse

@@tracing =
false

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(buffer, position) ⇒ Cursor

Initialize a cursor within a buffer at the given position.



37
38
39
40
41
42
# File 'lib/innodb/cursor.rb', line 37

def initialize(buffer, position)
  @buffer = buffer
  @stack = [ StackEntry.new(self, position) ]

  trace_with :print_trace
end

Class Method Details

.trace!(arg = true) ⇒ Object

Enable tracing for all Innodb::Cursor objects.



32
33
34
# File 'lib/innodb/cursor.rb', line 32

def self.trace!(arg=true)
  @@tracing = arg
end

Instance Method Details

#adjust(relative_position) ⇒ Object

Adjust the current cursor to a new relative position.



129
130
131
132
# File 'lib/innodb/cursor.rb', line 129

def adjust(relative_position)
  current.position += relative_position
  self
end

#backwardObject

Set the direction of the cursor to “backward”.



113
114
115
# File 'lib/innodb/cursor.rb', line 113

def backward
  direction(:backward)
end

#currentObject

The current cursor object; the top of the stack.



77
78
79
# File 'lib/innodb/cursor.rb', line 77

def current
  @stack.last
end

#direction(direction_arg = nil) ⇒ Object

Return the direction of the current cursor.



98
99
100
101
102
103
104
105
# File 'lib/innodb/cursor.rb', line 98

def direction(direction_arg=nil)
  if direction_arg.nil?
    return current.direction
  end

  current.direction = direction_arg
  self
end

#each_byte_as_uint8(length) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
# File 'lib/innodb/cursor.rb', line 182

def each_byte_as_uint8(length)
  unless block_given?
    return enum_for(:each_byte_as_uint8, length)
  end

  read_and_advance(length).bytes.each do |byte|
    yield byte
  end

  nil
end

#forwardObject

Set the direction of the cursor to “forward”.



108
109
110
# File 'lib/innodb/cursor.rb', line 108

def forward
  direction(:forward)
end

#get_bit_array(num_bits) ⇒ Object

Read an array of 1-bit integers.



294
295
296
297
298
299
# File 'lib/innodb/cursor.rb', line 294

def get_bit_array(num_bits)
  size = (num_bits + 7) / 8
  data = read_and_advance(size)
  bit_array = BinData::Array.new(:type => :bit1, :initial_length => size * 8)
  bit_array.read(data).to_ary
end

#get_bytes(length) ⇒ Object

Return raw bytes.



178
179
180
# File 'lib/innodb/cursor.rb', line 178

def get_bytes(length)
  read_and_advance(length)
end

#get_hex(length) ⇒ Object

Return raw bytes as hex.



195
196
197
# File 'lib/innodb/cursor.rb', line 195

def get_hex(length)
  read_and_advance(length).bytes.map { |c| "%02x" % c }.join
end

#get_ic_uint32Object

Read an InnoDB-compressed unsigned 32-bit integer.



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/innodb/cursor.rb', line 273

def get_ic_uint32
  flag = peek { name("ic_uint32") { get_uint8 } }

  case
  when flag < 0x80
    name("uint8") { get_uint8 }
  when flag < 0xc0
    name("uint16") { get_uint16 } & 0x7fff
  when flag < 0xe0
    name("uint24") { get_uint24 } & 0x3fffff
  when flag < 0xf0
    name("uint32") { get_uint32 } & 0x1fffffff
  when flag == 0xf0
    adjust(+1) # Skip the flag.
    name("uint32+1") { get_uint32 }
  else
    raise "Invalid flag #{flag.to_s} seen"
  end
end

#get_sint16(position = nil) ⇒ Object

Read a big-endian signed 16-bit integer.



214
215
216
217
218
# File 'lib/innodb/cursor.rb', line 214

def get_sint16(position=nil)
  seek(position)
  data = read_and_advance(2)
  BinData::Int16be.read(data)
end

#get_uint16(position = nil) ⇒ Object

Read a big-endian unsigned 16-bit integer.



207
208
209
210
211
# File 'lib/innodb/cursor.rb', line 207

def get_uint16(position=nil)
  seek(position)
  data = read_and_advance(2)
  BinData::Uint16be.read(data)
end

#get_uint24(position = nil) ⇒ Object

Read a big-endian unsigned 24-bit integer.



221
222
223
224
225
# File 'lib/innodb/cursor.rb', line 221

def get_uint24(position=nil)
  seek(position)
  data = read_and_advance(3)
  BinData::Uint24be.read(data)
end

#get_uint32(position = nil) ⇒ Object

Read a big-endian unsigned 32-bit integer.



228
229
230
231
232
# File 'lib/innodb/cursor.rb', line 228

def get_uint32(position=nil)
  seek(position)
  data = read_and_advance(4)
  BinData::Uint32be.read(data)
end

#get_uint48(position = nil) ⇒ Object

Read a big-endian unsigned 48-bit integer.



235
236
237
238
239
# File 'lib/innodb/cursor.rb', line 235

def get_uint48(position=nil)
  seek(position)
  data = read_and_advance(6)
  BinData::Uint48be.read(data)
end

#get_uint64(position = nil) ⇒ Object

Read a big-endian unsigned 64-bit integer.



242
243
244
245
246
# File 'lib/innodb/cursor.rb', line 242

def get_uint64(position=nil)
  seek(position)
  data = read_and_advance(8)
  BinData::Uint64be.read(data)
end

#get_uint8(position = nil) ⇒ Object

Read an unsigned 8-bit integer.



200
201
202
203
204
# File 'lib/innodb/cursor.rb', line 200

def get_uint8(position=nil)
  seek(position)
  data = read_and_advance(1)
  BinData::Uint8.read(data)
end

#get_uint_array_by_size(size, count) ⇒ Object



268
269
270
# File 'lib/innodb/cursor.rb', line 268

def get_uint_array_by_size(size, count)
  (0...count).to_a.inject([]) { |a, n| a << get_uint_by_size(size); a }
end

#get_uint_by_size(size) ⇒ Object

Read a big-endian unsigned integer given its size in bytes.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/innodb/cursor.rb', line 249

def get_uint_by_size(size)
  case size
  when 1
    get_uint8
  when 2
    get_uint16
  when 3
    get_uint24
  when 4
    get_uint32
  when 6
    get_uint48
  when 8
    get_uint64
  else
    raise "Not implemented"
  end
end

#name(name_arg = nil) ⇒ Object

Set the field name.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/innodb/cursor.rb', line 82

def name(name_arg=nil)
  if name_arg.nil?
    return current.name
  end

  unless block_given?
    raise "No block given"
  end

  current.name.push name_arg
  ret = yield(self)
  current.name.pop
  ret
end

#peek(position = nil) ⇒ Object

Execute a block and restore the cursor to the previous position after the block returns. Return the block’s return value after restoring the cursor. Optionally seek to provided position before executing block.



151
152
153
154
155
156
157
# File 'lib/innodb/cursor.rb', line 151

def peek(position=nil)
  raise "No block given" unless block_given?
  push(position)
  result = yield(self)
  pop
  result
end

#popObject

Restore the last cursor position.



142
143
144
145
146
# File 'lib/innodb/cursor.rb', line 142

def pop
  raise "No cursors to pop" unless @stack.size > 1
  @stack.pop
  self
end

#positionObject

Return the position of the current cursor.



118
119
120
# File 'lib/innodb/cursor.rb', line 118

def position
  current.position
end

Print a trace output for this cursor. The method is passed a cursor object, position, raw byte buffer, and array of names.



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/innodb/cursor.rb', line 46

def print_trace(cursor, position, bytes, name)
  slice_size = 16
  bytes.each_slice(slice_size).each_with_index do |slice_bytes, slice_count|
    puts "%06i %s %-32s  %s" % [
      position + (slice_count * slice_size),
      direction == :backward ? "←" : "→",
      slice_bytes.map { |n| "%02x" % n }.join,
      slice_count == 0 ? name.join(".") : "↵",
    ]
  end
end

#push(position = nil) ⇒ Object

Save the current cursor position and start a new (nested, stacked) cursor.



135
136
137
138
139
# File 'lib/innodb/cursor.rb', line 135

def push(position=nil)
  @stack.push current.dup
  seek(position)
  self
end

#read_and_advance(length) ⇒ Object

Read a number of bytes forwards or backwards from the current cursor position and adjust the cursor position by that amount.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/innodb/cursor.rb', line 161

def read_and_advance(length)
  data = nil
  cursor_start = current.position
  case current.direction
  when :forward
    data = @buffer.data(current.position, length)
    adjust(length)
  when :backward
    adjust(-length)
    data = @buffer.data(current.position, length)
  end

  trace(cursor_start, data.bytes, current.name)
  data
end

#seek(position) ⇒ Object

Move the current cursor to a new absolute position.



123
124
125
126
# File 'lib/innodb/cursor.rb', line 123

def seek(position)
  current.position = position if position
  self
end

#trace(position, bytes, name) ⇒ Object

Generate a trace record from the current cursor.



72
73
74
# File 'lib/innodb/cursor.rb', line 72

def trace(position, bytes, name)
  @trace_proc.call(self, position, bytes, name) if @@tracing && @trace_proc
end

#trace_with(arg = nil) ⇒ Object

Set a Proc or method on self to trace with.



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/innodb/cursor.rb', line 59

def trace_with(arg=nil)
  if arg.nil?
    @trace_proc = nil
  elsif arg.class == Proc
    @trace_proc = arg
  elsif arg.class == Symbol
    @trace_proc = lambda { |cursor, position, bytes, name| self.send(arg, cursor, position, bytes, name) }
  else
    raise "Don't know how to trace with #{arg}"
  end
end