@@ -15,12 +15,6 @@ VALUE opt_escape_regex, opt_escape_dblquote;
15
15
tinytds_client_wrapper *cwrap; \
16
16
Data_Get_Struct(self, tinytds_client_wrapper, cwrap)
17
17
18
- #define REQUIRE_OPEN_CLIENT (cwrap ) \
19
- if (cwrap->closed || cwrap->userdata->closed) { \
20
- rb_raise(cTinyTdsError, "closed connection"); \
21
- return Qnil; \
22
- }
23
-
24
18
25
19
// Lib Backend (Helpers)
26
20
@@ -55,6 +49,128 @@ VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, tinytds_errordata error) {
55
49
return Qnil ;
56
50
}
57
51
52
+ static void rb_tinytds_client_reset_userdata (tinytds_client_userdata * userdata ) {
53
+ userdata -> timing_out = 0 ;
54
+ userdata -> dbsql_sent = 0 ;
55
+ userdata -> dbsqlok_sent = 0 ;
56
+ userdata -> dbcancel_sent = 0 ;
57
+ userdata -> nonblocking = 0 ;
58
+ // the following is mainly done for consistency since the values are reset accordingly in nogvl_setup/cleanup.
59
+ // the nonblocking_errors array does not need to be freed here. That is done as part of nogvl_cleanup.
60
+ userdata -> nonblocking_errors_length = 0 ;
61
+ userdata -> nonblocking_errors_size = 0 ;
62
+ }
63
+
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
+
80
+ // code part used to invoke FreeTDS functions with releasing the Ruby GVL
81
+ // basically, while FreeTDS is interacting with the SQL server, other Ruby code can be executed
82
+ #define NOGVL_DBCALL (_dbfunction , _client ) ( \
83
+ (RETCODE)(intptr_t)rb_thread_call_without_gvl( \
84
+ (void *(*)(void *))_dbfunction, _client, \
85
+ (rb_unblock_function_t*)dbcancel_ubf, _client ) \
86
+ )
87
+
88
+ static void dbcancel_ubf (DBPROCESS * client ) {
89
+ GET_CLIENT_USERDATA (client );
90
+ dbcancel (client );
91
+ userdata -> dbcancel_sent = 1 ;
92
+ }
93
+
94
+ static void nogvl_setup (DBPROCESS * client ) {
95
+ GET_CLIENT_USERDATA (client );
96
+ userdata -> nonblocking = 1 ;
97
+ userdata -> nonblocking_errors_length = 0 ;
98
+ userdata -> nonblocking_errors = malloc (ERRORS_STACK_INIT_SIZE * sizeof (tinytds_errordata ));
99
+ userdata -> nonblocking_errors_size = ERRORS_STACK_INIT_SIZE ;
100
+ }
101
+
102
+ static void nogvl_cleanup (DBPROCESS * client ) {
103
+ GET_CLIENT_USERDATA (client );
104
+ userdata -> nonblocking = 0 ;
105
+ userdata -> timing_out = 0 ;
106
+ /*
107
+ Now that the blocking operation is done, we can finally throw any
108
+ exceptions based on errors from SQL Server.
109
+ */
110
+ short int i ;
111
+ for (i = 0 ; i < userdata -> nonblocking_errors_length ; i ++ ) {
112
+ tinytds_errordata error = userdata -> nonblocking_errors [i ];
113
+
114
+ // lookahead to drain any info messages ahead of raising error
115
+ if (!error .is_message ) {
116
+ short int j ;
117
+ for (j = i ; j < userdata -> nonblocking_errors_length ; j ++ ) {
118
+ tinytds_errordata msg_error = userdata -> nonblocking_errors [j ];
119
+ if (msg_error .is_message ) {
120
+ rb_tinytds_raise_error (client , msg_error );
121
+ }
122
+ }
123
+ }
124
+
125
+ rb_tinytds_raise_error (client , error );
126
+ }
127
+
128
+ free (userdata -> nonblocking_errors );
129
+ userdata -> nonblocking_errors_length = 0 ;
130
+ userdata -> nonblocking_errors_size = 0 ;
131
+ }
132
+
133
+ static RETCODE nogvl_dbnextrow (DBPROCESS * client ) {
134
+ int retcode = FAIL ;
135
+ nogvl_setup (client );
136
+ retcode = NOGVL_DBCALL (dbnextrow , client );
137
+ nogvl_cleanup (client );
138
+ return retcode ;
139
+ }
140
+
141
+ static RETCODE nogvl_dbresults (DBPROCESS * client ) {
142
+ int retcode = FAIL ;
143
+ nogvl_setup (client );
144
+ retcode = NOGVL_DBCALL (dbresults , client );
145
+ nogvl_cleanup (client );
146
+ return retcode ;
147
+ }
148
+
149
+ static RETCODE nogvl_dbsqlexec (DBPROCESS * client ) {
150
+ int retcode = FAIL ;
151
+ nogvl_setup (client );
152
+ retcode = NOGVL_DBCALL (dbsqlexec , client );
153
+ nogvl_cleanup (client );
154
+ return retcode ;
155
+ }
156
+
157
+ static RETCODE nogvl_dbsqlok (DBPROCESS * client ) {
158
+ int retcode = FAIL ;
159
+ GET_CLIENT_USERDATA (client );
160
+ nogvl_setup (client );
161
+ retcode = NOGVL_DBCALL (dbsqlok , client );
162
+ nogvl_cleanup (client );
163
+ userdata -> dbsqlok_sent = 1 ;
164
+ return retcode ;
165
+ }
166
+
167
+ static RETCODE rb_tinytds_result_ok_helper (DBPROCESS * client ) {
168
+ GET_CLIENT_USERDATA (client );
169
+ if (userdata -> dbsqlok_sent == 0 ) {
170
+ userdata -> dbsqlok_retcode = nogvl_dbsqlok (client );
171
+ }
172
+ return userdata -> dbsqlok_retcode ;
173
+ }
58
174
59
175
// Lib Backend (Memory Management & Handlers)
60
176
static void push_userdata_error (tinytds_client_userdata * userdata , tinytds_errordata error ) {
@@ -207,18 +323,6 @@ static int handle_interrupt(void *ptr) {
207
323
return INT_CONTINUE ;
208
324
}
209
325
210
- static void rb_tinytds_client_reset_userdata (tinytds_client_userdata * userdata ) {
211
- userdata -> timing_out = 0 ;
212
- userdata -> dbsql_sent = 0 ;
213
- userdata -> dbsqlok_sent = 0 ;
214
- userdata -> dbcancel_sent = 0 ;
215
- userdata -> nonblocking = 0 ;
216
- // the following is mainly done for consistency since the values are reset accordingly in nogvl_setup/cleanup.
217
- // the nonblocking_errors array does not need to be freed here. That is done as part of nogvl_cleanup.
218
- userdata -> nonblocking_errors_length = 0 ;
219
- userdata -> nonblocking_errors_size = 0 ;
220
- }
221
-
222
326
static void rb_tinytds_client_mark (void * ptr ) {
223
327
tinytds_client_wrapper * cwrap = (tinytds_client_wrapper * )ptr ;
224
328
if (cwrap ) {
@@ -295,13 +399,7 @@ static VALUE rb_tinytds_execute(VALUE self, VALUE sql) {
295
399
VALUE result ;
296
400
297
401
GET_CLIENT_WRAPPER (self );
298
- rb_tinytds_client_reset_userdata (cwrap -> userdata );
299
- REQUIRE_OPEN_CLIENT (cwrap );
300
- dbcmd (cwrap -> client , StringValueCStr (sql ));
301
- if (dbsqlsend (cwrap -> client ) == FAIL ) {
302
- rb_raise (cTinyTdsError , "failed dbsqlsend() function" );
303
- }
304
- cwrap -> userdata -> dbsql_sent = 1 ;
402
+ rb_tinytds_send_sql_to_server (cwrap , sql );
305
403
result = rb_tinytds_new_result_obj (cwrap );
306
404
rb_iv_set (result , "@query_options" , rb_funcall (rb_iv_get (self , "@query_options" ), intern_dup , 0 ));
307
405
{
@@ -312,6 +410,55 @@ static VALUE rb_tinytds_execute(VALUE self, VALUE sql) {
312
410
}
313
411
}
314
412
413
+ static VALUE rb_tiny_tds_insert (VALUE self , VALUE sql ) {
414
+ VALUE identity = Qnil ;
415
+ GET_CLIENT_WRAPPER (self );
416
+ 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
+ }
434
+
435
+ dbcancel (cwrap -> client );
436
+ cwrap -> userdata -> dbcancel_sent = 1 ;
437
+ cwrap -> userdata -> dbsql_sent = 0 ;
438
+
439
+ // prepare second query to fetch last identity
440
+ dbcmd (cwrap -> client , cwrap -> identity_insert_sql );
441
+
442
+ if (
443
+ nogvl_dbsqlexec (cwrap -> client ) != FAIL
444
+ && nogvl_dbresults (cwrap -> client ) != FAIL
445
+ && DBROWS (cwrap -> client ) != FAIL
446
+ ) {
447
+ while (nogvl_dbnextrow (cwrap -> client ) != NO_MORE_ROWS ) {
448
+ int col = 1 ;
449
+ BYTE * data = dbdata (cwrap -> client , col );
450
+ DBINT data_len = dbdatlen (cwrap -> client , col );
451
+ int null_val = ((data == NULL ) && (data_len == 0 ));
452
+
453
+ if (!null_val ) {
454
+ identity = LL2NUM (* (DBBIGINT * )data );
455
+ }
456
+ }
457
+ }
458
+
459
+ return identity ;
460
+ }
461
+
315
462
static VALUE rb_tinytds_charset (VALUE self ) {
316
463
GET_CLIENT_WRAPPER (self );
317
464
return cwrap -> charset ;
@@ -451,6 +598,7 @@ void init_tinytds_client() {
451
598
rb_define_method (cTinyTdsClient , "dead?" , rb_tinytds_dead , 0 );
452
599
rb_define_method (cTinyTdsClient , "sqlsent?" , rb_tinytds_sqlsent , 0 );
453
600
rb_define_method (cTinyTdsClient , "execute" , rb_tinytds_execute , 1 );
601
+ rb_define_method (cTinyTdsClient , "insert" , rb_tiny_tds_insert , 1 );
454
602
rb_define_method (cTinyTdsClient , "charset" , rb_tinytds_charset , 0 );
455
603
rb_define_method (cTinyTdsClient , "encoding" , rb_tinytds_encoding , 0 );
456
604
rb_define_method (cTinyTdsClient , "escape" , rb_tinytds_escape , 1 );
0 commit comments