tiny_tds 0.6.2-x64-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +19 -0
- data/CHANGELOG +180 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +23 -0
- data/README.md +385 -0
- data/Rakefile +114 -0
- data/compile/rake-compiler-dev-box.patch +31 -0
- data/ext/patch/Makefile.in.diff +29 -0
- data/ext/patch/dblib-30-char-username.diff +11 -0
- data/ext/patch/sspi_w_kerberos.diff +42 -0
- data/ext/tiny_tds/client.c +408 -0
- data/ext/tiny_tds/client.h +46 -0
- data/ext/tiny_tds/extconf.rb +102 -0
- data/ext/tiny_tds/result.c +599 -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 +15 -0
- data/lib/tiny_tds.rb +19 -0
- data/lib/tiny_tds/client.rb +96 -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/tasks/ports.rake +79 -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/client_test.rb +170 -0
- data/test/result_test.rb +732 -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/sybase_ase.sql +138 -0
- data/test/schema_test.rb +305 -0
- data/test/test_helper.rb +195 -0
- data/test/thread_test.rb +95 -0
- metadata +171 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
#ifndef TINYTDS_CLIENT_H
|
3
|
+
#define TINYTDS_CLIENT_H
|
4
|
+
|
5
|
+
void init_tinytds_client();
|
6
|
+
|
7
|
+
typedef struct {
|
8
|
+
short int is_set;
|
9
|
+
int cancel;
|
10
|
+
char error[1024];
|
11
|
+
char source[1024];
|
12
|
+
int severity;
|
13
|
+
int dberr;
|
14
|
+
int oserr;
|
15
|
+
} tinytds_errordata;
|
16
|
+
|
17
|
+
typedef struct {
|
18
|
+
short int closed;
|
19
|
+
short int timing_out;
|
20
|
+
short int dbsql_sent;
|
21
|
+
short int dbsqlok_sent;
|
22
|
+
RETCODE dbsqlok_retcode;
|
23
|
+
short int dbcancel_sent;
|
24
|
+
short int nonblocking;
|
25
|
+
tinytds_errordata nonblocking_error;
|
26
|
+
} tinytds_client_userdata;
|
27
|
+
|
28
|
+
typedef struct {
|
29
|
+
LOGINREC *login;
|
30
|
+
RETCODE return_code;
|
31
|
+
DBPROCESS *client;
|
32
|
+
short int closed;
|
33
|
+
VALUE charset;
|
34
|
+
tinytds_client_userdata *userdata;
|
35
|
+
const char *identity_insert_sql;
|
36
|
+
rb_encoding *encoding;
|
37
|
+
} tinytds_client_wrapper;
|
38
|
+
|
39
|
+
|
40
|
+
// Lib Macros
|
41
|
+
|
42
|
+
#define GET_CLIENT_USERDATA(dbproc) \
|
43
|
+
tinytds_client_userdata *userdata = (tinytds_client_userdata *)dbgetuserdata(dbproc);
|
44
|
+
|
45
|
+
|
46
|
+
#endif
|
@@ -0,0 +1,102 @@
|
|
1
|
+
ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
|
2
|
+
|
3
|
+
# :stopdoc:
|
4
|
+
|
5
|
+
require 'mkmf'
|
6
|
+
|
7
|
+
# Shamelessly copied from nokogiri
|
8
|
+
#
|
9
|
+
|
10
|
+
FREETDSDIR = ENV['FREETDS_DIR']
|
11
|
+
|
12
|
+
if FREETDSDIR.nil? || FREETDSDIR.empty?
|
13
|
+
LIBDIR = RbConfig::CONFIG['libdir']
|
14
|
+
INCLUDEDIR = RbConfig::CONFIG['includedir']
|
15
|
+
else
|
16
|
+
puts "Will use #{FREETDSDIR}"
|
17
|
+
LIBDIR = "#{FREETDSDIR}/lib"
|
18
|
+
INCLUDEDIR = "#{FREETDSDIR}/include"
|
19
|
+
end
|
20
|
+
|
21
|
+
$CFLAGS << " #{ENV["CFLAGS"]}"
|
22
|
+
$LDFLAGS << " #{ENV["LDFLAGS"]}"
|
23
|
+
$LIBS << " #{ENV["LIBS"]}"
|
24
|
+
|
25
|
+
SEARCHABLE_PATHS = begin
|
26
|
+
eop_regexp = /#{File::SEPARATOR}bin$/
|
27
|
+
paths = ENV['PATH']
|
28
|
+
paths = paths.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
29
|
+
paths = paths.split(File::PATH_SEPARATOR)
|
30
|
+
bin_paths = paths.select{ |p| p =~ eop_regexp }
|
31
|
+
bin_paths.map{ |p| p.sub(eop_regexp,'') }.compact.reject{ |p| p.empty? }.uniq
|
32
|
+
end
|
33
|
+
|
34
|
+
def searchable_paths_with_directories(*directories)
|
35
|
+
SEARCHABLE_PATHS.map do |path|
|
36
|
+
directories.map do |paths|
|
37
|
+
dir = File.join path, *paths
|
38
|
+
File.directory?(dir) ? dir : nil
|
39
|
+
end.flatten.compact
|
40
|
+
end.flatten.compact
|
41
|
+
end
|
42
|
+
|
43
|
+
if RbConfig::CONFIG['target_os'] =~ /mswin32|mingw32/
|
44
|
+
lib_prefix = 'lib' unless RbConfig::CONFIG['target_os'] =~ /mingw32/
|
45
|
+
# There's no default include/lib dir on Windows. Let's just add the Ruby ones
|
46
|
+
# and resort on the search path specified by INCLUDE and LIB environment
|
47
|
+
# variables
|
48
|
+
HEADER_DIRS = [INCLUDEDIR]
|
49
|
+
LIB_DIRS = [LIBDIR]
|
50
|
+
else
|
51
|
+
lib_prefix = ''
|
52
|
+
HEADER_DIRS = [
|
53
|
+
# First search /opt/local for macports
|
54
|
+
'/opt/local/include',
|
55
|
+
# Then search /usr/local for people that installed from source
|
56
|
+
'/usr/local/include',
|
57
|
+
# Check the ruby install locations
|
58
|
+
INCLUDEDIR,
|
59
|
+
# Finally fall back to /usr
|
60
|
+
'/usr/include'
|
61
|
+
].reject{ |dir| !File.directory?(dir) }
|
62
|
+
LIB_DIRS = [
|
63
|
+
# First search /opt/local for macports
|
64
|
+
'/opt/local/lib',
|
65
|
+
# Then search /usr/local for people that installed from source
|
66
|
+
'/usr/local/lib',
|
67
|
+
# Check the ruby install locations
|
68
|
+
LIBDIR,
|
69
|
+
# Finally fall back to /usr
|
70
|
+
'/usr/lib',
|
71
|
+
].reject{ |dir| !File.directory?(dir) }
|
72
|
+
end
|
73
|
+
|
74
|
+
FREETDS_HEADER_DIRS = (searchable_paths_with_directories(['include'],['include','freetds']) + HEADER_DIRS).uniq
|
75
|
+
FREETDS_LIB_DIRS = (searchable_paths_with_directories(['lib'],['lib','freetds']) + LIB_DIRS).uniq
|
76
|
+
|
77
|
+
# lookup over searchable paths is great for native compilation, however, when
|
78
|
+
# cross compiling we need to specify our own paths.
|
79
|
+
if enable_config("lookup", true)
|
80
|
+
dir_config('iconv', FREETDS_HEADER_DIRS, FREETDS_LIB_DIRS)
|
81
|
+
dir_config('freetds', FREETDS_HEADER_DIRS, FREETDS_LIB_DIRS)
|
82
|
+
else
|
83
|
+
dir_config('iconv')
|
84
|
+
dir_config('freetds')
|
85
|
+
|
86
|
+
# remove LDFLAGS
|
87
|
+
$LDFLAGS = ENV.fetch("LDFLAGS", "")
|
88
|
+
end
|
89
|
+
|
90
|
+
def asplode(lib)
|
91
|
+
abort "-----\n#{lib} is missing.\n-----"
|
92
|
+
end
|
93
|
+
|
94
|
+
asplode 'libiconv' unless have_func('iconv_open', 'iconv.h') || have_library('iconv', 'iconv_open', 'iconv.h')
|
95
|
+
asplode 'freetds' unless have_header('sybfront.h') && have_header('sybdb.h')
|
96
|
+
|
97
|
+
asplode 'freetds' unless find_library("#{lib_prefix}sybdb", 'tdsdbopen')
|
98
|
+
asplode 'freetds' unless find_library("#{lib_prefix}ct", 'ct_bind')
|
99
|
+
|
100
|
+
create_makefile('tiny_tds/tiny_tds')
|
101
|
+
|
102
|
+
# :startdoc:
|
@@ -0,0 +1,599 @@
|
|
1
|
+
|
2
|
+
#include <tiny_tds_ext.h>
|
3
|
+
#include <stdint.h>
|
4
|
+
|
5
|
+
// TINY_TDS_MAX_TIME
|
6
|
+
|
7
|
+
#define TINY_TDS_MAX_TIME 315607276799ULL
|
8
|
+
|
9
|
+
|
10
|
+
// TINY_TDS_MIN_TIME
|
11
|
+
|
12
|
+
#define TINY_TDS_MIN_TIME 2678400ULL
|
13
|
+
|
14
|
+
|
15
|
+
// File Types/Vars
|
16
|
+
|
17
|
+
VALUE cTinyTdsResult;
|
18
|
+
extern VALUE mTinyTds, cTinyTdsClient, cTinyTdsError;
|
19
|
+
VALUE cBigDecimal, cDate, cDateTime;
|
20
|
+
VALUE opt_decimal_zero, opt_float_zero, opt_one, opt_zero, opt_four, opt_19hdr, opt_tenk, opt_onemil;
|
21
|
+
int opt_ruby_186;
|
22
|
+
static ID intern_new, intern_utc, intern_local, intern_localtime, intern_merge,
|
23
|
+
intern_civil, intern_new_offset, intern_plus, intern_divide, intern_Rational;
|
24
|
+
static ID sym_symbolize_keys, sym_as, sym_array, sym_cache_rows, sym_first, sym_timezone, sym_local, sym_utc, sym_empty_sets;
|
25
|
+
|
26
|
+
|
27
|
+
// Lib Macros
|
28
|
+
|
29
|
+
rb_encoding *binaryEncoding;
|
30
|
+
#define ENCODED_STR_NEW(_data, _len) ({ \
|
31
|
+
VALUE _val = rb_str_new((char *)_data, (long)_len); \
|
32
|
+
rb_enc_associate(_val, rwrap->encoding); \
|
33
|
+
_val; \
|
34
|
+
})
|
35
|
+
#define ENCODED_STR_NEW2(_data2) ({ \
|
36
|
+
VALUE _val = rb_str_new2((char *)_data2); \
|
37
|
+
rb_enc_associate(_val, rwrap->encoding); \
|
38
|
+
_val; \
|
39
|
+
})
|
40
|
+
|
41
|
+
|
42
|
+
// Lib Backend (Memory Management)
|
43
|
+
|
44
|
+
static void rb_tinytds_result_mark(void *ptr) {
|
45
|
+
tinytds_result_wrapper *rwrap = (tinytds_result_wrapper *)ptr;
|
46
|
+
if (rwrap) {
|
47
|
+
rb_gc_mark(rwrap->local_offset);
|
48
|
+
rb_gc_mark(rwrap->fields);
|
49
|
+
rb_gc_mark(rwrap->fields_processed);
|
50
|
+
rb_gc_mark(rwrap->results);
|
51
|
+
rb_gc_mark(rwrap->dbresults_retcodes);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
static void rb_tinytds_result_free(void *ptr) {
|
56
|
+
tinytds_result_wrapper *rwrap = (tinytds_result_wrapper *)ptr;
|
57
|
+
xfree(ptr);
|
58
|
+
}
|
59
|
+
|
60
|
+
VALUE rb_tinytds_new_result_obj(tinytds_client_wrapper *cwrap) {
|
61
|
+
VALUE obj;
|
62
|
+
tinytds_result_wrapper *rwrap;
|
63
|
+
obj = Data_Make_Struct(cTinyTdsResult, tinytds_result_wrapper, rb_tinytds_result_mark, rb_tinytds_result_free, rwrap);
|
64
|
+
rwrap->cwrap = cwrap;
|
65
|
+
rwrap->client = cwrap->client;
|
66
|
+
rwrap->local_offset = Qnil;
|
67
|
+
rwrap->fields = rb_ary_new();
|
68
|
+
rwrap->fields_processed = rb_ary_new();
|
69
|
+
rwrap->results = Qnil;
|
70
|
+
rwrap->dbresults_retcodes = rb_ary_new();
|
71
|
+
rwrap->number_of_results = 0;
|
72
|
+
rwrap->number_of_fields = 0;
|
73
|
+
rwrap->number_of_rows = 0;
|
74
|
+
rb_obj_call_init(obj, 0, NULL);
|
75
|
+
return obj;
|
76
|
+
}
|
77
|
+
|
78
|
+
// No GVL Helpers
|
79
|
+
|
80
|
+
#define NOGVL_DBCALL(_dbfunction, _client) ( \
|
81
|
+
(RETCODE)rb_thread_blocking_region( \
|
82
|
+
(rb_blocking_function_t*)_dbfunction, _client, \
|
83
|
+
(rb_unblock_function_t*)dbcancel_ubf, _client ) \
|
84
|
+
)
|
85
|
+
|
86
|
+
static void dbcancel_ubf(DBPROCESS *client) {
|
87
|
+
GET_CLIENT_USERDATA(client);
|
88
|
+
dbcancel(client);
|
89
|
+
userdata->dbcancel_sent = 1;
|
90
|
+
}
|
91
|
+
|
92
|
+
static void nogvl_setup(DBPROCESS *client) {
|
93
|
+
GET_CLIENT_USERDATA(client);
|
94
|
+
userdata->nonblocking = 1;
|
95
|
+
}
|
96
|
+
|
97
|
+
static void nogvl_cleanup(DBPROCESS *client) {
|
98
|
+
GET_CLIENT_USERDATA(client);
|
99
|
+
userdata->nonblocking = 0;
|
100
|
+
/*
|
101
|
+
Now that the blocking operation is done, we can finally throw any
|
102
|
+
exceptions based on errors from SQL Server.
|
103
|
+
*/
|
104
|
+
if (userdata->nonblocking_error.is_set) {
|
105
|
+
userdata->nonblocking_error.is_set = 0;
|
106
|
+
rb_tinytds_raise_error(client,
|
107
|
+
userdata->nonblocking_error.cancel,
|
108
|
+
&userdata->nonblocking_error.error,
|
109
|
+
&userdata->nonblocking_error.source,
|
110
|
+
userdata->nonblocking_error.severity,
|
111
|
+
userdata->nonblocking_error.dberr,
|
112
|
+
userdata->nonblocking_error.oserr);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
static RETCODE nogvl_dbsqlok(DBPROCESS *client) {
|
117
|
+
int retcode = FAIL;
|
118
|
+
GET_CLIENT_USERDATA(client);
|
119
|
+
nogvl_setup(client);
|
120
|
+
retcode = NOGVL_DBCALL(dbsqlok, client);
|
121
|
+
nogvl_cleanup(client);
|
122
|
+
userdata->dbsqlok_sent = 1;
|
123
|
+
return retcode;
|
124
|
+
}
|
125
|
+
|
126
|
+
static RETCODE nogvl_dbsqlexec(DBPROCESS *client) {
|
127
|
+
int retcode = FAIL;
|
128
|
+
nogvl_setup(client);
|
129
|
+
retcode = NOGVL_DBCALL(dbsqlexec, client);
|
130
|
+
nogvl_cleanup(client);
|
131
|
+
return retcode;
|
132
|
+
}
|
133
|
+
|
134
|
+
static RETCODE nogvl_dbresults(DBPROCESS *client) {
|
135
|
+
int retcode = FAIL;
|
136
|
+
nogvl_setup(client);
|
137
|
+
retcode = NOGVL_DBCALL(dbresults, client);
|
138
|
+
nogvl_cleanup(client);
|
139
|
+
return retcode;
|
140
|
+
}
|
141
|
+
|
142
|
+
static RETCODE nogvl_dbnextrow(DBPROCESS * client) {
|
143
|
+
int retcode = FAIL;
|
144
|
+
nogvl_setup(client);
|
145
|
+
retcode = NOGVL_DBCALL(dbnextrow, client);
|
146
|
+
nogvl_cleanup(client);
|
147
|
+
return retcode;
|
148
|
+
}
|
149
|
+
|
150
|
+
// Lib Backend (Helpers)
|
151
|
+
|
152
|
+
static RETCODE rb_tinytds_result_dbresults_retcode(VALUE self) {
|
153
|
+
GET_RESULT_WRAPPER(self);
|
154
|
+
VALUE ruby_rc;
|
155
|
+
RETCODE db_rc;
|
156
|
+
ruby_rc = rb_ary_entry(rwrap->dbresults_retcodes, rwrap->number_of_results);
|
157
|
+
if (NIL_P(ruby_rc)) {
|
158
|
+
db_rc = nogvl_dbresults(rwrap->client);
|
159
|
+
ruby_rc = INT2FIX(db_rc);
|
160
|
+
rb_ary_store(rwrap->dbresults_retcodes, rwrap->number_of_results, ruby_rc);
|
161
|
+
} else {
|
162
|
+
db_rc = FIX2INT(ruby_rc);
|
163
|
+
}
|
164
|
+
return db_rc;
|
165
|
+
}
|
166
|
+
|
167
|
+
static RETCODE rb_tinytds_result_ok_helper(DBPROCESS *client) {
|
168
|
+
GET_CLIENT_USERDATA(client);
|
169
|
+
if (userdata->dbsqlok_sent == 0) {
|
170
|
+
userdata->dbsqlok_retcode = nogvl_dbsqlok(client);
|
171
|
+
}
|
172
|
+
return userdata->dbsqlok_retcode;
|
173
|
+
}
|
174
|
+
|
175
|
+
static void rb_tinytds_result_exec_helper(DBPROCESS *client) {
|
176
|
+
GET_CLIENT_USERDATA(client);
|
177
|
+
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(client);
|
178
|
+
if (dbsqlok_rc == SUCCEED) {
|
179
|
+
/*
|
180
|
+
This is to just process each result set. Commands such as backup and
|
181
|
+
restore are not done when the first result set is returned, so we need to
|
182
|
+
exhaust the result sets before it is complete.
|
183
|
+
*/
|
184
|
+
while (nogvl_dbresults(client) == SUCCEED) {
|
185
|
+
/*
|
186
|
+
If we don't loop through each row for calls to TinyTds::Result.do that
|
187
|
+
actually do return result sets, we will trigger error 20019 about trying
|
188
|
+
to execute a new command with pending results. Oh well.
|
189
|
+
*/
|
190
|
+
while (dbnextrow(client) != NO_MORE_ROWS);
|
191
|
+
}
|
192
|
+
}
|
193
|
+
dbcancel(client);
|
194
|
+
userdata->dbcancel_sent = 1;
|
195
|
+
userdata->dbsql_sent = 0;
|
196
|
+
}
|
197
|
+
|
198
|
+
static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_keys, int as_array) {
|
199
|
+
/* Wrapper And Local Vars */
|
200
|
+
GET_RESULT_WRAPPER(self);
|
201
|
+
/* Create Empty Row */
|
202
|
+
VALUE row = as_array ? rb_ary_new2(rwrap->number_of_fields) : rb_hash_new();
|
203
|
+
/* Storing Values */
|
204
|
+
unsigned int i = 0;
|
205
|
+
for (i = 0; i < rwrap->number_of_fields; i++) {
|
206
|
+
VALUE val = Qnil;
|
207
|
+
int col = i+1;
|
208
|
+
int coltype = dbcoltype(rwrap->client, col);
|
209
|
+
BYTE *data = dbdata(rwrap->client, col);
|
210
|
+
DBINT data_len = dbdatlen(rwrap->client, col);
|
211
|
+
int null_val = ((data == NULL) && (data_len == 0));
|
212
|
+
if (!null_val) {
|
213
|
+
switch(coltype) {
|
214
|
+
case SYBINT1:
|
215
|
+
val = INT2FIX(*(DBTINYINT *)data);
|
216
|
+
break;
|
217
|
+
case SYBINT2:
|
218
|
+
val = INT2FIX(*(DBSMALLINT *)data);
|
219
|
+
break;
|
220
|
+
case SYBINT4:
|
221
|
+
val = INT2NUM(*(DBINT *)data);
|
222
|
+
break;
|
223
|
+
case SYBINT8:
|
224
|
+
val = LL2NUM(*(DBBIGINT *)data);
|
225
|
+
break;
|
226
|
+
case SYBBIT:
|
227
|
+
val = *(int *)data ? Qtrue : Qfalse;
|
228
|
+
break;
|
229
|
+
case SYBNUMERIC:
|
230
|
+
case SYBDECIMAL: {
|
231
|
+
DBTYPEINFO *data_info = dbcoltypeinfo(rwrap->client, col);
|
232
|
+
int data_slength = (int)data_info->precision + (int)data_info->scale + 1;
|
233
|
+
char converted_decimal[data_slength];
|
234
|
+
dbconvert(rwrap->client, coltype, data, data_len, SYBVARCHAR, (BYTE *)converted_decimal, -1);
|
235
|
+
val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2((char *)converted_decimal));
|
236
|
+
break;
|
237
|
+
}
|
238
|
+
case SYBFLT8: {
|
239
|
+
double col_to_double = *(double *)data;
|
240
|
+
val = (col_to_double == 0.000000) ? opt_float_zero : rb_float_new(col_to_double);
|
241
|
+
break;
|
242
|
+
}
|
243
|
+
case SYBREAL: {
|
244
|
+
float col_to_float = *(float *)data;
|
245
|
+
val = (col_to_float == 0.0) ? opt_float_zero : rb_float_new(col_to_float);
|
246
|
+
break;
|
247
|
+
}
|
248
|
+
case SYBMONEY: {
|
249
|
+
DBMONEY *money = (DBMONEY *)data;
|
250
|
+
char converted_money[25];
|
251
|
+
long long money_value = ((long long)money->mnyhigh << 32) | money->mnylow;
|
252
|
+
sprintf(converted_money, "%lld", money_value);
|
253
|
+
val = rb_funcall(cBigDecimal, intern_new, 2, rb_str_new2(converted_money), opt_four);
|
254
|
+
val = rb_funcall(val, intern_divide, 1, opt_tenk);
|
255
|
+
break;
|
256
|
+
}
|
257
|
+
case SYBMONEY4: {
|
258
|
+
DBMONEY4 *money = (DBMONEY4 *)data;
|
259
|
+
char converted_money[20];
|
260
|
+
sprintf(converted_money, "%f", money->mny4 / 10000.0);
|
261
|
+
val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2(converted_money));
|
262
|
+
break;
|
263
|
+
}
|
264
|
+
case SYBBINARY:
|
265
|
+
case SYBIMAGE:
|
266
|
+
val = rb_str_new((char *)data, (long)data_len);
|
267
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
268
|
+
rb_enc_associate(val, binaryEncoding);
|
269
|
+
#endif
|
270
|
+
break;
|
271
|
+
case 36: { // SYBUNIQUE
|
272
|
+
char converted_unique[37];
|
273
|
+
dbconvert(rwrap->client, coltype, data, 37, SYBVARCHAR, (BYTE *)converted_unique, -1);
|
274
|
+
val = ENCODED_STR_NEW2(converted_unique);
|
275
|
+
break;
|
276
|
+
}
|
277
|
+
case SYBDATETIME4: {
|
278
|
+
DBDATETIME new_data;
|
279
|
+
dbconvert(rwrap->client, coltype, data, data_len, SYBDATETIME, (BYTE *)&new_data, sizeof(new_data));
|
280
|
+
data = (BYTE *)&new_data;
|
281
|
+
data_len = sizeof(new_data);
|
282
|
+
}
|
283
|
+
case SYBDATETIME: {
|
284
|
+
DBDATEREC date_rec;
|
285
|
+
dbdatecrack(rwrap->client, &date_rec, (DBDATETIME *)data);
|
286
|
+
int year = date_rec.dateyear,
|
287
|
+
month = date_rec.datemonth+1,
|
288
|
+
day = date_rec.datedmonth,
|
289
|
+
hour = date_rec.datehour,
|
290
|
+
min = date_rec.dateminute,
|
291
|
+
sec = date_rec.datesecond,
|
292
|
+
msec = date_rec.datemsecond;
|
293
|
+
if (year+month+day+hour+min+sec+msec != 0) {
|
294
|
+
VALUE offset = (timezone == intern_local) ? rwrap->local_offset : opt_zero;
|
295
|
+
uint64_t seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec;
|
296
|
+
/* Use DateTime */
|
297
|
+
if (seconds < TINY_TDS_MIN_TIME || seconds > TINY_TDS_MAX_TIME) {
|
298
|
+
VALUE datetime_sec = INT2NUM(sec);
|
299
|
+
if (msec != 0) {
|
300
|
+
if ((opt_ruby_186 == 1 && sec < 59) || (opt_ruby_186 != 1)) {
|
301
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
302
|
+
VALUE rational_msec = rb_Rational2(INT2NUM(msec*1000), opt_onemil);
|
303
|
+
#else
|
304
|
+
VALUE rational_msec = rb_funcall(rb_cObject, intern_Rational, 2, INT2NUM(msec*1000), opt_onemil);
|
305
|
+
#endif
|
306
|
+
datetime_sec = rb_funcall(datetime_sec, intern_plus, 1, rational_msec);
|
307
|
+
}
|
308
|
+
}
|
309
|
+
val = rb_funcall(cDateTime, intern_civil, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), datetime_sec, offset);
|
310
|
+
val = rb_funcall(val, intern_new_offset, 1, offset);
|
311
|
+
/* Use Time */
|
312
|
+
} else {
|
313
|
+
val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(msec*1000));
|
314
|
+
}
|
315
|
+
}
|
316
|
+
break;
|
317
|
+
}
|
318
|
+
case SYBCHAR:
|
319
|
+
case SYBTEXT:
|
320
|
+
val = ENCODED_STR_NEW(data, data_len);
|
321
|
+
break;
|
322
|
+
default:
|
323
|
+
val = ENCODED_STR_NEW(data, data_len);
|
324
|
+
break;
|
325
|
+
}
|
326
|
+
}
|
327
|
+
if (as_array) {
|
328
|
+
rb_ary_store(row, i, val);
|
329
|
+
} else {
|
330
|
+
VALUE key;
|
331
|
+
if (rwrap->number_of_results == 0) {
|
332
|
+
key = rb_ary_entry(rwrap->fields, i);
|
333
|
+
} else {
|
334
|
+
key = rb_ary_entry(rb_ary_entry(rwrap->fields, rwrap->number_of_results), i);
|
335
|
+
}
|
336
|
+
rb_hash_aset(row, key, val);
|
337
|
+
}
|
338
|
+
}
|
339
|
+
return row;
|
340
|
+
}
|
341
|
+
|
342
|
+
|
343
|
+
// TinyTds::Client (public)
|
344
|
+
|
345
|
+
static VALUE rb_tinytds_result_fields(VALUE self) {
|
346
|
+
GET_RESULT_WRAPPER(self);
|
347
|
+
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client);
|
348
|
+
RETCODE dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
349
|
+
VALUE fields_processed = rb_ary_entry(rwrap->fields_processed, rwrap->number_of_results);
|
350
|
+
if ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED) && (fields_processed == Qnil)) {
|
351
|
+
/* Default query options. */
|
352
|
+
int symbolize_keys = 0;
|
353
|
+
VALUE qopts = rb_iv_get(self, "@query_options");
|
354
|
+
if (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue)
|
355
|
+
symbolize_keys = 1;
|
356
|
+
/* Set number_of_fields count for this result set. */
|
357
|
+
rwrap->number_of_fields = dbnumcols(rwrap->client);
|
358
|
+
if (rwrap->number_of_fields > 0) {
|
359
|
+
/* Create fields for this result set. */
|
360
|
+
unsigned int fldi = 0;
|
361
|
+
VALUE fields = rb_ary_new2(rwrap->number_of_fields);
|
362
|
+
for (fldi = 0; fldi < rwrap->number_of_fields; fldi++) {
|
363
|
+
char *colname = dbcolname(rwrap->client, fldi+1);
|
364
|
+
VALUE field = symbolize_keys ? rb_str_intern(ENCODED_STR_NEW2(colname)) : rb_obj_freeze(ENCODED_STR_NEW2(colname));
|
365
|
+
rb_ary_store(fields, fldi, field);
|
366
|
+
}
|
367
|
+
/* Store the fields. */
|
368
|
+
if (rwrap->number_of_results == 0) {
|
369
|
+
rwrap->fields = fields;
|
370
|
+
} else if (rwrap->number_of_results == 1) {
|
371
|
+
VALUE multi_rs_fields = rb_ary_new();
|
372
|
+
rb_ary_store(multi_rs_fields, 0, rwrap->fields);
|
373
|
+
rb_ary_store(multi_rs_fields, 1, fields);
|
374
|
+
rwrap->fields = multi_rs_fields;
|
375
|
+
} else {
|
376
|
+
rb_ary_store(rwrap->fields, rwrap->number_of_results, fields);
|
377
|
+
}
|
378
|
+
}
|
379
|
+
rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qtrue);
|
380
|
+
}
|
381
|
+
return rwrap->fields;
|
382
|
+
}
|
383
|
+
|
384
|
+
static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
385
|
+
GET_RESULT_WRAPPER(self);
|
386
|
+
GET_CLIENT_USERDATA(rwrap->client);
|
387
|
+
/* Local Vars */
|
388
|
+
VALUE qopts, opts, block;
|
389
|
+
ID timezone;
|
390
|
+
int symbolize_keys = 0, as_array = 0, cache_rows = 0, first = 0, empty_sets = 0;
|
391
|
+
/* Merge Options Hash To Query Options. Populate Opts & Block Var. */
|
392
|
+
qopts = rb_iv_get(self, "@query_options");
|
393
|
+
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1)
|
394
|
+
qopts = rb_funcall(qopts, intern_merge, 1, opts);
|
395
|
+
rb_iv_set(self, "@query_options", qopts);
|
396
|
+
/* Locals From Options */
|
397
|
+
if (rb_hash_aref(qopts, sym_first) == Qtrue)
|
398
|
+
first = 1;
|
399
|
+
if (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue)
|
400
|
+
symbolize_keys = 1;
|
401
|
+
if (rb_hash_aref(qopts, sym_as) == sym_array)
|
402
|
+
as_array = 1;
|
403
|
+
if (rb_hash_aref(qopts, sym_cache_rows) == Qtrue)
|
404
|
+
cache_rows = 1;
|
405
|
+
if (rb_hash_aref(qopts, sym_timezone) == sym_local) {
|
406
|
+
timezone = intern_local;
|
407
|
+
} else if (rb_hash_aref(qopts, sym_timezone) == sym_utc) {
|
408
|
+
timezone = intern_utc;
|
409
|
+
} else {
|
410
|
+
rb_warn(":timezone option must be :utc or :local - defaulting to :local");
|
411
|
+
timezone = intern_local;
|
412
|
+
}
|
413
|
+
if (rb_hash_aref(qopts, sym_empty_sets) == Qtrue)
|
414
|
+
empty_sets = 1;
|
415
|
+
/* Make The Results Or Yield Existing */
|
416
|
+
if (NIL_P(rwrap->results)) {
|
417
|
+
rwrap->results = rb_ary_new();
|
418
|
+
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client);
|
419
|
+
RETCODE dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
420
|
+
while ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED)) {
|
421
|
+
int has_rows = (DBROWS(rwrap->client) == SUCCEED) ? 1 : 0;
|
422
|
+
if (has_rows || empty_sets || (rwrap->number_of_results == 0))
|
423
|
+
rb_tinytds_result_fields(self);
|
424
|
+
if ((has_rows || empty_sets) && rwrap->number_of_fields > 0) {
|
425
|
+
/* Create rows for this result set. */
|
426
|
+
unsigned long rowi = 0;
|
427
|
+
VALUE result = rb_ary_new();
|
428
|
+
while (nogvl_dbnextrow(rwrap->client) != NO_MORE_ROWS) {
|
429
|
+
VALUE row = rb_tinytds_result_fetch_row(self, timezone, symbolize_keys, as_array);
|
430
|
+
if (cache_rows)
|
431
|
+
rb_ary_store(result, rowi, row);
|
432
|
+
if (!NIL_P(block))
|
433
|
+
rb_yield(row);
|
434
|
+
if (first) {
|
435
|
+
dbcanquery(rwrap->client);
|
436
|
+
userdata->dbcancel_sent = 1;
|
437
|
+
}
|
438
|
+
rowi++;
|
439
|
+
}
|
440
|
+
rwrap->number_of_rows = rowi;
|
441
|
+
/* Store the result. */
|
442
|
+
if (cache_rows) {
|
443
|
+
if (rwrap->number_of_results == 0) {
|
444
|
+
rwrap->results = result;
|
445
|
+
} else if (rwrap->number_of_results == 1) {
|
446
|
+
VALUE multi_resultsets = rb_ary_new();
|
447
|
+
rb_ary_store(multi_resultsets, 0, rwrap->results);
|
448
|
+
rb_ary_store(multi_resultsets, 1, result);
|
449
|
+
rwrap->results = multi_resultsets;
|
450
|
+
} else {
|
451
|
+
rb_ary_store(rwrap->results, rwrap->number_of_results, result);
|
452
|
+
}
|
453
|
+
}
|
454
|
+
// If we find results increment the counter that helpers use and setup the next loop.
|
455
|
+
rwrap->number_of_results = rwrap->number_of_results + 1;
|
456
|
+
dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
457
|
+
rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qnil);
|
458
|
+
} else {
|
459
|
+
// If we do not find results, side step the rb_tinytds_result_dbresults_retcode helper and
|
460
|
+
// manually populate its memoized array while nullifing any memoized fields too before loop.
|
461
|
+
dbresults_rc = nogvl_dbresults(rwrap->client);
|
462
|
+
rb_ary_store(rwrap->dbresults_retcodes, rwrap->number_of_results, INT2FIX(dbresults_rc));
|
463
|
+
rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qnil);
|
464
|
+
}
|
465
|
+
}
|
466
|
+
if (dbresults_rc == FAIL)
|
467
|
+
rb_warn("TinyTDS: Something in the dbresults() while loop set the return code to FAIL.\n");
|
468
|
+
userdata->dbsql_sent = 0;
|
469
|
+
} else if (!NIL_P(block)) {
|
470
|
+
unsigned long i;
|
471
|
+
for (i = 0; i < rwrap->number_of_rows; i++) {
|
472
|
+
rb_yield(rb_ary_entry(rwrap->results, i));
|
473
|
+
}
|
474
|
+
}
|
475
|
+
return rwrap->results;
|
476
|
+
}
|
477
|
+
|
478
|
+
static VALUE rb_tinytds_result_cancel(VALUE self) {
|
479
|
+
GET_RESULT_WRAPPER(self);
|
480
|
+
GET_CLIENT_USERDATA(rwrap->client);
|
481
|
+
if (rwrap->client && !userdata->dbcancel_sent) {
|
482
|
+
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client);
|
483
|
+
dbcancel(rwrap->client);
|
484
|
+
userdata->dbcancel_sent = 1;
|
485
|
+
userdata->dbsql_sent = 0;
|
486
|
+
}
|
487
|
+
return Qtrue;
|
488
|
+
}
|
489
|
+
|
490
|
+
static VALUE rb_tinytds_result_do(VALUE self) {
|
491
|
+
GET_RESULT_WRAPPER(self);
|
492
|
+
if (rwrap->client) {
|
493
|
+
rb_tinytds_result_exec_helper(rwrap->client);
|
494
|
+
return LONG2NUM((long)dbcount(rwrap->client));
|
495
|
+
} else {
|
496
|
+
return Qnil;
|
497
|
+
}
|
498
|
+
}
|
499
|
+
|
500
|
+
static VALUE rb_tinytds_result_affected_rows(VALUE self) {
|
501
|
+
GET_RESULT_WRAPPER(self);
|
502
|
+
if (rwrap->client) {
|
503
|
+
return LONG2NUM((long)dbcount(rwrap->client));
|
504
|
+
} else {
|
505
|
+
return Qnil;
|
506
|
+
}
|
507
|
+
}
|
508
|
+
|
509
|
+
/* Duplicated in client.c */
|
510
|
+
static VALUE rb_tinytds_result_return_code(VALUE self) {
|
511
|
+
GET_RESULT_WRAPPER(self);
|
512
|
+
if (rwrap->client && dbhasretstat(rwrap->client)) {
|
513
|
+
return LONG2NUM((long)dbretstatus(rwrap->client));
|
514
|
+
} else {
|
515
|
+
return Qnil;
|
516
|
+
}
|
517
|
+
}
|
518
|
+
|
519
|
+
static VALUE rb_tinytds_result_insert(VALUE self) {
|
520
|
+
GET_RESULT_WRAPPER(self);
|
521
|
+
if (rwrap->client) {
|
522
|
+
rb_tinytds_result_exec_helper(rwrap->client);
|
523
|
+
VALUE identity = Qnil;
|
524
|
+
dbcmd(rwrap->client, rwrap->cwrap->identity_insert_sql);
|
525
|
+
if (nogvl_dbsqlexec(rwrap->client) != FAIL
|
526
|
+
&& nogvl_dbresults(rwrap->client) != FAIL
|
527
|
+
&& DBROWS(rwrap->client) != FAIL) {
|
528
|
+
while (nogvl_dbnextrow(rwrap->client) != NO_MORE_ROWS) {
|
529
|
+
int col = 1;
|
530
|
+
BYTE *data = dbdata(rwrap->client, col);
|
531
|
+
DBINT data_len = dbdatlen(rwrap->client, col);
|
532
|
+
int null_val = ((data == NULL) && (data_len == 0));
|
533
|
+
if (!null_val)
|
534
|
+
identity = LL2NUM(*(DBBIGINT *)data);
|
535
|
+
}
|
536
|
+
}
|
537
|
+
return identity;
|
538
|
+
} else {
|
539
|
+
return Qnil;
|
540
|
+
}
|
541
|
+
}
|
542
|
+
|
543
|
+
|
544
|
+
// Lib Init
|
545
|
+
|
546
|
+
void init_tinytds_result() {
|
547
|
+
/* Data Classes */
|
548
|
+
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
549
|
+
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
550
|
+
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
551
|
+
/* Define TinyTds::Result */
|
552
|
+
cTinyTdsResult = rb_define_class_under(mTinyTds, "Result", rb_cObject);
|
553
|
+
/* Define TinyTds::Result Public Methods */
|
554
|
+
rb_define_method(cTinyTdsResult, "fields", rb_tinytds_result_fields, 0);
|
555
|
+
rb_define_method(cTinyTdsResult, "each", rb_tinytds_result_each, -1);
|
556
|
+
rb_define_method(cTinyTdsResult, "cancel", rb_tinytds_result_cancel, 0);
|
557
|
+
rb_define_method(cTinyTdsResult, "do", rb_tinytds_result_do, 0);
|
558
|
+
rb_define_method(cTinyTdsResult, "affected_rows", rb_tinytds_result_affected_rows, 0);
|
559
|
+
rb_define_method(cTinyTdsResult, "return_code", rb_tinytds_result_return_code, 0);
|
560
|
+
rb_define_method(cTinyTdsResult, "insert", rb_tinytds_result_insert, 0);
|
561
|
+
/* Intern String Helpers */
|
562
|
+
intern_new = rb_intern("new");
|
563
|
+
intern_utc = rb_intern("utc");
|
564
|
+
intern_local = rb_intern("local");
|
565
|
+
intern_merge = rb_intern("merge");
|
566
|
+
intern_localtime = rb_intern("localtime");
|
567
|
+
intern_civil = rb_intern("civil");
|
568
|
+
intern_new_offset = rb_intern("new_offset");
|
569
|
+
intern_plus = rb_intern("+");
|
570
|
+
intern_divide = rb_intern("/");
|
571
|
+
intern_Rational = rb_intern("Rational");
|
572
|
+
/* Symbol Helpers */
|
573
|
+
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
|
574
|
+
sym_as = ID2SYM(rb_intern("as"));
|
575
|
+
sym_array = ID2SYM(rb_intern("array"));
|
576
|
+
sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
|
577
|
+
sym_first = ID2SYM(rb_intern("first"));
|
578
|
+
sym_local = ID2SYM(intern_local);
|
579
|
+
sym_utc = ID2SYM(intern_utc);
|
580
|
+
sym_timezone = ID2SYM(rb_intern("timezone"));
|
581
|
+
sym_empty_sets = ID2SYM(rb_intern("empty_sets"));
|
582
|
+
/* Data Conversion Options */
|
583
|
+
opt_decimal_zero = rb_str_new2("0.0");
|
584
|
+
rb_global_variable(&opt_decimal_zero);
|
585
|
+
opt_float_zero = rb_float_new((double)0);
|
586
|
+
rb_global_variable(&opt_float_zero);
|
587
|
+
opt_one = INT2NUM(1);
|
588
|
+
opt_zero = INT2NUM(0);
|
589
|
+
opt_four = INT2NUM(4);
|
590
|
+
opt_19hdr = INT2NUM(1900);
|
591
|
+
opt_tenk = INT2NUM(10000);
|
592
|
+
opt_onemil = INT2NUM(1000000);
|
593
|
+
/* Ruby version flags */
|
594
|
+
opt_ruby_186 = (rb_eval_string("RUBY_VERSION == '1.8.6'") == Qtrue) ? 1 : 0;
|
595
|
+
/* Encoding */
|
596
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
597
|
+
binaryEncoding = rb_enc_find("binary");
|
598
|
+
#endif
|
599
|
+
}
|