aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2005-01-01 19:22:16 +0000
committerDavid Heinemeier Hansson <david@loudthinking.com>2005-01-01 19:22:16 +0000
commitdaf3e92a316c041b6b2bf331c8d2a51e710fe7bf (patch)
treeca492afaf76db59ec1ab99105c07b54ffb28cc6a /activerecord/lib/active_record
parenta6fefad354d36f3e9a91f8582659ffcda1a35855 (diff)
downloadrails-daf3e92a316c041b6b2bf331c8d2a51e710fe7bf.tar.gz
rails-daf3e92a316c041b6b2bf331c8d2a51e710fe7bf.tar.bz2
rails-daf3e92a316c041b6b2bf331c8d2a51e710fe7bf.zip
Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://raa.ruby-lang.org/project/ruby-db2/) #386 [Maik Schmidt]. Converted all the fixtures to YAML style ones.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@303 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib/active_record')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb2
-rwxr-xr-xactiverecord/lib/active_record/base.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/db2_adapter.rb127
-rwxr-xr-xactiverecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/vendor/db2.rb357
5 files changed, 489 insertions, 5 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 019de942b9..742491ae48 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -577,4 +577,4 @@ module ActiveRecord
end
end
end
-end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 849fb1578b..284f06c694 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -311,7 +311,7 @@ module ActiveRecord #:nodoc:
sql << "ORDER BY #{orderings} " unless orderings.nil?
connection.add_limit!(sql, 1)
-
+
record = connection.select_one(sql, "#{name} Load First")
instantiate(record) unless record.nil?
end
@@ -919,7 +919,7 @@ module ActiveRecord #:nodoc:
# table with a master_id foreign key can instantiate master through Client#master.
def method_missing(method_id, *arguments)
method_name = method_id.id2name
-
+
if method_name =~ read_method? && @attributes.include?($1)
return read_attribute($1)
elsif method_name =~ read_untyped_method? && @attributes.include?($1)
@@ -959,7 +959,7 @@ module ActiveRecord #:nodoc:
# Returns true if the attribute is of a text column and marked for serialization.
def unserializable_attribute?(attr_name, column)
- @attributes[attr_name] && column.send(:type) == :text && @attributes[attr_name].is_a?(String) && self.class.serialized_attributes[attr_name]
+ @attributes[attr_name] && [:text, :string].include?(column.send(:type)) && @attributes[attr_name].is_a?(String) && self.class.serialized_attributes[attr_name]
end
# Returns the unserialized object of the attribute.
diff --git a/activerecord/lib/active_record/connection_adapters/db2_adapter.rb b/activerecord/lib/active_record/connection_adapters/db2_adapter.rb
new file mode 100644
index 0000000000..b4f4f24ca4
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/db2_adapter.rb
@@ -0,0 +1,127 @@
+# db2_adapter.rb
+# author: Maik Schmidt <contact@maik-schmidt.de>
+
+require 'active_record/connection_adapters/abstract_adapter'
+
+begin
+ require 'db2/db2cli' unless self.class.const_defined?(:DB2CLI)
+ require 'active_record/vendor/db2'
+
+ module ActiveRecord
+ class Base
+ # Establishes a connection to the database that's used by
+ # all Active Record objects
+ def self.db2_connection(config) # :nodoc:
+ symbolize_strings_in_hash(config)
+ usr = config[:username]
+ pwd = config[:password]
+
+ if config.has_key?(:database)
+ database = config[:database]
+ else
+ raise ArgumentError, "No database specified. Missing argument: database."
+ end
+
+ connection = DB2::Connection.new(DB2::Environment.new)
+ connection.connect(database, usr, pwd)
+ ConnectionAdapters::DB2Adapter.new(connection)
+ end
+ end
+
+ module ConnectionAdapters
+ class DB2Adapter < AbstractAdapter # :nodoc:
+ def select_all(sql, name = nil)
+ select(sql, name)
+ end
+
+ def select_one(sql, name = nil)
+ select(sql, name).first
+ end
+
+ def insert(sql, name = nil, pk = nil, id_value = nil)
+ execute(sql, name = nil)
+ id_value || last_insert_id
+ end
+
+ def execute(sql, name = nil)
+ rows_affected = 0
+ log(sql, name, @connection) do |connection|
+ stmt = DB2::Statement.new(connection)
+ stmt.exec_direct(sql)
+ rows_affected = stmt.row_count
+ stmt.free
+ end
+ rows_affected
+ end
+
+ alias_method :update, :execute
+ alias_method :delete, :execute
+
+ def begin_db_transaction
+ @connection.set_auto_commit_off
+ end
+
+ def commit_db_transaction
+ @connection.commit
+ @connection.set_auto_commit_on
+ end
+
+ def rollback_db_transaction
+ @connection.rollback
+ @connection.set_auto_commit_on
+ end
+
+ def quote_column_name(name) name; end
+
+ def quote_string(s)
+ s.gsub(/'/, "''") # ' (for ruby-mode)
+ end
+
+ def add_limit!(sql, limit)
+ sql << " FETCH FIRST #{limit} ROWS ONLY"
+ end
+
+ def columns(table_name, name = nil)
+ stmt = DB2::Statement.new(@connection)
+ result = []
+ stmt.columns(table_name.upcase).each do |c|
+ c_name = c[3].downcase
+ c_default = c[12] == 'NULL' ? nil : c[12]
+ c_type = c[5].downcase
+ c_type += "(#{c[6]})" if !c[6].nil? && c[6] != ''
+ result << Column.new(c_name, c_default, c_type)
+ end
+ stmt.free
+ result
+ end
+
+ private
+
+ def last_insert_id
+ row = select_one(<<-GETID.strip)
+ with temp(id) as (values (identity_val_local())) select * from temp
+ GETID
+ row['id'].to_i
+ end
+
+ def select(sql, name = nil)
+ stmt = nil
+ log(sql, name, @connection) do |connection|
+ stmt = DB2::Statement.new(connection)
+ stmt.exec_direct(sql)
+ end
+
+ rows = []
+ while row = stmt.fetch_as_hash
+ rows << row
+ end
+ stmt.free
+ rows
+ end
+ end
+ end
+ end
+rescue LoadError
+ retry if require('rubygems') rescue LoadError
+ # DB2 driver is unavailable.
+end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 21d0615dfa..1db5d29bd7 100755
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -364,4 +364,4 @@ module Test#:nodoc:
end
end
end
-end \ No newline at end of file
+end
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