tiny_tds 2.1.6-x64-mingw-ucrt
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +407 -0
- data/.codeclimate.yml +20 -0
- data/.gitattributes +1 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +31 -0
- data/CHANGELOG.md +280 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/Gemfile +2 -0
- data/ISSUE_TEMPLATE.md +38 -0
- data/MIT-LICENSE +23 -0
- data/README.md +504 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/bin/defncopy-ttds +3 -0
- data/bin/tsql-ttds +3 -0
- data/docker-compose.yml +34 -0
- data/exe/.keep +0 -0
- data/ext/tiny_tds/client.c +499 -0
- data/ext/tiny_tds/client.h +53 -0
- data/ext/tiny_tds/extconf.rb +92 -0
- data/ext/tiny_tds/extconsts.rb +15 -0
- data/ext/tiny_tds/result.c +634 -0
- data/ext/tiny_tds/result.h +32 -0
- data/ext/tiny_tds/tiny_tds_ext.c +12 -0
- data/ext/tiny_tds/tiny_tds_ext.h +17 -0
- data/lib/tiny_tds/3.1/tiny_tds.so +0 -0
- data/lib/tiny_tds/3.2/tiny_tds.so +0 -0
- data/lib/tiny_tds/bin.rb +104 -0
- data/lib/tiny_tds/client.rb +136 -0
- data/lib/tiny_tds/error.rb +14 -0
- data/lib/tiny_tds/gem.rb +27 -0
- data/lib/tiny_tds/result.rb +7 -0
- data/lib/tiny_tds/version.rb +3 -0
- data/lib/tiny_tds.rb +61 -0
- data/patches/freetds/1.00.27/0001-mingw_missing_inet_pton.diff +34 -0
- 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/ports/x64-mingw-ucrt/freetds/1.1.24/bin/bsqldb.exe +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/datacopy.exe +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/defncopy.exe +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/freebcp.exe +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/libct-4.dll +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/libsybdb-5.dll +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/osql +388 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/tdspool.exe +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/bin/tsql.exe +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/lib/libct.dll.a +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/lib/libct.la +41 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/lib/libsybdb.dll.a +0 -0
- data/ports/x64-mingw-ucrt/freetds/1.1.24/lib/libsybdb.la +41 -0
- data/ports/x64-mingw-ucrt/libiconv/1.15/bin/iconv.exe +0 -0
- data/ports/x64-mingw-ucrt/libiconv/1.15/bin/libcharset-1.dll +0 -0
- data/ports/x64-mingw-ucrt/libiconv/1.15/bin/libiconv-2.dll +0 -0
- data/ports/x64-mingw-ucrt/libiconv/1.15/lib/charset.alias +4 -0
- data/ports/x64-mingw-ucrt/libiconv/1.15/lib/libcharset.dll.a +0 -0
- data/ports/x64-mingw-ucrt/libiconv/1.15/lib/libcharset.la +41 -0
- data/ports/x64-mingw-ucrt/libiconv/1.15/lib/libiconv.dll.a +0 -0
- data/ports/x64-mingw-ucrt/libiconv/1.15/lib/libiconv.la +41 -0
- data/ports/x64-mingw-ucrt/openssl/1.1.1s/bin/c_rehash +251 -0
- data/ports/x64-mingw-ucrt/openssl/1.1.1s/bin/libcrypto-1_1-x64.dll +0 -0
- data/ports/x64-mingw-ucrt/openssl/1.1.1s/bin/libssl-1_1-x64.dll +0 -0
- data/ports/x64-mingw-ucrt/openssl/1.1.1s/bin/openssl.exe +0 -0
- data/ports/x64-mingw-ucrt/openssl/1.1.1s/lib/libcrypto.a +0 -0
- data/ports/x64-mingw-ucrt/openssl/1.1.1s/lib/libcrypto.dll.a +0 -0
- data/ports/x64-mingw-ucrt/openssl/1.1.1s/lib/libssl.a +0 -0
- data/ports/x64-mingw-ucrt/openssl/1.1.1s/lib/libssl.dll.a +0 -0
- data/setup_cimgruby_dev.sh +25 -0
- data/start_dev.sh +21 -0
- data/tasks/native_gem.rake +23 -0
- data/tasks/package.rake +8 -0
- data/tasks/ports/freetds.rb +37 -0
- data/tasks/ports/libiconv.rb +26 -0
- data/tasks/ports/openssl.rb +62 -0
- data/tasks/ports/recipe.rb +64 -0
- data/tasks/ports.rake +108 -0
- data/tasks/test.rake +9 -0
- data/test/benchmark/query.rb +77 -0
- data/test/benchmark/query_odbc.rb +106 -0
- data/test/benchmark/query_tinytds.rb +126 -0
- data/test/bin/install-freetds.sh +20 -0
- data/test/bin/install-mssql.ps1 +31 -0
- data/test/bin/install-mssqltools.sh +9 -0
- data/test/bin/install-openssl.sh +18 -0
- data/test/bin/setup_tinytds_db.sh +7 -0
- data/test/bin/setup_volume_permissions.sh +10 -0
- data/test/client_test.rb +275 -0
- data/test/gem_test.rb +177 -0
- data/test/result_test.rb +814 -0
- data/test/schema/1px.gif +0 -0
- data/test/schema/sqlserver_2000.sql +140 -0
- data/test/schema/sqlserver_2005.sql +140 -0
- data/test/schema/sqlserver_2008.sql +140 -0
- data/test/schema/sqlserver_2014.sql +140 -0
- data/test/schema/sqlserver_2016.sql +140 -0
- data/test/schema/sqlserver_azure.sql +140 -0
- data/test/schema/sybase_ase.sql +138 -0
- data/test/schema_test.rb +443 -0
- data/test/sql/db-create.sql +18 -0
- data/test/sql/db-login.sql +38 -0
- data/test/test_helper.rb +280 -0
- data/test/thread_test.rb +98 -0
- data/tiny_tds.gemspec +31 -0
- metadata +267 -0
@@ -0,0 +1,499 @@
|
|
1
|
+
#include <tiny_tds_ext.h>
|
2
|
+
#include <errno.h>
|
3
|
+
|
4
|
+
VALUE cTinyTdsClient;
|
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, sym_contained, sym_use_utf16, sym_message_handler;
|
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, intern_call;
|
9
|
+
VALUE opt_escape_regex, opt_escape_dblquote;
|
10
|
+
|
11
|
+
|
12
|
+
// Lib Macros
|
13
|
+
|
14
|
+
#define GET_CLIENT_WRAPPER(self) \
|
15
|
+
tinytds_client_wrapper *cwrap; \
|
16
|
+
Data_Get_Struct(self, tinytds_client_wrapper, cwrap)
|
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
|
+
|
25
|
+
// Lib Backend (Helpers)
|
26
|
+
|
27
|
+
VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, tinytds_errordata error) {
|
28
|
+
VALUE e;
|
29
|
+
GET_CLIENT_USERDATA(dbproc);
|
30
|
+
if (error.cancel && !dbdead(dbproc) && userdata && !userdata->closed) {
|
31
|
+
userdata->dbsqlok_sent = 1;
|
32
|
+
dbsqlok(dbproc);
|
33
|
+
userdata->dbcancel_sent = 1;
|
34
|
+
dbcancel(dbproc);
|
35
|
+
}
|
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
|
+
|
54
|
+
rb_exc_raise(e);
|
55
|
+
return Qnil;
|
56
|
+
}
|
57
|
+
|
58
|
+
|
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
|
+
}
|
70
|
+
|
71
|
+
int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) {
|
72
|
+
static const char *source = "error";
|
73
|
+
/* Everything should cancel by default */
|
74
|
+
int return_value = INT_CANCEL;
|
75
|
+
int cancel = 0;
|
76
|
+
|
77
|
+
GET_CLIENT_USERDATA(dbproc);
|
78
|
+
|
79
|
+
/* These error codes are documented in include/sybdb.h in FreeTDS */
|
80
|
+
switch(dberr) {
|
81
|
+
|
82
|
+
/* We don't want to raise these as a ruby exception for various reasons */
|
83
|
+
case 100: /* SYBEVERDOWN, indicating the connection can only be v7.1 */
|
84
|
+
case SYBESEOF: /* Usually accompanied by another more useful error */
|
85
|
+
case SYBESMSG: /* Generic "check messages from server" error */
|
86
|
+
case SYBEICONVI: /* Just return ?s to the client, as explained in readme */
|
87
|
+
return return_value;
|
88
|
+
|
89
|
+
case SYBEICONVO:
|
90
|
+
dbfreebuf(dbproc);
|
91
|
+
return return_value;
|
92
|
+
|
93
|
+
case SYBETIME:
|
94
|
+
/*
|
95
|
+
SYBETIME is the only error that can send INT_TIMEOUT or INT_CONTINUE,
|
96
|
+
but we don't ever want to automatically retry. Instead have the app
|
97
|
+
decide what to do.
|
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
|
+
}
|
106
|
+
return_value = INT_TIMEOUT;
|
107
|
+
cancel = 1;
|
108
|
+
break;
|
109
|
+
|
110
|
+
case SYBEWRIT:
|
111
|
+
/* Write errors may happen after we abort a statement */
|
112
|
+
if (userdata && (userdata->dbsqlok_sent || userdata->dbcancel_sent)) {
|
113
|
+
return return_value;
|
114
|
+
}
|
115
|
+
cancel = 1;
|
116
|
+
break;
|
117
|
+
}
|
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
|
+
|
129
|
+
/*
|
130
|
+
When in non-blocking mode we need to store the exception data to throw it
|
131
|
+
once the blocking call returns, otherwise we will segfault ruby since part
|
132
|
+
of the contract of the ruby non-blocking indicator is that you do not call
|
133
|
+
any of the ruby C API.
|
134
|
+
*/
|
135
|
+
if (userdata && userdata->nonblocking) {
|
136
|
+
if (cancel && !dbdead(dbproc) && !userdata->closed) {
|
137
|
+
dbcancel(dbproc);
|
138
|
+
userdata->dbcancel_sent = 1;
|
139
|
+
}
|
140
|
+
push_userdata_error(userdata, error_data);
|
141
|
+
} else {
|
142
|
+
rb_tinytds_raise_error(dbproc, error_data);
|
143
|
+
}
|
144
|
+
|
145
|
+
return return_value;
|
146
|
+
}
|
147
|
+
|
148
|
+
int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line) {
|
149
|
+
static const char *source = "message";
|
150
|
+
GET_CLIENT_USERDATA(dbproc);
|
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;
|
176
|
+
}
|
177
|
+
} else {
|
178
|
+
rb_tinytds_raise_error(dbproc, error_data);
|
179
|
+
}
|
180
|
+
return 0;
|
181
|
+
}
|
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
|
+
|
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
|
+
static void rb_tinytds_client_mark(void *ptr) {
|
223
|
+
tinytds_client_wrapper *cwrap = (tinytds_client_wrapper *)ptr;
|
224
|
+
if (cwrap) {
|
225
|
+
rb_gc_mark(cwrap->charset);
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
static void rb_tinytds_client_free(void *ptr) {
|
230
|
+
tinytds_client_wrapper *cwrap = (tinytds_client_wrapper *)ptr;
|
231
|
+
if (cwrap->login)
|
232
|
+
dbloginfree(cwrap->login);
|
233
|
+
if (cwrap->client && !cwrap->closed) {
|
234
|
+
dbclose(cwrap->client);
|
235
|
+
cwrap->client = NULL;
|
236
|
+
cwrap->closed = 1;
|
237
|
+
cwrap->userdata->closed = 1;
|
238
|
+
}
|
239
|
+
xfree(cwrap->userdata);
|
240
|
+
xfree(ptr);
|
241
|
+
}
|
242
|
+
|
243
|
+
static VALUE allocate(VALUE klass) {
|
244
|
+
VALUE obj;
|
245
|
+
tinytds_client_wrapper *cwrap;
|
246
|
+
obj = Data_Make_Struct(klass, tinytds_client_wrapper, rb_tinytds_client_mark, rb_tinytds_client_free, cwrap);
|
247
|
+
cwrap->closed = 1;
|
248
|
+
cwrap->charset = Qnil;
|
249
|
+
cwrap->userdata = malloc(sizeof(tinytds_client_userdata));
|
250
|
+
cwrap->userdata->closed = 1;
|
251
|
+
rb_tinytds_client_reset_userdata(cwrap->userdata);
|
252
|
+
return obj;
|
253
|
+
}
|
254
|
+
|
255
|
+
|
256
|
+
// TinyTds::Client (public)
|
257
|
+
|
258
|
+
static VALUE rb_tinytds_tds_version(VALUE self) {
|
259
|
+
GET_CLIENT_WRAPPER(self);
|
260
|
+
return INT2FIX(dbtds(cwrap->client));
|
261
|
+
}
|
262
|
+
|
263
|
+
static VALUE rb_tinytds_close(VALUE self) {
|
264
|
+
GET_CLIENT_WRAPPER(self);
|
265
|
+
if (cwrap->client && !cwrap->closed) {
|
266
|
+
dbclose(cwrap->client);
|
267
|
+
cwrap->client = NULL;
|
268
|
+
cwrap->closed = 1;
|
269
|
+
cwrap->userdata->closed = 1;
|
270
|
+
}
|
271
|
+
return Qtrue;
|
272
|
+
}
|
273
|
+
|
274
|
+
static VALUE rb_tinytds_dead(VALUE self) {
|
275
|
+
GET_CLIENT_WRAPPER(self);
|
276
|
+
return dbdead(cwrap->client) ? Qtrue : Qfalse;
|
277
|
+
}
|
278
|
+
|
279
|
+
static VALUE rb_tinytds_closed(VALUE self) {
|
280
|
+
GET_CLIENT_WRAPPER(self);
|
281
|
+
return (cwrap->closed || cwrap->userdata->closed) ? Qtrue : Qfalse;
|
282
|
+
}
|
283
|
+
|
284
|
+
static VALUE rb_tinytds_canceled(VALUE self) {
|
285
|
+
GET_CLIENT_WRAPPER(self);
|
286
|
+
return cwrap->userdata->dbcancel_sent ? Qtrue : Qfalse;
|
287
|
+
}
|
288
|
+
|
289
|
+
static VALUE rb_tinytds_sqlsent(VALUE self) {
|
290
|
+
GET_CLIENT_WRAPPER(self);
|
291
|
+
return cwrap->userdata->dbsql_sent ? Qtrue : Qfalse;
|
292
|
+
}
|
293
|
+
|
294
|
+
static VALUE rb_tinytds_execute(VALUE self, VALUE sql) {
|
295
|
+
VALUE result;
|
296
|
+
|
297
|
+
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_warn("TinyTds: dbsqlsend() returned FAIL.\n");
|
303
|
+
return Qfalse;
|
304
|
+
}
|
305
|
+
cwrap->userdata->dbsql_sent = 1;
|
306
|
+
result = rb_tinytds_new_result_obj(cwrap);
|
307
|
+
rb_iv_set(result, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), intern_dup, 0));
|
308
|
+
{
|
309
|
+
GET_RESULT_WRAPPER(result);
|
310
|
+
rwrap->local_offset = rb_funcall(cTinyTdsClient, intern_local_offset, 0);
|
311
|
+
rwrap->encoding = cwrap->encoding;
|
312
|
+
return result;
|
313
|
+
}
|
314
|
+
}
|
315
|
+
|
316
|
+
static VALUE rb_tinytds_charset(VALUE self) {
|
317
|
+
GET_CLIENT_WRAPPER(self);
|
318
|
+
return cwrap->charset;
|
319
|
+
}
|
320
|
+
|
321
|
+
static VALUE rb_tinytds_encoding(VALUE self) {
|
322
|
+
GET_CLIENT_WRAPPER(self);
|
323
|
+
return rb_enc_from_encoding(cwrap->encoding);
|
324
|
+
}
|
325
|
+
|
326
|
+
static VALUE rb_tinytds_escape(VALUE self, VALUE string) {
|
327
|
+
VALUE new_string;
|
328
|
+
GET_CLIENT_WRAPPER(self);
|
329
|
+
|
330
|
+
Check_Type(string, T_STRING);
|
331
|
+
new_string = rb_funcall(string, intern_gsub, 2, opt_escape_regex, opt_escape_dblquote);
|
332
|
+
rb_enc_associate(new_string, cwrap->encoding);
|
333
|
+
return new_string;
|
334
|
+
}
|
335
|
+
|
336
|
+
/* Duplicated in result.c */
|
337
|
+
static VALUE rb_tinytds_return_code(VALUE self) {
|
338
|
+
GET_CLIENT_WRAPPER(self);
|
339
|
+
if (cwrap->client && dbhasretstat(cwrap->client)) {
|
340
|
+
return LONG2NUM((long)dbretstatus(cwrap->client));
|
341
|
+
} else {
|
342
|
+
return Qnil;
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
static VALUE rb_tinytds_identity_sql(VALUE self) {
|
347
|
+
GET_CLIENT_WRAPPER(self);
|
348
|
+
return rb_str_new2(cwrap->identity_insert_sql);
|
349
|
+
}
|
350
|
+
|
351
|
+
|
352
|
+
|
353
|
+
// TinyTds::Client (protected)
|
354
|
+
|
355
|
+
static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
|
356
|
+
/* Parsing options hash to local vars. */
|
357
|
+
VALUE user, pass, dataserver, database, app, version, ltimeout, timeout, charset, azure, contained, use_utf16;
|
358
|
+
GET_CLIENT_WRAPPER(self);
|
359
|
+
|
360
|
+
user = rb_hash_aref(opts, sym_username);
|
361
|
+
pass = rb_hash_aref(opts, sym_password);
|
362
|
+
dataserver = rb_hash_aref(opts, sym_dataserver);
|
363
|
+
database = rb_hash_aref(opts, sym_database);
|
364
|
+
app = rb_hash_aref(opts, sym_appname);
|
365
|
+
version = rb_hash_aref(opts, sym_tds_version);
|
366
|
+
ltimeout = rb_hash_aref(opts, sym_login_timeout);
|
367
|
+
timeout = rb_hash_aref(opts, sym_timeout);
|
368
|
+
charset = rb_hash_aref(opts, sym_encoding);
|
369
|
+
azure = rb_hash_aref(opts, sym_azure);
|
370
|
+
contained = rb_hash_aref(opts, sym_contained);
|
371
|
+
use_utf16 = rb_hash_aref(opts, sym_use_utf16);
|
372
|
+
cwrap->userdata->message_handler = rb_hash_aref(opts, sym_message_handler);
|
373
|
+
/* Dealing with options. */
|
374
|
+
if (dbinit() == FAIL) {
|
375
|
+
rb_raise(cTinyTdsError, "failed dbinit() function");
|
376
|
+
return self;
|
377
|
+
}
|
378
|
+
dberrhandle(tinytds_err_handler);
|
379
|
+
dbmsghandle(tinytds_msg_handler);
|
380
|
+
cwrap->login = dblogin();
|
381
|
+
if (!NIL_P(version))
|
382
|
+
dbsetlversion(cwrap->login, NUM2INT(version));
|
383
|
+
if (!NIL_P(user))
|
384
|
+
dbsetluser(cwrap->login, StringValueCStr(user));
|
385
|
+
if (!NIL_P(pass))
|
386
|
+
dbsetlpwd(cwrap->login, StringValueCStr(pass));
|
387
|
+
if (!NIL_P(app))
|
388
|
+
dbsetlapp(cwrap->login, StringValueCStr(app));
|
389
|
+
if (!NIL_P(ltimeout))
|
390
|
+
dbsetlogintime(NUM2INT(ltimeout));
|
391
|
+
if (!NIL_P(charset))
|
392
|
+
DBSETLCHARSET(cwrap->login, StringValueCStr(charset));
|
393
|
+
if (!NIL_P(database)) {
|
394
|
+
if (azure == Qtrue || contained == Qtrue) {
|
395
|
+
#ifdef DBSETLDBNAME
|
396
|
+
DBSETLDBNAME(cwrap->login, StringValueCStr(database));
|
397
|
+
#else
|
398
|
+
if (azure == Qtrue) {
|
399
|
+
rb_warn("TinyTds: :azure option is not supported in this version of FreeTDS.\n");
|
400
|
+
}
|
401
|
+
if (contained == Qtrue) {
|
402
|
+
rb_warn("TinyTds: :contained option is not supported in this version of FreeTDS.\n");
|
403
|
+
}
|
404
|
+
#endif
|
405
|
+
}
|
406
|
+
}
|
407
|
+
#ifdef DBSETUTF16
|
408
|
+
if (use_utf16 == Qtrue) { DBSETLUTF16(cwrap->login, 1); }
|
409
|
+
if (use_utf16 == Qfalse) { DBSETLUTF16(cwrap->login, 0); }
|
410
|
+
#else
|
411
|
+
if (use_utf16 == Qtrue || use_utf16 == Qfalse) {
|
412
|
+
rb_warning("TinyTds: Please consider upgrading to FreeTDS 0.99 or higher for better unicode support.\n");
|
413
|
+
}
|
414
|
+
#endif
|
415
|
+
|
416
|
+
cwrap->client = dbopen(cwrap->login, StringValueCStr(dataserver));
|
417
|
+
if (cwrap->client) {
|
418
|
+
VALUE transposed_encoding, timeout_string;
|
419
|
+
|
420
|
+
cwrap->closed = 0;
|
421
|
+
cwrap->charset = charset;
|
422
|
+
if (!NIL_P(version))
|
423
|
+
dbsetversion(NUM2INT(version));
|
424
|
+
if (!NIL_P(timeout)) {
|
425
|
+
timeout_string = rb_sprintf("%"PRIsVALUE"", timeout);
|
426
|
+
if (dbsetopt(cwrap->client, DBSETTIME, StringValueCStr(timeout_string), 0) == FAIL) {
|
427
|
+
dbsettime(NUM2INT(timeout));
|
428
|
+
}
|
429
|
+
}
|
430
|
+
dbsetuserdata(cwrap->client, (BYTE*)cwrap->userdata);
|
431
|
+
dbsetinterrupt(cwrap->client, check_interrupt, handle_interrupt);
|
432
|
+
cwrap->userdata->closed = 0;
|
433
|
+
if (!NIL_P(database) && (azure != Qtrue)) {
|
434
|
+
dbuse(cwrap->client, StringValueCStr(database));
|
435
|
+
}
|
436
|
+
transposed_encoding = rb_funcall(cTinyTdsClient, intern_transpose_iconv_encoding, 1, charset);
|
437
|
+
cwrap->encoding = rb_enc_find(StringValueCStr(transposed_encoding));
|
438
|
+
if (dbtds(cwrap->client) <= 7) {
|
439
|
+
cwrap->identity_insert_sql = "SELECT CAST(@@IDENTITY AS bigint) AS Ident";
|
440
|
+
} else {
|
441
|
+
cwrap->identity_insert_sql = "SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident";
|
442
|
+
}
|
443
|
+
}
|
444
|
+
return self;
|
445
|
+
}
|
446
|
+
|
447
|
+
|
448
|
+
// Lib Init
|
449
|
+
|
450
|
+
void init_tinytds_client() {
|
451
|
+
cTinyTdsClient = rb_define_class_under(mTinyTds, "Client", rb_cObject);
|
452
|
+
rb_define_alloc_func(cTinyTdsClient, allocate);
|
453
|
+
/* Define TinyTds::Client Public Methods */
|
454
|
+
rb_define_method(cTinyTdsClient, "tds_version", rb_tinytds_tds_version, 0);
|
455
|
+
rb_define_method(cTinyTdsClient, "close", rb_tinytds_close, 0);
|
456
|
+
rb_define_method(cTinyTdsClient, "closed?", rb_tinytds_closed, 0);
|
457
|
+
rb_define_method(cTinyTdsClient, "canceled?", rb_tinytds_canceled, 0);
|
458
|
+
rb_define_method(cTinyTdsClient, "dead?", rb_tinytds_dead, 0);
|
459
|
+
rb_define_method(cTinyTdsClient, "sqlsent?", rb_tinytds_sqlsent, 0);
|
460
|
+
rb_define_method(cTinyTdsClient, "execute", rb_tinytds_execute, 1);
|
461
|
+
rb_define_method(cTinyTdsClient, "charset", rb_tinytds_charset, 0);
|
462
|
+
rb_define_method(cTinyTdsClient, "encoding", rb_tinytds_encoding, 0);
|
463
|
+
rb_define_method(cTinyTdsClient, "escape", rb_tinytds_escape, 1);
|
464
|
+
rb_define_method(cTinyTdsClient, "return_code", rb_tinytds_return_code, 0);
|
465
|
+
rb_define_method(cTinyTdsClient, "identity_sql", rb_tinytds_identity_sql, 0);
|
466
|
+
/* Define TinyTds::Client Protected Methods */
|
467
|
+
rb_define_protected_method(cTinyTdsClient, "connect", rb_tinytds_connect, 1);
|
468
|
+
/* Symbols For Connect */
|
469
|
+
sym_username = ID2SYM(rb_intern("username"));
|
470
|
+
sym_password = ID2SYM(rb_intern("password"));
|
471
|
+
sym_dataserver = ID2SYM(rb_intern("dataserver"));
|
472
|
+
sym_database = ID2SYM(rb_intern("database"));
|
473
|
+
sym_appname = ID2SYM(rb_intern("appname"));
|
474
|
+
sym_tds_version = ID2SYM(rb_intern("tds_version"));
|
475
|
+
sym_login_timeout = ID2SYM(rb_intern("login_timeout"));
|
476
|
+
sym_timeout = ID2SYM(rb_intern("timeout"));
|
477
|
+
sym_encoding = ID2SYM(rb_intern("encoding"));
|
478
|
+
sym_azure = ID2SYM(rb_intern("azure"));
|
479
|
+
sym_contained = ID2SYM(rb_intern("contained"));
|
480
|
+
sym_use_utf16 = ID2SYM(rb_intern("use_utf16"));
|
481
|
+
sym_message_handler = ID2SYM(rb_intern("message_handler"));
|
482
|
+
/* Intern TinyTds::Error Accessors */
|
483
|
+
intern_source_eql = rb_intern("source=");
|
484
|
+
intern_severity_eql = rb_intern("severity=");
|
485
|
+
intern_db_error_number_eql = rb_intern("db_error_number=");
|
486
|
+
intern_os_error_number_eql = rb_intern("os_error_number=");
|
487
|
+
/* Intern Misc */
|
488
|
+
intern_new = rb_intern("new");
|
489
|
+
intern_dup = rb_intern("dup");
|
490
|
+
intern_transpose_iconv_encoding = rb_intern("transpose_iconv_encoding");
|
491
|
+
intern_local_offset = rb_intern("local_offset");
|
492
|
+
intern_gsub = rb_intern("gsub");
|
493
|
+
intern_call = rb_intern("call");
|
494
|
+
/* Escape Regexp Global */
|
495
|
+
opt_escape_regex = rb_funcall(rb_cRegexp, intern_new, 1, rb_str_new2("\\\'"));
|
496
|
+
opt_escape_dblquote = rb_str_new2("''");
|
497
|
+
rb_global_variable(&opt_escape_regex);
|
498
|
+
rb_global_variable(&opt_escape_dblquote);
|
499
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
#ifndef TINYTDS_CLIENT_H
|
3
|
+
#define TINYTDS_CLIENT_H
|
4
|
+
|
5
|
+
void init_tinytds_client();
|
6
|
+
|
7
|
+
#define ERROR_MSG_SIZE 1024
|
8
|
+
#define ERRORS_STACK_INIT_SIZE 2
|
9
|
+
|
10
|
+
typedef struct {
|
11
|
+
int is_message;
|
12
|
+
int cancel;
|
13
|
+
char error[ERROR_MSG_SIZE];
|
14
|
+
char source[ERROR_MSG_SIZE];
|
15
|
+
int severity;
|
16
|
+
int dberr;
|
17
|
+
int oserr;
|
18
|
+
} tinytds_errordata;
|
19
|
+
|
20
|
+
typedef struct {
|
21
|
+
short int closed;
|
22
|
+
short int timing_out;
|
23
|
+
short int dbsql_sent;
|
24
|
+
short int dbsqlok_sent;
|
25
|
+
RETCODE dbsqlok_retcode;
|
26
|
+
short int dbcancel_sent;
|
27
|
+
short int nonblocking;
|
28
|
+
short int nonblocking_errors_length;
|
29
|
+
short int nonblocking_errors_size;
|
30
|
+
tinytds_errordata *nonblocking_errors;
|
31
|
+
VALUE message_handler;
|
32
|
+
} tinytds_client_userdata;
|
33
|
+
|
34
|
+
typedef struct {
|
35
|
+
LOGINREC *login;
|
36
|
+
RETCODE return_code;
|
37
|
+
DBPROCESS *client;
|
38
|
+
short int closed;
|
39
|
+
VALUE charset;
|
40
|
+
tinytds_client_userdata *userdata;
|
41
|
+
const char *identity_insert_sql;
|
42
|
+
rb_encoding *encoding;
|
43
|
+
} tinytds_client_wrapper;
|
44
|
+
|
45
|
+
VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, tinytds_errordata error);
|
46
|
+
|
47
|
+
// Lib Macros
|
48
|
+
|
49
|
+
#define GET_CLIENT_USERDATA(dbproc) \
|
50
|
+
tinytds_client_userdata *userdata = (tinytds_client_userdata *)dbgetuserdata(dbproc);
|
51
|
+
|
52
|
+
|
53
|
+
#endif
|
@@ -0,0 +1,92 @@
|
|
1
|
+
ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
|
2
|
+
|
3
|
+
# :stopdoc:
|
4
|
+
|
5
|
+
require 'mkmf'
|
6
|
+
require 'rbconfig'
|
7
|
+
require_relative './extconsts'
|
8
|
+
|
9
|
+
# Shamelessly copied from nokogiri
|
10
|
+
#
|
11
|
+
|
12
|
+
def do_help
|
13
|
+
print <<HELP
|
14
|
+
usage: ruby #{$0} [options]
|
15
|
+
--with-freetds-dir=DIR
|
16
|
+
Use the freetds library placed under DIR.
|
17
|
+
HELP
|
18
|
+
exit! 0
|
19
|
+
end
|
20
|
+
|
21
|
+
do_help if arg_config('--help')
|
22
|
+
|
23
|
+
# Make sure to check the ports path for the configured host
|
24
|
+
architecture = RbConfig::CONFIG['arch']
|
25
|
+
architecture = "x86-mingw32" if architecture == "i386-mingw32"
|
26
|
+
|
27
|
+
project_dir = File.expand_path("../../..", __FILE__)
|
28
|
+
freetds_ports_dir = File.join(project_dir, 'ports', architecture, 'freetds', FREETDS_VERSION)
|
29
|
+
freetds_ports_dir = File.expand_path(freetds_ports_dir)
|
30
|
+
|
31
|
+
# Add all the special path searching from the original tiny_tds build
|
32
|
+
# order is important here! First in, first searched.
|
33
|
+
DIRS = %w(
|
34
|
+
/opt/local
|
35
|
+
/usr/local
|
36
|
+
)
|
37
|
+
|
38
|
+
if RbConfig::CONFIG['host_os'] =~ /darwin/i
|
39
|
+
# Ruby below 2.7 seems to label the host CPU on Apple Silicon as aarch64
|
40
|
+
# 2.7 and above print is as ARM64
|
41
|
+
target_host_cpu = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7') ? 'aarch64' : 'arm64'
|
42
|
+
|
43
|
+
if RbConfig::CONFIG['host_cpu'] == target_host_cpu
|
44
|
+
# Homebrew on Apple Silicon installs into /opt/hombrew
|
45
|
+
# https://docs.brew.sh/Installation
|
46
|
+
# On Intel Macs, it is /usr/local, so no changes necessary to DIRS
|
47
|
+
DIRS.unshift("/opt/homebrew")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if ENV["RI_DEVKIT"] && ENV["MINGW_PREFIX"] # RubyInstaller Support
|
52
|
+
DIRS.unshift(File.join(ENV["RI_DEVKIT"], ENV["MINGW_PREFIX"]))
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add the ports directory if it exists for local developer builds
|
56
|
+
DIRS.unshift(freetds_ports_dir) if File.directory?(freetds_ports_dir)
|
57
|
+
|
58
|
+
# Grab freetds environment variable for use by people on services like
|
59
|
+
# Heroku who they can't easily use bundler config to set directories
|
60
|
+
DIRS.unshift(ENV['FREETDS_DIR']) if ENV.has_key?('FREETDS_DIR')
|
61
|
+
|
62
|
+
# Add the search paths for freetds configured above
|
63
|
+
ldirs = DIRS.flat_map do |path|
|
64
|
+
ldir = "#{path}/lib"
|
65
|
+
[ldir, "#{ldir}/freetds"]
|
66
|
+
end
|
67
|
+
|
68
|
+
idirs = DIRS.flat_map do |path|
|
69
|
+
idir = "#{path}/include"
|
70
|
+
[idir, "#{idir}/freetds"]
|
71
|
+
end
|
72
|
+
|
73
|
+
puts "looking for freetds headers in the following directories:\n#{idirs.map{|a| " - #{a}\n"}.join}"
|
74
|
+
puts "looking for freetds library in the following directories:\n#{ldirs.map{|a| " - #{a}\n"}.join}"
|
75
|
+
dir_config('freetds', idirs, ldirs)
|
76
|
+
|
77
|
+
have_dependencies = [
|
78
|
+
find_header('sybfront.h'),
|
79
|
+
find_header('sybdb.h'),
|
80
|
+
find_library('sybdb', 'tdsdbopen'),
|
81
|
+
find_library('sybdb', 'dbanydatecrack')
|
82
|
+
].inject(true) do |memo, current|
|
83
|
+
memo && current
|
84
|
+
end
|
85
|
+
|
86
|
+
unless have_dependencies
|
87
|
+
abort 'Failed! Do you have FreeTDS 0.95.80 or higher installed?'
|
88
|
+
end
|
89
|
+
|
90
|
+
create_makefile('tiny_tds/tiny_tds')
|
91
|
+
|
92
|
+
# :startdoc:
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
ICONV_VERSION = ENV['TINYTDS_ICONV_VERSION'] || "1.15"
|
3
|
+
ICONV_SOURCE_URI = "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-#{ICONV_VERSION}.tar.gz"
|
4
|
+
|
5
|
+
OPENSSL_VERSION = ENV['TINYTDS_OPENSSL_VERSION'] || '1.1.1s'
|
6
|
+
OPENSSL_SOURCE_URI = "https://www.openssl.org/source/openssl-#{OPENSSL_VERSION}.tar.gz"
|
7
|
+
|
8
|
+
FREETDS_VERSION = ENV['TINYTDS_FREETDS_VERSION'] || "1.1.24"
|
9
|
+
FREETDS_VERSION_INFO = Hash.new { |h,k|
|
10
|
+
h[k] = {files: "http://www.freetds.org/files/stable/freetds-#{k}.tar.bz2"}
|
11
|
+
}
|
12
|
+
FREETDS_VERSION_INFO['1.00'] = {files: 'http://www.freetds.org/files/stable/freetds-1.00.tar.bz2'}
|
13
|
+
FREETDS_VERSION_INFO['0.99'] = {files: 'http://www.freetds.org/files/current/freetds-dev.0.99.678.tar.gz'}
|
14
|
+
FREETDS_VERSION_INFO['0.95'] = {files: 'http://www.freetds.org/files/stable/freetds-0.95.92.tar.gz'}
|
15
|
+
FREETDS_SOURCE_URI = FREETDS_VERSION_INFO[FREETDS_VERSION][:files]
|