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 +14 -0
- data/README.rdoc +8 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +21 -11
- data/lib/core_ext/active_record.rb +28 -24
- data/lib/core_ext/dbi.rb +11 -7
- data/test/cases/adapter_test_sqlserver.rb +43 -14
- metadata +2 -2
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
|
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.
|
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("%
|
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
|
-
|
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
|
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
|
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 =
|
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] =
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
21
|
-
# do the reset. DBI
|
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
|
24
|
-
# "2008-11-08 10:24:36 123000000" # => "2008-11-08 10:24:36.
|
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,
|
29
|
-
"#{date} #{time}.#{sprintf("%03d",
|
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
|
-
|
208
|
-
@
|
209
|
-
|
210
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
+
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:
|
16
|
+
date: 2009-01-04 00:00:00 -08:00
|
17
17
|
default_executable:
|
18
18
|
dependencies: []
|
19
19
|
|