@@ -24,25 +24,25 @@ VALUE opt_escape_regex, opt_escape_dblquote;
2424
2525// Lib Backend (Helpers)
2626
27- VALUE rb_tinytds_raise_error (DBPROCESS * dbproc , int is_message , int cancel , const char * error , const char * source , int severity , int dberr , int oserr ) {
27+ VALUE rb_tinytds_raise_error (DBPROCESS * dbproc , tinytds_errordata error ) {
2828 VALUE e ;
2929 GET_CLIENT_USERDATA (dbproc );
30- if (cancel && !dbdead (dbproc ) && userdata && !userdata -> closed ) {
30+ if (error . cancel && !dbdead (dbproc ) && userdata && !userdata -> closed ) {
3131 userdata -> dbsqlok_sent = 1 ;
3232 dbsqlok (dbproc );
3333 userdata -> dbcancel_sent = 1 ;
3434 dbcancel (dbproc );
3535 }
36- e = rb_exc_new2 (cTinyTdsError , error );
37- rb_funcall (e , intern_source_eql , 1 , rb_str_new2 (source ));
38- if (severity )
39- rb_funcall (e , intern_severity_eql , 1 , INT2FIX (severity ));
40- if (dberr )
41- rb_funcall (e , intern_db_error_number_eql , 1 , INT2FIX (dberr ));
42- if (oserr )
43- rb_funcall (e , intern_os_error_number_eql , 1 , INT2FIX (oserr ));
44-
45- if (severity <= 10 && is_message ) {
36+ e = rb_exc_new2 (cTinyTdsError , error . error );
37+ rb_funcall (e , intern_source_eql , 1 , rb_str_new2 (error . source ));
38+ if (error . severity )
39+ rb_funcall (e , intern_severity_eql , 1 , INT2FIX (error . severity ));
40+ if (error . dberr )
41+ rb_funcall (e , intern_db_error_number_eql , 1 , INT2FIX (error . dberr ));
42+ if (error . oserr )
43+ rb_funcall (e , intern_os_error_number_eql , 1 , INT2FIX (error . oserr ));
44+
45+ if (error . severity <= 10 && error . is_message ) {
4646 VALUE message_handler = userdata && userdata -> message_handler ? userdata -> message_handler : Qnil ;
4747 if (message_handler && message_handler != Qnil && rb_respond_to (message_handler , intern_call ) != 0 ) {
4848 rb_funcall (message_handler , intern_call , 1 , e );
@@ -57,6 +57,16 @@ VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, int is_message, int cancel, cons
5757
5858
5959// Lib Backend (Memory Management & Handlers)
60+ static void push_userdata_error (tinytds_client_userdata * userdata , tinytds_errordata error ) {
61+ // reallocate memory for the array as needed
62+ if (userdata -> nonblocking_errors_size == userdata -> nonblocking_errors_length ) {
63+ userdata -> nonblocking_errors_size *= 2 ;
64+ userdata -> nonblocking_errors = realloc (userdata -> nonblocking_errors , userdata -> nonblocking_errors_size * sizeof (tinytds_errordata ));
65+ }
66+
67+ userdata -> nonblocking_errors [userdata -> nonblocking_errors_length ] = error ;
68+ userdata -> nonblocking_errors_length ++ ;
69+ }
6070
6171int tinytds_err_handler (DBPROCESS * dbproc , int severity , int dberr , int oserr , char * dberrstr , char * oserrstr ) {
6272 static const char * source = "error" ;
@@ -105,6 +115,16 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
105115 break ;
106116 }
107117
118+ tinytds_errordata error_data = {
119+ .is_message = 0 ,
120+ .cancel = cancel ,
121+ .severity = severity ,
122+ .dberr = dberr ,
123+ .oserr = oserr
124+ };
125+ strncpy (error_data .error , dberrstr , ERROR_MSG_SIZE );
126+ strncpy (error_data .source , source , ERROR_MSG_SIZE );
127+
108128 /*
109129 When in non-blocking mode we need to store the exception data to throw it
110130 once the blocking call returns, otherwise we will segfault ruby since part
@@ -116,27 +136,9 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
116136 dbcancel (dbproc );
117137 userdata -> dbcancel_sent = 1 ;
118138 }
119-
120- /*
121- If we've already captured an error message, don't overwrite it. This is
122- here because FreeTDS sends a generic "General SQL Server error" message
123- that will overwrite the real message. This is not normally a problem
124- because a ruby exception is normally thrown and we bail before the
125- generic message can be sent.
126- */
127- if (!userdata -> nonblocking_error .is_set ) {
128- userdata -> nonblocking_error .is_message = 0 ;
129- userdata -> nonblocking_error .cancel = cancel ;
130- strncpy (userdata -> nonblocking_error .error , dberrstr , ERROR_MSG_SIZE );
131- strncpy (userdata -> nonblocking_error .source , source , ERROR_MSG_SIZE );
132- userdata -> nonblocking_error .severity = severity ;
133- userdata -> nonblocking_error .dberr = dberr ;
134- userdata -> nonblocking_error .oserr = oserr ;
135- userdata -> nonblocking_error .is_set = 1 ;
136- }
137-
139+ push_userdata_error (userdata , error_data );
138140 } else {
139- rb_tinytds_raise_error (dbproc , 0 , cancel , dberrstr , source , severity , dberr , oserr );
141+ rb_tinytds_raise_error (dbproc , error_data );
140142 }
141143
142144 return return_value ;
@@ -148,25 +150,31 @@ int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severi
148150
149151 int is_message_an_error = severity > 10 ? 1 : 0 ;
150152
153+ tinytds_errordata error_data = {
154+ .is_message = !is_message_an_error ,
155+ .cancel = is_message_an_error ,
156+ .severity = severity ,
157+ .dberr = msgno ,
158+ .oserr = msgstate
159+ };
160+ strncpy (error_data .error , msgtext , ERROR_MSG_SIZE );
161+ strncpy (error_data .source , source , ERROR_MSG_SIZE );
162+
151163 // See tinytds_err_handler() for info about why we do this
152164 if (userdata && userdata -> nonblocking ) {
153- if (!userdata -> nonblocking_error .is_set ) {
154- userdata -> nonblocking_error .is_message = !is_message_an_error ;
155- userdata -> nonblocking_error .cancel = is_message_an_error ;
156- strncpy (userdata -> nonblocking_error .error , msgtext , ERROR_MSG_SIZE );
157- strncpy (userdata -> nonblocking_error .source , source , ERROR_MSG_SIZE );
158- userdata -> nonblocking_error .severity = severity ;
159- userdata -> nonblocking_error .dberr = msgno ;
160- userdata -> nonblocking_error .oserr = msgstate ;
161- userdata -> nonblocking_error .is_set = 1 ;
162- }
165+ /*
166+ In the case of non-blocking command batch execution we can receive multiple messages
167+ (including errors). We keep track of those here so they can be processed once the
168+ non-blocking call returns.
169+ */
170+ push_userdata_error (userdata , error_data );
163171
164172 if (is_message_an_error && !dbdead (dbproc ) && !userdata -> closed ) {
165173 dbcancel (dbproc );
166174 userdata -> dbcancel_sent = 1 ;
167175 }
168176 } else {
169- rb_tinytds_raise_error (dbproc , ! is_message_an_error , is_message_an_error , msgtext , source , severity , msgno , msgstate );
177+ rb_tinytds_raise_error (dbproc , error_data );
170178 }
171179 return 0 ;
172180}
@@ -204,7 +212,10 @@ static void rb_tinytds_client_reset_userdata(tinytds_client_userdata *userdata)
204212 userdata -> dbsqlok_sent = 0 ;
205213 userdata -> dbcancel_sent = 0 ;
206214 userdata -> nonblocking = 0 ;
207- userdata -> nonblocking_error .is_set = 0 ;
215+ // the following is mainly done for consistency since the values are reset accordingly in nogvl_setup/cleanup.
216+ // the nonblocking_errors array does not need to be freed here. That is done as part of nogvl_cleanup.
217+ userdata -> nonblocking_errors_length = 0 ;
218+ userdata -> nonblocking_errors_size = 0 ;
208219}
209220
210221static void rb_tinytds_client_mark (void * ptr ) {
0 commit comments