1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

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
This commit is contained in:
David Heinemeier Hansson 2005-01-01 19:22:16 +00:00
parent a6fefad354
commit daf3e92a31
37 changed files with 740 additions and 70 deletions

View file

@ -1,5 +1,7 @@
*SVN*
* Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://raa.ruby-lang.org/project/ruby-db2/) #386 [Maik Schmidt]
* Added the final touches to the Microsoft SQL Server adapter by DeLynn Berry that makes it suitable for actual use #394 [DeLynn Barry]
* Fixed a bug in the Ruby/MySQL that caused binary content to be escaped badly and come back mangled #405 [Tobias Luetke]

View file

@ -51,6 +51,12 @@ Rake::TestTask.new("test_sqlserver") { |t|
t.verbose = true
}
Rake::TestTask.new("test_db2") { |t|
t.libs << "test" << "test/connections/native_db2"
t.pattern = 'test/*_test.rb'
t.verbose = true
}
# Generate the RDoc documentation
Rake::RDocTask.new { |rdoc|

View file

@ -38,6 +38,7 @@ files = %w-
active_record/connection_adapters/mysql_adapter.rb
active_record/connection_adapters/postgresql_adapter.rb
active_record/connection_adapters/sqlite_adapter.rb
active_record/connection_adapters/db2_adapter.rb
active_record/deprecated_associations.rb
active_record/fixtures.rb
active_record/observer.rb

View file

@ -58,3 +58,4 @@ require 'active_record/connection_adapters/mysql_adapter'
require 'active_record/connection_adapters/postgresql_adapter'
require 'active_record/connection_adapters/sqlite_adapter'
require 'active_record/connection_adapters/sqlserver_adapter'
require 'active_record/connection_adapters/db2_adapter'

View file

@ -577,4 +577,4 @@ module ActiveRecord
end
end
end
end
end

View file

@ -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.

View file

@ -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

View file

@ -364,4 +364,4 @@ module Test#:nodoc:
end
end
end
end
end

View file

@ -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

View file

@ -519,7 +519,7 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
def test_removing_associations_on_destroy
Developer.find(1).destroy
assert Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = '1'").empty?
assert Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty?
end
def test_additional_columns_from_join_table

View file

@ -119,7 +119,7 @@ class BasicsTest < Test::Unit::TestCase
def test_update
topic = Topic.new
topic.title = "Another New Topic"
topic.written_on = "2003-12-12 23:23"
topic.written_on = "2003-12-12 23:23:00"
topic.save
id = topic.id
assert_equal(id, topic.id)
@ -162,7 +162,7 @@ class BasicsTest < Test::Unit::TestCase
def test_destroy
topic = Topic.new
topic.title = "Yet Another New Topic"
topic.written_on = "2003-12-12 23:23"
topic.written_on = "2003-12-12 23:23:00"
topic.save
id = topic.id
topic.destroy
@ -585,4 +585,4 @@ class BasicsTest < Test::Unit::TestCase
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1) }
assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
end
end
end

View file

@ -0,0 +1,24 @@
print "Using native DB2\n"
require 'fixtures/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
db1 = 'arunit'
db2 = 'arunit2'
ActiveRecord::Base.establish_connection(
:adapter => "db2",
:host => "localhost",
:username => "arunit",
:password => "arunit",
:database => db1
)
Course.establish_connection(
:adapter => "db2",
:host => "localhost",
:username => "arunit2",
:password => "arunit2",
:database => db2
)

View file

@ -0,0 +1,21 @@
first_client:
id: 2
type: Client
firm_id: 1
client_of: 2
name: Summit
ruby_type: Client
first_firm:
id: 1
type: Firm
name: 37signals
ruby_type: Firm
second_client:
id: 3
type: Client
firm_id: 1
client_of: 1
name: Microsoft
ruby_type: Client

View file

@ -1,6 +0,0 @@
id => 2
type => Client
firm_id => 1
client_of => 2
name => Summit
ruby_type => Client

View file

@ -1,4 +0,0 @@
id => 1
type => Firm
name => 37signals
ruby_type => Firm

View file

@ -1,6 +0,0 @@
id => 3
type => Client
firm_id => 1
client_of => 1
name => Microsoft
ruby_type => Client

View file

@ -0,0 +1,7 @@
java:
id: 2
name: Java Development
ruby:
id: 1
name: Ruby Development

View file

@ -1,2 +0,0 @@
id => 2
name => Java Development

View file

@ -1,2 +0,0 @@
id => 1
name => Ruby Development

View file

@ -0,0 +1,7 @@
david:
id: 1
name: David
balance: 50
address_street: Funny Street
address_city: Scary Town
address_country: Loony Land

View file

@ -1,6 +0,0 @@
id => 1
name => David
balance => 50
address_street => Funny Street
address_city => Scary Town
address_country => Loony Land

View file

@ -0,0 +1,112 @@
CREATE TABLE accounts (
id int generated by default as identity (start with +10000),
firm_id int default NULL,
credit_limit int default NULL,
PRIMARY KEY (id)
);
CREATE TABLE companies (
id int generated by default as identity (start with +10000),
type varchar(50) default NULL,
ruby_type varchar(50) default NULL,
firm_id int default NULL,
name varchar(50) default NULL,
client_of int default NULL,
rating int default 1,
PRIMARY KEY (id)
);
CREATE TABLE topics (
id int generated by default as identity (start with +10000),
title varchar(255) default NULL,
author_name varchar(255) default NULL,
author_email_address varchar(255) default NULL,
written_on timestamp default NULL,
bonus_time time default NULL,
last_read date default NULL,
content varchar(3000),
approved smallint default 1,
replies_count int default 0,
parent_id int default NULL,
type varchar(50) default NULL,
PRIMARY KEY (id)
);
CREATE TABLE developers (
id int generated by default as identity (start with +10000),
name varchar(100) default NULL,
salary int default 70000,
PRIMARY KEY (id)
);
CREATE TABLE projects (
id int generated by default as identity (start with +10000),
name varchar(100) default NULL,
PRIMARY KEY (id)
);
CREATE TABLE developers_projects (
developer_id int NOT NULL,
project_id int NOT NULL,
joined_on date default NULL
);
CREATE TABLE customers (
id int generated by default as identity (start with +10000),
name varchar(100) default NULL,
balance int default 0,
address_street varchar(100) default NULL,
address_city varchar(100) default NULL,
address_country varchar(100) default NULL,
PRIMARY KEY (id)
);
CREATE TABLE movies (
movieid int generated by default as identity (start with +10000),
name varchar(100) default NULL,
PRIMARY KEY (movieid)
);
CREATE TABLE subscribers (
nick varchar(100) NOT NULL,
name varchar(100) default NULL,
PRIMARY KEY (nick)
);
CREATE TABLE booleantests (
id int generated by default as identity (start with +10000),
value int default NULL,
PRIMARY KEY (id)
);
CREATE TABLE auto_id_tests (
auto_id int generated by default as identity (start with +10000),
value int default NULL,
PRIMARY KEY (auto_id)
);
CREATE TABLE entrants (
id int NOT NULL PRIMARY KEY,
name varchar(255) NOT NULL,
course_id int NOT NULL
);
CREATE TABLE colnametests (
id int generated by default as identity (start with +10000),
references int NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE mixins (
id int generated by default as identity (start with +10000),
parent_id int default NULL,
pos int default NULL,
created_at timestamp default NULL,
updated_at timestamp default NULL,
lft int default NULL,
rgt int default NULL,
root_id int default NULL,
type varchar(40) default NULL,
PRIMARY KEY (id)
);

View file

@ -0,0 +1,4 @@
CREATE TABLE courses (
id int NOT NULL PRIMARY KEY,
name varchar(255) NOT NULL
);

View file

@ -0,0 +1,13 @@
david_action_controller:
developer_id: 1
project_id: 2
joined_on: 2004-10-10
david_active_record:
developer_id: 1
project_id: 1
joined_on: 2004-10-10
jamis_active_record:
developer_id: 2
project_id: 1

14
activerecord/test/fixtures/entrants.yml vendored Normal file
View file

@ -0,0 +1,14 @@
first:
id: 1
course_id: 1
name: Ruby Developer
second:
id: 2
course_id: 1
name: Ruby Guru
third:
id: 3
course_id: 2
name: Java Lover

View file

@ -1,3 +0,0 @@
id => 1
course_id => 1
name => Ruby Developer

View file

@ -1,3 +0,0 @@
id => 2
course_id => 1
name => Ruby Guru

View file

@ -1,3 +0,0 @@
id => 3
course_id => 2
name => Java Lover

7
activerecord/test/fixtures/movies.yml vendored Normal file
View file

@ -0,0 +1,7 @@
first:
movieid: 1
name: Terminator
second:
movieid: 2
name: Gladiator

View file

@ -1,2 +0,0 @@
movieid => 1
name => Terminator

View file

@ -1,2 +0,0 @@
movieid => 2
name => Gladiator

View file

@ -0,0 +1,7 @@
action_controller:
id: 2
name: Active Controller
active_record:
id: 1
name: Active Record

View file

@ -1,2 +0,0 @@
id => 2
name => Active Controller

View file

@ -1,2 +0,0 @@
id => 1
name => Active Record

21
activerecord/test/fixtures/topics.yml vendored Normal file
View file

@ -0,0 +1,21 @@
first:
id: 1
title: The First Topic
author_name: David
author_email_address: david@loudthinking.com
written_on: 2003-07-16 15:28:00
bonus_time: 12:13:14
last_read: 2004-04-15
content: Have a nice day
approved: 0
replies_count: 0
second:
id: 2
title: The Second Topic's of the day
author_name: Mary
written_on: 2003-07-15 15:28:00
content: Have a great day!
approved: 1
replies_count: 2
parent_id: 1

View file

@ -1,10 +0,0 @@
id => 1
title => The First Topic
author_name => David
author_email_address => david@loudthinking.com
written_on => 2003-07-16 15:28
bonus_time => 12:13:14
last_read => 2004-04-15
content => Have a nice day
approved => 0
replies_count => 0

View file

@ -1,8 +0,0 @@
id => 2
title => The Second Topic's of the day
author_name => Mary
written_on => 2003-07-15 15:28
content => Have a great day!
approved => 1
replies_count => 2
parent_id => 1