· 8 years ago · Mar 11, 2018, 02:36 AM
1#!/usr/bin/env ruby
2
3require 'socket'
4
5# FastDB API
6# This module contains Ruby API to FastDB
7module FastDB
8 # Connection to the FastDB server
9 class Connection
10 # Opens connection to the server
11 # +host_address+ -- string with server host name
12 # +host_port+ -- integer number with server port
13 def initialize(host_address, host_port)
14 @socket = TCPSocket.new(host_address, host_port)
15 @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
16 @n_statements = 0
17 load_schema
18 end
19
20 # Close connection with server
21 def close
22 send_command(CliCmdCloseSession)
23 @socket.close
24 @socket = nil
25 end
26
27 # Create select statement.
28 # sql - SubSQL select statement with parameters. Parameters should be started with % character.
29 # Each used parameter should be set before execution of the statement.
30 def create_statement(sql)
31 @n_statements += 1
32 Statement.new(self, sql, @n_statements)
33 end
34
35 # Commit current transaction
36 def commit
37 send_receive_command(CliCmdCommit)
38 end
39
40 # Exclusively lock database (FastDB set locks implicitely, explicit exclusive lock may be needed to avoid deadlock caused by lock upgrade)
41 def lock
42 send_command(CliCmdLock)
43 end
44
45 # Release all locks set by the current transaction
46 def unlock
47 send_receive_command(CliCmdPrecommit)
48 end
49
50 # Rollback curent transaction. All changes made by current transaction are lost.
51 def rollback
52 send_receive_command(CliCmdAbort)
53 end
54
55 # Insert object in the database. There is should be table in the database with
56 # name equal to the full class name of the inserted object (comparison is
57 # case sensitive). FastDB will store to the database all non-static and
58 # non-transient fields from the class.
59 # obj - object to be inserted in the database
60 # table - name of the table in which object should be inserted (by default - table corresponding to the object class)
61 # Returns reference to the inserted object
62 def insert(table, obj)
63 column_defs=""
64 column_values=""
65 n_columns=0
66 table_desc = @tables[table]
67 raise CliError, "Table #{table} is not found in the database" unless table_desc
68 obj.each do |key, value|
69 field = key.to_s
70 field_desc = table_desc[field]
71 raise CliError, "Column #{field} is not found in the table #{table}" unless field_desc
72
73 n_columns += 1
74 case value ## Why do we look at the type of value? Should we look at schema?
75 when Fixnum
76 column_defs << CliInt4 << field << 0
77 column_values << [value].pack("N")
78 when Bignum
79 raise CliError, "#{value} is too big. Only numbers up to 8 bytes are supported" if value.size > 8
80 column_defs << CliInt8 << field << 0
81 column_values << [value >> 32, value & 0xffffffff].pack("NN")
82 when Float
83 column_defs << CliReal8 << field << 0
84 column_values << [value].pack("G")
85 when String
86 if field_desc.type == CliArrayOfInt1
87 column_defs << CliArrayOfInt1 << field << 0
88 column_values << [value.length].pack("N") << value
89 else
90 column_defs << CliAsciiz << field << 0
91 column_values << [value.length+1].pack("N") << value << 0
92 end
93 when Reference
94 column_defs << CliOid << field << 0
95 column_values << [value.oid].pack("N")
96 when TrueClass
97 column_defs << CliBool << field << 0
98 column_values << 1
99 when FalseClass
100 column_defs << CliBool << field << 0
101 column_values << 0
102 when Rectangle
103 column_defs << CliRectangle << field << 0
104 column_values << [value.left, value.top, value.right, value.bottom].pack("NNNN")
105 when Array
106 column_defs << field_desc.type << field << 0
107 column_values << [value.length].pack("N")
108 case field_desc.type
109 when CliArrayOfInt1
110 column_values << value.pack("c*")
111 when CliArrayOfBool
112 value.each { |elem| column_values << elem ? 1 : 0 }
113 when CliArrayOfInt2
114 column_values << value.pack("n*")
115 when CliArrayOfInt4
116 column_values << value.pack("N*")
117 when CliArrayOfInt8
118 value.each { |elem| column_values << [elem >> 32, elem & 0xffffffff].pack("NN") }
119 when CliArrayOfReal4
120 column_values << value.pack("g*")
121 when CliArrayOfReal8
122 column_values << value.pack("G*")
123 when CliArrayOfOid
124 value.each { |elem| column_values << [elem.oid].pack("N") }
125 when CliArrayOfString
126 value.each { |elem| column_values << elem << 0 }
127 else
128 raise CliError, "Unsupported element type " + field_desc.type
129 end
130 else
131 raise CliError, "Unsupported type #{type.name}"
132 end
133 end
134
135 req = [CliRequestSize + 14 +table.length + column_defs.length + column_values.length, CliCmdPrepareAndInsert, 0].pack("NNN")
136 req << "insert into " << table << 0 << n_columns << column_defs << column_values
137 @socket.send(req, 0)
138 rc = @socket.recv(CliRequestSize).unpack("NNN")
139
140 raise_error(rc[0]) unless rc[0] == CliOk
141 Reference.new(rc[2]) if rc[2] != 0
142 end
143
144 def load_schema
145 send_command(CliCmdShowTables)
146 ret = @socket.recv(8).unpack("NN")
147 len = ret[0]
148 n_tables = ret[1]
149 table_names = @socket.recv(len)
150 @tables = {}
151 tables = table_names.split("\0")
152 tables.each do |table|
153 @socket.send([CliRequestSize + table.length + 1, CliCmdDescribeTable, 0].pack("NNN") << table << 0, 0)
154 ret = @socket.recv(8).unpack("NN")
155 len = ret[0]
156 n_fields = ret[1]
157 field_info = @socket.recv(len)
158 fields = Array.new(n_fields)
159 j = 0
160 for k in 0...n_fields
161 type = field_info[j]
162 j += 1
163 flags = field_info[j]
164 j += 1
165
166 z = field_info.index(0, j)
167 name = field_info[j...z]
168 j = z + 1
169
170 z = field_info.index(0, j)
171 if z != j
172 ref_table = field_info[j...z]
173 else
174 ref_table = nil
175 end
176 j = z + 1
177
178 z = field_info.index(0, j)
179 if z != j
180 inverse_field = field_info[j...z]
181 else
182 inverse_field = nil
183 end
184 j = z + 1
185
186 fields[k] = FieldDescriptor.new(name, ref_table, inverse_field, type, flags)
187 end
188 @tables[table] = TableDescriptor.new(fields)
189 end
190 end
191
192 def send_command(cmd, id=0)
193 @socket.send([CliRequestSize, cmd, id].pack("NNN"), 0)
194 end
195
196 def receive(len)
197 @socket.recv(len)
198 end
199
200 def send_receive_command(cmd, id=0)
201 send_command(cmd, id)
202 rc = @socket.recv(4).unpack("N")[0]
203
204 raise CliError, "Request failed with status #{rc}" if rc < 0
205
206 rc
207 end
208
209 def send_receive_request(req)
210 @socket.send(req, 0)
211 rc = @socket.recv(4).unpack("N")[0]
212
213 raise CliError, "Request failed with status #{rc}" if rc < 0
214
215 rc
216 end
217
218
219 # Close connection with server
220 def close
221 send_command(CliCmdCloseSession)
222 @socket.close()
223 @socket = nil
224 end
225
226
227 def raise_error(error_code, prefix = "")
228 raise CliError, "#{prefix}#{ERROR_DESCRIPTIONS[error_code]}" if ERROR_DESCRIPTIONS[error_code]
229 raise CliError, "#{prefix}Unknown error code #{error_code}"
230 end
231
232 CliRequestSize = 12
233
234 # Field flag
235 CliHashed = 1 # field should be indexed usnig hash table
236 CliIndexed = 2 # field should be indexed using B-Tree
237 CliCascadeDelete = 8 # perfrom cascade delete for for reference or array of reference fields
238 CliAutoincremented = 16 # field is assigned automaticall incremented value
239
240 # Operation result codes
241 CliOk = 0
242 CliBadAddress = 4294967295
243 CliConnectionRefused = 4294967294
244 CliDatabaseNotFound = 4294967293
245 CliBadStatement = 4294967292
246 CliParameterNotFound = 4294967291
247 CliUnboundParameter = 4294967290
248
249 CliColumnNotFound = 4294967289
250 CliIncompatibleType = 4294967288
251 CliNetworkError = 4294967287
252 CliRuntimeError = 4294967286
253 CliClosedStatement = 4294967285
254 CliUnsupportedType = 4294967284
255 CliNotFound = 4294967283
256 CliNotUpdateMode = 4294967282
257 CliTableNotFound = 4294967281
258 CliNotAllColumnsSpecified = 4294967280
259 CliNotFetched = 4294967279
260 CliAlreadyUpdated = 4294967278
261 CliTableAlreadyExists = 4294967277
262 CliNotImplemented = 4294967276
263 CliLoginFailed = 4294967275
264 CliEmptyParameter = 4294967274
265 CliClosedConnection = 4294967273
266
267 ERROR_DESCRIPTIONS = {
268 CliBadAddress => "Bad address",
269 CliConnectionRefused => "Connection refused",
270 CliDatabaseNotFound => "Database not found",
271 CliBadStatement => "Bad statement",
272 CliParameterNotFound => "Parameter not found",
273 CliUnboundParameter => "Unbound parameter",
274 CliColumnNotFound => "Column not found",
275 CliIncompatibleType => "Incomptaible type",
276 CliNetworkError => "Network error",
277 CliRuntimeError => "Runtime error",
278 CliClosedStatement => "Closed statement",
279 CliUnsupportedType => "Unsupported type",
280 CliNotFound => "Not found",
281 CliNotUpdateMode => "Not update mode",
282 CliTableNotFound => "Table not found",
283 CliNotAllColumnsSpecified => "Not all columns specified",
284 CliNotFetched => "Not fetched",
285 CliAlreadyUpdated => "Already updated",
286 CliTableAlreadyExists => "Table already exists",
287 CliNotImplemented => "Not implemented",
288 CliLoginFailed => "Login failed",
289 CliEmptyParameter => "Empty parameter",
290 CliClosedConnection => "Closed connection"}
291
292 # Command codes
293 CliCmdCloseSession = 0
294 CliCmdPrepareAndExecute = 1
295 CliCmdExecute = 2
296 CliCmdGetFirst = 3
297 CliCmdGetLast = 4
298 CliCmdGetNext = 5
299 CliCmdGetPrev = 6
300 CliCmdFreeStatement = 7
301 CliCmdAbort = 8
302 CliCmdCommit = 9
303 CliCmdUpdate = 10
304 CliCmdRemove = 11
305 CliCmdRemoveCurrent = 12
306 CliCmdInsert = 13
307 CliCmdPrepareAndInsert = 14
308 CliCmdDescribeTable = 15
309 CliCmdShowTables = 16
310 CliCmdPrecommit = 17
311 CliCmdSkip = 18
312 CliCmdCreateTable = 19
313 CliCmdDropTable = 20
314 CliCmdAlterIndex = 21
315 CliCmdFreeze = 22
316 CliCmdUnfreeze = 23
317 CliCmdSeek = 24
318 CliCmdAlterTable = 25
319 CliCmdLock = 26
320
321
322 # Field type codes
323 CliOid = 0
324 CliBool = 1
325 CliInt1 = 2
326 CliInt2 = 3
327 CliInt4 = 4
328 CliInt8 = 5
329 CliReal4 = 6
330 CliReal8 = 7
331 CliDecimal = 8
332 CliAsciiz = 9
333 CliPasciiz = 10
334 CliCstring = 11
335 CliArrayOfOid = 12
336 CliArrayOfBool = 13
337 CliArrayOfInt1 = 14
338 CliArrayOfInt2 = 15
339 CliArrayOfInt4 = 16
340 CliArrayOfInt8 = 17
341 CliArrayOfReal4 = 18
342 CliArrayOfReal8 = 19
343 CliArrayOfDecimal = 20
344 CliArrayOfString = 21
345 CliAny = 22
346 CliDatetime = 23
347 CliAutoincrement = 24
348 CliRectangle = 25
349 CliUndefined = 26
350
351
352 attr_reader :tables
353 end
354
355
356
357 # Statement class is used to prepare and execute select statement
358 class Statement
359 private
360 SPACE = 32
361 PERCENT = 37
362 QUOTE = 39
363 LETTER_A = 65
364 LETTER_Z = 90
365 LETTER_a = 97
366 LETTER_z = 122
367 DIGIT_0 = 48
368 DIGIT_9 = 57
369 UNDERSCORE = 95
370
371 public
372
373 # Statement constructor called by Connection class
374 def initialize(con, sql, stmt_id)
375 @con = con
376 @stmt_id = stmt_id
377 r = sql.match(/\s+from\s+([^\s]+)/i)
378 raise CliError, "Bad statement: table name is expected after FROM" unless r
379
380 table_name = r[1]
381 @table = con.tables[table_name]
382 in_quotes = false
383 param_name = nil
384 @param_hash = {}
385 @param_list = []
386 req_str=""
387 sql.each_byte do |ch|
388 if ch == QUOTE
389 in_quotes = !in_quotes
390 req_str << ch
391 elsif ch == PERCENT and !in_quotes
392 param_name=""
393 elsif param_name != nil and ((ch >= LETTER_a and ch <= LETTER_z) or (ch >= LETTER_A and ch <= LETTER_Z) or (ch >= DIGIT_0 and ch <= DIGIT_9) or ch == UNDERSCORE)
394 param_name << ch
395 else
396 if param_name != nil
397 p = Parameter.new(param_name)
398 @param_list << p
399 @param_hash[param_name] = p
400 param_name = nil
401 req_str << 0
402 end
403 req_str << ch
404 end
405 end
406 if param_name != nil
407 p = Parameter.new(param_name)
408 @param_list << p
409 @param_hash[param_name] = p
410 req_str << 0
411 end
412 if req_str.length == 0 or req_str[-1] != 0
413 req_str << 0
414 end
415 @stmt = req_str
416 @prepared = false
417 end
418
419 # Get parameter value
420 def [](param_name)
421 @param_hash[param_name].value
422 end
423
424 # Assign value to the statement parameter
425 def []=(param_name, value)
426 @param_hash[param_name].value = value
427 end
428
429
430 # Prepare (if needed) and execute select statement
431 # Only object set returned by the select for updated statement allows
432 # update and deletion of the objects.
433 # forUpdate - if cursor is opened in for update mode
434 # Returns object set with the selected objects
435 def fetch(for_update = false)
436 cmd=Connection::CliCmdExecute
437 req=""
438 if !@prepared
439 cmd=Connection::CliCmdPrepareAndExecute
440 @prepared=true
441 req << @param_list.length << @table.fields.length << [@stmt.length + @param_list.length].pack("n")
442 param_no=0
443 @stmt.each_byte do |ch|
444 req << ch
445 if ch == 0 and param_no < @param_list.length
446 param = @param_list[param_no]
447 if param.type == Connection::CliUndefined
448 raise CliError, "Unbound parameter " + param.name
449 end
450 param_no += 1
451 req << param.type
452 end
453 end
454 for field in @table.fields
455 req << field.type << field.name << 0
456 end
457 end
458 @for_update = for_update
459 if for_update
460 req << 1
461 else
462 req << 0
463 end
464 for param in @param_list
465 case param.type
466 when Connection::CliOid
467 req << [param.value.oid].pack("N")
468 when Connection::CliBool
469 param.value ? 1 : 0
470 when Connection::CliInt4
471 req << [param.value].pack("N")
472 when Connection::CliInt8
473 req << [param.value >> 32, param.value & 0xffffffff].pack("NN")
474 when Connection::CliReal8
475 req << [param.value].pack("G")
476 when Connection::CliAsciiz
477 req << param.value << 0
478 when Connection::CliRectangle
479 req << [param.value.left, param.value.top, param.value.right, param.value.bottom].pack("NNNN")
480 else
481 raise CliError, "Unsupported parameter type #{param.type}"
482 end
483 end
484 req = [req.length + Connection::CliRequestSize, cmd, @stmt_id].pack("NNN") + req
485 ResultSet.new(self, con.send_receive_request(req))
486 end
487
488 # Close connection with server
489 def close
490 raise CliError, "Statement already closed" unless con
491 @con.send_command(Connection::CliCmdFreeStatement)
492 @con = nil
493 end
494
495 attr_reader :for_update, :con, :stmt_id, :table
496 end
497
498 # Rectangle class for spatial coordinates
499 class Rectangle
500
501 # Rectangle constructor
502 def initialize(left, top, right, bottom)
503 @left = left
504 @right = right
505 @top = top
506 @bottom = bottom
507 end
508
509 def inspect
510 "<Rectangle:0x#{"%x" % object_id} #{@left.inspect}, #{@top.inspect}, #{@right.inspect}, #{@bottom.inspect}>"
511 end
512
513 attr_accessor :left, :right, :top, :bottom
514 end
515
516 # Descriptor of database table
517 class TableDescriptor
518 # Class descriptor constructor
519 # cls - class
520 # fields - array of FieldDescriptor
521 def initialize(fields)
522 @fields=fields
523 @fields_map=fields.inject({}) { |h, f| h[f.name] = f; h }
524 end
525
526 def [](field_name)
527 @fields_map[field_name]
528 end
529
530 attr_reader :fields
531 end
532
533
534 # Descriptor of database table field
535 class FieldDescriptor
536 def initialize(name, ref_table, inverse_field, type, flags)
537 @name = name
538 @type = type
539 @flags = flags
540 @ref_table = ref_table
541 @inverse_field = inverse_field
542 end
543
544 attr_reader :name, :ref_table, :inverse_field, :type, :flags
545 end
546
547
548 # Reference to the persistent object
549 class Reference
550 def initialize(oid)
551 @oid=oid
552 end
553
554 def to_s()
555 "\##{@oid}"
556 end
557
558 attr_reader :oid
559 end
560
561 # Statement parameter
562 class Parameter
563 def initialize(name)
564 @name = name
565 @type = Connection::CliUndefined
566 end
567
568 def value=(v)
569 @value = v
570 type = value.class
571 if type == Fixnum
572 @type = Connection::CliInt4
573 elsif type == Bignum
574 @type = Connection::CliInt8
575 elsif type == Float
576 @type = Connection::CliReal8
577 elsif type == String
578 @type = Connection::CliAsciiz
579 elsif type == Reference
580 @type = Connection::CliOid
581 elsif type == TrueClass or type == FalseClass
582 @type = Connection::CliBool
583 elsif type == Rectangle
584 @type = Connection::CliRectangle
585 else
586 raise CliError, "Unsupported parameter type " + value.class.name
587 end
588 end
589
590 attr_reader :name, :type, :value
591 end
592
593
594 # CLI exception class
595 class CliError < RuntimeError
596 end
597
598 # Set of objects returned by select. This class allows navigation though the selected objects in orward or backward direction
599 class ResultSet
600 def initialize(stmt, n_objects)
601 @stmt = stmt
602 @n_objects = n_objects
603 @updated = false
604 @curr_oid = 0
605 @curr_obj = nil
606 end
607
608 # Get first selected object
609 # Returns first object in the set or nil if no objects were selected
610 def first
611 get_object(Connection::CliCmdGetFirst)
612 end
613
614 # Get last selected object
615 # Returns last object in the set or nil if no objects were selected
616 def last
617 get_object(Connection::CliCmdGetLast)
618 end
619
620
621 # Get next selected object
622 # Returns next object in the set or nil if current object is the last one in the
623 # set or no objects were selected
624 def next
625 get_object(Connection::CliCmdGetNext)
626 end
627
628 # Get previous selected object
629 # Returns previous object in the set or nil if the current object is the first
630 # one in the set or no objects were selected
631 def prev
632 get_object(Connection::CliCmdGetPrev)
633 end
634
635 # Skip specified number of objects.
636 # if ((|n|)|)) is positive, then this method has the same effect as
637 # executing getNext() mehod ((|n|)) times.
638 # if ((|n|)) is negative, then this method has the same effect of
639 # executing getPrev() mehod ((|-n|)) times.
640 # if ((|n|)) is zero, this method has no effect
641 # n - number of objects to be skipped
642 # Returns object ((|n|)) positions relative to the current position
643 def skip(n)
644 get_object(Connection::CliCmdSkip, n)
645 end
646
647 # Get reference to the current object
648 # Return return reference to the current object or nil if no objects were selected
649 def ref
650 if @curr_oid != 0
651 Reference.new(@curr_oid)
652 end
653 end
654
655
656 # Update the current object in the set. Changes made in the current object
657 # are saved in the database
658 def update
659 if @stmt == nil
660 raise CliError, "ResultSet was aleady closed"
661 end
662 if @stmt.con == nil
663 raise CliError, "Statement was closed"
664 end
665 if @curr_oid == 0
666 raise CliError, "No object was selected"
667 end
668 if !@stmt.for_update
669 raise CliError, "Updates not allowed"
670 end
671 if @updated
672 raise CliError, "Record was already updated"
673 end
674 @updated=true
675 column_values=""
676 for field in @stmt.table.fields
677 value = @curr_obj[field.name]
678 case field.type
679 when Connection::CliBool
680 if value
681 column_values << 1
682 else
683 column_values << 0
684 end
685 when Connection::CliInt1
686 column_values << value.to_i
687 when Connection::CliInt2
688 column_values << [value].pack("n")
689 when Connection::CliInt4
690 column_values << [value].pack("N")
691 when Connection::CliInt8
692 column_values << [value >> 32, value & 0xffffffff].pack("NN")
693 when Connection::CliReal4
694 column_values << [value].pack("g")
695 when Connection::CliReal8
696 column_values << [value].pack("G")
697 when Connection::CliAsciiz
698 column_values << [value.length+1].pack("N") << value << 0
699 when Connection::CliOid
700 column_values << [value.oid].pack("N")
701 when Connection::CliRectangle
702 column_values << [value.left,value.top,value.right,value.bottom].pack("NNNN")
703 when Connection::CliArrayOfInt1
704 column_values << [value.length].pack("N") << value.pack("c*")
705 when Connection::CliArrayOfBool
706 column_values << [value.length].pack("N")
707 for elem in value
708 if elem
709 column_values << 1
710 else
711 column_values << 0
712 end
713 end
714 when Connection::CliArrayOfInt2
715 column_values << [value.length].pack("N") << value.pack("n*")
716 when Connection::CliArrayOfInt4
717 column_values << [value.length].pack("N") << value.pack("N*")
718 when Connection::CliArrayOfInt8
719 column_values << [value.length].pack("N")
720 for elem in value
721 column_values << [elem >> 32, elem & 0xffffffff].pack("NN")
722 end
723 when Connection::CliArrayOfReal4
724 column_values << [value.length].pack("N") << value.pack("g*")
725 when Connection::CliArrayOfReal8
726 column_values << [value.length].pack("N") << value.pack("G*")
727 when Connection::CliArrayOfOid
728 column_values << [value.length].pack("N")
729 for elem in value
730 column_values << [elem.oid].pack("N")
731 end
732 when Connection::CliArrayOfString
733 column_values << [value.length].pack("N")
734 for elem in value
735 column_values << elem << 0
736 end
737 else
738 raise CliError, "Unsuppported type " + field.type
739 end
740 end
741 req = [Connection::CliRequestSize + column_values.length, Connection::CliCmdUpdate, @stmt.stmt_id].pack("NNN") + column_values
742 @stmt.con.send_receive_request(req)
743 end
744
745
746 # Remove all selected objects.
747 # All objects in the object set are removed from the database.
748 def remove_all
749 if @stmt == nil
750 raise CliError, "ResultSet was aleady closed"
751 end
752 if @stmt.con == nil
753 raise CliError, "Statement was closed"
754 end
755 if !@stmt.for_update
756 raise CliError, "Updates not allowed"
757 end
758 @stmt.con.send_receive_command(Connection::CliCmdRemove, @stmt.stmt_id)
759 end
760
761 # Get the number of objects in the object set.
762 # Return number of the selected objects
763 def size
764 @n_objects
765 end
766
767 # Close object set. Any followin operation with this object set will raise an exception.
768 def close()
769 @stmt = nil
770 end
771
772 # Iterator through object set
773 def each
774 if first != nil
775 yield @curr_obj
776 while self.next != nil
777 yield @curr_obj
778 end
779 end
780 end
781
782 def get_object(cmd, n=0)
783 if @stmt == nil
784 raise CliError, "ResultSet was aleady closed"
785 end
786 if @stmt.con == nil
787 raise CliError, "Statement was closed"
788 end
789 if cmd == Connection::CliCmdSkip
790 @socket.send([16, cmd, @stmt.stmt_id, n].pack("NNNN"), 0)
791 else
792 @stmt.con.send_command(cmd, @stmt.stmt_id)
793 end
794 rc = @stmt.con.receive(4).unpack("N")[0]
795 if rc == Connection::CliNotFound
796 return nil
797 elsif rc <= 0
798 @stmt.con.raise_error(rc, "Failed to get object: ")
799 end
800 resp = @stmt.con.receive(rc-4)
801 @curr_oid = resp.unpack("N")[0]
802 @updated = false
803 @curr_obj = nil
804 if @curr_oid == 0
805 return nil
806 end
807 obj = {}
808 @curr_obj = obj
809 i = 4
810 for field in @stmt.table.fields
811 type = resp[i]
812 if field.type != type
813 raise CliError, "Unexpected type of column: " + type.to_s + " instead of " + field.type.to_s
814 end
815 i += 1
816 case type
817 when Connection::CliBool
818 value = (resp[i] != 0)
819 i += 1
820 when Connection::CliInt1
821 value = resp[i]
822 i += 1
823 when Connection::CliInt2
824 value = resp[i,2].unpack("n")[0]
825 i += 2
826 when Connection::CliInt4
827 value = resp[i,4].unpack("N")[0]
828 i += 4
829 when Connection::CliInt8
830 word = resp[i,8].unpack("NN")
831 value = (word[0] << 32) | (word[1] & 0xffffffff)
832 i += 8
833 when Connection::CliReal4
834 value = resp[i,4].unpack("g")[0]
835 i += 4
836 when Connection::CliReal8
837 value = resp[i,8].unpack("G")[0]
838 i += 8
839 when Connection::CliAsciiz
840 len = resp[i,4].unpack("N")[0]
841 value = resp[i+4, len-1]
842 i += len + 4
843 when Connection::CliOid
844 value = Reference.new(resp[i,4].unpack("N")[0])
845 i += 4
846 when Connection::CliRectangle
847 coord = resp[i, 16].unpack("NNNN")
848 value = Rectangle.new(coord[0], coord[1], coord[2], coord[3])
849 when Connection::CliArrayOfInt1
850 len = resp[i,4].unpack("N")[0]
851 i += 4
852 value = resp[i, len]
853 i += len
854 when Connection::CliArrayOfBool
855 value = Array.new(resp[i,4].unpack("N")[0])
856 i += 4
857 for j in 0...value.length
858 value[j] = resp[i] != 0
859 i += 1
860 end
861 when Connection::CliArrayOfInt2
862 len = resp[i,4].unpack("N")[0]
863 i += 4
864 value = resp[i, len*2].unpack("n*")
865 i += len*2
866 when Connection::CliArrayOfInt4
867 len = resp[i,4].unpack("N")[0]
868 i += 4
869 value = resp[i, len*4].unpack("N*")
870 i += len*4
871 when Connection::CliArrayOfInt8
872 len = resp[i,4].unpack("N")[0]
873 i += 4
874 word = resp[i, len*8].unpack("N*")
875 value = Array.new(len)
876 for j in 0...value.length
877 value[j] = (word[j*2] << 32) | (word[j*2+1] & 0xffffffff)
878 end
879 i += len*8
880 when Connection::CliArrayOfReal4
881 len = resp[i,4].unpack("N")[0]
882 i += 4
883 value = resp[i, len*4].unpack("g*")
884 i += len*4
885 when Connection::CliArrayOfReal8
886 len = resp[i,4].unpack("N")[0]
887 i += 4
888 value = resp[i, len*8].unpack("G*")
889 i += len*8
890 when Connection::CliArrayOfOid
891 len = resp[i,4].unpack("N")[0]
892 value = Array.new(len)
893 i += 4
894 oid = resp[i, len*4].unpack("N*")
895 for j in 0...len
896 value[j] = Reference.new(oid[j])
897 end
898 i += len*4
899 when Connection::CliArrayOfString
900 value = Array.new(resp[i,4].unpack("N")[0])
901 i += 4
902 for j in 0...value.length
903 k = resp.index(0, i)
904 value[j] = resp[i...k]
905 i = k + 1
906 end
907 else
908 raise CliError, "Unsuppported type " + type.to_s
909 end
910 obj[field.name] = value
911 end
912 obj
913 end
914 end
915
916end