Created
August 6, 2009 00:58
-
-
Save libc/163073 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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!") | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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