aq1018-sqlserver-2000-2008-adpater 0.0.2

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.
@@ -0,0 +1,93 @@
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_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_cache_support
32
+ connection.send(:initialize_sqlserver_caches) if connection.respond_to?(:sqlserver?)
33
+ reset_column_information_without_sqlserver_cache_support
34
+ end
35
+
36
+ private
37
+
38
+ def add_order_with_sqlserver_unique_checking!(sql, order, scope = :auto)
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)
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+
75
+ ActiveRecord::Base.send :include, ActiveRecord::ConnectionAdapters::SQLServerActiveRecordExtensions
76
+
77
+ module ActiveRecord
78
+ class Base
79
+ class << self
80
+ def add_limit!(sql, options, scope = :auto)
81
+ scope = scope(:find) if :auto == scope
82
+
83
+ if scope
84
+ options[:limit] ||= scope[:limit]
85
+ options[:offset] ||= scope[:offset]
86
+ options[:order] ||= scope[:order]
87
+ end
88
+
89
+ connection.add_limit_offset!(sql, options)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,87 @@
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
+ module Type
17
+
18
+ # Make sure we get DBI::Type::Timestamp returning a string NOT a time object
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
+ #
26
+ # "1985-04-15 00:00:00 0" # => "1985-04-15 00:00:00.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"
29
+ class SqlserverTimestamp
30
+ def self.parse(obj)
31
+ return nil if ::DBI::Type::Null.parse(obj).nil?
32
+ date, time, nanoseconds = obj.split(' ')
33
+ "#{date} #{time}.#{sprintf("%03d",nanoseconds.to_i/1000000)}"
34
+ end
35
+ end
36
+
37
+ # The adapter and rails will parse our floats, decimals, and money field correctly
38
+ # from a string. Do not let the DBI::Type classes create Float/BigDecimal objects
39
+ # for us. Trust rails .type_cast to do what it is built to do.
40
+ class SqlserverForcedString
41
+ def self.parse(obj)
42
+ return nil if ::DBI::Type::Null.parse(obj).nil?
43
+ obj.to_s
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ module TypeUtil
50
+
51
+ def self.included(klass)
52
+ klass.extend ClassMethods
53
+ class << klass
54
+ alias_method_chain :type_name_to_module, :sqlserver_types
55
+ end
56
+ end
57
+
58
+ module ClassMethods
59
+
60
+ # Capture all types classes that we need to handle directly for SQL Server
61
+ # and allow normal processing for those that we do not.
62
+ def type_name_to_module_with_sqlserver_types(type_name)
63
+ case type_name
64
+ when /^timestamp$/i
65
+ DBI::Type::SqlserverTimestamp
66
+ when /^float|decimal|money$/i
67
+ DBI::Type::SqlserverForcedString
68
+ else
69
+ type_name_to_module_without_sqlserver_types(type_name)
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+
78
+ end
79
+
80
+
81
+ if defined?(DBI::TypeUtil)
82
+ DBI::Type.send :include, SQLServerDBI::Type
83
+ DBI::TypeUtil.send :include, SQLServerDBI::TypeUtil
84
+ elsif defined?(DBI::Timestamp) # DEPRECATED in DBI 0.4.0 and above. Remove when 0.2.2 and lower is no longer supported.
85
+ DBI::Timestamp.send :include, SQLServerDBI::Timestamp
86
+ end
87
+
@@ -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,560 @@
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 'include version in inspect' do
31
+ assert_match(/version\: \d.\d.\d/,@connection.inspect)
32
+ end
33
+
34
+ should 'support migrations' do
35
+ assert @connection.supports_migrations?
36
+ end
37
+
38
+ should 'support DDL in transactions' do
39
+ assert @connection.supports_ddl_transactions?
40
+ end
41
+
42
+ context 'for database version' do
43
+
44
+ setup do
45
+ @version_regexp = ActiveRecord::ConnectionAdapters::SQLServerAdapter::DATABASE_VERSION_REGEXP
46
+ @supported_version = ActiveRecord::ConnectionAdapters::SQLServerAdapter::SUPPORTED_VERSIONS
47
+ @sqlserver_2000_string = "Microsoft SQL Server 2000 - 8.00.2039 (Intel X86)"
48
+ @sqlserver_2005_string = "Microsoft SQL Server 2005 - 9.00.3215.00 (Intel X86)"
49
+ end
50
+
51
+ should 'return a string from #database_version that matches class regexp' do
52
+ assert_match @version_regexp, @connection.database_version
53
+ end
54
+
55
+ should 'return a 4 digit year fixnum for #database_year' do
56
+ assert_instance_of Fixnum, @connection.database_year
57
+ assert_contains @supported_version, @connection.database_year
58
+ end
59
+
60
+ should 'return true to #sqlserver_2000?' do
61
+ @connection.stubs(:database_version).returns(@sqlserver_2000_string)
62
+ assert @connection.sqlserver_2000?
63
+ end
64
+
65
+ should 'return true to #sqlserver_2005?' do
66
+ @connection.stubs(:database_version).returns(@sqlserver_2005_string)
67
+ assert @connection.sqlserver_2005?
68
+ end
69
+
70
+ end
71
+
72
+ context 'for #unqualify_table_name and #unqualify_db_name' do
73
+
74
+ setup do
75
+ @expected_table_name = 'baz'
76
+ @expected_db_name = 'foo'
77
+ @first_second_table_names = ['[baz]','baz','[bar].[baz]','bar.baz']
78
+ @third_table_names = ['[foo].[bar].[baz]','foo.bar.baz']
79
+ @qualifed_table_names = @first_second_table_names + @third_table_names
80
+ end
81
+
82
+ should 'return clean table_name from #unqualify_table_name' do
83
+ @qualifed_table_names.each do |qtn|
84
+ assert_equal @expected_table_name,
85
+ @connection.send(:unqualify_table_name,qtn),
86
+ "This qualifed_table_name #{qtn} did not unqualify correctly."
87
+ end
88
+ end
89
+
90
+ should 'return nil from #unqualify_db_name when table_name is less than 2 qualified' do
91
+ @first_second_table_names.each do |qtn|
92
+ assert_equal nil, @connection.send(:unqualify_db_name,qtn),
93
+ "This qualifed_table_name #{qtn} did not return nil."
94
+ end
95
+ end
96
+
97
+ should 'return clean db_name from #unqualify_db_name when table is thrid level qualified' do
98
+ @third_table_names.each do |qtn|
99
+ assert_equal @expected_db_name,
100
+ @connection.send(:unqualify_db_name,qtn),
101
+ "This qualifed_table_name #{qtn} did not unqualify the db_name correctly."
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ should 'return true to #insert_sql? for inserts only' do
108
+ assert @connection.send(:insert_sql?,'INSERT...')
109
+ assert !@connection.send(:insert_sql?,'UPDATE...')
110
+ assert !@connection.send(:insert_sql?,'SELECT...')
111
+ end
112
+
113
+ context 'for #sql_for_association_limiting?' do
114
+
115
+ should 'return false for simple selects with no GROUP BY and ORDER BY' do
116
+ assert !sql_for_association_limiting?("SELECT * FROM [posts]")
117
+ end
118
+
119
+ should 'return true to single SELECT, ideally a table/primarykey, that also has a GROUP BY and ORDER BY' do
120
+ assert sql_for_association_limiting?("SELECT [posts].id FROM...GROUP BY [posts].id ORDER BY MIN(posts.id)")
121
+ end
122
+
123
+ should 'return false to single * wildcard SELECT that also has a GROUP BY and ORDER BY' do
124
+ assert !sql_for_association_limiting?("SELECT * FROM...GROUP BY [posts].id ORDER BY MIN(posts.id)")
125
+ end
126
+
127
+ should 'return false to multiple columns in the select even when GROUP BY and ORDER BY are present' do
128
+ sql = "SELECT [accounts].credit_limit, firm_id FROM...GROUP BY firm_id ORDER BY firm_id"
129
+ assert !sql_for_association_limiting?(sql)
130
+ end
131
+
132
+ end
133
+
134
+ context 'for #get_table_name' do
135
+
136
+ should 'return quoted table name from basic INSERT, UPDATE and SELECT statements' do
137
+ assert_equal '[funny_jokes]', @connection.send(:get_table_name,@basic_insert_sql)
138
+ assert_equal '[customers]', @connection.send(:get_table_name,@basic_update_sql)
139
+ assert_equal '[customers]', @connection.send(:get_table_name,@basic_select_sql)
140
+ end
141
+
142
+ end
143
+
144
+ context 'dealing with various orders SQL snippets' do
145
+
146
+ setup do
147
+ @single_order = 'comments.id'
148
+ @single_order_with_desc = 'comments.id DESC'
149
+ @two_orders = 'comments.id, comments.post_id'
150
+ @two_orders_with_asc = 'comments.id, comments.post_id ASC'
151
+ @two_orders_with_desc_and_asc = 'comments.id DESC, comments.post_id ASC'
152
+ @two_duplicate_order_with_dif_dir = "id, id DESC"
153
+ end
154
+
155
+ should 'convert to an 2D array of column/direction arrays using #orders_and_dirs_set' do
156
+ assert_equal [['comments.id',nil]], orders_and_dirs_set('ORDER BY comments.id'), 'Needs to remove ORDER BY'
157
+ assert_equal [['comments.id',nil]], orders_and_dirs_set(@single_order)
158
+ assert_equal [['comments.id',nil],['comments.post_id',nil]], orders_and_dirs_set(@two_orders)
159
+ assert_equal [['comments.id',nil],['comments.post_id','ASC']], orders_and_dirs_set(@two_orders_with_asc)
160
+ assert_equal [['id',nil],['id','DESC']], orders_and_dirs_set(@two_duplicate_order_with_dif_dir)
161
+ end
162
+
163
+ should 'remove duplicate or maintain the same order by statements giving precedence to first using #add_order! method chain extension' do
164
+ assert_equal ' ORDER BY comments.id', add_order!(@single_order)
165
+ assert_equal ' ORDER BY comments.id DESC', add_order!(@single_order_with_desc)
166
+ assert_equal ' ORDER BY comments.id, comments.post_id', add_order!(@two_orders)
167
+ assert_equal ' ORDER BY comments.id DESC, comments.post_id ASC', add_order!(@two_orders_with_desc_and_asc)
168
+ assert_equal 'SELECT * FROM [developers] ORDER BY id', add_order!('id, developers.id DESC','SELECT * FROM [developers]')
169
+ assert_equal 'SELECT * FROM [developers] ORDER BY [developers].[id] DESC', add_order!('[developers].[id] DESC, id','SELECT * FROM [developers]')
170
+ end
171
+
172
+ should 'take all types of order options and convert them to MIN functions using #order_to_min_set' do
173
+ assert_equal 'MIN(comments.id)', order_to_min_set(@single_order)
174
+ assert_equal 'MIN(comments.id), MIN(comments.post_id)', order_to_min_set(@two_orders)
175
+ assert_equal 'MIN(comments.id) DESC', order_to_min_set(@single_order_with_desc)
176
+ assert_equal 'MIN(comments.id), MIN(comments.post_id) ASC', order_to_min_set(@two_orders_with_asc)
177
+ assert_equal 'MIN(comments.id) DESC, MIN(comments.post_id) ASC', order_to_min_set(@two_orders_with_desc_and_asc)
178
+ end
179
+
180
+ end
181
+
182
+ context 'with different language' do
183
+
184
+ teardown do
185
+ @connection.execute("SET LANGUAGE us_english") rescue nil
186
+ end
187
+
188
+ should_eventually 'do a date insertion when language is german' do
189
+ @connection.execute("SET LANGUAGE deutsch")
190
+ assert_nothing_raised do
191
+ Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31))
192
+ end
193
+ end
194
+
195
+ end
196
+
197
+ end
198
+
199
+ context 'For chronic data types' do
200
+
201
+ context 'with a usec' do
202
+
203
+ setup do
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
211
+ end
212
+
213
+ teardown do
214
+ @all_datetimes.each do |datetime|
215
+ @connection.execute("DELETE FROM [sql_server_chronics] WHERE [datetime] = '#{datetime}'")
216
+ end
217
+ end
218
+
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
+
251
+ end
252
+
253
+ end
254
+
255
+ end
256
+
257
+ context 'For identity inserts' do
258
+
259
+ setup do
260
+ @identity_insert_sql = "INSERT INTO [funny_jokes] ([id],[name]) VALUES(420,'Knock knock')"
261
+ end
262
+
263
+ should 'return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id_column' do
264
+ assert_equal '[funny_jokes]', @connection.send(:query_requires_identity_insert?,@identity_insert_sql)
265
+ end
266
+
267
+ should 'return false to #query_requires_identity_insert? for normal SQL' do
268
+ [@basic_insert_sql, @basic_update_sql, @basic_select_sql].each do |sql|
269
+ assert !@connection.send(:query_requires_identity_insert?,sql), "SQL was #{sql}"
270
+ end
271
+ end
272
+
273
+ should 'find identity column using #identity_column' do
274
+ joke_id_column = Joke.columns.detect { |c| c.name == 'id' }
275
+ assert_equal joke_id_column, @connection.send(:identity_column,Joke.table_name)
276
+ end
277
+
278
+ should 'return nil when calling #identity_column for a table_name with no identity' do
279
+ assert_nil @connection.send(:identity_column,Subscriber.table_name)
280
+ end
281
+
282
+ end
283
+
284
+ context 'For Quoting' do
285
+
286
+ should 'return 1 for #quoted_true' do
287
+ assert_equal '1', @connection.quoted_true
288
+ end
289
+
290
+ should 'return 0 for #quoted_false' do
291
+ assert_equal '0', @connection.quoted_false
292
+ end
293
+
294
+ should 'not escape backslash characters like abstract adapter' do
295
+ string_with_backslashs = "\\n"
296
+ assert_equal string_with_backslashs, @connection.quote_string(string_with_backslashs)
297
+ end
298
+
299
+ should 'quote column names with brackets' do
300
+ assert_equal '[foo]', @connection.quote_column_name(:foo)
301
+ assert_equal '[foo]', @connection.quote_column_name('foo')
302
+ assert_equal '[foo].[bar]', @connection.quote_column_name('foo.bar')
303
+ end
304
+
305
+ should 'quote table names like columns' do
306
+ assert_equal '[foo].[bar]', @connection.quote_column_name('foo.bar')
307
+ assert_equal '[foo].[bar].[baz]', @connection.quote_column_name('foo.bar.baz')
308
+ end
309
+
310
+ end
311
+
312
+ context 'When disableing referential integrity' do
313
+
314
+ setup do
315
+ @parent = FkTestHasPk.create!
316
+ @member = FkTestHasFk.create!(:fk_id => @parent.id)
317
+ end
318
+
319
+ should 'NOT ALLOW by default the deletion of a referenced parent' do
320
+ FkTestHasPk.connection.disable_referential_integrity { }
321
+ assert_raise(ActiveRecord::StatementInvalid) { @parent.destroy }
322
+ end
323
+
324
+ should 'ALLOW deletion of referenced parent using #disable_referential_integrity block' do
325
+ FkTestHasPk.connection.disable_referential_integrity { @parent.destroy }
326
+ end
327
+
328
+ should 'again NOT ALLOW deletion of referenced parent after #disable_referential_integrity block' do
329
+ assert_raise(ActiveRecord::StatementInvalid) do
330
+ FkTestHasPk.connection.disable_referential_integrity { }
331
+ @parent.destroy
332
+ end
333
+ end
334
+
335
+ end
336
+
337
+ context 'For DatabaseStatements' do
338
+
339
+ end
340
+
341
+ context 'For SchemaStatements' do
342
+
343
+ context 'returning from #type_to_sql' do
344
+
345
+ should 'create integers when no limit supplied' do
346
+ assert_equal 'integer', @connection.type_to_sql(:integer)
347
+ end
348
+
349
+ should 'create integers when limit is 4' do
350
+ assert_equal 'integer', @connection.type_to_sql(:integer, 4)
351
+ end
352
+
353
+ should 'create integers when limit is 3' do
354
+ assert_equal 'integer', @connection.type_to_sql(:integer, 3)
355
+ end
356
+
357
+ should 'create smallints when limit is less than 3' do
358
+ assert_equal 'smallint', @connection.type_to_sql(:integer, 2)
359
+ assert_equal 'smallint', @connection.type_to_sql(:integer, 1)
360
+ end
361
+
362
+ should 'create bigints when limit is greateer than 4' do
363
+ assert_equal 'bigint', @connection.type_to_sql(:integer, 5)
364
+ assert_equal 'bigint', @connection.type_to_sql(:integer, 6)
365
+ assert_equal 'bigint', @connection.type_to_sql(:integer, 7)
366
+ assert_equal 'bigint', @connection.type_to_sql(:integer, 8)
367
+ end
368
+
369
+ end
370
+
371
+ end
372
+
373
+ context 'For indexes' do
374
+
375
+ setup do
376
+ @desc_index_name = 'idx_credit_limit_test_desc'
377
+ @connection.execute "CREATE INDEX #{@desc_index_name} ON accounts (credit_limit DESC)"
378
+ end
379
+
380
+ teardown do
381
+ @connection.execute "DROP INDEX accounts.#{@desc_index_name}"
382
+ end
383
+
384
+ should 'have indexes with descending order' do
385
+ assert @connection.indexes('accounts').detect { |i| i.name == @desc_index_name }
386
+ end
387
+
388
+ end
389
+
390
+ context 'For views' do
391
+
392
+ context 'using @connection.views' do
393
+
394
+ should 'return an array' do
395
+ assert_instance_of Array, @connection.views
396
+ end
397
+
398
+ should 'find CustomersView table name' do
399
+ assert_contains @connection.views, 'customers_view'
400
+ end
401
+
402
+ should 'not contain system views' do
403
+ systables = ['sysconstraints','syssegments']
404
+ systables.each do |systable|
405
+ assert !@connection.views.include?(systable), "This systable #{systable} should not be in the views array."
406
+ end
407
+ end
408
+
409
+ should 'allow the connection.view_information method to return meta data on the view' do
410
+ view_info = @connection.view_information('customers_view')
411
+ assert_equal('customers_view', view_info['TABLE_NAME'])
412
+ assert_match(/CREATE VIEW customers_view/, view_info['VIEW_DEFINITION'])
413
+ end
414
+
415
+ should 'allow the connection.view_table_name method to return true table_name for the view' do
416
+ assert_equal 'customers', @connection.view_table_name('customers_view')
417
+ assert_equal 'topics', @connection.view_table_name('topics'), 'No view here, the same table name should come back.'
418
+ end
419
+
420
+ end
421
+
422
+ context 'used by a class for table_name' do
423
+
424
+ context 'with same column names' do
425
+
426
+ should 'have matching column objects' do
427
+ columns = ['id','name','balance']
428
+ assert !CustomersView.columns.blank?
429
+ assert_equal columns.size, CustomersView.columns.size
430
+ columns.each do |colname|
431
+ assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
432
+ CustomersView.columns_hash[colname],
433
+ "Column name #{colname.inspect} was not found in these columns #{CustomersView.columns.map(&:name).inspect}"
434
+ end
435
+ end
436
+
437
+ should 'find identity column' do
438
+ assert CustomersView.columns_hash['id'].primary
439
+ assert CustomersView.columns_hash['id'].is_identity?
440
+ end
441
+
442
+ should 'find default values' do
443
+ assert_equal 0, CustomersView.new.balance
444
+ end
445
+
446
+ should 'respond true to table_exists?' do
447
+ assert CustomersView.table_exists?
448
+ end
449
+
450
+ should 'have correct table name for all column objects' do
451
+ assert CustomersView.columns.all?{ |c| c.table_name == 'customers_view' },
452
+ CustomersView.columns.map(&:table_name).inspect
453
+ end
454
+
455
+ end
456
+
457
+ context 'with aliased column names' do
458
+
459
+ should 'have matching column objects' do
460
+ columns = ['id','pretend_null']
461
+ assert !StringDefaultsView.columns.blank?
462
+ assert_equal columns.size, StringDefaultsView.columns.size
463
+ columns.each do |colname|
464
+ assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
465
+ StringDefaultsView.columns_hash[colname],
466
+ "Column name #{colname.inspect} was not found in these columns #{StringDefaultsView.columns.map(&:name).inspect}"
467
+ end
468
+ end
469
+
470
+ should 'find identity column' do
471
+ assert StringDefaultsView.columns_hash['id'].primary
472
+ assert StringDefaultsView.columns_hash['id'].is_identity?
473
+ end
474
+
475
+ should 'find default values' do
476
+ assert_equal 'null', StringDefaultsView.new.pretend_null,
477
+ StringDefaultsView.columns_hash['pretend_null'].inspect
478
+ end
479
+
480
+ should 'respond true to table_exists?' do
481
+ assert StringDefaultsView.table_exists?
482
+ end
483
+
484
+ should 'have correct table name for all column objects' do
485
+ assert StringDefaultsView.columns.all?{ |c| c.table_name == 'string_defaults_view' },
486
+ StringDefaultsView.columns.map(&:table_name).inspect
487
+ end
488
+
489
+ end
490
+
491
+ end
492
+
493
+ context 'doing identity inserts' do
494
+
495
+ setup do
496
+ @view_insert_sql = "INSERT INTO [customers_view] ([id],[name],[balance]) VALUES (420,'Microsoft',0)"
497
+ end
498
+
499
+ should 'respond true/tablename to #query_requires_identity_insert?' do
500
+ assert_equal '[customers_view]', @connection.send(:query_requires_identity_insert?,@view_insert_sql)
501
+ end
502
+
503
+ should 'be able to do an identity insert' do
504
+ assert_nothing_raised { @connection.execute(@view_insert_sql) }
505
+ assert CustomersView.find(420)
506
+ end
507
+
508
+ end
509
+
510
+ end
511
+
512
+
513
+
514
+ private
515
+
516
+ def sql_for_association_limiting?(sql)
517
+ @connection.send :sql_for_association_limiting?, sql
518
+ end
519
+
520
+ def orders_and_dirs_set(order)
521
+ @connection.send :orders_and_dirs_set, order
522
+ end
523
+
524
+ def add_order!(order,sql='')
525
+ ActiveRecord::Base.send :add_order!, sql, order, nil
526
+ sql
527
+ end
528
+
529
+ def order_to_min_set(order)
530
+ @connection.send :order_to_min_set, order
531
+ end
532
+
533
+ end
534
+
535
+
536
+ class AdapterTest < ActiveRecord::TestCase
537
+
538
+ COERCED_TESTS = [
539
+ :test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas,
540
+ :test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
541
+ ]
542
+
543
+ include SqlserverCoercedTest
544
+
545
+ def test_coerced_test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
546
+ sql_inject = "1 select * from schema"
547
+ connection = ActiveRecord::Base.connection
548
+ assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject) }
549
+ assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) }
550
+ end
551
+
552
+ def test_coerced_test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
553
+ sql_inject = "1, 7 procedure help()"
554
+ connection = ActiveRecord::Base.connection
555
+ assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject) }
556
+ assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7) }
557
+ assert_raise(ArgumentError) { connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) }
558
+ end
559
+
560
+ end