tiny_tds 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,111 @@
1
+ require 'rubygems'
2
+ require 'bench_press'
3
+ $:.unshift File.expand_path('../../../lib',__FILE__)
4
+ require 'tiny_tds'
5
+
6
+ extend BenchPress
7
+
8
+ author 'Ken Collins'
9
+ summary 'Benchmark TinyTds Querys'
10
+
11
+ reps 1_000
12
+
13
+ @client = TinyTds::Client.new({
14
+ :dataserver => ENV['TINYTDS_UNIT_DATASERVER'],
15
+ :username => 'tinytds',
16
+ :password => '',
17
+ :database => 'tinytdstest',
18
+ :appname => 'TinyTds Dev',
19
+ :login_timeout => 5,
20
+ :timeout => 5
21
+ })
22
+
23
+ @query_nothing = "SELECT NULL AS [null]"
24
+ @query_ints = "SELECT [int], [bigint], [smallint], [tinyint] FROM [datatypes]"
25
+ @query_binaries = "SELECT [binary_50], [image], [varbinary_50] FROM [datatypes]"
26
+ @query_bits = "SELECT [bit] FROM [datatypes]"
27
+ @query_chars = "SELECT [char_10], [nchar_10], [ntext], [nvarchar_50], [text], [varchar_50] FROM [datatypes]"
28
+ @query_dates = "SELECT [datetime], [smalldatetime] FROM [datatypes]"
29
+ @query_decimals = "SELECT [decimal_9_2], [decimal_16_4], [numeric_18_0], [numeric_36_2] FROM [datatypes]"
30
+ @query_floats = "SELECT [float], [real] FROM [datatypes]"
31
+ @query_moneys = "SELECT [money], [smallmoney] FROM [datatypes]"
32
+ @query_guids = "SELECT [uniqueidentifier] FROM [datatypes]"
33
+ @query_all = "SELECT * FROM [datatypes]"
34
+
35
+ def select_all(query)
36
+ @client.execute(query).each
37
+ end
38
+
39
+
40
+ measure "Nothing" do
41
+ select_all @query_nothing
42
+ end
43
+
44
+ measure "Integers" do
45
+ select_all @query_ints
46
+ end
47
+
48
+ measure "Binaries" do
49
+ select_all @query_binaries
50
+ end
51
+
52
+ measure "Bits" do
53
+ select_all @query_bits
54
+ end
55
+
56
+ measure "Chars" do
57
+ select_all @query_chars
58
+ end
59
+
60
+ measure "Dates" do
61
+ select_all @query_dates
62
+ end
63
+
64
+ measure "Decimals" do
65
+ select_all @query_decimals
66
+ end
67
+
68
+ measure "Floats" do
69
+ select_all @query_floats
70
+ end
71
+
72
+ measure "Moneys" do
73
+ select_all @query_moneys
74
+ end
75
+
76
+ measure "Guids" do
77
+ select_all @query_guids
78
+ end
79
+
80
+ measure "All" do
81
+ select_all @query_all
82
+ end
83
+
84
+
85
+ =begin
86
+
87
+ System Information
88
+ ------------------
89
+ Operating System: Mac OS X 10.6.4 (10F569)
90
+ CPU: Intel Core 2 Duo 2.4 GHz
91
+ Processor Count: 2
92
+ Memory: 4 GB
93
+ ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10.4.0]
94
+
95
+ "Nothing" is up to 89% faster over 1,000 repetitions
96
+ ----------------------------------------------------
97
+
98
+ Nothing 0.357741832733154 secs Fastest
99
+ Guids 0.460683107376099 secs 22% Slower
100
+ Bits 0.483309984207153 secs 25% Slower
101
+ Floats 0.505340099334717 secs 29% Slower
102
+ Moneys 0.523844003677368 secs 31% Slower
103
+ Integers 0.616975069046021 secs 42% Slower
104
+ Binaries 0.639773845672607 secs 44% Slower
105
+ Decimals 0.670897960662842 secs 46% Slower
106
+ Chars 0.800287008285522 secs 55% Slower
107
+ Dates 0.950634956359863 secs 62% Slower
108
+ All 2.91044211387634 secs 87% Slower
109
+
110
+ =end
111
+
@@ -0,0 +1,163 @@
1
+ require 'test_helper'
2
+
3
+ class ClientTest < TinyTds::TestCase
4
+
5
+ context 'With valid credentials' do
6
+
7
+ setup do
8
+ @client = new_connection
9
+ end
10
+
11
+ should 'not be closed' do
12
+ assert !@client.closed?
13
+ assert @client.active?
14
+ end
15
+
16
+ should 'allow client connection to be closed' do
17
+ assert @client.close
18
+ assert @client.closed?
19
+ assert !@client.active?
20
+ action = lambda { @client.execute('SELECT 1 as [one]').each }
21
+ assert_raise_tinytds_error(action) do |e|
22
+ assert_match %r{closed connection}i, e.message, 'ignore if non-english test run'
23
+ end
24
+ end
25
+
26
+ should 'have a getters for the tds version information (brittle since conf takes precedence)' do
27
+ assert_equal 9, @client.tds_version
28
+ assert_equal 'DBTDS_8_0 - Microsoft SQL Server 2000', @client.tds_version_info
29
+ end
30
+
31
+ should 'use UTF-8 client charset/encoding by default' do
32
+ assert_equal 'UTF-8', @client.charset
33
+ assert_equal Encoding.find('UTF-8'), @client.encoding if ruby19?
34
+ end
35
+
36
+ should 'have a #escape method used for quote strings' do
37
+ assert_equal "''hello''", @client.escape("'hello'")
38
+ end
39
+
40
+ should 'allow valid iconv character set' do
41
+ ['CP850', 'CP1252', 'ISO-8859-1'].each do |encoding|
42
+ client = new_connection(:encoding => encoding)
43
+ assert_equal encoding, client.charset
44
+ assert_equal Encoding.find(encoding), client.encoding if ruby19?
45
+ end
46
+ end
47
+
48
+ should 'be able to use :host/:port connection' do
49
+ client = new_connection :dataserver => nil, :host => ENV['TINYTDS_UNIT_HOST'], :port => 1433
50
+ end
51
+
52
+ end
53
+
54
+ context 'With in-valid options' do
55
+
56
+ should 'raise an argument error when no :host given and :dataserver is blank' do
57
+ assert_raises(ArgumentError) { new_connection :dataserver => nil, :host => nil }
58
+ end
59
+
60
+ should 'raise an argument error when no :username is supplied' do
61
+ assert_raises(ArgumentError) { TinyTds::Client.new :username => nil }
62
+ end
63
+
64
+ should 'raise TinyTds exception with undefined :dataserver' do
65
+ options = connection_options :login_timeout => 1, :dataserver => '127.0.0.2'
66
+ action = lambda { new_connection(options) }
67
+ assert_raise_tinytds_error(action) do |e|
68
+ assert [20008,20009].include?(e.db_error_number)
69
+ assert_equal 9, e.severity
70
+ assert_match %r{unable to (open|connect)}i, e.message, 'ignore if non-english test run'
71
+ end
72
+ assert_new_connections_work
73
+ end
74
+
75
+ should 'raise TinyTds exception with long query past :timeout option' do
76
+ client = new_connection :timeout => 1
77
+ action = lambda { client.execute("WaitFor Delay '00:00:02'").do }
78
+ assert_raise_tinytds_error(action) do |e|
79
+ assert_equal 20003, e.db_error_number
80
+ assert_equal 6, e.severity
81
+ assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
82
+ end
83
+ assert_client_works(client)
84
+ assert_new_connections_work
85
+ end
86
+
87
+ should 'not timeout per sql batch when not under transaction' do
88
+ client = new_connection :timeout => 2
89
+ client.execute("WaitFor Delay '00:00:01'").do
90
+ client.execute("WaitFor Delay '00:00:01'").do
91
+ client.execute("WaitFor Delay '00:00:01'").do
92
+ end
93
+
94
+ should 'not timeout per sql batch when under transaction' do
95
+ client = new_connection :timeout => 2
96
+ begin
97
+ client.execute("BEGIN TRANSACTION").do
98
+ client.execute("WaitFor Delay '00:00:01'").do
99
+ client.execute("WaitFor Delay '00:00:01'").do
100
+ client.execute("WaitFor Delay '00:00:01'").do
101
+ ensure
102
+ client.execute("COMMIT TRANSACTION").do
103
+ end
104
+ end
105
+
106
+ should_eventually 'run this test to prove we account for dropped connections' do
107
+ begin
108
+ client = new_connection :login_timeout => 2, :timeout => 2
109
+ assert_client_works(client)
110
+ STDOUT.puts "Disconnect network!"
111
+ sleep 10
112
+ STDOUT.puts "This should not get stuck past 6 seconds!"
113
+ action = lambda { client.execute('SELECT 1 as [one]').each }
114
+ assert_raise_tinytds_error(action) do |e|
115
+ assert_equal 20003, e.db_error_number
116
+ assert_equal 6, e.severity
117
+ assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
118
+ end
119
+ ensure
120
+ STDOUT.puts "Reconnect network!"
121
+ sleep 10
122
+ action = lambda { client.execute('SELECT 1 as [one]').each }
123
+ assert_raise_tinytds_error(action) do |e|
124
+ assert_equal 20047, e.db_error_number
125
+ assert_equal 1, e.severity
126
+ assert_match %r{dead or not enabled}i, e.message, 'ignore if non-english test run'
127
+ end
128
+ assert_new_connections_work
129
+ end
130
+ end
131
+
132
+ should 'raise TinyTds exception with wrong :username' do
133
+ options = connection_options :username => 'willnotwork'
134
+ action = lambda { new_connection(options) }
135
+ assert_raise_tinytds_error(action) do |e|
136
+ if sqlserver_azure?
137
+ assert_match %r{server name cannot be determined}i, e.message, 'ignore if non-english test run'
138
+ else
139
+ assert_equal 18456, e.db_error_number
140
+ assert_equal 14, e.severity
141
+ assert_match %r{login failed}i, e.message, 'ignore if non-english test run'
142
+ end
143
+ end
144
+ assert_new_connections_work
145
+ end
146
+
147
+ should 'fail miserably with unknown encoding option' do
148
+ options = connection_options :encoding => 'ISO-WTF'
149
+ action = lambda { new_connection(options) }
150
+ assert_raise_tinytds_error(action) do |e|
151
+ assert_equal 20002, e.db_error_number
152
+ assert_equal 9, e.severity
153
+ assert_match %r{connection failed}i, e.message, 'ignore if non-english test run'
154
+ end
155
+ assert_new_connections_work
156
+ end
157
+
158
+ end
159
+
160
+
161
+
162
+ end
163
+
@@ -0,0 +1,437 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class ResultTest < TinyTds::TestCase
5
+
6
+ context 'Basic query and result' do
7
+
8
+ setup do
9
+ @@current_schema_loaded ||= load_current_schema
10
+ @client = new_connection
11
+ @query1 = 'SELECT 1 AS [one]'
12
+ end
13
+
14
+ should 'have included Enumerable' do
15
+ assert TinyTds::Result.ancestors.include?(Enumerable)
16
+ end
17
+
18
+ should 'respond to #each' do
19
+ result = @client.execute(@query1)
20
+ assert result.respond_to?(:each)
21
+ end
22
+
23
+ should 'return all results for #each with no block' do
24
+ result = @client.execute(@query1)
25
+ data = result.each
26
+ row = data.first
27
+ assert_instance_of Array, data
28
+ assert_equal 1, data.size
29
+ assert_instance_of Hash, row, 'hash is the default query option'
30
+ end
31
+
32
+ should 'return all results for #each with a block yielding a row at a time' do
33
+ result = @client.execute(@query1)
34
+ data = result.each do |row|
35
+ assert_instance_of Hash, row, 'hash is the default query option'
36
+ end
37
+ assert_instance_of Array, data
38
+ end
39
+
40
+ should 'allow successive calls to each returning the same data' do
41
+ result = @client.execute(@query1)
42
+ data = result.each
43
+ result.each
44
+ assert_equal data.object_id, result.each.object_id
45
+ assert_equal data.first.object_id, result.each.first.object_id
46
+ end
47
+
48
+ should 'return hashes with string keys' do
49
+ result = @client.execute(@query1)
50
+ row = result.each(:as => :hash, :symbolize_keys => false).first
51
+ assert_instance_of Hash, row
52
+ assert_equal ['one'], row.keys
53
+ assert_equal ['one'], result.fields
54
+ end
55
+
56
+ should 'return hashes with symbol keys' do
57
+ result = @client.execute(@query1)
58
+ row = result.each(:as => :hash, :symbolize_keys => true).first
59
+ assert_instance_of Hash, row
60
+ assert_equal [:one], row.keys
61
+ assert_equal [:one], result.fields
62
+ end
63
+
64
+ should 'return arrays with string fields' do
65
+ result = @client.execute(@query1)
66
+ row = result.each(:as => :array, :symbolize_keys => false).first
67
+ assert_instance_of Array, row
68
+ assert_equal ['one'], result.fields
69
+ end
70
+
71
+ should 'return arrays with symbol fields' do
72
+ result = @client.execute(@query1)
73
+ row = result.each(:as => :array, :symbolize_keys => true).first
74
+ assert_instance_of Array, row
75
+ assert_equal [:one], result.fields
76
+ end
77
+
78
+ should 'be able to turn :cache_rows option off' do
79
+ result = @client.execute(@query1)
80
+ local = []
81
+ result.each(:cache_rows => false) do |row|
82
+ local << row
83
+ end
84
+ assert local.first, 'should have iterated over each row'
85
+ assert_equal [], result.each, 'should not have been cached'
86
+ assert_equal ['one'], result.fields, 'should still cache field names'
87
+ end
88
+
89
+ should 'be able to get the first result row only' do
90
+ load_current_schema
91
+ big_query = "SELECT [id] FROM [datatypes]"
92
+ one = @client.execute(big_query).each(:first => true)
93
+ many = @client.execute(big_query).each
94
+ assert many.size > 1
95
+ assert one.size == 1
96
+ end
97
+
98
+ should 'cope with no results when using first option' do
99
+ data = @client.execute("SELECT [id] FROM [datatypes] WHERE [id] = -1").each(:first => true)
100
+ assert_equal [], data
101
+ end
102
+
103
+ should 'delete, insert and find data' do
104
+ rollback_transaction(@client) do
105
+ text = 'test insert and delete'
106
+ @client.execute("DELETE FROM [datatypes] WHERE [varchar_50] IS NOT NULL").do
107
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
108
+ row = @client.execute("SELECT [varchar_50] FROM [datatypes] WHERE [varchar_50] IS NOT NULL").each.first
109
+ assert row
110
+ assert_equal text, row['varchar_50']
111
+ end
112
+ end
113
+
114
+ should 'insert and find unicode data' do
115
+ rollback_transaction(@client) do
116
+ text = '✓'
117
+ @client.execute("DELETE FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").do
118
+ @client.execute("INSERT INTO [datatypes] ([nvarchar_50]) VALUES (N'#{text}')").do
119
+ row = @client.execute("SELECT [nvarchar_50] FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").each.first
120
+ assert_equal text, row['nvarchar_50']
121
+ end
122
+ end
123
+
124
+ should 'delete and update with affected rows support and insert with identity support in native sql' do
125
+ rollback_transaction(@client) do
126
+ text = 'test affected rows sql'
127
+ @client.execute("DELETE FROM [datatypes]").do
128
+ afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
129
+ assert_instance_of Fixnum, afrows
130
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
131
+ pk1 = @client.execute("SELECT SCOPE_IDENTITY() AS Ident").each.first['Ident']
132
+ assert_instance_of BigDecimal, pk1, 'native is numeric(38,0) for SCOPE_IDENTITY() function'
133
+ pk2 = @client.execute("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident").each.first['Ident']
134
+ assert_instance_of Fixnum, pk2, 'we should be able to CAST to bigint'
135
+ assert_equal pk2, pk1.to_i, 'just making sure the 2 line up'
136
+ @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
137
+ afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
138
+ assert_equal 1, afrows
139
+ end
140
+ end
141
+
142
+ should 'have a #do method that cancels result rows and returns affected rows natively' do
143
+ rollback_transaction(@client) do
144
+ text = 'test affected rows native'
145
+ count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
146
+ deleted_rows = @client.execute("DELETE FROM [datatypes]").do
147
+ assert_equal count, deleted_rows, 'should have deleted rows equal to count'
148
+ inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
149
+ assert_equal 1, inserted_rows, 'should have inserted row for one above'
150
+ updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
151
+ assert_equal 1, updated_rows, 'should have updated row for one above' unless sqlserver_2000? # Will report -1
152
+ end
153
+ end
154
+
155
+ should 'allow native affected rows using #do to work under transaction' do
156
+ rollback_transaction(@client) do
157
+ text = 'test affected rows native in transaction'
158
+ @client.execute("BEGIN TRANSACTION").do
159
+ @client.execute("DELETE FROM [datatypes]").do
160
+ inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
161
+ assert_equal 1, inserted_rows, 'should have inserted row for one above'
162
+ updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
163
+ assert_equal 1, updated_rows, 'should have updated row for one above' unless sqlserver_2000? # Will report -1
164
+ end
165
+ end
166
+
167
+ should 'have an #insert method that cancels result rows and returns the SCOPE_IDENTITY() natively' do
168
+ rollback_transaction(@client) do
169
+ text = 'test scope identity rows native'
170
+ @client.execute("DELETE FROM [datatypes] WHERE [varchar_50] = '#{text}'").do
171
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
172
+ sql_identity = @client.execute("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident").each.first['Ident']
173
+ native_identity = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").insert
174
+ assert_equal sql_identity+1, native_identity
175
+ end
176
+ end
177
+
178
+ should 'be able to begin/commit transactions with raw sql' do
179
+ rollback_transaction(@client) do
180
+ @client.execute("BEGIN TRANSACTION").do
181
+ @client.execute("DELETE FROM [datatypes]").do
182
+ @client.execute("COMMIT TRANSACTION").do
183
+ count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
184
+ assert_equal 0, count
185
+ end
186
+ end
187
+
188
+ should 'be able to begin/rollback transactions with raw sql' do
189
+ load_current_schema
190
+ @client.execute("BEGIN TRANSACTION").do
191
+ @client.execute("DELETE FROM [datatypes]").do
192
+ @client.execute("ROLLBACK TRANSACTION").do
193
+ count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
194
+ 0.wont_equal count
195
+ end
196
+
197
+ should 'have a #fields accessor with logic default and valid outcome' do
198
+ result = @client.execute(@query1)
199
+ result.fields.must_equal ['one']
200
+ result.each
201
+ result.fields.must_equal ['one']
202
+ end
203
+
204
+ should 'always return an array for fields for all sql' do
205
+ result = @client.execute("USE [tinytdstest]")
206
+ result.fields.must_equal []
207
+ result.do
208
+ result.fields.must_equal []
209
+ end
210
+
211
+ should 'return fields even when no results are found' do
212
+ no_results_query = "SELECT [id], [varchar_50] FROM [datatypes] WHERE [varchar_50] = 'NOTFOUND'"
213
+ # Fields before each.
214
+ result = @client.execute(no_results_query)
215
+ result.fields.must_equal ['id','varchar_50']
216
+ result.each
217
+ result.fields.must_equal ['id','varchar_50']
218
+ # Each then fields
219
+ result = @client.execute(no_results_query)
220
+ result.each
221
+ result.fields.must_equal ['id','varchar_50']
222
+ end
223
+
224
+ should 'allow the result to be canceled before reading' do
225
+ result = @client.execute(@query1)
226
+ result.cancel
227
+ @client.execute(@query1).each
228
+ end
229
+
230
+ should 'work in tandem with the client when needing to find out if client has sql sent and result is canceled or not' do
231
+ # Default state.
232
+ @client = TinyTds::Client.new(connection_options)
233
+ @client.sqlsent?.must_equal false
234
+ @client.canceled?.must_equal false
235
+ # With active result before and after cancel.
236
+ result = @client.execute(@query1)
237
+ @client.sqlsent?.must_equal true
238
+ @client.canceled?.must_equal false
239
+ result.cancel
240
+ @client.sqlsent?.must_equal false
241
+ @client.canceled?.must_equal true
242
+ assert result.cancel, 'must be safe to call again'
243
+ # With each and no block.
244
+ @client.execute(@query1).each
245
+ @client.sqlsent?.must_equal false
246
+ @client.canceled?.must_equal false
247
+ # With each and block.
248
+ @client.execute(@query1).each do |row|
249
+ @client.sqlsent?.must_equal true, 'when iterating over each row in a block'
250
+ @client.canceled?.must_equal false
251
+ end
252
+ @client.sqlsent?.must_equal false
253
+ @client.canceled?.must_equal false
254
+ # With each and block canceled half way thru.
255
+ count = @client.execute("SELECT COUNT([id]) AS [count] FROM [datatypes]").each[0]['count']
256
+ assert count > 10, 'since we want to cancel early for test'
257
+ result = @client.execute("SELECT [id] FROM [datatypes]")
258
+ index = 0
259
+ result.each do |row|
260
+ break if index > 10
261
+ index += 1
262
+ end
263
+ @client.sqlsent?.must_equal true
264
+ @client.canceled?.must_equal false
265
+ result.cancel
266
+ @client.sqlsent?.must_equal false
267
+ @client.canceled?.must_equal true
268
+ # With do method.
269
+ @client.execute(@query1).do
270
+ @client.sqlsent?.must_equal false
271
+ @client.canceled?.must_equal true
272
+ # With insert method.
273
+ rollback_transaction(@client) do
274
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('test')").insert
275
+ @client.sqlsent?.must_equal false
276
+ @client.canceled?.must_equal true
277
+ end
278
+ # With first
279
+ @client.execute("SELECT [id] FROM [datatypes]").each(:first => true)
280
+ @client.sqlsent?.must_equal false
281
+ @client.canceled?.must_equal true
282
+ end
283
+
284
+ should 'use same string object for hash keys' do
285
+ data = @client.execute("SELECT [id], [bigint] FROM [datatypes]").each
286
+ assert_equal data.first.keys.map{ |r| r.object_id }, data.last.keys.map{ |r| r.object_id }
287
+ end
288
+
289
+ should 'have properly encoded column names' do
290
+ col_name = "öäüß"
291
+ @client.execute("DROP TABLE [test_encoding]").do rescue nil
292
+ @client.execute("CREATE TABLE [dbo].[test_encoding] ( [#{col_name}] [nvarchar](10) NOT NULL )").do
293
+ @client.execute("INSERT INTO [test_encoding] ([#{col_name}]) VALUES (N'#{col_name}')").do
294
+ result = @client.execute("SELECT [#{col_name}] FROM [test_encoding]")
295
+ row = result.each.first
296
+ assert_equal col_name, result.fields.first
297
+ assert_equal col_name, row.keys.first
298
+ assert_utf8_encoding result.fields.first
299
+ assert_utf8_encoding row.keys.first
300
+ end unless sqlserver_azure?
301
+
302
+ should 'allow #return_code to work with stored procedures and reset per sql batch' do
303
+ assert_nil @client.return_code
304
+ result = @client.execute("EXEC tinytds_TestReturnCodes")
305
+ assert_equal [{"one"=>1}], result.each
306
+ assert_equal 420, @client.return_code
307
+ assert_equal 420, result.return_code
308
+ result = @client.execute('SELECT 1 as [one]')
309
+ result.each
310
+ assert_nil @client.return_code
311
+ assert_nil result.return_code
312
+ end
313
+
314
+ context 'with multiple result sets' do
315
+
316
+ setup do
317
+ @double_select = "SELECT 1 AS [rs1]\nSELECT 2 AS [rs2]"
318
+ end
319
+
320
+ should 'handle a command buffer with double selects' do
321
+ result = @client.execute(@double_select)
322
+ result_sets = result.each
323
+ assert_equal 2, result_sets.size
324
+ assert_equal [{'rs1' => 1}], result_sets.first
325
+ assert_equal [{'rs2' => 2}], result_sets.last
326
+ assert_equal [['rs1'],['rs2']], result.fields
327
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
328
+ # As array
329
+ result = @client.execute(@double_select)
330
+ result_sets = result.each(:as => :array)
331
+ assert_equal 2, result_sets.size
332
+ assert_equal [[1]], result_sets.first
333
+ assert_equal [[2]], result_sets.last
334
+ assert_equal [['rs1'],['rs2']], result.fields
335
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
336
+ end
337
+
338
+ should 'yield each row for each result set' do
339
+ data = []
340
+ result_sets = @client.execute(@double_select).each { |row| data << row }
341
+ assert_equal data.first, result_sets.first[0]
342
+ assert_equal data.last, result_sets.last[0]
343
+ end
344
+
345
+ should 'from a stored procedure' do
346
+ results1, results2 = @client.execute("EXEC sp_helpconstraint '[datatypes]'").each
347
+ assert_equal [{"Object Name"=>"[datatypes]"}], results1
348
+ constraint_info = results2.first
349
+ assert constraint_info.key?("constraint_keys")
350
+ assert constraint_info.key?("constraint_type")
351
+ assert constraint_info.key?("constraint_name")
352
+ end
353
+
354
+ should 'ignore empty result sets' do
355
+ rollback_transaction(@client) do
356
+ @client.execute("DELETE FROM [datatypes]").do
357
+ id = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('test empty result sets')").insert
358
+ sql = %|
359
+ SET NOCOUNT ON
360
+ DECLARE @row_number TABLE (row int identity(1,1), id int)
361
+ INSERT INTO @row_number (id)
362
+ SELECT [datatypes].[id] FROM [datatypes]
363
+ SET NOCOUNT OFF
364
+ SELECT id FROM @row_number|
365
+ result = @client.execute(sql)
366
+ result.each.must_equal [{"id"=>id}]
367
+ result.fields.must_equal ['id']
368
+ end
369
+ end
370
+
371
+ end
372
+
373
+ context 'when casting to native ruby values' do
374
+
375
+ should 'return fixnum for 1' do
376
+ value = @client.execute('SELECT 1 AS [fixnum]').each.first['fixnum']
377
+ assert_equal 1, value
378
+ end
379
+
380
+ should 'return nil for NULL' do
381
+ value = @client.execute('SELECT NULL AS [null]').each.first['null']
382
+ assert_equal nil, value
383
+ end
384
+
385
+ end
386
+
387
+ context 'when shit happens' do
388
+
389
+ should 'cope with nil or empty buffer' do
390
+ assert_raises(TypeError) { @client.execute(nil) }
391
+ assert_equal [], @client.execute('').each
392
+ end
393
+
394
+ should 'throw an error when you execute another query with other results pending' do
395
+ result1 = @client.execute(@query1)
396
+ action = lambda { @client.execute(@query1) }
397
+ assert_raise_tinytds_error(action) do |e|
398
+ assert_match %r|with results pending|i, e.message
399
+ assert_equal 7, e.severity
400
+ assert_equal 20019, e.db_error_number
401
+ end
402
+ end
403
+
404
+ should 'error gracefully with bad table name' do
405
+ action = lambda { @client.execute('SELECT * FROM [foobar]').each }
406
+ assert_raise_tinytds_error(action) do |e|
407
+ assert_match %r|invalid object name.*foobar|i, e.message
408
+ assert_equal 16, e.severity
409
+ assert_equal 208, e.db_error_number
410
+ end
411
+ assert_followup_query
412
+ end
413
+
414
+ should 'error gracefully with invalid syntax' do
415
+ action = lambda { @client.execute('this will not work').each }
416
+ assert_raise_tinytds_error(action) do |e|
417
+ assert_match %r|incorrect syntax|i, e.message
418
+ assert_equal 15, e.severity
419
+ assert_equal 156, e.db_error_number
420
+ end
421
+ assert_followup_query
422
+ end
423
+
424
+ end
425
+
426
+ end
427
+
428
+
429
+ protected
430
+
431
+ def assert_followup_query
432
+ result = @client.execute(@query1)
433
+ assert_equal 1, result.each.first['one']
434
+ end
435
+
436
+ end
437
+