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