q-ruby-driver 1.1.0
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.
- data/README.markdown +66 -0
- data/README.txt +58 -0
- data/bin/q-ruby-driver +7 -0
- data/lib/q-ruby-driver.rb +49 -0
- data/lib/q-ruby-driver/q_connection.rb +78 -0
- data/lib/q-ruby-driver/q_exception.rb +24 -0
- data/lib/q-ruby-driver/q_io.rb +481 -0
- data/lib/q-ruby-driver/q_message.rb +57 -0
- data/spec/q-ruby-driver_spec.rb +186 -0
- data/spec/spec_helper.rb +17 -0
- data/test/test_connection.rb +63 -0
- data/test/test_helper.rb +15 -0
- data/test/test_io.rb +167 -0
- metadata +137 -0
data/README.markdown
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
q-ruby-driver
|
2
|
+
=
|
3
|
+
by Philip Dodds and John Shields
|
4
|
+
http://www.github.com/pdodds/q-ruby-driver
|
5
|
+
|
6
|
+
DESCRIPTION:
|
7
|
+
-
|
8
|
+
|
9
|
+
A ruby interface to a Q server from http://www.kx.com, see http://code.kx.com for more
|
10
|
+
information.
|
11
|
+
|
12
|
+
FEATURES:
|
13
|
+
-
|
14
|
+
|
15
|
+
* Provides a pure Ruby implementation of the IPC protocol
|
16
|
+
* Supports single-pass read/write for all main Q-types (19 atom types, 19 vector types, lists, dicts, flips, exceptions)
|
17
|
+
* Converts Q types to/from native Ruby types, including Array, Hash, Bignum, Float, Symbol, and TrueClass/FalseClass. Date/Time types are not yet natively supported.
|
18
|
+
|
19
|
+
EXAMPLE USAGE:
|
20
|
+
-
|
21
|
+
|
22
|
+
q_connection = QConnection.new 'localhost', 5001
|
23
|
+
|
24
|
+
# Note we'll use the sync call (get)
|
25
|
+
q_connection.get("a:`IBM`GOOG`APPL")
|
26
|
+
response = q_connection.get("a")
|
27
|
+
|
28
|
+
# Get the body of the response
|
29
|
+
puts response.inspect
|
30
|
+
|
31
|
+
|
32
|
+
REQUIREMENTS:
|
33
|
+
-
|
34
|
+
|
35
|
+
Ruby 1.8+
|
36
|
+
|
37
|
+
INSTALL:
|
38
|
+
-
|
39
|
+
|
40
|
+
sudo gem install q-ruby-driver
|
41
|
+
|
42
|
+
LICENSE:
|
43
|
+
-
|
44
|
+
|
45
|
+
(The MIT License)
|
46
|
+
|
47
|
+
Copyright (c) 2009 Philip Dodds
|
48
|
+
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
50
|
+
a copy of this software and associated documentation files (the
|
51
|
+
'Software'), to deal in the Software without restriction, including
|
52
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
53
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
54
|
+
permit persons to whom the Software is furnished to do so, subject to
|
55
|
+
the following conditions:
|
56
|
+
|
57
|
+
The above copyright notice and this permission notice shall be
|
58
|
+
included in all copies or substantial portions of the Software.
|
59
|
+
|
60
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
61
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
62
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
63
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
64
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
65
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
66
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.txt
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
q-ruby-driver
|
2
|
+
by Philip Dodds and John Shields
|
3
|
+
http://www.github.com/pdodds/q-ruby-driver
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
A ruby interface to a Q server from Kx Systems (http://www.kx.com). See: http://code.kx.com for more information.
|
8
|
+
|
9
|
+
== FEATURES:
|
10
|
+
|
11
|
+
* Provides a pure Ruby implementation of the IPC protocol
|
12
|
+
* Supports single-pass read/write for all main Q-types (19 atom types, 19 vector types, lists, dicts, flips, exceptions)
|
13
|
+
* Converts Q types to/from native Ruby types, including Array, Hash, Bignum, Float, Symbol, and TrueClass/FalseClass. Date/Time types are not yet natively supported.
|
14
|
+
|
15
|
+
== EXAMPLE USAGE:
|
16
|
+
|
17
|
+
q_connection = QConnection.new 'localhost', 5001
|
18
|
+
|
19
|
+
# Note we'll use the sync call (get)
|
20
|
+
q_connection.get("a:`IBM`GOOG`APPL")
|
21
|
+
response = q_connection.get("a")
|
22
|
+
|
23
|
+
# Get the body of the response
|
24
|
+
puts response.inspect
|
25
|
+
|
26
|
+
|
27
|
+
== REQUIREMENTS:
|
28
|
+
|
29
|
+
Ruby 1.8+
|
30
|
+
|
31
|
+
== INSTALL:
|
32
|
+
|
33
|
+
sudo gem install q-ruby-driver
|
34
|
+
|
35
|
+
== LICENSE:
|
36
|
+
|
37
|
+
(The MIT License)
|
38
|
+
|
39
|
+
Copyright (c) 2009 Philip Dodds
|
40
|
+
|
41
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
42
|
+
a copy of this software and associated documentation files (the
|
43
|
+
'Software'), to deal in the Software without restriction, including
|
44
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
45
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
46
|
+
permit persons to whom the Software is furnished to do so, subject to
|
47
|
+
the following conditions:
|
48
|
+
|
49
|
+
The above copyright notice and this permission notice shall be
|
50
|
+
included in all copies or substantial portions of the Software.
|
51
|
+
|
52
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
53
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
54
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
55
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
56
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
57
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
58
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/q-ruby-driver
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
module QRubyDriver
|
3
|
+
|
4
|
+
# :stopdoc:
|
5
|
+
VERSION = '1.1.0'
|
6
|
+
LIBPATH = File.expand_path(File.dirname(__FILE__)) + File::SEPARATOR
|
7
|
+
PATH = File.dirname(LIBPATH) + File::SEPARATOR
|
8
|
+
|
9
|
+
# :startdoc:
|
10
|
+
|
11
|
+
# Returns the version string for the library.
|
12
|
+
#
|
13
|
+
def self.version
|
14
|
+
VERSION
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the library path for the module. If any arguments are given,
|
18
|
+
# they will be joined to the end of the libray path using
|
19
|
+
# <tt>File.join</tt>.
|
20
|
+
#
|
21
|
+
def self.libpath( *args )
|
22
|
+
args.empty? ? LIBPATH : File.join(LIBPATH, args.flatten)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the lpath for the module. If any arguments are given,
|
26
|
+
# they will be joined to the end of the path using
|
27
|
+
# <tt>File.join</tt>.
|
28
|
+
#
|
29
|
+
def self.path( *args )
|
30
|
+
args.empty? ? PATH : File.join(PATH, args.flatten)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Utility method used to require all files ending in .rb that lie in the
|
34
|
+
# directory below this file that has the same name as the filename passed
|
35
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
36
|
+
# the _filename_ does not have to be equivalent to the directory.
|
37
|
+
#
|
38
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
39
|
+
dir ||= File.basename(fname, '.*')
|
40
|
+
search_me = File.expand_path(
|
41
|
+
File.join(File.dirname(fname), dir, '**', '*.rb'))
|
42
|
+
|
43
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
44
|
+
end
|
45
|
+
|
46
|
+
end # module QRubyDriver
|
47
|
+
|
48
|
+
QRubyDriver.require_all_libs_relative_to(__FILE__)
|
49
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module QRubyDriver
|
4
|
+
class QConnection
|
5
|
+
|
6
|
+
@@BUFFER_SIZE = 2048
|
7
|
+
|
8
|
+
# Initializes the connection
|
9
|
+
def initialize(host="localhost", port=3000, username = ENV['USER'])
|
10
|
+
@client_socket = TCPSocket.new(host, port)
|
11
|
+
@client_socket.write [username, "001"].pack("a*H")
|
12
|
+
@client_socket.recv(4).unpack("H*")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Sync Send
|
16
|
+
def get(obj)
|
17
|
+
write_to_socket(obj, true)
|
18
|
+
read_from_socket()
|
19
|
+
end
|
20
|
+
alias :execute :get
|
21
|
+
|
22
|
+
# ASync send
|
23
|
+
def set(obj)
|
24
|
+
write_to_socket(obj, false)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Takes a hex encoded representation of the message to send
|
28
|
+
def send_raw(raw_message, sync=true)
|
29
|
+
encoded_message = [raw_message].pack("H*")
|
30
|
+
@client_socket.write encoded_message
|
31
|
+
|
32
|
+
if (encoded_message[1] == 1)
|
33
|
+
read_from_socket()
|
34
|
+
else
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Dumps table schema
|
40
|
+
def table_info(table_name)
|
41
|
+
get("meta #{table_name}")
|
42
|
+
end
|
43
|
+
|
44
|
+
# Closes the connection
|
45
|
+
def close
|
46
|
+
@client_socket.close
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def write_to_socket(obj, sync=true)
|
52
|
+
qio = QIO.new
|
53
|
+
qio.write_message(obj, sync ? :sync : :async)
|
54
|
+
qio.pos=0
|
55
|
+
@client_socket.write qio.read
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_from_socket()
|
59
|
+
qio = buffered_recv()
|
60
|
+
qio.read_message
|
61
|
+
end
|
62
|
+
|
63
|
+
def buffered_recv()
|
64
|
+
# peek at the total message length
|
65
|
+
peek = @client_socket.recvfrom(8, Socket::MSG_PEEK)
|
66
|
+
length = QIO.new(peek[0]).read_message_header[0]
|
67
|
+
|
68
|
+
# read up to full message length
|
69
|
+
qio = QIO.new()
|
70
|
+
while qio.length < length
|
71
|
+
qio.write @client_socket.recvfrom(@@BUFFER_SIZE)[0]
|
72
|
+
end
|
73
|
+
qio.pos=0
|
74
|
+
return qio
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module QRubyDriver
|
2
|
+
|
3
|
+
# An exception which was raised by the Q service itself, originating
|
4
|
+
# from the server-side
|
5
|
+
class QException < RuntimeError
|
6
|
+
|
7
|
+
attr_reader :message
|
8
|
+
|
9
|
+
def initialize(message)
|
10
|
+
@message = message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# An exception which occurs on the client-side (within this app)
|
15
|
+
# during the I/O processing of Q messages
|
16
|
+
class QIOException < RuntimeError
|
17
|
+
|
18
|
+
attr_reader :message
|
19
|
+
|
20
|
+
def initialize(q_message)
|
21
|
+
@message = message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,481 @@
|
|
1
|
+
module QRubyDriver
|
2
|
+
|
3
|
+
# @author John Shields
|
4
|
+
# Single-pass Ruby reader/writer for byte-streams in Q IPC protocol format
|
5
|
+
class QIO < StringIO
|
6
|
+
|
7
|
+
# Q type constants
|
8
|
+
Q_ATOM_TYPES = -19..-1
|
9
|
+
Q_VECTOR_TYPES = 1..19
|
10
|
+
Q_TYPE_EXCEPTION = -128
|
11
|
+
Q_TYPE_BOOLEAN = -1
|
12
|
+
Q_TYPE_BYTE = -4
|
13
|
+
Q_TYPE_SHORT = -5
|
14
|
+
Q_TYPE_INT = -6
|
15
|
+
Q_TYPE_LONG = -7
|
16
|
+
Q_TYPE_REAL = -8 # single-prec float
|
17
|
+
Q_TYPE_FLOAT = -9 # double-prec float
|
18
|
+
Q_TYPE_CHAR = -10
|
19
|
+
Q_TYPE_SYMBOL = -11
|
20
|
+
Q_TYPE_TIMESTAMP = -12
|
21
|
+
Q_TYPE_MONTH = -13
|
22
|
+
Q_TYPE_DATE = -14
|
23
|
+
Q_TYPE_DATETIME = -15
|
24
|
+
Q_TYPE_TIMESPAN = -16
|
25
|
+
Q_TYPE_MINUTE = -17
|
26
|
+
Q_TYPE_SECOND = -18
|
27
|
+
Q_TYPE_TIME = -19
|
28
|
+
Q_TYPE_CHAR_VECTOR= 10
|
29
|
+
Q_TYPE_SYMBOL_VECTOR= 11
|
30
|
+
Q_TYPE_LIST = 0
|
31
|
+
Q_TYPE_FLIP = 98
|
32
|
+
Q_TYPE_DICTIONARY= 99
|
33
|
+
|
34
|
+
# Read methods
|
35
|
+
|
36
|
+
# Decodes a binary Q message into Ruby types
|
37
|
+
def read_message()
|
38
|
+
self.read(8) # skip message header
|
39
|
+
return read_item
|
40
|
+
end
|
41
|
+
|
42
|
+
# Extracts length and message type from the message header
|
43
|
+
def read_message_header
|
44
|
+
header = self.read(8).unpack("H2H2H4I")
|
45
|
+
length = header[3]
|
46
|
+
case header[1]
|
47
|
+
when "00" then
|
48
|
+
msg_type = :async
|
49
|
+
when "01" then
|
50
|
+
msg_type = :sync
|
51
|
+
when "02" then
|
52
|
+
msg_type = :response
|
53
|
+
end
|
54
|
+
return length, msg_type
|
55
|
+
end
|
56
|
+
|
57
|
+
# Reads the next item and extracts it into a Ruby type
|
58
|
+
# Will extract vectors, dictionaries, lists, etc. recursively
|
59
|
+
def read_item(type = nil)
|
60
|
+
type = read_byte() if type.nil?
|
61
|
+
case type
|
62
|
+
when Q_TYPE_EXCEPTION then
|
63
|
+
raise QException.new(read_symbol)
|
64
|
+
when Q_ATOM_TYPES then
|
65
|
+
return read_atom(type)
|
66
|
+
when Q_TYPE_LIST then
|
67
|
+
return read_list
|
68
|
+
when Q_VECTOR_TYPES then
|
69
|
+
return read_vector(type)
|
70
|
+
when Q_TYPE_FLIP then
|
71
|
+
return read_flip
|
72
|
+
when Q_TYPE_DICTIONARY then
|
73
|
+
return read_dictionary
|
74
|
+
when 100 then
|
75
|
+
read_symbol
|
76
|
+
return read_item
|
77
|
+
when 101..103 then
|
78
|
+
return read_byte == 0 && type == 101 ? nil : "func";
|
79
|
+
when 104 then
|
80
|
+
read_int.times { read_item }
|
81
|
+
return "func"
|
82
|
+
when 105..255 then
|
83
|
+
read_item
|
84
|
+
return "func"
|
85
|
+
else
|
86
|
+
raise "Cannot read unknown type #{type}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Complex type handlers
|
91
|
+
|
92
|
+
# Reads a vector into a Ruby Array
|
93
|
+
def read_vector(type)
|
94
|
+
length = self.read(5).unpack("c1I")[1]
|
95
|
+
byte_type, num_bytes = get_atom_pack(-type)
|
96
|
+
|
97
|
+
if type==Q_TYPE_SYMBOL_VECTOR
|
98
|
+
raw = length.times.map{ self.readline("\x00") }.inject{|str, n| str + n}
|
99
|
+
value = raw.unpack(byte_type*length)
|
100
|
+
else
|
101
|
+
raw = self.read(length*num_bytes)
|
102
|
+
value = raw.unpack(byte_type+length.to_s)
|
103
|
+
end
|
104
|
+
|
105
|
+
# char vectors are returned as strings
|
106
|
+
# all other types are returned as arrays
|
107
|
+
(type == Q_TYPE_CHAR_VECTOR) ? value[0] : value.map{|i| atom_to_ruby(i, -type)}
|
108
|
+
end
|
109
|
+
|
110
|
+
# Reads a dictionary into a Ruby Hash
|
111
|
+
def read_dictionary
|
112
|
+
# The first item is a vector containing the dictionary keys
|
113
|
+
keys = read_item
|
114
|
+
keys = [keys] unless keys.is_a? Array
|
115
|
+
|
116
|
+
# The second item is a list containing the values of each key
|
117
|
+
values = read_item
|
118
|
+
values = [values] unless values.is_a? Array
|
119
|
+
|
120
|
+
hash = {}
|
121
|
+
keys.zip(values) { |k,v| hash[k]=v }
|
122
|
+
return hash
|
123
|
+
end
|
124
|
+
|
125
|
+
# Decodes a flip table into a Ruby Hash
|
126
|
+
def read_flip
|
127
|
+
self.read(1)
|
128
|
+
read_item # should be a dictionary
|
129
|
+
end
|
130
|
+
|
131
|
+
# Decodes a list into an array
|
132
|
+
def read_list
|
133
|
+
length = self.read(5).unpack("c1I")[1]
|
134
|
+
length.times.map { read_item }
|
135
|
+
end
|
136
|
+
|
137
|
+
# Extracts atom types into Ruby types
|
138
|
+
def read_atom(type)
|
139
|
+
raise QIOException.new "Cannot read atom type #{type}" unless (type>=-19 and type<0)
|
140
|
+
byte_type, num_bytes = get_atom_pack(type)
|
141
|
+
raw = (type==Q_TYPE_SYMBOL) ? self.readline("\x00") : self.read(num_bytes)
|
142
|
+
value = raw.unpack(byte_type)[0]
|
143
|
+
|
144
|
+
atom_to_ruby(value, type)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Extracts atom types into Ruby types
|
148
|
+
def atom_to_ruby(value, type)
|
149
|
+
case type
|
150
|
+
when Q_TYPE_BOOLEAN then
|
151
|
+
boolean_to_ruby value
|
152
|
+
# TODO: add support for date/time types
|
153
|
+
else
|
154
|
+
value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Atom type handlers
|
159
|
+
def boolean_to_ruby value
|
160
|
+
value==1
|
161
|
+
end
|
162
|
+
|
163
|
+
# Short cut methods for reading atoms
|
164
|
+
def read_boolean
|
165
|
+
read_atom(Q_TYPE_BOOLEAN)
|
166
|
+
end
|
167
|
+
def read_byte
|
168
|
+
read_atom(Q_TYPE_BYTE)
|
169
|
+
end
|
170
|
+
def read_short
|
171
|
+
read_atom(Q_TYPE_SHORT)
|
172
|
+
end
|
173
|
+
def read_int
|
174
|
+
read_atom(Q_TYPE_INT)
|
175
|
+
end
|
176
|
+
def read_long
|
177
|
+
read_atom(Q_TYPE_LONG)
|
178
|
+
end
|
179
|
+
def read_real
|
180
|
+
read_atom(Q_TYPE_REAL)
|
181
|
+
end
|
182
|
+
def read_float
|
183
|
+
read_atom(Q_TYPE_FLOAT)
|
184
|
+
end
|
185
|
+
def read_char
|
186
|
+
read_atom(Q_TYPE_CHAR)
|
187
|
+
end
|
188
|
+
def read_symbol
|
189
|
+
read_atom(Q_TYPE_SYMBOL)
|
190
|
+
end
|
191
|
+
def read_timestamp
|
192
|
+
read_atom(Q_TYPE_TIMESTAMP)
|
193
|
+
end
|
194
|
+
def read_month
|
195
|
+
read_atom(Q_TYPE_MONTH)
|
196
|
+
end
|
197
|
+
def read_date
|
198
|
+
read_atom(Q_TYPE_DATE)
|
199
|
+
end
|
200
|
+
def read_datetime
|
201
|
+
read_atom(Q_TYPE_DATETIME)
|
202
|
+
end
|
203
|
+
def read_timespan
|
204
|
+
read_atom(Q_TYPE_TIMESPAN)
|
205
|
+
end
|
206
|
+
def read_minute
|
207
|
+
read_atom(Q_TYPE_MINUTE)
|
208
|
+
end
|
209
|
+
def read_second
|
210
|
+
read_atom(Q_TYPE_SECOND)
|
211
|
+
end
|
212
|
+
def read_time
|
213
|
+
read_atom(Q_TYPE_TIME)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Write methods
|
217
|
+
|
218
|
+
# Writes a Ruby object to a Q message
|
219
|
+
def write_message(message, msg_type=:async)
|
220
|
+
offset = self.pos
|
221
|
+
self.write ["01"].pack("H*")
|
222
|
+
self.write case msg_type
|
223
|
+
when :async then ["00"].pack("H*")
|
224
|
+
when :sync then ["01"].pack("H*")
|
225
|
+
when :response then ["02"].pack("H*")
|
226
|
+
else raise QIOException.new("Cannot write unknown message type #{msg_type.to_s}")
|
227
|
+
end
|
228
|
+
self.write ["0000"].pack("H*")
|
229
|
+
self.pos += 4 # will write size here
|
230
|
+
write_item(message)
|
231
|
+
# write size
|
232
|
+
size = self.pos - offset
|
233
|
+
self.pos = offset+4
|
234
|
+
write_int(size)
|
235
|
+
# set position to end of buffer
|
236
|
+
self.pos = offset + size
|
237
|
+
end
|
238
|
+
|
239
|
+
# Helper method to infer Q type from native Ruby types
|
240
|
+
def get_q_type(item)
|
241
|
+
if item.is_a? Exception
|
242
|
+
Q_TYPE_EXCEPTION
|
243
|
+
elsif item.is_a?(TrueClass) || item.is_a?(FalseClass)
|
244
|
+
Q_TYPE_BOOLEAN
|
245
|
+
elsif item.is_a? Bignum
|
246
|
+
Q_TYPE_LONG
|
247
|
+
elsif item.is_a? Fixnum
|
248
|
+
Q_TYPE_INT
|
249
|
+
elsif item.is_a? Float
|
250
|
+
Q_TYPE_FLOAT
|
251
|
+
elsif item.is_a? String
|
252
|
+
Q_TYPE_CHAR_VECTOR
|
253
|
+
elsif item.is_a? Symbol
|
254
|
+
Q_TYPE_SYMBOL
|
255
|
+
# not yet supported
|
256
|
+
# elsif item.is_a? Date
|
257
|
+
# Q_TYPE_DATE
|
258
|
+
# elsif item.is_a? DateTime
|
259
|
+
# Q_TYPE_DATETIME
|
260
|
+
# elsif item.is_a? Time
|
261
|
+
# Q_TYPE_TIME
|
262
|
+
elsif item.is_a? Array
|
263
|
+
get_q_array_type(item)
|
264
|
+
elsif item.is_a? Hash
|
265
|
+
Q_TYPE_FLIP
|
266
|
+
else
|
267
|
+
raise QIOException.new("Cannot infer Q type from #{item.class.to_s}")
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Helper method to write a Ruby array into either a list or a vector,
|
272
|
+
# depending on whether or not the array contains mixed types
|
273
|
+
def get_q_array_type(array)
|
274
|
+
raise QIOException.new("Cannot write empty array") if array.empty?
|
275
|
+
|
276
|
+
klass = array[0].class
|
277
|
+
return 0 if klass==String # String is a vector type; cannot make a vector of vectors
|
278
|
+
|
279
|
+
if klass==TrueClass || klass==FalseClass # special routine for booleans
|
280
|
+
array.each do |item|
|
281
|
+
return 0 unless item.is_a?(TrueClass) || item.is_a?(FalseClass)
|
282
|
+
end
|
283
|
+
else
|
284
|
+
array.each do |item|
|
285
|
+
return 0 unless item.is_a? klass
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
-1 * get_q_type(array[0])
|
290
|
+
end
|
291
|
+
|
292
|
+
# Encodes a type into the IPC representation
|
293
|
+
# no native support for the following atom types: byte, short, real, char
|
294
|
+
def write_item(item, type=nil)
|
295
|
+
type=get_q_type(item) if type.nil?
|
296
|
+
write_type type
|
297
|
+
case type
|
298
|
+
when Q_TYPE_EXCEPTION then
|
299
|
+
write_exception item
|
300
|
+
when Q_ATOM_TYPES then
|
301
|
+
write_atom item, type
|
302
|
+
when Q_TYPE_LIST then
|
303
|
+
write_list item
|
304
|
+
when Q_TYPE_CHAR_VECTOR then
|
305
|
+
write_string item
|
306
|
+
when 1..9, 11..19 then # Q_VECTOR_TYPES minus Q_TYPE_CHAR_VECTOR
|
307
|
+
write_vector item, type
|
308
|
+
when Q_TYPE_FLIP then
|
309
|
+
write_flip item
|
310
|
+
when Q_TYPE_DICTIONARY then
|
311
|
+
write_dictionary item
|
312
|
+
else
|
313
|
+
raise QIOException.new "Cannot write type #{type}"
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Writes the type byte
|
318
|
+
def write_type(type)
|
319
|
+
self.write [type].pack("c1")
|
320
|
+
end
|
321
|
+
|
322
|
+
# Encodes an array as a vector
|
323
|
+
def write_vector(array, type=nil)
|
324
|
+
raise QIOException("Cannot write empty vector") if array.empty?
|
325
|
+
type = -1 * get_q_type(array[0]) if type.nil?
|
326
|
+
self.write ["00", array.length].pack("H1I")
|
327
|
+
if type==Q_TYPE_SYMBOL_VECTOR
|
328
|
+
array.each{|x| self.write [ruby_to_atom(x, -type)].pack( get_atom_pack(-type)[0] ) }
|
329
|
+
else
|
330
|
+
self.write array.map{|x| ruby_to_atom(x, -type)}.pack( get_atom_pack(-type)[0] + array.length.to_s )
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Encodes a string as a char vector
|
335
|
+
def write_string(item)
|
336
|
+
value = item.is_a?(String) ? item.scan(/./) : item # convert string into a char array
|
337
|
+
self.write ["00", value.length].pack("H1I")
|
338
|
+
self.write value.pack("A"*value.length)
|
339
|
+
end
|
340
|
+
|
341
|
+
# Encodes a list
|
342
|
+
def write_list(array)
|
343
|
+
raise QIOException("Cannot write empty list") if array.empty?
|
344
|
+
self.write ["00", array.length].pack("H1I1")
|
345
|
+
array.each { |item| write_item(item) }
|
346
|
+
end
|
347
|
+
|
348
|
+
# Encodes a dictionary
|
349
|
+
def write_dictionary(hash)
|
350
|
+
write_type Q_TYPE_SYMBOL_VECTOR
|
351
|
+
write_vector hash.keys, Q_TYPE_SYMBOL_VECTOR
|
352
|
+
write_type Q_TYPE_LIST
|
353
|
+
write_list hash.values
|
354
|
+
end
|
355
|
+
|
356
|
+
# Encodes a flip table
|
357
|
+
def write_flip(hash)
|
358
|
+
self.write ["00"].pack("H1")
|
359
|
+
write_item(hash, 99) # dictionary
|
360
|
+
end
|
361
|
+
|
362
|
+
# Encodes atom types
|
363
|
+
def write_atom(value, type)
|
364
|
+
raise QIOException.new "Cannot write atom type #{type}" unless ((type>=-19 and type<0) || type==Q_TYPE_EXCEPTION)
|
365
|
+
self.write [ruby_to_atom(value, type)].pack(get_atom_pack(type)[0])
|
366
|
+
end
|
367
|
+
|
368
|
+
# Returns pack type and byte-length of q atom type
|
369
|
+
def get_atom_pack(type)
|
370
|
+
case type
|
371
|
+
when Q_TYPE_BOOLEAN, Q_TYPE_BYTE then
|
372
|
+
['c',1]
|
373
|
+
when Q_TYPE_SHORT then
|
374
|
+
['s',2]
|
375
|
+
when Q_TYPE_INT, Q_TYPE_MONTH, Q_TYPE_DATE, Q_TYPE_MINUTE, Q_TYPE_SECOND, Q_TYPE_TIME then
|
376
|
+
['I',4]
|
377
|
+
when Q_TYPE_LONG, Q_TYPE_TIMESTAMP, Q_TYPE_TIMESPAN then
|
378
|
+
['q',8]
|
379
|
+
when Q_TYPE_REAL then
|
380
|
+
['F',4]
|
381
|
+
when Q_TYPE_FLOAT, Q_TYPE_DATETIME then
|
382
|
+
['D',8]
|
383
|
+
when Q_TYPE_CHAR then
|
384
|
+
['Z',1]
|
385
|
+
when Q_TYPE_SYMBOL, Q_TYPE_EXCEPTION then
|
386
|
+
['Z*',0]
|
387
|
+
else
|
388
|
+
raise QIOException.new "Unknown atom type #{type}"
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def ruby_to_atom(value, type)
|
393
|
+
case type
|
394
|
+
when Q_TYPE_BOOLEAN then
|
395
|
+
ruby_to_boolean value
|
396
|
+
when Q_TYPE_SYMBOL then
|
397
|
+
ruby_to_symbol value
|
398
|
+
when Q_TYPE_EXCEPTION then
|
399
|
+
ruby_to_exception value
|
400
|
+
else
|
401
|
+
value
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def ruby_to_boolean(value)
|
406
|
+
if value.is_a? TrueClass
|
407
|
+
1
|
408
|
+
elsif value.is_a? FalseClass
|
409
|
+
0
|
410
|
+
else
|
411
|
+
value == true
|
412
|
+
end
|
413
|
+
end
|
414
|
+
def ruby_to_symbol(value)
|
415
|
+
value.to_s
|
416
|
+
end
|
417
|
+
def ruby_to_exception(value)
|
418
|
+
if value.is_a? Exception
|
419
|
+
value.message
|
420
|
+
else
|
421
|
+
value.to_s
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
# Atom type write shortcut methods
|
426
|
+
def write_boolean(value)
|
427
|
+
write_atom(value, Q_TYPE_BOOLEAN)
|
428
|
+
end
|
429
|
+
def write_byte(value)
|
430
|
+
write_atom(value, Q_TYPE_BYTE)
|
431
|
+
end
|
432
|
+
def write_short(value)
|
433
|
+
write_atom(value, Q_TYPE_SHORT)
|
434
|
+
end
|
435
|
+
def write_int(value)
|
436
|
+
write_atom(value, Q_TYPE_INT)
|
437
|
+
end
|
438
|
+
def write_long(value)
|
439
|
+
write_atom(value, Q_TYPE_LONG)
|
440
|
+
end
|
441
|
+
def write_real(value)
|
442
|
+
write_atom(value, Q_TYPE_REAL)
|
443
|
+
end
|
444
|
+
def write_float(value)
|
445
|
+
write_atom(value, Q_TYPE_FLOAT)
|
446
|
+
end
|
447
|
+
def write_char(value)
|
448
|
+
write_atom(value, Q_TYPE_CHAR)
|
449
|
+
end
|
450
|
+
def write_symbol(value)
|
451
|
+
write_atom(value, Q_TYPE_SYMBOL)
|
452
|
+
end
|
453
|
+
def write_exception(value)
|
454
|
+
write_atom(value, Q_TYPE_EXCEPTION)
|
455
|
+
end
|
456
|
+
def write_timestamp(value)
|
457
|
+
write_atom(value, Q_TYPE_TIMESTAMP)
|
458
|
+
end
|
459
|
+
def write_month(value)
|
460
|
+
write_atom(value, Q_TYPE_MONTH)
|
461
|
+
end
|
462
|
+
def write_date(value)
|
463
|
+
write_atom(value, Q_TYPE_DATE)
|
464
|
+
end
|
465
|
+
def write_datetime(value)
|
466
|
+
write_atom(value, Q_TYPE_DATETIME)
|
467
|
+
end
|
468
|
+
def write_timespan(value)
|
469
|
+
write_atom(value, Q_TYPE_TIMESPAN)
|
470
|
+
end
|
471
|
+
def write_minute(value)
|
472
|
+
write_atom(value, Q_TYPE_MINUTE)
|
473
|
+
end
|
474
|
+
def write_second(value)
|
475
|
+
write_atom(value, Q_TYPE_SECOND)
|
476
|
+
end
|
477
|
+
def write_time(value)
|
478
|
+
write_atom(value, Q_TYPE_TIME)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|