activerecord-sqlserver-adapter 2.3.24 → 3.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.
Files changed (45) hide show
  1. data/CHANGELOG +5 -108
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +33 -61
  4. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +57 -0
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +57 -0
  6. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  7. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +336 -0
  8. data/lib/active_record/connection_adapters/sqlserver/errors.rb +33 -0
  9. data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +17 -0
  10. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +61 -0
  11. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +373 -0
  12. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +131 -1121
  13. data/lib/arel/engines/sql/compilers/sqlserver_compiler.rb +267 -0
  14. metadata +26 -76
  15. data/RUNNING_UNIT_TESTS +0 -31
  16. data/Rakefile +0 -60
  17. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
  18. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
  19. data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
  20. data/test/cases/adapter_test_sqlserver.rb +0 -755
  21. data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
  22. data/test/cases/basics_test_sqlserver.rb +0 -86
  23. data/test/cases/calculations_test_sqlserver.rb +0 -20
  24. data/test/cases/column_test_sqlserver.rb +0 -354
  25. data/test/cases/connection_test_sqlserver.rb +0 -148
  26. data/test/cases/eager_association_test_sqlserver.rb +0 -42
  27. data/test/cases/execute_procedure_test_sqlserver.rb +0 -35
  28. data/test/cases/inheritance_test_sqlserver.rb +0 -28
  29. data/test/cases/method_scoping_test_sqlserver.rb +0 -28
  30. data/test/cases/migration_test_sqlserver.rb +0 -108
  31. data/test/cases/named_scope_test_sqlserver.rb +0 -21
  32. data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
  33. data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
  34. data/test/cases/query_cache_test_sqlserver.rb +0 -24
  35. data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
  36. data/test/cases/specific_schema_test_sqlserver.rb +0 -154
  37. data/test/cases/sqlserver_helper.rb +0 -140
  38. data/test/cases/table_name_test_sqlserver.rb +0 -38
  39. data/test/cases/transaction_test_sqlserver.rb +0 -93
  40. data/test/cases/unicode_test_sqlserver.rb +0 -54
  41. data/test/cases/validations_test_sqlserver.rb +0 -18
  42. data/test/connections/native_sqlserver/connection.rb +0 -26
  43. data/test/connections/native_sqlserver_odbc/connection.rb +0 -28
  44. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
  45. data/test/schema/sqlserver_specific_schema.rb +0 -113
@@ -1,151 +0,0 @@
1
- require 'active_record/version'
2
-
3
- module ActiveRecord
4
- module ConnectionAdapters
5
- module SQLServerCoreExtensions
6
-
7
-
8
- module ActiveRecord
9
-
10
- def self.included(klass)
11
- klass.extend ClassMethods
12
- class << klass
13
- alias_method_chain :reset_column_information, :sqlserver_cache_support
14
- alias_method_chain :add_order!, :sqlserver_unique_checking
15
- alias_method_chain :add_limit!, :sqlserver_order_checking
16
- end
17
- end
18
-
19
- module ClassMethods
20
-
21
- def execute_procedure(proc_name, *variables)
22
- if connection.respond_to?(:execute_procedure)
23
- connection.execute_procedure(proc_name,*variables)
24
- else
25
- []
26
- end
27
- end
28
-
29
- def coerce_sqlserver_date(*attributes)
30
- write_inheritable_attribute :coerced_sqlserver_date_columns, Set.new(attributes.map(&:to_s))
31
- end
32
-
33
- def coerce_sqlserver_time(*attributes)
34
- write_inheritable_attribute :coerced_sqlserver_time_columns, Set.new(attributes.map(&:to_s))
35
- end
36
-
37
- def coerced_sqlserver_date_columns
38
- read_inheritable_attribute(:coerced_sqlserver_date_columns) || []
39
- end
40
-
41
- def coerced_sqlserver_time_columns
42
- read_inheritable_attribute(:coerced_sqlserver_time_columns) || []
43
- end
44
-
45
- def reset_column_information_with_sqlserver_cache_support
46
- connection.send(:initialize_sqlserver_caches) if connection.respond_to?(:sqlserver?)
47
- reset_column_information_without_sqlserver_cache_support
48
- end
49
-
50
- private
51
-
52
- def add_limit_with_sqlserver_order_checking!(sql, options, scope = :auto)
53
- if connection.respond_to?(:sqlserver?)
54
- scope = scope(:find) if :auto == scope
55
- if scope
56
- options = options.dup
57
- scoped_order = scope[:order]
58
- order = options[:order]
59
- if order && scoped_order
60
- options[:order] = add_order_with_sqlserver_unique_checking!('', order, scope).gsub(/^ ORDER BY /,'')
61
- elsif scoped_order
62
- options[:order] = scoped_order
63
- end
64
- end
65
- end
66
- add_limit_without_sqlserver_order_checking!(sql, options, scope)
67
- end
68
-
69
- def add_order_with_sqlserver_unique_checking!(sql, order, scope = :auto)
70
- if connection.respond_to?(:sqlserver?)
71
- order_sql = ''
72
- add_order_without_sqlserver_unique_checking!(order_sql, order, scope)
73
- unless order_sql.blank?
74
- unique_order_hash = {}
75
- select_table_name = connection.send(:get_table_name,sql)
76
- select_table_name.tr!('[]','') if select_table_name
77
- orders_and_dirs_set = connection.send(:orders_and_dirs_set,order_sql)
78
- unique_order_sql = orders_and_dirs_set.inject([]) do |array,order_dir|
79
- ord, dir = order_dir
80
- ord_tn_and_cn = ord.to_s.split('.').map{|o|o.tr('[]','')}
81
- ord_table_name, ord_column_name = if ord_tn_and_cn.size > 1
82
- ord_tn_and_cn
83
- else
84
- [nil, ord_tn_and_cn.first]
85
- end
86
- unique_key = [(ord_table_name || select_table_name), ord_column_name]
87
- if unique_order_hash[unique_key]
88
- array
89
- else
90
- unique_order_hash[unique_key] = true
91
- array << "#{ord} #{dir}".strip
92
- end
93
- end.join(', ')
94
- sql << " ORDER BY #{unique_order_sql}"
95
- end
96
- else
97
- add_order_without_sqlserver_unique_checking!(sql, order, scope)
98
- end
99
- end
100
-
101
- end
102
-
103
- module JoinAssociationChanges
104
-
105
- def self.included(klass)
106
- klass.class_eval do
107
- include InstanceMethods
108
- alias_method_chain :aliased_table_name_for, :sqlserver_support
109
- end
110
- end
111
-
112
- module InstanceMethods
113
-
114
- protected
115
-
116
- # An exact copy, except this method has a Regexp escape on the quoted table name.
117
- def aliased_table_name_for_with_sqlserver_support(name,suffix=nil)
118
- if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{Regexp.escape(active_record.connection.quote_table_name(name.downcase))}\son}i
119
- @join_dependency.table_aliases[name] += 1
120
- end
121
- unless @join_dependency.table_aliases[name].zero?
122
- # if the table name has been used, then use an alias
123
- name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
124
- table_index = @join_dependency.table_aliases[name]
125
- @join_dependency.table_aliases[name] += 1
126
- name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
127
- else
128
- @join_dependency.table_aliases[name] += 1
129
- end
130
- name
131
- end
132
-
133
- end
134
-
135
- end
136
-
137
- end
138
-
139
-
140
- end
141
- end
142
- end
143
-
144
-
145
- ActiveRecord::Base.send :include, ActiveRecord::ConnectionAdapters::SQLServerCoreExtensions::ActiveRecord
146
-
147
- if ActiveRecord::VERSION::MAJOR == 2 && ActiveRecord::VERSION::MINOR >= 3
148
- require 'active_record/associations'
149
- ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.send :include, ActiveRecord::ConnectionAdapters::SQLServerCoreExtensions::ActiveRecord::JoinAssociationChanges
150
- end
151
-
@@ -1,40 +0,0 @@
1
-
2
- module ActiveRecord
3
- module ConnectionAdapters
4
- module SQLServerCoreExtensions
5
- module ODBC
6
-
7
- module TimeStamp
8
- def to_sqlserver_string
9
- date, time, nanoseconds = to_s.split(' ')
10
- "#{date} #{time}.#{sprintf("%03d",nanoseconds.to_i/1000000)}"
11
- end
12
- end
13
-
14
- module Statement
15
- def finished?
16
- begin
17
- connected?
18
- false
19
- rescue ::ODBC::Error => e
20
- true
21
- end
22
- end
23
- end
24
-
25
- module Database
26
- def run_block(*args)
27
- yield sth = run(*args)
28
- sth.drop
29
- end
30
- end
31
-
32
- end
33
- end
34
- end
35
- end
36
-
37
- ODBC::TimeStamp.send :include, ActiveRecord::ConnectionAdapters::SQLServerCoreExtensions::ODBC::TimeStamp if defined?(ODBC::TimeStamp)
38
- ODBC::Statement.send :include, ActiveRecord::ConnectionAdapters::SQLServerCoreExtensions::ODBC::Statement if defined?(ODBC::Statement)
39
- ODBC::Database.send :include, ActiveRecord::ConnectionAdapters::SQLServerCoreExtensions::ODBC::Database if defined?(ODBC::Database)
40
-
@@ -1,19 +0,0 @@
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
@@ -1,755 +0,0 @@
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
- fixtures :tasks
10
-
11
- def setup
12
- @connection = ActiveRecord::Base.connection
13
- @basic_insert_sql = "INSERT INTO [funny_jokes] ([name]) VALUES('Knock knock')"
14
- @basic_update_sql = "UPDATE [customers] SET [address_street] = NULL WHERE [id] = 2"
15
- @basic_select_sql = "SELECT * FROM [customers] WHERE ([customers].[id] = 1)"
16
- end
17
-
18
- context 'For abstract behavior' do
19
-
20
- should 'have a 128 max #table_alias_length' do
21
- assert @connection.table_alias_length <= 128
22
- end
23
-
24
- should 'raise invalid statement error' do
25
- assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.update("UPDATE XXX") }
26
- end
27
-
28
- should 'be our adapter_name' do
29
- assert_equal 'SQLServer', @connection.adapter_name
30
- end
31
-
32
- should 'include version in inspect' do
33
- assert_match(/version\: \d.\d/,@connection.inspect)
34
- end
35
-
36
- should 'support migrations' do
37
- assert @connection.supports_migrations?
38
- end
39
-
40
- should 'support DDL in transactions' do
41
- assert @connection.supports_ddl_transactions?
42
- end
43
-
44
- should 'allow owner table name prefixs like dbo. to still allow table_exists? to return true' do
45
- begin
46
- assert_equal 'tasks', Task.table_name
47
- assert Task.table_exists?
48
- Task.table_name = 'dbo.tasks'
49
- assert Task.table_exists?, 'Tasks table name of dbo.tasks should return true for exists.'
50
- ensure
51
- Task.table_name = 'tasks'
52
- end
53
- end
54
-
55
- context 'for database version' do
56
-
57
- setup do
58
- @version_regexp = ActiveRecord::ConnectionAdapters::SQLServerAdapter::DATABASE_VERSION_REGEXP
59
- @supported_version = ActiveRecord::ConnectionAdapters::SQLServerAdapter::SUPPORTED_VERSIONS
60
- @sqlserver_2000_string = "Microsoft SQL Server 2000 - 8.00.2039 (Intel X86)"
61
- @sqlserver_2005_string = "Microsoft SQL Server 2005 - 9.00.3215.00 (Intel X86)"
62
- @sqlserver_2008_string = "Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86)"
63
- end
64
-
65
- should 'return a string from #database_version that matches class regexp' do
66
- assert_match @version_regexp, @connection.database_version
67
- end
68
-
69
- should 'return a 4 digit year fixnum for #database_year' do
70
- assert_instance_of Fixnum, @connection.database_year
71
- assert_contains @supported_version, @connection.database_year
72
- end
73
-
74
- end
75
-
76
- context 'for #unqualify_table_name and #unqualify_db_name' do
77
-
78
- setup do
79
- @expected_table_name = 'baz'
80
- @expected_db_name = 'foo'
81
- @first_second_table_names = ['[baz]','baz','[bar].[baz]','bar.baz']
82
- @third_table_names = ['[foo].[bar].[baz]','foo.bar.baz']
83
- @qualifed_table_names = @first_second_table_names + @third_table_names
84
- end
85
-
86
- should 'return clean table_name from #unqualify_table_name' do
87
- @qualifed_table_names.each do |qtn|
88
- assert_equal @expected_table_name,
89
- @connection.send(:unqualify_table_name,qtn),
90
- "This qualifed_table_name #{qtn} did not unqualify correctly."
91
- end
92
- end
93
-
94
- should 'return nil from #unqualify_db_name when table_name is less than 2 qualified' do
95
- @first_second_table_names.each do |qtn|
96
- assert_equal nil, @connection.send(:unqualify_db_name,qtn),
97
- "This qualifed_table_name #{qtn} did not return nil."
98
- end
99
- end
100
-
101
- should 'return clean db_name from #unqualify_db_name when table is thrid level qualified' do
102
- @third_table_names.each do |qtn|
103
- assert_equal @expected_db_name,
104
- @connection.send(:unqualify_db_name,qtn),
105
- "This qualifed_table_name #{qtn} did not unqualify the db_name correctly."
106
- end
107
- end
108
-
109
- end
110
-
111
- should 'return true to #insert_sql? for inserts only' do
112
- assert @connection.send(:insert_sql?,'INSERT...')
113
- assert !@connection.send(:insert_sql?,'UPDATE...')
114
- assert !@connection.send(:insert_sql?,'SELECT...')
115
- end
116
-
117
- context 'for #limited_update_conditions' do
118
-
119
- should 'only match up to the first WHERE' do
120
- where_sql = "TOP 1 WHERE ([posts].author_id = 1 and [posts].columnWHEREname = 2) ORDER BY posts.id"
121
- assert_equal "WHERE bar IN (SELECT TOP 1 bar FROM foo WHERE ([posts].author_id = 1 and [posts].columnWHEREname = 2) ORDER BY posts.id)", @connection.limited_update_conditions(where_sql, 'foo', 'bar')
122
- end
123
-
124
- end
125
-
126
- context 'for #sql_for_association_limiting?' do
127
-
128
- should 'return false for simple selects with no GROUP BY and ORDER BY' do
129
- assert !sql_for_association_limiting?("SELECT * FROM [posts]")
130
- end
131
-
132
- should 'return true to single SELECT, ideally a table/primarykey, that also has a GROUP BY and ORDER BY' do
133
- assert sql_for_association_limiting?("SELECT [posts].id FROM...GROUP BY [posts].id ORDER BY MIN(posts.id)")
134
- end
135
-
136
- should 'return false to single * wildcard SELECT that also has a GROUP BY and ORDER BY' do
137
- assert !sql_for_association_limiting?("SELECT * FROM...GROUP BY [posts].id ORDER BY MIN(posts.id)")
138
- end
139
-
140
- should 'return false to multiple columns in the select even when GROUP BY and ORDER BY are present' do
141
- sql = "SELECT [accounts].credit_limit, firm_id FROM...GROUP BY firm_id ORDER BY firm_id"
142
- assert !sql_for_association_limiting?(sql)
143
- end
144
-
145
- end
146
-
147
- context 'for #get_table_name' do
148
-
149
- should 'return quoted table name from basic INSERT, UPDATE and SELECT statements' do
150
- assert_equal '[funny_jokes]', @connection.send(:get_table_name,@basic_insert_sql)
151
- assert_equal '[customers]', @connection.send(:get_table_name,@basic_update_sql)
152
- assert_equal '[customers]', @connection.send(:get_table_name,@basic_select_sql)
153
- end
154
-
155
- end
156
-
157
- context "for add_limit! within a scoped method call" do
158
- setup do
159
- @connection.stubs(:select_value).with(regexp_matches(/TotalRows/)).returns '100000000'
160
- end
161
-
162
- should 'not add any ordering if the scope doesn\'t have an order' do
163
- assert_equal 'SELECT * FROM (SELECT TOP 10 * FROM (SELECT TOP 40 * FROM [developers]) AS tmp1) AS tmp2', add_limit!('SELECT * FROM [developers]', {:offset => 30, :limit => 10}, {})
164
- end
165
-
166
- should 'still add the default ordering if the scope doesn\'t have an order but the raw order option is there' do
167
- assert_equal 'SELECT * FROM (SELECT TOP 10 * FROM (SELECT TOP 40 * FROM [developers]) AS tmp1 ORDER BY [name] DESC) AS tmp2 ORDER BY [name]', add_limit!('SELECT * FROM [developers]', {:offset => 30, :limit => 10, :order => 'name'}, {})
168
- end
169
-
170
- should 'add scoped order options to the offset and limit sql' do
171
- assert_equal 'SELECT * FROM (SELECT TOP 10 * FROM (SELECT TOP 40 * FROM [developers]) AS tmp1 ORDER BY [id] DESC) AS tmp2 ORDER BY [id]', add_limit!('SELECT * FROM [developers]', {:offset => 30, :limit => 10}, {:order => 'id'})
172
- end
173
-
174
- should 'combine scoped order with raw order options in the offset and limit sql' do
175
- assert_equal 'SELECT * FROM (SELECT TOP 10 * FROM (SELECT TOP 40 * FROM [developers]) AS tmp1 ORDER BY [name] DESC, [id] DESC) AS tmp2 ORDER BY [name], [id]', add_limit!('SELECT * FROM [developers]', {:offset => 30, :limit => 10, :order => 'name'}, {:order => 'id'})
176
- end
177
- end
178
-
179
- context 'dealing with various orders SQL snippets' do
180
-
181
- setup do
182
- @single_order = 'comments.id'
183
- @single_order_with_desc = 'comments.id DESC'
184
- @two_orders = 'comments.id, comments.post_id'
185
- @two_orders_with_asc = 'comments.id, comments.post_id ASC'
186
- @two_orders_with_desc_and_asc = 'comments.id DESC, comments.post_id ASC'
187
- @two_duplicate_order_with_dif_dir = "id, id DESC"
188
- end
189
-
190
- should 'convert to an 2D array of column/direction arrays using #orders_and_dirs_set' do
191
- assert_equal [['comments.id',nil]], orders_and_dirs_set('ORDER BY comments.id'), 'Needs to remove ORDER BY'
192
- assert_equal [['comments.id',nil]], orders_and_dirs_set(@single_order)
193
- assert_equal [['comments.id',nil],['comments.post_id',nil]], orders_and_dirs_set(@two_orders)
194
- assert_equal [['comments.id',nil],['comments.post_id','ASC']], orders_and_dirs_set(@two_orders_with_asc)
195
- assert_equal [['id',nil],['id','DESC']], orders_and_dirs_set(@two_duplicate_order_with_dif_dir)
196
- end
197
-
198
- should 'remove duplicate or maintain the same order by statements giving precedence to first using #add_order! method chain extension' do
199
- assert_equal ' ORDER BY comments.id', add_order!(@single_order)
200
- assert_equal ' ORDER BY comments.id DESC', add_order!(@single_order_with_desc)
201
- assert_equal ' ORDER BY comments.id, comments.post_id', add_order!(@two_orders)
202
- assert_equal ' ORDER BY comments.id DESC, comments.post_id ASC', add_order!(@two_orders_with_desc_and_asc)
203
- assert_equal 'SELECT * FROM [developers] ORDER BY id', add_order!('id, developers.id DESC','SELECT * FROM [developers]')
204
- assert_equal 'SELECT * FROM [developers] ORDER BY [developers].[id] DESC', add_order!('[developers].[id] DESC, id','SELECT * FROM [developers]')
205
- end
206
-
207
- should 'take all types of order options and convert them to MIN functions using #order_to_min_set' do
208
- assert_equal 'MIN(comments.id)', order_to_min_set(@single_order)
209
- assert_equal 'MIN(comments.id), MIN(comments.post_id)', order_to_min_set(@two_orders)
210
- assert_equal 'MIN(comments.id) DESC', order_to_min_set(@single_order_with_desc)
211
- assert_equal 'MIN(comments.id), MIN(comments.post_id) ASC', order_to_min_set(@two_orders_with_asc)
212
- assert_equal 'MIN(comments.id) DESC, MIN(comments.post_id) ASC', order_to_min_set(@two_orders_with_desc_and_asc)
213
- end
214
-
215
- should 'leave order by alone when same column crosses two tables' do
216
- assert_equal ' ORDER BY developers.name, projects.name', add_order!('developers.name, projects.name')
217
- end
218
-
219
- end
220
-
221
- context 'with different language' do
222
-
223
- teardown do
224
- @connection.execute("SET LANGUAGE us_english") rescue nil
225
- end
226
-
227
- should_eventually 'do a date insertion when language is german' do
228
- @connection.execute("SET LANGUAGE deutsch")
229
- assert_nothing_raised do
230
- Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31))
231
- end
232
- end
233
-
234
- end
235
-
236
- context 'testing #enable_default_unicode_types configuration' do
237
-
238
- should 'use non-unicode types when set to false' do
239
- with_enable_default_unicode_types(false) do
240
- if sqlserver_2000?
241
- assert_equal 'varchar', @connection.native_string_database_type
242
- assert_equal 'text', @connection.native_text_database_type
243
- elsif sqlserver_2005?
244
- assert_equal 'varchar', @connection.native_string_database_type
245
- assert_equal 'varchar(max)', @connection.native_text_database_type
246
- end
247
- end
248
- end
249
-
250
- should 'use unicode types when set to true' do
251
- with_enable_default_unicode_types(true) do
252
- if sqlserver_2000?
253
- assert_equal 'nvarchar', @connection.native_string_database_type
254
- assert_equal 'ntext', @connection.native_text_database_type
255
- elsif sqlserver_2005?
256
- assert_equal 'nvarchar', @connection.native_string_database_type
257
- assert_equal 'nvarchar(max)', @connection.native_text_database_type
258
- end
259
- end
260
- end
261
-
262
- end
263
-
264
-
265
- end
266
-
267
- context 'For chronic data types' do
268
-
269
- context 'with a usec' do
270
-
271
- setup do
272
- @time = Time.now
273
- @db_datetime_003 = '2012-11-08 10:24:36.003'
274
- @db_datetime_123 = '2012-11-08 10:24:36.123'
275
- @all_datetimes = [@db_datetime_003, @db_datetime_123]
276
- @all_datetimes.each do |datetime|
277
- @connection.execute("INSERT INTO [sql_server_chronics] ([datetime]) VALUES('#{datetime}')")
278
- end
279
- end
280
-
281
- teardown do
282
- @all_datetimes.each do |datetime|
283
- @connection.execute("DELETE FROM [sql_server_chronics] WHERE [datetime] = '#{datetime}'")
284
- end
285
- end
286
-
287
- context 'finding existing DB objects' do
288
-
289
- should 'find 003 millisecond in the DB with before and after casting' do
290
- existing_003 = SqlServerChronic.find_by_datetime!(@db_datetime_003)
291
- assert_equal @db_datetime_003, existing_003.datetime_before_type_cast if existing_003.datetime_before_type_cast.is_a?(String)
292
- assert_equal 3000, existing_003.datetime.usec, 'A 003 millisecond in SQL Server is 3000 microseconds'
293
- end
294
-
295
- should 'find 123 millisecond in the DB with before and after casting' do
296
- existing_123 = SqlServerChronic.find_by_datetime!(@db_datetime_123)
297
- assert_equal @db_datetime_123, existing_123.datetime_before_type_cast if existing_123.datetime_before_type_cast.is_a?(String)
298
- assert_equal 123000, existing_123.datetime.usec, 'A 123 millisecond in SQL Server is 123000 microseconds'
299
- end
300
-
301
- end
302
-
303
- context 'saving new datetime objects' do
304
-
305
- should 'truncate 123456 usec to just 123 in the DB cast back to 123000' do
306
- @time.stubs(:usec).returns(123456)
307
- saved = SqlServerChronic.create!(:datetime => @time).reload
308
- assert_equal '123', saved.datetime_before_type_cast.split('.')[1] if saved.datetime_before_type_cast.is_a?(String)
309
- assert_equal 123000, saved.datetime.usec
310
- end
311
-
312
- should 'truncate 3001 usec to just 003 in the DB cast back to 3000' do
313
- @time.stubs(:usec).returns(3001)
314
- saved = SqlServerChronic.create!(:datetime => @time).reload
315
- assert_equal '003', saved.datetime_before_type_cast.split('.')[1] if saved.datetime_before_type_cast.is_a?(String)
316
- assert_equal 3000, saved.datetime.usec
317
- end
318
-
319
- end
320
-
321
- end
322
-
323
- end
324
-
325
- context 'For identity inserts' do
326
-
327
- setup do
328
- @identity_insert_sql = "INSERT INTO [funny_jokes] ([id],[name]) VALUES(420,'Knock knock')"
329
- @identity_insert_sql_unquoted = "INSERT INTO funny_jokes (id, name) VALUES(420, 'Knock knock')"
330
- @identity_insert_sql_unordered = "INSERT INTO [funny_jokes] ([name],[id]) VALUES('Knock knock',420)"
331
- end
332
-
333
- should 'return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column' do
334
- assert_equal '[funny_jokes]', @connection.send(:query_requires_identity_insert?,@identity_insert_sql)
335
- assert_equal '[funny_jokes]', @connection.send(:query_requires_identity_insert?,@identity_insert_sql_unquoted)
336
- assert_equal '[funny_jokes]', @connection.send(:query_requires_identity_insert?,@identity_insert_sql_unordered)
337
- end
338
-
339
- should 'return false to #query_requires_identity_insert? for normal SQL' do
340
- [@basic_insert_sql, @basic_update_sql, @basic_select_sql].each do |sql|
341
- assert !@connection.send(:query_requires_identity_insert?,sql), "SQL was #{sql}"
342
- end
343
- end
344
-
345
- should 'find identity column using #identity_column' do
346
- joke_id_column = Joke.columns.detect { |c| c.name == 'id' }
347
- assert_equal joke_id_column, @connection.send(:identity_column,Joke.table_name)
348
- end
349
-
350
- should 'return nil when calling #identity_column for a table_name with no identity' do
351
- assert_nil @connection.send(:identity_column,Subscriber.table_name)
352
- end
353
-
354
- should 'return retreive auto incremented id column' do
355
- joke = Joke.new
356
- joke.id = 999
357
- joke.save
358
- assert_equal 999, joke.id
359
- end
360
- end
361
-
362
- context 'For Quoting' do
363
-
364
- should 'return 1 for #quoted_true' do
365
- assert_equal '1', @connection.quoted_true
366
- end
367
-
368
- should 'return 0 for #quoted_false' do
369
- assert_equal '0', @connection.quoted_false
370
- end
371
-
372
- should 'not escape backslash characters like abstract adapter' do
373
- string_with_backslashs = "\\n"
374
- assert_equal string_with_backslashs, @connection.quote_string(string_with_backslashs)
375
- end
376
-
377
- should 'quote column names with brackets' do
378
- assert_equal '[foo]', @connection.quote_column_name(:foo)
379
- assert_equal '[foo]', @connection.quote_column_name('foo')
380
- assert_equal '[foo].[bar]', @connection.quote_column_name('foo.bar')
381
- end
382
-
383
- should 'not quote already quoted column names with brackets' do
384
- assert_equal '[foo]', @connection.quote_column_name('[foo]')
385
- assert_equal '[foo].[bar]', @connection.quote_column_name('[foo].[bar]')
386
- end
387
-
388
- should 'quote table names like columns' do
389
- assert_equal '[foo].[bar]', @connection.quote_column_name('foo.bar')
390
- assert_equal '[foo].[bar].[baz]', @connection.quote_column_name('foo.bar.baz')
391
- end
392
-
393
- end
394
-
395
- context 'When disableing referential integrity' do
396
-
397
- setup do
398
- @parent = FkTestHasPk.create!
399
- @member = FkTestHasFk.create!(:fk_id => @parent.id)
400
- end
401
-
402
- should 'NOT ALLOW by default the deletion of a referenced parent' do
403
- FkTestHasPk.connection.disable_referential_integrity { }
404
- assert_raise(ActiveRecord::StatementInvalid) { @parent.destroy }
405
- end
406
-
407
- should 'ALLOW deletion of referenced parent using #disable_referential_integrity block' do
408
- FkTestHasPk.connection.disable_referential_integrity { @parent.destroy }
409
- end
410
-
411
- should 'again NOT ALLOW deletion of referenced parent after #disable_referential_integrity block' do
412
- assert_raise(ActiveRecord::StatementInvalid) do
413
- FkTestHasPk.connection.disable_referential_integrity { }
414
- @parent.destroy
415
- end
416
- end
417
-
418
- end
419
-
420
- context 'For DatabaseStatements' do
421
-
422
- context "finding out what user_options are available" do
423
-
424
- should "run the database consistency checker useroptions command" do
425
- @connection.expects(:select_rows).with(regexp_matches(/^dbcc\s+useroptions$/i)).returns []
426
- @connection.user_options
427
- end
428
-
429
- should "return a underscored key hash with indifferent access of the results" do
430
- @connection.expects(:select_rows).with(regexp_matches(/^dbcc\s+useroptions$/i)).returns [['some', 'thing'], ['isolation level', 'read uncommitted']]
431
- uo = @connection.user_options
432
- assert_equal 2, uo.keys.size
433
- assert_equal 'thing', uo['some']
434
- assert_equal 'thing', uo[:some]
435
- assert_equal 'read uncommitted', uo['isolation_level']
436
- assert_equal 'read uncommitted', uo[:isolation_level]
437
- end
438
-
439
- end
440
-
441
- context "altering isolation levels" do
442
-
443
- should "barf if the requested isolation level is not valid" do
444
- assert_raise(ArgumentError) do
445
- @connection.run_with_isolation_level 'INVALID ISOLATION LEVEL' do; end
446
- end
447
- end
448
-
449
- context "with a valid isolation level" do
450
-
451
- setup do
452
- @t1 = tasks(:first_task)
453
- @t2 = tasks(:another_task)
454
- assert @t1, 'Tasks :first_task should be in AR fixtures'
455
- assert @t2, 'Tasks :another_task should be in AR fixtures'
456
- good_isolation_level = @connection.user_options[:isolation_level].blank? || @connection.user_options[:isolation_level] =~ /read committed/i
457
- assert good_isolation_level, "User isolation level is not at a happy starting place: #{@connection.user_options[:isolation_level].inspect}"
458
- end
459
-
460
- should 'allow #run_with_isolation_level to not take a block to set it' do
461
- begin
462
- @connection.run_with_isolation_level 'READ UNCOMMITTED'
463
- assert_match %r|read uncommitted|i, @connection.user_options[:isolation_level]
464
- ensure
465
- @connection.run_with_isolation_level 'READ COMMITTED'
466
- end
467
- end
468
-
469
- should 'return block value using #run_with_isolation_level' do
470
- assert_same_elements Task.find(:all), @connection.run_with_isolation_level('READ UNCOMMITTED') { Task.find(:all) }
471
- end
472
-
473
- should 'pass a read uncommitted isolation level test' do
474
- assert_nil @t2.starting, 'Fixture should have this empty.'
475
- begin
476
- Task.transaction do
477
- @t2.starting = Time.now
478
- @t2.save
479
- @dirty_t2 = @connection.run_with_isolation_level('READ UNCOMMITTED') { Task.find(@t2.id) }
480
- raise ActiveRecord::ActiveRecordError
481
- end
482
- rescue
483
- 'Do Nothing'
484
- end
485
- assert @dirty_t2, 'Should have a Task record from within block above.'
486
- assert @dirty_t2.starting, 'Should have a dirty date.'
487
- assert_nil Task.find(@t2.id).starting, 'Should be nil again from botched transaction above.'
488
- end
489
-
490
- end
491
-
492
- end
493
-
494
- end
495
-
496
- context 'For SchemaStatements' do
497
-
498
- context 'returning from #type_to_sql' do
499
-
500
- should 'create integers when no limit supplied' do
501
- assert_equal 'integer', @connection.type_to_sql(:integer)
502
- end
503
-
504
- should 'create integers when limit is 4' do
505
- assert_equal 'integer', @connection.type_to_sql(:integer, 4)
506
- end
507
-
508
- should 'create integers when limit is 3' do
509
- assert_equal 'integer', @connection.type_to_sql(:integer, 3)
510
- end
511
-
512
- should 'create smallints when limit is less than 3' do
513
- assert_equal 'smallint', @connection.type_to_sql(:integer, 2)
514
- assert_equal 'smallint', @connection.type_to_sql(:integer, 1)
515
- end
516
-
517
- should 'create bigints when limit is greateer than 4' do
518
- assert_equal 'bigint', @connection.type_to_sql(:integer, 5)
519
- assert_equal 'bigint', @connection.type_to_sql(:integer, 6)
520
- assert_equal 'bigint', @connection.type_to_sql(:integer, 7)
521
- assert_equal 'bigint', @connection.type_to_sql(:integer, 8)
522
- end
523
-
524
- should 'create floats when no limit supplied' do
525
- assert_equal 'float(8)', @connection.type_to_sql(:float)
526
- end
527
-
528
- should 'create floats when limit is supplied' do
529
- assert_equal 'float(27)', @connection.type_to_sql(:float, 27)
530
- end
531
-
532
- end
533
-
534
- end
535
-
536
- context 'For indexes' do
537
-
538
- setup do
539
- @desc_index_name = 'idx_credit_limit_test_desc'
540
- @connection.execute "CREATE INDEX #{@desc_index_name} ON accounts (credit_limit DESC)"
541
- end
542
-
543
- teardown do
544
- @connection.execute "DROP INDEX accounts.#{@desc_index_name}"
545
- end
546
-
547
- should 'have indexes with descending order' do
548
- assert @connection.indexes('accounts').detect { |i| i.name == @desc_index_name }
549
- end
550
-
551
- end
552
-
553
- context 'For views' do
554
-
555
- context 'using @connection.views' do
556
-
557
- should 'return an array' do
558
- assert_instance_of Array, @connection.views
559
- end
560
-
561
- should 'find CustomersView table name' do
562
- assert_contains @connection.views, 'customers_view'
563
- end
564
-
565
- should 'not contain system views' do
566
- systables = ['sysconstraints','syssegments']
567
- systables.each do |systable|
568
- assert !@connection.views.include?(systable), "This systable #{systable} should not be in the views array."
569
- end
570
- end
571
-
572
- should 'allow the connection.view_information method to return meta data on the view' do
573
- view_info = @connection.view_information('customers_view')
574
- assert_equal('customers_view', view_info['TABLE_NAME'])
575
- assert_match(/CREATE VIEW customers_view/, view_info['VIEW_DEFINITION'])
576
- end
577
-
578
- should 'allow the connection.view_table_name method to return true table_name for the view' do
579
- assert_equal 'customers', @connection.view_table_name('customers_view')
580
- assert_equal 'topics', @connection.view_table_name('topics'), 'No view here, the same table name should come back.'
581
- end
582
-
583
- end
584
-
585
- context 'used by a class for table_name' do
586
-
587
- context 'with same column names' do
588
-
589
- should 'have matching column objects' do
590
- columns = ['id','name','balance']
591
- assert !CustomersView.columns.blank?
592
- assert_equal columns.size, CustomersView.columns.size
593
- columns.each do |colname|
594
- assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
595
- CustomersView.columns_hash[colname],
596
- "Column name #{colname.inspect} was not found in these columns #{CustomersView.columns.map(&:name).inspect}"
597
- end
598
- end
599
-
600
- should 'find identity column' do
601
- assert CustomersView.columns_hash['id'].primary
602
- assert CustomersView.columns_hash['id'].is_identity?
603
- end
604
-
605
- should 'find default values' do
606
- assert_equal 0, CustomersView.new.balance
607
- end
608
-
609
- should 'respond true to table_exists?' do
610
- assert CustomersView.table_exists?
611
- end
612
-
613
- should 'have correct table name for all column objects' do
614
- assert CustomersView.columns.all?{ |c| c.table_name == 'customers_view' },
615
- CustomersView.columns.map(&:table_name).inspect
616
- end
617
-
618
- end
619
-
620
- context 'with aliased column names' do
621
-
622
- should 'have matching column objects' do
623
- columns = ['id','pretend_null']
624
- assert !StringDefaultsView.columns.blank?
625
- assert_equal columns.size, StringDefaultsView.columns.size
626
- columns.each do |colname|
627
- assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
628
- StringDefaultsView.columns_hash[colname],
629
- "Column name #{colname.inspect} was not found in these columns #{StringDefaultsView.columns.map(&:name).inspect}"
630
- end
631
- end
632
-
633
- should 'find identity column' do
634
- assert StringDefaultsView.columns_hash['id'].primary
635
- assert StringDefaultsView.columns_hash['id'].is_identity?
636
- end
637
-
638
- should 'find default values' do
639
- assert_equal 'null', StringDefaultsView.new.pretend_null,
640
- StringDefaultsView.columns_hash['pretend_null'].inspect
641
- end
642
-
643
- should 'respond true to table_exists?' do
644
- assert StringDefaultsView.table_exists?
645
- end
646
-
647
- should 'have correct table name for all column objects' do
648
- assert StringDefaultsView.columns.all?{ |c| c.table_name == 'string_defaults_view' },
649
- StringDefaultsView.columns.map(&:table_name).inspect
650
- end
651
-
652
- end
653
-
654
- end
655
-
656
- context 'doing identity inserts' do
657
-
658
- setup do
659
- @view_insert_sql = "INSERT INTO [customers_view] ([id],[name],[balance]) VALUES (420,'Microsoft',0)"
660
- end
661
-
662
- should 'respond true/tablename to #query_requires_identity_insert?' do
663
- assert_equal '[customers_view]', @connection.send(:query_requires_identity_insert?,@view_insert_sql)
664
- end
665
-
666
- should 'be able to do an identity insert' do
667
- assert_nothing_raised { @connection.execute(@view_insert_sql) }
668
- assert CustomersView.find(420)
669
- end
670
-
671
- end
672
-
673
- context 'that have more than 4000 chars for their defintion' do
674
-
675
- should 'cope with null returned for the defintion' do
676
- assert_nothing_raised() { StringDefaultsBigView.columns }
677
- end
678
-
679
- should 'using alternate view defintion still be able to find real default' do
680
- assert_equal 'null', StringDefaultsBigView.new.pretend_null,
681
- StringDefaultsBigView.columns_hash['pretend_null'].inspect
682
- end
683
-
684
- end
685
-
686
- end
687
-
688
-
689
-
690
- private
691
-
692
- def sql_for_association_limiting?(sql)
693
- @connection.send :sql_for_association_limiting?, sql
694
- end
695
-
696
- def orders_and_dirs_set(order)
697
- @connection.send :orders_and_dirs_set, order
698
- end
699
-
700
- def add_order!(order,sql='')
701
- ActiveRecord::Base.send :add_order!, sql, order, nil
702
- sql
703
- end
704
-
705
- def add_limit!(sql, options, scope = :auto)
706
- ActiveRecord::Base.send :add_limit!, sql, options, scope
707
- sql
708
- end
709
-
710
- def order_to_min_set(order)
711
- @connection.send :order_to_min_set, order
712
- end
713
-
714
- def with_enable_default_unicode_types(setting)
715
- old_setting = ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types
716
- old_text = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type
717
- old_string = ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type
718
- ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = setting
719
- ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = nil
720
- ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = nil
721
- yield
722
- ensure
723
- ActiveRecord::ConnectionAdapters::SQLServerAdapter.enable_default_unicode_types = old_setting
724
- ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_text_database_type = old_text
725
- ActiveRecord::ConnectionAdapters::SQLServerAdapter.native_string_database_type = old_string
726
- end
727
-
728
- end
729
-
730
-
731
- class AdapterTest < ActiveRecord::TestCase
732
-
733
- COERCED_TESTS = [
734
- :test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas,
735
- :test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
736
- ]
737
-
738
- include SqlserverCoercedTest
739
-
740
- def test_coerced_test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
741
- sql_inject = "1 select * from schema"
742
- connection = ActiveRecord::Base.connection
743
- assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject) }
744
- assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) }
745
- end
746
-
747
- def test_coerced_test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
748
- sql_inject = "1, 7 procedure help()"
749
- connection = ActiveRecord::Base.connection
750
- assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject) }
751
- assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7) }
752
- assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) }
753
- end
754
-
755
- end