rails-sqlserver-2000-2005-adapter 1.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.rdoc CHANGED
@@ -1,36 +1,117 @@
1
1
 
2
2
  == Rails SQL Server 2000 & 2005 Adapter
3
3
 
4
- For Rails/ActiveRecord 2.2 and up. More details to come.
4
+ The SQL Server adapter for rails is back for ActiveRecord 2.2 and up! We are currently passing all tests and hope to continue to do so moving forward.
5
+
6
+
7
+ == What's New
8
+
9
+ * Enabled support for DDL transactions.
10
+ * Micro second support. Time#usec is automatically converted to SQL Server's 3.33 millisecond limitation.
11
+ * Datetime data types before type casting are represented correctly. For example: 1998-01-01 23:59:59.997
12
+ * Implementation for #disable_referential_integrity used by ActiveRecord's Fixtures class.
13
+ * Pessimistic locking suppot. See the #add_lock! method for details.
14
+ * Enabled #case_sensitive_equality_operator used by unique validations.
15
+ * Unicode character support for nchar, nvarchar and ntext data types.
16
+
17
+ ==== Date/Time Data Type Hinting
18
+
19
+ Both SQL Server 2000 and 2005 do not have native data types for just 'date' or 'time', it only has 'datetime'. To pass the ActiveRecord tests we implemented two simple class methods that can teach your models to coerce column information to be cast correctly. Simply past a list of symbols to either the <tt>coerce_sqlserver_date</tt> or <tt>coerce_sqlserver_time</tt> methods that correspond to 'datetime' columns that need to be cast correctly.
20
+
21
+ class Topic < ActiveRecord::Base
22
+ coerce_sqlserver_date :last_read
23
+ coerce_sqlserver_time :bonus_time
24
+ end
25
+
26
+ This implementation has some limitations. To date we can only coerce date/time types for models that conform to the expected ActiveRecord class to table naming convention. So a table of 'foo_bar_widgets' will look for coerced types in the FooBarWidget class if it exists.
27
+
28
+ ==== Native Data Type Support
29
+
30
+ Currently the following custom data types have been tested for schema definitions.
31
+
32
+ * char
33
+ * nchar
34
+ * nvarchar
35
+ * ntext
36
+ * varchar(max) for SQL Server 2005 only.
37
+ * nvarchar(max) for SQL Server 2005 only.
38
+
39
+ For example:
40
+
41
+ create_table :sql_server_custom_types, :force => true do |t|
42
+ t.column :ten_code, :char, :limit => 10
43
+ t.column :ten_code_utf8, :nchar, :limit => 10
44
+ t.column :title_utf8, :nvarchar
45
+ t.column :body, :varchar_max # Creates varchar(max)
46
+ t.column :body_utf8, :ntext
47
+ t.column :body2_utf8, :nvarchar_max # Creates nvarchar(max)
48
+ end
49
+
50
+ 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.
51
+
52
+
53
+ ==== Native Text/Binary Data Type Accessor
54
+
55
+ 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.
56
+
57
+ * SQL Server 2000 is 'text'
58
+ * SQL Server 2005 is 'varchar(max)'
59
+
60
+ 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 include this line in your environment.rb file.
61
+
62
+ ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = 'varchar(8000)'
63
+
64
+ By default any :binary column created by migrations will create these native types
65
+
66
+ * SQL Server 2000 is 'image'
67
+ * SQL Server 2005 is 'varbinary(max)'
68
+
69
+
70
+ == Versions
71
+
72
+ It is our goal to match the adapter version with each version of rails. However we will track our own tiny version independent of ActiveRecord. For example, an adapter version of 2.2.x will work on any 2.2.x version of ActiveRecord. This convention will be used in both the Git tags as well as the Rubygems versioning.
5
73
 
6
74
 
7
75
  == Installation
8
-
9
- This method is unconfirmed. You can install the adapter as a gem using the following command. Once I confirm this I can give you an example of a config.gem command too. For now I know that rails and/or ActiveRecord expects to find the SQL Server adapter in the vendor/plugins/adapters/sqlserver folder of your rails project.
10
76
 
11
- $ sudo gem install rails-sqlserver-2000-2005-adapter
77
+ First, you will need Ruby DBI and Ruby ODBC. To my knowledge the ADO DBD for DBI is no longer supported. The installation below is not a comprehensive walk thru on how to get all the required moving parts like FreeTDS installed and/or configured. It will also assume gem installations of both the dependent libraries and the adapter itself.
12
78
 
13
- Ruby DBI is required and to my knowledge the ADO DBD driver is no longer supported, meaning that ODBC is the only way to go. During development ancient versions of DBI back to 0.0.23 were tested along with the current latest 0.4.0 version. Because later versions of DBI will be changing many things, IT IS HIGHLY RECOMMENDED that you install 0.4.0 which the examples below show. This is not a compressive how to since ODBC mode requires also that you install Ruby ODBC and possibly FreeTDS.
79
+ It should be noted that this version of the adapter was developed using both the ancient 0.0.23 version of DBI up to the current stable release of 0.4.0. Because later versions of DBI will be changing many things, IT IS HIGHLY RECOMMENDED that you max your install to version 0.4.0 which the examples below show. For the time being we are not supporting DBI versions higher than 0.4.0. The good news is that if you were using a very old DBI with ADO, technically this adapter will still work for you, but be warned your path is getting old and may not be supported for long.
14
80
 
15
- $ sudo gem install dbi --version 0.4.0
16
- $ sudo gem install dbd-odbc --version 0.2.4
81
+ $ gem install dbi --version 0.4.0
82
+ $ gem install dbd-odbc --version 0.2.4
83
+ $ gem install rails-sqlserver-2000-2005-adapter
17
84
 
18
85
  Optionally configure your gem dependencies in your rails environment.rb file.
19
86
 
20
87
  config.gem 'dbi', :version => '0.4.0'
21
88
  config.gem 'dbd-odbc', :version => '0.2.4', :lib => 'dbd/ODBC'
89
+ config.gem 'rails-sqlserver-2000-2005-adapter', :lib => 'active_record/connection_adapters/sqlserver_adapter'
90
+
91
+ Here are some external links for libraries and/or tutorials on how to install any other additional components to use this adapter. If you know of a good one that we can include here, just let us know.
92
+
93
+ * http://ruby-dbi.sourceforge.net/
94
+ * http://www.ch-werner.de/rubyodbc/
22
95
 
23
96
 
24
97
  == Contributing
25
98
 
26
- Please read the RUNNING_UNIT_TESTS file for the details of how to run the unit tests. Bugs can be reported to our lighthouse page at http://rails-sqlserver.lighthouseapp.com/projects/20277-sql-server-05-adapter/overview
99
+ If you’d like to contribute a feature or bugfix, thanks! To make sure your fix/feature has a high chance of being added, please read the following guidelines. First, ask on the Google list, IRC, or post a ticket in Lighthouse. Second, make sure there are tests! We will not accept any patch that is not tested. Please read the RUNNING_UNIT_TESTS file for the details of how to run the unit tests.
27
100
 
28
- There is also a #rails-sqlserver channel on irc.freenode.net if you want to discuss any topics about the adapter.
101
+ * Lighthouse: http://rails-sqlserver.lighthouseapp.com/projects/20277-sql-server-05-adapter/overview
102
+ * Google Group: http://groups.google.com/group/rails-sqlserver-adapter
103
+ * IRC Room: #rails-sqlserver on irc.freenode.net
29
104
 
30
105
 
31
106
  == Credits
32
107
 
33
- Many many people have contributed to this over
108
+ Many many people have contributed. If you do not see your name here and it should be let us know.
109
+
110
+ * Ken Collins
111
+ * Murray Steele
112
+ * Shawn Balestracci
113
+ * Joe Rafaniello
114
+ * Tom Ward
34
115
 
35
116
 
36
117
  == License
data/Rakefile CHANGED
@@ -1,52 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
3
  require 'rake/testtask'
4
- require 'rake/packagetask'
5
- require 'rake/gempackagetask'
6
- require 'rake/contrib/rubyforgepublisher'
7
4
 
8
- PKG_NAME = 'activerecord-sqlserver-adapter'
9
- PKG_BUILD = (".#{ENV['PKG_BUILD']}" if ENV['PKG_BUILD'])
10
- PKG_VERSION = "1.0.0.314#{PKG_BUILD}"
11
-
12
- spec = Gem::Specification.new do |s|
13
- s.name = PKG_NAME
14
- s.summary = 'SQL Server adapter for Active Record - ABR'
15
- s.version = PKG_VERSION
16
-
17
- s.add_dependency 'activerecord', '>= 1.15.5.7843'
18
- s.require_path = 'lib'
19
-
20
- s.files = %w(lib/active_record/connection_adapters/sqlserver_adapter.rb)
21
-
22
- s.author = 'Tom Ward'
23
- s.email = 'tom@popdog.net'
24
- s.homepage = 'http://wiki.rubyonrails.org/rails/pages/SQL+Server'
25
- s.rubyforge_project = 'activerecord'
26
- end
27
-
28
- Rake::GemPackageTask.new(spec) do |p|
29
- p.gem_spec = spec
30
- p.need_tar = true
31
- p.need_zip = true
32
- end
33
-
34
- desc "Publish the beta gem"
35
- task :pgem => :package do
36
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_NAME}-#{PKG_VERSION}.gem").upload
37
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
38
- end
39
-
40
- desc "Publish the release files to RubyForge."
41
- task :release => :package do
42
- require 'rubyforge'
43
-
44
- packages = %w(gem tgz zip).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
45
-
46
- rubyforge = RubyForge.new
47
- rubyforge.login
48
- rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
49
- end
50
5
 
51
6
  desc 'Create the SQL Server test databases'
52
7
  task :create_databases do
@@ -87,9 +42,3 @@ for adapter in %w( sqlserver sqlserver_odbc )
87
42
  end
88
43
  end
89
44
 
90
-
91
- desc 'Clean existing gems out'
92
- task :clean do
93
- packages = %w(gem tgz zip).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
94
- FileUtils.rm(packages, :force => true)
95
- end
@@ -61,7 +61,7 @@ module ActiveRecord
61
61
  end
62
62
 
63
63
  def is_utf8?
64
- sql_type =~ /nvarchar|ntext|nchar|nvarchar(max)/i
64
+ sql_type =~ /nvarchar|ntext|nchar/i
65
65
  end
66
66
 
67
67
  def table_name
@@ -77,10 +77,16 @@ module ActiveRecord
77
77
 
78
78
  def extract_limit(sql_type)
79
79
  case sql_type
80
- when /^smallint/i then 2
81
- when /^int/i then 4
82
- when /^bigint/i then 8
83
- else super
80
+ when /^smallint/i
81
+ 2
82
+ when /^int/i
83
+ 4
84
+ when /^bigint/i
85
+ 8
86
+ when /\(max\)/, /decimal/, /numeric/
87
+ nil
88
+ else
89
+ super
84
90
  end
85
91
  end
86
92
 
@@ -146,14 +152,14 @@ module ActiveRecord
146
152
  ADAPTER_NAME = 'SQLServer'.freeze
147
153
  DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+(\d{4})/
148
154
  SUPPORTED_VERSIONS = [2000,2005].freeze
149
- LIMITABLE_TYPES = [:string,:integer,:float].freeze
155
+ LIMITABLE_TYPES = ['string','integer','float','char','nchar','varchar','nvarchar'].freeze
150
156
 
151
- cattr_accessor :native_text_database_type
157
+ cattr_accessor :native_text_database_type, :native_binary_database_type
152
158
 
153
159
  class << self
154
160
 
155
161
  def type_limitable?(type)
156
- LIMITABLE_TYPES.include?(type.to_sym)
162
+ LIMITABLE_TYPES.include?(type.to_s)
157
163
  end
158
164
 
159
165
  end
@@ -205,11 +211,22 @@ module ActiveRecord
205
211
  self.class.native_text_database_type || (sqlserver_2005? ? 'varchar(max)' : 'text')
206
212
  end
207
213
 
214
+ def native_binary_database_type
215
+ self.class.native_binary_database_type || (sqlserver_2005? ? 'varbinary(max)' : 'image')
216
+ end
217
+
208
218
  # QUOTING ==================================================#
209
219
 
210
220
  def quote(value, column = nil)
211
- if value.kind_of?(String) && column && column.type == :binary
212
- column.class.string_to_binary(value)
221
+ case value
222
+ when String, ActiveSupport::Multibyte::Chars
223
+ if column && column.type == :binary
224
+ column.class.string_to_binary(value)
225
+ elsif column && column.respond_to?(:is_utf8?) && column.is_utf8?
226
+ quoted_utf8_value(value)
227
+ else
228
+ super
229
+ end
213
230
  else
214
231
  super
215
232
  end
@@ -223,6 +240,11 @@ module ActiveRecord
223
240
  column_name.to_s.split('.').map{ |name| "[#{name}]" }.join('.')
224
241
  end
225
242
 
243
+ def quote_table_name(table_name)
244
+ return table_name if table_name =~ /^\[.*\]$/
245
+ quote_column_name(table_name)
246
+ end
247
+
226
248
  def quoted_true
227
249
  '1'
228
250
  end
@@ -239,6 +261,10 @@ module ActiveRecord
239
261
  end
240
262
  end
241
263
 
264
+ def quoted_utf8_value(value)
265
+ "N'#{quote_string(value)}'"
266
+ end
267
+
242
268
  # REFERENTIAL INTEGRITY ====================================#
243
269
 
244
270
  def disable_referential_integrity(&block)
@@ -389,20 +415,26 @@ module ActiveRecord
389
415
  # SCHEMA STATEMENTS ========================================#
390
416
 
391
417
  def native_database_types
392
- binary = sqlserver_2005? ? "varbinary(max)" : "image"
393
418
  {
394
- :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
395
- :string => { :name => "varchar", :limit => 255 },
396
- :text => { :name => native_text_database_type },
397
- :integer => { :name => "int", :limit => 4 },
398
- :float => { :name => "float", :limit => 8 },
399
- :decimal => { :name => "decimal" },
400
- :datetime => { :name => "datetime" },
401
- :timestamp => { :name => "datetime" },
402
- :time => { :name => "datetime" },
403
- :date => { :name => "datetime" },
404
- :binary => { :name => binary },
405
- :boolean => { :name => "bit"}
419
+ :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
420
+ :string => { :name => "varchar", :limit => 255 },
421
+ :text => { :name => native_text_database_type },
422
+ :integer => { :name => "int", :limit => 4 },
423
+ :float => { :name => "float", :limit => 8 },
424
+ :decimal => { :name => "decimal" },
425
+ :datetime => { :name => "datetime" },
426
+ :timestamp => { :name => "datetime" },
427
+ :time => { :name => "datetime" },
428
+ :date => { :name => "datetime" },
429
+ :binary => { :name => native_binary_database_type },
430
+ :boolean => { :name => "bit"},
431
+ # These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
432
+ :char => { :name => 'char' },
433
+ :varchar_max => { :name => 'varchar(max)' },
434
+ :nchar => { :name => "nchar" },
435
+ :nvarchar => { :name => "nvarchar", :limit => 255 },
436
+ :nvarchar_max => { :name => "nvarchar(max)" },
437
+ :ntext => { :name => "ntext" }
406
438
  }
407
439
  end
408
440
 
@@ -500,7 +532,8 @@ module ActiveRecord
500
532
 
501
533
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
502
534
  limit = nil unless self.class.type_limitable?(type)
503
- if type.to_s == 'integer'
535
+ case type.to_s
536
+ when 'integer'
504
537
  case limit
505
538
  when 1..2 then 'smallint'
506
539
  when 3..4, nil then 'integer'
@@ -601,12 +634,10 @@ module ActiveRecord
601
634
  end
602
635
 
603
636
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
604
- set_utf8_values!(sql)
605
637
  super || select_value("SELECT SCOPE_IDENTITY() AS Ident")
606
638
  end
607
639
 
608
640
  def update_sql(sql, name = nil)
609
- set_utf8_values!(sql)
610
641
  execute(sql, name)
611
642
  select_value('SELECT @@ROWCOUNT AS AffectedRows')
612
643
  end
@@ -789,11 +820,9 @@ module ActiveRecord
789
820
  def column_definitions(table_name)
790
821
  db_name = unqualify_db_name(table_name)
791
822
  table_name = unqualify_table_name(table_name)
792
- # COL_LENGTH returns values that do not reflect how much data can be stored in certain data types.
793
- # COL_LENGTH returns -1 for varchar(max), nvarchar(max), and varbinary(max)
794
- # COL_LENGTH returns 16 for ntext, text, image types
795
823
  sql = %{
796
824
  SELECT
825
+ columns.TABLE_NAME as table_name,
797
826
  columns.COLUMN_NAME as name,
798
827
  columns.DATA_TYPE as type,
799
828
  CASE
@@ -803,10 +832,7 @@ module ActiveRecord
803
832
  columns.NUMERIC_SCALE as numeric_scale,
804
833
  columns.NUMERIC_PRECISION as numeric_precision,
805
834
  CASE
806
- WHEN columns.DATA_TYPE IN ('nvarchar') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = -1 THEN 1073741823
807
- WHEN columns.DATA_TYPE IN ('varchar', 'varbinary') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = -1 THEN 2147483647
808
- WHEN columns.DATA_TYPE IN ('ntext') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = 16 THEN 1073741823
809
- WHEN columns.DATA_TYPE IN ('text', 'image') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = 16 THEN 2147483647
835
+ WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
810
836
  ELSE COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME)
811
837
  END as length,
812
838
  CASE
@@ -824,12 +850,16 @@ module ActiveRecord
824
850
  results = without_type_conversion { select(sql,nil,true) }
825
851
  results.collect do |ci|
826
852
  ci.symbolize_keys!
827
- ci[:type] = if ci[:type] =~ /numeric|decimal/i
853
+ ci[:type] = case ci[:type]
854
+ when /^bit|image|text|ntext|datetime$/
855
+ ci[:type]
856
+ when /^numeric|decimal$/i
828
857
  "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
858
+ when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
859
+ ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
829
860
  else
830
- "#{ci[:type]}(#{ci[:length]})"
861
+ ci[:type]
831
862
  end
832
- ci[:table_name] = table_name
833
863
  ci[:default_value] = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/)[1] if ci[:default_value]
834
864
  ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
835
865
  ci
@@ -843,8 +873,6 @@ module ActiveRecord
843
873
  column
844
874
  end
845
875
 
846
-
847
-
848
876
  def change_order_direction(order)
849
877
  order.split(",").collect {|fragment|
850
878
  case fragment
@@ -854,11 +882,11 @@ module ActiveRecord
854
882
  end
855
883
  }.join(",")
856
884
  end
857
-
885
+
858
886
  def special_columns(table_name)
859
887
  columns(table_name).select(&:is_special?).map(&:name)
860
888
  end
861
-
889
+
862
890
  def repair_special_columns(sql)
863
891
  special_cols = special_columns(get_table_name(sql))
864
892
  for col in special_cols.to_a
@@ -867,35 +895,7 @@ module ActiveRecord
867
895
  end
868
896
  sql
869
897
  end
870
-
871
- def utf8_columns(table_name)
872
- columns(table_name).select(&:is_utf8?).map(&:name)
873
- end
874
-
875
- def set_utf8_values!(sql)
876
- utf8_cols = utf8_columns(get_table_name(sql))
877
- if sql =~ /^\s*UPDATE/i
878
- utf8_cols.each do |col|
879
- sql.gsub!("[#{col.to_s}] = '", "[#{col.to_s}] = N'")
880
- end
881
- elsif sql =~ /^\s*INSERT(?!.*DEFAULT VALUES\s*$)/i
882
- # TODO This code should be simplified
883
- # Get columns and values, split them into arrays, and store the original_values for when we need to replace them
884
- columns_and_values = sql.scan(/\((.*?)\)/m).flatten
885
- columns = columns_and_values.first.split(',')
886
- values = columns_and_values[1].split(',')
887
- original_values = values.dup
888
- # Iterate columns that should be UTF8, and append an N to the value, if the value is not NULL
889
- utf8_cols.each do |col|
890
- columns.each_with_index do |column, idx|
891
- values[idx] = " N#{values[idx].gsub(/^ /, '')}" if column =~ /\[#{col}\]/ and values[idx] !~ /^NULL$/
892
- end
893
- end
894
- # Replace (in place) the SQL
895
- sql.gsub!(original_values.join(','), values.join(','))
896
- end
897
- end
898
-
898
+
899
899
  end #class SQLServerAdapter < AbstractAdapter
900
900
 
901
901
  end #module ConnectionAdapters
@@ -215,33 +215,6 @@ class AdapterTestSqlserver < ActiveRecord::TestCase
215
215
 
216
216
  end
217
217
 
218
- context 'which have coerced types' do
219
-
220
- setup do
221
- christmas_08 = "2008-12-25".to_time
222
- christmas_08_afternoon = "2008-12-25 12:00".to_time
223
- @chronic_date = SqlServerChronic.create!(:date => christmas_08).reload
224
- @chronic_time = SqlServerChronic.create!(:time => christmas_08_afternoon).reload
225
- end
226
-
227
- should 'have an inheritable attribute ' do
228
- assert SqlServerChronic.coerced_sqlserver_date_columns.include?('date')
229
- end
230
-
231
- should 'have column and objects cast to date' do
232
- date_column = SqlServerChronic.columns_hash['date']
233
- assert_equal :date, date_column.type, "This column: \n#{date_column.inspect}"
234
- assert_instance_of Date, @chronic_date.date
235
- end
236
-
237
- should 'have column objects cast to time' do
238
- time_column = SqlServerChronic.columns_hash['time']
239
- assert_equal :time, time_column.type, "This column: \n#{time_column.inspect}"
240
- assert_instance_of Time, @chronic_time.time
241
- end
242
-
243
- end
244
-
245
218
  end
246
219
 
247
220
  context 'For identity inserts' do
@@ -4,6 +4,7 @@ require 'models/binary'
4
4
  class ColumnTestSqlserver < ActiveRecord::TestCase
5
5
 
6
6
  def setup
7
+ @connection = ActiveRecord::Base.connection
7
8
  @column_klass = ActiveRecord::ConnectionAdapters::SQLServerColumn
8
9
  end
9
10
 
@@ -18,7 +19,16 @@ class ColumnTestSqlserver < ActiveRecord::TestCase
18
19
  end
19
20
  end
20
21
 
21
- context 'For :binary columns' do
22
+ should 'return correct null, limit, and default for Topic' do
23
+ tch = Topic.columns_hash
24
+ assert_equal false, tch['id'].null
25
+ assert_equal true, tch['title'].null
26
+ assert_equal 255, tch['author_name'].limit
27
+ assert_equal true, tch['approved'].default
28
+ assert_equal 0, tch['replies_count'].default
29
+ end
30
+
31
+ context 'For binary columns' do
22
32
 
23
33
  setup do
24
34
  @binary_string = "GIF89a\001\000\001\000\200\000\000\377\377\377\000\000\000!\371\004\000\000\000\000\000,\000\000\000\000\001\000\001\000\000\002\002D\001\000;"
@@ -29,6 +39,13 @@ class ColumnTestSqlserver < ActiveRecord::TestCase
29
39
  assert_equal @binary_string, Binary.find(@saved_bdata).data
30
40
  end
31
41
 
42
+ should 'have correct attributes' do
43
+ column = Binary.columns_hash['data']
44
+ assert_equal :binary, column.type
45
+ assert_equal @connection.native_binary_database_type, column.sql_type
46
+ assert_equal nil, column.limit
47
+ end
48
+
32
49
  should 'quote data for sqlserver with literal 0x prefix' do
33
50
  # See the output of the stored procedure: 'exec sp_datatype_info'
34
51
  sqlserver_encoded_bdata = "0x47494638396101000100800000ffffff00000021f90400000000002c00000000010001000002024401003b"
@@ -37,30 +54,193 @@ class ColumnTestSqlserver < ActiveRecord::TestCase
37
54
 
38
55
  end
39
56
 
40
- context 'For .columns method' do
57
+ context 'For string columns' do
58
+
59
+ setup do
60
+ @char = SqlServerString.columns_hash['char']
61
+ @char10 = SqlServerString.columns_hash['char_10']
62
+ @varcharmax = SqlServerString.columns_hash['varchar_max']
63
+ @varcharmax10 = SqlServerString.columns_hash['varchar_max_10']
64
+ end
41
65
 
42
- should 'return correct scales and precisions for NumericData' do
43
- bank_balance = NumericData.columns_hash['bank_balance']
44
- big_bank_balance = NumericData.columns_hash['big_bank_balance']
45
- world_population = NumericData.columns_hash['world_population']
46
- my_house_population = NumericData.columns_hash['my_house_population']
47
- assert_equal [2,10], [bank_balance.scale, bank_balance.precision]
48
- assert_equal [2,15], [big_bank_balance.scale, big_bank_balance.precision]
49
- assert_equal [0,10], [world_population.scale, world_population.precision]
50
- assert_equal [0,2], [my_house_population.scale, my_house_population.precision]
51
- end
52
-
53
- should 'return correct null, limit, and default for Topic' do
54
- tch = Topic.columns_hash
55
- assert_equal false, tch['id'].null
56
- assert_equal true, tch['title'].null
57
- assert_equal 255, tch['author_name'].limit
58
- assert_equal true, tch['approved'].default
59
- assert_equal 0, tch['replies_count'].default
66
+ should 'have correct simplified types' do
67
+ assert_equal :string, @char.type
68
+ assert_equal :string, @char10.type
69
+ if sqlserver_2005?
70
+ assert_equal :string, @varcharmax.type
71
+ assert_equal :string, @varcharmax10.type
72
+ end
73
+ end
74
+
75
+ should 'have correct #sql_type per schema definition' do
76
+ assert_equal 'char(1)', @char.sql_type, 'Specifing a char type with no limit is 1 by SQL Server standards.'
77
+ assert_equal 'char(10)', @char10.sql_type, @char10.inspect
78
+ if sqlserver_2005?
79
+ assert_equal 'varchar(max)', @varcharmax.sql_type, 'A -1 limit should be converted to max (max) type.'
80
+ assert_equal 'varchar(max)', @varcharmax10.sql_type, 'A -1 limit should be converted to max (max) type.'
81
+ end
82
+ end
83
+
84
+ should 'have correct #limit per schema definition' do
85
+ assert_equal 1, @char.limit
86
+ assert_equal 10, @char10.limit
87
+ if sqlserver_2005?
88
+ assert_equal nil, @varcharmax.limit, 'Limits on max types are moot and we should let rails know that.'
89
+ assert_equal nil, @varcharmax10.limit, 'Limits on max types are moot and we should let rails know that.'
90
+ end
60
91
  end
61
92
 
62
93
  end
63
94
 
64
95
 
96
+ context 'For all national/unicode columns' do
97
+
98
+ setup do
99
+ @nchar = SqlServerUnicode.columns_hash['nchar']
100
+ @nvarchar = SqlServerUnicode.columns_hash['nvarchar']
101
+ @ntext = SqlServerUnicode.columns_hash['ntext']
102
+ @ntext10 = SqlServerUnicode.columns_hash['ntext_10']
103
+ @nchar10 = SqlServerUnicode.columns_hash['nchar_10']
104
+ @nvarchar100 = SqlServerUnicode.columns_hash['nvarchar_100']
105
+ @nvarcharmax = SqlServerUnicode.columns_hash['nvarchar_max']
106
+ @nvarcharmax10 = SqlServerUnicode.columns_hash['nvarchar_max_10']
107
+ end
108
+
109
+ should 'all respond true to #is_utf8?' do
110
+ SqlServerUnicode.columns_hash.except('id').values.each do |column|
111
+ assert column.is_utf8?, "This column #{column.inspect} should have been a unicode column."
112
+ end
113
+ end
114
+
115
+ should 'have correct simplified types' do
116
+ assert_equal :string, @nchar.type
117
+ assert_equal :string, @nvarchar.type
118
+ assert_equal :text, @ntext.type
119
+ assert_equal :text, @ntext10.type
120
+ assert_equal :string, @nchar10.type
121
+ assert_equal :string, @nvarchar100.type
122
+ if sqlserver_2005?
123
+ assert_equal :string, @nvarcharmax.type
124
+ assert_equal :string, @nvarcharmax10.type
125
+ end
126
+ end
127
+
128
+ should 'have correct #sql_type per schema definition' do
129
+ assert_equal 'nchar(1)', @nchar.sql_type, 'Specifing a nchar type with no limit is 1 by SQL Server standards.'
130
+ assert_equal 'nvarchar(255)', @nvarchar.sql_type, 'Default nvarchar limit is 255.'
131
+ assert_equal 'ntext', @ntext.sql_type, 'Nice and clean ntext, limit means nothing here.'
132
+ assert_equal 'ntext', @ntext10.sql_type, 'Even a next with a limit of 10 specified will mean nothing.'
133
+ assert_equal 'nchar(10)', @nchar10.sql_type, 'An nchar with a limit of 10 needs to have it show up here.'
134
+ assert_equal 'nvarchar(100)', @nvarchar100.sql_type, 'An nvarchar with a specified limit of 100 needs to show it.'
135
+ if sqlserver_2005?
136
+ assert_equal 'nvarchar(max)', @nvarcharmax.sql_type, 'A -1 limit should be converted to max (max) type.'
137
+ assert_equal 'nvarchar(max)', @nvarcharmax10.sql_type, 'A -1 limit should be converted to max (max) type.'
138
+ end
139
+ end
140
+
141
+ should 'have correct #limit per schema definition' do
142
+ assert_equal 1, @nchar.limit
143
+ assert_equal 255, @nvarchar.limit
144
+ assert_equal nil, @ntext.limit, 'An ntext column limit is moot, it is a fixed variable length'
145
+ assert_equal 10, @nchar10.limit
146
+ assert_equal 100, @nvarchar100.limit
147
+ if sqlserver_2005?
148
+ assert_equal nil, @nvarcharmax.limit, 'Limits on max types are moot and we should let rails know that.'
149
+ assert_equal nil, @nvarcharmax10.limit, 'Limits on max types are moot and we should let rails know that.'
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ context 'For datetime columns' do
156
+
157
+ setup do
158
+ @date = SqlServerChronic.columns_hash['date']
159
+ @time = SqlServerChronic.columns_hash['time']
160
+ @datetime = SqlServerChronic.columns_hash['datetime']
161
+ end
162
+
163
+ should 'have correct simplified type for uncast datetime' do
164
+ assert_equal :datetime, @datetime.type
165
+ end
166
+
167
+ should 'all be a datetime #sql_type' do
168
+ assert_equal 'datetime', @date.sql_type
169
+ assert_equal 'datetime', @time.sql_type
170
+ assert_equal 'datetime', @datetime.sql_type
171
+ end
172
+
173
+ should 'all be have nil #limit' do
174
+ assert_equal nil, @date.limit
175
+ assert_equal nil, @time.limit
176
+ assert_equal nil, @datetime.limit
177
+ end
178
+
179
+ context 'which have coerced types' do
180
+
181
+ setup do
182
+ christmas_08 = "2008-12-25".to_time
183
+ christmas_08_afternoon = "2008-12-25 12:00".to_time
184
+ @chronic_date = SqlServerChronic.create!(:date => christmas_08).reload
185
+ @chronic_time = SqlServerChronic.create!(:time => christmas_08_afternoon).reload
186
+ end
187
+
188
+ should 'have an inheritable attribute ' do
189
+ assert SqlServerChronic.coerced_sqlserver_date_columns.include?('date')
190
+ end
191
+
192
+ should 'have column and objects cast to date' do
193
+ assert_equal :date, @date.type, "This column: \n#{@date.inspect}"
194
+ assert_instance_of Date, @chronic_date.date
195
+ end
196
+
197
+ should 'have column objects cast to time' do
198
+ assert_equal :time, @time.type, "This column: \n#{@time.inspect}"
199
+ assert_instance_of Time, @chronic_time.time
200
+ end
201
+
202
+ end
203
+
204
+ end
205
+
206
+ context 'For decimal and numeric columns' do
207
+
208
+ setup do
209
+ @bank_balance = NumericData.columns_hash['bank_balance']
210
+ @big_bank_balance = NumericData.columns_hash['big_bank_balance']
211
+ @world_population = NumericData.columns_hash['world_population']
212
+ @my_house_population = NumericData.columns_hash['my_house_population']
213
+ end
214
+
215
+ should 'have correct simplified types' do
216
+ assert_equal :decimal, @bank_balance.type
217
+ assert_equal :decimal, @big_bank_balance.type
218
+ assert_equal :integer, @world_population.type, 'Since #extract_scale == 0'
219
+ assert_equal :integer, @my_house_population.type, 'Since #extract_scale == 0'
220
+ end
221
+
222
+ should 'have correct #sql_type' do
223
+ assert_equal 'decimal(10,2)', @bank_balance.sql_type
224
+ assert_equal 'decimal(15,2)', @big_bank_balance.sql_type
225
+ assert_equal 'decimal(10,0)', @world_population.sql_type
226
+ assert_equal 'decimal(2,0)', @my_house_population.sql_type
227
+ end
228
+
229
+ should 'have correct #limit' do
230
+ assert_equal nil, @bank_balance.limit
231
+ assert_equal nil, @big_bank_balance.limit
232
+ assert_equal nil, @world_population.limit
233
+ assert_equal nil, @my_house_population.limit
234
+ end
235
+
236
+ should 'return correct precisions and scales' do
237
+ assert_equal [10,2], [@bank_balance.precision, @bank_balance.scale]
238
+ assert_equal [15,2], [@big_bank_balance.precision, @big_bank_balance.scale]
239
+ assert_equal [10,0], [@world_population.precision, @world_population.scale]
240
+ assert_equal [2,0], [@my_house_population.precision, @my_house_population.scale]
241
+ end
242
+
243
+ end
244
+
65
245
 
66
246
  end
@@ -2,34 +2,47 @@ require 'cases/sqlserver_helper'
2
2
 
3
3
  class SchemaDumperTestSqlserver < ActiveRecord::TestCase
4
4
 
5
- context 'In schema dump' do
6
-
7
- should 'include limit constraint for integer columns' do
8
- output = standard_dump(/^(?!integer_limits)/)
9
- assert_match %r{c_int_1.*:limit => 2}, output
10
- assert_match %r{c_int_2.*:limit => 2}, output
11
- assert_match %r{c_int_3.*}, output
12
- assert_match %r{c_int_4.*}, output
13
- assert_no_match %r{c_int_3.*:limit}, output
14
- assert_no_match %r{c_int_4.*:limit}, output
15
- assert_match %r{c_int_5.*:limit => 8}, output
16
- assert_match %r{c_int_6.*:limit => 8}, output
17
- assert_match %r{c_int_7.*:limit => 8}, output
18
- assert_match %r{c_int_8.*:limit => 8}, output
5
+ setup :find_all_tables
6
+
7
+ context 'For primary keys' do
8
+
9
+ should 'honor nonstandards' do
10
+ table_dump('movies') do |output|
11
+ match = output.match(%r{create_table "movies"(.*)do})
12
+ assert_not_nil(match, "nonstandardpk table not found")
13
+ assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
14
+ end
19
15
  end
20
16
 
21
- should 'honor nonstandard primary keys' do
22
- output = standard_dump
23
- match = output.match(%r{create_table "movies"(.*)do})
24
- assert_not_nil(match, "nonstandardpk table not found")
25
- assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
17
+ end
18
+
19
+ context 'For integers' do
20
+
21
+ should 'include limit constraint that match logic for smallint and bigint in #extract_limit' do
22
+ table_dump('integer_limits') do |output|
23
+ assert_match %r{c_int_1.*:limit => 2}, output
24
+ assert_match %r{c_int_2.*:limit => 2}, output
25
+ assert_match %r{c_int_3.*}, output
26
+ assert_match %r{c_int_4.*}, output
27
+ assert_no_match %r{c_int_3.*:limit}, output
28
+ assert_no_match %r{c_int_4.*:limit}, output
29
+ assert_match %r{c_int_5.*:limit => 8}, output
30
+ assert_match %r{c_int_6.*:limit => 8}, output
31
+ assert_match %r{c_int_7.*:limit => 8}, output
32
+ assert_match %r{c_int_8.*:limit => 8}, output
33
+ end
26
34
  end
27
35
 
28
36
  end
29
37
 
30
38
 
39
+
31
40
  private
32
41
 
42
+ def find_all_tables
43
+ @all_tables ||= ActiveRecord::Base.connection.tables
44
+ end
45
+
33
46
  def standard_dump(ignore_tables = [])
34
47
  stream = StringIO.new
35
48
  ActiveRecord::SchemaDumper.ignore_tables = [*ignore_tables]
@@ -37,4 +50,12 @@ class SchemaDumperTestSqlserver < ActiveRecord::TestCase
37
50
  stream.string
38
51
  end
39
52
 
53
+ def table_dump(*table_names)
54
+ stream = StringIO.new
55
+ ActiveRecord::SchemaDumper.ignore_tables = @all_tables-table_names
56
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
57
+ yield stream.string
58
+ stream.string
59
+ end
60
+
40
61
  end
@@ -19,6 +19,8 @@ class TableWithRealColumn < ActiveRecord::Base; end
19
19
  class FkTestHasFk < ActiveRecord::Base ; end
20
20
  class FkTestHasPk < ActiveRecord::Base ; end
21
21
  class NumericData < ActiveRecord::Base ; self.table_name = 'numeric_data' ; end
22
+ class SqlServerUnicode < ActiveRecord::Base ; end
23
+ class SqlServerString < ActiveRecord::Base ; end
22
24
  class SqlServerChronic < ActiveRecord::Base
23
25
  coerce_sqlserver_date :date
24
26
  coerce_sqlserver_time :time
@@ -72,6 +74,10 @@ end
72
74
 
73
75
  module ActiveRecord
74
76
  class TestCase < ActiveSupport::TestCase
77
+ class << self
78
+ def sqlserver_2000? ; ActiveRecord::Base.connection.sqlserver_2000? ; end
79
+ def sqlserver_2005? ; ActiveRecord::Base.connection.sqlserver_2005? ; end
80
+ end
75
81
  def assert_sql(*patterns_to_match)
76
82
  $queries_executed = []
77
83
  yield
@@ -82,6 +88,8 @@ module ActiveRecord
82
88
  end
83
89
  assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found in:\n#{$queries_executed.inspect}"
84
90
  end
91
+ def sqlserver_2000? ; self.class.sqlserver_2000? ; end
92
+ def sqlserver_2005? ; self.class.sqlserver_2005? ; end
85
93
  end
86
94
  end
87
95
 
@@ -0,0 +1,23 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/order'
3
+
4
+ class TableNameTestSqlserver < ActiveRecord::TestCase
5
+
6
+ self.use_transactional_fixtures = false
7
+
8
+ def setup
9
+ Order.table_name = '[orders]'
10
+ Order.reset_column_information
11
+ end
12
+
13
+ should 'load columns with escaped table name for model' do
14
+ assert_equal 4, Order.columns.length
15
+
16
+ end
17
+
18
+ should 'not re-escape table name if it is escaped already for SQL queries' do
19
+ assert_sql(/SELECT \* FROM \[orders\]/) { Order.all }
20
+ end
21
+
22
+
23
+ end
@@ -0,0 +1,44 @@
1
+ require 'cases/sqlserver_helper'
2
+
3
+ class UnicodeTestSqlserver < ActiveRecord::TestCase
4
+
5
+
6
+ context 'Testing basic saves and unicode limits' do
7
+
8
+ should 'save and reload simple nchar string' do
9
+ assert nchar_data = SqlServerUnicode.create!(:nchar => 'A')
10
+ assert_equal 'A', SqlServerUnicode.find(nchar_data.id).nchar
11
+ end
12
+
13
+ should 'save and reload simple nvarchar(max) string' do
14
+ test_string = 'Ken Collins'
15
+ assert nvarcharmax_data = SqlServerUnicode.create!(:nvarchar_max => test_string)
16
+ assert_equal test_string, SqlServerUnicode.find(nvarcharmax_data.id).nvarchar_max
17
+ end if sqlserver_2005?
18
+
19
+ should 'enforce default nchar_10 limit of 10' do
20
+ assert_raise(ActiveRecord::StatementInvalid) { SqlServerUnicode.create!(:nchar => '01234567891') }
21
+ end
22
+
23
+ should 'enforce default nvarchar_100 limit of 100' do
24
+ assert_raise(ActiveRecord::StatementInvalid) { SqlServerUnicode.create!(:nvarchar_100 => '0123456789'*10+'1') }
25
+ end
26
+
27
+ end
28
+
29
+ context 'Testing unicode data' do
30
+
31
+ setup do
32
+ @unicode_data = "一二34五六"
33
+ end
34
+
35
+ should 'insert into nvarchar field' do
36
+ assert data = SqlServerUnicode.create!(:nvarchar => @unicode_data)
37
+ assert_equal @unicode_data, data.reload.nvarchar
38
+ end
39
+
40
+ end
41
+
42
+
43
+
44
+ end
@@ -2,6 +2,7 @@ ActiveRecord::Schema.define do
2
2
 
3
3
  create_table :table_with_real_columns, :force => true do |t|
4
4
  t.column :real_number, :real
5
+ # t.column :varchar_max, :varchar_max if ActiveRecord::Base.connection.sqlserver_2005?
5
6
  end
6
7
 
7
8
  create_table :defaults, :force => true do |t|
@@ -34,5 +35,30 @@ ActiveRecord::Schema.define do
34
35
  REFERENCES #{quote_table_name('fk_test_has_pks')} (#{quote_column_name('id')})
35
36
  ADDFKSQL
36
37
 
38
+ create_table :sql_server_unicodes, :force => true do |t|
39
+ t.column :nchar, :nchar
40
+ t.column :nvarchar, :nvarchar
41
+ t.column :ntext, :ntext
42
+ t.column :ntext_10, :ntext, :limit => 10
43
+ t.column :nchar_10, :nchar, :limit => 10
44
+ t.column :nvarchar_100, :nvarchar, :limit => 100
45
+ if ActiveRecord::Base.connection.sqlserver_2005?
46
+ t.column :nvarchar_max, :nvarchar_max
47
+ t.column :nvarchar_max_10, :nvarchar_max, :limit => 10
48
+ end
49
+ end
50
+
51
+ create_table :sql_server_strings, :force => true do |t|
52
+ t.column :char, :char
53
+ t.column :char_10, :char, :limit => 10
54
+ if ActiveRecord::Base.connection.sqlserver_2005?
55
+ t.column :varchar_max, :varchar_max
56
+ t.column :varchar_max_10, :varchar_max, :limit => 10
57
+ end
58
+ end
59
+
60
+ create_table :sql_server_binary_types, :force => true do |t|
61
+ # TODO: Add some different native binary types and test.
62
+ end
37
63
 
38
64
  end
metadata CHANGED
@@ -1,18 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-sqlserver-2000-2005-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken Collins
8
8
  - Murray Steele
9
9
  - Shawn Balestracci
10
+ - Joe Rafaniello
10
11
  - Tom Ward
11
12
  autorequire:
12
13
  bindir: bin
13
14
  cert_chain: []
14
15
 
15
- date: 2008-11-19 00:00:00 -08:00
16
+ date: 2008-11-21 00:00:00 -08:00
16
17
  default_executable:
17
18
  dependencies: []
18
19
 
@@ -82,6 +83,8 @@ test_files:
82
83
  - test/cases/schema_dumper_test_sqlserver.rb
83
84
  - test/cases/specific_schema_test_sqlserver.rb
84
85
  - test/cases/sqlserver_helper.rb
86
+ - test/cases/table_name_test_sqlserver.rb
87
+ - test/cases/unicode_test_sqlserver.rb
85
88
  - test/connections/native_sqlserver/connection.rb
86
89
  - test/connections/native_sqlserver_odbc/connection.rb
87
90
  - test/migrations/transaction_table/1_table_will_never_be_created.rb