aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/vendor/db2.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/vendor/db2.rb')
-rw-r--r--activerecord/lib/active_record/vendor/db2.rb357
1 files changed, 357 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/vendor/db2.rb b/activerecord/lib/active_record/vendor/db2.rb
new file mode 100644
index 0000000000..f0ec847d72
--- /dev/null
+++ b/activerecord/lib/active_record/vendor/db2.rb
@@ -0,0 +1,357 @@
+require 'db2/db2cli.rb'
+
+module DB2
+ module DB2Util
+ include DB2CLI
+
+ def free() SQLFreeHandle(@handle_type, @handle); end
+ def handle() @handle; end
+
+ def check_rc(rc)
+ if rc != SQL_SUCCESS and rc != SQL_SUCCESS_WITH_INFO and rc != SQL_NO_DATA_FOUND
+ rec = 1
+ msg = ''
+ loop do
+ a = SQLGetDiagRec(@handle_type, @handle, rec, 500)
+ break if a[0] != SQL_SUCCESS
+ msg << a[3] if !a[3].nil? and a[3] != '' # Create message.
+ rec += 1
+ end
+ raise "DB2 error: #{msg}"
+ end
+ end
+ end
+
+ class Environment
+ include DB2Util
+
+ def initialize
+ @handle_type = SQL_HANDLE_ENV
+ rc, @handle = SQLAllocHandle(@handle_type, SQL_NULL_HANDLE)
+ check_rc(rc)
+ end
+
+ def data_sources(buffer_length = 1024)
+ retval = []
+ max_buffer_length = buffer_length
+
+ a = SQLDataSources(@handle, SQL_FETCH_FIRST, SQL_MAX_DSN_LENGTH + 1, buffer_length)
+ retval << [a[1], a[3]]
+ max_buffer_length = [max_buffer_length, a[4]].max
+
+ loop do
+ a = SQLDataSources(@handle, SQL_FETCH_NEXT, SQL_MAX_DSN_LENGTH + 1, buffer_length)
+ break if a[0] == SQL_NO_DATA_FOUND
+
+ retval << [a[1], a[3]]
+ max_buffer_length = [max_buffer_length, a[4]].max
+ end
+
+ if max_buffer_length > buffer_length
+ get_data_sources(max_buffer_length)
+ else
+ retval
+ end
+ end
+ end
+
+ class Connection
+ include DB2Util
+
+ def initialize(environment)
+ @env = environment
+ @handle_type = SQL_HANDLE_DBC
+ rc, @handle = SQLAllocHandle(@handle_type, @env.handle)
+ check_rc(rc)
+ end
+
+ def connect(server_name, user_name = '', auth = '')
+ check_rc(SQLConnect(@handle, server_name, user_name, auth))
+ end
+
+ def set_connect_attr(attr, value)
+ value += "\0" if value.class == String
+ check_rc(SQLSetConnectAttr(@handle, attr, value))
+ end
+
+ def set_auto_commit_on
+ set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON)
+ end
+
+ def set_auto_commit_off
+ set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF)
+ end
+
+ def disconnect
+ check_rc(SQLDisconnect(@handle))
+ end
+
+ def rollback
+ check_rc(SQLEndTran(@handle_type, @handle, SQL_ROLLBACK))
+ end
+
+ def commit
+ check_rc(SQLEndTran(@handle_type, @handle, SQL_COMMIT))
+ end
+ end
+
+ class Statement
+ include DB2Util
+
+ def initialize(connection)
+ @conn = connection
+ @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 columns(table_name)
+ check_rc(SQLColumns(@handle, "", "%", table_name, "%"))
+ fetch_all
+ end
+
+ def tables
+ check_rc(SQLTables(@handle, "", "%", "%", "TABLE"))
+ fetch_all
+ end
+
+ def prepare(sql)
+ @sql = sql
+ check_rc(SQLPrepare(@handle, sql))
+ 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 |i| # parameter number starts from 1
+ rc, type, size, decimalDigits = SQLDescribeParam(@handle, i)
+ check_rc(rc)
+ @parmArray << Parameter.new(type, size, decimalDigits)
+ end
+ end
+ @prepared = true
+ self
+ end
+
+ def execute(*parms)
+ raise "The statement was not prepared" if @prepared == false
+
+ if parms.size == 1 and parms[0].class == 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)
+
+ valueArray = []
+ 1.upto(@numParms) do |i| # parameter number starts from 1
+ type = @parmArray[i - 1].class
+ size = @parmArray[i - 1].size
+ decimalDigits = @parmArray[i - 1].decimalDigits
+
+ if parms[i - 1].class == 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
+
+ check_rc(SQLExecute(@handle))
+
+ if @numParms != 0
+ check_rc(SQLFreeStmt(@handle, SQL_RESET_PARAMS)) # Reset parameters
+ end
+
+ self
+ end
+
+ #-------------------------------------------------------------------------------
+ # 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 |i| # parameter number starts from 1
+ rc, dataType, parmSize, decimalDigits = SQLDescribeParam(@handle, i)
+ check_rc(rc)
+ if parms[i - 1].class == 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 do |part|
+ case part
+ when '?'
+ result << "'" + (args[arg_index]) + "'" #put it into quotes
+ arg_index += 1
+ when '??'
+ result << "?"
+ else
+ result << part
+ end
+ 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)
+ check_rc(SQLExecDirect(@handle, sql))
+ self
+ end
+
+ def set_cursor_name(name)
+ check_rc(SQLSetCursorName(@handle, name))
+ self
+ end
+
+ def get_cursor_name
+ rc, name = SQLGetCursorName(@handle)
+ check_rc(rc)
+ name
+ end
+
+ def row_count
+ rc, rowcount = SQLRowCount(@handle)
+ check_rc(rc)
+ rowcount
+ end
+
+ def num_result_cols
+ rc, cols = SQLNumResultCols(@handle)
+ check_rc(rc)
+ cols
+ end
+
+ def fetch_all
+ if block_given?
+ while row = fetch do
+ yield row
+ end
+ else
+ res = []
+ while row = fetch do
+ res << row
+ end
+ res
+ end
+ end
+
+ def fetch
+ cols = get_col_desc
+ rc = SQLFetch(@handle)
+ if rc == SQL_NO_DATA_FOUND
+ 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 << adjust_content(content)
+ end
+ retval
+ end
+
+ def fetch_as_hash
+ cols = get_col_desc
+ rc = SQLFetch(@handle)
+ if rc == SQL_NO_DATA_FOUND
+ 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]] = adjust_content(content)
+ end
+ retval
+ end
+
+ def get_col_desc
+ rc, nr_cols = SQLNumResultCols(@handle)
+ cols = (1..nr_cols).collect do |c|
+ rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024)
+ [name.downcase, type, col_sz]
+ end
+ end
+
+ def adjust_content(c)
+ case c.class.to_s
+ when 'DB2CLI::NullClass'
+ return nil
+ when 'DB2CLI::Time'
+ "%02d:%02d:%02d" % [c.hour, c.minute, c.second]
+ when 'DB2CLI::Date'
+ "%04d-%02d-%02d" % [c.year, c.month, c.day]
+ when 'DB2CLI::Timestamp'
+ "%04d-%02d-%02d %02d:%02d:%02d" % [c.year, c.month, c.day, c.hour, c.minute, c.second]
+ else
+ return c
+ end
+ end
+ end
+
+ class Parameter
+ attr_reader :type, :size, :decimalDigits
+ def initialize(type, size, decimalDigits)
+ @type, @size, @decimalDigits = type, size, decimalDigits
+ end
+ end
+end