|
From: Elias K. <el...@us...> - 2002-12-28 14:36:36
|
Update of /cvsroot/ruby-dbi/src/lib/dbd_db2
In directory sc8-pr-cvs1:/tmp/cvs-serv27888/lib/dbd_db2
Modified Files:
Tag: DB2BLOBS
DB2.rb
Log Message:
experimental support for BLOBs via SQLBindParameter
Index: DB2.rb
===================================================================
RCS file: /cvsroot/ruby-dbi/src/lib/dbd_db2/DB2.rb,v
retrieving revision 1.6
retrieving revision 1.6.4.1
diff -u -r1.6 -r1.6.4.1
--- DB2.rb 3 Jul 2002 16:48:35 -0000 1.6
+++ DB2.rb 28 Dec 2002 14:36:30 -0000 1.6.4.1
@@ -29,6 +29,8 @@
#
# $Id$
#
+# Severly hacked by Elias Karakoulakis <ek...@na...>
+# to support large object functionality
require 'db2/db2cli.rb'
@@ -36,7 +38,7 @@
module DBD
module DB2
-USED_DBD_VERSION = "0.1"
+USED_DBD_VERSION = "0.2.2"
module Util
include DB2CLI
@@ -44,11 +46,16 @@
private
def rc_ok(rc)
- rc == SQL_SUCCESS or rc == SQL_SUCCESS_WITH_INFO
+ rc == SQL_SUCCESS or rc == SQL_SUCCESS_WITH_INFO or rc == SQL_NEED_DATA
end
- def error(rc, msg)
- raise DBI::DatabaseError.new(msg) unless rc_ok(rc)
+ def error(rc, msg, handleType=nil, handle=nil)
+ begin
+ unless handle.nil? or handleType.nil?
+ SQLGetDiagRec(handleType, handle, 1, 512).each { |a| msg << "\n\t#{a}" }
+ end
+ raise DBI::DatabaseError.new(msg, rc)
+ end unless rc_ok(rc)
end
end # module DB2Util
@@ -65,10 +72,10 @@
def connect(dbname, user, auth, attr)
rc, dbc = SQLAllocHandle(SQL_HANDLE_DBC, @env)
- error(rc, "Could not allocate Database Connection")
+ error(rc, "Could not allocate Database Connection", SQL_HANDLE_ENV, @env)
rc = SQLConnect(dbc, dbname, user, auth)
- error(rc, "Could not connect to Database")
+ error(rc, "Could not connect to Database", SQL_HANDLE_DBC, dbc)
return Database.new(dbc, attr)
end
@@ -107,25 +114,26 @@
class Database < DBI::BaseDatabase
include Util
- include SQL::BasicBind
- include SQL::BasicQuote
+ #include SQL::BasicBind
+ #include SQL::BasicQuote
def disconnect
rollback
rc = SQLDisconnect(@handle)
- error(rc, "Could not disconnect from Database")
+ error(rc, "Could not disconnect from Database", SQL_HANDLE_DBC, @handle)
rc = SQLFreeHandle(SQL_HANDLE_DBC, @handle)
- error(rc, "Could not free Database handle")
- end
+ error(rc, "Could not free Database handle", SQL_HANDLE_DBC, @handle)
+ end
+=begin
def tables
rc, stmt = SQLAllocHandle(SQL_HANDLE_STMT, @handle)
- error(rc, "Could not allocate Statement")
+ error(rc, "Could not allocate Statement", SQL_HANDLE_DBC, @handle)
rc = SQLTables(stmt, "", "%", "%", "TABLE, VIEW")
- error(rc, "Could not execute SQLTables")
+ error(rc, "Could not execute SQLTables", SQL_HANDLE_STMT, stmt)
st = Statement.new(stmt, nil)
res = st.fetch_all
@@ -133,7 +141,13 @@
res.collect {|row| row[1].to_s + "." + row[2].to_s}
end
+=end
+ def tables
+ execute("SELECT TABSCHEMA,TABNAME FROM SYSCAT.TABLES").fetch_all.collect { |row|
+ "#{row[0].strip}.#{row[1].strip}"
+ }
+ end
def ping
begin
@@ -145,45 +159,37 @@
return false
end
end
-
- def do(stmt, *bindvars)
- rc, sth = SQLAllocHandle(SQL_HANDLE_STMT, @handle)
- error(rc, "Could not allocate Statement")
-
- sql = bind(self, stmt, bindvars)
- rc = SQLExecDirect(sth, sql)
- error(rc, "Could not execute statement")
-
- rc, rpc = SQLRowCount(sth)
- error(rc, "Could not get RPC")
-
- rc = SQLFreeHandle(SQL_HANDLE_STMT, sth)
- error(rc, "Could not free Statement")
-
- return rpc
+
+ def prepare(sql)
+ new_statement = Statement.new(@handle, sql)
+ new_statement.prepare
+ return new_statement
end
- def prepare(statement)
- rc, stmt = SQLAllocHandle(SQL_HANDLE_STMT, @handle)
- error(rc, "Could not allocate Statement")
-
- Statement.new(stmt, statement)
+ def []=(attribute, value)
+ rc = case attribute.downcase
+ when 'autocommit'
+ SQLSetConnectAttr(@handle, SQL_ATTR_AUTOCOMMIT, (value ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), SQL_IS_INTEGER)
+ else -1
+ end
+ error(rc, "Could not set DB2 connection attribute (#{attribute.inspect}=#{value.inspect})", SQL_HANDLE_DBC, @handle)
+ @attr[attribute] = value
+ end
+
+ def [](attribute)
+ @attr[attribute]
end
-
- # TODO
- #def []=(attr, value)
- #end
# TODO: method columns(table)
def commit
rc = SQLEndTran(SQL_HANDLE_DBC, @handle, SQL_COMMIT)
- error(rc, "Could not commit transaction")
+ error(rc, "Could not commit transaction", SQL_HANDLE_DBC, @handle)
end
def rollback
rc = SQLEndTran(SQL_HANDLE_DBC, @handle, SQL_ROLLBACK)
- error(rc, "Could not rollback transaction")
+ error(rc, "Could not rollback transaction", SQL_HANDLE_DBC, @handle)
end
end # class Database
@@ -193,22 +199,102 @@
include Util
include SQL::BasicBind
include SQL::BasicQuote
-
- def initialize(handle, statement)
+
+ attr_reader :handle
+
+ # WARNING: Statement.new first argument is its parent database handle
+ # and NOT a statement handle (to converge with other DBD drivers API)
+ def initialize(dbhandle, statement)
super(nil)
- @handle = handle
+ @dbhandle = dbhandle
@statement = statement
+ rc, @handle = SQLAllocHandle(SQL_HANDLE_STMT, dbhandle)
+ error(rc, "Could not allocate Statement", SQL_HANDLE_DBC, dbhandle)
@arr = []
- @params = []
@cols = nil
- @cols = get_col_info if @statement.nil?
+ #@cols = get_col_info if @statement.nil?
+ #
+ @prepared = false
+ @params = Hash.new
+ @boundvars = 0
+ puts "#{self}.initialize handle=#{@handle}, dbhandle=#{dbhandle}, statement=#{statement}" if $DEBUG
end
-
+
+ #Ruby's finalizers are strange: finalizer closure must be defined OUTSIDE self.new()
+ # so that no reference to object is visible to the finalizer...!
+ @@__statement_new__ = Statement.method('new')
+ def self.new(*args)
+ obj = @@__statement_new__[args[0], args[1]]
+ puts "new Statement, id=#{obj.id}" if $DEBUG
+ ObjectSpace.define_finalizer(obj, Statement.finalizer)
+ return obj
+ end
+ def self.finalizer
+ return proc {|id|
+ puts "Statement id=#{id} is FINALIZED" if $DEBUG
+ DB2CLI.free_bound_params(id)
+ }
+ end
+ def prepare
+ puts "#{self}.prepare hnd=#{@handle} stmt=#{@statement}" if $DEBUG
+ raise DBI::DatabaseError.new('statement already prepared') if @prepared
+ rc = SQLPrepare(@handle, @statement)
+ error(rc, "Could not prepare statement", SQL_HANDLE_STMT, @handle)
+ if $DEBUG
+ rc, count = SQLNumParams(@handle)
+ puts "#{self}.prepare: DB2CLI counted #{count} parameters in statement"
+ end
+ parse_params
+ @prepared = true
+ end
+
+ def bind_param(param_id, param_value, attribs=nil)
+ puts "#{self}.bind_param: idx=#{param_id}, param_value.size=#{param_value.to_s.length}, attribs=#{attribs.inspect}" if $DEBUG
+ raise InterfaceError, "only '?' parameters supported" unless param_id.is_a? Fixnum
+ direction = SQL_PARAM_INPUT
+ # TODO: ruby-to-CLI mapping should be done by the DBI layer, not the driver
+ rc, db2_sql_type, size, decimalDigits = SQLDescribeParam(@handle, param_id)
+ error(rc, 'SQLDescribeParam failed', SQL_HANDLE_STMT, @handle)
+ if attribs
+ case attribs['direction']
+ when 'in' then direction = SQL_PARAM_INPUT
+ when 'out' then direction = SQL_PARAM_OUTPUT
+ when 'inout' then direction = SQL_PARAM_INPUT_OUTPUT
+ end
+ if attribs['sql_type']
+ db2_sql_type = DBI_to_DB2_type_mapping[attribs['sql_type']][0]
+ puts 'db2_sql_type overriden: ' + DBI::Utils::ConstResolver.to_name(DBI::DB2CLI, db2_sql_type).join(',') if $DEBUG
+ end
+ end
+ # do the doo
+ rc = SQLBindParameter(@handle, param_id, direction, db2_sql_type, param_value)
+ error(rc, "Could not bind parameter #{param_id.inspect}", SQL_HANDLE_STMT, @handle)
+ @boundvars += 1
+ end
+
+=begin
def bind_param(param, value, attribs)
raise InterfaceError, "only ? parameters supported" unless param.is_a? Fixnum
@params[param-1] = value
end
+=end
+
+ def execute
+ puts "#{self}.execute" if $DEBUG
+ if @prepared and (@params.size != @boundvars)
+ errstr = (@params.size < @boundvars)? 'Too many' : 'Not enough'
+ errstr << " SQL parameters (expected #{@params.size}, got #{@boundvars})"
+ raise DBI::DatabaseError.new(errstr)
+ end
+
+ rc = SQLExecute(@handle)
+ error(rc, "Could not execute statement:\n#{@statement}", SQL_HANDLE_STMT, @handle)
+
+ @cols = get_col_info
+ backbind_params
+ end
+=begin
def execute
sql = bind(self, @statement, @params)
@@ -220,10 +306,12 @@
#rc = SQLExecute(@handle)
#error(rc, "Could not execute statement")
end
+=end
def finish
+ flush_params
rc = SQLFreeHandle(SQL_HANDLE_STMT, @handle)
- error(rc, "Could not free Statement")
+ error(rc, "Could not free Statement", SQL_HANDLE_STMT, @handle)
end
def fetch
@@ -250,18 +338,55 @@
def cancel
rc = SQLFreeStmt(@handle, SQL_CLOSE)
- error(rc, "Could not close/cancel statment")
+ error(rc, "Could not close/cancel statement", SQL_HANDLE_STMT, @handle)
@cols = nil
end
def rows
rc, rpc = SQLRowCount(@handle)
- error(rc, "Could not get RPC")
+ error(rc, "Could not get RPC", SQL_HANDLE_STMT, @handle)
return rpc
end
-
+ #------
private
+ #------
+
+ # parses statements for parameters
+ def parse_params
+ puts "#{self}.parse_params" if $DEBUG
+ result = ""
+ @params = []
+ #flush_params
+ #puts "statement tokens: #{tokens(@statement).join(', ')}" if $DEBUG
+ tokens(@statement).each { |part|
+ case part
+ when '?'
+ @params << part
+ result << '?'
+ when '??'
+ result << '?'
+ else
+ result << part
+ end
+ }
+ puts "#{self}.parse_params: params size=#{@params.size}" if $DEBUG
+ end
+
+ # binds inout/out (result) parameter buffers after query execution into ruby values
+ def backbind_params
+ puts "#{self}.backbind_params" if $DEBUG
+ flush_params
+ # TODO: some beautiful code that uses @boundvars to return new Ruby values
+ # from stored procedure execution
+ end
+
+ # frees buffers allocated for inout/out parameters
+ def flush_params
+ puts "#{self}.flush_params" if $DEBUG
+ DB2CLI.free_bound_params(self.id) # let CLI free up its internal param buffers
+ @boundvars = 0
+ end
# TODO: check typenames
DB2_to_DBI_type_mapping = {
@@ -300,6 +425,42 @@
DB2CLI::SQL_LONGVARGRAPHIC => [DBI::SQL_OTHER, 'LONG VARGRAPHIC']
}
+ DBI_to_DB2_type_mapping = {
+ DBI::SQL_DOUBLE => [DB2CLI::SQL_DOUBLE, 'DOUBLE'],
+ DBI::SQL_FLOAT => [DB2CLI::SQL_FLOAT, 'FLOAT'],
+ DBI::SQL_REAL => [DB2CLI::SQL_REAL, 'REAL'],
+
+ DBI::SQL_INTEGER => [DB2CLI::SQL_INTEGER, 'INTEGER'],
+ DBI::SQL_BIGINT => [DB2CLI::SQL_BIGINT, 'BIGINT'],
+ DBI::SQL_SMALLINT => [DB2CLI::SQL_SMALLINT, 'SMALLINT'],
+
+ DBI::SQL_DECIMAL => [DB2CLI::SQL_DECIMAL, 'DECIMAL'],
+ DBI::SQL_NUMERIC => [DB2CLI::SQL_NUMERIC, 'NUMERIC'],
+
+ DBI::SQL_DATE => [DB2CLI::SQL_TYPE_DATE, 'DATE'],
+ DBI::SQL_TIME => [DB2CLI::SQL_TYPE_TIME, 'TIME'],
+ DBI::SQL_TIMESTAMP => [DB2CLI::SQL_TYPE_TIMESTAMP,'TIMESTAMP'],
+
+ DBI::SQL_CHAR => [DB2CLI::SQL_CHAR, 'CHAR'],
+ DBI::SQL_VARCHAR => [DB2CLI::SQL_VARCHAR, 'VARCHAR'],
+ DBI::SQL_LONGVARCHAR => [DB2CLI::SQL_LONGVARCHAR, 'LONG VARCHAR'],
+ DBI::SQL_CLOB => [DB2CLI::SQL_CLOB, 'CLOB'],
+
+ DBI::SQL_BINARY => [DB2CLI::SQL_BINARY, 'BINARY'],
+ DBI::SQL_VARBINARY => [DB2CLI::SQL_VARBINARY, 'VARBINARY'],
+ DBI::SQL_LONGVARBINARY => [DB2CLI::SQL_LONGVARBINARY, 'LONG VARBINARY'],
+ DBI::SQL_BLOB => [DB2CLI::SQL_BLOB, 'BLOB'],
+
+ # DB2 specific types
+ 'SQL_BLOB_LOCATOR' => [DB2CLI::SQL_BLOB_LOCATOR, 'BLOB LOCATOR'],
+ 'SQL_CLOB_LOCATOR' => [DB2CLI::SQL_CLOB_LOCATOR, 'CLOB LOCATOR'],
+ 'SQL_DBCLOB' => [DB2CLI::SQL_DBCLOB, 'DBCLOB'],
+ 'SQL_DBCLOB_LOCATOR' => [DB2CLI::SQL_DBCLOB_LOCATOR, 'DBCLOB LOCATOR'],
+ 'SQL_GRAPHIC' => [DB2CLI::SQL_GRAPHIC, 'GRAPHIC'],
+ 'SQL_VARGRAPHIC' => [DB2CLI::SQL_VARGRAPHIC, 'VARGRAPHIC'],
+ 'SQL_LONGVARGRAPHIC' => [DB2CLI::SQL_LONGVARGRAPHIC, 'LONG VARGRAPHIC']
+ }
+
MAX_COL_SIZE = 256
#
@@ -307,11 +468,11 @@
#
def get_col_info
rc, nr_cols = SQLNumResultCols(@handle)
- error(rc, "Could not get number of result columns")
+ error(rc, "Could not get number of result columns", SQL_HANDLE_STMT, @handle)
(1..nr_cols).collect do |c|
rc, column_name, buflen, data_type, column_size, decimal_digits, nullable = SQLDescribeCol(@handle, c, MAX_COL_SIZE)
- error(rc, "Could not describe column")
+ error(rc, "Could not describe column", @handle, SQL_HANDLE_STMT)
sql_type, type_name = DB2_to_DBI_type_mapping[data_type]
@@ -329,11 +490,11 @@
def do_fetch(rc)
return nil if rc == SQL_NO_DATA_FOUND
- error(rc, "Could not fetch row")
+ error(rc, "Could not fetch row", @handle, SQL_HANDLE_STMT)
@cols.each_with_index do |c, i|
rc, content = SQLGetData(@handle, i+1, c['db2_type'], c['precision'])
- error(rc, "Could not get data")
+ error(rc, "Could not get data", @handle, SQL_HANDLE_STMT)
@arr[i] =
case content
|