tiny_tds 2.1.6-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +407 -0
  3. data/.codeclimate.yml +20 -0
  4. data/.gitattributes +1 -0
  5. data/.gitignore +22 -0
  6. data/.rubocop.yml +31 -0
  7. data/CHANGELOG.md +280 -0
  8. data/CODE_OF_CONDUCT.md +31 -0
  9. data/Gemfile +2 -0
  10. data/ISSUE_TEMPLATE.md +38 -0
  11. data/MIT-LICENSE +23 -0
  12. data/README.md +504 -0
  13. data/Rakefile +62 -0
  14. data/VERSION +1 -0
  15. data/bin/defncopy-ttds +3 -0
  16. data/bin/tsql-ttds +3 -0
  17. data/docker-compose.yml +34 -0
  18. data/exe/.keep +0 -0
  19. data/ext/tiny_tds/client.c +499 -0
  20. data/ext/tiny_tds/client.h +53 -0
  21. data/ext/tiny_tds/extconf.rb +92 -0
  22. data/ext/tiny_tds/extconsts.rb +15 -0
  23. data/ext/tiny_tds/result.c +634 -0
  24. data/ext/tiny_tds/result.h +32 -0
  25. data/ext/tiny_tds/tiny_tds_ext.c +12 -0
  26. data/ext/tiny_tds/tiny_tds_ext.h +17 -0
  27. data/lib/tiny_tds/3.1/tiny_tds.so +0 -0
  28. data/lib/tiny_tds/3.2/tiny_tds.so +0 -0
  29. data/lib/tiny_tds/bin.rb +104 -0
  30. data/lib/tiny_tds/client.rb +136 -0
  31. data/lib/tiny_tds/error.rb +14 -0
  32. data/lib/tiny_tds/gem.rb +27 -0
  33. data/lib/tiny_tds/result.rb +7 -0
  34. data/lib/tiny_tds/version.rb +3 -0
  35. data/lib/tiny_tds.rb +61 -0
  36. data/patches/freetds/1.00.27/0001-mingw_missing_inet_pton.diff +34 -0
  37. data/patches/freetds/1.00.27/0002-Don-t-use-MSYS2-file-libws2_32.diff +28 -0
  38. data/patches/libiconv/1.14/1-avoid-gets-error.patch +17 -0
  39. data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/bsqldb.exe +0 -0
  40. data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/datacopy.exe +0 -0
  41. data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/defncopy.exe +0 -0
  42. data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/freebcp.exe +0 -0
  43. data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/libct-4.dll +0 -0
  44. data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/libsybdb-5.dll +0 -0
  45. data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/osql +388 -0
  46. data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/tdspool.exe +0 -0
  47. data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/tsql.exe +0 -0
  48. data/ports/x64-mingw-ucrt/freetds/1.1.24/lib/libct.dll.a +0 -0
  49. data/ports/x64-mingw-ucrt/freetds/1.1.24/lib/libct.la +41 -0
  50. data/ports/x64-mingw-ucrt/freetds/1.1.24/lib/libsybdb.dll.a +0 -0
  51. data/ports/x64-mingw-ucrt/freetds/1.1.24/lib/libsybdb.la +41 -0
  52. data/ports/x64-mingw-ucrt/libiconv/1.15/bin/iconv.exe +0 -0
  53. data/ports/x64-mingw-ucrt/libiconv/1.15/bin/libcharset-1.dll +0 -0
  54. data/ports/x64-mingw-ucrt/libiconv/1.15/bin/libiconv-2.dll +0 -0
  55. data/ports/x64-mingw-ucrt/libiconv/1.15/lib/charset.alias +4 -0
  56. data/ports/x64-mingw-ucrt/libiconv/1.15/lib/libcharset.dll.a +0 -0
  57. data/ports/x64-mingw-ucrt/libiconv/1.15/lib/libcharset.la +41 -0
  58. data/ports/x64-mingw-ucrt/libiconv/1.15/lib/libiconv.dll.a +0 -0
  59. data/ports/x64-mingw-ucrt/libiconv/1.15/lib/libiconv.la +41 -0
  60. data/ports/x64-mingw-ucrt/openssl/1.1.1s/bin/c_rehash +251 -0
  61. data/ports/x64-mingw-ucrt/openssl/1.1.1s/bin/libcrypto-1_1-x64.dll +0 -0
  62. data/ports/x64-mingw-ucrt/openssl/1.1.1s/bin/libssl-1_1-x64.dll +0 -0
  63. data/ports/x64-mingw-ucrt/openssl/1.1.1s/bin/openssl.exe +0 -0
  64. data/ports/x64-mingw-ucrt/openssl/1.1.1s/lib/libcrypto.a +0 -0
  65. data/ports/x64-mingw-ucrt/openssl/1.1.1s/lib/libcrypto.dll.a +0 -0
  66. data/ports/x64-mingw-ucrt/openssl/1.1.1s/lib/libssl.a +0 -0
  67. data/ports/x64-mingw-ucrt/openssl/1.1.1s/lib/libssl.dll.a +0 -0
  68. data/setup_cimgruby_dev.sh +25 -0
  69. data/start_dev.sh +21 -0
  70. data/tasks/native_gem.rake +23 -0
  71. data/tasks/package.rake +8 -0
  72. data/tasks/ports/freetds.rb +37 -0
  73. data/tasks/ports/libiconv.rb +26 -0
  74. data/tasks/ports/openssl.rb +62 -0
  75. data/tasks/ports/recipe.rb +64 -0
  76. data/tasks/ports.rake +108 -0
  77. data/tasks/test.rake +9 -0
  78. data/test/benchmark/query.rb +77 -0
  79. data/test/benchmark/query_odbc.rb +106 -0
  80. data/test/benchmark/query_tinytds.rb +126 -0
  81. data/test/bin/install-freetds.sh +20 -0
  82. data/test/bin/install-mssql.ps1 +31 -0
  83. data/test/bin/install-mssqltools.sh +9 -0
  84. data/test/bin/install-openssl.sh +18 -0
  85. data/test/bin/setup_tinytds_db.sh +7 -0
  86. data/test/bin/setup_volume_permissions.sh +10 -0
  87. data/test/client_test.rb +275 -0
  88. data/test/gem_test.rb +177 -0
  89. data/test/result_test.rb +814 -0
  90. data/test/schema/1px.gif +0 -0
  91. data/test/schema/sqlserver_2000.sql +140 -0
  92. data/test/schema/sqlserver_2005.sql +140 -0
  93. data/test/schema/sqlserver_2008.sql +140 -0
  94. data/test/schema/sqlserver_2014.sql +140 -0
  95. data/test/schema/sqlserver_2016.sql +140 -0
  96. data/test/schema/sqlserver_azure.sql +140 -0
  97. data/test/schema/sybase_ase.sql +138 -0
  98. data/test/schema_test.rb +443 -0
  99. data/test/sql/db-create.sql +18 -0
  100. data/test/sql/db-login.sql +38 -0
  101. data/test/test_helper.rb +280 -0
  102. data/test/thread_test.rb +98 -0
  103. data/tiny_tds.gemspec +31 -0
  104. metadata +267 -0
@@ -0,0 +1,814 @@
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
+ _(['Fixnum', 'Integer']).must_include afrows.class.name
139
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
140
+ pk1 = @client.execute(@client.identity_sql).each.first['Ident']
141
+ _(['Fixnum', 'Integer']).must_include pk1.class.name, '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
+ _(count).wont_equal 0
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] ( [id] int NOT NULL IDENTITY(1,1) PRIMARY KEY, [#{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
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
+ it 'with LOGINPROPERTY function' do
339
+ v = @client.execute("SELECT LOGINPROPERTY('sa', 'IsLocked') as v").first['v']
340
+ _(v).must_equal 0
341
+ end
342
+
343
+ describe 'with multiple result sets' do
344
+
345
+ before do
346
+ @empty_select = "SELECT 1 AS [rs1] WHERE 1 = 0"
347
+ @double_select = "SELECT 1 AS [rs1]
348
+ SELECT 2 AS [rs2]"
349
+ @triple_select_1st_empty = "SELECT 1 AS [rs1] WHERE 1 = 0
350
+ SELECT 2 AS [rs2]
351
+ SELECT 3 AS [rs3]"
352
+ @triple_select_2nd_empty = "SELECT 1 AS [rs1]
353
+ SELECT 2 AS [rs2] WHERE 1 = 0
354
+ SELECT 3 AS [rs3]"
355
+ @triple_select_3rd_empty = "SELECT 1 AS [rs1]
356
+ SELECT 2 AS [rs2]
357
+ SELECT 3 AS [rs3] WHERE 1 = 0"
358
+ end
359
+
360
+ it 'handles a command buffer with double selects' do
361
+ result = @client.execute(@double_select)
362
+ result_sets = result.each
363
+ assert_equal 2, result_sets.size
364
+ assert_equal [{'rs1' => 1}], result_sets.first
365
+ assert_equal [{'rs2' => 2}], result_sets.last
366
+ assert_equal [['rs1'], ['rs2']], result.fields
367
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
368
+ # As array
369
+ result = @client.execute(@double_select)
370
+ result_sets = result.each(:as => :array)
371
+ assert_equal 2, result_sets.size
372
+ assert_equal [[1]], result_sets.first
373
+ assert_equal [[2]], result_sets.last
374
+ assert_equal [['rs1'], ['rs2']], result.fields
375
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
376
+ end
377
+
378
+ it 'yields each row for each result set' do
379
+ data = []
380
+ result_sets = @client.execute(@double_select).each { |row| data << row }
381
+ assert_equal data.first, result_sets.first[0]
382
+ assert_equal data.last, result_sets.last[0]
383
+ end
384
+
385
+ it 'works from a stored procedure' do
386
+ if sqlserver?
387
+ results1, results2 = @client.execute("EXEC sp_helpconstraint '[datatypes]'").each
388
+ assert_equal [{"Object Name"=>"[datatypes]"}], results1
389
+ constraint_info = results2.first
390
+ assert constraint_info.key?("constraint_keys")
391
+ assert constraint_info.key?("constraint_type")
392
+ assert constraint_info.key?("constraint_name")
393
+ elsif sybase_ase?
394
+ results1, results2 = @client.execute("EXEC sp_helpconstraint 'datatypes'").each
395
+ assert results1['name'] =~ /^datatypes_bit/
396
+ assert results1['defintion'] == 'DEFAULT 0'
397
+ assert results2['name'] =~ /^datatypes_id/
398
+ assert results2['defintion'] =~ /^PRIMARY KEY/
399
+ end
400
+ end
401
+
402
+ describe 'using :empty_sets TRUE' do
403
+
404
+ before do
405
+ close_client
406
+ @old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
407
+ TinyTds::Client.default_query_options[:empty_sets] = true
408
+ @client = new_connection
409
+ end
410
+
411
+ after do
412
+ TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
413
+ end
414
+
415
+ it 'handles a basic empty result set' do
416
+ result = @client.execute(@empty_select)
417
+ assert_equal [], result.each
418
+ assert_equal ['rs1'], result.fields
419
+ end
420
+
421
+ it 'includes empty result sets by default - using 1st empty buffer' do
422
+ result = @client.execute(@triple_select_1st_empty)
423
+ result_sets = result.each
424
+ assert_equal 3, result_sets.size
425
+ assert_equal [], result_sets[0]
426
+ assert_equal [{'rs2' => 2}], result_sets[1]
427
+ assert_equal [{'rs3' => 3}], result_sets[2]
428
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
429
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
430
+ # As array
431
+ result = @client.execute(@triple_select_1st_empty)
432
+ result_sets = result.each(:as => :array)
433
+ assert_equal 3, result_sets.size
434
+ assert_equal [], result_sets[0]
435
+ assert_equal [[2]], result_sets[1]
436
+ assert_equal [[3]], result_sets[2]
437
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
438
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
439
+ end
440
+
441
+ it 'includes empty result sets by default - using 2nd empty buffer' do
442
+ result = @client.execute(@triple_select_2nd_empty)
443
+ result_sets = result.each
444
+ assert_equal 3, result_sets.size
445
+ assert_equal [{'rs1' => 1}], result_sets[0]
446
+ assert_equal [], result_sets[1]
447
+ assert_equal [{'rs3' => 3}], result_sets[2]
448
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
449
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
450
+ # As array
451
+ result = @client.execute(@triple_select_2nd_empty)
452
+ result_sets = result.each(:as => :array)
453
+ assert_equal 3, result_sets.size
454
+ assert_equal [[1]], result_sets[0]
455
+ assert_equal [], result_sets[1]
456
+ assert_equal [[3]], result_sets[2]
457
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
458
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
459
+ end
460
+
461
+ it 'includes empty result sets by default - using 3rd empty buffer' do
462
+ result = @client.execute(@triple_select_3rd_empty)
463
+ result_sets = result.each
464
+ assert_equal 3, result_sets.size
465
+ assert_equal [{'rs1' => 1}], result_sets[0]
466
+ assert_equal [{'rs2' => 2}], result_sets[1]
467
+ assert_equal [], result_sets[2]
468
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
469
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
470
+ # As array
471
+ result = @client.execute(@triple_select_3rd_empty)
472
+ result_sets = result.each(:as => :array)
473
+ assert_equal 3, result_sets.size
474
+ assert_equal [[1]], result_sets[0]
475
+ assert_equal [[2]], result_sets[1]
476
+ assert_equal [], result_sets[2]
477
+ assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
478
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
479
+ end
480
+
481
+ end
482
+
483
+ describe 'using :empty_sets FALSE' do
484
+
485
+ before do
486
+ close_client
487
+ @old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
488
+ TinyTds::Client.default_query_options[:empty_sets] = false
489
+ @client = new_connection
490
+ end
491
+
492
+ after do
493
+ TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
494
+ end
495
+
496
+ it 'handles a basic empty result set' do
497
+ result = @client.execute(@empty_select)
498
+ assert_equal [], result.each
499
+ assert_equal ['rs1'], result.fields
500
+ end
501
+
502
+ it 'must not include empty result sets by default - using 1st empty buffer' do
503
+ result = @client.execute(@triple_select_1st_empty)
504
+ result_sets = result.each
505
+ assert_equal 2, result_sets.size
506
+ assert_equal [{'rs2' => 2}], result_sets[0]
507
+ assert_equal [{'rs3' => 3}], result_sets[1]
508
+ assert_equal [['rs2'], ['rs3']], result.fields
509
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
510
+ # As array
511
+ result = @client.execute(@triple_select_1st_empty)
512
+ result_sets = result.each(:as => :array)
513
+ assert_equal 2, result_sets.size
514
+ assert_equal [[2]], result_sets[0]
515
+ assert_equal [[3]], result_sets[1]
516
+ assert_equal [['rs2'], ['rs3']], result.fields
517
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
518
+ end
519
+
520
+ it 'must not include empty result sets by default - using 2nd empty buffer' do
521
+ result = @client.execute(@triple_select_2nd_empty)
522
+ result_sets = result.each
523
+ assert_equal 2, result_sets.size
524
+ assert_equal [{'rs1' => 1}], result_sets[0]
525
+ assert_equal [{'rs3' => 3}], result_sets[1]
526
+ assert_equal [['rs1'], ['rs3']], result.fields
527
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
528
+ # As array
529
+ result = @client.execute(@triple_select_2nd_empty)
530
+ result_sets = result.each(:as => :array)
531
+ assert_equal 2, result_sets.size
532
+ assert_equal [[1]], result_sets[0]
533
+ assert_equal [[3]], result_sets[1]
534
+ assert_equal [['rs1'], ['rs3']], result.fields
535
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
536
+ end
537
+
538
+ it 'must not include empty result sets by default - using 3rd empty buffer' do
539
+ result = @client.execute(@triple_select_3rd_empty)
540
+ result_sets = result.each
541
+ assert_equal 2, result_sets.size
542
+ assert_equal [{'rs1' => 1}], result_sets[0]
543
+ assert_equal [{'rs2' => 2}], result_sets[1]
544
+ assert_equal [['rs1'], ['rs2']], result.fields
545
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
546
+ # As array
547
+ result = @client.execute(@triple_select_3rd_empty)
548
+ result_sets = result.each(:as => :array)
549
+ assert_equal 2, result_sets.size
550
+ assert_equal [[1]], result_sets[0]
551
+ assert_equal [[2]], result_sets[1]
552
+ assert_equal [['rs1'], ['rs2']], result.fields
553
+ assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
554
+ end
555
+
556
+ end
557
+
558
+ end
559
+
560
+ describe 'Complex query with multiple results sets but no actual results' do
561
+
562
+ let(:backup_file) { 'C:\\Users\\Public\\tinytdstest.bak' }
563
+
564
+ after { File.delete(backup_file) if File.exist?(backup_file) }
565
+
566
+ it 'must not cancel the query until complete' do
567
+ @client.execute("BACKUP DATABASE tinytdstest TO DISK = '#{backup_file}'").do
568
+ end
569
+
570
+ end unless sqlserver_azure?
571
+
572
+ describe 'when casting to native ruby values' do
573
+
574
+ it 'returns fixnum for 1' do
575
+ value = @client.execute('SELECT 1 AS [fixnum]').each.first['fixnum']
576
+ assert_equal 1, value
577
+ end
578
+
579
+ it 'returns nil for NULL' do
580
+ value = @client.execute('SELECT NULL AS [null]').each.first['null']
581
+ assert_nil value
582
+ end
583
+
584
+ end
585
+
586
+ describe 'with data type' do
587
+
588
+ describe 'char max' do
589
+
590
+ before do
591
+ @big_text = 'x' * 2_000_000
592
+ @old_textsize = @client.execute("SELECT @@TEXTSIZE AS [textsize]").each.first['textsize'].inspect
593
+ @client.execute("SET TEXTSIZE #{(@big_text.length*2)+1}").do
594
+ end
595
+
596
+ it 'must insert and select large varchar_max' do
597
+ insert_and_select_datatype :varchar_max
598
+ end
599
+
600
+ it 'must insert and select large nvarchar_max' do
601
+ insert_and_select_datatype :nvarchar_max
602
+ end
603
+
604
+ end unless sqlserver_2000? || sybase_ase?
605
+
606
+ end
607
+
608
+ describe 'when shit happens' do
609
+
610
+ it 'copes with nil or empty buffer' do
611
+ assert_raises(TypeError) { @client.execute(nil) }
612
+ assert_equal [], @client.execute('').each
613
+ end
614
+
615
+ if sqlserver?
616
+
617
+ describe 'using :message_handler option' do
618
+ let(:messages) { Array.new }
619
+
620
+ before do
621
+ close_client
622
+ @client = new_connection message_handler: Proc.new { |m| messages << m }
623
+ end
624
+
625
+ after do
626
+ messages.clear
627
+ end
628
+
629
+ it 'has a message handler that responds to call' do
630
+ assert @client.message_handler.respond_to?(:call)
631
+ end
632
+
633
+ it 'calls the provided message handler when severity is 10 or less' do
634
+ (1..10).to_a.each do |severity|
635
+ messages.clear
636
+ msg = "Test #{severity} severity"
637
+ state = rand(1..255)
638
+ @client.execute("RAISERROR(N'#{msg}', #{severity}, #{state})").do
639
+ m = messages.first
640
+ assert_equal 1, messages.length, 'there should be one message after one raiserror'
641
+ assert_equal msg, m.message, 'message text'
642
+ assert_equal severity, m.severity, 'message severity' unless severity == 10 && m.severity.to_i == 0
643
+ assert_equal state, m.os_error_number, 'message state'
644
+ end
645
+ end
646
+
647
+ it 'calls the provided message handler for `print` messages' do
648
+ messages.clear
649
+ msg = 'hello'
650
+ @client.execute("PRINT '#{msg}'").do
651
+ m = messages.first
652
+ assert_equal 1, messages.length, 'there should be one message after one print statement'
653
+ assert_equal msg, m.message, 'message text'
654
+ end
655
+
656
+ it 'must raise an error preceded by a `print` message' do
657
+ messages.clear
658
+ action = lambda { @client.execute("EXEC tinytds_TestPrintWithError").do }
659
+ assert_raise_tinytds_error(action) do |e|
660
+ assert_equal 'hello', messages.first.message, 'message text'
661
+
662
+ assert_equal "Error following print", e.message
663
+ assert_equal 16, e.severity
664
+ assert_equal 50000, e.db_error_number
665
+ end
666
+ end
667
+
668
+ it 'calls the provided message handler for each of a series of `print` messages' do
669
+ messages.clear
670
+ @client.execute("EXEC tinytds_TestSeveralPrints").do
671
+ assert_equal ['hello 1', 'hello 2', 'hello 3'], messages.map { |e| e.message }, 'message list'
672
+ end
673
+
674
+ it 'should flush info messages before raising error in cases of timeout' do
675
+ @client = new_connection timeout: 1, message_handler: Proc.new { |m| messages << m }
676
+ action = lambda { @client.execute("print 'hello'; waitfor delay '00:00:02'").do }
677
+ messages.clear
678
+ assert_raise_tinytds_error(action) do |e|
679
+ assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
680
+ assert_equal 6, e.severity
681
+ assert_equal 20003, e.db_error_number
682
+ assert_equal 'hello', messages.first&.message, 'message text'
683
+ end
684
+ end
685
+
686
+ it 'should print info messages before raising error in cases of timeout' do
687
+ @client = new_connection timeout: 1, message_handler: Proc.new { |m| messages << m }
688
+ action = lambda { @client.execute("raiserror('hello', 1, 1) with nowait; waitfor delay '00:00:02'").do }
689
+ messages.clear
690
+ assert_raise_tinytds_error(action) do |e|
691
+ assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
692
+ assert_equal 6, e.severity
693
+ assert_equal 20003, e.db_error_number
694
+ assert_equal 'hello', messages.first&.message, 'message text'
695
+ end
696
+ end
697
+ end
698
+
699
+ it 'must not raise an error when severity is 10 or less' do
700
+ (1..10).to_a.each do |severity|
701
+ @client.execute("RAISERROR(N'Test #{severity} severity', #{severity}, 1)").do
702
+ end
703
+ end
704
+
705
+ it 'raises an error when severity is greater than 10' do
706
+ action = lambda { @client.execute("RAISERROR(N'Test 11 severity', 11, 1)").do }
707
+ assert_raise_tinytds_error(action) do |e|
708
+ assert_equal "Test 11 severity", e.message
709
+ assert_equal 11, e.severity
710
+ assert_equal 50000, e.db_error_number
711
+ end
712
+ end
713
+
714
+ else
715
+
716
+ it 'raises an error' do
717
+ action = lambda { @client.execute("RAISERROR 50000 N'Hello World'").do }
718
+ assert_raise_tinytds_error(action) do |e|
719
+ assert_equal "Hello World", e.message
720
+ assert_equal 16, e.severity # predefined on ASE
721
+ assert_equal 50000, e.db_error_number
722
+ end
723
+ assert_followup_query
724
+ end
725
+
726
+ end
727
+
728
+ it 'throws an error when you execute another query with other results pending' do
729
+ @client.execute(@query1)
730
+ action = lambda { @client.execute(@query1) }
731
+ assert_raise_tinytds_error(action) do |e|
732
+ assert_match %r|with results pending|i, e.message
733
+ assert_equal 7, e.severity
734
+ assert_equal 20019, e.db_error_number
735
+ end
736
+ end
737
+
738
+ it 'must error gracefully with bad table name' do
739
+ action = lambda { @client.execute('SELECT * FROM [foobar]').each }
740
+ assert_raise_tinytds_error(action) do |e|
741
+ pattern = sybase_ase? ? /foobar not found/ : %r|invalid object name.*foobar|i
742
+ assert_match pattern, e.message
743
+ assert_equal 16, e.severity
744
+ assert_equal 208, e.db_error_number
745
+ end
746
+ assert_followup_query
747
+ end
748
+
749
+ it 'must error gracefully with incorrect syntax' do
750
+ action = lambda { @client.execute('this will not work').each }
751
+ assert_raise_tinytds_error(action) do |e|
752
+ assert_match %r|incorrect syntax|i, e.message
753
+ assert_equal 15, e.severity
754
+ assert_equal 156, e.db_error_number
755
+ end
756
+ assert_followup_query
757
+ end
758
+
759
+ it 'must not error at all from reading non-convertable charcters and just use ? marks' do
760
+ close_client
761
+ @client = new_connection :encoding => 'ASCII'
762
+ _(@client.charset).must_equal 'ASCII'
763
+ _(find_value(202, :nvarchar_50)).must_equal 'test nvarchar_50 ??'
764
+ end
765
+
766
+ it 'must error gracefully from writing non-convertable characters' do
767
+ close_client
768
+ @client = new_connection :encoding => 'ASCII'
769
+ _(@client.charset).must_equal 'ASCII'
770
+ rollback_transaction(@client) do
771
+ text = 'Test ✓'
772
+ @client.execute("DELETE FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").do
773
+ action = lambda { @client.execute("INSERT INTO [datatypes] ([nvarchar_50]) VALUES ('#{text}')").do }
774
+ assert_raise_tinytds_error(action) do |e|
775
+ _(e.message).must_match %r{Unclosed quotation mark}i
776
+ _(e.severity).must_equal 15
777
+ _(e.db_error_number).must_equal 105
778
+ end
779
+ assert_followup_query
780
+ end
781
+ end
782
+
783
+ it 'errors gracefully with incorrect syntax in sp_executesql' do
784
+ action = lambda { @client.execute("EXEC sp_executesql N'this will not work'").each }
785
+ assert_raise_tinytds_error(action) do |e|
786
+ assert_match %r|incorrect syntax|i, e.message
787
+ assert_equal 15, e.severity
788
+ assert_equal 156, e.db_error_number
789
+ end
790
+ assert_followup_query
791
+ end unless sybase_ase?
792
+
793
+ end
794
+
795
+ end
796
+
797
+
798
+ protected
799
+
800
+ def assert_followup_query
801
+ result = @client.execute(@query1)
802
+ assert_equal 1, result.each.first['one']
803
+ end
804
+
805
+ def insert_and_select_datatype(datatype)
806
+ rollback_transaction(@client) do
807
+ @client.execute("DELETE FROM [datatypes] WHERE [#{datatype}] IS NOT NULL").do
808
+ id = @client.execute("INSERT INTO [datatypes] ([#{datatype}]) VALUES (N'#{@big_text}')").insert
809
+ found_text = find_value id, datatype
810
+ 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
811
+ end
812
+ end
813
+
814
+ end