tiny_tds 0.4.5.rc1-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/CHANGELOG +99 -0
- data/Gemfile +18 -0
- data/MIT-LICENSE +22 -0
- data/NOTES +11 -0
- data/README.rdoc +277 -0
- data/Rakefile +52 -0
- data/ext/tiny_tds/client.c +347 -0
- data/ext/tiny_tds/client.h +35 -0
- data/ext/tiny_tds/extconf.rb +84 -0
- data/ext/tiny_tds/result.c +493 -0
- data/ext/tiny_tds/result.h +36 -0
- data/ext/tiny_tds/tiny_tds_ext.c +12 -0
- data/ext/tiny_tds/tiny_tds_ext.h +18 -0
- data/lib/tiny_tds/1.8/tiny_tds.so +0 -0
- data/lib/tiny_tds/1.9/tiny_tds.so +0 -0
- data/lib/tiny_tds/client.rb +79 -0
- data/lib/tiny_tds/error.rb +29 -0
- data/lib/tiny_tds/result.rb +8 -0
- data/lib/tiny_tds/version.rb +3 -0
- data/lib/tiny_tds.rb +19 -0
- data/tasks/ports.rake +58 -0
- data/test/benchmark/query.rb +77 -0
- data/test/benchmark/query_odbc.rb +106 -0
- data/test/benchmark/query_tinytds.rb +111 -0
- data/test/client_test.rb +163 -0
- data/test/result_test.rb +437 -0
- data/test/schema/1px.gif +0 -0
- data/test/schema/sqlserver_2000.sql +138 -0
- data/test/schema/sqlserver_2005.sql +138 -0
- data/test/schema/sqlserver_2008.sql +138 -0
- data/test/schema/sqlserver_azure.sql +138 -0
- data/test/schema_test.rb +284 -0
- data/test/test_helper.rb +190 -0
- metadata +117 -0
@@ -0,0 +1,493 @@
|
|
1
|
+
|
2
|
+
#include <tiny_tds_ext.h>
|
3
|
+
|
4
|
+
VALUE cTinyTdsResult;
|
5
|
+
extern VALUE mTinyTds, cTinyTdsClient, cTinyTdsError;
|
6
|
+
VALUE cBigDecimal, cDate, cDateTime;
|
7
|
+
VALUE opt_decimal_zero, opt_float_zero, opt_one, opt_zero, opt_four, opt_19hdr, opt_tenk, opt_onemil;
|
8
|
+
int opt_ruby_186;
|
9
|
+
static ID intern_new, intern_utc, intern_local, intern_localtime, intern_merge,
|
10
|
+
intern_civil, intern_new_offset, intern_plus, intern_divide, intern_Rational;
|
11
|
+
static ID sym_symbolize_keys, sym_as, sym_array, sym_cache_rows, sym_first, sym_timezone, sym_local, sym_utc;
|
12
|
+
|
13
|
+
|
14
|
+
// Lib Macros
|
15
|
+
|
16
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
17
|
+
rb_encoding *binaryEncoding;
|
18
|
+
#define ENCODED_STR_NEW(_data, _len) ({ \
|
19
|
+
VALUE _val = rb_str_new((char *)_data, (long)_len); \
|
20
|
+
rb_enc_associate(_val, rwrap->encoding); \
|
21
|
+
_val; \
|
22
|
+
})
|
23
|
+
#define ENCODED_STR_NEW2(_data2) ({ \
|
24
|
+
VALUE _val = rb_str_new2((char *)_data2); \
|
25
|
+
rb_enc_associate(_val, rwrap->encoding); \
|
26
|
+
_val; \
|
27
|
+
})
|
28
|
+
#else
|
29
|
+
#define ENCODED_STR_NEW(_data, _len) \
|
30
|
+
rb_str_new((char *)_data, (long)_len)
|
31
|
+
#define ENCODED_STR_NEW2(_data2) \
|
32
|
+
rb_str_new2((char *)_data2)
|
33
|
+
#endif
|
34
|
+
|
35
|
+
|
36
|
+
// Lib Backend (Memory Management)
|
37
|
+
|
38
|
+
static void rb_tinytds_result_mark(void *ptr) {
|
39
|
+
tinytds_result_wrapper *rwrap = (tinytds_result_wrapper *)ptr;
|
40
|
+
if (rwrap) {
|
41
|
+
rb_gc_mark(rwrap->local_offset);
|
42
|
+
rb_gc_mark(rwrap->fields);
|
43
|
+
rb_gc_mark(rwrap->fields_processed);
|
44
|
+
rb_gc_mark(rwrap->results);
|
45
|
+
rb_gc_mark(rwrap->dbresults_retcodes);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
static void rb_tinytds_result_free(void *ptr) {
|
50
|
+
tinytds_result_wrapper *rwrap = (tinytds_result_wrapper *)ptr;
|
51
|
+
xfree(ptr);
|
52
|
+
}
|
53
|
+
|
54
|
+
VALUE rb_tinytds_new_result_obj(DBPROCESS *c) {
|
55
|
+
VALUE obj;
|
56
|
+
tinytds_result_wrapper *rwrap;
|
57
|
+
obj = Data_Make_Struct(cTinyTdsResult, tinytds_result_wrapper, rb_tinytds_result_mark, rb_tinytds_result_free, rwrap);
|
58
|
+
rwrap->client = c;
|
59
|
+
rwrap->local_offset = Qnil;
|
60
|
+
rwrap->fields = rb_ary_new();
|
61
|
+
rwrap->fields_processed = rb_ary_new();
|
62
|
+
rwrap->results = Qnil;
|
63
|
+
rwrap->dbresults_retcodes = rb_ary_new();
|
64
|
+
rwrap->number_of_results = 0;
|
65
|
+
rwrap->number_of_fields = 0;
|
66
|
+
rwrap->number_of_rows = 0;
|
67
|
+
rb_obj_call_init(obj, 0, NULL);
|
68
|
+
return obj;
|
69
|
+
}
|
70
|
+
|
71
|
+
|
72
|
+
// Lib Backend (Helpers)
|
73
|
+
|
74
|
+
static RETCODE rb_tinytds_result_dbresults_retcode(VALUE self) {
|
75
|
+
GET_RESULT_WRAPPER(self);
|
76
|
+
VALUE ruby_rc;
|
77
|
+
RETCODE db_rc;
|
78
|
+
ruby_rc = rb_ary_entry(rwrap->dbresults_retcodes, rwrap->number_of_results);
|
79
|
+
if (NIL_P(ruby_rc)) {
|
80
|
+
db_rc = dbresults(rwrap->client);
|
81
|
+
ruby_rc = INT2FIX(db_rc);
|
82
|
+
rb_ary_store(rwrap->dbresults_retcodes, rwrap->number_of_results, ruby_rc);
|
83
|
+
} else {
|
84
|
+
db_rc = FIX2INT(ruby_rc);
|
85
|
+
}
|
86
|
+
return db_rc;
|
87
|
+
}
|
88
|
+
|
89
|
+
static RETCODE rb_tinytds_result_ok_helper(DBPROCESS *client) {
|
90
|
+
GET_CLIENT_USERDATA(client);
|
91
|
+
if (userdata->dbsqlok_sent == 0) {
|
92
|
+
userdata->dbsqlok_retcode = dbsqlok(client);
|
93
|
+
userdata->dbsqlok_sent = 1;
|
94
|
+
}
|
95
|
+
return userdata->dbsqlok_retcode;
|
96
|
+
}
|
97
|
+
|
98
|
+
static void rb_tinytds_result_cancel_helper(DBPROCESS *client) {
|
99
|
+
GET_CLIENT_USERDATA(client);
|
100
|
+
rb_tinytds_result_ok_helper(client);
|
101
|
+
dbcancel(client);
|
102
|
+
userdata->dbcancel_sent = 1;
|
103
|
+
userdata->dbsql_sent = 0;
|
104
|
+
}
|
105
|
+
|
106
|
+
static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_keys, int as_array) {
|
107
|
+
/* Wrapper And Local Vars */
|
108
|
+
GET_RESULT_WRAPPER(self);
|
109
|
+
/* Create Empty Row */
|
110
|
+
VALUE row = as_array ? rb_ary_new2(rwrap->number_of_fields) : rb_hash_new();
|
111
|
+
/* Storing Values */
|
112
|
+
unsigned int i = 0;
|
113
|
+
for (i = 0; i < rwrap->number_of_fields; i++) {
|
114
|
+
VALUE val = Qnil;
|
115
|
+
int col = i+1;
|
116
|
+
int coltype = dbcoltype(rwrap->client, col);
|
117
|
+
BYTE *data = dbdata(rwrap->client, col);
|
118
|
+
DBINT data_len = dbdatlen(rwrap->client, col);
|
119
|
+
int null_val = ((data == NULL) && (data_len == 0));
|
120
|
+
if (!null_val) {
|
121
|
+
switch(coltype) {
|
122
|
+
case SYBINT1:
|
123
|
+
val = INT2FIX(*(DBTINYINT *)data);
|
124
|
+
break;
|
125
|
+
case SYBINT2:
|
126
|
+
val = INT2FIX(*(DBSMALLINT *)data);
|
127
|
+
break;
|
128
|
+
case SYBINT4:
|
129
|
+
val = INT2NUM(*(DBINT *)data);
|
130
|
+
break;
|
131
|
+
case SYBINT8:
|
132
|
+
val = LL2NUM(*(DBBIGINT *)data);
|
133
|
+
break;
|
134
|
+
case SYBBIT:
|
135
|
+
val = *(int *)data ? Qtrue : Qfalse;
|
136
|
+
break;
|
137
|
+
case SYBNUMERIC:
|
138
|
+
case SYBDECIMAL: {
|
139
|
+
DBTYPEINFO *data_info = dbcoltypeinfo(rwrap->client, col);
|
140
|
+
int data_slength = (int)data_info->precision + (int)data_info->scale + 1;
|
141
|
+
char converted_decimal[data_slength];
|
142
|
+
dbconvert(rwrap->client, coltype, data, data_len, SYBVARCHAR, (BYTE *)converted_decimal, -1);
|
143
|
+
val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2((char *)converted_decimal));
|
144
|
+
break;
|
145
|
+
}
|
146
|
+
case SYBFLT8: {
|
147
|
+
double col_to_double = *(double *)data;
|
148
|
+
val = (col_to_double == 0.000000) ? opt_float_zero : rb_float_new(col_to_double);
|
149
|
+
break;
|
150
|
+
}
|
151
|
+
case SYBREAL: {
|
152
|
+
float col_to_float = *(float *)data;
|
153
|
+
val = (col_to_float == 0.0) ? opt_float_zero : rb_float_new(col_to_float);
|
154
|
+
break;
|
155
|
+
}
|
156
|
+
case SYBMONEY: {
|
157
|
+
DBMONEY *money = (DBMONEY *)data;
|
158
|
+
char converted_money[25];
|
159
|
+
long long money_value = ((long long)money->mnyhigh << 32) | money->mnylow;
|
160
|
+
sprintf(converted_money, "%lld", money_value);
|
161
|
+
val = rb_funcall(cBigDecimal, intern_new, 2, rb_str_new2(converted_money), opt_four);
|
162
|
+
val = rb_funcall(val, intern_divide, 1, opt_tenk);
|
163
|
+
break;
|
164
|
+
}
|
165
|
+
case SYBMONEY4: {
|
166
|
+
DBMONEY4 *money = (DBMONEY4 *)data;
|
167
|
+
char converted_money[20];
|
168
|
+
sprintf(converted_money, "%f", money->mny4 / 10000.0);
|
169
|
+
val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2(converted_money));
|
170
|
+
break;
|
171
|
+
}
|
172
|
+
case SYBBINARY:
|
173
|
+
case SYBIMAGE:
|
174
|
+
val = rb_str_new((char *)data, (long)data_len);
|
175
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
176
|
+
rb_enc_associate(val, binaryEncoding);
|
177
|
+
#endif
|
178
|
+
break;
|
179
|
+
case 36: { // SYBUNIQUE
|
180
|
+
char converted_unique[37];
|
181
|
+
dbconvert(rwrap->client, coltype, data, 37, SYBVARCHAR, (BYTE *)converted_unique, -1);
|
182
|
+
val = ENCODED_STR_NEW2(converted_unique);
|
183
|
+
break;
|
184
|
+
}
|
185
|
+
case SYBDATETIME4: {
|
186
|
+
DBDATETIME new_data;
|
187
|
+
dbconvert(rwrap->client, coltype, data, data_len, SYBDATETIME, (BYTE *)&new_data, sizeof(new_data));
|
188
|
+
data = (BYTE *)&new_data;
|
189
|
+
data_len = sizeof(new_data);
|
190
|
+
}
|
191
|
+
case SYBDATETIME: {
|
192
|
+
DBDATEREC date_rec;
|
193
|
+
dbdatecrack(rwrap->client, &date_rec, (DBDATETIME *)data);
|
194
|
+
int year = date_rec.dateyear,
|
195
|
+
month = date_rec.datemonth+1,
|
196
|
+
day = date_rec.datedmonth,
|
197
|
+
hour = date_rec.datehour,
|
198
|
+
min = date_rec.dateminute,
|
199
|
+
sec = date_rec.datesecond,
|
200
|
+
msec = date_rec.datemsecond;
|
201
|
+
if (year+month+day+hour+min+sec+msec != 0) {
|
202
|
+
VALUE offset = (timezone == intern_local) ? rwrap->local_offset : opt_zero;
|
203
|
+
/* Use DateTime */
|
204
|
+
if (year < 1902 || year+month+day > 2058) {
|
205
|
+
VALUE datetime_sec = INT2NUM(sec);
|
206
|
+
if (msec != 0) {
|
207
|
+
if ((opt_ruby_186 == 1 && sec < 59) || (opt_ruby_186 != 1)) {
|
208
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
209
|
+
VALUE rational_msec = rb_Rational2(INT2NUM(msec*1000), opt_onemil);
|
210
|
+
#else
|
211
|
+
VALUE rational_msec = rb_funcall(rb_cObject, intern_Rational, 2, INT2NUM(msec*1000), opt_onemil);
|
212
|
+
#endif
|
213
|
+
datetime_sec = rb_funcall(datetime_sec, intern_plus, 1, rational_msec);
|
214
|
+
}
|
215
|
+
}
|
216
|
+
val = rb_funcall(cDateTime, intern_civil, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), datetime_sec, offset);
|
217
|
+
val = rb_funcall(val, intern_new_offset, 1, offset);
|
218
|
+
/* Use Time */
|
219
|
+
} else {
|
220
|
+
val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(msec*1000));
|
221
|
+
}
|
222
|
+
}
|
223
|
+
break;
|
224
|
+
}
|
225
|
+
case SYBCHAR:
|
226
|
+
case SYBTEXT:
|
227
|
+
val = ENCODED_STR_NEW(data, data_len);
|
228
|
+
break;
|
229
|
+
default:
|
230
|
+
val = ENCODED_STR_NEW(data, data_len);
|
231
|
+
break;
|
232
|
+
}
|
233
|
+
}
|
234
|
+
if (as_array) {
|
235
|
+
rb_ary_store(row, i, val);
|
236
|
+
} else {
|
237
|
+
VALUE key;
|
238
|
+
if (rwrap->number_of_results == 0) {
|
239
|
+
key = rb_ary_entry(rwrap->fields, i);
|
240
|
+
} else {
|
241
|
+
key = rb_ary_entry(rb_ary_entry(rwrap->fields, rwrap->number_of_results), i);
|
242
|
+
}
|
243
|
+
rb_hash_aset(row, key, val);
|
244
|
+
}
|
245
|
+
}
|
246
|
+
return row;
|
247
|
+
}
|
248
|
+
|
249
|
+
|
250
|
+
// TinyTds::Client (public)
|
251
|
+
|
252
|
+
static VALUE rb_tinytds_result_fields(VALUE self) {
|
253
|
+
GET_RESULT_WRAPPER(self);
|
254
|
+
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client);
|
255
|
+
RETCODE dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
256
|
+
VALUE fields_processed = rb_ary_entry(rwrap->fields_processed, rwrap->number_of_results);
|
257
|
+
if ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED) && (fields_processed == Qnil)) {
|
258
|
+
/* Default query options. */
|
259
|
+
VALUE qopts = rb_iv_get(self, "@query_options");
|
260
|
+
int symbolize_keys = (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue) ? 1 : 0;
|
261
|
+
/* Set number_of_fields count for this result set. */
|
262
|
+
rwrap->number_of_fields = dbnumcols(rwrap->client);
|
263
|
+
if (rwrap->number_of_fields > 0) {
|
264
|
+
/* Create fields for this result set. */
|
265
|
+
unsigned int fldi = 0;
|
266
|
+
VALUE fields = rb_ary_new2(rwrap->number_of_fields);
|
267
|
+
for (fldi = 0; fldi < rwrap->number_of_fields; fldi++) {
|
268
|
+
char *colname = dbcolname(rwrap->client, fldi+1);
|
269
|
+
VALUE field = symbolize_keys ? ID2SYM(rb_intern(colname)) : rb_obj_freeze(ENCODED_STR_NEW2(colname));
|
270
|
+
rb_ary_store(fields, fldi, field);
|
271
|
+
}
|
272
|
+
/* Store the fields. */
|
273
|
+
if (rwrap->number_of_results == 0) {
|
274
|
+
rwrap->fields = fields;
|
275
|
+
} else if (rwrap->number_of_results == 1) {
|
276
|
+
VALUE multi_rs_fields = rb_ary_new();
|
277
|
+
rb_ary_store(multi_rs_fields, 0, rwrap->fields);
|
278
|
+
rb_ary_store(multi_rs_fields, 1, fields);
|
279
|
+
rwrap->fields = multi_rs_fields;
|
280
|
+
} else {
|
281
|
+
rb_ary_store(rwrap->fields, rwrap->number_of_results, fields);
|
282
|
+
}
|
283
|
+
}
|
284
|
+
rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qtrue);
|
285
|
+
}
|
286
|
+
return rwrap->fields;
|
287
|
+
}
|
288
|
+
|
289
|
+
static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
290
|
+
GET_RESULT_WRAPPER(self);
|
291
|
+
GET_CLIENT_USERDATA(rwrap->client);
|
292
|
+
/* Local Vars */
|
293
|
+
VALUE qopts, opts, block;
|
294
|
+
ID timezone;
|
295
|
+
int symbolize_keys = 0, as_array = 0, cache_rows = 0, first = 0;
|
296
|
+
/* Merge Options Hash To Query Options. Populate Opts & Block Var. */
|
297
|
+
qopts = rb_iv_get(self, "@query_options");
|
298
|
+
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1)
|
299
|
+
qopts = rb_funcall(qopts, intern_merge, 1, opts);
|
300
|
+
rb_iv_set(self, "@query_options", qopts);
|
301
|
+
/* Locals From Options */
|
302
|
+
if (rb_hash_aref(qopts, sym_first) == Qtrue)
|
303
|
+
first = 1;
|
304
|
+
if (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue)
|
305
|
+
symbolize_keys = 1;
|
306
|
+
if (rb_hash_aref(qopts, sym_as) == sym_array)
|
307
|
+
as_array = 1;
|
308
|
+
if (rb_hash_aref(qopts, sym_cache_rows) == Qtrue)
|
309
|
+
cache_rows = 1;
|
310
|
+
if (rb_hash_aref(qopts, sym_timezone) == sym_local) {
|
311
|
+
timezone = intern_local;
|
312
|
+
} else if (rb_hash_aref(qopts, sym_timezone) == sym_utc) {
|
313
|
+
timezone = intern_utc;
|
314
|
+
} else {
|
315
|
+
rb_warn(":timezone option must be :utc or :local - defaulting to :local");
|
316
|
+
timezone = intern_local;
|
317
|
+
}
|
318
|
+
/* Make The Results Or Yield Existing */
|
319
|
+
if (NIL_P(rwrap->results)) {
|
320
|
+
rwrap->results = rb_ary_new();
|
321
|
+
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client);
|
322
|
+
RETCODE dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
323
|
+
while ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED)) {
|
324
|
+
int has_rows = (DBROWS(rwrap->client) == SUCCEED) ? 1 : 0;
|
325
|
+
rb_tinytds_result_fields(self);
|
326
|
+
if (has_rows && rwrap->number_of_fields > 0) {
|
327
|
+
/* Create rows for this result set. */
|
328
|
+
unsigned long rowi = 0;
|
329
|
+
VALUE result = rb_ary_new();
|
330
|
+
while (dbnextrow(rwrap->client) != NO_MORE_ROWS) {
|
331
|
+
VALUE row = rb_tinytds_result_fetch_row(self, timezone, symbolize_keys, as_array);
|
332
|
+
if (cache_rows)
|
333
|
+
rb_ary_store(result, rowi, row);
|
334
|
+
if (!NIL_P(block))
|
335
|
+
rb_yield(row);
|
336
|
+
if (first) {
|
337
|
+
dbcanquery(rwrap->client);
|
338
|
+
userdata->dbcancel_sent = 1;
|
339
|
+
}
|
340
|
+
rowi++;
|
341
|
+
}
|
342
|
+
rwrap->number_of_rows = rowi;
|
343
|
+
/* Store the result. */
|
344
|
+
if (cache_rows) {
|
345
|
+
if (rwrap->number_of_results == 0) {
|
346
|
+
rwrap->results = result;
|
347
|
+
} else if (rwrap->number_of_results == 1) {
|
348
|
+
VALUE multi_resultsets = rb_ary_new();
|
349
|
+
rb_ary_store(multi_resultsets, 0, rwrap->results);
|
350
|
+
rb_ary_store(multi_resultsets, 1, result);
|
351
|
+
rwrap->results = multi_resultsets;
|
352
|
+
} else {
|
353
|
+
rb_ary_store(rwrap->results, rwrap->number_of_results, result);
|
354
|
+
}
|
355
|
+
}
|
356
|
+
// If we find results increment the counter that helpers use and setup the next loop.
|
357
|
+
rwrap->number_of_results = rwrap->number_of_results + 1;
|
358
|
+
dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
359
|
+
} else {
|
360
|
+
// If we do not find results, side step the rb_tinytds_result_dbresults_retcode helper and
|
361
|
+
// manually populate its memoized array while nullifing any memoized fields too before loop.
|
362
|
+
dbresults_rc = dbresults(rwrap->client);
|
363
|
+
rb_ary_store(rwrap->dbresults_retcodes, rwrap->number_of_results, INT2FIX(dbresults_rc));
|
364
|
+
rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qnil);
|
365
|
+
}
|
366
|
+
}
|
367
|
+
if (dbresults_rc == FAIL)
|
368
|
+
rb_warn("TinyTDS: Something in the dbresults() while loop set the return code to FAIL.\n");
|
369
|
+
userdata->dbsql_sent = 0;
|
370
|
+
} else if (!NIL_P(block)) {
|
371
|
+
unsigned long i;
|
372
|
+
for (i = 0; i < rwrap->number_of_rows; i++) {
|
373
|
+
rb_yield(rb_ary_entry(rwrap->results, i));
|
374
|
+
}
|
375
|
+
}
|
376
|
+
return rwrap->results;
|
377
|
+
}
|
378
|
+
|
379
|
+
static VALUE rb_tinytds_result_cancel(VALUE self) {
|
380
|
+
GET_RESULT_WRAPPER(self);
|
381
|
+
GET_CLIENT_USERDATA(rwrap->client);
|
382
|
+
if (rwrap->client && !userdata->dbcancel_sent)
|
383
|
+
rb_tinytds_result_cancel_helper(rwrap->client);
|
384
|
+
return Qtrue;
|
385
|
+
}
|
386
|
+
|
387
|
+
static VALUE rb_tinytds_result_do(VALUE self) {
|
388
|
+
GET_RESULT_WRAPPER(self);
|
389
|
+
if (rwrap->client) {
|
390
|
+
rb_tinytds_result_cancel_helper(rwrap->client);
|
391
|
+
return LONG2NUM((long)dbcount(rwrap->client));
|
392
|
+
} else {
|
393
|
+
return Qnil;
|
394
|
+
}
|
395
|
+
}
|
396
|
+
|
397
|
+
static VALUE rb_tinytds_result_affected_rows(VALUE self) {
|
398
|
+
GET_RESULT_WRAPPER(self);
|
399
|
+
if (rwrap->client) {
|
400
|
+
return LONG2NUM((long)dbcount(rwrap->client));
|
401
|
+
} else {
|
402
|
+
return Qnil;
|
403
|
+
}
|
404
|
+
}
|
405
|
+
|
406
|
+
/* Duplicated in client.c */
|
407
|
+
static VALUE rb_tinytds_result_return_code(VALUE self) {
|
408
|
+
GET_RESULT_WRAPPER(self);
|
409
|
+
if (rwrap->client && dbhasretstat(rwrap->client)) {
|
410
|
+
return LONG2NUM((long)dbretstatus(rwrap->client));
|
411
|
+
} else {
|
412
|
+
return Qnil;
|
413
|
+
}
|
414
|
+
}
|
415
|
+
|
416
|
+
static VALUE rb_tinytds_result_insert(VALUE self) {
|
417
|
+
GET_RESULT_WRAPPER(self);
|
418
|
+
if (rwrap->client) {
|
419
|
+
rb_tinytds_result_cancel_helper(rwrap->client);
|
420
|
+
VALUE identity = Qnil;
|
421
|
+
dbcmd(rwrap->client, "SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident");
|
422
|
+
if (dbsqlexec(rwrap->client) != FAIL && dbresults(rwrap->client) != FAIL && DBROWS(rwrap->client) != FAIL) {
|
423
|
+
while (dbnextrow(rwrap->client) != NO_MORE_ROWS) {
|
424
|
+
int col = 1;
|
425
|
+
BYTE *data = dbdata(rwrap->client, col);
|
426
|
+
DBINT data_len = dbdatlen(rwrap->client, col);
|
427
|
+
int null_val = ((data == NULL) && (data_len == 0));
|
428
|
+
if (!null_val)
|
429
|
+
identity = LONG2NUM(*(long *)data);
|
430
|
+
}
|
431
|
+
}
|
432
|
+
return identity;
|
433
|
+
} else {
|
434
|
+
return Qnil;
|
435
|
+
}
|
436
|
+
}
|
437
|
+
|
438
|
+
|
439
|
+
// Lib Init
|
440
|
+
|
441
|
+
void init_tinytds_result() {
|
442
|
+
/* Data Classes */
|
443
|
+
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
444
|
+
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
445
|
+
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
446
|
+
/* Define TinyTds::Result */
|
447
|
+
cTinyTdsResult = rb_define_class_under(mTinyTds, "Result", rb_cObject);
|
448
|
+
/* Define TinyTds::Result Public Methods */
|
449
|
+
rb_define_method(cTinyTdsResult, "fields", rb_tinytds_result_fields, 0);
|
450
|
+
rb_define_method(cTinyTdsResult, "each", rb_tinytds_result_each, -1);
|
451
|
+
rb_define_method(cTinyTdsResult, "cancel", rb_tinytds_result_cancel, 0);
|
452
|
+
rb_define_method(cTinyTdsResult, "do", rb_tinytds_result_do, 0);
|
453
|
+
rb_define_method(cTinyTdsResult, "affected_rows", rb_tinytds_result_affected_rows, 0);
|
454
|
+
rb_define_method(cTinyTdsResult, "return_code", rb_tinytds_result_return_code, 0);
|
455
|
+
rb_define_method(cTinyTdsResult, "insert", rb_tinytds_result_insert, 0);
|
456
|
+
/* Intern String Helpers */
|
457
|
+
intern_new = rb_intern("new");
|
458
|
+
intern_utc = rb_intern("utc");
|
459
|
+
intern_local = rb_intern("local");
|
460
|
+
intern_merge = rb_intern("merge");
|
461
|
+
intern_localtime = rb_intern("localtime");
|
462
|
+
intern_civil = rb_intern("civil");
|
463
|
+
intern_new_offset = rb_intern("new_offset");
|
464
|
+
intern_plus = rb_intern("+");
|
465
|
+
intern_divide = rb_intern("/");
|
466
|
+
intern_Rational = rb_intern("Rational");
|
467
|
+
/* Symbol Helpers */
|
468
|
+
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
|
469
|
+
sym_as = ID2SYM(rb_intern("as"));
|
470
|
+
sym_array = ID2SYM(rb_intern("array"));
|
471
|
+
sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
|
472
|
+
sym_first = ID2SYM(rb_intern("first"));
|
473
|
+
sym_local = ID2SYM(intern_local);
|
474
|
+
sym_utc = ID2SYM(intern_utc);
|
475
|
+
sym_timezone = ID2SYM(rb_intern("timezone"));
|
476
|
+
/* Data Conversion Options */
|
477
|
+
opt_decimal_zero = rb_str_new2("0.0");
|
478
|
+
rb_global_variable(&opt_decimal_zero);
|
479
|
+
opt_float_zero = rb_float_new((double)0);
|
480
|
+
rb_global_variable(&opt_float_zero);
|
481
|
+
opt_one = INT2NUM(1);
|
482
|
+
opt_zero = INT2NUM(0);
|
483
|
+
opt_four = INT2NUM(4);
|
484
|
+
opt_19hdr = INT2NUM(1900);
|
485
|
+
opt_tenk = INT2NUM(10000);
|
486
|
+
opt_onemil = INT2NUM(1000000);
|
487
|
+
/* Ruby version flags */
|
488
|
+
opt_ruby_186 = (rb_eval_string("RUBY_VERSION == '1.8.6'") == Qtrue) ? 1 : 0;
|
489
|
+
/* Encoding */
|
490
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
491
|
+
binaryEncoding = rb_enc_find("binary");
|
492
|
+
#endif
|
493
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
#ifndef TINYTDS_RESULT_H
|
3
|
+
#define TINYTDS_RESULT_H
|
4
|
+
|
5
|
+
// TODO: Is this needed?
|
6
|
+
typedef tds_sysdep_int64_type DBBIGINT; /* Missing in sybdb.h ?!?! */
|
7
|
+
|
8
|
+
void init_tinytds_result();
|
9
|
+
VALUE rb_tinytds_new_result_obj(DBPROCESS *c);
|
10
|
+
|
11
|
+
typedef struct {
|
12
|
+
DBPROCESS *client;
|
13
|
+
VALUE local_offset;
|
14
|
+
VALUE fields;
|
15
|
+
VALUE fields_processed;
|
16
|
+
VALUE results;
|
17
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
18
|
+
rb_encoding *encoding;
|
19
|
+
#endif
|
20
|
+
VALUE dbresults_retcodes;
|
21
|
+
unsigned int number_of_results;
|
22
|
+
unsigned int number_of_fields;
|
23
|
+
unsigned long number_of_rows;
|
24
|
+
} tinytds_result_wrapper;
|
25
|
+
|
26
|
+
|
27
|
+
// Lib Macros
|
28
|
+
|
29
|
+
#define GET_RESULT_WRAPPER(self) \
|
30
|
+
tinytds_result_wrapper *rwrap; \
|
31
|
+
Data_Get_Struct(self, tinytds_result_wrapper, rwrap)
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
#endif
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#ifndef TINYTDS_EXT
|
2
|
+
#define TINYTDS_EXT
|
3
|
+
|
4
|
+
#undef MSDBLIB
|
5
|
+
#define SYBDBLIB
|
6
|
+
|
7
|
+
#include <ruby.h>
|
8
|
+
#include <sybfront.h>
|
9
|
+
#include <sybdb.h>
|
10
|
+
|
11
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
12
|
+
#include <ruby/encoding.h>
|
13
|
+
#endif
|
14
|
+
|
15
|
+
#include <client.h>
|
16
|
+
#include <result.h>
|
17
|
+
|
18
|
+
#endif
|
Binary file
|
Binary file
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module TinyTds
|
2
|
+
class Client
|
3
|
+
|
4
|
+
TDS_VERSIONS_SETTERS = {
|
5
|
+
'unknown' => 0,
|
6
|
+
'46' => 1,
|
7
|
+
'100' => 2,
|
8
|
+
'42' => 3,
|
9
|
+
'70' => 4,
|
10
|
+
'80' => 5,
|
11
|
+
'90' => 6 # TODO: untested
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
TDS_VERSIONS_GETTERS = {
|
15
|
+
0 => {:name => 'DBTDS_UNKNOWN', :description => 'Unknown'},
|
16
|
+
1 => {:name => 'DBTDS_2_0', :description => 'Pre 4.0 SQL Server'},
|
17
|
+
2 => {:name => 'DBTDS_3_4', :description => 'Microsoft SQL Server (3.0)'},
|
18
|
+
3 => {:name => 'DBTDS_4_0', :description => '4.0 SQL Server'},
|
19
|
+
4 => {:name => 'DBTDS_4_2', :description => '4.2 SQL Server'},
|
20
|
+
5 => {:name => 'DBTDS_4_6', :description => '2.0 OpenServer and 4.6 SQL Server.'},
|
21
|
+
6 => {:name => 'DBTDS_4_9_5', :description => '4.9.5 (NCR) SQL Server'},
|
22
|
+
7 => {:name => 'DBTDS_5_0', :description => '5.0 SQL Server'},
|
23
|
+
8 => {:name => 'DBTDS_7_0', :description => 'Microsoft SQL Server 7.0'},
|
24
|
+
9 => {:name => 'DBTDS_8_0', :description => 'Microsoft SQL Server 2000'},
|
25
|
+
10 => {:name => 'DBTDS_9_0', :description => 'Microsoft SQL Server 2005'}
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
@@default_query_options = {
|
29
|
+
:as => :hash,
|
30
|
+
:symbolize_keys => false,
|
31
|
+
:cache_rows => true,
|
32
|
+
:timezone => :local
|
33
|
+
}
|
34
|
+
|
35
|
+
attr_reader :query_options
|
36
|
+
|
37
|
+
def self.default_query_options
|
38
|
+
@@default_query_options
|
39
|
+
end
|
40
|
+
|
41
|
+
# Most, if not all, iconv encoding names can be found by ruby. Just in case, you can
|
42
|
+
# overide this method to return a string name that Encoding.find would work with. Default
|
43
|
+
# is to return the passed encoding.
|
44
|
+
def self.transpose_iconv_encoding(encoding)
|
45
|
+
encoding
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def initialize(opts={})
|
50
|
+
raise ArgumentError, 'missing :username option' if opts[:username].to_s.empty?
|
51
|
+
raise ArgumentError, 'missing :host option if no :dataserver given' if opts[:dataserver].to_s.empty? && opts[:host].to_s.empty?
|
52
|
+
@query_options = @@default_query_options.dup
|
53
|
+
opts[:appname] ||= 'TinyTds'
|
54
|
+
opts[:tds_version] = TDS_VERSIONS_SETTERS[opts[:tds_version].to_s] || TDS_VERSIONS_SETTERS['80']
|
55
|
+
opts[:login_timeout] ||= 60
|
56
|
+
opts[:timeout] ||= 5
|
57
|
+
opts[:encoding] = (opts[:encoding].nil? || opts[:encoding].downcase == 'utf8') ? 'UTF-8' : opts[:encoding].upcase
|
58
|
+
opts[:port] ||= 1433
|
59
|
+
opts[:dataserver] = "#{opts[:host]}:#{opts[:port]}" if opts[:dataserver].to_s.empty?
|
60
|
+
connect(opts)
|
61
|
+
end
|
62
|
+
|
63
|
+
def tds_version_info
|
64
|
+
info = TDS_VERSIONS_GETTERS[tds_version]
|
65
|
+
"#{info[:name]} - #{info[:description]}" if info
|
66
|
+
end
|
67
|
+
|
68
|
+
def active?
|
69
|
+
!closed? && !dead?
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def self.local_offset
|
75
|
+
::Time.local(2010).utc_offset.to_r / 86400
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module TinyTds
|
2
|
+
class Error < StandardError
|
3
|
+
|
4
|
+
SEVERITIES = [
|
5
|
+
{:number => 1, :severity => 'EXINFO', :explanation => 'Informational, non-error.'},
|
6
|
+
{:number => 2, :severity => 'EXUSER', :explanation => 'User error.'},
|
7
|
+
{:number => 3, :severity => 'EXNONFATAL', :explanation => 'Non-fatal error.'},
|
8
|
+
{:number => 4, :severity => 'EXCONVERSION', :explanation => 'Error in DB-Library data conversion.'},
|
9
|
+
{:number => 5, :severity => 'EXSERVER', :explanation => 'The Server has returned an error flag.'},
|
10
|
+
{:number => 6, :severity => 'EXTIME', :explanation => 'We have exceeded our timeout period while waiting for a response from the Server - the DBPROCESS is still alive.'},
|
11
|
+
{:number => 7, :severity => 'EXPROGRAM', :explanation => 'Coding error in user program.'},
|
12
|
+
{:number => 8, :severity => 'EXRESOURCE', :explanation => 'Running out of resources - the DBPROCESS may be dead.'},
|
13
|
+
{:number => 9, :severity => 'EXCOMM', :explanation => 'Failure in communication with Server - the DBPROCESS is dead.'},
|
14
|
+
{:number => 10, :severity => 'EXFATAL', :explanation => 'Fatal error - the DBPROCESS is dead.'},
|
15
|
+
{:number => 11, :severity => 'EXCONSISTENCY', :explanation => 'Internal software error - notify Sybase Technical Support.'}
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
attr_accessor :source, :severity, :db_error_number, :os_error_number
|
19
|
+
|
20
|
+
def initialize(message)
|
21
|
+
super
|
22
|
+
@severity = nil
|
23
|
+
@db_error_number = nil
|
24
|
+
@os_error_number = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|