tiny_tds 1.0.4 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.codeclimate.yml +20 -0
- data/.gitattributes +1 -0
- data/.rubocop.yml +31 -0
- data/.travis.yml +25 -0
- data/{CHANGELOG → CHANGELOG.md} +102 -26
- data/Gemfile +4 -1
- data/ISSUE_TEMPLATE.md +35 -2
- data/README.md +131 -56
- data/Rakefile +31 -88
- data/VERSION +1 -1
- data/appveyor.yml +38 -17
- data/docker-compose.yml +22 -0
- data/ext/tiny_tds/client.c +147 -60
- data/ext/tiny_tds/client.h +11 -5
- data/ext/tiny_tds/extconf.rb +41 -297
- data/ext/tiny_tds/extconsts.rb +7 -7
- data/ext/tiny_tds/result.c +40 -15
- data/lib/tiny_tds/bin.rb +45 -27
- data/lib/tiny_tds/client.rb +46 -34
- data/lib/tiny_tds/error.rb +0 -1
- data/lib/tiny_tds/gem.rb +32 -0
- data/lib/tiny_tds/result.rb +2 -3
- data/lib/tiny_tds/version.rb +1 -1
- data/lib/tiny_tds.rb +38 -14
- data/{ports/patches/freetds/1.00 → patches/freetds/1.00.27}/0001-mingw_missing_inet_pton.diff +4 -4
- data/patches/freetds/1.00.27/0002-Don-t-use-MSYS2-file-libws2_32.diff +28 -0
- data/patches/libiconv/1.14/1-avoid-gets-error.patch +17 -0
- data/tasks/native_gem.rake +14 -0
- data/tasks/package.rake +8 -0
- data/tasks/ports/freetds.rb +37 -0
- data/tasks/ports/libiconv.rb +43 -0
- data/tasks/ports/openssl.rb +62 -0
- data/tasks/ports/recipe.rb +52 -0
- data/tasks/ports.rake +85 -0
- data/tasks/test.rake +9 -0
- data/test/appveyor/dbsetup.ps1 +1 -1
- data/test/bin/install-freetds.sh +20 -0
- data/test/bin/install-openssl.sh +18 -0
- data/test/bin/setup.sh +19 -0
- data/test/client_test.rb +124 -66
- data/test/gem_test.rb +179 -0
- data/test/result_test.rb +128 -42
- data/test/schema/sqlserver_2016.sql +140 -0
- data/test/schema_test.rb +23 -23
- data/test/test_helper.rb +65 -7
- data/test/thread_test.rb +1 -1
- data/tiny_tds.gemspec +9 -7
- metadata +60 -20
- /data/bin/{defncopy → defncopy-ttds} +0 -0
- /data/bin/{tsql → tsql-ttds} +0 -0
data/ext/tiny_tds/client.c
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
VALUE cTinyTdsClient;
|
|
5
5
|
extern VALUE mTinyTds, cTinyTdsError;
|
|
6
|
-
static ID sym_username, sym_password, sym_dataserver, sym_database, sym_appname, sym_tds_version, sym_login_timeout, sym_timeout, sym_encoding, sym_azure;
|
|
6
|
+
static ID sym_username, sym_password, sym_dataserver, sym_database, sym_appname, sym_tds_version, sym_login_timeout, sym_timeout, sym_encoding, sym_azure, sym_contained, sym_use_utf16, sym_message_handler;
|
|
7
7
|
static ID intern_source_eql, intern_severity_eql, intern_db_error_number_eql, intern_os_error_number_eql;
|
|
8
|
-
static ID intern_new, intern_dup, intern_transpose_iconv_encoding, intern_local_offset, intern_gsub;
|
|
8
|
+
static ID intern_new, intern_dup, intern_transpose_iconv_encoding, intern_local_offset, intern_gsub, intern_call;
|
|
9
9
|
VALUE opt_escape_regex, opt_escape_dblquote;
|
|
10
10
|
|
|
11
11
|
|
|
@@ -24,29 +24,49 @@ VALUE opt_escape_regex, opt_escape_dblquote;
|
|
|
24
24
|
|
|
25
25
|
// Lib Backend (Helpers)
|
|
26
26
|
|
|
27
|
-
VALUE rb_tinytds_raise_error(DBPROCESS *dbproc,
|
|
27
|
+
VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, tinytds_errordata error) {
|
|
28
28
|
VALUE e;
|
|
29
29
|
GET_CLIENT_USERDATA(dbproc);
|
|
30
|
-
if (cancel && !dbdead(dbproc) && userdata && !userdata->closed) {
|
|
30
|
+
if (error.cancel && !dbdead(dbproc) && userdata && !userdata->closed) {
|
|
31
31
|
userdata->dbsqlok_sent = 1;
|
|
32
32
|
dbsqlok(dbproc);
|
|
33
33
|
userdata->dbcancel_sent = 1;
|
|
34
34
|
dbcancel(dbproc);
|
|
35
35
|
}
|
|
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));
|
|
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) {
|
|
46
|
+
VALUE message_handler = userdata && userdata->message_handler ? userdata->message_handler : Qnil;
|
|
47
|
+
if (message_handler && message_handler != Qnil && rb_respond_to(message_handler, intern_call) != 0) {
|
|
48
|
+
rb_funcall(message_handler, intern_call, 1, e);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return Qnil;
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
rb_exc_raise(e);
|
|
45
55
|
return Qnil;
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
|
|
49
59
|
// 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
|
+
}
|
|
50
70
|
|
|
51
71
|
int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) {
|
|
52
72
|
static const char *source = "error";
|
|
@@ -76,6 +96,13 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
|
|
|
76
96
|
but we don't ever want to automatically retry. Instead have the app
|
|
77
97
|
decide what to do.
|
|
78
98
|
*/
|
|
99
|
+
if (userdata && userdata->timing_out) {
|
|
100
|
+
return INT_CANCEL;
|
|
101
|
+
}
|
|
102
|
+
// userdata will not be set if hitting timeout during login so check for it first
|
|
103
|
+
if (userdata) {
|
|
104
|
+
userdata->timing_out = 1;
|
|
105
|
+
}
|
|
79
106
|
return_value = INT_TIMEOUT;
|
|
80
107
|
cancel = 1;
|
|
81
108
|
break;
|
|
@@ -89,6 +116,16 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
|
|
|
89
116
|
break;
|
|
90
117
|
}
|
|
91
118
|
|
|
119
|
+
tinytds_errordata error_data = {
|
|
120
|
+
.is_message = 0,
|
|
121
|
+
.cancel = cancel,
|
|
122
|
+
.severity = severity,
|
|
123
|
+
.dberr = dberr,
|
|
124
|
+
.oserr = oserr
|
|
125
|
+
};
|
|
126
|
+
strncpy(error_data.error, dberrstr, ERROR_MSG_SIZE);
|
|
127
|
+
strncpy(error_data.source, source, ERROR_MSG_SIZE);
|
|
128
|
+
|
|
92
129
|
/*
|
|
93
130
|
When in non-blocking mode we need to store the exception data to throw it
|
|
94
131
|
once the blocking call returns, otherwise we will segfault ruby since part
|
|
@@ -100,26 +137,9 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
|
|
|
100
137
|
dbcancel(dbproc);
|
|
101
138
|
userdata->dbcancel_sent = 1;
|
|
102
139
|
}
|
|
103
|
-
|
|
104
|
-
/*
|
|
105
|
-
If we've already captured an error message, don't overwrite it. This is
|
|
106
|
-
here because FreeTDS sends a generic "General SQL Server error" message
|
|
107
|
-
that will overwrite the real message. This is not normally a problem
|
|
108
|
-
because a ruby exception is normally thrown and we bail before the
|
|
109
|
-
generic message can be sent.
|
|
110
|
-
*/
|
|
111
|
-
if (!userdata->nonblocking_error.is_set) {
|
|
112
|
-
userdata->nonblocking_error.cancel = cancel;
|
|
113
|
-
strcpy(userdata->nonblocking_error.error, dberrstr);
|
|
114
|
-
strcpy(userdata->nonblocking_error.source, source);
|
|
115
|
-
userdata->nonblocking_error.severity = severity;
|
|
116
|
-
userdata->nonblocking_error.dberr = dberr;
|
|
117
|
-
userdata->nonblocking_error.oserr = oserr;
|
|
118
|
-
userdata->nonblocking_error.is_set = 1;
|
|
119
|
-
}
|
|
120
|
-
|
|
140
|
+
push_userdata_error(userdata, error_data);
|
|
121
141
|
} else {
|
|
122
|
-
rb_tinytds_raise_error(dbproc,
|
|
142
|
+
rb_tinytds_raise_error(dbproc, error_data);
|
|
123
143
|
}
|
|
124
144
|
|
|
125
145
|
return return_value;
|
|
@@ -128,36 +148,75 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
|
|
|
128
148
|
int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line) {
|
|
129
149
|
static const char *source = "message";
|
|
130
150
|
GET_CLIENT_USERDATA(dbproc);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
|
|
152
|
+
int is_message_an_error = severity > 10 ? 1 : 0;
|
|
153
|
+
|
|
154
|
+
tinytds_errordata error_data = {
|
|
155
|
+
.is_message = !is_message_an_error,
|
|
156
|
+
.cancel = is_message_an_error,
|
|
157
|
+
.severity = severity,
|
|
158
|
+
.dberr = msgno,
|
|
159
|
+
.oserr = msgstate
|
|
160
|
+
};
|
|
161
|
+
strncpy(error_data.error, msgtext, ERROR_MSG_SIZE);
|
|
162
|
+
strncpy(error_data.source, source, ERROR_MSG_SIZE);
|
|
163
|
+
|
|
164
|
+
// See tinytds_err_handler() for info about why we do this
|
|
165
|
+
if (userdata && userdata->nonblocking) {
|
|
166
|
+
/*
|
|
167
|
+
In the case of non-blocking command batch execution we can receive multiple messages
|
|
168
|
+
(including errors). We keep track of those here so they can be processed once the
|
|
169
|
+
non-blocking call returns.
|
|
170
|
+
*/
|
|
171
|
+
push_userdata_error(userdata, error_data);
|
|
172
|
+
|
|
173
|
+
if (is_message_an_error && !dbdead(dbproc) && !userdata->closed) {
|
|
174
|
+
dbcancel(dbproc);
|
|
175
|
+
userdata->dbcancel_sent = 1;
|
|
149
176
|
}
|
|
177
|
+
} else {
|
|
178
|
+
rb_tinytds_raise_error(dbproc, error_data);
|
|
150
179
|
}
|
|
151
180
|
return 0;
|
|
152
181
|
}
|
|
153
182
|
|
|
183
|
+
/*
|
|
184
|
+
Used by dbsetinterrupt -
|
|
185
|
+
This gets called periodically while waiting on a read from the server
|
|
186
|
+
Right now, we only care about cases where a read from the server is
|
|
187
|
+
taking longer than the specified timeout and dbcancel is not working.
|
|
188
|
+
In these cases we decide that we actually want to handle the interrupt
|
|
189
|
+
*/
|
|
190
|
+
static int check_interrupt(void *ptr) {
|
|
191
|
+
GET_CLIENT_USERDATA((DBPROCESS *)ptr);
|
|
192
|
+
return userdata->timing_out;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
Used by dbsetinterrupt -
|
|
197
|
+
This gets called if check_interrupt returns TRUE.
|
|
198
|
+
Right now, this is only used in cases where a read from the server is
|
|
199
|
+
taking longer than the specified timeout and dbcancel is not working.
|
|
200
|
+
Return INT_CANCEL to abort the current command batch.
|
|
201
|
+
*/
|
|
202
|
+
static int handle_interrupt(void *ptr) {
|
|
203
|
+
GET_CLIENT_USERDATA((DBPROCESS *)ptr);
|
|
204
|
+
if (userdata->timing_out) {
|
|
205
|
+
return INT_CANCEL;
|
|
206
|
+
}
|
|
207
|
+
return INT_CONTINUE;
|
|
208
|
+
}
|
|
209
|
+
|
|
154
210
|
static void rb_tinytds_client_reset_userdata(tinytds_client_userdata *userdata) {
|
|
155
211
|
userdata->timing_out = 0;
|
|
156
212
|
userdata->dbsql_sent = 0;
|
|
157
213
|
userdata->dbsqlok_sent = 0;
|
|
158
214
|
userdata->dbcancel_sent = 0;
|
|
159
215
|
userdata->nonblocking = 0;
|
|
160
|
-
|
|
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;
|
|
161
220
|
}
|
|
162
221
|
|
|
163
222
|
static void rb_tinytds_client_mark(void *ptr) {
|
|
@@ -293,7 +352,7 @@ static VALUE rb_tinytds_identity_sql(VALUE self) {
|
|
|
293
352
|
|
|
294
353
|
static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
|
|
295
354
|
/* Parsing options hash to local vars. */
|
|
296
|
-
VALUE user, pass, dataserver, database, app, version, ltimeout, timeout, charset, azure;
|
|
355
|
+
VALUE user, pass, dataserver, database, app, version, ltimeout, timeout, charset, azure, contained, use_utf16;
|
|
297
356
|
GET_CLIENT_WRAPPER(self);
|
|
298
357
|
|
|
299
358
|
user = rb_hash_aref(opts, sym_username);
|
|
@@ -306,6 +365,9 @@ static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
|
|
|
306
365
|
timeout = rb_hash_aref(opts, sym_timeout);
|
|
307
366
|
charset = rb_hash_aref(opts, sym_encoding);
|
|
308
367
|
azure = rb_hash_aref(opts, sym_azure);
|
|
368
|
+
contained = rb_hash_aref(opts, sym_contained);
|
|
369
|
+
use_utf16 = rb_hash_aref(opts, sym_use_utf16);
|
|
370
|
+
cwrap->userdata->message_handler = rb_hash_aref(opts, sym_message_handler);
|
|
309
371
|
/* Dealing with options. */
|
|
310
372
|
if (dbinit() == FAIL) {
|
|
311
373
|
rb_raise(cTinyTdsError, "failed dbinit() function");
|
|
@@ -324,26 +386,47 @@ static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
|
|
|
324
386
|
dbsetlapp(cwrap->login, StringValueCStr(app));
|
|
325
387
|
if (!NIL_P(ltimeout))
|
|
326
388
|
dbsetlogintime(NUM2INT(ltimeout));
|
|
327
|
-
if (!NIL_P(timeout))
|
|
328
|
-
dbsettime(NUM2INT(timeout));
|
|
329
389
|
if (!NIL_P(charset))
|
|
330
390
|
DBSETLCHARSET(cwrap->login, StringValueCStr(charset));
|
|
331
|
-
if (!NIL_P(database)
|
|
332
|
-
|
|
333
|
-
DBSETLDBNAME
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
391
|
+
if (!NIL_P(database)) {
|
|
392
|
+
if (azure == Qtrue || contained == Qtrue) {
|
|
393
|
+
#ifdef DBSETLDBNAME
|
|
394
|
+
DBSETLDBNAME(cwrap->login, StringValueCStr(database));
|
|
395
|
+
#else
|
|
396
|
+
if (azure == Qtrue) {
|
|
397
|
+
rb_warn("TinyTds: :azure option is not supported in this version of FreeTDS.\n");
|
|
398
|
+
}
|
|
399
|
+
if (contained == Qtrue) {
|
|
400
|
+
rb_warn("TinyTds: :contained option is not supported in this version of FreeTDS.\n");
|
|
401
|
+
}
|
|
402
|
+
#endif
|
|
403
|
+
}
|
|
337
404
|
}
|
|
405
|
+
#ifdef DBSETUTF16
|
|
406
|
+
if (use_utf16 == Qtrue) { DBSETLUTF16(cwrap->login, 1); }
|
|
407
|
+
if (use_utf16 == Qfalse) { DBSETLUTF16(cwrap->login, 0); }
|
|
408
|
+
#else
|
|
409
|
+
if (use_utf16 == Qtrue || use_utf16 == Qfalse) {
|
|
410
|
+
rb_warning("TinyTds: Please consider upgrading to FreeTDS 0.99 or higher for better unicode support.\n");
|
|
411
|
+
}
|
|
412
|
+
#endif
|
|
413
|
+
|
|
338
414
|
cwrap->client = dbopen(cwrap->login, StringValueCStr(dataserver));
|
|
339
415
|
if (cwrap->client) {
|
|
340
|
-
VALUE transposed_encoding;
|
|
416
|
+
VALUE transposed_encoding, timeout_string;
|
|
341
417
|
|
|
342
418
|
cwrap->closed = 0;
|
|
343
419
|
cwrap->charset = charset;
|
|
344
420
|
if (!NIL_P(version))
|
|
345
421
|
dbsetversion(NUM2INT(version));
|
|
422
|
+
if (!NIL_P(timeout)) {
|
|
423
|
+
timeout_string = rb_sprintf("%"PRIsVALUE"", timeout);
|
|
424
|
+
if (dbsetopt(cwrap->client, DBSETTIME, StringValueCStr(timeout_string), 0) == FAIL) {
|
|
425
|
+
dbsettime(NUM2INT(timeout));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
346
428
|
dbsetuserdata(cwrap->client, (BYTE*)cwrap->userdata);
|
|
429
|
+
dbsetinterrupt(cwrap->client, check_interrupt, handle_interrupt);
|
|
347
430
|
cwrap->userdata->closed = 0;
|
|
348
431
|
if (!NIL_P(database) && (azure != Qtrue)) {
|
|
349
432
|
dbuse(cwrap->client, StringValueCStr(database));
|
|
@@ -391,6 +474,9 @@ void init_tinytds_client() {
|
|
|
391
474
|
sym_timeout = ID2SYM(rb_intern("timeout"));
|
|
392
475
|
sym_encoding = ID2SYM(rb_intern("encoding"));
|
|
393
476
|
sym_azure = ID2SYM(rb_intern("azure"));
|
|
477
|
+
sym_contained = ID2SYM(rb_intern("contained"));
|
|
478
|
+
sym_use_utf16 = ID2SYM(rb_intern("use_utf16"));
|
|
479
|
+
sym_message_handler = ID2SYM(rb_intern("message_handler"));
|
|
394
480
|
/* Intern TinyTds::Error Accessors */
|
|
395
481
|
intern_source_eql = rb_intern("source=");
|
|
396
482
|
intern_severity_eql = rb_intern("severity=");
|
|
@@ -402,6 +488,7 @@ void init_tinytds_client() {
|
|
|
402
488
|
intern_transpose_iconv_encoding = rb_intern("transpose_iconv_encoding");
|
|
403
489
|
intern_local_offset = rb_intern("local_offset");
|
|
404
490
|
intern_gsub = rb_intern("gsub");
|
|
491
|
+
intern_call = rb_intern("call");
|
|
405
492
|
/* Escape Regexp Global */
|
|
406
493
|
opt_escape_regex = rb_funcall(rb_cRegexp, intern_new, 1, rb_str_new2("\\\'"));
|
|
407
494
|
opt_escape_dblquote = rb_str_new2("''");
|
data/ext/tiny_tds/client.h
CHANGED
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
void init_tinytds_client();
|
|
6
6
|
|
|
7
|
+
#define ERROR_MSG_SIZE 1024
|
|
8
|
+
#define ERRORS_STACK_INIT_SIZE 2
|
|
9
|
+
|
|
7
10
|
typedef struct {
|
|
8
|
-
|
|
11
|
+
int is_message;
|
|
9
12
|
int cancel;
|
|
10
|
-
char error[
|
|
11
|
-
char source[
|
|
13
|
+
char error[ERROR_MSG_SIZE];
|
|
14
|
+
char source[ERROR_MSG_SIZE];
|
|
12
15
|
int severity;
|
|
13
16
|
int dberr;
|
|
14
17
|
int oserr;
|
|
@@ -22,7 +25,10 @@ typedef struct {
|
|
|
22
25
|
RETCODE dbsqlok_retcode;
|
|
23
26
|
short int dbcancel_sent;
|
|
24
27
|
short int nonblocking;
|
|
25
|
-
|
|
28
|
+
short int nonblocking_errors_length;
|
|
29
|
+
short int nonblocking_errors_size;
|
|
30
|
+
tinytds_errordata *nonblocking_errors;
|
|
31
|
+
VALUE message_handler;
|
|
26
32
|
} tinytds_client_userdata;
|
|
27
33
|
|
|
28
34
|
typedef struct {
|
|
@@ -36,7 +42,7 @@ typedef struct {
|
|
|
36
42
|
rb_encoding *encoding;
|
|
37
43
|
} tinytds_client_wrapper;
|
|
38
44
|
|
|
39
|
-
VALUE rb_tinytds_raise_error(DBPROCESS *dbproc,
|
|
45
|
+
VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, tinytds_errordata error);
|
|
40
46
|
|
|
41
47
|
// Lib Macros
|
|
42
48
|
|