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

Added support for ODBC connections to MS SQL Server so you can connect from a non-Windows machine #1569 [Mark Imbriaco/DeLynn Berry] Added support for limit/offset with the MS SQL Server driver so that pagination will now work #1569 [DeLynn Berry]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1583 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
David Heinemeier Hansson 2005-07-01 17:20:04 +00:00
parent 64612db7cf
commit f2a29ca43c
15 changed files with 234 additions and 130 deletions

View file

@ -1,5 +1,9 @@
*SVN* *SVN*
* Added support for limit/offset with the MS SQL Server driver so that pagination will now work #1569 [DeLynn Berry]
* Added support for ODBC connections to MS SQL Server so you can connect from a non-Windows machine #1569 [Mark Imbriaco/DeLynn Berry]
* Fixed that multiparameter posts ignored attr_protected #1532 [alec+rails@veryclever.net] * Fixed that multiparameter posts ignored attr_protected #1532 [alec+rails@veryclever.net]
* Fixed problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 [flash@vanklinkenbergsoftware.nl] * Fixed problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 [flash@vanklinkenbergsoftware.nl]

View file

@ -38,7 +38,7 @@ Rake::TestTask.new("test_mysql_ruby") { |t|
t.verbose = true t.verbose = true
} }
for adapter in %w( postgresql sqlite sqlite3 sqlserver db2 oci ) for adapter in %w( postgresql sqlite sqlite3 sqlserver sqlserver_odbc db2 oci )
Rake::TestTask.new("test_#{adapter}") { |t| Rake::TestTask.new("test_#{adapter}") { |t|
t.libs << "test" << "test/connections/native_#{adapter}" t.libs << "test" << "test/connections/native_#{adapter}"
t.pattern = "test/*_test{,_#{adapter}}.rb" t.pattern = "test/*_test{,_#{adapter}}.rb"

View file

@ -748,11 +748,7 @@ module ActiveRecord #:nodoc:
end end
def add_limit!(sql, options) def add_limit!(sql, options)
if options[:limit] && options[:offset] connection.add_limit_offset!(sql, options)
connection.add_limit_with_offset!(sql, options[:limit].to_i, options[:offset].to_i)
elsif options[:limit]
connection.add_limit_without_offset!(sql, options[:limit].to_i)
end
end end
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed. # Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.

View file

@ -352,21 +352,15 @@ module ActiveRecord
# Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database. # Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database.
def structure_dump() end def structure_dump() end
def add_limit!(sql, limit) def add_limit!(sql, options)
if limit.is_a? Array return unless options
limit, offset = *limit add_limit_offset!(sql, options)
add_limit_with_offset!(sql, limit.to_i, offset.to_i)
else
add_limit_without_offset!(sql, limit)
end
end end
def add_limit_with_offset!(sql, limit, offset) def add_limit_offset!(sql, options)
sql << " LIMIT #{limit} OFFSET #{offset}" return if options[:limit].nil?
end sql << " LIMIT #{options[:limit]}"
sql << " OFFSET #{options[:offset]}" if options.has_key?(:offset) and !options[:offset].nil?
def add_limit_without_offset!(sql, limit)
sql << " LIMIT #{limit}"
end end
def initialize_schema_information def initialize_schema_information

View file

@ -172,11 +172,17 @@ module ActiveRecord
structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n" structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
end end
end end
def add_limit_with_offset!(sql, limit, offset) def add_limit_offset!(sql, options)
sql << " LIMIT #{offset}, #{limit}" return if options[:limit].nil?
if options[:offset].blank?
sql << " LIMIT #{options[:limit]}"
else
sql << " LIMIT #{options[:offset]}, #{options[:limit]}"
end
end end
def recreate_database(name) def recreate_database(name)
drop_database(name) drop_database(name)
create_database(name) create_database(name)

View file

@ -8,26 +8,49 @@ require 'active_record/connection_adapters/abstract_adapter'
# Modifications: DeLynn Berry <delynnb@megastarfinancial.com> # Modifications: DeLynn Berry <delynnb@megastarfinancial.com>
# Date: 3/22/2005 # Date: 3/22/2005
# #
# This adapter will ONLY work on Windows systems, since it relies on Win32OLE, which, # Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
# to my knowledge, is only available on Window. # Date: 6/26/2005
# #
# It relies on the ADO support in the DBI module. If you are using the # In ADO mode, this adapter will ONLY work on Windows systems,
# since it relies on Win32OLE, which, to my knowledge, is only
# available on Windows.
#
# This mode also relies on the ADO support in the DBI module. If you are using the
# one-click installer of Ruby, then you already have DBI installed, but # one-click installer of Ruby, then you already have DBI installed, but
# the ADO module is *NOT* installed. You will need to get the latest # the ADO module is *NOT* installed. You will need to get the latest
# source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/ # source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
# unzip it, and copy the file <tt>src/lib/dbd_ado/ADO.rb</tt> to # unzip it, and copy the file
# <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt> (you will need to create # <tt>src/lib/dbd_ado/ADO.rb</tt>
# the ADO directory). Once you've installed that file, you are ready to go. # to
# <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt>
# (you will more than likely need to create the ADO directory).
# Once you've installed that file, you are ready to go.
#
# In ODBC mode, the adapter requires the ODBC support in the DBI module which requires
# the Ruby ODBC module. Ruby ODBC 0.996 was used in development and testing,
# and it is available at http://www.ch-werner.de/rubyodbc/
# #
# Options: # Options:
# #
# * <tt>:host</tt> -- Defaults to localhost # * <tt>:mode</tt> -- ADO or ODBC. Defaults to ADO.
# * <tt>:username</tt> -- Defaults to sa # * <tt>:username</tt> -- Defaults to sa.
# * <tt>:password</tt> -- Defaults to nothing # * <tt>:password</tt> -- Defaults to empty string.
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
# #
# I have tested this code on a WindowsXP Pro SP1 system, # ADO specific options:
# ruby 1.8.2 (2004-07-29) [i386-mswin32], SQL Server 2000. #
# * <tt>:host</tt> -- Defaults to localhost.
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
#
# ODBC specific options:
#
# * <tt>:dsn</tt> -- Defaults to nothing.
#
# ADO code tested on Windows 2000 and higher systems,
# running ruby 1.8.2 (2004-07-29) [i386-mswin32], and SQL Server 2000 SP3.
#
# ODBC code tested on a Fedora Core 4 system, running FreeTDS 0.63,
# unixODBC 2.2.11, Ruby ODBC 0.996, Ruby DBI 0.0.23 and Ruby 1.8.2.
# [Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux]
# #
module ActiveRecord module ActiveRecord
class Base class Base
@ -36,44 +59,47 @@ module ActiveRecord
symbolize_strings_in_hash(config) symbolize_strings_in_hash(config)
host = config[:host] mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
username = config[:username] ? config[:username].to_s : 'sa' username = config[:username] ? config[:username].to_s : 'sa'
password = config[:password].to_s password = config[:password] ? config[:password].to_s : ''
if mode == "ODBC"
if config.has_key?(:database) raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
database = config[:database] dsn = config[:dsn]
conn = DBI.connect("DBI:ODBC:#{dsn}", username, password)
else else
raise ArgumentError, "No database specified. Missing argument: database." raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
database = config[:database]
host = config[:host] ? config[:host].to_s : 'localhost'
conn = DBI.connect("DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};")
end end
conn = DBI.connect("DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};")
conn["AutoCommit"] = true conn["AutoCommit"] = true
ConnectionAdapters::SQLServerAdapter.new(conn, logger) ConnectionAdapters::SQLServerAdapter.new(conn, logger)
end end
end end # class Base
module ConnectionAdapters module ConnectionAdapters
class ColumnWithIdentity < Column# :nodoc: class ColumnWithIdentity < Column# :nodoc:
attr_reader :identity, :scale attr_reader :identity, :is_special, :scale
def initialize(name, default, sql_type = nil, is_identity = false, scale_value = 0) def initialize(name, default, sql_type = nil, is_identity = false, scale_value = 0)
super(name, default, sql_type) super(name, default, sql_type)
@scale = scale_value
@identity = is_identity @identity = is_identity
@is_special = sql_type =~ /text|ntext|image/i ? true : false
@scale = scale_value
end end
def simplified_type(field_type) def simplified_type(field_type)
case field_type case field_type
when /int|bigint|smallint|tinyint/i : :integer when /int|bigint|smallint|tinyint/i then :integer
when /float|double|decimal|money|numeric|real|smallmoney/i : @scale == 0 ? :integer : :float when /float|double|decimal|money|numeric|real|smallmoney/i then @scale == 0 ? :integer : :float
when /datetime|smalldatetime/i : :datetime when /datetime|smalldatetime/i then :datetime
when /timestamp/i : :timestamp when /timestamp/i then :timestamp
when /time/i : :time when /time/i then :time
when /text|ntext/i : :text when /text|ntext/i then :text
when /binary|image|varbinary/i : :binary when /binary|image|varbinary/i then :binary
when /char|nchar|nvarchar|string|varchar/i : :string when /char|nchar|nvarchar|string|varchar/i then :string
when /bit/i : :boolean when /bit/i then :boolean
end end
end end
@ -83,28 +109,32 @@ module ActiveRecord
when :string then value when :string then value
when :integer then value == true || value == false ? value == true ? '1' : '0' : value.to_i when :integer then value == true || value == false ? value == true ? '1' : '0' : value.to_i
when :float then value.to_f when :float then value.to_f
when :datetime then cast_to_date_or_time(value) when :datetime then cast_to_datetime(value)
when :timestamp then cast_to_time(value) when :timestamp then cast_to_time(value)
when :time then cast_to_time(value) when :time then cast_to_time(value)
else value else value
end end
end end
def cast_to_date_or_time(value)
return value if value.is_a?(Date)
guess_date_or_time (value.is_a?(Time)) ? value : cast_to_time(value)
end
def cast_to_time(value) def cast_to_time(value)
return value if value.is_a?(Time) return value if value.is_a?(Time)
time_array = ParseDate.parsedate value time_array = ParseDate.parsedate(value)
time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1; time_array[0] ||= 2000
Time.send Base.default_timezone, *time_array time_array[1] ||= 1
time_array[2] ||= 1
Time.send(Base.default_timezone, *time_array) rescue nil
end end
def guess_date_or_time(value) def cast_to_datetime(value)
(value.hour == 0 and value.min == 0 and value.sec == 0) ? if value.is_a?(Time)
Date.new(value.year, value.month, value.day) : value if value.year != 0 and value.month != 0 and value.day != 0
return value
else
return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
end
end
return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
value
end end
# These methods will only allow the adapter to insert binary data with a length of 7K or less # These methods will only allow the adapter to insert binary data with a length of 7K or less
@ -112,14 +142,10 @@ module ActiveRecord
def string_to_binary(value) def string_to_binary(value)
value.gsub(/(\r|\n|\0|\x1a)/) do value.gsub(/(\r|\n|\0|\x1a)/) do
case $1 case $1
when "\r" when "\r" then "%00"
"%00" when "\n" then "%01"
when "\n" when "\0" then "%02"
"%01" when "\x1a" then "%03"
when "\0"
"%02"
when "\x1a"
"%03"
end end
end end
end end
@ -127,22 +153,17 @@ module ActiveRecord
def binary_to_string(value) def binary_to_string(value)
value.gsub(/(%00|%01|%02|%03)/) do value.gsub(/(%00|%01|%02|%03)/) do
case $1 case $1
when "%00" when "%00" then "\r"
"\r" when "%01" then "\n"
when "%01" when "%02\0" then "\0"
"\n" when "%03" then "\x1a"
when "%02\0"
"\0"
when "%03"
"\x1a"
end end
end end
end end
end end #class ColumnWithIdentity < Column
class SQLServerAdapter < AbstractAdapter class SQLServerAdapter < AbstractAdapter
def native_database_types def native_database_types
{ {
:primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY", :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
@ -164,7 +185,6 @@ module ActiveRecord
end end
def select_all(sql, name = nil) def select_all(sql, name = nil)
add_limit!(sql, nil)
select(sql, name) select(sql, name)
end end
@ -177,9 +197,9 @@ module ActiveRecord
def columns(table_name, name = nil) def columns(table_name, name = nil)
sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = '#{table_name}'" sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = '#{table_name}'"
result = nil result = nil
# Uncomment if you want to have the Columns select statment logged. # Comment out if you want to have the Columns select statment logged.
# Personnally, I think it adds unneccessary bloat to the log. # Personnally, I think it adds unneccessary bloat to the log.
# If you do uncomment, make sure to comment the "result" line that follows # If you do comment it out, make sure to un-comment the "result" line that follows
log(sql, name, @connection) { |conn| result = conn.select_all(sql) } log(sql, name, @connection) { |conn| result = conn.select_all(sql) }
#result = @connection.select_all(sql) #result = @connection.select_all(sql)
columns = [] columns = []
@ -199,7 +219,7 @@ module ActiveRecord
execute enable_identity_insert(table_name, true) execute enable_identity_insert(table_name, true)
ii_enabled = true ii_enabled = true
rescue Exception => e rescue Exception => e
# Coulnd't turn on IDENTITY_INSERT raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
end end
end end
end end
@ -212,7 +232,7 @@ module ActiveRecord
begin begin
execute enable_identity_insert(table_name, false) execute enable_identity_insert(table_name, false)
rescue Exception => e rescue Exception => e
# Couldn't turn off IDENTITY_INSERT raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
end end
end end
end end
@ -288,13 +308,13 @@ module ActiveRecord
"[#{name}]" "[#{name}]"
end end
def add_limit_with_offset!(sql, limit, offset) def add_limit_offset!(sql, options)
order_by = sql.include?("ORDER BY") ? get_order_by(sql.sub(/.*ORDER\sBY./, "")) : nil if options.has_key?(:limit) and options.has_key?(:offset) and !options[:limit].nil? and !options[:offset].nil?
sql.gsub!(/SELECT/i, "SELECT * FROM ( SELECT TOP #{limit} * FROM ( SELECT TOP #{limit + offset}")<<" ) AS tmp1 ORDER BY #{order_by[1]} ) AS tmp2 ORDER BY #{order_by[0]}" options[:order] ||= "id ASC"
end sql.gsub!(/SELECT/i, "SELECT * FROM ( SELECT TOP #{options[:limit]} * FROM ( SELECT TOP #{options[:limit] + options[:offset]}")<<" ) AS tmp1 ORDER BY #{change_order_direction(options[:order])} ) AS tmp2 ORDER BY #{options[:order]}"
else
def add_limit_without_offset!(sql, limit) sql.gsub!(/SELECT/i, "SELECT TOP #{options[:limit]}") unless options[:limit].nil?
limit.nil? ? sql : sql.gsub!(/SELECT/i, "SELECT TOP #{limit}") end
end end
def recreate_database(name) def recreate_database(name)
@ -313,6 +333,7 @@ module ActiveRecord
private private
def select(sql, name = nil) def select(sql, name = nil)
rows = [] rows = []
repair_special_columns(sql)
log(sql, name, @connection) do |conn| log(sql, name, @connection) do |conn|
conn.select_all(sql) do |row| conn.select_all(sql) do |row|
record = {} record = {}
@ -332,8 +353,9 @@ module ActiveRecord
end end
def get_table_name(sql) def get_table_name(sql)
if sql =~ /into\s*([^\s]+)\s*/i or if sql =~ /into\s*([^\s]+)\s*|update\s*([^\s]+)\s*/i
sql =~ /update\s*([^\s]+)\s*/i $1
elsif sql =~ /from\s*([^\s]+)\s*/i
$1 $1
else else
nil nil
@ -345,14 +367,8 @@ module ActiveRecord
end end
def get_identity_column(table_name) def get_identity_column(table_name)
if not @table_columns @table_columns = {} unless @table_columns
@table_columns = {} @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
end
if @table_columns[table_name] == nil
@table_columns[table_name] = columns(table_name)
end
@table_columns[table_name].each do |col| @table_columns[table_name].each do |col|
return col.name if col.identity return col.name if col.identity
end end
@ -361,17 +377,35 @@ module ActiveRecord
end end
def query_contains_identity_column(sql, col) def query_contains_identity_column(sql, col)
return sql =~ /[\[.,]\s*#{col}/ return sql =~ /\[#{col}\]/
end end
def get_order_by(sql) def change_order_direction(order)
return sql, sql.gsub(/\s*DESC\s*/, "").gsub(/\s*ASC\s*/, " DESC") case order
when /DESC/i then order.gsub(/DESC/i, "ASC")
when /ASC/i then order.gsub(/ASC/i, "DESC")
else String.new(order).insert(-1, " DESC")
end
end end
def get_offset_amount(limit) def get_special_columns(table_name)
limit = limit.gsub!(/.OFFSET./i, ",").split(',') special = []
return limit[0].to_i, limit[0].to_i+limit[1].to_i @table_columns = {} unless @table_columns
@table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
@table_columns[table_name].each do |col|
special << col.name if col.is_special
end
special
end end
end
end def repair_special_columns(sql)
end special_cols = get_special_columns(get_table_name(sql))
for col in special_cols.to_a
sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
end
sql
end
end #class SQLServerAdapter < AbstractAdapter
end #module ConnectionAdapters
end #module ActiveRecord

View file

@ -902,6 +902,7 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
no_of_devels = Developer.count no_of_devels = Developer.count
no_of_projects = Project.count no_of_projects = Project.count
now = Date.today now = Date.today
sqlnow = Time.now.strftime("%Y/%m/%d 00:00:00")
ken = Developer.new("name" => "Ken") ken = Developer.new("name" => "Ken")
ken.projects.push_with_attributes( Project.find(1), :joined_on => now ) ken.projects.push_with_attributes( Project.find(1), :joined_on => now )
p = Project.new("name" => "Foomatic") p = Project.new("name" => "Foomatic")
@ -916,7 +917,13 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
assert_equal 2, ken.projects(true).size assert_equal 2, ken.projects(true).size
kenReloaded = Developer.find_by_name 'Ken' kenReloaded = Developer.find_by_name 'Ken'
kenReloaded.projects.each { |prj| assert_equal(now.to_s, prj.joined_on.to_s) } # SQL Server doesn't have a separate column type just for dates,
# so the time is in the string and incorrectly formatted
if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
kenReloaded.projects.each { |prj| assert_equal(sqlnow, prj.joined_on.to_s) }
else
kenReloaded.projects.each { |prj| assert_equal(now.to_s, prj.joined_on.to_s) }
end
end end
def test_build def test_build
@ -1004,7 +1011,13 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
end end
def test_additional_columns_from_join_table def test_additional_columns_from_join_table
assert_equal Date.new(2004, 10, 10).to_s, Developer.find(1).projects.first.joined_on.to_s # SQL Server doesn't have a separate column type just for dates,
# so the time is in the string and incorrectly formatted
if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
assert_equal Time.mktime(2004, 10, 10).strftime("%Y/%m/%d 00:00:00"), Developer.find(1).projects.first.joined_on.to_s
else
assert_equal Date.new(2004, 10, 10).to_s, Developer.find(1).projects.first.joined_on.to_s
end
end end
def test_destroy_all def test_destroy_all
@ -1019,8 +1032,15 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
def test_rich_association def test_rich_association
jamis = developers(:jamis) jamis = developers(:jamis)
jamis.projects.push_with_attributes(projects(:action_controller), :joined_on => Date.today) jamis.projects.push_with_attributes(projects(:action_controller), :joined_on => Date.today)
assert_equal Date.today.to_s, jamis.projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s # SQL Server doesn't have a separate column type just for dates,
assert_equal Date.today.to_s, developers(:jamis).projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s # so the time is in the string and incorrectly formatted
if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
assert_equal Time.now.strftime("%Y/%m/%d 00:00:00"), jamis.projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s
assert_equal Time.now.strftime("%Y/%m/%d 00:00:00"), developers(:jamis).projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s
else
assert_equal Date.today.to_s, jamis.projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s
assert_equal Date.today.to_s, developers(:jamis).projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on.to_s
end
end end
def test_associations_with_conditions def test_associations_with_conditions

View file

@ -319,6 +319,10 @@ class BasicsTest < Test::Unit::TestCase
end end
def test_update_all def test_update_all
# The ADO library doesn't support the number of affected rows
if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter
return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
end
assert_equal 2, Topic.update_all("content = 'bulk updated!'") assert_equal 2, Topic.update_all("content = 'bulk updated!'")
assert_equal "bulk updated!", Topic.find(1).content assert_equal "bulk updated!", Topic.find(1).content
assert_equal "bulk updated!", Topic.find(2).content assert_equal "bulk updated!", Topic.find(2).content
@ -337,6 +341,10 @@ class BasicsTest < Test::Unit::TestCase
end end
def test_delete_all def test_delete_all
# The ADO library doesn't support the number of affected rows
if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter
return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
end
assert_equal 2, Topic.delete_all assert_equal 2, Topic.delete_all
end end

View file

@ -7,6 +7,13 @@ class BinaryTest < Test::Unit::TestCase
end end
def test_load_save def test_load_save
# Without using prepared statements, it makes no sense to test
# BLOB data with SQL Server, because the length of a statement is
# limited to 8KB.
if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter
return true if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
end
# Without using prepared statements, it makes no sense to test # Without using prepared statements, it makes no sense to test
# BLOB data with DB2, because the length of a statement is # BLOB data with DB2, because the length of a statement is
# limited to 32KB. # limited to 32KB.

View file

@ -0,0 +1,26 @@
print "Using native SQLServer via ODBC\n"
require 'fixtures/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
dsn1 = 'activerecord_unittest'
dsn2 = 'activerecord_unittest2'
ActiveRecord::Base.establish_connection(
:adapter => "sqlserver",
:mode => "ODBC",
:host => "localhost",
:username => "sa",
:password => "",
:dsn => dsn1
)
Course.establish_connection(
:adapter => "sqlserver",
:mode => "ODBC",
:host => "localhost",
:username => "sa",
:password => "",
:dsn => dsn2
)

View file

@ -112,7 +112,7 @@ class FinderTest < Test::Unit::TestCase
assert_equal first_five_developers, Developer.find_all(nil, 'id ASC', [5]) assert_equal first_five_developers, Developer.find_all(nil, 'id ASC', [5])
assert_equal no_developers, Developer.find_all(nil, 'id ASC', [0]) assert_equal no_developers, Developer.find_all(nil, 'id ASC', [0])
end end
def test_find_all_with_limit_and_offset def test_find_all_with_limit_and_offset
first_three_developers = Developer.find_all nil, 'id ASC', [3, 0] first_three_developers = Developer.find_all nil, 'id ASC', [3, 0]
second_three_developers = Developer.find_all nil, 'id ASC', [3, 3] second_three_developers = Developer.find_all nil, 'id ASC', [3, 3]
@ -128,14 +128,13 @@ class FinderTest < Test::Unit::TestCase
end end
def test_find_all_by_one_attribute_with_options def test_find_all_by_one_attribute_with_options
topics = Topic.find_all_by_content("Have a nice day", nil, "id DESC") topics = Topic.find_all_by_content("Have a nice day", "id DESC")
assert topics(:first), topics.last assert topics(:first), topics.last
topics = Topic.find_all_by_content("Have a nice day", nil, "id DESC") topics = Topic.find_all_by_content("Have a nice day", "id DESC")
assert topics(:first), topics.first assert topics(:first), topics.first
end end
protected protected
def bind(statement, *vars) def bind(statement, *vars)
if vars.first.is_a?(Hash) if vars.first.is_a?(Hash)

View file

@ -21,3 +21,5 @@ DROP TABLE authors;
DROP TABLE tasks; DROP TABLE tasks;
DROP TABLE categories; DROP TABLE categories;
DROP TABLE categories_posts; DROP TABLE categories_posts;
DROP TABLE fk_test_has_pd;
DROP TABLE fk_test_has_fk;

View file

@ -2,7 +2,7 @@ CREATE TABLE accounts (
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
firm_id int default NULL, firm_id int default NULL,
credit_limit int default NULL credit_limit int default NULL
) );
CREATE TABLE companies ( CREATE TABLE companies (
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
@ -12,7 +12,7 @@ CREATE TABLE companies (
name varchar(50) default NULL, name varchar(50) default NULL,
client_of int default NULL, client_of int default NULL,
rating int default 1 rating int default 1
) );
CREATE TABLE topics ( CREATE TABLE topics (
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
@ -27,7 +27,7 @@ CREATE TABLE topics (
replies_count int default 0, replies_count int default 0,
parent_id int default NULL, parent_id int default NULL,
type varchar(50) default NULL type varchar(50) default NULL
) );
CREATE TABLE developers ( CREATE TABLE developers (
id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, id int NOT NULL IDENTITY(1, 1) PRIMARY KEY,

View file

@ -6,7 +6,15 @@ class InheritanceTest < Test::Unit::TestCase
fixtures :companies, :projects fixtures :companies, :projects
def test_a_bad_type_column def test_a_bad_type_column
#SQLServer need to turn Identity Insert On before manually inserting into the Identity column
if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
Company.connection.execute "SET IDENTITY_INSERT companies ON"
end
Company.connection.insert "INSERT INTO companies (id, type, name) VALUES(100, 'bad_class!', 'Not happening')" Company.connection.insert "INSERT INTO companies (id, type, name) VALUES(100, 'bad_class!', 'Not happening')"
#We then need to turn it back Off before continuing.
if ActiveRecord::ConnectionAdapters.const_defined? :SQLServerAdapter and ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
Company.connection.execute "SET IDENTITY_INSERT companies OFF"
end
assert_raises(ActiveRecord::SubclassNotFound) { Company.find(100) } assert_raises(ActiveRecord::SubclassNotFound) { Company.find(100) }
end end

View file

@ -707,7 +707,7 @@ class ValidationsTest < Test::Unit::TestCase
def test_validates_numericality_of def test_validates_numericality_of
Topic.validates_numericality_of( :approved, :allow_nil => true ) Topic.validates_numericality_of( :approved, :allow_nil => true )
["10", "10.0", "10.5", "-10.5", "-0.0001","0090","-090","-090.1",nil,""].each do |v| ["10", "10.0", "10.5", "-10.5", "-0.0001","0090","-090","-090.1",nil,""].each do |v|
t = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => v) t = Topic.new("title" => "numeric test", "content" => "whatever", "approved" => v)
assert t.valid?, "#{v} not recognized as a number" assert t.valid?, "#{v} not recognized as a number"
# we cannot check this as approved is actually an integer field # we cannot check this as approved is actually an integer field
#assert_in_delta v.to_f, t.approved, 0.0000001 #assert_in_delta v.to_f, t.approved, 0.0000001
@ -726,7 +726,7 @@ class ValidationsTest < Test::Unit::TestCase
def test_validates_numericality_of_int def test_validates_numericality_of_int
Topic.validates_numericality_of( :approved, :only_integer => true, :allow_nil => true ) Topic.validates_numericality_of( :approved, :only_integer => true, :allow_nil => true )
["42", "+42", "-42", "042", "0042", "-042", 42, nil,""].each do |v| ["42", "+42", "-42", "042", "0042", "-042", 42, nil,""].each do |v|
t = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => v) t = Topic.new("title" => "numeric test", "content" => "whatever", "approved" => v)
assert t.valid?, "#{v} not recognized as integer" assert t.valid?, "#{v} not recognized as integer"
assert_equal((v.nil? or v == "")? nil : v.to_i, t.approved) assert_equal((v.nil? or v == "")? nil : v.to_i, t.approved)
end end