Skip to content

Commit 0dba8b0

Browse files
committed
[Rails3] Revert back to using our own coerced tests. Allow case_sensitive_equality_operator to be configurable. Reorganize rake file. Quote pretty much all strings to the DB with N'...' prefix.
1 parent a6a9719 commit 0dba8b0

File tree

10 files changed

+124
-77
lines changed

10 files changed

+124
-77
lines changed

Rakefile

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,37 @@ require 'rake/testtask'
33
require 'rake/rdoctask'
44

55

6-
desc 'Default runs tests for the adapters ODBC mode.'
7-
task :test do
8-
test = Rake::Task['sqlserver:test:odbc']
9-
test.invoke
10-
end
11-
6+
task :test => ['test:odbc']
127

13-
namespace :sqlserver do
8+
namespace :test do
149

15-
namespace :test do
16-
17-
['odbc','adonet'].each do |mode|
18-
19-
Rake::TestTask.new(mode) do |t|
20-
t.libs << "test"
21-
t.libs << "test/connections/native_sqlserver#{mode == 'adonet' ? '' : "_#{mode}"}"
22-
t.libs << "#{ENV['RAILS_SOURCE']}/activerecord/test"
23-
t.test_files = \
24-
Dir.glob("test/cases/**/*_test_sqlserver.rb").sort +
25-
(Dir.glob("#{ENV['RAILS_SOURCE']}/activerecord/test/cases/**/*_test.rb") -
26-
Dir.glob("#{ENV['RAILS_SOURCE']}/activerecord/test/cases/adapters/**/*_test.rb")).sort
27-
t.verbose = true
28-
end
29-
30-
end
10+
['odbc','adonet'].each do |mode|
3111

32-
desc 'Test with unicode types enabled, uses ODBC mode.'
33-
task :unicode_types do
12+
Rake::TestTask.new(mode) do |t|
3413
ENV['ENABLE_DEFAULT_UNICODE_TYPES'] = 'true'
35-
test = Rake::Task['sqlserver:test:odbc']
36-
test.invoke
14+
t.libs << "test"
15+
t.libs << "test/connections/native_sqlserver#{mode == 'adonet' ? '' : "_#{mode}"}"
16+
t.libs << "#{ENV['RAILS_SOURCE']}/activerecord/test"
17+
t.test_files = \
18+
Dir.glob("test/cases/**/*_test_sqlserver.rb").sort +
19+
(Dir.glob("#{ENV['RAILS_SOURCE']}/activerecord/test/cases/**/*_test.rb") -
20+
Dir.glob("#{ENV['RAILS_SOURCE']}/activerecord/test/cases/adapters/**/*_test.rb")).sort
21+
t.verbose = true
3722
end
3823

3924
end
25+
26+
desc 'Test without unicode types enabled, uses ODBC mode.'
27+
task :non_unicode_types do
28+
ENV['ENABLE_DEFAULT_UNICODE_TYPES'] = 'false'
29+
test = Rake::Task['test:odbc']
30+
test.invoke
31+
end
4032

4133
end
4234

4335

36+
4437
namespace :rvm do
4538

4639
RUBIES = {
@@ -105,7 +98,7 @@ namespace :rvm do
10598
RVM.run "curl -O http://www.ch-werner.de/rubyodbc/#{odbc}.tar.gz"
10699
puts "info: RubyODBC extracting clean work directory..."
107100
RVM.run "tar -xf #{odbc}.tar.gz"
108-
RVM.chdir("#{odbc}/ext") do
101+
RVM.chdir("#{odbc}/ext/utf8") do
109102
puts "info: RubyODBC configuring..."
110103
RVM.ruby 'extconf.rb', "--with-odbc-dir=#{rvm_odbc_dir}"
111104
puts "info: RubyODBC make and installing for #{rvm_current_name}..."

lib/active_record/connection_adapters/sqlserver/database_statements.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def empty_insert_statement_value
5151
end
5252

5353
def case_sensitive_equality_operator
54-
"COLLATE Latin1_General_CS_AS ="
54+
cs_equality_operator
5555
end
5656

5757
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)

lib/active_record/connection_adapters/sqlserver/quoting.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def quote(value, column = nil)
88
when String, ActiveSupport::Multibyte::Chars
99
if column && column.type == :binary
1010
column.class.string_to_binary(value)
11-
elsif column && column.respond_to?(:is_utf8?) && column.is_utf8?
11+
elsif quote_value_as_utf8?(value) || column && column.respond_to?(:is_utf8?) && column.is_utf8?
1212
quoted_utf8_value(value)
1313
else
1414
super
@@ -51,6 +51,10 @@ def quoted_utf8_value(value)
5151
"N'#{quote_string(value)}'"
5252
end
5353

54+
def quote_value_as_utf8?(value)
55+
value.is_utf8? || enable_default_unicode_types
56+
end
57+
5458
end
5559
end
5660
end

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ class SQLServerAdapter < AbstractAdapter
171171
SUPPORTED_VERSIONS = [2005,2008].freeze
172172

173173
cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
174-
:log_info_schema_queries, :enable_default_unicode_types, :auto_connect
174+
:log_info_schema_queries, :enable_default_unicode_types, :auto_connect,
175+
:cs_equality_operator
175176

176177
def initialize(logger,config)
177178
@connection_options = config
@@ -310,6 +311,10 @@ def native_binary_database_type
310311
@@native_binary_database_type || 'varbinary(max)'
311312
end
312313

314+
def cs_equality_operator
315+
@@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS ='
316+
end
317+
313318

314319
protected
315320

test/cases/adapter_test_sqlserver.rb

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -585,24 +585,6 @@ def setup
585585

586586
end
587587

588-
589-
590-
private
591-
592-
def with_enable_default_unicode_types(setting)
593-
old_setting = ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types
594-
old_text = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type
595-
old_string = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type
596-
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = setting
597-
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = nil
598-
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = nil
599-
yield
600-
ensure
601-
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = old_setting
602-
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = old_text
603-
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = old_string
604-
end
605-
606588
end
607589

608590

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
require 'cases/sqlserver_helper'
22
require 'models/developer'
3+
require 'models/topic'
34

45
class AttributeMethodsTestSqlserver < ActiveRecord::TestCase
56
end
67

78
class AttributeMethodsTest < ActiveRecord::TestCase
89

9-
COERCED_TESTS = [:test_read_attributes_before_type_cast_on_datetime]
10+
COERCED_TESTS = [
11+
:test_read_attributes_before_type_cast_on_datetime,
12+
:test_typecast_attribute_from_select_to_false,
13+
:test_typecast_attribute_from_select_to_true
14+
]
1015

1116
include SqlserverCoercedTest
1217

1318
fixtures :developers
1419

15-
def test_coerced_test_read_attributes_before_type_cast_on_datetime
20+
def test_coerced_read_attributes_before_type_cast_on_datetime
1621
developer = Developer.find(:first)
1722
assert_equal "#{developer.created_at.to_s(:db)}.000" , developer.attributes_before_type_cast["created_at"]
1823
end
1924

25+
def test_coerced_typecast_attribute_from_select_to_false
26+
topic = Topic.create(:title => 'Budget')
27+
topic = Topic.find(:first, :select => "topics.*, CASE WHEN 1=2 THEN 1 ELSE 0 END as is_test")
28+
assert !topic.is_test?
29+
end
30+
31+
def test_coerced_typecast_attribute_from_select_to_true
32+
topic = Topic.create(:title => 'Budget')
33+
topic = Topic.find(:first, :select => "topics.*, CASE WHEN 2=2 THEN 1 ELSE 0 END as is_test")
34+
assert topic.is_test?
35+
end
36+
2037

2138
end

test/cases/execute_procedure_test_sqlserver.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ def setup
2121
end
2222

2323
should 'quote bind vars correctly' do
24-
assert_sql(/EXEC sp_tables '%sql_server%', NULL, NULL, NULL, 1/) do
24+
regex = if quote_values_as_utf8?
25+
/EXEC sp_tables N'%sql_server%', NULL, NULL, NULL, 1/
26+
else
27+
/EXEC sp_tables '%sql_server%', NULL, NULL, NULL, 1/
28+
end
29+
assert_sql(regex) do
2530
@klass.execute_procedure :sp_tables, '%sql_server%', nil, nil, nil, true
2631
end
2732
end

test/cases/schema_dumper_test_sqlserver.rb

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'cases/sqlserver_helper'
2+
require 'stringio'
23

34
class SchemaDumperTestSqlserver < ActiveRecord::TestCase
45

@@ -46,8 +47,6 @@ class SchemaDumperTestSqlserver < ActiveRecord::TestCase
4647
end
4748

4849

49-
50-
5150
private
5251

5352
def find_all_tables
@@ -70,3 +69,19 @@ def table_dump(*table_names)
7069
end
7170

7271
end
72+
73+
74+
class SchemaDumperTest < ActiveRecord::TestCase
75+
76+
COERCED_TESTS = [:test_schema_dump_keeps_large_precision_integer_columns_as_decimal]
77+
78+
include SqlserverCoercedTest
79+
80+
def test_coerced_schema_dump_keeps_large_precision_integer_columns_as_decimal
81+
output = standard_dump
82+
assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 38,\s+:scale => 0}, output
83+
end
84+
85+
end
86+
87+

test/cases/sqlserver_helper.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def coerced_tests
5858
end
5959
def method_added(method)
6060
if coerced_tests && coerced_tests.include?(method)
61-
undef_method(method)
61+
undef_method(method) rescue nil
6262
STDOUT.puts("Undefined coerced test: #{self.name}##{method}")
6363
end
6464
end
@@ -94,6 +94,7 @@ class << self
9494
def sqlserver_2005? ; ActiveRecord::Base.connection.sqlserver_2005? ; end
9595
def sqlserver_2008? ; ActiveRecord::Base.connection.sqlserver_2008? ; end
9696
def ruby_19? ; RUBY_VERSION >= '1.9' ; end
97+
def quote_values_as_utf8? ; ActiveRecord::Base.connection.quote_value_as_utf8?('') ; end
9798
end
9899
def assert_sql(*patterns_to_match)
99100
$queries_executed = []
@@ -108,6 +109,23 @@ def assert_sql(*patterns_to_match)
108109
def sqlserver_2005? ; self.class.sqlserver_2005? ; end
109110
def sqlserver_2008? ; self.class.sqlserver_2008? ; end
110111
def ruby_19? ; self.class.ruby_19? ; end
112+
def quote_values_as_utf8? ; self.class.quote_values_as_utf8? ; end
113+
def with_enable_default_unicode_types?
114+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types.is_a?(TrueClass)
115+
end
116+
def with_enable_default_unicode_types(setting)
117+
old_setting = ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types
118+
old_text = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type
119+
old_string = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type
120+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = setting
121+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = nil
122+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = nil
123+
yield
124+
ensure
125+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = old_setting
126+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = old_text
127+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = old_string
128+
end
111129
end
112130
end
113131

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,44 @@
11
# encoding: utf-8
22
require 'cases/sqlserver_helper'
3+
require 'models/event'
4+
5+
class Event < ActiveRecord::Base
6+
before_validation :strip_mb_chars_for_sqlserver
7+
protected
8+
def strip_mb_chars_for_sqlserver
9+
self.title = title.mb_chars.to(4).to_s if title && title.is_utf8?
10+
end
11+
end
312

413
class UniquenessValidationTestSqlserver < ActiveRecord::TestCase
514
end
615

716
class UniquenessValidationTest < ActiveRecord::TestCase
817

9-
# COERCED_TESTS = [:test_validate_uniqueness_with_limit_and_utf8]
18+
COERCED_TESTS = [:test_validate_uniqueness_with_limit_and_utf8]
1019

11-
# include SqlserverCoercedTest
20+
include SqlserverCoercedTest
1221

13-
# This test is tricky to pass. The validation SQL would generate something like this:
14-
#
15-
# SELECT TOP 1 [events].id FROM [events] WHERE ([events].[title] COLLATE Latin1_General_CS_AS = '一二三四五')
16-
#
17-
# The problem is that we can not change the adapters quote method from this:
18-
#
19-
# elsif column && column.respond_to?(:is_utf8?) && column.is_utf8?
20-
# quoted_utf8_value(value)
22+
# I guess most databases just truncate a string when inserting. To pass this test we do a few things.
23+
# First, we make sure the type is unicode safe, second we extend the limit to well beyond what is
24+
# needed. At the top we make sure to auto truncate the :title string like other databases would do
25+
# automatically.
2126
#
22-
# To something like this for all quoting like blind bind vars:
23-
#
24-
# elsif value.is_utf8?
25-
# quoted_utf8_value(value)
26-
#
27-
# As it would cause way more errors, sure this piggybacks on ActiveSupport's 1.8/1.9 abstract
28-
# code to infer if the passed in string is indeed a national/unicde type. Perhaps in rails 3
29-
# and using AREL this might get better, but I do not see a solution right now.
30-
#
31-
# def test_coerced_test_validate_uniqueness_with_limit_and_utf8
32-
# assert true
33-
# end
27+
# "一二三四五".mb_chars.size # => 5
28+
# "一二三四五六七八".mb_chars.size # => 8
29+
# "一二三四五六七八".mb_chars.to(4).to_s # => "一二三四五"
30+
31+
def test_coerced_validate_uniqueness_with_limit_and_utf8
32+
with_kcode('UTF8') do
33+
Event.connection.change_column :events, :title, :nvarchar, :limit => 30
34+
Event.reset_column_information
35+
# Now the actual test copied from core.
36+
e1 = Event.create(:title => "一二三四五")
37+
assert e1.valid?, "Could not create an event with a unique, 5 character title"
38+
e2 = Event.create(:title => "一二三四五六七八")
39+
assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
40+
end
41+
end
3442

3543
end
3644

0 commit comments

Comments
 (0)