rails-sqlserver-2000-2005-adapter 1.0.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/CHANGELOG +25 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +0 -0
- data/RUNNING_UNIT_TESTS +60 -0
- data/Rakefile +95 -0
- data/autotest/discover.rb +4 -0
- data/autotest/railssqlserver.rb +16 -0
- data/autotest/sqlserver.rb +54 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +913 -0
- data/lib/core_ext/active_record.rb +71 -0
- data/lib/core_ext/dbi.rb +83 -0
- data/test/cases/aaaa_create_tables_test_sqlserver.rb +19 -0
- data/test/cases/adapter_test_sqlserver.rb +428 -0
- data/test/cases/basics_test_sqlserver.rb +21 -0
- data/test/cases/calculations_test_sqlserver.rb +20 -0
- data/test/cases/column_test_sqlserver.rb +66 -0
- data/test/cases/connection_test_sqlserver.rb +103 -0
- data/test/cases/eager_association_test_sqlserver.rb +22 -0
- data/test/cases/inheritance_test_sqlserver.rb +28 -0
- data/test/cases/migration_test_sqlserver.rb +57 -0
- data/test/cases/offset_and_limit_test_sqlserver.rb +82 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +100 -0
- data/test/cases/query_cache_test_sqlserver.rb +24 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +40 -0
- data/test/cases/specific_schema_test_sqlserver.rb +25 -0
- data/test/cases/sqlserver_helper.rb +88 -0
- data/test/connections/native_sqlserver/connection.rb +23 -0
- data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/schema/sqlserver_specific_schema.rb +38 -0
- metadata +96 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module SQLServerActiveRecordExtensions
|
4
|
+
|
5
|
+
def self.included(klass)
|
6
|
+
klass.extend ClassMethods
|
7
|
+
class << klass
|
8
|
+
alias_method_chain :reset_column_information, :sqlserver_columns_cache_support
|
9
|
+
alias_method_chain :add_order!, :sqlserver_unique_checking
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
def coerce_sqlserver_date(*attributes)
|
16
|
+
write_inheritable_attribute :coerced_sqlserver_date_columns, Set.new(attributes.map(&:to_s))
|
17
|
+
end
|
18
|
+
|
19
|
+
def coerce_sqlserver_time(*attributes)
|
20
|
+
write_inheritable_attribute :coerced_sqlserver_time_columns, Set.new(attributes.map(&:to_s))
|
21
|
+
end
|
22
|
+
|
23
|
+
def coerced_sqlserver_date_columns
|
24
|
+
read_inheritable_attribute(:coerced_sqlserver_date_columns) || []
|
25
|
+
end
|
26
|
+
|
27
|
+
def coerced_sqlserver_time_columns
|
28
|
+
read_inheritable_attribute(:coerced_sqlserver_time_columns) || []
|
29
|
+
end
|
30
|
+
|
31
|
+
def reset_column_information_with_sqlserver_columns_cache_support
|
32
|
+
connection.instance_variable_set :@sqlserver_columns_cache, {}
|
33
|
+
reset_column_information_without_sqlserver_columns_cache_support
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
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}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
ActiveRecord::Base.send :include, ActiveRecord::ConnectionAdapters::SQLServerActiveRecordExtensions
|
data/lib/core_ext/dbi.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
module SQLServerDBI
|
3
|
+
|
4
|
+
module Timestamp
|
5
|
+
# Will further change DBI::Timestamp #to_s return value by limiting the usec of
|
6
|
+
# the time to 3 digits and in some cases adding zeros if needed. For example:
|
7
|
+
# "1985-04-15 00:00:00.0" # => "1985-04-15 00:00:00.000"
|
8
|
+
# "2008-11-08 10:24:36.547000" # => "2008-11-08 10:24:36.547"
|
9
|
+
# "2008-11-08 10:24:36.123" # => "2008-11-08 10:24:36.000"
|
10
|
+
def to_sqlserver_string
|
11
|
+
datetime, usec = to_s[0..22].split('.')
|
12
|
+
"#{datetime}.#{sprintf("%03d",usec)}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
module Type
|
18
|
+
|
19
|
+
# 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:
|
22
|
+
# "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"
|
25
|
+
class SqlserverTimestamp
|
26
|
+
def self.parse(obj)
|
27
|
+
return nil if ::DBI::Type::Null.parse(obj).nil?
|
28
|
+
date, time, fraction = obj.split(' ')
|
29
|
+
"#{date} #{time}.#{sprintf("%03d",fraction)}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# The adapter and rails will parse our floats, decimals, and money field correctly
|
34
|
+
# from a string. Do not let the DBI::Type classes create Float/BigDecimal objects
|
35
|
+
# for us. Trust rails .type_cast to do what it is built to do.
|
36
|
+
class SqlserverForcedString
|
37
|
+
def self.parse(obj)
|
38
|
+
return nil if ::DBI::Type::Null.parse(obj).nil?
|
39
|
+
obj.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
module TypeUtil
|
46
|
+
|
47
|
+
def self.included(klass)
|
48
|
+
klass.extend ClassMethods
|
49
|
+
class << klass
|
50
|
+
alias_method_chain :type_name_to_module, :sqlserver_types
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module ClassMethods
|
55
|
+
|
56
|
+
# Capture all types classes that we need to handle directly for SQL Server
|
57
|
+
# and allow normal processing for those that we do not.
|
58
|
+
def type_name_to_module_with_sqlserver_types(type_name)
|
59
|
+
case type_name
|
60
|
+
when /^timestamp$/i
|
61
|
+
DBI::Type::SqlserverTimestamp
|
62
|
+
when /^float|decimal|money$/i
|
63
|
+
DBI::Type::SqlserverForcedString
|
64
|
+
else
|
65
|
+
type_name_to_module_without_sqlserver_types(type_name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
if defined?(DBI::TypeUtil)
|
78
|
+
DBI::Type.send :include, SQLServerDBI::Type
|
79
|
+
DBI::TypeUtil.send :include, SQLServerDBI::TypeUtil
|
80
|
+
elsif defined?(DBI::Timestamp) # DEPRECATED in DBI 0.4.0 and above. Remove when 0.2.2 and lower is no longer supported.
|
81
|
+
DBI::Timestamp.send :include, SQLServerDBI::Timestamp
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# The filename begins with "aaaa" to ensure this is the first test.
|
2
|
+
require 'cases/sqlserver_helper'
|
3
|
+
|
4
|
+
class AAAACreateTablesTestSqlserver < ActiveRecord::TestCase
|
5
|
+
self.use_transactional_fixtures = false
|
6
|
+
|
7
|
+
should 'load activerecord schema' do
|
8
|
+
schema_file = "#{ACTIVERECORD_TEST_ROOT}/schema/schema.rb"
|
9
|
+
eval(File.read(schema_file))
|
10
|
+
assert true
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'load sqlserver specific schema' do
|
14
|
+
sqlserver_specific_schema_file = "#{SQLSERVER_SCHEMA_ROOT}/sqlserver_specific_schema.rb"
|
15
|
+
eval(File.read(sqlserver_specific_schema_file))
|
16
|
+
assert true
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,428 @@
|
|
1
|
+
require 'cases/sqlserver_helper'
|
2
|
+
require 'models/task'
|
3
|
+
require 'models/reply'
|
4
|
+
require 'models/joke'
|
5
|
+
require 'models/subscriber'
|
6
|
+
|
7
|
+
class AdapterTestSqlserver < ActiveRecord::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@connection = ActiveRecord::Base.connection
|
11
|
+
@basic_insert_sql = "INSERT INTO [funny_jokes] ([name]) VALUES('Knock knock')"
|
12
|
+
@basic_update_sql = "UPDATE [customers] SET [address_street] = NULL WHERE [id] = 2"
|
13
|
+
@basic_select_sql = "SELECT * FROM [customers] WHERE ([customers].[id] = 1)"
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'For abstract behavior' do
|
17
|
+
|
18
|
+
should 'have a 128 max #table_alias_length' do
|
19
|
+
assert @connection.table_alias_length <= 128
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'raise invalid statement error' do
|
23
|
+
assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.update("UPDATE XXX") }
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'be our adapter_name' do
|
27
|
+
assert_equal 'SQLServer', @connection.adapter_name
|
28
|
+
end
|
29
|
+
|
30
|
+
should 'support migrations' do
|
31
|
+
assert @connection.supports_migrations?
|
32
|
+
end
|
33
|
+
|
34
|
+
should 'support DDL in transactions' do
|
35
|
+
assert @connection.supports_ddl_transactions?
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'for database version' do
|
39
|
+
|
40
|
+
setup do
|
41
|
+
@version_regexp = ActiveRecord::ConnectionAdapters::SQLServerAdapter::DATABASE_VERSION_REGEXP
|
42
|
+
@supported_version = ActiveRecord::ConnectionAdapters::SQLServerAdapter::SUPPORTED_VERSIONS
|
43
|
+
@sqlserver_2000_string = "Microsoft SQL Server 2000 - 8.00.2039 (Intel X86)"
|
44
|
+
@sqlserver_2005_string = "Microsoft SQL Server 2005 - 9.00.3215.00 (Intel X86)"
|
45
|
+
end
|
46
|
+
|
47
|
+
should 'return a string from #database_version that matches class regexp' do
|
48
|
+
assert_match @version_regexp, @connection.database_version
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'return a 4 digit year fixnum for #database_year' do
|
52
|
+
assert_instance_of Fixnum, @connection.database_year
|
53
|
+
assert_contains @supported_version, @connection.database_year
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'return true to #sqlserver_2000?' do
|
57
|
+
@connection.stubs(:database_version).returns(@sqlserver_2000_string)
|
58
|
+
assert @connection.sqlserver_2000?
|
59
|
+
end
|
60
|
+
|
61
|
+
should 'return true to #sqlserver_2005?' do
|
62
|
+
@connection.stubs(:database_version).returns(@sqlserver_2005_string)
|
63
|
+
assert @connection.sqlserver_2005?
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'for #unqualify_table_name and #unqualify_db_name' do
|
69
|
+
|
70
|
+
setup do
|
71
|
+
@expected_table_name = 'baz'
|
72
|
+
@expected_db_name = 'foo'
|
73
|
+
@first_second_table_names = ['[baz]','baz','[bar].[baz]','bar.baz']
|
74
|
+
@third_table_names = ['[foo].[bar].[baz]','foo.bar.baz']
|
75
|
+
@qualifed_table_names = @first_second_table_names + @third_table_names
|
76
|
+
end
|
77
|
+
|
78
|
+
should 'return clean table_name from #unqualify_table_name' do
|
79
|
+
@qualifed_table_names.each do |qtn|
|
80
|
+
assert_equal @expected_table_name,
|
81
|
+
@connection.send(:unqualify_table_name,qtn),
|
82
|
+
"This qualifed_table_name #{qtn} did not unqualify correctly."
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
should 'return nil from #unqualify_db_name when table_name is less than 2 qualified' do
|
87
|
+
@first_second_table_names.each do |qtn|
|
88
|
+
assert_equal nil, @connection.send(:unqualify_db_name,qtn),
|
89
|
+
"This qualifed_table_name #{qtn} did not return nil."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
should 'return clean db_name from #unqualify_db_name when table is thrid level qualified' do
|
94
|
+
@third_table_names.each do |qtn|
|
95
|
+
assert_equal @expected_db_name,
|
96
|
+
@connection.send(:unqualify_db_name,qtn),
|
97
|
+
"This qualifed_table_name #{qtn} did not unqualify the db_name correctly."
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
should 'return true to #insert_sql? for inserts only' do
|
104
|
+
assert @connection.send(:insert_sql?,'INSERT...')
|
105
|
+
assert !@connection.send(:insert_sql?,'UPDATE...')
|
106
|
+
assert !@connection.send(:insert_sql?,'SELECT...')
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'for #sql_for_association_limiting?' do
|
110
|
+
|
111
|
+
should 'return false for simple selects with no GROUP BY and ORDER BY' do
|
112
|
+
assert !sql_for_association_limiting?("SELECT * FROM [posts]")
|
113
|
+
end
|
114
|
+
|
115
|
+
should 'return true to single SELECT, ideally a table/primarykey, that also has a GROUP BY and ORDER BY' do
|
116
|
+
assert sql_for_association_limiting?("SELECT [posts].id FROM...GROUP BY [posts].id ORDER BY MIN(posts.id)")
|
117
|
+
end
|
118
|
+
|
119
|
+
should 'return false to single * wildcard SELECT that also has a GROUP BY and ORDER BY' do
|
120
|
+
assert !sql_for_association_limiting?("SELECT * FROM...GROUP BY [posts].id ORDER BY MIN(posts.id)")
|
121
|
+
end
|
122
|
+
|
123
|
+
should 'return false to multiple columns in the select even when GROUP BY and ORDER BY are present' do
|
124
|
+
sql = "SELECT [accounts].credit_limit, firm_id FROM...GROUP BY firm_id ORDER BY firm_id"
|
125
|
+
assert !sql_for_association_limiting?(sql)
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'for #get_table_name' do
|
131
|
+
|
132
|
+
should 'return quoted table name from basic INSERT, UPDATE and SELECT statements' do
|
133
|
+
assert_equal '[funny_jokes]', @connection.send(:get_table_name,@basic_insert_sql)
|
134
|
+
assert_equal '[customers]', @connection.send(:get_table_name,@basic_update_sql)
|
135
|
+
assert_equal '[customers]', @connection.send(:get_table_name,@basic_select_sql)
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'dealing with various orders SQL snippets' do
|
141
|
+
|
142
|
+
setup do
|
143
|
+
@single_order = 'comments.id'
|
144
|
+
@single_order_with_desc = 'comments.id DESC'
|
145
|
+
@two_orders = 'comments.id, comments.post_id'
|
146
|
+
@two_orders_with_asc = 'comments.id, comments.post_id ASC'
|
147
|
+
@two_orders_with_desc_and_asc = 'comments.id DESC, comments.post_id ASC'
|
148
|
+
@two_duplicate_order_with_dif_dir = "id, id DESC"
|
149
|
+
end
|
150
|
+
|
151
|
+
should 'convert to an 2D array of column/direction arrays using #orders_and_dirs_set' do
|
152
|
+
assert_equal [['comments.id',nil]], orders_and_dirs_set('ORDER BY comments.id'), 'Needs to remove ORDER BY'
|
153
|
+
assert_equal [['comments.id',nil]], orders_and_dirs_set(@single_order)
|
154
|
+
assert_equal [['comments.id',nil],['comments.post_id',nil]], orders_and_dirs_set(@two_orders)
|
155
|
+
assert_equal [['comments.id',nil],['comments.post_id','ASC']], orders_and_dirs_set(@two_orders_with_asc)
|
156
|
+
assert_equal [['id',nil],['id','DESC']], orders_and_dirs_set(@two_duplicate_order_with_dif_dir)
|
157
|
+
end
|
158
|
+
|
159
|
+
should 'remove duplicate or maintain the same order by statements giving precedence to first using #add_order! method chain extension' do
|
160
|
+
assert_equal ' ORDER BY comments.id', add_order!(@single_order)
|
161
|
+
assert_equal ' ORDER BY comments.id DESC', add_order!(@single_order_with_desc)
|
162
|
+
assert_equal ' ORDER BY comments.id, comments.post_id', add_order!(@two_orders)
|
163
|
+
assert_equal ' ORDER BY comments.id DESC, comments.post_id ASC', add_order!(@two_orders_with_desc_and_asc)
|
164
|
+
assert_equal 'SELECT * FROM [developers] ORDER BY id', add_order!('id, developers.id DESC','SELECT * FROM [developers]')
|
165
|
+
assert_equal 'SELECT * FROM [developers] ORDER BY [developers].[id] DESC', add_order!('[developers].[id] DESC, id','SELECT * FROM [developers]')
|
166
|
+
end
|
167
|
+
|
168
|
+
should 'take all types of order options and convert them to MIN functions using #order_to_min_set' do
|
169
|
+
assert_equal 'MIN(comments.id)', order_to_min_set(@single_order)
|
170
|
+
assert_equal 'MIN(comments.id), MIN(comments.post_id)', order_to_min_set(@two_orders)
|
171
|
+
assert_equal 'MIN(comments.id) DESC', order_to_min_set(@single_order_with_desc)
|
172
|
+
assert_equal 'MIN(comments.id), MIN(comments.post_id) ASC', order_to_min_set(@two_orders_with_asc)
|
173
|
+
assert_equal 'MIN(comments.id) DESC, MIN(comments.post_id) ASC', order_to_min_set(@two_orders_with_desc_and_asc)
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'with different language' do
|
179
|
+
|
180
|
+
teardown do
|
181
|
+
@connection.execute("SET LANGUAGE us_english") rescue nil
|
182
|
+
end
|
183
|
+
|
184
|
+
should_eventually 'do a date insertion when language is german' do
|
185
|
+
@connection.execute("SET LANGUAGE deutsch")
|
186
|
+
assert_nothing_raised do
|
187
|
+
Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31))
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'For chronic data types' do
|
196
|
+
|
197
|
+
context 'with a usec' do
|
198
|
+
|
199
|
+
setup do
|
200
|
+
@time = Time.now
|
201
|
+
end
|
202
|
+
|
203
|
+
should 'truncate 123456 usec to just 123' do
|
204
|
+
@time.stubs(:usec).returns(123456)
|
205
|
+
saved = SqlServerChronic.create!(:datetime => @time).reload
|
206
|
+
assert_equal 123000, saved.datetime.usec
|
207
|
+
end
|
208
|
+
|
209
|
+
should 'drop 123 to 0' do
|
210
|
+
@time.stubs(:usec).returns(123)
|
211
|
+
saved = SqlServerChronic.create!(:datetime => @time).reload
|
212
|
+
assert_equal 0, saved.datetime.usec
|
213
|
+
assert_equal '000', saved.datetime_before_type_cast.split('.').last
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
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
|
+
end
|
246
|
+
|
247
|
+
context 'For identity inserts' do
|
248
|
+
|
249
|
+
setup do
|
250
|
+
@identity_insert_sql = "INSERT INTO [funny_jokes] ([id],[name]) VALUES(420,'Knock knock')"
|
251
|
+
end
|
252
|
+
|
253
|
+
should 'return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id_column' do
|
254
|
+
assert_equal '[funny_jokes]', @connection.send(:query_requires_identity_insert?,@identity_insert_sql)
|
255
|
+
end
|
256
|
+
|
257
|
+
should 'return false to #query_requires_identity_insert? for normal SQL' do
|
258
|
+
[@basic_insert_sql, @basic_update_sql, @basic_select_sql].each do |sql|
|
259
|
+
assert !@connection.send(:query_requires_identity_insert?,sql), "SQL was #{sql}"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
should 'find identity column using #identity_column' do
|
264
|
+
joke_id_column = Joke.columns.detect { |c| c.name == 'id' }
|
265
|
+
assert_equal joke_id_column, @connection.send(:identity_column,Joke.table_name)
|
266
|
+
end
|
267
|
+
|
268
|
+
should 'return nil when calling #identity_column for a table_name with no identity' do
|
269
|
+
assert_nil @connection.send(:identity_column,Subscriber.table_name)
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
context 'For Quoting' do
|
275
|
+
|
276
|
+
should 'return 1 for #quoted_true' do
|
277
|
+
assert_equal '1', @connection.quoted_true
|
278
|
+
end
|
279
|
+
|
280
|
+
should 'return 0 for #quoted_false' do
|
281
|
+
assert_equal '0', @connection.quoted_false
|
282
|
+
end
|
283
|
+
|
284
|
+
should 'not escape backslash characters like abstract adapter' do
|
285
|
+
string_with_backslashs = "\\n"
|
286
|
+
assert_equal string_with_backslashs, @connection.quote_string(string_with_backslashs)
|
287
|
+
end
|
288
|
+
|
289
|
+
should 'quote column names with brackets' do
|
290
|
+
assert_equal '[foo]', @connection.quote_column_name(:foo)
|
291
|
+
assert_equal '[foo]', @connection.quote_column_name('foo')
|
292
|
+
assert_equal '[foo].[bar]', @connection.quote_column_name('foo.bar')
|
293
|
+
end
|
294
|
+
|
295
|
+
should 'quote table names like columns' do
|
296
|
+
assert_equal '[foo].[bar]', @connection.quote_column_name('foo.bar')
|
297
|
+
assert_equal '[foo].[bar].[baz]', @connection.quote_column_name('foo.bar.baz')
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
context 'When disableing referential integrity' do
|
303
|
+
|
304
|
+
setup do
|
305
|
+
@parent = FkTestHasPk.create!
|
306
|
+
@member = FkTestHasFk.create!(:fk_id => @parent.id)
|
307
|
+
end
|
308
|
+
|
309
|
+
should 'NOT ALLOW by default the deletion of a referenced parent' do
|
310
|
+
assert_raise(ActiveRecord::StatementInvalid) { @parent.destroy }
|
311
|
+
end
|
312
|
+
|
313
|
+
should 'ALLOW deletion of referenced parent using #disable_referential_integrity block' do
|
314
|
+
assert_nothing_raised(ActiveRecord::StatementInvalid) do
|
315
|
+
FkTestHasPk.connection.disable_referential_integrity { @parent.destroy }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
should 'again NOT ALLOW deletion of referenced parent after #disable_referential_integrity block' do
|
320
|
+
assert_raise(ActiveRecord::StatementInvalid) do
|
321
|
+
FkTestHasPk.connection.disable_referential_integrity { }
|
322
|
+
@parent.destroy
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
|
328
|
+
context 'For DatabaseStatements' do
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
context 'For SchemaStatements' do
|
333
|
+
|
334
|
+
context 'returning from #type_to_sql' do
|
335
|
+
|
336
|
+
should 'create integers when no limit supplied' do
|
337
|
+
assert_equal 'integer', @connection.type_to_sql(:integer)
|
338
|
+
end
|
339
|
+
|
340
|
+
should 'create integers when limit is 4' do
|
341
|
+
assert_equal 'integer', @connection.type_to_sql(:integer, 4)
|
342
|
+
end
|
343
|
+
|
344
|
+
should 'create integers when limit is 3' do
|
345
|
+
assert_equal 'integer', @connection.type_to_sql(:integer, 3)
|
346
|
+
end
|
347
|
+
|
348
|
+
should 'create smallints when limit is less than 3' do
|
349
|
+
assert_equal 'smallint', @connection.type_to_sql(:integer, 2)
|
350
|
+
assert_equal 'smallint', @connection.type_to_sql(:integer, 1)
|
351
|
+
end
|
352
|
+
|
353
|
+
should 'create bigints when limit is greateer than 4' do
|
354
|
+
assert_equal 'bigint', @connection.type_to_sql(:integer, 5)
|
355
|
+
assert_equal 'bigint', @connection.type_to_sql(:integer, 6)
|
356
|
+
assert_equal 'bigint', @connection.type_to_sql(:integer, 7)
|
357
|
+
assert_equal 'bigint', @connection.type_to_sql(:integer, 8)
|
358
|
+
end
|
359
|
+
|
360
|
+
end
|
361
|
+
|
362
|
+
end
|
363
|
+
|
364
|
+
context 'For indexes' do
|
365
|
+
|
366
|
+
setup do
|
367
|
+
@desc_index_name = 'idx_credit_limit_test_desc'
|
368
|
+
@connection.execute "CREATE INDEX #{@desc_index_name} ON accounts (credit_limit DESC)"
|
369
|
+
end
|
370
|
+
|
371
|
+
teardown do
|
372
|
+
@connection.execute "DROP INDEX accounts.#{@desc_index_name}"
|
373
|
+
end
|
374
|
+
|
375
|
+
should 'have indexes with descending order' do
|
376
|
+
assert @connection.indexes('accounts').detect { |i| i.name == @desc_index_name }
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|
380
|
+
|
381
|
+
|
382
|
+
private
|
383
|
+
|
384
|
+
def sql_for_association_limiting?(sql)
|
385
|
+
@connection.send :sql_for_association_limiting?, sql
|
386
|
+
end
|
387
|
+
|
388
|
+
def orders_and_dirs_set(order)
|
389
|
+
@connection.send :orders_and_dirs_set, order
|
390
|
+
end
|
391
|
+
|
392
|
+
def add_order!(order,sql='')
|
393
|
+
ActiveRecord::Base.send :add_order!, sql, order, nil
|
394
|
+
sql
|
395
|
+
end
|
396
|
+
|
397
|
+
def order_to_min_set(order)
|
398
|
+
@connection.send :order_to_min_set, order
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
|
404
|
+
class AdapterTest < ActiveRecord::TestCase
|
405
|
+
|
406
|
+
COERCED_TESTS = [
|
407
|
+
:test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas,
|
408
|
+
:test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
|
409
|
+
]
|
410
|
+
|
411
|
+
include SqlserverCoercedTest
|
412
|
+
|
413
|
+
def test_coerced_test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
|
414
|
+
sql_inject = "1 select * from schema"
|
415
|
+
connection = ActiveRecord::Base.connection
|
416
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject) }
|
417
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) }
|
418
|
+
end
|
419
|
+
|
420
|
+
def test_coerced_test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
|
421
|
+
sql_inject = "1, 7 procedure help()"
|
422
|
+
connection = ActiveRecord::Base.connection
|
423
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject) }
|
424
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7) }
|
425
|
+
assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) }
|
426
|
+
end
|
427
|
+
|
428
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'cases/sqlserver_helper'
|
2
|
+
require 'models/developer'
|
3
|
+
|
4
|
+
class BasicsTestSqlserver < ActiveRecord::TestCase
|
5
|
+
end
|
6
|
+
|
7
|
+
class BasicsTest < ActiveRecord::TestCase
|
8
|
+
|
9
|
+
COERCED_TESTS = [:test_read_attributes_before_type_cast_on_datetime]
|
10
|
+
|
11
|
+
include SqlserverCoercedTest
|
12
|
+
|
13
|
+
fixtures :developers
|
14
|
+
|
15
|
+
def test_coerced_test_read_attributes_before_type_cast_on_datetime
|
16
|
+
developer = Developer.find(:first)
|
17
|
+
assert_equal developer.created_at.to_s(:db)+'.000' , developer.attributes_before_type_cast["created_at"]
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'cases/sqlserver_helper'
|
2
|
+
require 'models/company'
|
3
|
+
|
4
|
+
class CalculationsTestSqlserver < ActiveRecord::TestCase
|
5
|
+
end
|
6
|
+
|
7
|
+
class CalculationsTest < ActiveRecord::TestCase
|
8
|
+
|
9
|
+
COERCED_TESTS = [:test_should_sum_expression]
|
10
|
+
|
11
|
+
include SqlserverCoercedTest
|
12
|
+
|
13
|
+
fixtures :accounts
|
14
|
+
|
15
|
+
def test_coerced_test_should_sum_expression
|
16
|
+
assert_equal 636, Account.sum("2 * credit_limit")
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
end
|