Skip to content

Commit 0598fde

Browse files
committed
Move do to client class
1 parent d05d70d commit 0598fde

File tree

7 files changed

+151
-167
lines changed

7 files changed

+151
-167
lines changed

ext/tiny_tds/client.c

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,6 @@ static void rb_tinytds_client_reset_userdata(tinytds_client_userdata *userdata)
6161
userdata->nonblocking_errors_size = 0;
6262
}
6363

64-
static VALUE rb_tinytds_send_sql_to_server(tinytds_client_wrapper *cwrap, VALUE sql) {
65-
rb_tinytds_client_reset_userdata(cwrap->userdata);
66-
67-
if (cwrap->closed || cwrap->userdata->closed) { \
68-
rb_raise(cTinyTdsError, "closed connection"); \
69-
return Qnil; \
70-
}
71-
72-
dbcmd(cwrap->client, StringValueCStr(sql));
73-
if (dbsqlsend(cwrap->client) == FAIL) {
74-
rb_raise(cTinyTdsError, "failed dbsqlsend() function");
75-
}
76-
77-
cwrap->userdata->dbsql_sent = 1;
78-
}
79-
8064
// code part used to invoke FreeTDS functions with releasing the Ruby GVL
8165
// basically, while FreeTDS is interacting with the SQL server, other Ruby code can be executed
8266
#define NOGVL_DBCALL(_dbfunction, _client) ( \
@@ -164,14 +148,55 @@ static RETCODE nogvl_dbsqlok(DBPROCESS *client) {
164148
return retcode;
165149
}
166150

167-
static RETCODE rb_tinytds_result_ok_helper(DBPROCESS *client) {
151+
// some additional helpers interacting with the SQL server
152+
static VALUE rb_tinytds_send_sql_to_server(tinytds_client_wrapper *cwrap, VALUE sql) {
153+
rb_tinytds_client_reset_userdata(cwrap->userdata);
154+
155+
if (cwrap->closed || cwrap->userdata->closed) { \
156+
rb_raise(cTinyTdsError, "closed connection"); \
157+
return Qnil; \
158+
}
159+
160+
dbcmd(cwrap->client, StringValueCStr(sql));
161+
if (dbsqlsend(cwrap->client) == FAIL) {
162+
rb_raise(cTinyTdsError, "failed dbsqlsend() function");
163+
}
164+
165+
cwrap->userdata->dbsql_sent = 1;
166+
}
167+
168+
static RETCODE rb_tiny_tds_client_ok_helper(DBPROCESS *client) {
168169
GET_CLIENT_USERDATA(client);
169170
if (userdata->dbsqlok_sent == 0) {
170171
userdata->dbsqlok_retcode = nogvl_dbsqlok(client);
171172
}
173+
172174
return userdata->dbsqlok_retcode;
173175
}
174176

177+
static void rb_tinytds_result_exec_helper(DBPROCESS *client) {
178+
RETCODE dbsqlok_rc = rb_tiny_tds_client_ok_helper(client);
179+
GET_CLIENT_USERDATA(client);
180+
if (dbsqlok_rc == SUCCEED) {
181+
/*
182+
This is to just process each result set. Commands such as backup and
183+
restore are not done when the first result set is returned, so we need to
184+
exhaust the result sets before it is complete.
185+
*/
186+
while (nogvl_dbresults(client) == SUCCEED) {
187+
/*
188+
If we don't loop through each row for calls to TinyTds::Client.do that
189+
actually do return result sets, we will trigger error 20019 about trying
190+
to execute a new command with pending results. Oh well.
191+
*/
192+
while (dbnextrow(client) != NO_MORE_ROWS);
193+
}
194+
}
195+
dbcancel(client);
196+
userdata->dbcancel_sent = 1;
197+
userdata->dbsql_sent = 0;
198+
}
199+
175200
// Lib Backend (Memory Management & Handlers)
176201
static void push_userdata_error(tinytds_client_userdata *userdata, tinytds_errordata error) {
177202
// reallocate memory for the array as needed
@@ -414,23 +439,7 @@ static VALUE rb_tiny_tds_insert(VALUE self, VALUE sql) {
414439
VALUE identity = Qnil;
415440
GET_CLIENT_WRAPPER(self);
416441
rb_tinytds_send_sql_to_server(cwrap, sql);
417-
418-
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(cwrap->client);
419-
if (dbsqlok_rc == SUCCEED) {
420-
/*
421-
This is to just process each result set. Commands such as backup and
422-
restore are not done when the first result set is returned, so we need to
423-
exhaust the result sets before it is complete.
424-
*/
425-
while (nogvl_dbresults(cwrap->client) == SUCCEED) {
426-
/*
427-
If we don't loop through each row for calls to TinyTds::Result.do that
428-
actually do return result sets, we will trigger error 20019 about trying
429-
to execute a new command with pending results. Oh well.
430-
*/
431-
while (nogvl_dbnextrow(cwrap->client) != NO_MORE_ROWS);
432-
}
433-
}
442+
rb_tinytds_result_exec_helper(cwrap->client);
434443

435444
dbcancel(cwrap->client);
436445
cwrap->userdata->dbcancel_sent = 1;
@@ -459,6 +468,14 @@ static VALUE rb_tiny_tds_insert(VALUE self, VALUE sql) {
459468
return identity;
460469
}
461470

471+
static VALUE rb_tiny_tds_do(VALUE self, VALUE sql) {
472+
GET_CLIENT_WRAPPER(self);
473+
rb_tinytds_send_sql_to_server(cwrap, sql);
474+
rb_tinytds_result_exec_helper(cwrap->client);
475+
476+
return LONG2NUM((long)dbcount(cwrap->client));
477+
}
478+
462479
static VALUE rb_tinytds_charset(VALUE self) {
463480
GET_CLIENT_WRAPPER(self);
464481
return cwrap->charset;
@@ -599,6 +616,7 @@ void init_tinytds_client() {
599616
rb_define_method(cTinyTdsClient, "sqlsent?", rb_tinytds_sqlsent, 0);
600617
rb_define_method(cTinyTdsClient, "execute", rb_tinytds_execute, 1);
601618
rb_define_method(cTinyTdsClient, "insert", rb_tiny_tds_insert, 1);
619+
rb_define_method(cTinyTdsClient, "do", rb_tiny_tds_do, 1);
602620
rb_define_method(cTinyTdsClient, "charset", rb_tinytds_charset, 0);
603621
rb_define_method(cTinyTdsClient, "encoding", rb_tinytds_encoding, 0);
604622
rb_define_method(cTinyTdsClient, "escape", rb_tinytds_escape, 1);

ext/tiny_tds/result.c

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -173,29 +173,6 @@ static RETCODE rb_tinytds_result_ok_helper(DBPROCESS *client) {
173173
return userdata->dbsqlok_retcode;
174174
}
175175

176-
static void rb_tinytds_result_exec_helper(DBPROCESS *client) {
177-
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(client);
178-
GET_CLIENT_USERDATA(client);
179-
if (dbsqlok_rc == SUCCEED) {
180-
/*
181-
This is to just process each result set. Commands such as backup and
182-
restore are not done when the first result set is returned, so we need to
183-
exhaust the result sets before it is complete.
184-
*/
185-
while (nogvl_dbresults(client) == SUCCEED) {
186-
/*
187-
If we don't loop through each row for calls to TinyTds::Result.do that
188-
actually do return result sets, we will trigger error 20019 about trying
189-
to execute a new command with pending results. Oh well.
190-
*/
191-
while (dbnextrow(client) != NO_MORE_ROWS);
192-
}
193-
}
194-
dbcancel(client);
195-
userdata->dbcancel_sent = 1;
196-
userdata->dbsql_sent = 0;
197-
}
198-
199176
static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_keys, int as_array) {
200177
VALUE row;
201178
/* Storing Values */
@@ -506,16 +483,6 @@ static VALUE rb_tinytds_result_cancel(VALUE self) {
506483
return Qtrue;
507484
}
508485

509-
static VALUE rb_tinytds_result_do(VALUE self) {
510-
GET_RESULT_WRAPPER(self);
511-
if (rwrap->client) {
512-
rb_tinytds_result_exec_helper(rwrap->client);
513-
return LONG2NUM((long)dbcount(rwrap->client));
514-
} else {
515-
return Qnil;
516-
}
517-
}
518-
519486
static VALUE rb_tinytds_result_affected_rows(VALUE self) {
520487
GET_RESULT_WRAPPER(self);
521488
if (rwrap->client) {
@@ -548,7 +515,6 @@ void init_tinytds_result() {
548515
rb_define_method(cTinyTdsResult, "fields", rb_tinytds_result_fields, 0);
549516
rb_define_method(cTinyTdsResult, "each", rb_tinytds_result_each, -1);
550517
rb_define_method(cTinyTdsResult, "cancel", rb_tinytds_result_cancel, 0);
551-
rb_define_method(cTinyTdsResult, "do", rb_tinytds_result_do, 0);
552518
rb_define_method(cTinyTdsResult, "affected_rows", rb_tinytds_result_affected_rows, 0);
553519
rb_define_method(cTinyTdsResult, "return_code", rb_tinytds_result_return_code, 0);
554520
/* Intern String Helpers */

test/client_test.rb

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class ClientTest < TinyTds::TestCase
9898

9999
it 'raises TinyTds exception with long query past :timeout option' do
100100
client = new_connection :timeout => 1
101-
action = lambda { client.execute("WaitFor Delay '00:00:02'").do }
101+
action = lambda { client.do("WaitFor Delay '00:00:02'") }
102102
assert_raise_tinytds_error(action) do |e|
103103
assert_equal 20003, e.db_error_number
104104
assert_equal 6, e.severity
@@ -111,21 +111,21 @@ class ClientTest < TinyTds::TestCase
111111

112112
it 'must not timeout per sql batch when not under transaction' do
113113
client = new_connection :timeout => 2
114-
client.execute("WaitFor Delay '00:00:01'").do
115-
client.execute("WaitFor Delay '00:00:01'").do
116-
client.execute("WaitFor Delay '00:00:01'").do
114+
client.do("WaitFor Delay '00:00:01'")
115+
client.do("WaitFor Delay '00:00:01'")
116+
client.do("WaitFor Delay '00:00:01'")
117117
close_client(client)
118118
end
119119

120120
it 'must not timeout per sql batch when under transaction' do
121121
client = new_connection :timeout => 2
122122
begin
123-
client.execute("BEGIN TRANSACTION").do
124-
client.execute("WaitFor Delay '00:00:01'").do
125-
client.execute("WaitFor Delay '00:00:01'").do
126-
client.execute("WaitFor Delay '00:00:01'").do
123+
client.do("BEGIN TRANSACTION")
124+
client.do("WaitFor Delay '00:00:01'")
125+
client.do("WaitFor Delay '00:00:01'")
126+
client.do("WaitFor Delay '00:00:01'")
127127
ensure
128-
client.execute("COMMIT TRANSACTION").do
128+
client.do("COMMIT TRANSACTION")
129129
close_client(client)
130130
end
131131
end
@@ -134,7 +134,7 @@ class ClientTest < TinyTds::TestCase
134134
begin
135135
client = new_connection timeout: 2, port: 1234, host: ENV['TOXIPROXY_HOST']
136136
assert_client_works(client)
137-
action = lambda { client.execute("waitfor delay '00:00:05'").do }
137+
action = lambda { client.do("waitfor delay '00:00:05'") }
138138

139139
# Use toxiproxy to close the TCP socket after 1 second.
140140
# We want TinyTds to execute the statement, hit the timeout configured above, and then not be able to use the network to cancel
@@ -157,7 +157,7 @@ class ClientTest < TinyTds::TestCase
157157
begin
158158
client = new_connection timeout: 2, port: 1234, host: ENV['TOXIPROXY_HOST']
159159
assert_client_works(client)
160-
action = lambda { client.execute("waitfor delay '00:00:05'").do }
160+
action = lambda { client.do("waitfor delay '00:00:05'") }
161161

162162
# Use toxiproxy to close the network connection after 1 second.
163163
# We want TinyTds to execute the statement, hit the timeout configured above, and then not be able to use the network to cancel
@@ -278,8 +278,8 @@ class ClientTest < TinyTds::TestCase
278278
it 'has an #insert method that cancels result rows and returns IDENTITY natively' do
279279
rollback_transaction(@client) do
280280
text = 'test scope identity rows native'
281-
@client.execute("DELETE FROM [datatypes] WHERE [varchar_50] = '#{text}'").do
282-
@client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
281+
@client.do("DELETE FROM [datatypes] WHERE [varchar_50] = '#{text}'")
282+
@client.do("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')")
283283
sql_identity = @client.execute(@client.identity_sql).each.first['Ident']
284284
native_identity = @client.insert("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')")
285285

@@ -295,12 +295,12 @@ class ClientTest < TinyTds::TestCase
295295

296296
rollback_transaction(@client) do
297297
seed = 9223372036854775805
298-
@client.execute("DELETE FROM [datatypes]").do
298+
@client.do("DELETE FROM [datatypes]")
299299
id_constraint_name = @client.execute("EXEC sp_helpindex [datatypes]").detect { |row| row['index_keys'] == 'id' }['index_name']
300-
@client.execute("ALTER TABLE [datatypes] DROP CONSTRAINT [#{id_constraint_name}]").do
301-
@client.execute("ALTER TABLE [datatypes] DROP COLUMN [id]").do
302-
@client.execute("ALTER TABLE [datatypes] ADD [id] [bigint] NOT NULL IDENTITY(1,1) PRIMARY KEY").do
303-
@client.execute("DBCC CHECKIDENT ('datatypes', RESEED, #{seed})").do
300+
@client.do("ALTER TABLE [datatypes] DROP CONSTRAINT [#{id_constraint_name}]")
301+
@client.do("ALTER TABLE [datatypes] DROP COLUMN [id]")
302+
@client.do("ALTER TABLE [datatypes] ADD [id] [bigint] NOT NULL IDENTITY(1,1) PRIMARY KEY")
303+
@client.do("DBCC CHECKIDENT ('datatypes', RESEED, #{seed})")
304304
identity = @client.insert("INSERT INTO [datatypes] ([varchar_50]) VALUES ('something')")
305305

306306
assert_equal(seed, identity)
@@ -318,4 +318,35 @@ class ClientTest < TinyTds::TestCase
318318
end
319319
end
320320
end
321+
322+
describe "#do" do
323+
before do
324+
@client = new_connection
325+
end
326+
327+
it 'has a #do method that cancels result rows and returns affected rows natively' do
328+
rollback_transaction(@client) do
329+
text = 'test affected rows native'
330+
count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
331+
deleted_rows = @client.do("DELETE FROM [datatypes]")
332+
assert_equal count, deleted_rows, 'should have deleted rows equal to count'
333+
inserted_rows = @client.do("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')")
334+
assert_equal 1, inserted_rows, 'should have inserted row for one above'
335+
updated_rows = @client.do("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'")
336+
assert_equal 1, updated_rows, 'should have updated row for one above'
337+
end
338+
end
339+
340+
it 'allows native affected rows using #do to work under transaction' do
341+
rollback_transaction(@client) do
342+
text = 'test affected rows native in transaction'
343+
@client.do("BEGIN TRANSACTION")
344+
@client.do("DELETE FROM [datatypes]")
345+
inserted_rows = @client.do("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')")
346+
assert_equal 1, inserted_rows, 'should have inserted row for one above'
347+
updated_rows = @client.do("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'")
348+
assert_equal 1, updated_rows, 'should have updated row for one above'
349+
end
350+
end
351+
end
321352
end

0 commit comments

Comments
 (0)