tiny_tds 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +17 -1
- data/README.rdoc +50 -14
- data/ext/tiny_tds/client.c +18 -5
- data/ext/tiny_tds/client.h +8 -0
- data/ext/tiny_tds/extconf.rb +9 -7
- data/ext/tiny_tds/result.c +132 -78
- data/ext/tiny_tds/result.h +5 -0
- data/ext/tiny_tds/tiny_tds_ext.h +1 -3
- data/lib/tiny_tds.rb +1 -1
- metadata +8 -8
data/CHANGELOG
CHANGED
@@ -1,4 +1,19 @@
|
|
1
1
|
|
2
|
+
* 0.4.0 *
|
3
|
+
|
4
|
+
* Allow SYBEICONVI errors to pass thru so that bad data is converted to ? marks.
|
5
|
+
|
6
|
+
* Build native deps using MiniPortile [Luis Lavena]
|
7
|
+
|
8
|
+
* Allow Result#fields to be called before iterating over the results.
|
9
|
+
|
10
|
+
* Two new client helper methods, #sqlsent? and #canceled?. Possible to use these to determine current
|
11
|
+
state of the client and the need to use Result#cancel to stop processing active results. It is also
|
12
|
+
safe to call Result#cancel over and over again.
|
13
|
+
|
14
|
+
* Look for the syb headers only.
|
15
|
+
|
16
|
+
|
2
17
|
* 0.3.2 *
|
3
18
|
|
4
19
|
* Small changes while testing JRuby. Using options hash for connect vs many args.
|
@@ -25,7 +40,8 @@
|
|
25
40
|
|
26
41
|
* 0.2.2 *
|
27
42
|
|
28
|
-
* Fixed failing test in Ruby 1.8.6. DateTime doesn't support fractional seconds greater than 59.
|
43
|
+
* Fixed failing test in Ruby 1.8.6. DateTime doesn't support fractional seconds greater than 59.
|
44
|
+
See: http://redmine.ruby-lang.org/issues/show/1490 [Erik Bryn]
|
29
45
|
|
30
46
|
|
31
47
|
* 0.2.1 *
|
data/README.rdoc
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
|
2
|
-
=
|
2
|
+
= TinyTDS - A modern, simple and fast FreeTDS library for Ruby using DB-Library.
|
3
3
|
|
4
|
-
The
|
4
|
+
The TinyTDS gem is meant to serve the extremely common use-case of connecting, querying and iterating over results to Microsoft SQL Server databases from ruby. Even though it uses FreeTDS's DB-Library, it is NOT meant to serve as direct 1:1 mapping of that complex C API.
|
5
5
|
|
6
|
-
The benefits are speed, automatic casting to ruby primitives, and proper encoding support. It converts all SQL Server datatypes to native ruby objects supporting :utc or :local time zones for time-like types. To date it is the only ruby client library that allows client encoding options, defaulting to UTF-8, while connecting to SQL Server. It also properly encodes all string and binary data. The motivation for
|
6
|
+
The benefits are speed, automatic casting to ruby primitives, and proper encoding support. It converts all SQL Server datatypes to native ruby objects supporting :utc or :local time zones for time-like types. To date it is the only ruby client library that allows client encoding options, defaulting to UTF-8, while connecting to SQL Server. It also properly encodes all string and binary data. The motivation for TinyTDS is to become the de-facto low level connection mode for the SQL Server adapter for ActiveRecord. For further details see the special thanks section at the bottom
|
7
7
|
|
8
8
|
The API is simple and consists of these classes:
|
9
9
|
|
@@ -12,10 +12,15 @@ The API is simple and consists of these classes:
|
|
12
12
|
* TinyTds::Error - A wrapper for all exceptions.
|
13
13
|
|
14
14
|
|
15
|
+
== New & Noteworthy
|
16
|
+
|
17
|
+
* Works with FreeTDS 0.83.dev
|
18
|
+
* Tested on Windows using MiniPortile & RailsInstaller.
|
19
|
+
|
15
20
|
|
16
21
|
== Install
|
17
22
|
|
18
|
-
Installing with rubygems should just work.
|
23
|
+
Installing with rubygems should just work. TinyTDS is tested on ruby version 1.8.6, 1.8.7, 1.9.1, 1.9.2 as well as REE & JRuby.
|
19
24
|
|
20
25
|
$ gem install tiny_tds
|
21
26
|
|
@@ -25,7 +30,7 @@ Although we search for FreeTDS's libraries and headers, you may have to specify
|
|
25
30
|
|
26
31
|
== FreeTDS Compatibility
|
27
32
|
|
28
|
-
|
33
|
+
TinyTDS is developed for FreeTDS 0.82 & 0.83.dev. It is tested with SQL Server 2000, 2005, and 2008 using TDS Version 8.0. We utilize FreeTDS's db-lib client library. We compile against sybdb.h and define MSDBLIB which means that our client enables Microsoft behavior in the db-lib API where it diverges from Sybase's. You do NOT need to compile FreeTDS with the "--enable-msdblib" option for our client to work properly. However, please make sure to compile FreeTDS with libiconv support for encodings to work at their best. Run "tsql -C" in your console and check for "iconv library: yes".
|
29
34
|
|
30
35
|
|
31
36
|
|
@@ -88,9 +93,18 @@ Calling #each on the result will lazily load each row from the database.
|
|
88
93
|
# Here's an leemer: http://is.gd/g61xo
|
89
94
|
end
|
90
95
|
|
91
|
-
|
96
|
+
A result object has a #fields accessor. It can be called before the result rows are iterated over. Even if no rows are returned, #fields will still return the column names you expected. Any SQL that does not return columned data will always return an empty array for #fields. It is important to remember that if you access the #fields before iterating over the results, the columns will always follow the default query option's :symbolize_keys setting at the client's level and will ignore the query options passed to each.
|
92
97
|
|
93
|
-
result
|
98
|
+
result = client.execute("USE [tinytds_test]")
|
99
|
+
result.fields # => []
|
100
|
+
result.do
|
101
|
+
|
102
|
+
result = client.execute("SELECT [id] FROM [datatypes]")
|
103
|
+
result.fields # => ["id"]
|
104
|
+
result.cancel
|
105
|
+
result = client.execute("SELECT [id] FROM [datatypes]")
|
106
|
+
result.each(:symbolize_keys => true)
|
107
|
+
result.fields # => [:id]
|
94
108
|
|
95
109
|
You can cancel a result object's data from being loading by the server.
|
96
110
|
|
@@ -136,6 +150,29 @@ The result object can handle multiple result sets form batched SQL or stored pro
|
|
136
150
|
# 2nd: [{"bigint"=>-9223372036854775807}, {"bigint"=>9223372036854775806}]
|
137
151
|
end
|
138
152
|
|
153
|
+
Use the #sqlsent? and #canceled? query methods on the client to determine if an active SQL batch still needs to be processed and or if data results were canceled from the last result object. These values reset to true and false respectively for the client at the start of each #execute and new result object. Or if all rows are processed normally, #sqlsent? will return false. To demonstrate, lets assume we have 100 rows in the result object.
|
154
|
+
|
155
|
+
client.sqlsent? # = false
|
156
|
+
client.canceled? # = false
|
157
|
+
|
158
|
+
result = client.execute("SELECT * FROM [super_big_table]")
|
159
|
+
|
160
|
+
client.sqlsent? # = true
|
161
|
+
client.canceled? # = false
|
162
|
+
|
163
|
+
result.each do |row|
|
164
|
+
# Assume we break after 20 rows with 80 still pending.
|
165
|
+
break if row["id"] > 20
|
166
|
+
end
|
167
|
+
|
168
|
+
client.sqlsent? # = true
|
169
|
+
client.canceled? # = false
|
170
|
+
|
171
|
+
result.cancel
|
172
|
+
|
173
|
+
client.sqlsent? # = false
|
174
|
+
client.canceled? # = true
|
175
|
+
|
139
176
|
It is possible to get the return code after executing a stored procedure from either the result or client object.
|
140
177
|
|
141
178
|
client.return_code # => nil
|
@@ -170,15 +207,14 @@ Besides the standard query options, the result object can take one additional op
|
|
170
207
|
|
171
208
|
== Row Caching
|
172
209
|
|
173
|
-
By default row caching is turned on because the SQL Server adapter for ActiveRecord would not work without it. I hope to find some time to create some performance patches for ActiveRecord that would allow it to take advantages of lazily created yielded rows from result objects. Currently only
|
210
|
+
By default row caching is turned on because the SQL Server adapter for ActiveRecord would not work without it. I hope to find some time to create some performance patches for ActiveRecord that would allow it to take advantages of lazily created yielded rows from result objects. Currently only TinyTDS and the Mysql2 gem allow such a performance gain.
|
174
211
|
|
175
212
|
|
176
213
|
|
177
|
-
== Using
|
214
|
+
== Using TinyTDS With Rails & The ActiveRecord SQL Server adapter.
|
178
215
|
|
179
|
-
As of version 2.3.11 & 3.0.3 of the adapter, you can specify a :dblib mode in database.yml and use
|
216
|
+
As of version 2.3.11 & 3.0.3 of the adapter, you can specify a :dblib mode in database.yml and use TinyTDS as the low level connection mode. Make sure to add a :dataserver option to that matches the name in your freetds.conf file. The SQL Server adapter can be found using the link below. Also included is a direct link to the wiki article covering common questions when using TinyTDS as the low level connection mode for the adapter.
|
180
217
|
|
181
|
-
http://github.com/rails-sqlserver/activerecord-sqlserver-adapter
|
182
218
|
http://github.com/rails-sqlserver/activerecord-sqlserver-adapter/wiki/Using-TinyTds
|
183
219
|
|
184
220
|
|
@@ -187,7 +223,9 @@ http://github.com/rails-sqlserver/activerecord-sqlserver-adapter/wiki/Using-Tiny
|
|
187
223
|
|
188
224
|
We use bundler for development. Simply run "bundle install" then "rake" to build the gem and run the unit tests. The tests assume you have created a database named "tinytds_test" accessible by a database owner named "tinytds". Before running the test rake task, you may need to define a pair of environment variables that help the client connect to your specific FreeTDS database server name and which schema (2000, 2005 or 2008) to use. For example:
|
189
225
|
|
190
|
-
$
|
226
|
+
$ rake TINYTDS_UNIT_DATASERVER=mydbserver TINYTDS_SCHEMA=sqlserver_2008
|
227
|
+
|
228
|
+
If you are compiling locally using MiniPortile (the default dev setup) and you do not have a user based freetds.conf file with your dataservers, you may have to configure the locally compiled freetds.conf file. You can find it at the ports/<platform>/freetds/<version>/etc/freetds.conf path locally to your repo. In this situation you may have to set the FREETDS environment variable too this full path.
|
191
229
|
|
192
230
|
For help and support.
|
193
231
|
|
@@ -198,8 +236,6 @@ For help and support.
|
|
198
236
|
|
199
237
|
Current to do list.
|
200
238
|
|
201
|
-
* Test 0.83 development of FreeTDS.
|
202
|
-
* Find someone brave enough to compile/test for Windows.
|
203
239
|
* Install an interrupt handler.
|
204
240
|
* Allow #escape to accept all ruby primitives.
|
205
241
|
* Get bug reports!
|
data/ext/tiny_tds/client.c
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
|
2
2
|
#include <tiny_tds_ext.h>
|
3
|
-
#include <client.h>
|
4
3
|
#include <errno.h>
|
5
4
|
|
6
5
|
VALUE cTinyTdsClient;
|
@@ -10,15 +9,13 @@ static ID intern_source_eql, intern_severity_eql, intern_db_error_number_eql, in
|
|
10
9
|
static ID intern_new, intern_dup, intern_transpose_iconv_encoding, intern_local_offset, intern_gsub;
|
11
10
|
VALUE opt_escape_regex, opt_escape_dblquote;
|
12
11
|
|
12
|
+
|
13
13
|
// Lib Macros
|
14
14
|
|
15
15
|
#define GET_CLIENT_WRAPPER(self) \
|
16
16
|
tinytds_client_wrapper *cwrap; \
|
17
17
|
Data_Get_Struct(self, tinytds_client_wrapper, cwrap)
|
18
18
|
|
19
|
-
#define GET_CLIENT_USERDATA(dbproc) \
|
20
|
-
tinytds_client_userdata *userdata = (tinytds_client_userdata *)dbgetuserdata(dbproc);
|
21
|
-
|
22
19
|
#define REQUIRE_OPEN_CLIENT(cwrap) \
|
23
20
|
if (cwrap->closed || cwrap->userdata->closed) { \
|
24
21
|
rb_raise(cTinyTdsError, "closed connection"); \
|
@@ -59,6 +56,8 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
|
|
59
56
|
switch(dberr) {
|
60
57
|
case SYBESMSG:
|
61
58
|
return return_value;
|
59
|
+
case SYBEICONVI:
|
60
|
+
return INT_CANCEL;
|
62
61
|
case SYBEFCON:
|
63
62
|
case SYBESOCK:
|
64
63
|
case SYBECONN:
|
@@ -96,6 +95,7 @@ int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severi
|
|
96
95
|
|
97
96
|
static void rb_tinytds_client_reset_userdata(tinytds_client_userdata *userdata) {
|
98
97
|
userdata->timing_out = 0;
|
98
|
+
userdata->dbsql_sent = 0;
|
99
99
|
userdata->dbsqlok_sent = 0;
|
100
100
|
userdata->dbcancel_sent = 0;
|
101
101
|
}
|
@@ -155,6 +155,16 @@ static VALUE rb_tinytds_closed(VALUE self) {
|
|
155
155
|
return (cwrap->closed || cwrap->userdata->closed) ? Qtrue : Qfalse;
|
156
156
|
}
|
157
157
|
|
158
|
+
static VALUE rb_tinytds_canceled(VALUE self) {
|
159
|
+
GET_CLIENT_WRAPPER(self);
|
160
|
+
return cwrap->userdata->dbcancel_sent ? Qtrue : Qfalse;
|
161
|
+
}
|
162
|
+
|
163
|
+
static VALUE rb_tinytds_sqlsent(VALUE self) {
|
164
|
+
GET_CLIENT_WRAPPER(self);
|
165
|
+
return cwrap->userdata->dbsql_sent ? Qtrue : Qfalse;
|
166
|
+
}
|
167
|
+
|
158
168
|
static VALUE rb_tinytds_execute(VALUE self, VALUE sql) {
|
159
169
|
GET_CLIENT_WRAPPER(self);
|
160
170
|
rb_tinytds_client_reset_userdata(cwrap->userdata);
|
@@ -164,6 +174,7 @@ static VALUE rb_tinytds_execute(VALUE self, VALUE sql) {
|
|
164
174
|
rb_warn("TinyTds: dbsqlsend() returned FAIL.\n");
|
165
175
|
return Qfalse;
|
166
176
|
}
|
177
|
+
cwrap->userdata->dbsql_sent = 1;
|
167
178
|
VALUE result = rb_tinytds_new_result_obj(cwrap->client);
|
168
179
|
rb_iv_set(result, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), intern_dup, 0));
|
169
180
|
GET_RESULT_WRAPPER(result);
|
@@ -250,7 +261,7 @@ static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
|
|
250
261
|
if (cwrap->client) {
|
251
262
|
cwrap->closed = 0;
|
252
263
|
cwrap->charset = charset;
|
253
|
-
dbsetuserdata(cwrap->client, cwrap->userdata);
|
264
|
+
dbsetuserdata(cwrap->client, (BYTE*)cwrap->userdata);
|
254
265
|
cwrap->userdata->closed = 0;
|
255
266
|
if (!NIL_P(database))
|
256
267
|
dbuse(cwrap->client, StringValuePtr(database));
|
@@ -272,6 +283,8 @@ void init_tinytds_client() {
|
|
272
283
|
rb_define_method(cTinyTdsClient, "tds_version", rb_tinytds_tds_version, 0);
|
273
284
|
rb_define_method(cTinyTdsClient, "close", rb_tinytds_close, 0);
|
274
285
|
rb_define_method(cTinyTdsClient, "closed?", rb_tinytds_closed, 0);
|
286
|
+
rb_define_method(cTinyTdsClient, "canceled?", rb_tinytds_canceled, 0);
|
287
|
+
rb_define_method(cTinyTdsClient, "sqlsent?", rb_tinytds_sqlsent, 0);
|
275
288
|
rb_define_method(cTinyTdsClient, "execute", rb_tinytds_execute, 1);
|
276
289
|
rb_define_method(cTinyTdsClient, "charset", rb_tinytds_charset, 0);
|
277
290
|
rb_define_method(cTinyTdsClient, "encoding", rb_tinytds_encoding, 0);
|
data/ext/tiny_tds/client.h
CHANGED
@@ -7,7 +7,9 @@ void init_tinytds_client();
|
|
7
7
|
typedef struct {
|
8
8
|
short int closed;
|
9
9
|
short int timing_out;
|
10
|
+
short int dbsql_sent;
|
10
11
|
short int dbsqlok_sent;
|
12
|
+
RETCODE dbsqlok_retcode;
|
11
13
|
short int dbcancel_sent;
|
12
14
|
} tinytds_client_userdata;
|
13
15
|
|
@@ -24,4 +26,10 @@ typedef struct {
|
|
24
26
|
} tinytds_client_wrapper;
|
25
27
|
|
26
28
|
|
29
|
+
// Lib Macros
|
30
|
+
|
31
|
+
#define GET_CLIENT_USERDATA(dbproc) \
|
32
|
+
tinytds_client_userdata *userdata = (tinytds_client_userdata *)dbgetuserdata(dbproc);
|
33
|
+
|
34
|
+
|
27
35
|
#endif
|
data/ext/tiny_tds/extconf.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
|
3
|
-
FREETDS_LIBRARIES = ['sybdb']
|
4
|
-
FREETDS_HEADERS = ['
|
3
|
+
FREETDS_LIBRARIES = ['iconv','sybdb']
|
4
|
+
FREETDS_HEADERS = ['sybfront.h', 'sybdb.h']
|
5
5
|
|
6
|
+
dir_config('iconv')
|
6
7
|
dir_config('freetds')
|
7
8
|
|
8
9
|
def root_paths
|
9
10
|
eop_regexp = /#{File::SEPARATOR}bin$/
|
10
|
-
paths = ENV['PATH']
|
11
|
+
paths = ENV['PATH']
|
12
|
+
paths = paths.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
13
|
+
paths = paths.split(File::PATH_SEPARATOR)
|
11
14
|
bin_paths = paths.select{ |p| p =~ eop_regexp }
|
12
15
|
bin_paths.map{ |p| p.sub(eop_regexp,'') }.compact.reject{ |p| p.empty? }.uniq
|
13
16
|
end
|
@@ -24,7 +27,7 @@ def find_freetds_libraries_path
|
|
24
27
|
if File.directory?(dir)
|
25
28
|
puts "#{message} yes"
|
26
29
|
if with_ldflags("#{$LDFLAGS} -L#{dir}".strip) { have_freetds_libraries?(*FREETDS_LIBRARIES) }
|
27
|
-
$LDFLAGS
|
30
|
+
$LDFLAGS = "-L#{dir} #{$LDFLAGS}".strip
|
28
31
|
true
|
29
32
|
else
|
30
33
|
false
|
@@ -49,7 +52,7 @@ def find_freetds_include_path
|
|
49
52
|
if File.directory?(dir)
|
50
53
|
puts "#{message} yes"
|
51
54
|
if with_cppflags("#{$CPPFLAGS} -I#{dir}".strip) { have_freetds_headers?(*FREETDS_HEADERS) }
|
52
|
-
$CPPFLAGS
|
55
|
+
$CPPFLAGS = "-I#{dir} #{$CPPFLAGS}".strip
|
53
56
|
true
|
54
57
|
else
|
55
58
|
false
|
@@ -63,8 +66,7 @@ def find_freetds_include_path
|
|
63
66
|
end
|
64
67
|
|
65
68
|
def have_freetds?
|
66
|
-
|
67
|
-
(have_freetds_headers?(*FREETDS_HEADERS) || find_freetds_include_path)
|
69
|
+
find_freetds_libraries_path && find_freetds_include_path
|
68
70
|
end
|
69
71
|
|
70
72
|
unless have_freetds?
|
data/ext/tiny_tds/result.c
CHANGED
@@ -10,6 +10,9 @@ static ID intern_new, intern_utc, intern_local, intern_localtime, intern_merge,
|
|
10
10
|
intern_civil, intern_new_offset, intern_plus, intern_divide, intern_Rational;
|
11
11
|
static ID sym_symbolize_keys, sym_as, sym_array, sym_cache_rows, sym_first, sym_timezone, sym_local, sym_utc;
|
12
12
|
|
13
|
+
|
14
|
+
// Lib Macros
|
15
|
+
|
13
16
|
#ifdef HAVE_RUBY_ENCODING_H
|
14
17
|
rb_encoding *binaryEncoding;
|
15
18
|
#define ENCODED_STR_NEW(_data, _len) ({ \
|
@@ -37,7 +40,9 @@ static void rb_tinytds_result_mark(void *ptr) {
|
|
37
40
|
if (rwrap) {
|
38
41
|
rb_gc_mark(rwrap->local_offset);
|
39
42
|
rb_gc_mark(rwrap->fields);
|
43
|
+
rb_gc_mark(rwrap->fields_processed);
|
40
44
|
rb_gc_mark(rwrap->results);
|
45
|
+
rb_gc_mark(rwrap->dbresults_retcodes);
|
41
46
|
}
|
42
47
|
}
|
43
48
|
|
@@ -52,8 +57,10 @@ VALUE rb_tinytds_new_result_obj(DBPROCESS *c) {
|
|
52
57
|
obj = Data_Make_Struct(cTinyTdsResult, tinytds_result_wrapper, rb_tinytds_result_mark, rb_tinytds_result_free, rwrap);
|
53
58
|
rwrap->client = c;
|
54
59
|
rwrap->local_offset = Qnil;
|
55
|
-
rwrap->fields =
|
60
|
+
rwrap->fields = rb_ary_new();
|
61
|
+
rwrap->fields_processed = rb_ary_new();
|
56
62
|
rwrap->results = Qnil;
|
63
|
+
rwrap->dbresults_retcodes = rb_ary_new();
|
57
64
|
rwrap->number_of_results = 0;
|
58
65
|
rwrap->number_of_fields = 0;
|
59
66
|
rwrap->number_of_rows = 0;
|
@@ -64,6 +71,38 @@ VALUE rb_tinytds_new_result_obj(DBPROCESS *c) {
|
|
64
71
|
|
65
72
|
// Lib Backend (Helpers)
|
66
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
|
+
|
67
106
|
static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_keys, int as_array) {
|
68
107
|
/* Wrapper And Local Vars */
|
69
108
|
GET_RESULT_WRAPPER(self);
|
@@ -152,13 +191,13 @@ static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_
|
|
152
191
|
case SYBDATETIME: {
|
153
192
|
DBDATEREC date_rec;
|
154
193
|
dbdatecrack(rwrap->client, &date_rec, (DBDATETIME *)data);
|
155
|
-
int year = date_rec.
|
156
|
-
month = date_rec.
|
157
|
-
day = date_rec.
|
158
|
-
hour = date_rec.
|
159
|
-
min = date_rec.
|
160
|
-
sec = date_rec.
|
161
|
-
msec = date_rec.
|
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;
|
162
201
|
if (year+month+day+hour+min+sec+msec != 0) {
|
163
202
|
VALUE offset = (timezone == intern_local) ? rwrap->local_offset : opt_zero;
|
164
203
|
/* Use DateTime */
|
@@ -210,31 +249,67 @@ static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_
|
|
210
249
|
|
211
250
|
// TinyTds::Client (public)
|
212
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
|
+
|
213
289
|
static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
214
290
|
GET_RESULT_WRAPPER(self);
|
291
|
+
GET_CLIENT_USERDATA(rwrap->client);
|
215
292
|
/* Local Vars */
|
216
|
-
VALUE
|
293
|
+
VALUE qopts, opts, block;
|
217
294
|
ID timezone;
|
218
295
|
int symbolize_keys = 0, as_array = 0, cache_rows = 0, first = 0;
|
219
|
-
/* Merge Options Hash
|
220
|
-
|
221
|
-
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1)
|
222
|
-
|
223
|
-
|
224
|
-
opts = defaults;
|
225
|
-
}
|
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);
|
226
301
|
/* Locals From Options */
|
227
|
-
if (rb_hash_aref(
|
302
|
+
if (rb_hash_aref(qopts, sym_first) == Qtrue)
|
228
303
|
first = 1;
|
229
|
-
if (rb_hash_aref(
|
304
|
+
if (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue)
|
230
305
|
symbolize_keys = 1;
|
231
|
-
if (rb_hash_aref(
|
306
|
+
if (rb_hash_aref(qopts, sym_as) == sym_array)
|
232
307
|
as_array = 1;
|
233
|
-
if (rb_hash_aref(
|
308
|
+
if (rb_hash_aref(qopts, sym_cache_rows) == Qtrue)
|
234
309
|
cache_rows = 1;
|
235
|
-
if (rb_hash_aref(
|
310
|
+
if (rb_hash_aref(qopts, sym_timezone) == sym_local) {
|
236
311
|
timezone = intern_local;
|
237
|
-
} else if (rb_hash_aref(
|
312
|
+
} else if (rb_hash_aref(qopts, sym_timezone) == sym_utc) {
|
238
313
|
timezone = intern_utc;
|
239
314
|
} else {
|
240
315
|
rb_warn(":timezone option must be :utc or :local - defaulting to :local");
|
@@ -243,35 +318,12 @@ static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
|
243
318
|
/* Make The Results Or Yield Existing */
|
244
319
|
if (NIL_P(rwrap->results)) {
|
245
320
|
rwrap->results = rb_ary_new();
|
246
|
-
RETCODE dbsqlok_rc =
|
247
|
-
RETCODE dbresults_rc =
|
248
|
-
dbsqlok_rc = dbsqlok(rwrap->client);
|
249
|
-
dbresults_rc = dbresults(rwrap->client);
|
321
|
+
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client);
|
322
|
+
RETCODE dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
250
323
|
while ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED)) {
|
251
|
-
/* Only do field and row work if there are rows in this result set. */
|
252
324
|
int has_rows = (DBROWS(rwrap->client) == SUCCEED) ? 1 : 0;
|
253
|
-
|
254
|
-
if (has_rows &&
|
255
|
-
/* Create fields for this result set. */
|
256
|
-
unsigned int fldi = 0;
|
257
|
-
rwrap->number_of_fields = number_of_fields;
|
258
|
-
VALUE fields = rb_ary_new2(rwrap->number_of_fields);
|
259
|
-
for (fldi = 0; fldi < rwrap->number_of_fields; fldi++) {
|
260
|
-
char *colname = dbcolname(rwrap->client, fldi+1);
|
261
|
-
VALUE field = symbolize_keys ? ID2SYM(rb_intern(colname)) : rb_obj_freeze(ENCODED_STR_NEW2(colname));
|
262
|
-
rb_ary_store(fields, fldi, field);
|
263
|
-
}
|
264
|
-
/* Store the fields. */
|
265
|
-
if (rwrap->number_of_results == 0) {
|
266
|
-
rwrap->fields = fields;
|
267
|
-
} else if (rwrap->number_of_results == 1) {
|
268
|
-
VALUE multi_rs_fields = rb_ary_new();
|
269
|
-
rb_ary_store(multi_rs_fields, 0, rwrap->fields);
|
270
|
-
rb_ary_store(multi_rs_fields, 1, fields);
|
271
|
-
rwrap->fields = multi_rs_fields;
|
272
|
-
} else {
|
273
|
-
rb_ary_store(rwrap->fields, rwrap->number_of_results, fields);
|
274
|
-
}
|
325
|
+
rb_tinytds_result_fields(self);
|
326
|
+
if (has_rows && rwrap->number_of_fields > 0) {
|
275
327
|
/* Create rows for this result set. */
|
276
328
|
unsigned long rowi = 0;
|
277
329
|
VALUE result = rb_ary_new();
|
@@ -281,31 +333,40 @@ static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
|
281
333
|
rb_ary_store(result, rowi, row);
|
282
334
|
if (!NIL_P(block))
|
283
335
|
rb_yield(row);
|
284
|
-
if (first)
|
336
|
+
if (first) {
|
285
337
|
dbcanquery(rwrap->client);
|
338
|
+
userdata->dbcancel_sent = 1;
|
339
|
+
}
|
286
340
|
rowi++;
|
287
341
|
}
|
288
342
|
rwrap->number_of_rows = rowi;
|
289
343
|
/* Store the result. */
|
290
|
-
if (
|
291
|
-
rwrap->
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
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
|
+
}
|
299
355
|
}
|
300
|
-
|
356
|
+
// If we find results increment the counter that helpers use and setup the next loop.
|
301
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);
|
302
365
|
}
|
303
|
-
dbresults_rc = dbresults(rwrap->client);
|
304
|
-
}
|
305
|
-
if (dbresults_rc == FAIL) {
|
306
|
-
// TODO: Account for something in the dbresults() while loop set the return code to FAIL.
|
307
|
-
rb_warn("TinyTds: Something in the dbresults() while loop set the return code to FAIL.\n");
|
308
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;
|
309
370
|
} else if (!NIL_P(block)) {
|
310
371
|
unsigned long i;
|
311
372
|
for (i = 0; i < rwrap->number_of_rows; i++) {
|
@@ -315,24 +376,18 @@ static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
|
315
376
|
return rwrap->results;
|
316
377
|
}
|
317
378
|
|
318
|
-
static VALUE rb_tinytds_result_fields(VALUE self) {
|
319
|
-
GET_RESULT_WRAPPER(self);
|
320
|
-
return rwrap->fields;
|
321
|
-
}
|
322
|
-
|
323
379
|
static VALUE rb_tinytds_result_cancel(VALUE self) {
|
324
380
|
GET_RESULT_WRAPPER(self);
|
325
|
-
|
326
|
-
|
327
|
-
|
381
|
+
GET_CLIENT_USERDATA(rwrap->client);
|
382
|
+
if (rwrap->client && !userdata->dbcancel_sent)
|
383
|
+
rb_tinytds_result_cancel_helper(rwrap->client);
|
328
384
|
return Qtrue;
|
329
385
|
}
|
330
386
|
|
331
387
|
static VALUE rb_tinytds_result_do(VALUE self) {
|
332
388
|
GET_RESULT_WRAPPER(self);
|
333
389
|
if (rwrap->client) {
|
334
|
-
|
335
|
-
dbcancel(rwrap->client);
|
390
|
+
rb_tinytds_result_cancel_helper(rwrap->client);
|
336
391
|
return LONG2NUM((long)dbcount(rwrap->client));
|
337
392
|
} else {
|
338
393
|
return Qnil;
|
@@ -361,8 +416,7 @@ static VALUE rb_tinytds_result_return_code(VALUE self) {
|
|
361
416
|
static VALUE rb_tinytds_result_insert(VALUE self) {
|
362
417
|
GET_RESULT_WRAPPER(self);
|
363
418
|
if (rwrap->client) {
|
364
|
-
|
365
|
-
dbcancel(rwrap->client);
|
419
|
+
rb_tinytds_result_cancel_helper(rwrap->client);
|
366
420
|
VALUE identity = Qnil;
|
367
421
|
dbcmd(rwrap->client, "SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident");
|
368
422
|
if (dbsqlexec(rwrap->client) != FAIL && dbresults(rwrap->client) != FAIL && DBROWS(rwrap->client) != FAIL) {
|
@@ -392,8 +446,8 @@ void init_tinytds_result() {
|
|
392
446
|
/* Define TinyTds::Result */
|
393
447
|
cTinyTdsResult = rb_define_class_under(mTinyTds, "Result", rb_cObject);
|
394
448
|
/* Define TinyTds::Result Public Methods */
|
395
|
-
rb_define_method(cTinyTdsResult, "each", rb_tinytds_result_each, -1);
|
396
449
|
rb_define_method(cTinyTdsResult, "fields", rb_tinytds_result_fields, 0);
|
450
|
+
rb_define_method(cTinyTdsResult, "each", rb_tinytds_result_each, -1);
|
397
451
|
rb_define_method(cTinyTdsResult, "cancel", rb_tinytds_result_cancel, 0);
|
398
452
|
rb_define_method(cTinyTdsResult, "do", rb_tinytds_result_do, 0);
|
399
453
|
rb_define_method(cTinyTdsResult, "affected_rows", rb_tinytds_result_affected_rows, 0);
|
data/ext/tiny_tds/result.h
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
#ifndef TINYTDS_RESULT_H
|
3
3
|
#define TINYTDS_RESULT_H
|
4
4
|
|
5
|
+
// TODO: Is this needed?
|
5
6
|
typedef tds_sysdep_int64_type DBBIGINT; /* Missing in sybdb.h ?!?! */
|
6
7
|
|
7
8
|
void init_tinytds_result();
|
@@ -11,16 +12,20 @@ typedef struct {
|
|
11
12
|
DBPROCESS *client;
|
12
13
|
VALUE local_offset;
|
13
14
|
VALUE fields;
|
15
|
+
VALUE fields_processed;
|
14
16
|
VALUE results;
|
15
17
|
#ifdef HAVE_RUBY_ENCODING_H
|
16
18
|
rb_encoding *encoding;
|
17
19
|
#endif
|
20
|
+
VALUE dbresults_retcodes;
|
18
21
|
unsigned int number_of_results;
|
19
22
|
unsigned int number_of_fields;
|
20
23
|
unsigned long number_of_rows;
|
21
24
|
} tinytds_result_wrapper;
|
22
25
|
|
23
26
|
|
27
|
+
// Lib Macros
|
28
|
+
|
24
29
|
#define GET_RESULT_WRAPPER(self) \
|
25
30
|
tinytds_result_wrapper *rwrap; \
|
26
31
|
Data_Get_Struct(self, tinytds_result_wrapper, rwrap)
|
data/ext/tiny_tds/tiny_tds_ext.h
CHANGED
data/lib/tiny_tds.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tiny_tds
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ken Collins
|
@@ -16,11 +16,11 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2011-
|
19
|
+
date: 2011-03-23 00:00:00 -04:00
|
20
20
|
default_executable:
|
21
21
|
dependencies: []
|
22
22
|
|
23
|
-
description:
|
23
|
+
description: TinyTDS - A modern, simple and fast FreeTDS library for Ruby using DB-Library. Developed for the ActiveRecord SQL Server adapter.
|
24
24
|
email: ken@metaskills.net
|
25
25
|
executables: []
|
26
26
|
|
@@ -73,9 +73,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
73
|
requirements: []
|
74
74
|
|
75
75
|
rubyforge_project:
|
76
|
-
rubygems_version: 1.
|
76
|
+
rubygems_version: 1.6.2
|
77
77
|
signing_key:
|
78
78
|
specification_version: 3
|
79
|
-
summary:
|
79
|
+
summary: TinyTDS - A modern, simple and fast FreeTDS library for Ruby using DB-Library.
|
80
80
|
test_files: []
|
81
81
|
|