activerecord-sqlserver-adapter 2.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/CHANGELOG +175 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest +36 -0
  4. data/README.rdoc +175 -0
  5. data/RUNNING_UNIT_TESTS +60 -0
  6. data/Rakefile +18 -0
  7. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1126 -0
  8. data/lib/activerecord-sqlserver-adapter.rb +1 -0
  9. data/lib/core_ext/active_record.rb +133 -0
  10. data/lib/core_ext/dbi.rb +85 -0
  11. data/tasks/sqlserver.rake +31 -0
  12. data/test/cases/aaaa_create_tables_test_sqlserver.rb +19 -0
  13. data/test/cases/adapter_test_sqlserver.rb +707 -0
  14. data/test/cases/attribute_methods_test_sqlserver.rb +33 -0
  15. data/test/cases/basics_test_sqlserver.rb +21 -0
  16. data/test/cases/calculations_test_sqlserver.rb +20 -0
  17. data/test/cases/column_test_sqlserver.rb +264 -0
  18. data/test/cases/connection_test_sqlserver.rb +142 -0
  19. data/test/cases/eager_association_test_sqlserver.rb +42 -0
  20. data/test/cases/execute_procedure_test_sqlserver.rb +33 -0
  21. data/test/cases/inheritance_test_sqlserver.rb +28 -0
  22. data/test/cases/method_scoping_test_sqlserver.rb +28 -0
  23. data/test/cases/migration_test_sqlserver.rb +93 -0
  24. data/test/cases/offset_and_limit_test_sqlserver.rb +108 -0
  25. data/test/cases/pessimistic_locking_test_sqlserver.rb +125 -0
  26. data/test/cases/query_cache_test_sqlserver.rb +24 -0
  27. data/test/cases/schema_dumper_test_sqlserver.rb +72 -0
  28. data/test/cases/specific_schema_test_sqlserver.rb +57 -0
  29. data/test/cases/sqlserver_helper.rb +123 -0
  30. data/test/cases/table_name_test_sqlserver.rb +22 -0
  31. data/test/cases/transaction_test_sqlserver.rb +93 -0
  32. data/test/cases/unicode_test_sqlserver.rb +50 -0
  33. data/test/connections/native_sqlserver/connection.rb +23 -0
  34. data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
  35. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  36. data/test/schema/sqlserver_specific_schema.rb +91 -0
  37. metadata +120 -0
@@ -0,0 +1,33 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/topic'
3
+
4
+ class AttributeMethodsTestSqlserver < ActiveRecord::TestCase
5
+ end
6
+
7
+ class AttributeMethodsTest < ActiveRecord::TestCase
8
+
9
+ COERCED_TESTS = [
10
+ :test_typecast_attribute_from_select_to_false,
11
+ :test_typecast_attribute_from_select_to_true
12
+ ]
13
+
14
+ include SqlserverCoercedTest
15
+
16
+ fixtures :topics
17
+
18
+
19
+ def test_coerced_typecast_attribute_from_select_to_false
20
+ topic = Topic.create(:title => 'Budget')
21
+ topic = Topic.find(:first, :select => "topics.*, CASE WHEN 1=2 THEN 1 ELSE 0 END as is_test")
22
+ assert !topic.is_test?
23
+ end
24
+
25
+ def test_coerced_typecast_attribute_from_select_to_true
26
+ topic = Topic.create(:title => 'Budget')
27
+ topic = Topic.find(:first, :select => "topics.*, CASE WHEN 2=2 THEN 1 ELSE 0 END as is_test")
28
+ assert topic.is_test?
29
+ end
30
+
31
+
32
+ end
33
+
@@ -0,0 +1,21 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/developer'
3
+
4
+ class BasicsTestSqlserver < ActiveRecord::TestCase
5
+ end
6
+
7
+ class BasicsTest < ActiveRecord::TestCase
8
+
9
+ COERCED_TESTS = [:test_read_attributes_before_type_cast_on_datetime]
10
+
11
+ include SqlserverCoercedTest
12
+
13
+ fixtures :developers
14
+
15
+ def test_coerced_test_read_attributes_before_type_cast_on_datetime
16
+ developer = Developer.find(:first)
17
+ assert_equal developer.created_at.to_s(:db)+'.000' , developer.attributes_before_type_cast["created_at"]
18
+ end
19
+
20
+
21
+ end
@@ -0,0 +1,20 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/company'
3
+
4
+ class CalculationsTestSqlserver < ActiveRecord::TestCase
5
+ end
6
+
7
+ class CalculationsTest < ActiveRecord::TestCase
8
+
9
+ COERCED_TESTS = [:test_should_sum_expression]
10
+
11
+ include SqlserverCoercedTest
12
+
13
+ fixtures :accounts
14
+
15
+ def test_coerced_test_should_sum_expression
16
+ assert_equal 636, Account.sum("2 * credit_limit")
17
+ end
18
+
19
+
20
+ end
@@ -0,0 +1,264 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/binary'
3
+
4
+ class ColumnTestSqlserver < ActiveRecord::TestCase
5
+
6
+ def setup
7
+ @connection = ActiveRecord::Base.connection
8
+ @column_klass = ActiveRecord::ConnectionAdapters::SQLServerColumn
9
+ end
10
+
11
+ should 'return real_number as float' do
12
+ assert_equal :float, TableWithRealColumn.columns_hash["real_number"].type
13
+ end
14
+
15
+ should 'know its #table_name and #table_klass' do
16
+ Topic.columns.each do |column|
17
+ assert_equal 'topics', column.table_name, "This column #{column.inspect} did not know it's #table_name"
18
+ assert_equal Topic, column.table_klass, "This column #{column.inspect} did not know it's #table_klass"
19
+ end
20
+ end
21
+
22
+ should 'return correct null, limit, and default for Topic' do
23
+ tch = Topic.columns_hash
24
+ assert_equal false, tch['id'].null
25
+ assert_equal true, tch['title'].null
26
+ assert_equal 255, tch['author_name'].limit
27
+ assert_equal true, tch['approved'].default
28
+ assert_equal 0, tch['replies_count'].default
29
+ end
30
+
31
+ context 'For binary columns' do
32
+
33
+ setup do
34
+ @binary_string = "GIF89a\001\000\001\000\200\000\000\377\377\377\000\000\000!\371\004\000\000\000\000\000,\000\000\000\000\001\000\001\000\000\002\002D\001\000;"
35
+ @saved_bdata = Binary.create!(:data => @binary_string)
36
+ end
37
+
38
+ should 'read and write binary data equally' do
39
+ assert_equal @binary_string, Binary.find(@saved_bdata).data
40
+ end
41
+
42
+ should 'have correct attributes' do
43
+ column = Binary.columns_hash['data']
44
+ assert_equal :binary, column.type
45
+ assert_equal @connection.native_binary_database_type, column.sql_type
46
+ assert_equal nil, column.limit
47
+ end
48
+
49
+ should 'quote data for sqlserver with literal 0x prefix' do
50
+ # See the output of the stored procedure: 'exec sp_datatype_info'
51
+ sqlserver_encoded_bdata = "0x47494638396101000100800000ffffff00000021f90400000000002c00000000010001000002024401003b"
52
+ assert_equal sqlserver_encoded_bdata, @column_klass.string_to_binary(@binary_string)
53
+ end
54
+
55
+ end
56
+
57
+ context 'For string columns' do
58
+
59
+ setup do
60
+ @char = SqlServerString.columns_hash['char']
61
+ @char10 = SqlServerString.columns_hash['char_10']
62
+ @varcharmax = SqlServerString.columns_hash['varchar_max']
63
+ @varcharmax10 = SqlServerString.columns_hash['varchar_max_10']
64
+ end
65
+
66
+ should 'have correct simplified types' do
67
+ assert_equal :string, @char.type
68
+ assert_equal :string, @char10.type
69
+ if sqlserver_2005? || sqlserver_2008?
70
+ assert_equal :text, @varcharmax.type, @varcharmax.inspect
71
+ assert_equal :text, @varcharmax10.type, @varcharmax10.inspect
72
+ end
73
+ end
74
+
75
+ should 'have correct #sql_type per schema definition' do
76
+ assert_equal 'char(1)', @char.sql_type, 'Specifing a char type with no limit is 1 by SQL Server standards.'
77
+ assert_equal 'char(10)', @char10.sql_type, @char10.inspect
78
+ if sqlserver_2005? || sqlserver_2008?
79
+ assert_equal 'varchar(max)', @varcharmax.sql_type, 'A -1 limit should be converted to max (max) type.'
80
+ assert_equal 'varchar(max)', @varcharmax10.sql_type, 'A -1 limit should be converted to max (max) type.'
81
+ end
82
+ end
83
+
84
+ should 'have correct #limit per schema definition' do
85
+ assert_equal 1, @char.limit
86
+ assert_equal 10, @char10.limit
87
+ if sqlserver_2005? || sqlserver_2008?
88
+ assert_equal nil, @varcharmax.limit, 'Limits on max types are moot and we should let rails know that.'
89
+ assert_equal nil, @varcharmax10.limit, 'Limits on max types are moot and we should let rails know that.'
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+
96
+ context 'For all national/unicode columns' do
97
+
98
+ setup do
99
+ @nchar = SqlServerUnicode.columns_hash['nchar']
100
+ @nvarchar = SqlServerUnicode.columns_hash['nvarchar']
101
+ @ntext = SqlServerUnicode.columns_hash['ntext']
102
+ @ntext10 = SqlServerUnicode.columns_hash['ntext_10']
103
+ @nchar10 = SqlServerUnicode.columns_hash['nchar_10']
104
+ @nvarchar100 = SqlServerUnicode.columns_hash['nvarchar_100']
105
+ @nvarcharmax = SqlServerUnicode.columns_hash['nvarchar_max']
106
+ @nvarcharmax10 = SqlServerUnicode.columns_hash['nvarchar_max_10']
107
+ end
108
+
109
+ should 'all respond true to #is_utf8?' do
110
+ SqlServerUnicode.columns_hash.except('id').values.each do |column|
111
+ assert column.is_utf8?, "This column #{column.inspect} should have been a unicode column."
112
+ end
113
+ end
114
+
115
+ should 'have correct simplified types' do
116
+ assert_equal :string, @nchar.type
117
+ assert_equal :string, @nvarchar.type
118
+ assert_equal :text, @ntext.type
119
+ assert_equal :text, @ntext10.type
120
+ assert_equal :string, @nchar10.type
121
+ assert_equal :string, @nvarchar100.type
122
+ if sqlserver_2005? || sqlserver_2008?
123
+ assert_equal :text, @nvarcharmax.type, @nvarcharmax.inspect
124
+ assert_equal :text, @nvarcharmax10.type, @nvarcharmax10.inspect
125
+ end
126
+ end
127
+
128
+ should 'have correct #sql_type per schema definition' do
129
+ assert_equal 'nchar(1)', @nchar.sql_type, 'Specifing a nchar type with no limit is 1 by SQL Server standards.'
130
+ assert_equal 'nvarchar(255)', @nvarchar.sql_type, 'Default nvarchar limit is 255.'
131
+ assert_equal 'ntext', @ntext.sql_type, 'Nice and clean ntext, limit means nothing here.'
132
+ assert_equal 'ntext', @ntext10.sql_type, 'Even a next with a limit of 10 specified will mean nothing.'
133
+ assert_equal 'nchar(10)', @nchar10.sql_type, 'An nchar with a limit of 10 needs to have it show up here.'
134
+ assert_equal 'nvarchar(100)', @nvarchar100.sql_type, 'An nvarchar with a specified limit of 100 needs to show it.'
135
+ if sqlserver_2005? || sqlserver_2008?
136
+ assert_equal 'nvarchar(max)', @nvarcharmax.sql_type, 'A -1 limit should be converted to max (max) type.'
137
+ assert_equal 'nvarchar(max)', @nvarcharmax10.sql_type, 'A -1 limit should be converted to max (max) type.'
138
+ end
139
+ end
140
+
141
+ should 'have correct #limit per schema definition' do
142
+ assert_equal 1, @nchar.limit
143
+ assert_equal 255, @nvarchar.limit
144
+ assert_equal nil, @ntext.limit, 'An ntext column limit is moot, it is a fixed variable length'
145
+ assert_equal 10, @nchar10.limit
146
+ assert_equal 100, @nvarchar100.limit
147
+ if sqlserver_2005? || sqlserver_2008?
148
+ assert_equal nil, @nvarcharmax.limit, 'Limits on max types are moot and we should let rails know that.'
149
+ assert_equal nil, @nvarcharmax10.limit, 'Limits on max types are moot and we should let rails know that.'
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ context 'For datetime columns' do
156
+
157
+ setup do
158
+ @date = SqlServerChronic.columns_hash['date']
159
+ @time = SqlServerChronic.columns_hash['time']
160
+ @datetime = SqlServerChronic.columns_hash['datetime']
161
+ @smalldatetime = SqlServerChronic.columns_hash['smalldatetime']
162
+ end
163
+
164
+ should 'have correct simplified type for uncast datetime' do
165
+ assert_equal :datetime, @datetime.type
166
+ end
167
+
168
+ should 'all be a datetime #sql_type' do
169
+ assert_equal 'datetime', @date.sql_type
170
+ assert_equal 'datetime', @time.sql_type
171
+ assert_equal 'datetime', @datetime.sql_type
172
+ end
173
+
174
+ should 'all be have nil #limit' do
175
+ assert_equal nil, @date.limit
176
+ assert_equal nil, @time.limit
177
+ assert_equal nil, @datetime.limit
178
+ end
179
+
180
+ context 'For smalldatetime types' do
181
+
182
+ should 'have created that type using rails migrations' do
183
+ assert_equal 'smalldatetime', @smalldatetime.sql_type
184
+ end
185
+
186
+ should 'be able to insert column without truncation warnings or the like' do
187
+ SqlServerChronic.create! :smalldatetime => Time.now
188
+ end
189
+
190
+ should 'be able to update column without truncation warnings or the like' do
191
+ ssc = SqlServerChronic.create! :smalldatetime => 2.days.ago
192
+ ssc.update_attributes! :smalldatetime => Time.now
193
+ end
194
+
195
+ end
196
+
197
+ context 'which have coerced types' do
198
+
199
+ setup do
200
+ christmas_08 = "2008-12-25".to_time
201
+ christmas_08_afternoon = "2008-12-25 12:00".to_time
202
+ @chronic_date = SqlServerChronic.create!(:date => christmas_08).reload
203
+ @chronic_time = SqlServerChronic.create!(:time => christmas_08_afternoon).reload
204
+ end
205
+
206
+ should 'have an inheritable attribute ' do
207
+ assert SqlServerChronic.coerced_sqlserver_date_columns.include?('date')
208
+ end
209
+
210
+ should 'have column and objects cast to date' do
211
+ assert_equal :date, @date.type, "This column: \n#{@date.inspect}"
212
+ assert_instance_of Date, @chronic_date.date
213
+ end
214
+
215
+ should 'have column objects cast to time' do
216
+ assert_equal :time, @time.type, "This column: \n#{@time.inspect}"
217
+ assert_instance_of Time, @chronic_time.time
218
+ end
219
+
220
+ end
221
+
222
+ end
223
+
224
+ context 'For decimal and numeric columns' do
225
+
226
+ setup do
227
+ @bank_balance = NumericData.columns_hash['bank_balance']
228
+ @big_bank_balance = NumericData.columns_hash['big_bank_balance']
229
+ @world_population = NumericData.columns_hash['world_population']
230
+ @my_house_population = NumericData.columns_hash['my_house_population']
231
+ end
232
+
233
+ should 'have correct simplified types' do
234
+ assert_equal :decimal, @bank_balance.type
235
+ assert_equal :decimal, @big_bank_balance.type
236
+ assert_equal :integer, @world_population.type, 'Since #extract_scale == 0'
237
+ assert_equal :integer, @my_house_population.type, 'Since #extract_scale == 0'
238
+ end
239
+
240
+ should 'have correct #sql_type' do
241
+ assert_equal 'decimal(10,2)', @bank_balance.sql_type
242
+ assert_equal 'decimal(15,2)', @big_bank_balance.sql_type
243
+ assert_equal 'decimal(10,0)', @world_population.sql_type
244
+ assert_equal 'decimal(2,0)', @my_house_population.sql_type
245
+ end
246
+
247
+ should 'have correct #limit' do
248
+ assert_equal nil, @bank_balance.limit
249
+ assert_equal nil, @big_bank_balance.limit
250
+ assert_equal nil, @world_population.limit
251
+ assert_equal nil, @my_house_population.limit
252
+ end
253
+
254
+ should 'return correct precisions and scales' do
255
+ assert_equal [10,2], [@bank_balance.precision, @bank_balance.scale]
256
+ assert_equal [15,2], [@big_bank_balance.precision, @big_bank_balance.scale]
257
+ assert_equal [10,0], [@world_population.precision, @world_population.scale]
258
+ assert_equal [2,0], [@my_house_population.precision, @my_house_population.scale]
259
+ end
260
+
261
+ end
262
+
263
+
264
+ end
@@ -0,0 +1,142 @@
1
+ require 'cases/sqlserver_helper'
2
+ require 'models/reply'
3
+
4
+ class ConnectionTestSqlserver < ActiveRecord::TestCase
5
+
6
+ self.use_transactional_fixtures = false
7
+
8
+ fixtures :topics, :accounts
9
+
10
+ def setup
11
+ @connection = ActiveRecord::Base.connection
12
+ end
13
+
14
+
15
+ should 'return finished DBI statment handle from #execute without block' do
16
+ handle = @connection.execute('SELECT * FROM [topics]')
17
+ assert_instance_of DBI::StatementHandle, handle
18
+ assert handle.finished?
19
+ end
20
+
21
+ should 'finish DBI statment handle from #execute with block' do
22
+ assert_all_statements_used_are_closed do
23
+ @connection.execute('SELECT * FROM [topics]') { }
24
+ end
25
+ end
26
+
27
+ should 'return an unfinished DBI statement handler from #raw_execute' do
28
+ handle = @connection.send(:raw_execute,'SELECT * FROM [topics]')
29
+ assert_instance_of DBI::StatementHandle, handle
30
+ assert !handle.finished?
31
+ end
32
+
33
+ should 'finish connection from #raw_select' do
34
+ assert_all_statements_used_are_closed do
35
+ @connection.send(:raw_select,'SELECT * FROM [topics]')
36
+ end
37
+ end
38
+
39
+ should 'affect rows' do
40
+ assert Topic.connection.instance_variable_get("@connection")["AutoCommit"]
41
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
42
+ updated = Topic.update(topic_data.keys, topic_data.values)
43
+ assert_equal 2, updated.size
44
+ assert_equal "1 updated", Topic.find(1).content
45
+ assert_equal "2 updated", Topic.find(2).content
46
+ assert_equal 2, Topic.delete([1, 2])
47
+ end
48
+
49
+ should 'execute without block closes statement' do
50
+ assert_all_statements_used_are_closed do
51
+ @connection.execute("SELECT 1")
52
+ end
53
+ end
54
+
55
+ should 'execute with block closes statement' do
56
+ assert_all_statements_used_are_closed do
57
+ @connection.execute("SELECT 1") do |sth|
58
+ assert !sth.finished?, "Statement should still be alive within block"
59
+ end
60
+ end
61
+ end
62
+
63
+ should 'insert with identity closes statement' do
64
+ assert_all_statements_used_are_closed do
65
+ @connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
66
+ end
67
+ end
68
+
69
+ should 'insert without identity closes statement' do
70
+ assert_all_statements_used_are_closed do
71
+ @connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
72
+ end
73
+ end
74
+
75
+ should 'active closes statement' do
76
+ assert_all_statements_used_are_closed do
77
+ @connection.active?
78
+ end
79
+ end
80
+
81
+ context 'Connection management' do
82
+
83
+ setup do
84
+ assert @connection.active?
85
+ end
86
+
87
+ should 'be able to disconnect and reconnect at will' do
88
+ @connection.disconnect!
89
+ assert !@connection.active?
90
+ @connection.reconnect!
91
+ assert @connection.active?
92
+ end
93
+
94
+ should 'auto reconnect when setting is on' do
95
+ with_auto_connect(true) do
96
+ @connection.disconnect!
97
+ assert_nothing_raised() { Topic.count }
98
+ assert @connection.active?
99
+ end
100
+ end
101
+
102
+ should 'not auto reconnect when setting is off' do
103
+ with_auto_connect(false) do
104
+ @connection.disconnect!
105
+ assert_raise(ActiveRecord::StatementInvalid) { Topic.count }
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+
112
+
113
+ private
114
+
115
+ def assert_all_statements_used_are_closed(&block)
116
+ existing_handles = []
117
+ ObjectSpace.each_object(DBI::StatementHandle) {|handle| existing_handles << handle}
118
+ GC.disable
119
+ yield
120
+ used_handles = []
121
+ ObjectSpace.each_object(DBI::StatementHandle) {|handle| used_handles << handle unless existing_handles.include? handle}
122
+ assert_block "No statements were used within given block" do
123
+ used_handles.size > 0
124
+ end
125
+ ObjectSpace.each_object(DBI::StatementHandle) do |handle|
126
+ assert_block "Statement should have been closed within given block" do
127
+ handle.finished?
128
+ end
129
+ end
130
+ ensure
131
+ GC.enable
132
+ end
133
+
134
+ def with_auto_connect(boolean)
135
+ existing = ActiveRecord::ConnectionAdapters::SQLServerAdapter.auto_connect
136
+ ActiveRecord::ConnectionAdapters::SQLServerAdapter.auto_connect = boolean
137
+ yield
138
+ ensure
139
+ ActiveRecord::ConnectionAdapters::SQLServerAdapter.auto_connect = existing
140
+ end
141
+
142
+ end