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:
parent
64612db7cf
commit
f2a29ca43c
15 changed files with 234 additions and 130 deletions
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -173,8 +173,14 @@ module ActiveRecord
|
|||
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)
|
||||
|
|
|
@ -8,26 +8,49 @@ require 'active_record/connection_adapters/abstract_adapter'
|
|||
# Modifications: DeLynn Berry <delynnb@megastarfinancial.com>
|
||||
# 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 <mark.imbriaco@pobox.com>
|
||||
# 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 <tt>src/lib/dbd_ado/ADO.rb</tt> to
|
||||
# <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt> (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
|
||||
# <tt>src/lib/dbd_ado/ADO.rb</tt>
|
||||
# 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:
|
||||
#
|
||||
# * <tt>:host</tt> -- Defaults to localhost
|
||||
# * <tt>:username</tt> -- Defaults to sa
|
||||
# * <tt>:password</tt> -- Defaults to nothing
|
||||
# * <tt>:mode</tt> -- ADO or ODBC. Defaults to ADO.
|
||||
# * <tt>:username</tt> -- Defaults to sa.
|
||||
# * <tt>:password</tt> -- Defaults to empty string.
|
||||
#
|
||||
# ADO specific options:
|
||||
#
|
||||
# * <tt>:host</tt> -- Defaults to localhost.
|
||||
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
||||
#
|
||||
# I have tested this code on a WindowsXP Pro SP1 system,
|
||||
# ruby 1.8.2 (2004-07-29) [i386-mswin32], SQL Server 2000.
|
||||
# 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
|
||||
class Base
|
||||
|
@ -36,44 +59,47 @@ module ActiveRecord
|
|||
|
||||
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'
|
||||
password = config[:password].to_s
|
||||
|
||||
if config.has_key?(:database)
|
||||
database = config[:database]
|
||||
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]}"
|
||||
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
|
||||
|
||||
def add_limit_without_offset!(sql, limit)
|
||||
limit.nil? ? sql : sql.gsub!(/SELECT/i, "SELECT TOP #{limit}")
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
@ -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,8 +917,14 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
|
|||
assert_equal 2, ken.projects(true).size
|
||||
|
||||
kenReloaded = Developer.find_by_name 'Ken'
|
||||
# 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
|
||||
devel = Developer.find(1)
|
||||
|
@ -1004,8 +1011,14 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_additional_columns_from_join_table
|
||||
# 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
|
||||
david = Developer.find(1)
|
||||
|
@ -1019,9 +1032,16 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
|
|||
def test_rich_association
|
||||
jamis = developers(:jamis)
|
||||
jamis.projects.push_with_attributes(projects(:action_controller), :joined_on => Date.today)
|
||||
# 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
|
||||
assert_equal 2, projects(:active_record).developers.size
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue