Skip to content

Commit 2ade154

Browse files
committed
Properly acknowledge results when not consuming results
1 parent bb1d8ae commit 2ade154

File tree

4 files changed

+68
-9
lines changed

4 files changed

+68
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Drop support for SQL Server < 2017
55
* Drop support for FreeTDS < 1.0
66
* Raise error if FreeTDS is unable to sent command buffer to the server
7+
* Cancel previous query results automatically when invoking a new query
78

89
## 2.1.7
910
* Add Ruby 3.3 to the cross compile list

README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ result = client.execute("SELECT * FROM [datatypes]")
150150

151151
## TinyTds::Result Usage
152152

153-
A result object is returned by the client's execute command. It is important that you either return the data from the query, most likely with the #each method, or that you cancel the results before asking the client to execute another SQL batch. Failing to do so will yield an error.
153+
A result object is returned by the client's execute command. It is important that you either return the data from the query, most likely with the #each method. You can manually cancel the results early using `#cancel`, otherwise tiny_tds will also automatically do so when running a new query with `#execute`.
154154

155155
Calling #each on the result will lazily load each row from the database.
156156

@@ -171,7 +171,6 @@ result.do
171171

172172
result = client.execute("SELECT [id] FROM [datatypes]")
173173
result.fields # => ["id"]
174-
result.cancel
175174
result = client.execute("SELECT [id] FROM [datatypes]")
176175
result.each(:symbolize_keys => true)
177176
result.fields # => [:id]

ext/tiny_tds/client.c

+25-7
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@ VALUE opt_escape_regex, opt_escape_dblquote;
1515
tinytds_client_wrapper *cwrap; \
1616
Data_Get_Struct(self, tinytds_client_wrapper, cwrap)
1717

18-
#define REQUIRE_OPEN_CLIENT(cwrap) \
19-
if (cwrap->closed || cwrap->userdata->closed) { \
20-
rb_raise(cTinyTdsError, "closed connection"); \
21-
return Qnil; \
22-
}
23-
2418

2519
// Lib Backend (Helpers)
2620

@@ -295,8 +289,32 @@ static VALUE rb_tinytds_execute(VALUE self, VALUE sql) {
295289
VALUE result;
296290

297291
GET_CLIENT_WRAPPER(self);
292+
293+
if (cwrap->closed || cwrap->userdata->closed) {
294+
rb_raise(cTinyTdsError, "closed connection");
295+
return Qnil;
296+
}
297+
298+
if (rb_tinytds_dead(self) == Qtrue) {
299+
rb_raise(cTinyTdsError, "client is dead, please create a new instance");
300+
return Qnil;
301+
}
302+
303+
// user is coming back from an each loop, make sure we cancel the pending results
304+
if (cwrap->userdata->dbsql_sent) {
305+
// if we do not run dbsqlok, FreeTDS will throw an error
306+
// Attempt to initiate a new Adaptive Server operation with results pending
307+
// note that both of these operations are blocking as we do not have access to these
308+
// "NOGVL" methods from result.c
309+
if (cwrap->userdata->dbsqlok_sent == 0) {
310+
dbsqlok(cwrap->client);
311+
}
312+
313+
dbcancel(cwrap->client);
314+
}
315+
298316
rb_tinytds_client_reset_userdata(cwrap->userdata);
299-
REQUIRE_OPEN_CLIENT(cwrap);
317+
300318
dbcmd(cwrap->client, StringValueCStr(sql));
301319
if (dbsqlsend(cwrap->client) == FAIL) {
302320
rb_raise(cTinyTdsError, "failed dbsqlsend() function");

test/client_test.rb

+41
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,45 @@ class ClientTest < TinyTds::TestCase
269269
).must_equal 'user'
270270
end
271271
end
272+
273+
describe "#execute" do
274+
it "cancels pending select query" do
275+
client = new_connection
276+
client.execute("SELECT 1 as [one]")
277+
278+
assert client.sqlsent?
279+
assert !client.canceled?
280+
281+
result = client.execute("SELECT 1 as [one]")
282+
assert_equal [{"one"=>1}], result.to_a
283+
assert_client_works(client)
284+
end
285+
286+
it "cancels pending wait query" do
287+
client = new_connection
288+
client.execute("WaitFor Delay '00:00:05'")
289+
290+
assert client.sqlsent?
291+
assert !client.canceled?
292+
293+
result = client.execute("SELECT 1 as [one]")
294+
assert_equal [{"one"=>1}], result.to_a
295+
assert_client_works(client)
296+
end
297+
298+
# this requires to not send another `dbsqlok` compared to the previous to test cases
299+
it "cancel partially retrieved results" do
300+
client = new_connection
301+
result = client.execute("SELECT 1 as [one]; SELECT 2 as [two]; SELECT 3 as [three]")
302+
result.each { |r| break if r.key?("two") }
303+
304+
assert_equal 1, result.count
305+
assert client.sqlsent?
306+
assert !client.canceled?
307+
308+
result = client.execute("SELECT 1 as [one]")
309+
assert_equal [{"one"=>1}], result.to_a
310+
assert_client_works(client)
311+
end
312+
end
272313
end

0 commit comments

Comments
 (0)