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 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