class QRubyDriver::QIO

@author John Shields Single-pass Ruby reader/writer for byte-streams in Q IPC protocol format

Constants

Q_ATOM_TYPES

Q type constants

Q_TYPE_BOOLEAN
Q_TYPE_BYTE
Q_TYPE_CHAR
Q_TYPE_CHAR_VECTOR
Q_TYPE_DATE
Q_TYPE_DATETIME
Q_TYPE_DICTIONARY
Q_TYPE_EXCEPTION
Q_TYPE_FLIP
Q_TYPE_FLOAT
Q_TYPE_INT
Q_TYPE_LIST
Q_TYPE_LONG
Q_TYPE_MINUTE
Q_TYPE_MONTH
Q_TYPE_REAL
Q_TYPE_SECOND
Q_TYPE_SHORT
Q_TYPE_SYMBOL
Q_TYPE_SYMBOL_VECTOR
Q_TYPE_TIME
Q_TYPE_TIMESPAN
Q_TYPE_TIMESTAMP
Q_VECTOR_TYPES

Public Instance Methods

atom_to_ruby(value, type) click to toggle source

Extracts atom types into Ruby types

# 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) click to toggle source

Atom type handlers

# File lib/q-ruby-driver/q_io.rb, line 159
def boolean_to_ruby value
  value==1
end
get_atom_pack(type) click to toggle source

Returns pack type and byte-length of q atom type

# 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) click to toggle source

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

# 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) click to toggle source

Helper method to infer Q type from native Ruby types

# 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) click to toggle source

Extracts atom types into Ruby types

# 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_boolean() click to toggle source

Short cut methods for reading atoms

# File lib/q-ruby-driver/q_io.rb, line 164
def read_boolean
  read_atom(Q_TYPE_BOOLEAN)
end
read_byte() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 167
def read_byte
  read_atom(Q_TYPE_BYTE)
end
read_char() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 185
def read_char
  read_atom(Q_TYPE_CHAR)
end
read_date() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 197
def read_date
  read_atom(Q_TYPE_DATE)
end
read_datetime() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 200
def read_datetime
  read_atom(Q_TYPE_DATETIME)
end
read_dictionary() click to toggle source

Reads a dictionary into a Ruby Hash

# 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_flip() click to toggle source

Decodes a flip table into a Ruby Hash

# File lib/q-ruby-driver/q_io.rb, line 126
def read_flip
  self.read(1)
  read_item   # should be a dictionary
end
read_float() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 182
def read_float
  read_atom(Q_TYPE_FLOAT)
end
read_int() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 173
def read_int
  read_atom(Q_TYPE_INT)
end
read_item(type = nil) click to toggle source

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

# 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_list() click to toggle source

Decodes a list into an array

# 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_long() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 176
def read_long
  read_atom(Q_TYPE_LONG)
end
read_message() click to toggle source

Decodes a binary Q message into Ruby types

# 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_header() click to toggle source

Extracts length and message type from the message header

# 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_minute() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 206
def read_minute
  read_atom(Q_TYPE_MINUTE)
end
read_month() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 194
def read_month
  read_atom(Q_TYPE_MONTH)
end
read_real() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 179
def read_real
  read_atom(Q_TYPE_REAL)
end
read_second() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 209
def read_second
  read_atom(Q_TYPE_SECOND)
end
read_short() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 170
def read_short
  read_atom(Q_TYPE_SHORT)
end
read_symbol() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 188
def read_symbol
  read_atom(Q_TYPE_SYMBOL)
end
read_time() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 212
def read_time
  read_atom(Q_TYPE_TIME)
end
read_timespan() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 203
def read_timespan
  read_atom(Q_TYPE_TIMESPAN)
end
read_timestamp() click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 191
def read_timestamp
  read_atom(Q_TYPE_TIMESTAMP)
end
read_vector(type) click to toggle source

Reads a vector into a Ruby Array

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 414
def ruby_to_symbol(value)
  value.to_s
end
write_atom(value, type) click to toggle source

Encodes atom types

# 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) click to toggle source

Atom type write shortcut methods

# File lib/q-ruby-driver/q_io.rb, line 426
def write_boolean(value)
  write_atom(value, Q_TYPE_BOOLEAN)
end
write_byte(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 429
def write_byte(value)
  write_atom(value, Q_TYPE_BYTE)
end
write_char(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 447
def write_char(value)
  write_atom(value, Q_TYPE_CHAR)
end
write_date(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 462
def write_date(value)
  write_atom(value, Q_TYPE_DATE)
end
write_datetime(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 465
def write_datetime(value)
  write_atom(value, Q_TYPE_DATETIME)
end
write_dictionary(hash) click to toggle source

Encodes a dictionary

# 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) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 453
def write_exception(value)
  write_atom(value, Q_TYPE_EXCEPTION)
end
write_flip(hash) click to toggle source

Encodes a flip table

# 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) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 444
def write_float(value)
  write_atom(value, Q_TYPE_FLOAT)
end
write_int(value) click to toggle source
# 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) click to toggle source

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

# 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) click to toggle source

Encodes a list

# 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) click to toggle source
# 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) click to toggle source

Writes a Ruby object to a Q message

# 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) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 471
def write_minute(value)
  write_atom(value, Q_TYPE_MINUTE)
end
write_month(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 459
def write_month(value)
  write_atom(value, Q_TYPE_MONTH)
end
write_real(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 441
def write_real(value)
  write_atom(value, Q_TYPE_REAL)
end
write_second(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 474
def write_second(value)
  write_atom(value, Q_TYPE_SECOND)
end
write_short(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 432
def write_short(value)
  write_atom(value, Q_TYPE_SHORT)
end
write_string(item) click to toggle source

Encodes a string as a char vector

# 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) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 450
def write_symbol(value)
  write_atom(value, Q_TYPE_SYMBOL)
end
write_time(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 477
def write_time(value)
  write_atom(value, Q_TYPE_TIME)
end
write_timespan(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 468
def write_timespan(value)
  write_atom(value, Q_TYPE_TIMESPAN)
end
write_timestamp(value) click to toggle source
# File lib/q-ruby-driver/q_io.rb, line 456
def write_timestamp(value)
  write_atom(value, Q_TYPE_TIMESTAMP)
end
write_type(type) click to toggle source

Writes the type byte

# 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) click to toggle source

Encodes an array as a vector

# 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