rails-sqlserver-2000-2005-adapter 2.2.4 → 2.2.5

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/CHANGELOG CHANGED
@@ -4,6 +4,20 @@ MASTER
4
4
  *
5
5
 
6
6
 
7
+ * 2.2.5 (January 4th, 2009)
8
+
9
+ * Added a log_info_schema_queries class attribute and make all queries to INFORMATION_SCHEMA silent by
10
+ default. [Ken Collins]
11
+
12
+ * Fix millisecond support in datetime columns. ODBC::Timestamp incorrectly takes SQL Server milliseconds
13
+ and applies them as nanoseconds. We cope with this at the DBI layer by using SQLServerDBI::Type::SqlserverTimestamp
14
+ class to parse the before type cast value appropriately. Also update the adapters #quoted_date method
15
+ to work more simply by converting ruby's #usec milliseconds to SQL Server microseconds. [Ken Collins]
16
+
17
+ * Core extensions for ActiveRecord now reflect on the connection before doing SQL Server things. Now
18
+ this adapter is compatible for using with other adapters. [Ken Collins]
19
+
20
+
7
21
  * 2.2.4 (December 5th, 2008)
8
22
 
9
23
  * Fix a type left in #views_real_column_name. Also cache #view_information lookups. [Ken Collins]
data/README.rdoc CHANGED
@@ -58,7 +58,7 @@ To pass the ActiveRecord tests we had to implement an class accessor for the nat
58
58
  * SQL Server 2000 is 'text'
59
59
  * SQL Server 2005 is 'varchar(max)'
60
60
 
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 include this line in your environment.rb file.
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.
62
62
 
63
63
  ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = 'varchar(8000)'
64
64
 
@@ -68,6 +68,13 @@ By default any :binary column created by migrations will create these native typ
68
68
  * SQL Server 2005 is 'varbinary(max)'
69
69
 
70
70
 
71
+ ==== Schema Information Logging
72
+
73
+ 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.
74
+
75
+ ActiveRecord::ConnectionAdapters::SQLServerAdapter.log_info_schema_queries = true
76
+
77
+
71
78
  == Versions
72
79
 
73
80
  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.
@@ -27,7 +27,7 @@ module ActiveRecord
27
27
  conn["AutoCommit"] = true
28
28
  ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
29
29
  end
30
-
30
+
31
31
  end
32
32
 
33
33
  module ConnectionAdapters
@@ -150,12 +150,12 @@ module ActiveRecord
150
150
  class SQLServerAdapter < AbstractAdapter
151
151
 
152
152
  ADAPTER_NAME = 'SQLServer'.freeze
153
- VERSION = '2.2.4'.freeze
153
+ VERSION = '2.2.5'.freeze
154
154
  DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+(\d{4})/
155
155
  SUPPORTED_VERSIONS = [2000,2005].freeze
156
156
  LIMITABLE_TYPES = ['string','integer','float','char','nchar','varchar','nvarchar'].freeze
157
157
 
158
- cattr_accessor :native_text_database_type, :native_binary_database_type
158
+ cattr_accessor :native_text_database_type, :native_binary_database_type, :log_info_schema_queries
159
159
 
160
160
  class << self
161
161
 
@@ -189,13 +189,17 @@ module ActiveRecord
189
189
  end
190
190
 
191
191
  def database_version
192
- @database_version ||= select_value('SELECT @@version')
192
+ @database_version ||= info_schema_query { select_value('SELECT @@version') }
193
193
  end
194
194
 
195
195
  def database_year
196
196
  DATABASE_VERSION_REGEXP.match(database_version)[1].to_i
197
197
  end
198
198
 
199
+ def sqlserver?
200
+ true
201
+ end
202
+
199
203
  def sqlserver_2000?
200
204
  database_year == 2000
201
205
  end
@@ -260,7 +264,7 @@ module ActiveRecord
260
264
 
261
265
  def quoted_date(value)
262
266
  if value.acts_like?(:time) && value.respond_to?(:usec)
263
- "#{super}.#{sprintf("%06d",value.usec)[0..2]}"
267
+ "#{super}.#{sprintf("%03d",value.usec/1000)}"
264
268
  else
265
269
  super
266
270
  end
@@ -448,18 +452,20 @@ module ActiveRecord
448
452
  end
449
453
 
450
454
  def tables(name = nil)
451
- select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME <> 'dtproperties'"
455
+ info_schema_query do
456
+ select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME <> 'dtproperties'"
457
+ end
452
458
  end
453
459
 
454
460
  def views(name = nil)
455
461
  @sqlserver_views_cache ||=
456
- select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME NOT IN ('sysconstraints','syssegments')"
462
+ info_schema_query { select_values("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME NOT IN ('sysconstraints','syssegments')") }
457
463
  end
458
464
 
459
465
  def view_information(table_name)
460
466
  table_name = unqualify_table_name(table_name)
461
467
  @sqlserver_view_information_cache[table_name] ||=
462
- select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'"
468
+ info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
463
469
  end
464
470
 
465
471
  def view_table_name(table_name)
@@ -667,6 +673,10 @@ module ActiveRecord
667
673
  select_value('SELECT @@ROWCOUNT AS AffectedRows')
668
674
  end
669
675
 
676
+ def info_schema_query
677
+ log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
678
+ end
679
+
670
680
  def raw_execute(sql, name = nil, &block)
671
681
  log(sql, name) do
672
682
  if block_given?
@@ -732,7 +742,7 @@ module ActiveRecord
732
742
  # SCHEMA STATEMENTS ========================================#
733
743
 
734
744
  def remove_check_constraints(table_name, column_name)
735
- constraints = select_values("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'")
745
+ constraints = info_schema_query { select_values("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'") }
736
746
  constraints.each do |constraint|
737
747
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
738
748
  end
@@ -888,7 +898,7 @@ module ActiveRecord
888
898
  WHERE columns.TABLE_NAME = '#{table_name}'
889
899
  ORDER BY columns.ordinal_position
890
900
  }.gsub(/[ \t\r\n]+/,' ')
891
- results = without_type_conversion { select(sql,nil,true) }
901
+ results = info_schema_query { without_type_conversion{ select(sql,nil,true) } }
892
902
  results.collect do |ci|
893
903
  ci.symbolize_keys!
894
904
  ci[:type] = case ci[:type]
@@ -905,7 +915,7 @@ module ActiveRecord
905
915
  real_table_name = table_name_or_views_table_name(table_name)
906
916
  real_column_name = views_real_column_name(table_name,ci[:name])
907
917
  col_default_sql = "SELECT c.COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = '#{real_table_name}' AND c.COLUMN_NAME = '#{real_column_name}'"
908
- ci[:default_value] = without_type_conversion { select_value(col_default_sql) }
918
+ ci[:default_value] = info_schema_query { without_type_conversion{ select_value(col_default_sql) } }
909
919
  end
910
920
  ci[:default_value] = case ci[:default_value]
911
921
  when nil, '(null)', '(NULL)'
@@ -29,36 +29,40 @@ module ActiveRecord
29
29
  end
30
30
 
31
31
  def reset_column_information_with_sqlserver_cache_support
32
- connection.send(:initialize_sqlserver_caches)
32
+ connection.send(:initialize_sqlserver_caches) if connection.respond_to?(:sqlserver?)
33
33
  reset_column_information_without_sqlserver_cache_support
34
34
  end
35
35
 
36
36
  private
37
37
 
38
38
  def add_order_with_sqlserver_unique_checking!(sql, order, scope = :auto)
39
- order_sql = ''
40
- add_order_without_sqlserver_unique_checking!(order_sql, order, scope)
41
- unless order_sql.blank?
42
- unique_order_hash = {}
43
- select_table_name = connection.send(:get_table_name,sql)
44
- select_table_name.tr!('[]','') if select_table_name
45
- orders_and_dirs_set = connection.send(:orders_and_dirs_set,order_sql)
46
- unique_order_sql = orders_and_dirs_set.inject([]) do |array,order_dir|
47
- ord, dir = order_dir
48
- ord_tn_and_cn = ord.to_s.split('.').map{|o|o.tr('[]','')}
49
- ord_table_name, ord_column_name = if ord_tn_and_cn.size > 1
50
- ord_tn_and_cn
51
- else
52
- [nil, ord_tn_and_cn.first]
53
- end
54
- if (ord_table_name && ord_table_name == select_table_name && unique_order_hash[ord_column_name]) || unique_order_hash[ord_column_name]
55
- array
56
- else
57
- unique_order_hash[ord_column_name] = true
58
- array << "#{ord} #{dir}".strip
59
- end
60
- end.join(', ')
61
- sql << " ORDER BY #{unique_order_sql}"
39
+ if connection.respond_to?(:sqlserver?)
40
+ order_sql = ''
41
+ add_order_without_sqlserver_unique_checking!(order_sql, order, scope)
42
+ unless order_sql.blank?
43
+ unique_order_hash = {}
44
+ select_table_name = connection.send(:get_table_name,sql)
45
+ select_table_name.tr!('[]','') if select_table_name
46
+ orders_and_dirs_set = connection.send(:orders_and_dirs_set,order_sql)
47
+ unique_order_sql = orders_and_dirs_set.inject([]) do |array,order_dir|
48
+ ord, dir = order_dir
49
+ ord_tn_and_cn = ord.to_s.split('.').map{|o|o.tr('[]','')}
50
+ ord_table_name, ord_column_name = if ord_tn_and_cn.size > 1
51
+ ord_tn_and_cn
52
+ else
53
+ [nil, ord_tn_and_cn.first]
54
+ end
55
+ if (ord_table_name && ord_table_name == select_table_name && unique_order_hash[ord_column_name]) || unique_order_hash[ord_column_name]
56
+ array
57
+ else
58
+ unique_order_hash[ord_column_name] = true
59
+ array << "#{ord} #{dir}".strip
60
+ end
61
+ end.join(', ')
62
+ sql << " ORDER BY #{unique_order_sql}"
63
+ end
64
+ else
65
+ add_order_without_sqlserver_unique_checking!(order_sql, order, scope)
62
66
  end
63
67
  end
64
68
 
data/lib/core_ext/dbi.rb CHANGED
@@ -13,20 +13,24 @@ module SQLServerDBI
13
13
  end
14
14
  end
15
15
 
16
-
17
16
  module Type
18
17
 
19
18
  # Make sure we get DBI::Type::Timestamp returning a string NOT a time object
20
- # that represents what is in the DB before type casting and let the adapter
21
- # do the reset. DBI::DBD::ODBC will typically return a string like:
19
+ # that represents what is in the DB before type casting while letting core
20
+ # ActiveRecord do the reset. It is assumed that DBI is using ODBC connections
21
+ # and that ODBC::Timestamp is taking the native milliseconds that SQL Server
22
+ # stores and returning them incorrect using ODBC::Timestamp#fraction which is
23
+ # nanoseconds. Below shows the incorrect ODBC::Timestamp represented by DBI
24
+ # and the conversion we expect to have in the DB before type casting.
25
+ #
22
26
  # "1985-04-15 00:00:00 0" # => "1985-04-15 00:00:00.000"
23
- # "2008-11-08 10:24:36 547000000" # => "2008-11-08 10:24:36.547"
24
- # "2008-11-08 10:24:36 123000000" # => "2008-11-08 10:24:36.000"
27
+ # "2008-11-08 10:24:36 300000000" # => "2008-11-08 10:24:36.003"
28
+ # "2008-11-08 10:24:36 123000000" # => "2008-11-08 10:24:36.123"
25
29
  class SqlserverTimestamp
26
30
  def self.parse(obj)
27
31
  return nil if ::DBI::Type::Null.parse(obj).nil?
28
- date, time, fraction = obj.split(' ')
29
- "#{date} #{time}.#{sprintf("%03d",fraction)}"
32
+ date, time, nanoseconds = obj.split(' ')
33
+ "#{date} #{time}.#{sprintf("%03d",nanoseconds.to_i/1000000)}"
30
34
  end
31
35
  end
32
36
 
@@ -199,22 +199,55 @@ class AdapterTestSqlserver < ActiveRecord::TestCase
199
199
  context 'For chronic data types' do
200
200
 
201
201
  context 'with a usec' do
202
-
202
+
203
203
  setup do
204
204
  @time = Time.now
205
+ @db_datetime_003 = '2012-11-08 10:24:36.003'
206
+ @db_datetime_123 = '2012-11-08 10:24:36.123'
207
+ @all_datetimes = [@db_datetime_003, @db_datetime_123]
208
+ @all_datetimes.each do |datetime|
209
+ @connection.execute("INSERT INTO [sql_server_chronics] ([datetime]) VALUES('#{datetime}')")
210
+ end
205
211
  end
206
212
 
207
- should 'truncate 123456 usec to just 123' do
208
- @time.stubs(:usec).returns(123456)
209
- saved = SqlServerChronic.create!(:datetime => @time).reload
210
- assert_equal 123000, saved.datetime.usec
213
+ teardown do
214
+ @all_datetimes.each do |datetime|
215
+ @connection.execute("DELETE FROM [sql_server_chronics] WHERE [datetime] = '#{datetime}'")
216
+ end
211
217
  end
212
218
 
213
- should 'drop 123 to 0' do
214
- @time.stubs(:usec).returns(123)
215
- saved = SqlServerChronic.create!(:datetime => @time).reload
216
- assert_equal 0, saved.datetime.usec
217
- assert_equal '000', saved.datetime_before_type_cast.split('.').last
219
+ context 'finding existing DB objects' do
220
+
221
+ should 'find 003 millisecond in the DB with before and after casting' do
222
+ existing_003 = SqlServerChronic.find_by_datetime!(@db_datetime_003)
223
+ assert_equal @db_datetime_003, existing_003.datetime_before_type_cast
224
+ assert_equal 3000, existing_003.datetime.usec, 'A 003 millisecond in SQL Server is 3000 milliseconds'
225
+ end
226
+
227
+ should 'find 123 millisecond in the DB with before and after casting' do
228
+ existing_123 = SqlServerChronic.find_by_datetime!(@db_datetime_123)
229
+ assert_equal @db_datetime_123, existing_123.datetime_before_type_cast
230
+ assert_equal 123000, existing_123.datetime.usec, 'A 123 millisecond in SQL Server is 123000 milliseconds'
231
+ end
232
+
233
+ end
234
+
235
+ context 'saving new datetime objects' do
236
+
237
+ should 'truncate 123456 usec to just 123 in the DB cast back to 123000' do
238
+ @time.stubs(:usec).returns(123456)
239
+ saved = SqlServerChronic.create!(:datetime => @time).reload
240
+ assert_equal '123', saved.datetime_before_type_cast.split('.')[1]
241
+ assert_equal 123000, saved.datetime.usec
242
+ end
243
+
244
+ should 'truncate 3001 usec to just 003 in the DB cast back to 3000' do
245
+ @time.stubs(:usec).returns(3001)
246
+ saved = SqlServerChronic.create!(:datetime => @time).reload
247
+ assert_equal '003', saved.datetime_before_type_cast.split('.')[1]
248
+ assert_equal 3000, saved.datetime.usec
249
+ end
250
+
218
251
  end
219
252
 
220
253
  end
@@ -440,10 +473,6 @@ class AdapterTestSqlserver < ActiveRecord::TestCase
440
473
  end
441
474
 
442
475
  should 'find default values' do
443
- # col_default_sql = "SELECT c.COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = 'string_defaults' AND c.COLUMN_NAME = 'string_with_pretend_null_one'"
444
- # raise @connection.select_value(col_default_sql).inspect
445
- # raise @connection.without_type_conversion{ @connection.select_value(col_default_sql) }.inspect
446
-
447
476
  assert_equal 'null', StringDefaultsView.new.pretend_null,
448
477
  StringDefaultsView.columns_hash['pretend_null'].inspect
449
478
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-sqlserver-2000-2005-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.4
4
+ version: 2.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken Collins
@@ -13,7 +13,7 @@ autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
15
 
16
- date: 2008-11-25 00:00:00 -08:00
16
+ date: 2009-01-04 00:00:00 -08:00
17
17
  default_executable:
18
18
  dependencies: []
19
19