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 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. See: http://redmine.ruby-lang.org/issues/show/1490 [Erik Bryn]
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
- = TinyTds - A modern, simple and fast FreeTDS library for Ruby using DB-Library.
2
+ = TinyTDS - A modern, simple and fast FreeTDS library for Ruby using DB-Library.
3
3
 
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.
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 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
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. TinyTds is tested on ruby version 1.8.6, 1.8.7, 1.9.1, 1.9.2 as well as REE & JRuby.
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
- TinyTds is developed primarily for FreeTDS 0.82 and 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".
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
- Once a result returns its rows, you can access the fields. Returns nil if the data has not yet been loaded or there are no rows returned.
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.fields # => [ ... ]
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 TinyTds and the Mysql2 gem allow such a performance gain.
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 TinyTds With Rails & The ActiveRecord SQL Server adapter.
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 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.
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
- $ env TINYTDS_UNIT_DATASERVER=mydbserver TINYTDS_SCHEMA=sqlserver_2008 rake
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!
@@ -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);
@@ -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
@@ -1,13 +1,16 @@
1
1
  require 'mkmf'
2
2
 
3
- FREETDS_LIBRARIES = ['sybdb']
4
- FREETDS_HEADERS = ['sqlfront.h', 'sybdb.h', 'syberror.h']
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'].split(File::PATH_SEPARATOR)
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 += "#{$LDFLAGS} -L#{dir}".strip
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 += "#{$CPPFLAGS} -I#{dir}".strip
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
- (have_freetds_libraries?(*FREETDS_LIBRARIES) || find_freetds_libraries_path) &&
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?
@@ -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 = Qnil;
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.year,
156
- month = date_rec.month,
157
- day = date_rec.day,
158
- hour = date_rec.hour,
159
- min = date_rec.minute,
160
- sec = date_rec.second,
161
- msec = date_rec.millisecond;
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 defaults, opts, block;
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, Populate Opts & Block Var */
220
- defaults = rb_iv_get(self, "@query_options");
221
- if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
222
- opts = rb_funcall(defaults, intern_merge, 1, opts);
223
- } else {
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(opts, sym_first) == Qtrue)
302
+ if (rb_hash_aref(qopts, sym_first) == Qtrue)
228
303
  first = 1;
229
- if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue)
304
+ if (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue)
230
305
  symbolize_keys = 1;
231
- if (rb_hash_aref(opts, sym_as) == sym_array)
306
+ if (rb_hash_aref(qopts, sym_as) == sym_array)
232
307
  as_array = 1;
233
- if (rb_hash_aref(opts, sym_cache_rows) == Qtrue)
308
+ if (rb_hash_aref(qopts, sym_cache_rows) == Qtrue)
234
309
  cache_rows = 1;
235
- if (rb_hash_aref(opts, sym_timezone) == sym_local) {
310
+ if (rb_hash_aref(qopts, sym_timezone) == sym_local) {
236
311
  timezone = intern_local;
237
- } else if (rb_hash_aref(opts, sym_timezone) == sym_utc) {
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 = 0;
247
- RETCODE dbresults_rc = 0;
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
- int number_of_fields = has_rows ? dbnumcols(rwrap->client) : 0;
254
- if (has_rows && (number_of_fields > 0)) {
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 (rwrap->number_of_results == 0) {
291
- rwrap->results = result;
292
- } else if (rwrap->number_of_results == 1) {
293
- VALUE multi_resultsets = rb_ary_new();
294
- rb_ary_store(multi_resultsets, 0, rwrap->results);
295
- rb_ary_store(multi_resultsets, 1, result);
296
- rwrap->results = multi_resultsets;
297
- } else {
298
- rb_ary_store(rwrap->results, rwrap->number_of_results, result);
344
+ if (cache_rows) {
345
+ if (rwrap->number_of_results == 0) {
346
+ rwrap->results = result;
347
+ } else if (rwrap->number_of_results == 1) {
348
+ VALUE multi_resultsets = rb_ary_new();
349
+ rb_ary_store(multi_resultsets, 0, rwrap->results);
350
+ rb_ary_store(multi_resultsets, 1, result);
351
+ rwrap->results = multi_resultsets;
352
+ } else {
353
+ rb_ary_store(rwrap->results, rwrap->number_of_results, result);
354
+ }
299
355
  }
300
- /* Record the result set */
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
- if (rwrap->client)
326
- dbsqlok(rwrap->client);
327
- dbcancel(rwrap->client);
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
- dbsqlok(rwrap->client);
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
- dbsqlok(rwrap->client);
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);
@@ -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)
@@ -1,11 +1,9 @@
1
1
  #ifndef TINYTDS_EXT
2
2
  #define TINYTDS_EXT
3
- #define MSDBLIB
4
3
 
5
4
  #include <ruby.h>
6
- #include <sqlfront.h>
5
+ #include <sybfront.h>
7
6
  #include <sybdb.h>
8
- #include <syberror.h>
9
7
 
10
8
  #ifdef HAVE_RUBY_ENCODING_H
11
9
  #include <ruby/encoding.h>
data/lib/tiny_tds.rb CHANGED
@@ -15,5 +15,5 @@ require 'tiny_tds/tiny_tds'
15
15
  #
16
16
  # Tiny Ruby Wrapper For FreeTDS Using DB-Library
17
17
  module TinyTds
18
- VERSION = '0.3.2'
18
+ VERSION = '0.4.0'
19
19
  end
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: 23
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 2
10
- version: 0.3.2
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-01-17 00:00:00 -05:00
19
+ date: 2011-03-23 00:00:00 -04:00
20
20
  default_executable:
21
21
  dependencies: []
22
22
 
23
- description: TinyTds - A modern, simple and fast FreeTDS library for Ruby using DB-Library. Developed for the ActiveRecord SQL Server adapter.
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.4.1
76
+ rubygems_version: 1.6.2
77
77
  signing_key:
78
78
  specification_version: 3
79
- summary: TinyTds - A modern, simple and fast FreeTDS library for Ruby using DB-Library.
79
+ summary: TinyTDS - A modern, simple and fast FreeTDS library for Ruby using DB-Library.
80
80
  test_files: []
81
81