@@ -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" ;
@@ -99,6 +109,16 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
99109 break ;
100110 }
101111
112+ tinytds_errordata error_data = {
113+ .is_message = 0 ,
114+ .cancel = cancel ,
115+ .severity = severity ,
116+ .dberr = dberr ,
117+ .oserr = oserr
118+ };
119+ strncpy (error_data .error , dberrstr , ERROR_MSG_SIZE );
120+ strncpy (error_data .source , source , ERROR_MSG_SIZE );
121+
102122 /*
103123 When in non-blocking mode we need to store the exception data to throw it
104124 once the blocking call returns, otherwise we will segfault ruby since part
@@ -110,27 +130,9 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
110130 dbcancel (dbproc );
111131 userdata -> dbcancel_sent = 1 ;
112132 }
113-
114- /*
115- If we've already captured an error message, don't overwrite it. This is
116- here because FreeTDS sends a generic "General SQL Server error" message
117- that will overwrite the real message. This is not normally a problem
118- because a ruby exception is normally thrown and we bail before the
119- generic message can be sent.
120- */
121- if (!userdata -> nonblocking_error .is_set ) {
122- userdata -> nonblocking_error .is_message = 0 ;
123- userdata -> nonblocking_error .cancel = cancel ;
124- strncpy (userdata -> nonblocking_error .error , dberrstr , ERROR_MSG_SIZE );
125- strncpy (userdata -> nonblocking_error .source , source , ERROR_MSG_SIZE );
126- userdata -> nonblocking_error .severity = severity ;
127- userdata -> nonblocking_error .dberr = dberr ;
128- userdata -> nonblocking_error .oserr = oserr ;
129- userdata -> nonblocking_error .is_set = 1 ;
130- }
131-
133+ push_userdata_error (userdata , error_data );
132134 } else {
133- rb_tinytds_raise_error (dbproc , 0 , cancel , dberrstr , source , severity , dberr , oserr );
135+ rb_tinytds_raise_error (dbproc , error_data );
134136 }
135137
136138 return return_value ;
@@ -142,25 +144,31 @@ int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severi
142144
143145 int is_message_an_error = severity > 10 ? 1 : 0 ;
144146
147+ tinytds_errordata error_data = {
148+ .is_message = !is_message_an_error ,
149+ .cancel = is_message_an_error ,
150+ .severity = severity ,
151+ .dberr = msgno ,
152+ .oserr = msgstate
153+ };
154+ strncpy (error_data .error , msgtext , ERROR_MSG_SIZE );
155+ strncpy (error_data .source , source , ERROR_MSG_SIZE );
156+
145157 // See tinytds_err_handler() for info about why we do this
146158 if (userdata && userdata -> nonblocking ) {
147- if (!userdata -> nonblocking_error .is_set ) {
148- userdata -> nonblocking_error .is_message = !is_message_an_error ;
149- userdata -> nonblocking_error .cancel = is_message_an_error ;
150- strncpy (userdata -> nonblocking_error .error , msgtext , ERROR_MSG_SIZE );
151- strncpy (userdata -> nonblocking_error .source , source , ERROR_MSG_SIZE );
152- userdata -> nonblocking_error .severity = severity ;
153- userdata -> nonblocking_error .dberr = msgno ;
154- userdata -> nonblocking_error .oserr = msgstate ;
155- userdata -> nonblocking_error .is_set = 1 ;
156- }
159+ /*
160+ In the case of non-blocking command batch execution we can receive multiple messages
161+ (including errors). We keep track of those here so they can be processed once the
162+ non-blocking call returns.
163+ */
164+ push_userdata_error (userdata , error_data );
157165
158166 if (is_message_an_error && !dbdead (dbproc ) && !userdata -> closed ) {
159167 dbcancel (dbproc );
160168 userdata -> dbcancel_sent = 1 ;
161169 }
162170 } else {
163- rb_tinytds_raise_error (dbproc , ! is_message_an_error , is_message_an_error , msgtext , source , severity , msgno , msgstate );
171+ rb_tinytds_raise_error (dbproc , error_data );
164172 }
165173 return 0 ;
166174}
@@ -171,7 +179,10 @@ static void rb_tinytds_client_reset_userdata(tinytds_client_userdata *userdata)
171179 userdata -> dbsqlok_sent = 0 ;
172180 userdata -> dbcancel_sent = 0 ;
173181 userdata -> nonblocking = 0 ;
174- userdata -> nonblocking_error .is_set = 0 ;
182+ // the following is mainly done for consistency since the values are reset accordingly in nogvl_setup/cleanup.
183+ // the nonblocking_errors array does not need to be freed here. That is done as part of nogvl_cleanup.
184+ userdata -> nonblocking_errors_length = 0 ;
185+ userdata -> nonblocking_errors_size = 0 ;
175186}
176187
177188static void rb_tinytds_client_mark (void * ptr ) {
0 commit comments