Skip to content

Instantly share code, notes, and snippets.

@libc
Created August 6, 2009 00:58
Show Gist options
  • Save libc/163073 to your computer and use it in GitHub Desktop.
Save libc/163073 to your computer and use it in GitHub Desktop.
#!/usr/local/bin/ruby
require 'fastdb'
include FastDB
class Person
def initialize(name, salary, address, rating, pets, subordinates)
@name = name
@salary = salary
@address = address
@rating = rating
@pets = pets
@subordinates = subordinates
end
attr_reader :name, :salary, :address, :rating, :pets, :subordinates;
attr_writer :salary, :address, :rating, :pets, :subordinates;
end
con = Connection.new("localhost", 6100)
john = con.insert("Person", :name => "John Smith", :salary => 80000, :address => "1 Guildhall St., Cambridge CB2 3NH, UK", :pets => ["dog", "cat"], :subordinates => [], :rating => 3.84)
joe = con.insert("Person", :name => "Joe Cooker", :salary => 50000, :address => "Outlook drive, 15/3", :pets => ["snake"], :subordinates => [john], :rating => 1.76)
hugo = con.insert("Person", :name => "Hugo Grant", :salary => 65000, :address => "London, Baker street,12", :pets => ["canary", "goldfish"], :subordinates => [], :rating => 2.14)
con.commit
stmt = con.create_statement("select * from Person where salary > %salary order by salary")
stmt["salary"] = 65000
cursor = stmt.fetch()
if cursor.size != 1
raise "stmt->fetch 1 returns " + cursor.size.to_s + " instead of 1"
end
puts("NAME\t\tSALARY\tRATING")
for p in cursor
puts(p["name"] + "\t" + p["salary"].to_s + "\t" + p["rating"].to_s)
end
stmt["salary"] = 50000
cursor = stmt.fetch
if cursor.size != 2
raise "stmt->fetch 2 returns " + cursor.size.to_s + " instead of 2"
end
stmt.close()
stmt = con.create_statement("select * from Person where current = %ref")
stmt["ref"] = joe
cursor = stmt.fetch
if cursor.size != 1
raise "stmt->fetch 3 returns " + cursor.size.to_s + " instead of 1"
end
p = cursor.first
str=""
str << "Object oid=" << cursor.ref.to_s << ", name=" << p["name"] << ", address=" << p["address"]
str << ", rating=" << p["rating"].to_s << ", salary=" << p["salary"].to_s << ", pets=["
for pet in p["pets"]
str << pet << " "
end
str << "], subordinates=["
stmt2 = con.create_statement("select * from Person where current = %ref")
for s in p["subordinates"]
stmt2["ref"] = s
str << stmt2.fetch.first["name"] << " "
end
str << "]";
puts(str)
stmt.close
stmt2.close
stmt = con.create_statement("select * from Person where rating > %rating and salary between %min and %max");
stmt["rating"] = 2.0
stmt["min"] = 50000
stmt["max"] = 100000
cursor = stmt.fetch(true)
if cursor.size != 2
raise "stmt->fetch 4 returns " + cursor.size.to_s + " instead of 2"
end
p = cursor.last
puts("NAME\t\tSALARY\tRATING\tADDRESS")
while p != nil
puts(p["name"]+"\t"+p["salary"].to_s+"\t"+p["rating"].to_s+"\t"+p["address"])
p["salary"] = (p["salary"] * 1.1).to_i
cursor.update
p = cursor.prev
end
stmt.close()
con.commit()
stmt = con.create_statement("select * from Person where address like %pattern")
stmt["pattern"] = "%"
cursor = stmt.fetch()
if cursor.size != 3
raise "stmt->fetch 5 returns " + cursor.size.to_s + " instead of 3"
end
puts("NAME\t\tSALARY\tRATING\tADDRESS")
for p in cursor
puts(p["name"]+"\t"+p["salary"].to_s+"\t"+p["rating"].to_s+"\t"+p["address"])
end
stmt.close()
stmt = con.create_statement("select * from Person")
cursor = stmt.fetch(true)
if cursor.size != 3
raise "stmt->fetch 6 returns " + cursor.size.to_s + " instead of 3"
end
cursor.remove_all
stmt.close
con.close
puts("*** CLI test sucessfully passed!")
#!/usr/bin/env ruby
require 'socket'
# FastDB API
# This module contains Ruby API to FastDB
module FastDB
# Connection to the FastDB server
class Connection
# Opens connection to the server
# +host_address+ -- string with server host name
# +host_port+ -- integer number with server port
def initialize(host_address, host_port)
@socket = TCPSocket.new(host_address, host_port)
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
@n_statements = 0
load_schema
end
# Close connection with server
def close
send_command(CliCmdCloseSession)
@socket.close
@socket = nil
end
# Create select statement.
# sql - SubSQL select statement with parameters. Parameters should be started with % character.
# Each used parameter should be set before execution of the statement.
def create_statement(sql)
@n_statements += 1
Statement.new(self, sql, @n_statements)
end
# Commit current transaction
def commit
send_receive_command(CliCmdCommit)
end
# Exclusively lock database (FastDB set locks implicitely, explicit exclusive lock may be needed to avoid deadlock caused by lock upgrade)
def lock
send_command(CliCmdLock)
end
# Release all locks set by the current transaction
def unlock
send_receive_command(CliCmdPrecommit)
end
# Rollback curent transaction. All changes made by current transaction are lost.
def rollback
send_receive_command(CliCmdAbort)
end
# Insert object in the database. There is should be table in the database with
# name equal to the full class name of the inserted object (comparison is
# case sensitive). FastDB will store to the database all non-static and
# non-transient fields from the class.
# obj - object to be inserted in the database
# table - name of the table in which object should be inserted (by default - table corresponding to the object class)
# Returns reference to the inserted object
def insert(table, obj)
column_defs=""
column_values=""
n_columns=0
table_desc = @tables[table]
raise CliError, "Table #{table} is not found in the database" unless table_desc
obj.each do |key, value|
field = key.to_s
field_desc = table_desc[field]
raise CliError, "Column #{field} is not found in the table #{table}" unless field_desc
n_columns += 1
case value ## Why do we look at the type of value? Should we look at schema?
when Fixnum
column_defs << CliInt4 << field << 0
column_values << [value].pack("N")
when Bignum
raise CliError, "#{value} is too big. Only numbers up to 8 bytes are supported" if value.size > 8
column_defs << CliInt8 << field << 0
column_values << [value >> 32, value & 0xffffffff].pack("NN")
when Float
column_defs << CliReal8 << field << 0
column_values << [value].pack("G")
when String
if field_desc.type == CliArrayOfInt1
column_defs << CliArrayOfInt1 << field << 0
column_values << [value.length].pack("N") << value
else
column_defs << CliAsciiz << field << 0
column_values << [value.length+1].pack("N") << value << 0
end
when Reference
column_defs << CliOid << field << 0
column_values << [value.oid].pack("N")
when TrueClass
column_defs << CliBool << field << 0
column_values << 1
when FalseClass
column_defs << CliBool << field << 0
column_values << 0
when Rectangle
column_defs << CliRectangle << field << 0
column_values << [value.left, value.top, value.right, value.bottom].pack("NNNN")
when Array
column_defs << field_desc.type << field << 0
column_values << [value.length].pack("N")
case field_desc.type
when CliArrayOfInt1
column_values << value.pack("c*")
when CliArrayOfBool
value.each { |elem| column_values << elem ? 1 : 0 }
when CliArrayOfInt2
column_values << value.pack("n*")
when CliArrayOfInt4
column_values << value.pack("N*")
when CliArrayOfInt8
value.each { |elem| column_values << [elem >> 32, elem & 0xffffffff].pack("NN") }
when CliArrayOfReal4
column_values << value.pack("g*")
when CliArrayOfReal8
column_values << value.pack("G*")
when CliArrayOfOid
value.each { |elem| column_values << [elem.oid].pack("N") }
when CliArrayOfString
value.each { |elem| column_values << elem << 0 }
else
raise CliError, "Unsupported element type " + field_desc.type
end
else
raise CliError, "Unsupported type #{type.name}"
end
end
req = [CliRequestSize + 14 +table.length + column_defs.length + column_values.length, CliCmdPrepareAndInsert, 0].pack("NNN")
req << "insert into " << table << 0 << n_columns << column_defs << column_values
@socket.send(req, 0)
rc = @socket.recv(CliRequestSize).unpack("NNN")
raise_error(rc[0]) unless rc[0] == CliOk
Reference.new(rc[2]) if rc[2] != 0
end
def load_schema
send_command(CliCmdShowTables)
ret = @socket.recv(8).unpack("NN")
len = ret[0]
n_tables = ret[1]
table_names = @socket.recv(len)
@tables = {}
tables = table_names.split("\0")
tables.each do |table|
@socket.send([CliRequestSize + table.length + 1, CliCmdDescribeTable, 0].pack("NNN") << table << 0, 0)
ret = @socket.recv(8).unpack("NN")
len = ret[0]
n_fields = ret[1]
field_info = @socket.recv(len)
fields = Array.new(n_fields)
j = 0
for k in 0...n_fields
type = field_info[j]
j += 1
flags = field_info[j]
j += 1
z = field_info.index(0, j)
name = field_info[j...z]
j = z + 1
z = field_info.index(0, j)
if z != j
ref_table = field_info[j...z]
else
ref_table = nil
end
j = z + 1
z = field_info.index(0, j)
if z != j
inverse_field = field_info[j...z]
else
inverse_field = nil
end
j = z + 1
fields[k] = FieldDescriptor.new(name, ref_table, inverse_field, type, flags)
end
@tables[table] = TableDescriptor.new(fields)
end
end
def send_command(cmd, id=0)
@socket.send([CliRequestSize, cmd, id].pack("NNN"), 0)
end
def receive(len)
@socket.recv(len)
end
def send_receive_command(cmd, id=0)
send_command(cmd, id)
rc = @socket.recv(4).unpack("N")[0]
raise CliError, "Request failed with status #{rc}" if rc < 0
rc
end
def send_receive_request(req)
@socket.send(req, 0)
rc = @socket.recv(4).unpack("N")[0]
raise CliError, "Request failed with status #{rc}" if rc < 0
rc
end
# Close connection with server
def close
send_command(CliCmdCloseSession)
@socket.close()
@socket = nil
end
def raise_error(error_code, prefix = "")
raise CliError, "#{prefix}#{ERROR_DESCRIPTIONS[error_code]}" if ERROR_DESCRIPTIONS[error_code]
raise CliError, "#{prefix}Unknown error code #{error_code}"
end
CliRequestSize = 12
# Field flag
CliHashed = 1 # field should be indexed usnig hash table
CliIndexed = 2 # field should be indexed using B-Tree
CliCascadeDelete = 8 # perfrom cascade delete for for reference or array of reference fields
CliAutoincremented = 16 # field is assigned automaticall incremented value
# Operation result codes
CliOk = 0
CliBadAddress = 4294967295
CliConnectionRefused = 4294967294
CliDatabaseNotFound = 4294967293
CliBadStatement = 4294967292
CliParameterNotFound = 4294967291
CliUnboundParameter = 4294967290
CliColumnNotFound = 4294967289
CliIncompatibleType = 4294967288
CliNetworkError = 4294967287
CliRuntimeError = 4294967286
CliClosedStatement = 4294967285
CliUnsupportedType = 4294967284
CliNotFound = 4294967283
CliNotUpdateMode = 4294967282
CliTableNotFound = 4294967281
CliNotAllColumnsSpecified = 4294967280
CliNotFetched = 4294967279
CliAlreadyUpdated = 4294967278
CliTableAlreadyExists = 4294967277
CliNotImplemented = 4294967276
CliLoginFailed = 4294967275
CliEmptyParameter = 4294967274
CliClosedConnection = 4294967273
ERROR_DESCRIPTIONS = {
CliBadAddress => "Bad address",
CliConnectionRefused => "Connection refused",
CliDatabaseNotFound => "Database not found",
CliBadStatement => "Bad statement",
CliParameterNotFound => "Parameter not found",
CliUnboundParameter => "Unbound parameter",
CliColumnNotFound => "Column not found",
CliIncompatibleType => "Incomptaible type",
CliNetworkError => "Network error",
CliRuntimeError => "Runtime error",
CliClosedStatement => "Closed statement",
CliUnsupportedType => "Unsupported type",
CliNotFound => "Not found",
CliNotUpdateMode => "Not update mode",
CliTableNotFound => "Table not found",
CliNotAllColumnsSpecified => "Not all columns specified",
CliNotFetched => "Not fetched",
CliAlreadyUpdated => "Already updated",
CliTableAlreadyExists => "Table already exists",
CliNotImplemented => "Not implemented",
CliLoginFailed => "Login failed",
CliEmptyParameter => "Empty parameter",
CliClosedConnection => "Closed connection"}
# Command codes
CliCmdCloseSession = 0
CliCmdPrepareAndExecute = 1
CliCmdExecute = 2
CliCmdGetFirst = 3
CliCmdGetLast = 4
CliCmdGetNext = 5
CliCmdGetPrev = 6
CliCmdFreeStatement = 7
CliCmdAbort = 8
CliCmdCommit = 9
CliCmdUpdate = 10
CliCmdRemove = 11
CliCmdRemoveCurrent = 12
CliCmdInsert = 13
CliCmdPrepareAndInsert = 14
CliCmdDescribeTable = 15
CliCmdShowTables = 16
CliCmdPrecommit = 17
CliCmdSkip = 18
CliCmdCreateTable = 19
CliCmdDropTable = 20
CliCmdAlterIndex = 21
CliCmdFreeze = 22
CliCmdUnfreeze = 23
CliCmdSeek = 24
CliCmdAlterTable = 25
CliCmdLock = 26
# Field type codes
CliOid = 0
CliBool = 1
CliInt1 = 2
CliInt2 = 3
CliInt4 = 4
CliInt8 = 5
CliReal4 = 6
CliReal8 = 7
CliDecimal = 8
CliAsciiz = 9
CliPasciiz = 10
CliCstring = 11
CliArrayOfOid = 12
CliArrayOfBool = 13
CliArrayOfInt1 = 14
CliArrayOfInt2 = 15
CliArrayOfInt4 = 16
CliArrayOfInt8 = 17
CliArrayOfReal4 = 18
CliArrayOfReal8 = 19
CliArrayOfDecimal = 20
CliArrayOfString = 21
CliAny = 22
CliDatetime = 23
CliAutoincrement = 24
CliRectangle = 25
CliUndefined = 26
attr_reader :tables
end
# Statement class is used to prepare and execute select statement
class Statement
private
SPACE = 32
PERCENT = 37
QUOTE = 39
LETTER_A = 65
LETTER_Z = 90
LETTER_a = 97
LETTER_z = 122
DIGIT_0 = 48
DIGIT_9 = 57
UNDERSCORE = 95
public
# Statement constructor called by Connection class
def initialize(con, sql, stmt_id)
@con = con
@stmt_id = stmt_id
r = sql.match(/\s+from\s+([^\s]+)/i)
raise CliError, "Bad statement: table name is expected after FROM" unless r
table_name = r[1]
@table = con.tables[table_name]
in_quotes = false
param_name = nil
@param_hash = {}
@param_list = []
req_str=""
sql.each_byte do |ch|
if ch == QUOTE
in_quotes = !in_quotes
req_str << ch
elsif ch == PERCENT and !in_quotes
param_name=""
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)
param_name << ch
else
if param_name != nil
p = Parameter.new(param_name)
@param_list << p
@param_hash[param_name] = p
param_name = nil
req_str << 0
end
req_str << ch
end
end
if param_name != nil
p = Parameter.new(param_name)
@param_list << p
@param_hash[param_name] = p
req_str << 0
end
if req_str.length == 0 or req_str[-1] != 0
req_str << 0
end
@stmt = req_str
@prepared = false
end
# Get parameter value
def [](param_name)
@param_hash[param_name].value
end
# Assign value to the statement parameter
def []=(param_name, value)
@param_hash[param_name].value = value
end
# Prepare (if needed) and execute select statement
# Only object set returned by the select for updated statement allows
# update and deletion of the objects.
# forUpdate - if cursor is opened in for update mode
# Returns object set with the selected objects
def fetch(for_update = false)
cmd=Connection::CliCmdExecute
req=""
if !@prepared
cmd=Connection::CliCmdPrepareAndExecute
@prepared=true
req << @param_list.length << @table.fields.length << [@stmt.length + @param_list.length].pack("n")
param_no=0
@stmt.each_byte do |ch|
req << ch
if ch == 0 and param_no < @param_list.length
param = @param_list[param_no]
if param.type == Connection::CliUndefined
raise CliError, "Unbound parameter " + param.name
end
param_no += 1
req << param.type
end
end
for field in @table.fields
req << field.type << field.name << 0
end
end
@for_update = for_update
if for_update
req << 1
else
req << 0
end
for param in @param_list
case param.type
when Connection::CliOid
req << [param.value.oid].pack("N")
when Connection::CliBool
param.value ? 1 : 0
when Connection::CliInt4
req << [param.value].pack("N")
when Connection::CliInt8
req << [param.value >> 32, param.value & 0xffffffff].pack("NN")
when Connection::CliReal8
req << [param.value].pack("G")
when Connection::CliAsciiz
req << param.value << 0
when Connection::CliRectangle
req << [param.value.left, param.value.top, param.value.right, param.value.bottom].pack("NNNN")
else
raise CliError, "Unsupported parameter type #{param.type}"
end
end
req = [req.length + Connection::CliRequestSize, cmd, @stmt_id].pack("NNN") + req
ResultSet.new(self, con.send_receive_request(req))
end
# Close connection with server
def close
raise CliError, "Statement already closed" unless con
@con.send_command(Connection::CliCmdFreeStatement)
@con = nil
end
attr_reader :for_update, :con, :stmt_id, :table
end
# Rectangle class for spatial coordinates
class Rectangle
# Rectangle constructor
def initialize(left, top, right, bottom)
@left = left
@right = right
@top = top
@bottom = bottom
end
def inspect
"<Rectangle:0x#{"%x" % object_id} #{@left.inspect}, #{@top.inspect}, #{@right.inspect}, #{@bottom.inspect}>"
end
attr_accessor :left, :right, :top, :bottom
end
# Descriptor of database table
class TableDescriptor
# Class descriptor constructor
# cls - class
# fields - array of FieldDescriptor
def initialize(fields)
@fields=fields
@fields_map=fields.inject({}) { |h, f| h[f.name] = f; h }
end
def [](field_name)
@fields_map[field_name]
end
attr_reader :fields
end
# Descriptor of database table field
class FieldDescriptor
def initialize(name, ref_table, inverse_field, type, flags)
@name = name
@type = type
@flags = flags
@ref_table = ref_table
@inverse_field = inverse_field
end
attr_reader :name, :ref_table, :inverse_field, :type, :flags
end
# Reference to the persistent object
class Reference
def initialize(oid)
@oid=oid
end
def to_s()
"\##{@oid}"
end
attr_reader :oid
end
# Statement parameter
class Parameter
def initialize(name)
@name = name
@type = Connection::CliUndefined
end
def value=(v)
@value = v
type = value.class
if type == Fixnum
@type = Connection::CliInt4
elsif type == Bignum
@type = Connection::CliInt8
elsif type == Float
@type = Connection::CliReal8
elsif type == String
@type = Connection::CliAsciiz
elsif type == Reference
@type = Connection::CliOid
elsif type == TrueClass or type == FalseClass
@type = Connection::CliBool
elsif type == Rectangle
@type = Connection::CliRectangle
else
raise CliError, "Unsupported parameter type " + value.class.name
end
end
attr_reader :name, :type, :value
end
# CLI exception class
class CliError < RuntimeError
end
# Set of objects returned by select. This class allows navigation though the selected objects in orward or backward direction
class ResultSet
def initialize(stmt, n_objects)
@stmt = stmt
@n_objects = n_objects
@updated = false
@curr_oid = 0
@curr_obj = nil
end
# Get first selected object
# Returns first object in the set or nil if no objects were selected
def first
get_object(Connection::CliCmdGetFirst)
end
# Get last selected object
# Returns last object in the set or nil if no objects were selected
def last
get_object(Connection::CliCmdGetLast)
end
# Get next selected object
# Returns next object in the set or nil if current object is the last one in the
# set or no objects were selected
def next
get_object(Connection::CliCmdGetNext)
end
# Get previous selected object
# Returns previous object in the set or nil if the current object is the first
# one in the set or no objects were selected
def prev
get_object(Connection::CliCmdGetPrev)
end
# Skip specified number of objects.
# if ((|n|)|)) is positive, then this method has the same effect as
# executing getNext() mehod ((|n|)) times.
# if ((|n|)) is negative, then this method has the same effect of
# executing getPrev() mehod ((|-n|)) times.
# if ((|n|)) is zero, this method has no effect
# n - number of objects to be skipped
# Returns object ((|n|)) positions relative to the current position
def skip(n)
get_object(Connection::CliCmdSkip, n)
end
# Get reference to the current object
# Return return reference to the current object or nil if no objects were selected
def ref
if @curr_oid != 0
Reference.new(@curr_oid)
end
end
# Update the current object in the set. Changes made in the current object
# are saved in the database
def update
if @stmt == nil
raise CliError, "ResultSet was aleady closed"
end
if @stmt.con == nil
raise CliError, "Statement was closed"
end
if @curr_oid == 0
raise CliError, "No object was selected"
end
if !@stmt.for_update
raise CliError, "Updates not allowed"
end
if @updated
raise CliError, "Record was already updated"
end
@updated=true
column_values=""
for field in @stmt.table.fields
value = @curr_obj[field.name]
case field.type
when Connection::CliBool
if value
column_values << 1
else
column_values << 0
end
when Connection::CliInt1
column_values << value.to_i
when Connection::CliInt2
column_values << [value].pack("n")
when Connection::CliInt4
column_values << [value].pack("N")
when Connection::CliInt8
column_values << [value >> 32, value & 0xffffffff].pack("NN")
when Connection::CliReal4
column_values << [value].pack("g")
when Connection::CliReal8
column_values << [value].pack("G")
when Connection::CliAsciiz
column_values << [value.length+1].pack("N") << value << 0
when Connection::CliOid
column_values << [value.oid].pack("N")
when Connection::CliRectangle
column_values << [value.left,value.top,value.right,value.bottom].pack("NNNN")
when Connection::CliArrayOfInt1
column_values << [value.length].pack("N") << value.pack("c*")
when Connection::CliArrayOfBool
column_values << [value.length].pack("N")
for elem in value
if elem
column_values << 1
else
column_values << 0
end
end
when Connection::CliArrayOfInt2
column_values << [value.length].pack("N") << value.pack("n*")
when Connection::CliArrayOfInt4
column_values << [value.length].pack("N") << value.pack("N*")
when Connection::CliArrayOfInt8
column_values << [value.length].pack("N")
for elem in value
column_values << [elem >> 32, elem & 0xffffffff].pack("NN")
end
when Connection::CliArrayOfReal4
column_values << [value.length].pack("N") << value.pack("g*")
when Connection::CliArrayOfReal8
column_values << [value.length].pack("N") << value.pack("G*")
when Connection::CliArrayOfOid
column_values << [value.length].pack("N")
for elem in value
column_values << [elem.oid].pack("N")
end
when Connection::CliArrayOfString
column_values << [value.length].pack("N")
for elem in value
column_values << elem << 0
end
else
raise CliError, "Unsuppported type " + field.type
end
end
req = [Connection::CliRequestSize + column_values.length, Connection::CliCmdUpdate, @stmt.stmt_id].pack("NNN") + column_values
@stmt.con.send_receive_request(req)
end
# Remove all selected objects.
# All objects in the object set are removed from the database.
def remove_all
if @stmt == nil
raise CliError, "ResultSet was aleady closed"
end
if @stmt.con == nil
raise CliError, "Statement was closed"
end
if !@stmt.for_update
raise CliError, "Updates not allowed"
end
@stmt.con.send_receive_command(Connection::CliCmdRemove, @stmt.stmt_id)
end
# Get the number of objects in the object set.
# Return number of the selected objects
def size
@n_objects
end
# Close object set. Any followin operation with this object set will raise an exception.
def close()
@stmt = nil
end
# Iterator through object set
def each
if first != nil
yield @curr_obj
while self.next != nil
yield @curr_obj
end
end
end
def get_object(cmd, n=0)
if @stmt == nil
raise CliError, "ResultSet was aleady closed"
end
if @stmt.con == nil
raise CliError, "Statement was closed"
end
if cmd == Connection::CliCmdSkip
@socket.send([16, cmd, @stmt.stmt_id, n].pack("NNNN"), 0)
else
@stmt.con.send_command(cmd, @stmt.stmt_id)
end
rc = @stmt.con.receive(4).unpack("N")[0]
if rc == Connection::CliNotFound
return nil
elsif rc <= 0
@stmt.con.raise_error(rc, "Failed to get object: ")
end
resp = @stmt.con.receive(rc-4)
@curr_oid = resp.unpack("N")[0]
@updated = false
@curr_obj = nil
if @curr_oid == 0
return nil
end
obj = {}
@curr_obj = obj
i = 4
for field in @stmt.table.fields
type = resp[i]
if field.type != type
raise CliError, "Unexpected type of column: " + type.to_s + " instead of " + field.type.to_s
end
i += 1
case type
when Connection::CliBool
value = (resp[i] != 0)
i += 1
when Connection::CliInt1
value = resp[i]
i += 1
when Connection::CliInt2
value = resp[i,2].unpack("n")[0]
i += 2
when Connection::CliInt4
value = resp[i,4].unpack("N")[0]
i += 4
when Connection::CliInt8
word = resp[i,8].unpack("NN")
value = (word[0] << 32) | (word[1] & 0xffffffff)
i += 8
when Connection::CliReal4
value = resp[i,4].unpack("g")[0]
i += 4
when Connection::CliReal8
value = resp[i,8].unpack("G")[0]
i += 8
when Connection::CliAsciiz
len = resp[i,4].unpack("N")[0]
value = resp[i+4, len-1]
i += len + 4
when Connection::CliOid
value = Reference.new(resp[i,4].unpack("N")[0])
i += 4
when Connection::CliRectangle
coord = resp[i, 16].unpack("NNNN")
value = Rectangle.new(coord[0], coord[1], coord[2], coord[3])
when Connection::CliArrayOfInt1
len = resp[i,4].unpack("N")[0]
i += 4
value = resp[i, len]
i += len
when Connection::CliArrayOfBool
value = Array.new(resp[i,4].unpack("N")[0])
i += 4
for j in 0...value.length
value[j] = resp[i] != 0
i += 1
end
when Connection::CliArrayOfInt2
len = resp[i,4].unpack("N")[0]
i += 4
value = resp[i, len*2].unpack("n*")
i += len*2
when Connection::CliArrayOfInt4
len = resp[i,4].unpack("N")[0]
i += 4
value = resp[i, len*4].unpack("N*")
i += len*4
when Connection::CliArrayOfInt8
len = resp[i,4].unpack("N")[0]
i += 4
word = resp[i, len*8].unpack("N*")
value = Array.new(len)
for j in 0...value.length
value[j] = (word[j*2] << 32) | (word[j*2+1] & 0xffffffff)
end
i += len*8
when Connection::CliArrayOfReal4
len = resp[i,4].unpack("N")[0]
i += 4
value = resp[i, len*4].unpack("g*")
i += len*4
when Connection::CliArrayOfReal8
len = resp[i,4].unpack("N")[0]
i += 4
value = resp[i, len*8].unpack("G*")
i += len*8
when Connection::CliArrayOfOid
len = resp[i,4].unpack("N")[0]
value = Array.new(len)
i += 4
oid = resp[i, len*4].unpack("N*")
for j in 0...len
value[j] = Reference.new(oid[j])
end
i += len*4
when Connection::CliArrayOfString
value = Array.new(resp[i,4].unpack("N")[0])
i += 4
for j in 0...value.length
k = resp.index(0, i)
value[j] = resp[i...k]
i = k + 1
end
else
raise CliError, "Unsuppported type " + type.to_s
end
obj[field.name] = value
end
obj
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment