tiny_tds 0.6.2-x64-mingw32
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 +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
|
+
}
|