tiny_tds 0.6.2-x64-mingw32

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,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
+