tiny_tds 0.6.2-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,126 @@
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
+ Query Tinytds
88
+ =============
89
+ Author: Ken Collins
90
+ Date: September 11, 2011
91
+ Summary: Benchmark TinyTds Querys
92
+
93
+ System Information
94
+ ------------------
95
+ Operating System: Mac OS X 10.7.1 (11B26)
96
+ CPU: Quad-Core Intel Xeon 2.66 GHz
97
+ Processor Count: 4
98
+ Memory: 24 GB
99
+ ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-darwin11.1.0], MBARI 0x6770, Ruby Enterprise Edition 2011.03
100
+
101
+ ----------------------------------------------------
102
+ (before 64bit times) (after 64bit times)
103
+ Nothing 0.287657022476196 secs Nothing 0.289273977279663 secs
104
+ Bits 0.406533002853394 secs Bits 0.424988031387329 secs
105
+ Guids 0.419962882995605 secs Guids 0.427381992340088 secs
106
+ Floats 0.452103137969971 secs Floats 0.455377101898193 secs
107
+ Moneys 0.481696844100952 secs Moneys 0.485175132751465 secs
108
+ Integers 0.496185064315796 secs Integers 0.525003910064697 secs
109
+ Binaries 0.538873195648193 secs Decimals 0.541536808013916 secs
110
+ Decimals 0.540570974349976 secs Binaries 0.542865991592407 secs
111
+ Dates 0.761389970779419 secs Dates 1.51440119743347 secs
112
+ Chars 0.793163061141968 secs Chars 0.666505098342896 secs
113
+ All 4.4630811214447 secs All 5.17242312431335 secs
114
+
115
+ =end
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
@@ -0,0 +1,170 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class ClientTest < TinyTds::TestCase
5
+
6
+ describe 'With valid credentials' do
7
+
8
+ before do
9
+ @client = new_connection
10
+ end
11
+
12
+ it 'must not be closed' do
13
+ assert !@client.closed?
14
+ assert @client.active?
15
+ end
16
+
17
+ it 'allows client connection to be closed' do
18
+ assert @client.close
19
+ assert @client.closed?
20
+ assert !@client.active?
21
+ action = lambda { @client.execute('SELECT 1 as [one]').each }
22
+ assert_raise_tinytds_error(action) do |e|
23
+ assert_match %r{closed connection}i, e.message, 'ignore if non-english test run'
24
+ end
25
+ end
26
+
27
+ it 'has getters for the tds version information (brittle since conf takes precedence)' do
28
+ if sybase_ase?
29
+ assert_equal 7, @client.tds_version
30
+ assert_equal 'DBTDS_5_0 - 5.0 SQL Server', @client.tds_version_info
31
+ else
32
+ assert_equal 9, @client.tds_version
33
+ assert_equal 'DBTDS_7_1/DBTDS_8_0 - Microsoft SQL Server 2000', @client.tds_version_info
34
+ end
35
+ end
36
+
37
+ it 'uses UTF-8 client charset/encoding by default' do
38
+ assert_equal 'UTF-8', @client.charset
39
+ assert_equal Encoding.find('UTF-8'), @client.encoding
40
+ end
41
+
42
+ it 'has a #escape method used for quote strings' do
43
+ assert_equal "''hello''", @client.escape("'hello'")
44
+ end
45
+
46
+ it 'allows valid iconv character set' do
47
+ ['CP850', 'CP1252', 'ISO-8859-1'].each do |encoding|
48
+ client = new_connection(:encoding => encoding)
49
+ assert_equal encoding, client.charset
50
+ assert_equal Encoding.find(encoding), client.encoding
51
+ end
52
+ end
53
+
54
+ it 'must be able to use :host/:port connection' do
55
+ client = new_connection :dataserver => nil, :host => ENV['TINYTDS_UNIT_HOST'], :port => ENV['TINYTDS_UNIT_PORT'] || 1433
56
+ end unless sqlserver_azure?
57
+
58
+ end
59
+
60
+ describe 'With in-valid options' do
61
+
62
+ it 'raises an argument error when no :host given and :dataserver is blank' do
63
+ assert_raises(ArgumentError) { new_connection :dataserver => nil, :host => nil }
64
+ end
65
+
66
+ it 'raises an argument error when no :username is supplied' do
67
+ assert_raises(ArgumentError) { TinyTds::Client.new :username => nil }
68
+ end
69
+
70
+ it 'raises TinyTds exception with undefined :dataserver' do
71
+ options = connection_options :login_timeout => 1, :dataserver => '127.0.0.2'
72
+ action = lambda { new_connection(options) }
73
+ assert_raise_tinytds_error(action) do |e|
74
+ assert [20008,20009].include?(e.db_error_number)
75
+ assert_equal 9, e.severity
76
+ assert_match %r{unable to (open|connect)}i, e.message, 'ignore if non-english test run'
77
+ end
78
+ assert_new_connections_work
79
+ end
80
+
81
+ it 'raises TinyTds exception with long query past :timeout option' do
82
+ client = new_connection :timeout => 1
83
+ action = lambda { client.execute("WaitFor Delay '00:00:02'").do }
84
+ assert_raise_tinytds_error(action) do |e|
85
+ assert_equal 20003, e.db_error_number
86
+ assert_equal 6, e.severity
87
+ assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
88
+ end
89
+ assert_client_works(client)
90
+ assert_new_connections_work
91
+ end
92
+
93
+ it 'must not timeout per sql batch when not under transaction' do
94
+ client = new_connection :timeout => 2
95
+ client.execute("WaitFor Delay '00:00:01'").do
96
+ client.execute("WaitFor Delay '00:00:01'").do
97
+ client.execute("WaitFor Delay '00:00:01'").do
98
+ end
99
+
100
+ it 'must not timeout per sql batch when under transaction' do
101
+ client = new_connection :timeout => 2
102
+ begin
103
+ client.execute("BEGIN TRANSACTION").do
104
+ client.execute("WaitFor Delay '00:00:01'").do
105
+ client.execute("WaitFor Delay '00:00:01'").do
106
+ client.execute("WaitFor Delay '00:00:01'").do
107
+ ensure
108
+ client.execute("COMMIT TRANSACTION").do
109
+ end
110
+ end
111
+
112
+ it 'must run this test to prove we account for dropped connections' do
113
+ skip
114
+ begin
115
+ client = new_connection :login_timeout => 2, :timeout => 2
116
+ assert_client_works(client)
117
+ STDOUT.puts "Disconnect network!"
118
+ sleep 10
119
+ STDOUT.puts "This should not get stuck past 6 seconds!"
120
+ action = lambda { client.execute('SELECT 1 as [one]').each }
121
+ assert_raise_tinytds_error(action) do |e|
122
+ assert_equal 20003, e.db_error_number
123
+ assert_equal 6, e.severity
124
+ assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
125
+ end
126
+ ensure
127
+ STDOUT.puts "Reconnect network!"
128
+ sleep 10
129
+ action = lambda { client.execute('SELECT 1 as [one]').each }
130
+ assert_raise_tinytds_error(action) do |e|
131
+ assert_equal 20047, e.db_error_number
132
+ assert_equal 1, e.severity
133
+ assert_match %r{dead or not enabled}i, e.message, 'ignore if non-english test run'
134
+ end
135
+ assert_new_connections_work
136
+ end
137
+ end
138
+
139
+ it 'raises TinyTds exception with wrong :username' do
140
+ options = connection_options :username => 'willnotwork'
141
+ action = lambda { new_connection(options) }
142
+ assert_raise_tinytds_error(action) do |e|
143
+ if sqlserver_azure?
144
+ assert_match %r{server name cannot be determined}i, e.message, 'ignore if non-english test run'
145
+ else
146
+ assert_equal sybase_ase? ? 4002 : 18456, e.db_error_number
147
+ assert_equal 14, e.severity
148
+ assert_match %r{login failed}i, e.message, 'ignore if non-english test run'
149
+ end
150
+ end
151
+ assert_new_connections_work
152
+ end
153
+
154
+ it 'fails miserably with unknown encoding option' do
155
+ options = connection_options :encoding => 'ISO-WTF'
156
+ action = lambda { new_connection(options) }
157
+ assert_raise_tinytds_error(action) do |e|
158
+ assert_equal 20002, e.db_error_number
159
+ assert_equal 9, e.severity
160
+ assert_match %r{connection failed}i, e.message, 'ignore if non-english test run'
161
+ end
162
+ assert_new_connections_work
163
+ end unless sybase_ase?
164
+
165
+ end
166
+
167
+
168
+
169
+ end
170
+
@@ -0,0 +1,732 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class ResultTest < TinyTds::TestCase
5
+
6
+ describe 'Basic query and result' do
7
+
8
+ before do
9
+ @@current_schema_loaded ||= load_current_schema
10
+ @client = new_connection
11
+ @query1 = 'SELECT 1 AS [one]'
12
+ end
13
+
14
+ it 'has included Enumerable' do
15
+ assert TinyTds::Result.ancestors.include?(Enumerable)
16
+ end
17
+
18
+ it 'responds to #each' do
19
+ result = @client.execute(@query1)
20
+ assert result.respond_to?(:each)
21
+ end
22
+
23
+ it 'returns 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
+ it 'returns 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
+ it 'allows 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
+ it 'returns 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
+ it 'returns 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
+ it 'returns 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
+ it 'returns 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
+ it 'allows sql concat + to work' do
79
+ rollback_transaction(@client) do
80
+ @client.execute("DELETE FROM [datatypes]").do
81
+ @client.execute("INSERT INTO [datatypes] ([char_10], [varchar_50]) VALUES ('1', '2')").do
82
+ result = @client.execute("SELECT TOP (1) [char_10] + 'test' + [varchar_50] AS [test] FROM [datatypes]").each.first['test']
83
+ result.must_equal "1 test2"
84
+ end
85
+ end
86
+
87
+ it 'must be able to turn :cache_rows option off' do
88
+ result = @client.execute(@query1)
89
+ local = []
90
+ result.each(:cache_rows => false) do |row|
91
+ local << row
92
+ end
93
+ assert local.first, 'should have iterated over each row'
94
+ assert_equal [], result.each, 'should not have been cached'
95
+ assert_equal ['one'], result.fields, 'should still cache field names'
96
+ end
97
+
98
+ it 'must be able to get the first result row only' do
99
+ load_current_schema
100
+ big_query = "SELECT [id] FROM [datatypes]"
101
+ one = @client.execute(big_query).each(:first => true)
102
+ many = @client.execute(big_query).each
103
+ assert many.size > 1
104
+ assert one.size == 1
105
+ end
106
+
107
+ it 'copes with no results when using first option' do
108
+ data = @client.execute("SELECT [id] FROM [datatypes] WHERE [id] = -1").each(:first => true)
109
+ assert_equal [], data
110
+ end
111
+
112
+ it 'must delete, insert and find data' do
113
+ rollback_transaction(@client) do
114
+ text = 'test insert and delete'
115
+ @client.execute("DELETE FROM [datatypes] WHERE [varchar_50] IS NOT NULL").do
116
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
117
+ row = @client.execute("SELECT [varchar_50] FROM [datatypes] WHERE [varchar_50] IS NOT NULL").each.first
118
+ assert row
119
+ assert_equal text, row['varchar_50']
120
+ end
121
+ end
122
+
123
+ it 'must insert and find unicode data' do
124
+ rollback_transaction(@client) do
125
+ text = '✓'
126
+ @client.execute("DELETE FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").do
127
+ @client.execute("INSERT INTO [datatypes] ([nvarchar_50]) VALUES (N'#{text}')").do
128
+ row = @client.execute("SELECT [nvarchar_50] FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").each.first
129
+ assert_equal text, row['nvarchar_50']
130
+ end
131
+ end
132
+
133
+ it 'must delete and update with affected rows support and insert with identity support in native sql' do
134
+ rollback_transaction(@client) do
135
+ text = 'test affected rows sql'
136
+ @client.execute("DELETE FROM [datatypes]").do
137
+ afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
138
+ assert_instance_of Fixnum, afrows
139
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
140
+ pk1 = @client.execute(@client.identity_sql).each.first['Ident']
141
+ assert_instance_of Fixnum, pk1, 'we it be able to CAST to bigint'
142
+ @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
143
+ afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
144
+ assert_equal 1, afrows
145
+ end
146
+ end
147
+
148
+ it 'has a #do method that cancels result rows and returns affected rows natively' do
149
+ rollback_transaction(@client) do
150
+ text = 'test affected rows native'
151
+ count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
152
+ deleted_rows = @client.execute("DELETE FROM [datatypes]").do
153
+ assert_equal count, deleted_rows, 'should have deleted rows equal to count'
154
+ inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
155
+ assert_equal 1, inserted_rows, 'should have inserted row for one above'
156
+ updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
157
+ assert_equal 1, updated_rows, 'should have updated row for one above' unless sqlserver_2000? # Will report -1
158
+ end
159
+ end
160
+
161
+ it 'allows native affected rows using #do to work under transaction' do
162
+ rollback_transaction(@client) do
163
+ text = 'test affected rows native in transaction'
164
+ @client.execute("BEGIN TRANSACTION").do
165
+ @client.execute("DELETE FROM [datatypes]").do
166
+ inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
167
+ assert_equal 1, inserted_rows, 'should have inserted row for one above'
168
+ updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
169
+ assert_equal 1, updated_rows, 'should have updated row for one above' unless sqlserver_2000? # Will report -1
170
+ end
171
+ end
172
+
173
+ it 'has an #insert method that cancels result rows and returns IDENTITY natively' do
174
+ rollback_transaction(@client) do
175
+ text = 'test scope identity rows native'
176
+ @client.execute("DELETE FROM [datatypes] WHERE [varchar_50] = '#{text}'").do
177
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
178
+ sql_identity = @client.execute(@client.identity_sql).each.first['Ident']
179
+ native_identity = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").insert
180
+ assert_equal sql_identity+1, native_identity
181
+ end
182
+ end
183
+
184
+ it 'returns bigint for #insert when needed' do
185
+ return if sqlserver_azure? # We can not alter clustered index like this test does.
186
+ return if sybase_ase? # On Sybase, sp_helpindex cannot be used inside a transaction since it does a
187
+ # 'CREATE TABLE' command is not allowed within a multi-statement transaction
188
+ # and and sp_helpindex creates a temporary table #spindtab.
189
+ rollback_transaction(@client) do
190
+ seed = 9223372036854775805
191
+ @client.execute("DELETE FROM [datatypes]").do
192
+ id_constraint_name = @client.execute("EXEC sp_helpindex [datatypes]").detect{ |row| row['index_keys'] == 'id' }['index_name']
193
+ @client.execute("ALTER TABLE [datatypes] DROP CONSTRAINT [#{id_constraint_name}]").do
194
+ @client.execute("ALTER TABLE [datatypes] DROP COLUMN [id]").do
195
+ @client.execute("ALTER TABLE [datatypes] ADD [id] [bigint] NOT NULL IDENTITY(1,1) PRIMARY KEY").do
196
+ @client.execute("DBCC CHECKIDENT ('datatypes', RESEED, #{seed})").do
197
+ identity = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('something')").insert
198
+ assert_equal seed, identity
199
+ end
200
+ end
201
+
202
+ it 'must be able to begin/commit transactions with raw sql' do
203
+ rollback_transaction(@client) do
204
+ @client.execute("BEGIN TRANSACTION").do
205
+ @client.execute("DELETE FROM [datatypes]").do
206
+ @client.execute("COMMIT TRANSACTION").do
207
+ count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
208
+ assert_equal 0, count
209
+ end
210
+ end
211
+
212
+ it 'must be able to begin/rollback transactions with raw sql' do
213
+ load_current_schema
214
+ @client.execute("BEGIN TRANSACTION").do
215
+ @client.execute("DELETE FROM [datatypes]").do
216
+ @client.execute("ROLLBACK TRANSACTION").do
217
+ count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
218
+ 0.wont_equal count
219
+ end
220
+
221
+ it 'has a #fields accessor with logic default and valid outcome' do
222
+ result = @client.execute(@query1)
223
+ result.fields.must_equal ['one']
224
+ result.each
225
+ result.fields.must_equal ['one']
226
+ end
227
+
228
+ it 'always returns an array for fields for all sql' do
229
+ result = @client.execute("USE [tinytdstest]")
230
+ result.fields.must_equal []
231
+ result.do
232
+ result.fields.must_equal []
233
+ end
234
+
235
+ it 'returns fields even when no results are found' do
236
+ no_results_query = "SELECT [id], [varchar_50] FROM [datatypes] WHERE [varchar_50] = 'NOTFOUND'"
237
+ # Fields before each.
238
+ result = @client.execute(no_results_query)
239
+ result.fields.must_equal ['id','varchar_50']
240
+ result.each
241
+ result.fields.must_equal ['id','varchar_50']
242
+ # Each then fields
243
+ result = @client.execute(no_results_query)
244
+ result.each
245
+ result.fields.must_equal ['id','varchar_50']
246
+ end
247
+
248
+ it 'allows the result to be canceled before reading' do
249
+ result = @client.execute(@query1)
250
+ result.cancel
251
+ @client.execute(@query1).each
252
+ end
253
+
254
+ it 'works in tandem with the client when needing to find out if client has sql sent and result is canceled or not' do
255
+ # Default state.
256
+ @client = TinyTds::Client.new(connection_options)
257
+ @client.sqlsent?.must_equal false
258
+ @client.canceled?.must_equal false
259
+ # With active result before and after cancel.
260
+ result = @client.execute(@query1)
261
+ @client.sqlsent?.must_equal true
262
+ @client.canceled?.must_equal false
263
+ result.cancel
264
+ @client.sqlsent?.must_equal false
265
+ @client.canceled?.must_equal true
266
+ assert result.cancel, 'must be safe to call again'
267
+ # With each and no block.
268
+ @client.execute(@query1).each
269
+ @client.sqlsent?.must_equal false
270
+ @client.canceled?.must_equal false
271
+ # With each and block.
272
+ @client.execute(@query1).each do |row|
273
+ @client.sqlsent?.must_equal true, 'when iterating over each row in a block'
274
+ @client.canceled?.must_equal false
275
+ end
276
+ @client.sqlsent?.must_equal false
277
+ @client.canceled?.must_equal false
278
+ # With each and block canceled half way thru.
279
+ count = @client.execute("SELECT COUNT([id]) AS [count] FROM [datatypes]").each[0]['count']
280
+ assert count > 10, 'since we want to cancel early for test'
281
+ result = @client.execute("SELECT [id] FROM [datatypes]")
282
+ index = 0
283
+ result.each do |row|
284
+ break if index > 10
285
+ index += 1
286
+ end
287
+ @client.sqlsent?.must_equal true
288
+ @client.canceled?.must_equal false
289
+ result.cancel
290
+ @client.sqlsent?.must_equal false
291
+ @client.canceled?.must_equal true
292
+ # With do method.
293
+ @client.execute(@query1).do
294
+ @client.sqlsent?.must_equal false
295
+ @client.canceled?.must_equal true
296
+ # With insert method.
297
+ rollback_transaction(@client) do
298
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('test')").insert
299
+ @client.sqlsent?.must_equal false
300
+ @client.canceled?.must_equal true
301
+ end
302
+ # With first
303
+ @client.execute("SELECT [id] FROM [datatypes]").each(:first => true)
304
+ @client.sqlsent?.must_equal false
305
+ @client.canceled?.must_equal true
306
+ end
307
+
308
+ it 'use same string object for hash keys' do
309
+ data = @client.execute("SELECT [id], [bigint] FROM [datatypes]").each
310
+ assert_equal data.first.keys.map{ |r| r.object_id }, data.last.keys.map{ |r| r.object_id }
311
+ end
312
+
313
+ it 'has properly encoded column names with symbol keys' do
314
+ col_name = "öäüß"
315
+ @client.execute("DROP TABLE [test_encoding]").do rescue nil
316
+ @client.execute("CREATE TABLE [dbo].[test_encoding] ( [#{col_name}] [nvarchar](10) NOT NULL )").do
317
+ @client.execute("INSERT INTO [test_encoding] ([#{col_name}]) VALUES (N'#{col_name}')").do
318
+ result = @client.execute("SELECT [#{col_name}] FROM [test_encoding]")
319
+ row = result.each(:as => :hash, :symbolize_keys => true).first
320
+ assert_instance_of Symbol, result.fields.first
321
+ assert_equal col_name.to_sym, result.fields.first
322
+ assert_instance_of Symbol, row.keys.first
323
+ assert_equal col_name.to_sym, row.keys.first
324
+ end unless sqlserver_azure?
325
+
326
+ it 'allows #return_code to work with stored procedures and reset per sql batch' do
327
+ assert_nil @client.return_code
328
+ result = @client.execute("EXEC tinytds_TestReturnCodes")
329
+ assert_equal [{"one"=>1}], result.each
330
+ assert_equal 420, @client.return_code
331
+ assert_equal 420, result.return_code
332
+ result = @client.execute('SELECT 1 as [one]')
333
+ result.each
334
+ assert_nil @client.return_code
335
+ assert_nil result.return_code
336
+ end
337
+
338
+ describe 'with multiple result sets' do
339
+
340
+ before do
341
+ @empty_select = "SELECT 1 AS [rs1] WHERE 1 = 0"
342
+ @double_select = "SELECT 1 AS [rs1]
343
+ SELECT 2 AS [rs2]"
344
+ @triple_select_1st_empty = "SELECT 1 AS [rs1] WHERE 1 = 0
345
+ SELECT 2 AS [rs2]
346
+ SELECT 3 AS [rs3]"
347
+ @triple_select_2nd_empty = "SELECT 1 AS [rs1]
348
+ SELECT 2 AS [rs2] WHERE 1 = 0
349
+ SELECT 3 AS [rs3]"
350
+ @triple_select_3rd_empty = "SELECT 1 AS [rs1]
351
+ SELECT 2 AS [rs2]
352
+ SELECT 3 AS [rs3] WHERE 1 = 0"
353
+ end
354
+
355
+ it 'handles a command buffer with double selects' do
356
+ result = @client.execute(@double_select)
357
+ result_sets = result.each
358
+ assert_equal 2, result_sets.size
359
+ assert_equal [{'rs1' => 1}], result_sets.first
360
+ assert_equal [{'rs2' => 2}], result_sets.last
361
+ assert_equal [['rs1'], ['rs2']], result.fields
362
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
363
+ # As array
364
+ result = @client.execute(@double_select)
365
+ result_sets = result.each(:as => :array)
366
+ assert_equal 2, result_sets.size
367
+ assert_equal [[1]], result_sets.first
368
+ assert_equal [[2]], result_sets.last
369
+ assert_equal [['rs1'], ['rs2']], result.fields
370
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
371
+ end
372
+
373
+ it 'yields each row for each result set' do
374
+ data = []
375
+ result_sets = @client.execute(@double_select).each { |row| data << row }
376
+ assert_equal data.first, result_sets.first[0]
377
+ assert_equal data.last, result_sets.last[0]
378
+ end
379
+
380
+ it 'works from a stored procedure' do
381
+ if sqlserver?
382
+ results1, results2 = @client.execute("EXEC sp_helpconstraint '[datatypes]'").each
383
+ assert_equal [{"Object Name"=>"[datatypes]"}], results1
384
+ constraint_info = results2.first
385
+ assert constraint_info.key?("constraint_keys")
386
+ assert constraint_info.key?("constraint_type")
387
+ assert constraint_info.key?("constraint_name")
388
+ elsif sybase_ase?
389
+ results1, results2 = @client.execute("EXEC sp_helpconstraint 'datatypes'").each
390
+ assert results1['name'] =~ /^datatypes_bit/
391
+ assert results1['defintion'] == 'DEFAULT 0'
392
+ assert results2['name'] =~ /^datatypes_id/
393
+ assert results2['defintion'] =~ /^PRIMARY KEY/
394
+ end
395
+ end
396
+
397
+ describe 'using :empty_sets TRUE' do
398
+
399
+ before do
400
+ @old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
401
+ TinyTds::Client.default_query_options[:empty_sets] = true
402
+ @client = new_connection
403
+ end
404
+
405
+ after do
406
+ TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
407
+ end
408
+
409
+ it 'handles a basic empty result set' do
410
+ result = @client.execute(@empty_select)
411
+ assert_equal [], result.each
412
+ assert_equal ['rs1'], result.fields
413
+ end
414
+
415
+ it 'includes empty result sets by default - using 1st empty buffer' do
416
+ result = @client.execute(@triple_select_1st_empty)
417
+ result_sets = result.each
418
+ assert_equal 3, result_sets.size
419
+ assert_equal [], result_sets[0]
420
+ assert_equal [{'rs2' => 2}], result_sets[1]
421
+ assert_equal [{'rs3' => 3}], result_sets[2]
422
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
423
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
424
+ # As array
425
+ result = @client.execute(@triple_select_1st_empty)
426
+ result_sets = result.each(:as => :array)
427
+ assert_equal 3, result_sets.size
428
+ assert_equal [], result_sets[0]
429
+ assert_equal [[2]], result_sets[1]
430
+ assert_equal [[3]], result_sets[2]
431
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
432
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
433
+ end
434
+
435
+ it 'includes empty result sets by default - using 2nd empty buffer' do
436
+ result = @client.execute(@triple_select_2nd_empty)
437
+ result_sets = result.each
438
+ assert_equal 3, result_sets.size
439
+ assert_equal [{'rs1' => 1}], result_sets[0]
440
+ assert_equal [], result_sets[1]
441
+ assert_equal [{'rs3' => 3}], result_sets[2]
442
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
443
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
444
+ # As array
445
+ result = @client.execute(@triple_select_2nd_empty)
446
+ result_sets = result.each(:as => :array)
447
+ assert_equal 3, result_sets.size
448
+ assert_equal [[1]], result_sets[0]
449
+ assert_equal [], result_sets[1]
450
+ assert_equal [[3]], result_sets[2]
451
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
452
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
453
+ end
454
+
455
+ it 'includes empty result sets by default - using 3rd empty buffer' do
456
+ result = @client.execute(@triple_select_3rd_empty)
457
+ result_sets = result.each
458
+ assert_equal 3, result_sets.size
459
+ assert_equal [{'rs1' => 1}], result_sets[0]
460
+ assert_equal [{'rs2' => 2}], result_sets[1]
461
+ assert_equal [], result_sets[2]
462
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
463
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
464
+ # As array
465
+ result = @client.execute(@triple_select_3rd_empty)
466
+ result_sets = result.each(:as => :array)
467
+ assert_equal 3, result_sets.size
468
+ assert_equal [[1]], result_sets[0]
469
+ assert_equal [[2]], result_sets[1]
470
+ assert_equal [], result_sets[2]
471
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
472
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
473
+ end
474
+
475
+ end
476
+
477
+ describe 'using :empty_sets FALSE' do
478
+
479
+ before do
480
+ @old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
481
+ TinyTds::Client.default_query_options[:empty_sets] = false
482
+ @client = new_connection
483
+ end
484
+
485
+ after do
486
+ TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
487
+ end
488
+
489
+ it 'handles a basic empty result set' do
490
+ result = @client.execute(@empty_select)
491
+ assert_equal [], result.each
492
+ assert_equal ['rs1'], result.fields
493
+ end
494
+
495
+ it 'must not include empty result sets by default - using 1st empty buffer' do
496
+ result = @client.execute(@triple_select_1st_empty)
497
+ result_sets = result.each
498
+ assert_equal 2, result_sets.size
499
+ assert_equal [{'rs2' => 2}], result_sets[0]
500
+ assert_equal [{'rs3' => 3}], result_sets[1]
501
+ assert_equal [['rs2'], ['rs3']], result.fields
502
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
503
+ # As array
504
+ result = @client.execute(@triple_select_1st_empty)
505
+ result_sets = result.each(:as => :array)
506
+ assert_equal 2, result_sets.size
507
+ assert_equal [[2]], result_sets[0]
508
+ assert_equal [[3]], result_sets[1]
509
+ assert_equal [['rs2'], ['rs3']], result.fields
510
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
511
+ end
512
+
513
+ it 'must not include empty result sets by default - using 2nd empty buffer' do
514
+ result = @client.execute(@triple_select_2nd_empty)
515
+ result_sets = result.each
516
+ assert_equal 2, result_sets.size
517
+ assert_equal [{'rs1' => 1}], result_sets[0]
518
+ assert_equal [{'rs3' => 3}], result_sets[1]
519
+ assert_equal [['rs1'], ['rs3']], result.fields
520
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
521
+ # As array
522
+ result = @client.execute(@triple_select_2nd_empty)
523
+ result_sets = result.each(:as => :array)
524
+ assert_equal 2, result_sets.size
525
+ assert_equal [[1]], result_sets[0]
526
+ assert_equal [[3]], result_sets[1]
527
+ assert_equal [['rs1'], ['rs3']], result.fields
528
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
529
+ end
530
+
531
+ it 'must not include empty result sets by default - using 3rd empty buffer' do
532
+ result = @client.execute(@triple_select_3rd_empty)
533
+ result_sets = result.each
534
+ assert_equal 2, result_sets.size
535
+ assert_equal [{'rs1' => 1}], result_sets[0]
536
+ assert_equal [{'rs2' => 2}], result_sets[1]
537
+ assert_equal [['rs1'], ['rs2']], result.fields
538
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
539
+ # As array
540
+ result = @client.execute(@triple_select_3rd_empty)
541
+ result_sets = result.each(:as => :array)
542
+ assert_equal 2, result_sets.size
543
+ assert_equal [[1]], result_sets[0]
544
+ assert_equal [[2]], result_sets[1]
545
+ assert_equal [['rs1'], ['rs2']], result.fields
546
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
547
+ end
548
+
549
+ end
550
+
551
+ end
552
+
553
+ describe 'Complex query with multiple results sets but no actual results' do
554
+ it 'must not cancel the query until complete' do
555
+ @client.execute("
556
+ BACKUP DATABASE tinytdstest
557
+ TO DISK = 'C:\\Users\\Public\\tinytdstest.bak'
558
+ ").do
559
+ end
560
+
561
+ after do
562
+ begin
563
+ File.delete 'C:\\Users\\Public\\tinytdstest.bak'
564
+ rescue
565
+ end
566
+ end
567
+ end
568
+
569
+ describe 'when casting to native ruby values' do
570
+
571
+ it 'returns fixnum for 1' do
572
+ value = @client.execute('SELECT 1 AS [fixnum]').each.first['fixnum']
573
+ assert_equal 1, value
574
+ end
575
+
576
+ it 'returns nil for NULL' do
577
+ value = @client.execute('SELECT NULL AS [null]').each.first['null']
578
+ assert_equal nil, value
579
+ end
580
+
581
+ end
582
+
583
+ describe 'with data type' do
584
+
585
+ describe 'char max' do
586
+
587
+ before do
588
+ @big_text = 'x' * 2_000_000
589
+ @old_textsize = @client.execute("SELECT @@TEXTSIZE AS [textsize]").each.first['textsize'].inspect
590
+ @client.execute("SET TEXTSIZE #{(@big_text.length*2)+1}").do
591
+ end
592
+
593
+ it 'must insert and select large varchar_max' do
594
+ insert_and_select_datatype :varchar_max
595
+ end
596
+
597
+ it 'must insert and select large nvarchar_max' do
598
+ insert_and_select_datatype :nvarchar_max
599
+ end
600
+
601
+ end unless sqlserver_2000? || sybase_ase?
602
+
603
+ end
604
+
605
+ describe 'when shit happens' do
606
+
607
+ it 'copes with nil or empty buffer' do
608
+ assert_raises(TypeError) { @client.execute(nil) }
609
+ assert_equal [], @client.execute('').each
610
+ end
611
+
612
+ if sqlserver?
613
+
614
+ it 'must not raise an error when severity is 10 or less' do
615
+ (1..10).to_a.each do |severity|
616
+ @client.execute("RAISERROR(N'Test #{severity} severity', #{severity}, 1)").do
617
+ end
618
+ end
619
+
620
+ it 'raises an error when severity is greater than 10' do
621
+ action = lambda { @client.execute("RAISERROR(N'Test 11 severity', 11, 1)").do }
622
+ assert_raise_tinytds_error(action) do |e|
623
+ assert_equal "Test 11 severity", e.message
624
+ assert_equal 11, e.severity
625
+ assert_equal 50000, e.db_error_number
626
+ end
627
+ end
628
+
629
+ else
630
+
631
+ it 'raises an error' do
632
+ action = lambda { @client.execute("RAISERROR 50000 N'Hello World'").do }
633
+ assert_raise_tinytds_error(action) do |e|
634
+ assert_equal "Hello World", e.message
635
+ assert_equal 16, e.severity # predefined on ASE
636
+ assert_equal 50000, e.db_error_number
637
+ end
638
+ assert_followup_query
639
+ end
640
+
641
+ end
642
+
643
+ it 'throws an error when you execute another query with other results pending' do
644
+ result1 = @client.execute(@query1)
645
+ action = lambda { @client.execute(@query1) }
646
+ assert_raise_tinytds_error(action) do |e|
647
+ assert_match %r|with results pending|i, e.message
648
+ assert_equal 7, e.severity
649
+ assert_equal 20019, e.db_error_number
650
+ end
651
+ end
652
+
653
+ it 'must error gracefully with bad table name' do
654
+ action = lambda { @client.execute('SELECT * FROM [foobar]').each }
655
+ assert_raise_tinytds_error(action) do |e|
656
+ pattern = sybase_ase? ? /foobar not found/ : %r|invalid object name.*foobar|i
657
+ assert_match pattern, e.message
658
+ assert_equal 16, e.severity
659
+ assert_equal 208, e.db_error_number
660
+ end
661
+ assert_followup_query
662
+ end
663
+
664
+ it 'must error gracefully with incorrect syntax' do
665
+ action = lambda { @client.execute('this will not work').each }
666
+ assert_raise_tinytds_error(action) do |e|
667
+ assert_match %r|incorrect syntax|i, e.message
668
+ assert_equal 15, e.severity
669
+ assert_equal 156, e.db_error_number
670
+ end
671
+ assert_followup_query
672
+ end
673
+
674
+ it 'must not error at all from reading non-convertable charcters and just use ? marks' do
675
+ @client = new_connection :encoding => 'ASCII'
676
+ @client.charset.must_equal 'ASCII'
677
+ find_value(202, :nvarchar_50).must_equal 'test nvarchar_50 ??'
678
+ end
679
+
680
+ it 'must error gracefully from writing non-convertable characters' do
681
+ @client = new_connection :encoding => 'ASCII'
682
+ @client.charset.must_equal 'ASCII'
683
+ rollback_transaction(@client) do
684
+ text = 'Test ✓'
685
+ @client.execute("DELETE FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").do
686
+ action = lambda { @client.execute("INSERT INTO [datatypes] ([nvarchar_50]) VALUES ('#{text}')").do }
687
+ assert_raise_tinytds_error(action) do |e|
688
+ e.message.must_match %r{Error converting characters into server's character set}i
689
+ e.severity.must_equal 4
690
+ e.db_error_number.must_equal 2402
691
+ end
692
+ assert_followup_query
693
+ end
694
+ end
695
+
696
+ it 'errors gracefully with incorrect syntax in sp_executesql' do
697
+ if @client.freetds_091_or_higer?
698
+ action = lambda { @client.execute("EXEC sp_executesql N'this will not work'").each }
699
+ assert_raise_tinytds_error(action) do |e|
700
+ assert_match %r|incorrect syntax|i, e.message
701
+ assert_equal 15, e.severity
702
+ assert_equal 156, e.db_error_number
703
+ end
704
+ assert_followup_query
705
+ else
706
+ skip 'FreeTDS 0.91 and higher can only pass this test.'
707
+ end
708
+ end unless sybase_ase?
709
+
710
+ end
711
+
712
+ end
713
+
714
+
715
+ protected
716
+
717
+ def assert_followup_query
718
+ result = @client.execute(@query1)
719
+ assert_equal 1, result.each.first['one']
720
+ end
721
+
722
+ def insert_and_select_datatype(datatype)
723
+ rollback_transaction(@client) do
724
+ @client.execute("DELETE FROM [datatypes] WHERE [#{datatype}] IS NOT NULL").do
725
+ id = @client.execute("INSERT INTO [datatypes] ([#{datatype}]) VALUES (N'#{@big_text}')").insert
726
+ found_text = find_value id, datatype
727
+ flunk "Large #{datatype} data with a length of #{@big_text.length} did not match found text with length of #{found_text.length}" unless @big_text == found_text
728
+ end
729
+ end
730
+
731
+ end
732
+