tiny_tds 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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
+