activerecord-sqlserver-adapter 2.3.7 → 3.2.18

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