tiny_tds 3.2.0-aarch64-linux-gnu

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.
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/aarch64-linux-gnu/bin/defncopy +0 -0
  44. data/ports/aarch64-linux-gnu/bin/tsql +0 -0
  45. data/ports/aarch64-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