Skip to content

Commit e67df49

Browse files
committed
Added a #enable_default_unicode_types class attribute access to make all new added or changed string types like :string/:text default to unicode/national data types. See the README for full details. Added a rake task that assists setting this to true when running tests.
1 parent c2e8c63 commit e67df49

File tree

7 files changed

+147
-13
lines changed

7 files changed

+147
-13
lines changed

CHANGELOG

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11

22
MASTER
33

4-
*
4+
* Added a #enable_default_unicode_types class attribute access to make all new added or changed string types
5+
like :string/:text default to unicode/national data types. See the README for full details. Added a rake
6+
task that assists setting this to true when running tests. [Ken Collins]
57

68

79
* 2.2.6 (January 8th, 2008)

README.rdoc

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The SQL Server adapter for rails is back for ActiveRecord 2.2 and up! We are cur
1212
* Implementation for #disable_referential_integrity used by ActiveRecord's Fixtures class.
1313
* Pessimistic locking suppot. See the #add_lock! method for details.
1414
* Enabled #case_sensitive_equality_operator used by unique validations.
15-
* Unicode character support for nchar, nvarchar and ntext data types.
15+
* Unicode character support for nchar, nvarchar and ntext data types. Configuration option for defaulting all string data types to the unicode safe types.
1616
* View support for table names, identity inserts, and column defaults.
1717

1818
==== Date/Time Data Type Hinting
@@ -51,23 +51,44 @@ For example:
5151
Manually creating a varchar(max) on SQL Server 2005 is not necessary since this is the default type created when specifying a :text field. As time goes on we will be testing other SQL Server specific data types are handled correctly when created in a migration.
5252

5353

54-
==== Native Text/Binary Data Type Accessor
54+
==== Native Text/String/Binary Data Type Accessor
5555

5656
To pass the ActiveRecord tests we had to implement an class accessor for the native type created for :text columns. By default any :text column created by migrations will create these native types.
5757

5858
* SQL Server 2000 is 'text'
5959
* SQL Server 2005 is 'varchar(max)'
6060

61-
During testing this type is set to 'varchar(8000)' for both versions. The reason is that rails expects to be able to use SQL = operators on text data types and this is not possible with a native 'text' data type in SQL Server. The default 'varchar(max)' for SQL Server 2005 can be queried using the SQL = operator and has plenty of storage space which is why we made it the default for 2005. If for some reason you want to change the data type created during migrations for any SQL Server version, you can configure this line to your liking in a config/initializers file.
61+
During testing this type is set to 'varchar(8000)' for SQL Server 2000. The reason is that rails expects to be able to use SQL = operators on text data types and this is not possible with a native 'text' data type in SQL Server. The default 'varchar(max)' for SQL Server 2005 can be queried using the SQL = operator and has plenty of storage space which is why we made it the default for 2005. If for some reason you want to change the data type created during migrations for any SQL Server version, you can configure this line to your liking in a config/initializers file.
6262

6363
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = 'varchar(8000)'
64+
65+
Also, there is a class attribute setter for the native string database type. This is the same for both SQL Server 2000 and 2005, 'varchar'. However in can be used instead of the #enable_default_unicode_types below for finer grain control over which types you want unicode safe when adding or changing the schema.
66+
67+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = 'nvarchar'
6468

6569
By default any :binary column created by migrations will create these native types
6670

6771
* SQL Server 2000 is 'image'
6872
* SQL Server 2005 is 'varbinary(max)'
6973

7074

75+
==== Setting Unicode Types As Default
76+
77+
By default the adapter will use non-unicode safe data types for :string and :text types when DEFINING or CHANGING the schema. If you choose, you can set the following class attribute in a config/initializers file that will change this behavior. When set to true it has the equivalent meaning as the two lower items. These examples show detail level alternatives to achieve similar effects.
78+
79+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = true
80+
81+
# SQL Server 2000
82+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = 'ntext'
83+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = 'nvarchar'
84+
85+
# SQL Server 2005
86+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = 'nvarchar(max)'
87+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = 'nvarchar'
88+
89+
It is important to remember that unicode types in SQL Server have approximately half the storage capacity as their counter parts. So where a normal string would max out at (8000) a unicode string will top off at (4000).
90+
91+
7192
==== Schema Information Logging
7293

7394
By default all queries to the INFORMATION_SCHEMA table is silenced. If you think logging these queries are useful, you can enable it by adding this like to a config/initializers file.

Rakefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ task :recreate_databases => [:drop_databases, :create_databases]
2727

2828

2929
for adapter in %w( sqlserver sqlserver_odbc )
30+
3031
Rake::TestTask.new("test_#{adapter}") { |t|
3132
t.libs << "test"
3233
t.libs << "test/connections/native_#{adapter}"
@@ -40,5 +41,12 @@ for adapter in %w( sqlserver sqlserver_odbc )
4041
namespace adapter do
4142
task :test => "test_#{adapter}"
4243
end
44+
4345
end
4446

47+
desc 'Test with unicode types enabled.'
48+
Rake::TestTask.new(:test_unicode_types) do |t|
49+
ENV['ENABLE_DEFAULT_UNICODE_TYPES'] = 'true'
50+
test = Rake::Task['test_sqlserver_odbc']
51+
test.invoke
52+
end

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ class SQLServerAdapter < AbstractAdapter
155155
SUPPORTED_VERSIONS = [2000,2005].freeze
156156
LIMITABLE_TYPES = ['string','integer','float','char','nchar','varchar','nvarchar'].freeze
157157

158-
cattr_accessor :native_text_database_type, :native_binary_database_type, :log_info_schema_queries
158+
cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
159+
:log_info_schema_queries, :enable_default_unicode_types
159160

160161
class << self
161162

@@ -216,12 +217,21 @@ def inspect
216217
"#<#{self.class} version: #{version}, year: #{database_year}, connection_options: #{@connection_options.inspect}>"
217218
end
218219

220+
def native_string_database_type
221+
@@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
222+
end
223+
219224
def native_text_database_type
220-
self.class.native_text_database_type || (sqlserver_2005? ? 'varchar(max)' : 'text')
225+
@@native_text_database_type ||
226+
if sqlserver_2005?
227+
enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
228+
else
229+
enable_default_unicode_types ? 'ntext' : 'text'
230+
end
221231
end
222232

223233
def native_binary_database_type
224-
self.class.native_binary_database_type || (sqlserver_2005? ? 'varbinary(max)' : 'image')
234+
@@native_binary_database_type || (sqlserver_2005? ? 'varbinary(max)' : 'image')
225235
end
226236

227237
# QUOTING ==================================================#
@@ -426,16 +436,16 @@ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
426436
def native_database_types
427437
{
428438
:primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
429-
:string => { :name => "varchar", :limit => 255 },
430-
:text => { :name => native_text_database_type },
439+
:string => { :name => native_string_database_type, :limit => 255 },
440+
:text => { :name => native_text_database_type },
431441
:integer => { :name => "int", :limit => 4 },
432442
:float => { :name => "float", :limit => 8 },
433443
:decimal => { :name => "decimal" },
434444
:datetime => { :name => "datetime" },
435445
:timestamp => { :name => "datetime" },
436446
:time => { :name => "datetime" },
437447
:date => { :name => "datetime" },
438-
:binary => { :name => native_binary_database_type },
448+
:binary => { :name => native_binary_database_type },
439449
:boolean => { :name => "bit"},
440450
# These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
441451
:char => { :name => 'char' },

test/cases/adapter_test_sqlserver.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,35 @@ def setup
194194

195195
end
196196

197+
context 'testing #enable_default_unicode_types configuration' do
198+
199+
should 'use non-unicode types when set to false' do
200+
with_enable_default_unicode_types(false) do
201+
if sqlserver_2000?
202+
assert_equal 'varchar', @connection.native_string_database_type
203+
assert_equal 'text', @connection.native_text_database_type
204+
elsif sqlserver_2005?
205+
assert_equal 'varchar', @connection.native_string_database_type
206+
assert_equal 'varchar(max)', @connection.native_text_database_type
207+
end
208+
end
209+
end
210+
211+
should 'use unicode types when set to true' do
212+
with_enable_default_unicode_types(true) do
213+
if sqlserver_2000?
214+
assert_equal 'nvarchar', @connection.native_string_database_type
215+
assert_equal 'ntext', @connection.native_text_database_type
216+
elsif sqlserver_2005?
217+
assert_equal 'nvarchar', @connection.native_string_database_type
218+
assert_equal 'nvarchar(max)', @connection.native_text_database_type
219+
end
220+
end
221+
end
222+
223+
end
224+
225+
197226
end
198227

199228
context 'For chronic data types' do
@@ -530,6 +559,20 @@ def order_to_min_set(order)
530559
@connection.send :order_to_min_set, order
531560
end
532561

562+
def with_enable_default_unicode_types(setting)
563+
old_setting = ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types
564+
old_text = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type
565+
old_string = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type
566+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = setting
567+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = nil
568+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = nil
569+
yield
570+
ensure
571+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = old_setting
572+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = old_text
573+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = old_string
574+
end
575+
533576
end
534577

535578

test/cases/migration_test_sqlserver.rb

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ class MigrationTest < ActiveRecord::TestCase
4040

4141
include SqlserverCoercedTest
4242

43-
4443
def test_coerced_test_add_column_not_null_without_default
4544
Person.connection.create_table :testings do |t|
4645
t.column :foo, :string
@@ -53,5 +52,42 @@ def test_coerced_test_add_column_not_null_without_default
5352
Person.connection.drop_table :testings rescue nil
5453
end
5554

55+
end
56+
57+
class ChangeTableMigrationsTest < ActiveRecord::TestCase
58+
59+
COERCED_TESTS = [:test_string_creates_string_column]
60+
61+
include SqlserverCoercedTest
62+
63+
def setup
64+
@connection = Person.connection
65+
@connection.create_table :delete_me, :force => true do |t|
66+
end
67+
end
68+
69+
def teardown
70+
@connection.drop_table :delete_me rescue nil
71+
end
72+
73+
def test_coerced_string_creates_string_column
74+
with_sqlserver_change_table do |t|
75+
@connection.expects(:add_column).with(:delete_me, :foo, sqlserver_string_column, {})
76+
@connection.expects(:add_column).with(:delete_me, :bar, sqlserver_string_column, {})
77+
t.string :foo, :bar
78+
end
79+
end
80+
81+
protected
82+
83+
def with_sqlserver_change_table
84+
@connection.change_table :delete_me do |t|
85+
yield t
86+
end
87+
end
88+
89+
def sqlserver_string_column
90+
"#{@connection.native_string_database_type}(255)"
91+
end
5692

5793
end

test/cases/sqlserver_helper.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,30 @@ def coerced_tests
4747
self.const_get(:COERCED_TESTS) rescue nil
4848
end
4949
def method_added(method)
50-
undef_method(method) if coerced_tests && coerced_tests.include?(method)
50+
if coerced_tests && coerced_tests.include?(method)
51+
undef_method(method)
52+
STDOUT.puts("Undefined coerced test: #{self.name}##{method}")
53+
end
5154
end
5255
end
5356
end
5457

58+
# Set weather to test unicode string defaults or not. Used from rake task.
59+
60+
if ENV['ENABLE_DEFAULT_UNICODE_TYPES'] == 'true'
61+
puts "With enabled unicode string types"
62+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = true
63+
end
64+
5565
# Change the text database type to support ActiveRecord's tests for = on text columns which
5666
# is not supported in SQL Server text columns, so use varchar(8000) instead.
5767

5868
if ActiveRecord::Base.connection.sqlserver_2000?
59-
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = 'varchar(8000)'
69+
if ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types
70+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = 'nvarchar(4000)'
71+
else
72+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = 'varchar(8000)'
73+
end
6074
end
6175

6276
# Our changes/additions to ActiveRecord test helpers specific for SQL Server.

0 commit comments

Comments
 (0)