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*
|
*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]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue