q-ruby-driver 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib q-ruby-driver]))
5
+
6
+ # Put your code here
7
+
@@ -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