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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +20 -0
- data/.gitattributes +1 -0
- data/.github/workflows/ci.yml +590 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +31 -0
- data/CHANGELOG.md +305 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/Gemfile +2 -0
- data/ISSUE_TEMPLATE.md +38 -0
- data/MIT-LICENSE +23 -0
- data/README.md +493 -0
- data/Rakefile +67 -0
- data/VERSION +1 -0
- data/bin/defncopy-ttds +3 -0
- data/bin/tsql-ttds +3 -0
- data/docker-compose.yml +34 -0
- data/exe/.keep +0 -0
- data/ext/tiny_tds/client.c +492 -0
- data/ext/tiny_tds/client.h +53 -0
- data/ext/tiny_tds/extconf.rb +190 -0
- data/ext/tiny_tds/extconsts.rb +8 -0
- data/ext/tiny_tds/result.c +626 -0
- data/ext/tiny_tds/result.h +32 -0
- data/ext/tiny_tds/tiny_tds_ext.c +15 -0
- data/ext/tiny_tds/tiny_tds_ext.h +17 -0
- data/lib/tiny_tds/2.7/tiny_tds.so +0 -0
- data/lib/tiny_tds/3.0/tiny_tds.so +0 -0
- data/lib/tiny_tds/3.1/tiny_tds.so +0 -0
- data/lib/tiny_tds/3.2/tiny_tds.so +0 -0
- data/lib/tiny_tds/3.3/tiny_tds.so +0 -0
- data/lib/tiny_tds/3.4/tiny_tds.so +0 -0
- data/lib/tiny_tds/bin.rb +90 -0
- data/lib/tiny_tds/client.rb +132 -0
- data/lib/tiny_tds/error.rb +12 -0
- data/lib/tiny_tds/gem.rb +23 -0
- data/lib/tiny_tds/result.rb +5 -0
- data/lib/tiny_tds/version.rb +3 -0
- data/lib/tiny_tds.rb +42 -0
- data/patches/freetds/1.00.27/0001-mingw_missing_inet_pton.diff +34 -0
- data/patches/freetds/1.00.27/0002-Don-t-use-MSYS2-file-libws2_32.diff +28 -0
- data/patches/libiconv/1.14/1-avoid-gets-error.patch +17 -0
- data/ports/aarch64-linux-gnu/bin/defncopy +0 -0
- data/ports/aarch64-linux-gnu/bin/tsql +0 -0
- data/ports/aarch64-linux-gnu/lib/libsybdb.so.5 +0 -0
- data/setup_cimgruby_dev.sh +25 -0
- data/start_dev.sh +21 -0
- data/tasks/native_gem.rake +16 -0
- data/tasks/package.rake +6 -0
- data/tasks/ports.rake +24 -0
- data/tasks/test.rake +7 -0
- data/test/bin/install-freetds.sh +18 -0
- data/test/bin/install-mssql.ps1 +42 -0
- data/test/bin/install-mssqltools.sh +9 -0
- data/test/bin/install-openssl.sh +18 -0
- data/test/bin/restore-from-native-gem.ps1 +10 -0
- data/test/bin/setup_tinytds_db.sh +7 -0
- data/test/bin/setup_volume_permissions.sh +10 -0
- data/test/client_test.rb +266 -0
- data/test/gem_test.rb +100 -0
- data/test/result_test.rb +708 -0
- data/test/schema/1px.gif +0 -0
- data/test/schema/sqlserver_2017.sql +140 -0
- data/test/schema/sqlserver_azure.sql +140 -0
- data/test/schema_test.rb +417 -0
- data/test/sql/db-create.sql +18 -0
- data/test/sql/db-login.sql +38 -0
- data/test/test_helper.rb +244 -0
- data/test/thread_test.rb +89 -0
- data/tiny_tds.gemspec +31 -0
- metadata +259 -0
data/test/result_test.rb
ADDED
@@ -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
|