|
From: Michael N. <mne...@us...> - 2002-12-20 10:07:35
|
Update of /cvsroot/ruby-dbi/subprojects/ruby-db2/samples
In directory sc8-pr-cvs1:/tmp/cvs-serv5135
Modified Files:
db2.rb
Log Message:
added Songsu Yun's patch
Index: db2.rb
===================================================================
RCS file: /cvsroot/ruby-dbi/subprojects/ruby-db2/samples/db2.rb,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- db2.rb 5 Sep 2002 09:57:14 -0000 1.1
+++ db2.rb 20 Dec 2002 10:07:31 -0000 1.2
@@ -3,87 +3,54 @@
module DB2
- FINALIZE_INFO = {}
- FINALIZE_PROC = proc do |id|
- info = FINALIZE_INFO[id]
- if $DEBUG then
- $stderr.puts "freeing handle <#{info[1]}> (#{info[0]})"
- end
- DB2CLI::SQLFreeHandle(info[0], info[1])
- end
-
-
- class CLIError < Exception
- attr_reader :sql_state, :native_error, :message_text
-
- def initialize(sql_state, native_error, message_text)
- @sql_state = sql_state
- @native_error = native_error
- @message_text = message_text
- end
-
- def message
- @message_text
- end
- end
-
- class Handle
+ module DB2Util
include DB2CLI
- def initialize(handle_type, parent_handle)
- @handle_type = handle_type
- @handle = parent_handle
-
- rc, handle = SQLAllocHandle(@handle_type, @handle)
- check_rc(rc)
- @handle = handle
- add_finalizer
- end
-
def free
- rc = SQLFreeHandle(@handle_type, @handle)
- check_rc(rc)
+ SQLFreeHandle(@handle_type, @handle)
end
def handle
@handle
end
- private
-
- def add_finalizer
- DB2::FINALIZE_INFO[self.id] = [@handle_type.dup, @handle.dup]
- ObjectSpace.define_finalizer(self, DB2::FINALIZE_PROC)
- end
-
def check_rc(rc)
- if rc != SQL_SUCCESS and rc != SQL_SUCCESS_WITH_INFO then
- err = SQLGetDiagRec(@handle_type, @handle, 1, 1024)
- raise CLIError.new(err[1], err[2], err[3])
+ if rc != SQL_SUCCESS and rc != SQL_SUCCESS_WITH_INFO
+ case rc
+ when SQL_NEED_DATA
+ rcText = 'SQL_NEED_DATA'
+ when SQL_NO_DATA_FOUND
+ rcText = 'SQL_NO_DATA'
+ when SQL_STILL_EXECUTING
+ rcText = 'SQL_STILL_EXECUTING'
+ when SQL_ERROR
+ rcText = 'SQL_ERROR'
+ when SQL_INVALID_HANDLE
+ rcText = 'SQL_INVALID_HANDLE'
+ end
+ puts "DB2 return code is #{rcText}"
+ rec = 1
+ loop do
+ a = SQLGetDiagRec(@handle_type, @handle, rec, 500)
+ break if a[0] != SQL_SUCCESS
+ puts a[3] if a[3] != nil and a[3] != '' #display message
+ rec += 1
+ end
+ raise "ERROR From DB2"
end
end
- end # class Handle
-
+ end # module DB2Util
- class Environment < Handle
+ class Environment
+ include DB2Util
def initialize
- super(SQL_HANDLE_ENV, SQL_NULL_HANDLE)
- end
-
- def dataSources
- data_sources_buffer
- end
-
-
- def createConnection(*a)
- Connection.new(self, *a)
+ @handle_type = SQL_HANDLE_ENV
+ rc, @handle = SQLAllocHandle(@handle_type, SQL_NULL_HANDLE)
+ check_rc(rc)
end
-
- private
-
- def data_sources_buffer(buffer_length = 1024)
+ def data_sources(buffer_length = 1024)
retval = []
max_buffer_length = buffer_length
@@ -97,44 +64,58 @@
retval << [a[1], a[3]]
max_buffer_length = [max_buffer_length, a[4]].max
- end
-
+ end
+
if max_buffer_length > buffer_length then
- data_sources_buffer(max_buffer_length)
+ get_data_sources(max_buffer_length)
else
retval
end
end
-
end # class Environment
+ class Connection
+ include DB2Util
- class Connection < Handle
-
- def initialize(environment, *conn_params)
+ def initialize(environment)
@env = environment
+ @handle_type = SQL_HANDLE_DBC
+ rc, @handle = SQLAllocHandle(@handle_type, @env.handle)
+ check_rc(rc)
+ end
- super(SQL_HANDLE_DBC, @env.handle)
+ def connect(server_name, user_name="", auth="")
+ rc = SQLConnect(@handle, server_name, user_name, auth)
+ check_rc(rc)
+ end
- @connected = false
+ def set_auto_commit(switch)
+ switch.upcase!
+ if switch != 'ON' and switch != 'OFF'
+ raise 'set_auto_commit: Specify either "ON" or "OFF"'
+ end
- unless conn_params.empty?
- connect(*conn_params)
+ if switch == 'ON'
+ rc = SQLSetConnectAttr(@handle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON)
+ else
+ rc = SQLSetConnectAttr(@handle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF)
end
+ check_rc(rc)
end
- def connect(server_name, user_name="", auth="")
- rc = SQLConnect(@handle, server_name, user_name, auth)
+ def set_connect_attr(attr, value)
+ if value.type == String
+ value += "\0"
+ end
+ rc = SQLSetConnectAttr(@handle, attr, value)
check_rc(rc)
- @connected = true
end
def disconnect
rc = SQLDisconnect(@handle)
check_rc(rc)
- @connected = false
end
def rollback
@@ -147,109 +128,270 @@
check_rc(rc)
end
- def createStatement
- Statement.new(self)
- end
end # class Connection
- class Statement < Handle
+ class Statement
+ include DB2Util
def initialize(connection)
@conn = connection
- super(SQL_HANDLE_STMT, @conn.handle)
+ @handle_type = SQL_HANDLE_STMT
+ @parms = [] #yun
+ @sql = '' #yun
+ @numParms = 0 #yun
+ @prepared = false #yun
+ @parmArray = [] #yun. attributes of the parameter markers
+ rc, @handle = SQLAllocHandle(@handle_type, @conn.handle)
+ check_rc(rc)
end
def prepare(sql)
+ @sql = sql
rc = SQLPrepare(@handle, sql)
check_rc(rc)
+ rc, @numParms = SQLNumParams(@handle) #number of question marks
+ check_rc(rc)
+ #--------------------------------------------------------------------------
+ # parameter attributes are stored in instance variable @parmArray so that
+ # they are available when execute method is called.
+ #--------------------------------------------------------------------------
+ if @numParms > 0 #get parameter marker attributes
+ 1.upto(@numParms) do #parameter number starts from 1
+ | i |
+ rc, type, size, decimalDigits = SQLDescribeParam(@handle, i)
+ check_rc(rc)
+ @parmArray << Parameter.new(type, size, decimalDigits)
+ end
+ end
+ @prepared = true;
self
end
- def freeStmt(options)
- rc = SQLFreeStmt(@handle, options)
- ckeck_rc(rc)
- end
+ def execute(*parms)
+ if @prepared == false
+ raise "The statement was not prepared"
+ end
+
+ if parms.size == 1 and parms[0].type == Array
+ parms = parms[0]
+ end
+
+ if (@numParms != parms.size)
+ raise "Number of parameters supplied does not match with the SQL statement"
+ end
+
+ if @numParms > 0 #need to bind parameters
+ #--------------------------------------------------------------------
+ #calling bindParms may not be safe. Look comment below.
+ #--------------------------------------------------------------------
+ #bindParms(parms) #may not be safe
+
+ valueArray = []
+ 1.upto(@numParms) do #parameter number starts from 1
+ | i |
+ type = @parmArray[i - 1].type
+ size = @parmArray[i - 1].size
+ decimalDigits = @parmArray[i - 1].decimalDigits
+
+ if parms[i - 1].type == String
+ valueArray << parms[i - 1]
+ else
+ valueArray << parms[i - 1].to_s
+ end
+
+ rc = SQLBindParameter(@handle, i, type, size, decimalDigits, valueArray[i - 1])
+ check_rc(rc)
+ end
+ end
- def execute
rc = SQLExecute(@handle)
check_rc(rc)
+
+ if @numParms != 0
+ SQLFreeStmt(@handle, SQL_RESET_PARAMS) #reset parameters
+ check_rc(rc)
+ end
+
self
end
- def execDirect(sql)
+ #-------------------------------------------------------------------------------
+ # The last argument(value) to SQLBindParameter is a deferred argument, that is,
+ # it should be available when SQLExecute is called. Even though "value" is
+ # local to bindParms method, it seems that it is available when SQLExecute
+ # is called. I am not sure whether it would still work if garbage collection
+ # is done between bindParms call and SQLExecute call inside the execute method
+ # above.
+ #-------------------------------------------------------------------------------
+ def bindParms(parms) #This is the real thing. It uses SQLBindParms
+ 1.upto(@numParms) do #parameter number starts from 1
+ | i |
+ rc, dataType, parmSize, decimalDigits = SQLDescribeParam(@handle, i)
+ check_rc(rc)
+ if parms[i - 1].type == String
+ value = parms[i - 1]
+ else
+ value = parms[i - 1].to_s
+ end
+ rc = SQLBindParameter(@handle, i, dataType, parmSize, decimalDigits, value)
+ check_rc(rc)
+ end
+ end
+
+ #------------------------------------------------------------------------------
+ # bind method does not use DB2's SQLBindParams, but replaces "?" in the
+ # SQL statement with the value before passing the SQL statement to DB2.
+ # It is not efficient and can handle only strings since it puts everything in
+ # quotes.
+ #------------------------------------------------------------------------------
+ def bind(sql, args) #does not use SQLBindParams
+ arg_index = 0
+ result = ""
+ tokens(sql).each { |part|
+ case part
+ when '?'
+ result << "'" + (args[arg_index]) + "'" #put it into quotes
+ arg_index += 1
+ when '??'
+ result << "?"
+ else
+ result << part
+ end
+ }
+ if arg_index < args.size
+ raise "Too many SQL parameters"
+ elsif arg_index > args.size
+ raise "Not enough SQL parameters"
+ end
+ result
+ end
+
+ ## Break the sql string into parts.
+ #
+ # This is NOT a full lexer for SQL. It just breaks up the SQL
+ # string enough so that question marks, double question marks and
+ # quoted strings are separated. This is used when binding
+ # arguments to "?" in the SQL string. Note: comments are not
+ # handled.
+ #
+ def tokens(sql)
+ toks = sql.scan(/('([^'\\]|''|\\.)*'|"([^"\\]|""|\\.)*"|\?\??|[^'"?]+)/)
+ toks.collect {|t| t[0]}
+ end
+
+
+ def exec_direct(sql)
rc = SQLExecDirect(@handle, sql)
check_rc(rc)
self
end
- def tables(schema_name="%", table_name="%", table_type="TABLE")
- rc = SQLTables(@handle, "", schema_name, table_name, table_type)
+
+ def set_cursor_name(name)
+ rc = SQLSetCursorName(@handle, name)
+ check_rc(rc)
+ self
+ end
+
+ def get_cursor_name
+ rc, name = SQLGetCursorName(@handle)
check_rc(rc)
- self
+ name
end
- def rowCount
+ def row_count
rc, rowcount = SQLRowCount(@handle)
check_rc(rc)
rowcount
end
- def numResultCols
+ def num_result_cols
rc, cols = SQLNumResultCols(@handle)
check_rc(rc)
cols
end
- def colNames
- rc, nr_cols = SQLNumResultCols(@handle)
-
- (1..nr_cols).collect do |c|
- rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024)
- name
- end
- end
-
- def fetchAll
+ def fetch_all
if block_given?
- while (row=fetchRow) do
+ while (row=fetch) do
yield row
end
else
res = []
- while (row=fetchRow) do
+ while (row=fetch) do
res << row
end
res
end
end
- def fetchRow(orientation=SQL_FETCH_NEXT, offset=0)
+
+ def fetch
rc, nr_cols = SQLNumResultCols(@handle)
-
- cols = (1..nr_cols).collect do |c|
+
+ cols = (1..nr_cols).collect do |c|
rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024)
[name, type, col_sz]
- end
+ end
- rc = SQLFetchScroll(@handle, orientation, offset)
+ rc = SQLFetch(@handle)
- return nil if rc == SQL_NO_DATA_FOUND
- raise "ERROR" unless rc == SQL_SUCCESS
-
- retval = []
+ if rc == SQL_NO_DATA_FOUND
+ #SQLCloseCursor(@handle)
+ SQLFreeStmt(@handle, SQL_CLOSE) #close cursor
+ return nil
+ end
+
+ raise "ERROR" unless rc == SQL_SUCCESS
+
+ retval = []
cols.each_with_index do |c, i|
- rc, content = SQLGetData(@handle, i+1, c[1], c[2])
+ rc, content = SQLGetData(@handle, i+1, c[1], c[2] + 1) #yun added 1 to c[2]
retval << content
- end
+ end
return retval
end
- alias :fetch :fetchRow
+ def fetch_as_hash
+ rc, nr_cols = SQLNumResultCols(@handle)
+
+ cols = (1..nr_cols).collect do |c|
+ rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024)
+ [name, type, col_sz]
+ end
+
+ rc = SQLFetch(@handle)
+
+ if rc == SQL_NO_DATA_FOUND
+ #SQLCloseCursor(@handle)
+ SQLFreeStmt(@handle, SQL_CLOSE) #close cursor
+ SQLFreeStmt(@handle, SQL_RESET_PARAMS) #reset parameters
+ return nil
+ end
+
+ raise "ERROR" unless rc == SQL_SUCCESS
+
+ retval = {}
+ cols.each_with_index do |c, i|
+ rc, content = SQLGetData(@handle, i+1, c[1], c[2] + 1) #yun added 1 to c[2]
+ retval[c[0]] = content
+ end
+
+ return retval
+ end
end # class Statement
+ class Parameter
+ attr_reader :type, :size, :decimalDigits
+ def initialize(type, size, decimalDigits)
+ @type = type
+ @size = size
+ @decimalDigits = decimalDigits
+ end
+ end
end # module DB2
|