activerecord-sqlserver-adapter 2.3.24 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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