tiny_tds 2.1.6-x64-mingw-ucrt

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 (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