From f2a29ca43cb0de38a25bf7f68bca5c11871692ce Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 1 Jul 2005 17:20:04 +0000 Subject: 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 --- activerecord/CHANGELOG | 4 + activerecord/Rakefile | 2 +- activerecord/lib/active_record/base.rb | 6 +- .../connection_adapters/abstract_adapter.rb | 20 +- .../connection_adapters/mysql_adapter.rb | 14 +- .../connection_adapters/sqlserver_adapter.rb | 222 ++++++++++++--------- activerecord/test/associations_test.rb | 28 ++- activerecord/test/base_test.rb | 8 + activerecord/test/binary_test.rb | 7 + .../native_sqlserver_odbc/connection.rb | 26 +++ activerecord/test/deprecated_finder_test.rb | 7 +- .../fixtures/db_definitions/sqlserver.drop.sql | 2 + .../test/fixtures/db_definitions/sqlserver.sql | 6 +- activerecord/test/inheritance_test.rb | 8 + activerecord/test/validations_test.rb | 4 +- 15 files changed, 234 insertions(+), 130 deletions(-) create mode 100644 activerecord/test/connections/native_sqlserver_odbc/connection.rb diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index cbbf33318a..79968297b8 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,9 @@ *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 problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 [flash@vanklinkenbergsoftware.nl] diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 6ceefb5522..57b583b007 100755 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -38,7 +38,7 @@ Rake::TestTask.new("test_mysql_ruby") { |t| 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| t.libs << "test" << "test/connections/native_#{adapter}" t.pattern = "test/*_test{,_#{adapter}}.rb" diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 0060f694a2..69c19cb1fb 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -748,11 +748,7 @@ module ActiveRecord #:nodoc: end def add_limit!(sql, options) - if options[:limit] && options[:offset] - 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 + connection.add_limit_offset!(sql, options) end # Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 2be587dc79..0503dbebea 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -352,21 +352,15 @@ module ActiveRecord # Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database. def structure_dump() end - def add_limit!(sql, limit) - if limit.is_a? Array - limit, offset = *limit - add_limit_with_offset!(sql, limit.to_i, offset.to_i) - else - add_limit_without_offset!(sql, limit) - end + def add_limit!(sql, options) + return unless options + add_limit_offset!(sql, options) end - def add_limit_with_offset!(sql, limit, offset) - sql << " LIMIT #{limit} OFFSET #{offset}" - end - - def add_limit_without_offset!(sql, limit) - sql << " LIMIT #{limit}" + def add_limit_offset!(sql, options) + return if options[:limit].nil? + sql << " LIMIT #{options[:limit]}" + sql << " OFFSET #{options[:offset]}" if options.has_key?(:offset) and !options[:offset].nil? end def initialize_schema_information diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 89633639d3..c11ce3772a 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -172,11 +172,17 @@ module ActiveRecord structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n" end end - - def add_limit_with_offset!(sql, limit, offset) - sql << " LIMIT #{offset}, #{limit}" + + def add_limit_offset!(sql, options) + return if options[:limit].nil? + + if options[:offset].blank? + sql << " LIMIT #{options[:limit]}" + else + sql << " LIMIT #{options[:offset]}, #{options[:limit]}" + end end - + def recreate_database(name) drop_database(name) create_database(name) diff --git a/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb index 03f5b5ee2b..dd5a7eb39f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb @@ -8,26 +8,49 @@ require 'active_record/connection_adapters/abstract_adapter' # Modifications: DeLynn Berry # Date: 3/22/2005 # -# This adapter will ONLY work on Windows systems, since it relies on Win32OLE, which, -# to my knowledge, is only available on Window. +# Modifications (ODBC): Mark Imbriaco +# 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 # the ADO module is *NOT* installed. You will need to get the latest # source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/ -# unzip it, and copy the file src/lib/dbd_ado/ADO.rb to -# X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb (you will need to create -# the ADO directory). Once you've installed that file, you are ready to go. +# unzip it, and copy the file +# src/lib/dbd_ado/ADO.rb +# to +# X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb +# (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: # -# * :host -- Defaults to localhost -# * :username -- Defaults to sa -# * :password -- Defaults to nothing -# * :database -- The name of the database. No default, must be provided. +# * :mode -- ADO or ODBC. Defaults to ADO. +# * :username -- Defaults to sa. +# * :password -- Defaults to empty string. +# +# ADO specific options: +# +# * :host -- Defaults to localhost. +# * :database -- The name of the database. No default, must be provided. +# +# ODBC specific options: # -# I have tested this code on a WindowsXP Pro SP1 system, -# ruby 1.8.2 (2004-07-29) [i386-mswin32], SQL Server 2000. +# * :dsn -- 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 class Base @@ -36,44 +59,47 @@ module ActiveRecord symbolize_strings_in_hash(config) - host = config[:host] - username = config[:username] ? config[:username].to_s : 'sa' - password = config[:password].to_s - - if config.has_key?(:database) - database = config[:database] + mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO' + username = config[:username] ? config[:username].to_s : 'sa' + password = config[:password] ? config[:password].to_s : '' + if mode == "ODBC" + raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn) + dsn = config[:dsn] + conn = DBI.connect("DBI:ODBC:#{dsn}", username, password) 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 - conn = DBI.connect("DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};") conn["AutoCommit"] = true - ConnectionAdapters::SQLServerAdapter.new(conn, logger) end - end + end # class Base module ConnectionAdapters 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) super(name, default, sql_type) - @scale = scale_value @identity = is_identity + @is_special = sql_type =~ /text|ntext|image/i ? true : false + @scale = scale_value end def simplified_type(field_type) case field_type - when /int|bigint|smallint|tinyint/i : :integer - when /float|double|decimal|money|numeric|real|smallmoney/i : @scale == 0 ? :integer : :float - when /datetime|smalldatetime/i : :datetime - when /timestamp/i : :timestamp - when /time/i : :time - when /text|ntext/i : :text - when /binary|image|varbinary/i : :binary - when /char|nchar|nvarchar|string|varchar/i : :string - when /bit/i : :boolean + when /int|bigint|smallint|tinyint/i then :integer + when /float|double|decimal|money|numeric|real|smallmoney/i then @scale == 0 ? :integer : :float + when /datetime|smalldatetime/i then :datetime + when /timestamp/i then :timestamp + when /time/i then :time + when /text|ntext/i then :text + when /binary|image|varbinary/i then :binary + when /char|nchar|nvarchar|string|varchar/i then :string + when /bit/i then :boolean end end @@ -83,28 +109,32 @@ module ActiveRecord when :string then value when :integer then value == true || value == false ? value == true ? '1' : '0' : value.to_i 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 :time then cast_to_time(value) else value 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) return value if value.is_a?(Time) - time_array = ParseDate.parsedate value - time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1; - Time.send Base.default_timezone, *time_array + time_array = ParseDate.parsedate(value) + time_array[0] ||= 2000 + time_array[1] ||= 1 + time_array[2] ||= 1 + Time.send(Base.default_timezone, *time_array) rescue nil end - def guess_date_or_time(value) - (value.hour == 0 and value.min == 0 and value.sec == 0) ? - Date.new(value.year, value.month, value.day) : value + def cast_to_datetime(value) + if value.is_a?(Time) + 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 # 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) value.gsub(/(\r|\n|\0|\x1a)/) do case $1 - when "\r" - "%00" - when "\n" - "%01" - when "\0" - "%02" - when "\x1a" - "%03" + when "\r" then "%00" + when "\n" then "%01" + when "\0" then "%02" + when "\x1a" then "%03" end end end @@ -127,22 +153,17 @@ module ActiveRecord def binary_to_string(value) value.gsub(/(%00|%01|%02|%03)/) do case $1 - when "%00" - "\r" - when "%01" - "\n" - when "%02\0" - "\0" - when "%03" - "\x1a" + when "%00" then "\r" + when "%01" then "\n" + when "%02\0" then "\0" + when "%03" then "\x1a" end end end - end + end #class ColumnWithIdentity < Column class SQLServerAdapter < AbstractAdapter - def native_database_types { :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY", @@ -164,7 +185,6 @@ module ActiveRecord end def select_all(sql, name = nil) - add_limit!(sql, nil) select(sql, name) end @@ -177,9 +197,9 @@ module ActiveRecord 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}'" 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. - # 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) } #result = @connection.select_all(sql) columns = [] @@ -199,7 +219,7 @@ module ActiveRecord execute enable_identity_insert(table_name, true) ii_enabled = true rescue Exception => e - # Coulnd't turn on IDENTITY_INSERT + raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON" end end end @@ -212,7 +232,7 @@ module ActiveRecord begin execute enable_identity_insert(table_name, false) rescue Exception => e - # Couldn't turn off IDENTITY_INSERT + raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF" end end end @@ -288,13 +308,13 @@ module ActiveRecord "[#{name}]" end - def add_limit_with_offset!(sql, limit, offset) - order_by = sql.include?("ORDER BY") ? get_order_by(sql.sub(/.*ORDER\sBY./, "")) : 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]}" - end - - def add_limit_without_offset!(sql, limit) - limit.nil? ? sql : sql.gsub!(/SELECT/i, "SELECT TOP #{limit}") + def add_limit_offset!(sql, options) + if options.has_key?(:limit) and options.has_key?(:offset) and !options[:limit].nil? and !options[:offset].nil? + options[:order] ||= "id ASC" + 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 + sql.gsub!(/SELECT/i, "SELECT TOP #{options[:limit]}") unless options[:limit].nil? + end end def recreate_database(name) @@ -313,6 +333,7 @@ module ActiveRecord private def select(sql, name = nil) rows = [] + repair_special_columns(sql) log(sql, name, @connection) do |conn| conn.select_all(sql) do |row| record = {} @@ -332,8 +353,9 @@ module ActiveRecord end def get_table_name(sql) - if sql =~ /into\s*([^\s]+)\s*/i or - sql =~ /update\s*([^\s]+)\s*/i + if sql =~ /into\s*([^\s]+)\s*|update\s*([^\s]+)\s*/i + $1 + elsif sql =~ /from\s*([^\s]+)\s*/i $1 else nil @@ -345,14 +367,8 @@ module ActiveRecord end def get_identity_column(table_name) - if not @table_columns - @table_columns = {} - end - - if @table_columns[table_name] == nil - @table_columns[table_name] = columns(table_name) - end - + @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| return col.name if col.identity end @@ -361,17 +377,35 @@ module ActiveRecord end def query_contains_identity_column(sql, col) - return sql =~ /[\[.,]\s*#{col}/ + return sql =~ /\[#{col}\]/ end - def get_order_by(sql) - return sql, sql.gsub(/\s*DESC\s*/, "").gsub(/\s*ASC\s*/, " DESC") + def change_order_direction(order) + 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 - def get_offset_amount(limit) - limit = limit.gsub!(/.OFFSET./i, ",").split(',') - return limit[0].to_i, limit[0].to_i+limit[1].to_i + def get_special_columns(table_name) + special = [] + @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 \ No newline at end of file + + def repair_special_columns(sql) + 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 diff --git a/activerecord/test/associations_test.rb b/activerecord/test/associations_test.rb index 0218f7e6c7..e7160c9365 100755 --- a/activerecord/test/associations_test.rb +++ b/activerecord/test/associations_test.rb @@ -902,6 +902,7 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase no_of_devels = Developer.count no_of_projects = Project.count now = Date.today + sqlnow = Time.now.strftime("%Y/%m/%d 00:00:00") ken = Developer.new("name" => "Ken") ken.projects.push_with_attributes( Project.find(1), :joined_on => now ) p = Project.new("name" => "Foomatic") @@ -916,7 +917,13 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase assert_equal 2, ken.projects(true).size 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 def test_build @@ -1004,7 +1011,13 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase end 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 def test_destroy_all @@ -1019,8 +1032,15 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase def test_rich_association jamis = developers(:jamis) 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 - assert_equal Date.today.to_s, developers(: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, + # 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 def test_associations_with_conditions diff --git a/activerecord/test/base_test.rb b/activerecord/test/base_test.rb index 1c5702741e..b6ae403ca0 100755 --- a/activerecord/test/base_test.rb +++ b/activerecord/test/base_test.rb @@ -319,6 +319,10 @@ class BasicsTest < Test::Unit::TestCase end 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 "bulk updated!", Topic.find(1).content assert_equal "bulk updated!", Topic.find(2).content @@ -337,6 +341,10 @@ class BasicsTest < Test::Unit::TestCase end 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 end diff --git a/activerecord/test/binary_test.rb b/activerecord/test/binary_test.rb index d635621cef..ba43524264 100644 --- a/activerecord/test/binary_test.rb +++ b/activerecord/test/binary_test.rb @@ -7,6 +7,13 @@ class BinaryTest < Test::Unit::TestCase end 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 # BLOB data with DB2, because the length of a statement is # limited to 32KB. diff --git a/activerecord/test/connections/native_sqlserver_odbc/connection.rb b/activerecord/test/connections/native_sqlserver_odbc/connection.rb new file mode 100644 index 0000000000..b156a07920 --- /dev/null +++ b/activerecord/test/connections/native_sqlserver_odbc/connection.rb @@ -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 +) diff --git a/activerecord/test/deprecated_finder_test.rb b/activerecord/test/deprecated_finder_test.rb index 27a0a66253..0111cff3ac 100755 --- a/activerecord/test/deprecated_finder_test.rb +++ b/activerecord/test/deprecated_finder_test.rb @@ -112,7 +112,7 @@ class FinderTest < Test::Unit::TestCase assert_equal first_five_developers, Developer.find_all(nil, 'id ASC', [5]) assert_equal no_developers, Developer.find_all(nil, 'id ASC', [0]) end - + def test_find_all_with_limit_and_offset first_three_developers = Developer.find_all nil, 'id ASC', [3, 0] second_three_developers = Developer.find_all nil, 'id ASC', [3, 3] @@ -128,14 +128,13 @@ class FinderTest < Test::Unit::TestCase end 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 - 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 end - protected def bind(statement, *vars) if vars.first.is_a?(Hash) diff --git a/activerecord/test/fixtures/db_definitions/sqlserver.drop.sql b/activerecord/test/fixtures/db_definitions/sqlserver.drop.sql index efa98fe500..226544392d 100644 --- a/activerecord/test/fixtures/db_definitions/sqlserver.drop.sql +++ b/activerecord/test/fixtures/db_definitions/sqlserver.drop.sql @@ -21,3 +21,5 @@ DROP TABLE authors; DROP TABLE tasks; DROP TABLE categories; DROP TABLE categories_posts; +DROP TABLE fk_test_has_pd; +DROP TABLE fk_test_has_fk; diff --git a/activerecord/test/fixtures/db_definitions/sqlserver.sql b/activerecord/test/fixtures/db_definitions/sqlserver.sql index 9c5658e387..1177b37035 100644 --- a/activerecord/test/fixtures/db_definitions/sqlserver.sql +++ b/activerecord/test/fixtures/db_definitions/sqlserver.sql @@ -2,7 +2,7 @@ CREATE TABLE accounts ( id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, firm_id int default NULL, credit_limit int default NULL -) +); CREATE TABLE companies ( id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, @@ -12,7 +12,7 @@ CREATE TABLE companies ( name varchar(50) default NULL, client_of int default NULL, rating int default 1 -) +); CREATE TABLE topics ( id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, @@ -27,7 +27,7 @@ CREATE TABLE topics ( replies_count int default 0, parent_id int default NULL, type varchar(50) default NULL -) +); CREATE TABLE developers ( id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, diff --git a/activerecord/test/inheritance_test.rb b/activerecord/test/inheritance_test.rb index 3ac29b73c8..dbbbb6eee7 100755 --- a/activerecord/test/inheritance_test.rb +++ b/activerecord/test/inheritance_test.rb @@ -6,7 +6,15 @@ class InheritanceTest < Test::Unit::TestCase fixtures :companies, :projects 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')" + #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) } end diff --git a/activerecord/test/validations_test.rb b/activerecord/test/validations_test.rb index 2ff618b0e6..576d1f6c3d 100755 --- a/activerecord/test/validations_test.rb +++ b/activerecord/test/validations_test.rb @@ -707,7 +707,7 @@ class ValidationsTest < Test::Unit::TestCase def test_validates_numericality_of 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| - 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" # we cannot check this as approved is actually an integer field #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 Topic.validates_numericality_of( :approved, :only_integer => true, :allow_nil => true ) ["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_equal((v.nil? or v == "")? nil : v.to_i, t.approved) end -- cgit v1.2.3