Class: QRubyDriver::QIO

Inherits:
StringIO
  • Object
show all
Defined in:
lib/q-ruby-driver/q_io.rb

Overview

Single-pass Ruby reader/writer for byte-streams in Q IPC protocol format

Author:

  • John Shields

Constant Summary collapse

Q_ATOM_TYPES =

Q type constants

-19..-1
Q_VECTOR_TYPES =
1..19
Q_TYPE_EXCEPTION =
-128
Q_TYPE_BOOLEAN =
-1
Q_TYPE_BYTE =
-4
Q_TYPE_SHORT =
-5
Q_TYPE_INT =
-6
Q_TYPE_LONG =
-7
Q_TYPE_REAL =

single-prec float

-8   # single-prec float
Q_TYPE_FLOAT =

double-prec float

-9   # double-prec float
Q_TYPE_CHAR =
-10
Q_TYPE_SYMBOL =
-11
Q_TYPE_TIMESTAMP =
-12
Q_TYPE_MONTH =
-13
Q_TYPE_DATE =
-14
Q_TYPE_DATETIME =
-15
Q_TYPE_TIMESPAN =
-16
Q_TYPE_MINUTE =
-17
Q_TYPE_SECOND =
-18
Q_TYPE_TIME =
-19
Q_TYPE_CHAR_VECTOR =
10
Q_TYPE_SYMBOL_VECTOR =
11
Q_TYPE_LIST =
0
Q_TYPE_FLIP =
98
Q_TYPE_DICTIONARY =
99

Instance Method Summary collapse

Instance Method Details

#atom_to_ruby(value, type) ⇒ Object

Extracts atom types into Ruby types



148
149
150
151
152
153
154
155
156
# File 'lib/q-ruby-driver/q_io.rb', line 148

def atom_to_ruby(value, type)
  case type
    when Q_TYPE_BOOLEAN then
      boolean_to_ruby value
    # TODO: add support for date/time types
    else
      value
  end
end

#boolean_to_ruby(value) ⇒ Object

Atom type handlers



159
160
161
# File 'lib/q-ruby-driver/q_io.rb', line 159

def boolean_to_ruby value
  value==1
end

#get_atom_pack(type) ⇒ Object

Returns pack type and byte-length of q atom type



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/q-ruby-driver/q_io.rb', line 369

def get_atom_pack(type)
  case type
    when Q_TYPE_BOOLEAN, Q_TYPE_BYTE then
      ['c',1]
    when Q_TYPE_SHORT then
      ['s',2]
    when Q_TYPE_INT, Q_TYPE_MONTH, Q_TYPE_DATE, Q_TYPE_MINUTE, Q_TYPE_SECOND, Q_TYPE_TIME  then
      ['I',4]
    when Q_TYPE_LONG, Q_TYPE_TIMESTAMP, Q_TYPE_TIMESPAN then
      ['q',8]
    when Q_TYPE_REAL then
      ['F',4]
    when Q_TYPE_FLOAT, Q_TYPE_DATETIME then
      ['D',8]
    when Q_TYPE_CHAR then
      ['Z',1]
    when Q_TYPE_SYMBOL, Q_TYPE_EXCEPTION then
      ['Z*',0]
    else
      raise QIOException.new "Unknown atom type #{type}"
  end
end

#get_q_array_type(array) ⇒ Object

Helper method to write a Ruby array into either a list or a vector, depending on whether or not the array contains mixed types

Raises:



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/q-ruby-driver/q_io.rb', line 273

def get_q_array_type(array)
  raise QIOException.new("Cannot write empty array") if array.empty?

  klass = array[0].class
  return 0 if klass==String # String is a vector type; cannot make a vector of vectors

  if klass==TrueClass || klass==FalseClass # special routine for booleans
    array.each do |item|
      return 0 unless item.is_a?(TrueClass) || item.is_a?(FalseClass)
    end
  else
    array.each do |item|
      return 0 unless item.is_a? klass
    end
  end

  -1 * get_q_type(array[0])
end

#get_q_type(item) ⇒ Object

Helper method to infer Q type from native Ruby types



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/q-ruby-driver/q_io.rb', line 240

def get_q_type(item)
  if item.is_a? Exception
    Q_TYPE_EXCEPTION
  elsif item.is_a?(TrueClass) || item.is_a?(FalseClass)
    Q_TYPE_BOOLEAN
  elsif item.is_a? Bignum
    Q_TYPE_LONG
  elsif item.is_a? Fixnum
    Q_TYPE_INT
  elsif item.is_a? Float
    Q_TYPE_FLOAT
  elsif item.is_a? String
    Q_TYPE_CHAR_VECTOR
  elsif item.is_a? Symbol
    Q_TYPE_SYMBOL
  # not yet supported
#      elsif item.is_a? Date
#        Q_TYPE_DATE
#      elsif item.is_a? DateTime
#        Q_TYPE_DATETIME
#      elsif item.is_a? Time
#        Q_TYPE_TIME
  elsif item.is_a? Array
    get_q_array_type(item)
  elsif item.is_a? Hash
    Q_TYPE_FLIP
  else
    raise QIOException.new("Cannot infer Q type from #{item.class.to_s}")
  end
end

#read_atom(type) ⇒ Object

Extracts atom types into Ruby types



138
139
140
141
142
143
144
145
# File 'lib/q-ruby-driver/q_io.rb', line 138

def read_atom(type)
  raise QIOException.new "Cannot read atom type #{type}" unless (type>=-19 and type<0)
  byte_type, num_bytes = get_atom_pack(type)
  raw = (type==Q_TYPE_SYMBOL) ? self.readline("\x00") : self.read(num_bytes)
  value = raw.unpack(byte_type)[0]

  atom_to_ruby(value, type)
end

#read_booleanObject

Short cut methods for reading atoms



164
165
166
# File 'lib/q-ruby-driver/q_io.rb', line 164

def read_boolean
  read_atom(Q_TYPE_BOOLEAN)
end

#read_byteObject



167
168
169
# File 'lib/q-ruby-driver/q_io.rb', line 167

def read_byte
  read_atom(Q_TYPE_BYTE)
end

#read_charObject



185
186
187
# File 'lib/q-ruby-driver/q_io.rb', line 185

def read_char
  read_atom(Q_TYPE_CHAR)
end

#read_dateObject



197
198
199
# File 'lib/q-ruby-driver/q_io.rb', line 197

def read_date
  read_atom(Q_TYPE_DATE)
end

#read_datetimeObject



200
201
202
# File 'lib/q-ruby-driver/q_io.rb', line 200

def read_datetime
  read_atom(Q_TYPE_DATETIME)
end

#read_dictionaryObject

Reads a dictionary into a Ruby Hash



111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/q-ruby-driver/q_io.rb', line 111

def read_dictionary
  # The first item is a vector containing the dictionary keys
  keys = read_item
  keys = [keys] unless keys.is_a? Array

  # The second item is a list containing the values of each key
  values = read_item
  values = [values] unless values.is_a? Array

  hash = {}
  keys.zip(values) { |k,v| hash[k]=v }
  return hash
end

#read_flipObject

Decodes a flip table into a Ruby Hash



126
127
128
129
# File 'lib/q-ruby-driver/q_io.rb', line 126

def read_flip
  self.read(1)
  read_item   # should be a dictionary
end

#read_floatObject



182
183
184
# File 'lib/q-ruby-driver/q_io.rb', line 182

def read_float
  read_atom(Q_TYPE_FLOAT)
end

#read_intObject



173
174
175
# File 'lib/q-ruby-driver/q_io.rb', line 173

def read_int
  read_atom(Q_TYPE_INT)
end

#read_item(type = nil) ⇒ Object

Reads the next item and extracts it into a Ruby type Will extract vectors, dictionaries, lists, etc. recursively



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/q-ruby-driver/q_io.rb', line 59

def read_item(type = nil)
  type = read_byte() if type.nil?
  case type
    when Q_TYPE_EXCEPTION then
      raise QException.new(read_symbol)
    when Q_ATOM_TYPES then
      return read_atom(type)
    when Q_TYPE_LIST then
      return read_list
    when Q_VECTOR_TYPES then
      return read_vector(type)
    when Q_TYPE_FLIP then
      return read_flip
    when Q_TYPE_DICTIONARY then
      return read_dictionary
    when 100 then
      read_symbol
      return read_item
    when 101..103 then
      return read_byte == 0 && type == 101 ? nil : "func";
    when 104 then
      read_int.times { read_item }
      return "func"
    when 105..255 then
      read_item
      return "func"
    else
      raise "Cannot read unknown type #{type}"
  end
end

#read_listObject

Decodes a list into an array



132
133
134
135
# File 'lib/q-ruby-driver/q_io.rb', line 132

def read_list
  length = self.read(5).unpack("c1I")[1]
  length.times.map { read_item }
end

#read_longObject



176
177
178
# File 'lib/q-ruby-driver/q_io.rb', line 176

def read_long
  read_atom(Q_TYPE_LONG)
end

#read_messageObject

Decodes a binary Q message into Ruby types



37
38
39
40
# File 'lib/q-ruby-driver/q_io.rb', line 37

def read_message()
  self.read(8) # skip message header
  return read_item
end

#read_message_headerObject

Extracts length and message type from the message header



43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/q-ruby-driver/q_io.rb', line 43

def read_message_header
  header = self.read(8).unpack("H2H2H4I")
  length = header[3]
  case header[1]
    when "00" then
      msg_type = :async
    when "01" then
      msg_type = :sync
    when "02" then
      msg_type = :response
  end
  return length, msg_type
end

#read_minuteObject



206
207
208
# File 'lib/q-ruby-driver/q_io.rb', line 206

def read_minute
  read_atom(Q_TYPE_MINUTE)
end

#read_monthObject



194
195
196
# File 'lib/q-ruby-driver/q_io.rb', line 194

def read_month
  read_atom(Q_TYPE_MONTH)
end

#read_realObject



179
180
181
# File 'lib/q-ruby-driver/q_io.rb', line 179

def read_real
  read_atom(Q_TYPE_REAL)
end

#read_secondObject



209
210
211
# File 'lib/q-ruby-driver/q_io.rb', line 209

def read_second
  read_atom(Q_TYPE_SECOND)
end

#read_shortObject



170
171
172
# File 'lib/q-ruby-driver/q_io.rb', line 170

def read_short
  read_atom(Q_TYPE_SHORT)
end

#read_symbolObject



188
189
190
# File 'lib/q-ruby-driver/q_io.rb', line 188

def read_symbol
  read_atom(Q_TYPE_SYMBOL)
end

#read_timeObject



212
213
214
# File 'lib/q-ruby-driver/q_io.rb', line 212

def read_time
  read_atom(Q_TYPE_TIME)
end

#read_timespanObject



203
204
205
# File 'lib/q-ruby-driver/q_io.rb', line 203

def read_timespan
  read_atom(Q_TYPE_TIMESPAN)
end

#read_timestampObject



191
192
193
# File 'lib/q-ruby-driver/q_io.rb', line 191

def read_timestamp
  read_atom(Q_TYPE_TIMESTAMP)
end

#read_vector(type) ⇒ Object

Reads a vector into a Ruby Array



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/q-ruby-driver/q_io.rb', line 93

def read_vector(type)
  length = self.read(5).unpack("c1I")[1]
  byte_type, num_bytes = get_atom_pack(-type)

  if type==Q_TYPE_SYMBOL_VECTOR
    raw = length.times.map{ self.readline("\x00") }.inject{|str, n| str + n}
    value = raw.unpack(byte_type*length)
  else
    raw = self.read(length*num_bytes)
    value = raw.unpack(byte_type+length.to_s)
  end

  # char vectors are returned as strings
  # all other types are returned as arrays
  (type == Q_TYPE_CHAR_VECTOR) ? value[0] : value.map{|i| atom_to_ruby(i, -type)}
end

#ruby_to_atom(value, type) ⇒ Object



392
393
394
395
396
397
398
399
400
401
402
403
# File 'lib/q-ruby-driver/q_io.rb', line 392

def ruby_to_atom(value, type)
  case type
    when Q_TYPE_BOOLEAN then
      ruby_to_boolean value
    when Q_TYPE_SYMBOL then
      ruby_to_symbol value
    when Q_TYPE_EXCEPTION then
      ruby_to_exception value
    else
       value
  end
end

#ruby_to_boolean(value) ⇒ Object



405
406
407
408
409
410
411
412
413
# File 'lib/q-ruby-driver/q_io.rb', line 405

def ruby_to_boolean(value)
  if value.is_a? TrueClass
    1
  elsif value.is_a? FalseClass
    0
  else
    value == true
  end
end

#ruby_to_exception(value) ⇒ Object



417
418
419
420
421
422
423
# File 'lib/q-ruby-driver/q_io.rb', line 417

def ruby_to_exception(value)
  if value.is_a? Exception
    value.message
  else
    value.to_s
  end
end

#ruby_to_symbol(value) ⇒ Object



414
415
416
# File 'lib/q-ruby-driver/q_io.rb', line 414

def ruby_to_symbol(value)
  value.to_s
end

#write_atom(value, type) ⇒ Object

Encodes atom types



363
364
365
366
# File 'lib/q-ruby-driver/q_io.rb', line 363

def write_atom(value, type)
  raise QIOException.new "Cannot write atom type #{type}" unless ((type>=-19 and type<0) || type==Q_TYPE_EXCEPTION)
  self.write [ruby_to_atom(value, type)].pack(get_atom_pack(type)[0])
end

#write_boolean(value) ⇒ Object

Atom type write shortcut methods



426
427
428
# File 'lib/q-ruby-driver/q_io.rb', line 426

def write_boolean(value)
  write_atom(value, Q_TYPE_BOOLEAN)
end

#write_byte(value) ⇒ Object



429
430
431
# File 'lib/q-ruby-driver/q_io.rb', line 429

def write_byte(value)
  write_atom(value, Q_TYPE_BYTE)
end

#write_char(value) ⇒ Object



447
448
449
# File 'lib/q-ruby-driver/q_io.rb', line 447

def write_char(value)
  write_atom(value, Q_TYPE_CHAR)
end

#write_date(value) ⇒ Object



462
463
464
# File 'lib/q-ruby-driver/q_io.rb', line 462

def write_date(value)
  write_atom(value, Q_TYPE_DATE)
end

#write_datetime(value) ⇒ Object



465
466
467
# File 'lib/q-ruby-driver/q_io.rb', line 465

def write_datetime(value)
  write_atom(value, Q_TYPE_DATETIME)
end

#write_dictionary(hash) ⇒ Object

Encodes a dictionary



349
350
351
352
353
354
# File 'lib/q-ruby-driver/q_io.rb', line 349

def write_dictionary(hash)
  write_type Q_TYPE_SYMBOL_VECTOR
  write_vector hash.keys, Q_TYPE_SYMBOL_VECTOR
  write_type Q_TYPE_LIST
  write_list hash.values
end

#write_exception(value) ⇒ Object



453
454
455
# File 'lib/q-ruby-driver/q_io.rb', line 453

def write_exception(value)
  write_atom(value, Q_TYPE_EXCEPTION)
end

#write_flip(hash) ⇒ Object

Encodes a flip table



357
358
359
360
# File 'lib/q-ruby-driver/q_io.rb', line 357

def write_flip(hash)
  self.write ["00"].pack("H1")
  write_item(hash, 99) # dictionary
end

#write_float(value) ⇒ Object



444
445
446
# File 'lib/q-ruby-driver/q_io.rb', line 444

def write_float(value)
  write_atom(value, Q_TYPE_FLOAT)
end

#write_int(value) ⇒ Object



435
436
437
# File 'lib/q-ruby-driver/q_io.rb', line 435

def write_int(value)
  write_atom(value, Q_TYPE_INT)
end

#write_item(item, type = nil) ⇒ Object

Encodes a type into the IPC representation no native support for the following atom types: byte, short, real, char



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/q-ruby-driver/q_io.rb', line 294

def write_item(item, type=nil)
  type=get_q_type(item) if type.nil?
  write_type type
  case type
    when Q_TYPE_EXCEPTION then
      write_exception item
    when Q_ATOM_TYPES then
      write_atom item, type
    when Q_TYPE_LIST then
      write_list item
    when Q_TYPE_CHAR_VECTOR then
      write_string item
    when 1..9, 11..19 then # Q_VECTOR_TYPES minus Q_TYPE_CHAR_VECTOR
      write_vector item, type
    when Q_TYPE_FLIP then
      write_flip item
    when Q_TYPE_DICTIONARY then
      write_dictionary item
    else
      raise QIOException.new "Cannot write type #{type}"
  end
end

#write_list(array) ⇒ Object

Encodes a list



342
343
344
345
346
# File 'lib/q-ruby-driver/q_io.rb', line 342

def write_list(array)
  raise QIOException("Cannot write empty list") if array.empty?
  self.write ["00", array.length].pack("H1I1")
  array.each { |item| write_item(item) }
end

#write_long(value) ⇒ Object



438
439
440
# File 'lib/q-ruby-driver/q_io.rb', line 438

def write_long(value)
  write_atom(value, Q_TYPE_LONG)
end

#write_message(message, msg_type = :async) ⇒ Object

Writes a Ruby object to a Q message



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/q-ruby-driver/q_io.rb', line 219

def write_message(message, msg_type=:async)
  offset = self.pos
  self.write ["01"].pack("H*")
  self.write case msg_type
    when :async    then ["00"].pack("H*")
    when :sync     then ["01"].pack("H*")
    when :response then ["02"].pack("H*")
    else raise QIOException.new("Cannot write unknown message type #{msg_type.to_s}")
  end
  self.write ["0000"].pack("H*")
  self.pos += 4 # will write size here
  write_item(message)
  # write size
  size = self.pos - offset
  self.pos = offset+4
  write_int(size)
  # set position to end of buffer
  self.pos = offset + size
end

#write_minute(value) ⇒ Object



471
472
473
# File 'lib/q-ruby-driver/q_io.rb', line 471

def write_minute(value)
  write_atom(value, Q_TYPE_MINUTE)
end

#write_month(value) ⇒ Object



459
460
461
# File 'lib/q-ruby-driver/q_io.rb', line 459

def write_month(value)
  write_atom(value, Q_TYPE_MONTH)
end

#write_real(value) ⇒ Object



441
442
443
# File 'lib/q-ruby-driver/q_io.rb', line 441

def write_real(value)
  write_atom(value, Q_TYPE_REAL)
end

#write_second(value) ⇒ Object



474
475
476
# File 'lib/q-ruby-driver/q_io.rb', line 474

def write_second(value)
  write_atom(value, Q_TYPE_SECOND)
end

#write_short(value) ⇒ Object



432
433
434
# File 'lib/q-ruby-driver/q_io.rb', line 432

def write_short(value)
  write_atom(value, Q_TYPE_SHORT)
end

#write_string(item) ⇒ Object

Encodes a string as a char vector



335
336
337
338
339
# File 'lib/q-ruby-driver/q_io.rb', line 335

def write_string(item)
  value = item.is_a?(String) ? item.scan(/./) : item  # convert string into a char array
  self.write ["00", value.length].pack("H1I")
  self.write value.pack("A"*value.length)
end

#write_symbol(value) ⇒ Object



450
451
452
# File 'lib/q-ruby-driver/q_io.rb', line 450

def write_symbol(value)
  write_atom(value, Q_TYPE_SYMBOL)
end

#write_time(value) ⇒ Object



477
478
479
# File 'lib/q-ruby-driver/q_io.rb', line 477

def write_time(value)
  write_atom(value, Q_TYPE_TIME)
end

#write_timespan(value) ⇒ Object



468
469
470
# File 'lib/q-ruby-driver/q_io.rb', line 468

def write_timespan(value)
  write_atom(value, Q_TYPE_TIMESPAN)
end

#write_timestamp(value) ⇒ Object



456
457
458
# File 'lib/q-ruby-driver/q_io.rb', line 456

def write_timestamp(value)
  write_atom(value, Q_TYPE_TIMESTAMP)
end

#write_type(type) ⇒ Object

Writes the type byte



318
319
320
# File 'lib/q-ruby-driver/q_io.rb', line 318

def write_type(type)
  self.write [type].pack("c1")
end

#write_vector(array, type = nil) ⇒ Object

Encodes an array as a vector



323
324
325
326
327
328
329
330
331
332
# File 'lib/q-ruby-driver/q_io.rb', line 323

def write_vector(array, type=nil)
  raise QIOException("Cannot write empty vector") if array.empty?
  type = -1 * get_q_type(array[0]) if type.nil?
  self.write ["00", array.length].pack("H1I")
  if type==Q_TYPE_SYMBOL_VECTOR
    array.each{|x| self.write [ruby_to_atom(x, -type)].pack( get_atom_pack(-type)[0] ) }
  else
    self.write array.map{|x| ruby_to_atom(x, -type)}.pack( get_atom_pack(-type)[0] + array.length.to_s )
  end
end