tiny_tds 3.2.0-x86_64-linux-gnu

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +20 -0
  3. data/.gitattributes +1 -0
  4. data/.github/workflows/ci.yml +590 -0
  5. data/.gitignore +22 -0
  6. data/.rubocop.yml +31 -0
  7. data/CHANGELOG.md +305 -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 +493 -0
  13. data/Rakefile +67 -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 +492 -0
  20. data/ext/tiny_tds/client.h +53 -0
  21. data/ext/tiny_tds/extconf.rb +190 -0
  22. data/ext/tiny_tds/extconsts.rb +8 -0
  23. data/ext/tiny_tds/result.c +626 -0
  24. data/ext/tiny_tds/result.h +32 -0
  25. data/ext/tiny_tds/tiny_tds_ext.c +15 -0
  26. data/ext/tiny_tds/tiny_tds_ext.h +17 -0
  27. data/lib/tiny_tds/2.7/tiny_tds.so +0 -0
  28. data/lib/tiny_tds/3.0/tiny_tds.so +0 -0
  29. data/lib/tiny_tds/3.1/tiny_tds.so +0 -0
  30. data/lib/tiny_tds/3.2/tiny_tds.so +0 -0
  31. data/lib/tiny_tds/3.3/tiny_tds.so +0 -0
  32. data/lib/tiny_tds/3.4/tiny_tds.so +0 -0
  33. data/lib/tiny_tds/bin.rb +90 -0
  34. data/lib/tiny_tds/client.rb +132 -0
  35. data/lib/tiny_tds/error.rb +12 -0
  36. data/lib/tiny_tds/gem.rb +23 -0
  37. data/lib/tiny_tds/result.rb +5 -0
  38. data/lib/tiny_tds/version.rb +3 -0
  39. data/lib/tiny_tds.rb +42 -0
  40. data/patches/freetds/1.00.27/0001-mingw_missing_inet_pton.diff +34 -0
  41. data/patches/freetds/1.00.27/0002-Don-t-use-MSYS2-file-libws2_32.diff +28 -0
  42. data/patches/libiconv/1.14/1-avoid-gets-error.patch +17 -0
  43. data/ports/x86_64-linux-gnu/bin/defncopy +0 -0
  44. data/ports/x86_64-linux-gnu/bin/tsql +0 -0
  45. data/ports/x86_64-linux-gnu/lib/libsybdb.so.5 +0 -0
  46. data/setup_cimgruby_dev.sh +25 -0
  47. data/start_dev.sh +21 -0
  48. data/tasks/native_gem.rake +16 -0
  49. data/tasks/package.rake +6 -0
  50. data/tasks/ports.rake +24 -0
  51. data/tasks/test.rake +7 -0
  52. data/test/bin/install-freetds.sh +18 -0
  53. data/test/bin/install-mssql.ps1 +42 -0
  54. data/test/bin/install-mssqltools.sh +9 -0
  55. data/test/bin/install-openssl.sh +18 -0
  56. data/test/bin/restore-from-native-gem.ps1 +10 -0
  57. data/test/bin/setup_tinytds_db.sh +7 -0
  58. data/test/bin/setup_volume_permissions.sh +10 -0
  59. data/test/client_test.rb +266 -0
  60. data/test/gem_test.rb +100 -0
  61. data/test/result_test.rb +708 -0
  62. data/test/schema/1px.gif +0 -0
  63. data/test/schema/sqlserver_2017.sql +140 -0
  64. data/test/schema/sqlserver_azure.sql +140 -0
  65. data/test/schema_test.rb +417 -0
  66. data/test/sql/db-create.sql +18 -0
  67. data/test/sql/db-login.sql +38 -0
  68. data/test/test_helper.rb +244 -0
  69. data/test/thread_test.rb +89 -0
  70. data/tiny_tds.gemspec +31 -0
  71. metadata +259 -0
@@ -0,0 +1,708 @@
1
+ require "test_helper"
2
+
3
+ class ResultTest < TinyTds::TestCase
4
+ describe "Basic query and result" do
5
+ before do
6
+ @@current_schema_loaded ||= load_current_schema
7
+ @client = new_connection
8
+ @query1 = "SELECT 1 AS [one]"
9
+ end
10
+
11
+ it "has included Enumerable" do
12
+ assert TinyTds::Result.ancestors.include?(Enumerable)
13
+ end
14
+
15
+ it "responds to #each" do
16
+ result = @client.execute(@query1)
17
+ assert result.respond_to?(:each)
18
+ end
19
+
20
+ it "returns all results for #each with no block" do
21
+ result = @client.execute(@query1)
22
+ data = result.each
23
+ row = data.first
24
+ assert_instance_of Array, data
25
+ assert_equal 1, data.size
26
+ assert_instance_of Hash, row, "hash is the default query option"
27
+ end
28
+
29
+ it "returns all results for #each with a block yielding a row at a time" do
30
+ result = @client.execute(@query1)
31
+ data = result.each do |row|
32
+ assert_instance_of Hash, row, "hash is the default query option"
33
+ end
34
+ assert_instance_of Array, data
35
+ end
36
+
37
+ it "allows successive calls to each returning the same data" do
38
+ result = @client.execute(@query1)
39
+ data = result.each
40
+ result.each
41
+ assert_equal data.object_id, result.each.object_id
42
+ assert_equal data.first.object_id, result.each.first.object_id
43
+ end
44
+
45
+ it "returns hashes with string keys" do
46
+ result = @client.execute(@query1)
47
+ row = result.each(as: :hash, symbolize_keys: false).first
48
+ assert_instance_of Hash, row
49
+ assert_equal ["one"], row.keys
50
+ assert_equal ["one"], result.fields
51
+ end
52
+
53
+ it "returns hashes with symbol keys" do
54
+ result = @client.execute(@query1)
55
+ row = result.each(as: :hash, symbolize_keys: true).first
56
+ assert_instance_of Hash, row
57
+ assert_equal [:one], row.keys
58
+ assert_equal [:one], result.fields
59
+ end
60
+
61
+ it "returns arrays with string fields" do
62
+ result = @client.execute(@query1)
63
+ row = result.each(as: :array, symbolize_keys: false).first
64
+ assert_instance_of Array, row
65
+ assert_equal ["one"], result.fields
66
+ end
67
+
68
+ it "returns arrays with symbol fields" do
69
+ result = @client.execute(@query1)
70
+ row = result.each(as: :array, symbolize_keys: true).first
71
+ assert_instance_of Array, row
72
+ assert_equal [:one], result.fields
73
+ end
74
+
75
+ it "allows sql concat + to work" do
76
+ rollback_transaction(@client) do
77
+ @client.execute("DELETE FROM [datatypes]").do
78
+ @client.execute("INSERT INTO [datatypes] ([char_10], [varchar_50]) VALUES ('1', '2')").do
79
+ result = @client.execute("SELECT TOP (1) [char_10] + 'test' + [varchar_50] AS [test] FROM [datatypes]").each.first["test"]
80
+ _(result).must_equal "1 test2"
81
+ end
82
+ end
83
+
84
+ it "must be able to turn :cache_rows option off" do
85
+ result = @client.execute(@query1)
86
+ local = []
87
+ result.each(cache_rows: false) do |row|
88
+ local << row
89
+ end
90
+ assert local.first, "should have iterated over each row"
91
+ assert_equal [], result.each, "should not have been cached"
92
+ assert_equal ["one"], result.fields, "should still cache field names"
93
+ end
94
+
95
+ it "must be able to get the first result row only" do
96
+ load_current_schema
97
+ big_query = "SELECT [id] FROM [datatypes]"
98
+ one = @client.execute(big_query).each(first: true)
99
+ many = @client.execute(big_query).each
100
+ assert many.size > 1
101
+ assert one.size == 1
102
+ end
103
+
104
+ it "copes with no results when using first option" do
105
+ data = @client.execute("SELECT [id] FROM [datatypes] WHERE [id] = -1").each(first: true)
106
+ assert_equal [], data
107
+ end
108
+
109
+ it "must delete, insert and find data" do
110
+ rollback_transaction(@client) do
111
+ text = "test insert and delete"
112
+ @client.execute("DELETE FROM [datatypes] WHERE [varchar_50] IS NOT NULL").do
113
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
114
+ row = @client.execute("SELECT [varchar_50] FROM [datatypes] WHERE [varchar_50] IS NOT NULL").each.first
115
+ assert row
116
+ assert_equal text, row["varchar_50"]
117
+ end
118
+ end
119
+
120
+ it "must insert and find unicode data" do
121
+ rollback_transaction(@client) do
122
+ text = "😍"
123
+ @client.execute("DELETE FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").do
124
+ @client.execute("INSERT INTO [datatypes] ([nvarchar_50]) VALUES (N'#{text}')").do
125
+ row = @client.execute("SELECT [nvarchar_50] FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").each.first
126
+ assert_equal text, row["nvarchar_50"]
127
+ end
128
+ end
129
+
130
+ it "must delete and update with affected rows support and insert with identity support in native sql" do
131
+ rollback_transaction(@client) do
132
+ text = "test affected rows sql"
133
+ @client.execute("DELETE FROM [datatypes]").do
134
+ afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first["AffectedRows"]
135
+ _(["Fixnum", "Integer"]).must_include afrows.class.name
136
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
137
+ pk1 = @client.execute(@client.identity_sql).each.first["Ident"]
138
+ _(["Fixnum", "Integer"]).must_include pk1.class.name, "we it be able to CAST to bigint"
139
+ @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
140
+ afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first["AffectedRows"]
141
+ assert_equal 1, afrows
142
+ end
143
+ end
144
+
145
+ it "has a #do method that cancels result rows and returns affected rows natively" do
146
+ rollback_transaction(@client) do
147
+ text = "test affected rows native"
148
+ count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first["count"]
149
+ deleted_rows = @client.execute("DELETE FROM [datatypes]").do
150
+ assert_equal count, deleted_rows, "should have deleted rows equal to count"
151
+ inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
152
+ assert_equal 1, inserted_rows, "should have inserted row for one above"
153
+ updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
154
+ assert_equal 1, updated_rows, "should have updated row for one above"
155
+ end
156
+ end
157
+
158
+ it "allows native affected rows using #do to work under transaction" do
159
+ rollback_transaction(@client) do
160
+ text = "test affected rows native in transaction"
161
+ @client.execute("BEGIN TRANSACTION").do
162
+ @client.execute("DELETE FROM [datatypes]").do
163
+ inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
164
+ assert_equal 1, inserted_rows, "should have inserted row for one above"
165
+ updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
166
+ assert_equal 1, updated_rows, "should have updated row for one above"
167
+ end
168
+ end
169
+
170
+ it "has an #insert method that cancels result rows and returns IDENTITY natively" do
171
+ rollback_transaction(@client) do
172
+ text = "test scope identity rows native"
173
+ @client.execute("DELETE FROM [datatypes] WHERE [varchar_50] = '#{text}'").do
174
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
175
+ sql_identity = @client.execute(@client.identity_sql).each.first["Ident"]
176
+ native_identity = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").insert
177
+ assert_equal sql_identity + 1, native_identity
178
+ end
179
+ end
180
+
181
+ it "returns bigint for #insert when needed" do
182
+ return if sqlserver_azure? # We can not alter clustered index like this test does.
183
+ # 'CREATE TABLE' command is not allowed within a multi-statement transaction
184
+ # and and sp_helpindex creates a temporary table #spindtab.
185
+ rollback_transaction(@client) do
186
+ seed = 9223372036854775805
187
+ @client.execute("DELETE FROM [datatypes]").do
188
+ id_constraint_name = @client.execute("EXEC sp_helpindex [datatypes]").detect { |row| row["index_keys"] == "id" }["index_name"]
189
+ @client.execute("ALTER TABLE [datatypes] DROP CONSTRAINT [#{id_constraint_name}]").do
190
+ @client.execute("ALTER TABLE [datatypes] DROP COLUMN [id]").do
191
+ @client.execute("ALTER TABLE [datatypes] ADD [id] [bigint] NOT NULL IDENTITY(1,1) PRIMARY KEY").do
192
+ @client.execute("DBCC CHECKIDENT ('datatypes', RESEED, #{seed})").do
193
+ identity = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('something')").insert
194
+ assert_equal seed, identity
195
+ end
196
+ end
197
+
198
+ it "must be able to begin/commit transactions with raw sql" do
199
+ rollback_transaction(@client) do
200
+ @client.execute("BEGIN TRANSACTION").do
201
+ @client.execute("DELETE FROM [datatypes]").do
202
+ @client.execute("COMMIT TRANSACTION").do
203
+ count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first["count"]
204
+ assert_equal 0, count
205
+ end
206
+ end
207
+
208
+ it "must be able to begin/rollback transactions with raw sql" do
209
+ load_current_schema
210
+ @client.execute("BEGIN TRANSACTION").do
211
+ @client.execute("DELETE FROM [datatypes]").do
212
+ @client.execute("ROLLBACK TRANSACTION").do
213
+ count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first["count"]
214
+ _(count).wont_equal 0
215
+ end
216
+
217
+ it "has a #fields accessor with logic default and valid outcome" do
218
+ result = @client.execute(@query1)
219
+ _(result.fields).must_equal ["one"]
220
+ result.each
221
+ _(result.fields).must_equal ["one"]
222
+ end
223
+
224
+ it "always returns an array for fields for all sql" do
225
+ result = @client.execute("USE [tinytdstest]")
226
+ _(result.fields).must_equal []
227
+ result.do
228
+ _(result.fields).must_equal []
229
+ end
230
+
231
+ it "returns fields even when no results are found" do
232
+ no_results_query = "SELECT [id], [varchar_50] FROM [datatypes] WHERE [varchar_50] = 'NOTFOUND'"
233
+ # Fields before each.
234
+ result = @client.execute(no_results_query)
235
+ _(result.fields).must_equal ["id", "varchar_50"]
236
+ result.each
237
+ _(result.fields).must_equal ["id", "varchar_50"]
238
+ # Each then fields
239
+ result = @client.execute(no_results_query)
240
+ result.each
241
+ _(result.fields).must_equal ["id", "varchar_50"]
242
+ end
243
+
244
+ it "allows the result to be canceled before reading" do
245
+ result = @client.execute(@query1)
246
+ result.cancel
247
+ @client.execute(@query1).each
248
+ end
249
+
250
+ it "works in tandem with the client when needing to find out if client has sql sent and result is canceled or not" do
251
+ # Default state.
252
+ @client = TinyTds::Client.new(connection_options)
253
+ _(@client.sqlsent?).must_equal false
254
+ _(@client.canceled?).must_equal false
255
+ # With active result before and after cancel.
256
+ result = @client.execute(@query1)
257
+ _(@client.sqlsent?).must_equal true
258
+ _(@client.canceled?).must_equal false
259
+ result.cancel
260
+ _(@client.sqlsent?).must_equal false
261
+ _(@client.canceled?).must_equal true
262
+ assert result.cancel, "must be safe to call again"
263
+ # With each and no block.
264
+ @client.execute(@query1).each
265
+ _(@client.sqlsent?).must_equal false
266
+ _(@client.canceled?).must_equal false
267
+ # With each and block.
268
+ @client.execute(@query1).each do |row|
269
+ _(@client.sqlsent?).must_equal true, "when iterating over each row in a block"
270
+ _(@client.canceled?).must_equal false
271
+ end
272
+ _(@client.sqlsent?).must_equal false
273
+ _(@client.canceled?).must_equal false
274
+ # With each and block canceled half way thru.
275
+ count = @client.execute("SELECT COUNT([id]) AS [count] FROM [datatypes]").each[0]["count"]
276
+ assert count > 10, "since we want to cancel early for test"
277
+ result = @client.execute("SELECT [id] FROM [datatypes]")
278
+ index = 0
279
+ result.each do |row|
280
+ break if index > 10
281
+ index += 1
282
+ end
283
+ _(@client.sqlsent?).must_equal true
284
+ _(@client.canceled?).must_equal false
285
+ result.cancel
286
+ _(@client.sqlsent?).must_equal false
287
+ _(@client.canceled?).must_equal true
288
+ # With do method.
289
+ @client.execute(@query1).do
290
+ _(@client.sqlsent?).must_equal false
291
+ _(@client.canceled?).must_equal true
292
+ # With insert method.
293
+ rollback_transaction(@client) do
294
+ @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('test')").insert
295
+ _(@client.sqlsent?).must_equal false
296
+ _(@client.canceled?).must_equal true
297
+ end
298
+ # With first
299
+ @client.execute("SELECT [id] FROM [datatypes]").each(first: true)
300
+ _(@client.sqlsent?).must_equal false
301
+ _(@client.canceled?).must_equal true
302
+ end
303
+
304
+ it "use same string object for hash keys" do
305
+ data = @client.execute("SELECT [id], [bigint] FROM [datatypes]").each
306
+ assert_equal data.first.keys.map { |r| r.object_id }, data.last.keys.map { |r| r.object_id }
307
+ end
308
+
309
+ it "has properly encoded column names with symbol keys" do
310
+ col_name = "öäüß"
311
+ begin
312
+ @client.execute("DROP TABLE [test_encoding]").do
313
+ rescue
314
+ nil
315
+ end
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
+ before do
345
+ @empty_select = "SELECT 1 AS [rs1] WHERE 1 = 0"
346
+ @double_select = "SELECT 1 AS [rs1]
347
+ SELECT 2 AS [rs2]"
348
+ @triple_select_1st_empty = "SELECT 1 AS [rs1] WHERE 1 = 0
349
+ SELECT 2 AS [rs2]
350
+ SELECT 3 AS [rs3]"
351
+ @triple_select_2nd_empty = "SELECT 1 AS [rs1]
352
+ SELECT 2 AS [rs2] WHERE 1 = 0
353
+ SELECT 3 AS [rs3]"
354
+ @triple_select_3rd_empty = "SELECT 1 AS [rs1]
355
+ SELECT 2 AS [rs2]
356
+ SELECT 3 AS [rs3] WHERE 1 = 0"
357
+ end
358
+
359
+ it "handles a command buffer with double selects" do
360
+ result = @client.execute(@double_select)
361
+ result_sets = result.each
362
+ assert_equal 2, result_sets.size
363
+ assert_equal [{"rs1" => 1}], result_sets.first
364
+ assert_equal [{"rs2" => 2}], result_sets.last
365
+ assert_equal [["rs1"], ["rs2"]], result.fields
366
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
367
+ # As array
368
+ result = @client.execute(@double_select)
369
+ result_sets = result.each(as: :array)
370
+ assert_equal 2, result_sets.size
371
+ assert_equal [[1]], result_sets.first
372
+ assert_equal [[2]], result_sets.last
373
+ assert_equal [["rs1"], ["rs2"]], result.fields
374
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
375
+ end
376
+
377
+ it "yields each row for each result set" do
378
+ data = []
379
+ result_sets = @client.execute(@double_select).each { |row| data << row }
380
+ assert_equal data.first, result_sets.first[0]
381
+ assert_equal data.last, result_sets.last[0]
382
+ end
383
+
384
+ it "works from a stored procedure" do
385
+ results1, results2 = @client.execute("EXEC sp_helpconstraint '[datatypes]'").each
386
+ assert_equal [{"Object Name" => "[datatypes]"}], results1
387
+ constraint_info = results2.first
388
+ assert constraint_info.key?("constraint_keys")
389
+ assert constraint_info.key?("constraint_type")
390
+ assert constraint_info.key?("constraint_name")
391
+ end
392
+
393
+ describe "using :empty_sets TRUE" do
394
+ before do
395
+ close_client
396
+ @old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
397
+ TinyTds::Client.default_query_options[:empty_sets] = true
398
+ @client = new_connection
399
+ end
400
+
401
+ after do
402
+ TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
403
+ end
404
+
405
+ it "handles a basic empty result set" do
406
+ result = @client.execute(@empty_select)
407
+ assert_equal [], result.each
408
+ assert_equal ["rs1"], result.fields
409
+ end
410
+
411
+ it "includes empty result sets by default - using 1st empty buffer" do
412
+ result = @client.execute(@triple_select_1st_empty)
413
+ result_sets = result.each
414
+ assert_equal 3, result_sets.size
415
+ assert_equal [], result_sets[0]
416
+ assert_equal [{"rs2" => 2}], result_sets[1]
417
+ assert_equal [{"rs3" => 3}], result_sets[2]
418
+ assert_equal [["rs1"], ["rs2"], ["rs3"]], result.fields
419
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
420
+ # As array
421
+ result = @client.execute(@triple_select_1st_empty)
422
+ result_sets = result.each(as: :array)
423
+ assert_equal 3, result_sets.size
424
+ assert_equal [], result_sets[0]
425
+ assert_equal [[2]], result_sets[1]
426
+ assert_equal [[3]], result_sets[2]
427
+ assert_equal [["rs1"], ["rs2"], ["rs3"]], result.fields
428
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
429
+ end
430
+
431
+ it "includes empty result sets by default - using 2nd empty buffer" do
432
+ result = @client.execute(@triple_select_2nd_empty)
433
+ result_sets = result.each
434
+ assert_equal 3, result_sets.size
435
+ assert_equal [{"rs1" => 1}], result_sets[0]
436
+ assert_equal [], result_sets[1]
437
+ assert_equal [{"rs3" => 3}], result_sets[2]
438
+ assert_equal [["rs1"], ["rs2"], ["rs3"]], result.fields
439
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
440
+ # As array
441
+ result = @client.execute(@triple_select_2nd_empty)
442
+ result_sets = result.each(as: :array)
443
+ assert_equal 3, result_sets.size
444
+ assert_equal [[1]], result_sets[0]
445
+ assert_equal [], result_sets[1]
446
+ assert_equal [[3]], result_sets[2]
447
+ assert_equal [["rs1"], ["rs2"], ["rs3"]], result.fields
448
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
449
+ end
450
+
451
+ it "includes empty result sets by default - using 3rd empty buffer" do
452
+ result = @client.execute(@triple_select_3rd_empty)
453
+ result_sets = result.each
454
+ assert_equal 3, result_sets.size
455
+ assert_equal [{"rs1" => 1}], result_sets[0]
456
+ assert_equal [{"rs2" => 2}], result_sets[1]
457
+ assert_equal [], result_sets[2]
458
+ assert_equal [["rs1"], ["rs2"], ["rs3"]], result.fields
459
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
460
+ # As array
461
+ result = @client.execute(@triple_select_3rd_empty)
462
+ result_sets = result.each(as: :array)
463
+ assert_equal 3, result_sets.size
464
+ assert_equal [[1]], result_sets[0]
465
+ assert_equal [[2]], result_sets[1]
466
+ assert_equal [], result_sets[2]
467
+ assert_equal [["rs1"], ["rs2"], ["rs3"]], result.fields
468
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
469
+ end
470
+ end
471
+
472
+ describe "using :empty_sets FALSE" do
473
+ before do
474
+ close_client
475
+ @old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
476
+ TinyTds::Client.default_query_options[:empty_sets] = false
477
+ @client = new_connection
478
+ end
479
+
480
+ after do
481
+ TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
482
+ end
483
+
484
+ it "handles a basic empty result set" do
485
+ result = @client.execute(@empty_select)
486
+ assert_equal [], result.each
487
+ assert_equal ["rs1"], result.fields
488
+ end
489
+
490
+ it "must not include empty result sets by default - using 1st empty buffer" do
491
+ result = @client.execute(@triple_select_1st_empty)
492
+ result_sets = result.each
493
+ assert_equal 2, result_sets.size
494
+ assert_equal [{"rs2" => 2}], result_sets[0]
495
+ assert_equal [{"rs3" => 3}], result_sets[1]
496
+ assert_equal [["rs2"], ["rs3"]], result.fields
497
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
498
+ # As array
499
+ result = @client.execute(@triple_select_1st_empty)
500
+ result_sets = result.each(as: :array)
501
+ assert_equal 2, result_sets.size
502
+ assert_equal [[2]], result_sets[0]
503
+ assert_equal [[3]], result_sets[1]
504
+ assert_equal [["rs2"], ["rs3"]], result.fields
505
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
506
+ end
507
+
508
+ it "must not include empty result sets by default - using 2nd empty buffer" do
509
+ result = @client.execute(@triple_select_2nd_empty)
510
+ result_sets = result.each
511
+ assert_equal 2, result_sets.size
512
+ assert_equal [{"rs1" => 1}], result_sets[0]
513
+ assert_equal [{"rs3" => 3}], result_sets[1]
514
+ assert_equal [["rs1"], ["rs3"]], result.fields
515
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
516
+ # As array
517
+ result = @client.execute(@triple_select_2nd_empty)
518
+ result_sets = result.each(as: :array)
519
+ assert_equal 2, result_sets.size
520
+ assert_equal [[1]], result_sets[0]
521
+ assert_equal [[3]], result_sets[1]
522
+ assert_equal [["rs1"], ["rs3"]], result.fields
523
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
524
+ end
525
+
526
+ it "must not include empty result sets by default - using 3rd empty buffer" do
527
+ result = @client.execute(@triple_select_3rd_empty)
528
+ result_sets = result.each
529
+ assert_equal 2, result_sets.size
530
+ assert_equal [{"rs1" => 1}], result_sets[0]
531
+ assert_equal [{"rs2" => 2}], result_sets[1]
532
+ assert_equal [["rs1"], ["rs2"]], result.fields
533
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
534
+ # As array
535
+ result = @client.execute(@triple_select_3rd_empty)
536
+ result_sets = result.each(as: :array)
537
+ assert_equal 2, result_sets.size
538
+ assert_equal [[1]], result_sets[0]
539
+ assert_equal [[2]], result_sets[1]
540
+ assert_equal [["rs1"], ["rs2"]], result.fields
541
+ assert_equal result.each.object_id, result.each.object_id, "same cached rows"
542
+ end
543
+ end
544
+ end
545
+
546
+ unless sqlserver_azure?
547
+ describe "Complex query with multiple results sets but no actual results" do
548
+ let(:backup_file) { 'C:\\Users\\Public\\tinytdstest.bak' }
549
+
550
+ after { File.delete(backup_file) if File.exist?(backup_file) }
551
+
552
+ it "must not cancel the query until complete" do
553
+ @client.execute("BACKUP DATABASE tinytdstest TO DISK = '#{backup_file}'").do
554
+ end
555
+ end
556
+ end
557
+
558
+ describe "when casting to native ruby values" do
559
+ it "returns fixnum for 1" do
560
+ value = @client.execute("SELECT 1 AS [fixnum]").each.first["fixnum"]
561
+ assert_equal 1, value
562
+ end
563
+
564
+ it "returns nil for NULL" do
565
+ value = @client.execute("SELECT NULL AS [null]").each.first["null"]
566
+ assert_nil value
567
+ end
568
+ end
569
+
570
+ describe "with data type" do
571
+ describe "char max" do
572
+ before do
573
+ @big_text = "x" * 2_000_000
574
+ @old_textsize = @client.execute("SELECT @@TEXTSIZE AS [textsize]").each.first["textsize"].inspect
575
+ @client.execute("SET TEXTSIZE #{(@big_text.length * 2) + 1}").do
576
+ end
577
+
578
+ it "must insert and select large varchar_max" do
579
+ insert_and_select_datatype :varchar_max
580
+ end
581
+
582
+ it "must insert and select large nvarchar_max" do
583
+ insert_and_select_datatype :nvarchar_max
584
+ end
585
+ end
586
+ end
587
+
588
+ describe "when shit happens" do
589
+ it "copes with nil or empty buffer" do
590
+ assert_raises(TypeError) { @client.execute(nil) }
591
+ assert_equal [], @client.execute("").each
592
+ end
593
+
594
+ describe "using :message_handler option" do
595
+ let(:messages) { [] }
596
+
597
+ before do
598
+ close_client
599
+ @client = new_connection message_handler: proc { |m| messages << m }
600
+ end
601
+
602
+ after do
603
+ messages.clear
604
+ end
605
+
606
+ it "has a message handler that responds to call" do
607
+ assert @client.message_handler.respond_to?(:call)
608
+ end
609
+
610
+ it "calls the provided message handler when severity is 10 or less" do
611
+ (1..10).to_a.each do |severity|
612
+ messages.clear
613
+ msg = "Test #{severity} severity"
614
+ state = rand(1..255)
615
+ @client.execute("RAISERROR(N'#{msg}', #{severity}, #{state})").do
616
+ m = messages.first
617
+ assert_equal 1, messages.length, "there should be one message after one raiserror"
618
+ assert_equal msg, m.message, "message text"
619
+ assert_equal severity, m.severity, "message severity" unless severity == 10 && m.severity.to_i == 0
620
+ assert_equal state, m.os_error_number, "message state"
621
+ end
622
+ end
623
+
624
+ it "calls the provided message handler for `print` messages" do
625
+ messages.clear
626
+ msg = "hello"
627
+ @client.execute("PRINT '#{msg}'").do
628
+ m = messages.first
629
+ assert_equal 1, messages.length, "there should be one message after one print statement"
630
+ assert_equal msg, m.message, "message text"
631
+ end
632
+
633
+ it "must raise an error preceded by a `print` message" do
634
+ messages.clear
635
+ action = lambda { @client.execute("EXEC tinytds_TestPrintWithError").do }
636
+ assert_raise_tinytds_error(action) do |e|
637
+ assert_equal "hello", messages.first.message, "message text"
638
+
639
+ assert_equal "Error following print", e.message
640
+ assert_equal 16, e.severity
641
+ assert_equal 50000, e.db_error_number
642
+ end
643
+ end
644
+
645
+ it "calls the provided message handler for each of a series of `print` messages" do
646
+ messages.clear
647
+ @client.execute("EXEC tinytds_TestSeveralPrints").do
648
+ assert_equal ["hello 1", "hello 2", "hello 3"], messages.map { |e| e.message }, "message list"
649
+ end
650
+
651
+ it "should flush info messages before raising error in cases of timeout" do
652
+ @client = new_connection timeout: 1, message_handler: proc { |m| messages << m }
653
+ action = lambda { @client.execute("print 'hello'; waitfor delay '00:00:02'").do }
654
+ messages.clear
655
+ assert_raise_tinytds_error(action) do |e|
656
+ assert_match %r{timed out}i, e.message, "ignore if non-english test run"
657
+ assert_equal 6, e.severity
658
+ assert_equal 20003, e.db_error_number
659
+ assert_equal "hello", messages.first&.message, "message text"
660
+ end
661
+ end
662
+
663
+ it "should print info messages before raising error in cases of timeout" do
664
+ @client = new_connection timeout: 1, message_handler: proc { |m| messages << m }
665
+ action = lambda { @client.execute("raiserror('hello', 1, 1) with nowait; waitfor delay '00:00:02'").do }
666
+ messages.clear
667
+ assert_raise_tinytds_error(action) do |e|
668
+ assert_match %r{timed out}i, e.message, "ignore if non-english test run"
669
+ assert_equal 6, e.severity
670
+ assert_equal 20003, e.db_error_number
671
+ assert_equal "hello", messages.first&.message, "message text"
672
+ end
673
+ end
674
+ end
675
+
676
+ it "must not raise an error when severity is 10 or less" do
677
+ (1..10).to_a.each do |severity|
678
+ @client.execute("RAISERROR(N'Test #{severity} severity', #{severity}, 1)").do
679
+ end
680
+ end
681
+
682
+ it "raises an error when severity is greater than 10" do
683
+ action = lambda { @client.execute("RAISERROR(N'Test 11 severity', 11, 1)").do }
684
+ assert_raise_tinytds_error(action) do |e|
685
+ assert_equal "Test 11 severity", e.message
686
+ assert_equal 11, e.severity
687
+ assert_equal 50000, e.db_error_number
688
+ end
689
+ end
690
+ end
691
+ end
692
+
693
+ protected
694
+
695
+ def assert_followup_query
696
+ result = @client.execute(@query1)
697
+ assert_equal 1, result.each.first["one"]
698
+ end
699
+
700
+ def insert_and_select_datatype(datatype)
701
+ rollback_transaction(@client) do
702
+ @client.execute("DELETE FROM [datatypes] WHERE [#{datatype}] IS NOT NULL").do
703
+ id = @client.execute("INSERT INTO [datatypes] ([#{datatype}]) VALUES (N'#{@big_text}')").insert
704
+ found_text = find_value id, datatype
705
+ 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
706
+ end
707
+ end
708
+ end