tiny_tds 0.4.5 → 0.5.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG +22 -1
- data/Gemfile +1 -8
- data/README.rdoc +48 -11
- data/Rakefile +11 -0
- data/ext/tiny_tds/client.c +12 -1
- data/ext/tiny_tds/result.c +55 -6
- data/ext/tiny_tds/result.h +3 -2
- data/lib/tiny_tds/client.rb +21 -14
- data/lib/tiny_tds/version.rb +1 -1
- data/tasks/ports.rake +13 -4
- data/test/benchmark/query_tinytds.rb +33 -18
- data/test/client_test.rb +1 -1
- data/test/result_test.rb +232 -24
- data/test/schema_test.rb +166 -83
- data/test/test_helper.rb +4 -0
- metadata +13 -9
data/.gitignore
CHANGED
data/CHANGELOG
CHANGED
@@ -1,4 +1,24 @@
|
|
1
1
|
|
2
|
+
* 0.5.0 *
|
3
|
+
|
4
|
+
* Copy mysql2s handling of Time and Datetime so 64bit systems are leveraged. Fixes #46 and #47. Thanks @lsylvester!
|
5
|
+
|
6
|
+
* Add CFLAGS='-fPIC' for libtool. Fix TDS version configs in our ports file. Document. Fixes #45
|
7
|
+
|
8
|
+
* Update our TDS version constants to reflect changed 8.0/9.0 to 7.1/7.2 DBLIB versions in FreeTDS
|
9
|
+
while making it backward compatible, again like FreeTDS. Even tho you can not configure FreeTDS with
|
10
|
+
TDS version 7.2 or technically even use it, I added tests to prove that we correctly handle both
|
11
|
+
varchar(max) and nvarchar(max) with large amounts of data.
|
12
|
+
|
13
|
+
* FreeTDS 0.91 has been released. Update our port scripts.
|
14
|
+
|
15
|
+
* Add test for 0.91 and higher to handle incorrect syntax in sp_executesql.
|
16
|
+
|
17
|
+
* Returning empty result sets with a command batch that has multiple statements is now the default. Use :empty_sets => false to override.
|
18
|
+
|
19
|
+
* Do not raise a TinyTds::Error with our message handler unless the severity is greater than 10.
|
20
|
+
|
21
|
+
|
2
22
|
* 0.4.5 *
|
3
23
|
|
4
24
|
* Includes precompiled Windows binaries for FreeTDS 0.91rc2 & LibIconv. No precompiled OpenSSL yet for Windows to SQL Azure.
|
@@ -28,7 +48,8 @@
|
|
28
48
|
|
29
49
|
* 0.4.2 *
|
30
50
|
|
31
|
-
* Iconv is a dep only when compiling locally. However, left in the ability to configure it for native gem installation
|
51
|
+
* Iconv is a dep only when compiling locally. However, left in the ability to configure it for native gem installation
|
52
|
+
but you must use --enable-iconv before using --with-iconv-dir=/some/dir
|
32
53
|
|
33
54
|
* Really fix what 0.4.1 was supposed to do, force SYBDBLIB compile.
|
34
55
|
|
data/Gemfile
CHANGED
@@ -4,18 +4,11 @@ source :rubygems
|
|
4
4
|
group :development do
|
5
5
|
gem 'rake', '0.8.7'
|
6
6
|
gem 'mini_portile', '0.2.2'
|
7
|
-
gem 'rake-compiler', '0.7.
|
7
|
+
gem 'rake-compiler', '0.7.9'
|
8
8
|
end
|
9
9
|
|
10
10
|
group :test do
|
11
11
|
gem 'mini_shoulda', '0.3.0'
|
12
12
|
gem 'activesupport', '2.3.5'
|
13
13
|
gem 'bench_press', '0.3.1'
|
14
|
-
platforms :mri_18 do
|
15
|
-
gem 'ruby-prof', '0.9.1'
|
16
|
-
gem 'ruby-debug', '0.10.3'
|
17
|
-
end
|
18
|
-
platforms :mri_19 do
|
19
|
-
gem 'ruby-debug19', '0.11.6'
|
20
|
-
end
|
21
14
|
end
|
data/README.rdoc
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
1
|
= TinyTDS - A modern, simple and fast FreeTDS library for Ruby using DB-Library.
|
3
2
|
|
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
|
3
|
+
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 C API.
|
5
4
|
|
6
5
|
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
6
|
|
@@ -12,6 +11,7 @@ The API is simple and consists of these classes:
|
|
12
11
|
* TinyTds::Error - A wrapper for all FreeTDS exceptions.
|
13
12
|
|
14
13
|
|
14
|
+
|
15
15
|
== New & Noteworthy
|
16
16
|
|
17
17
|
* Works with FreeTDS 0.91
|
@@ -19,9 +19,10 @@ The API is simple and consists of these classes:
|
|
19
19
|
* New :host/:port connection options. Removes need for freetds.conf file.
|
20
20
|
|
21
21
|
|
22
|
+
|
22
23
|
== Install
|
23
24
|
|
24
|
-
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.
|
25
|
+
Installing with rubygems should just work. TinyTDS is tested on ruby version 1.8.6, 1.8.7, 1.9.1, 1.9.2, 1.9.3 as well as REE & JRuby.
|
25
26
|
|
26
27
|
$ gem install tiny_tds
|
27
28
|
|
@@ -29,9 +30,19 @@ Although we search for FreeTDS's libraries and headers, you may have to specify
|
|
29
30
|
|
30
31
|
|
31
32
|
|
32
|
-
== FreeTDS Compatibility
|
33
|
+
== FreeTDS Compatibility & Configuration
|
34
|
+
|
35
|
+
TinyTDS is developed against FreeTDS 0.82 & 0.91, the latest is recommended. It is tested with SQL Server 2000, 2005, 2008 and Azure. Below are a few QA style notes about installing FreeTDS.
|
36
|
+
|
37
|
+
* <b>Do I need to install FreeTDS?</b> Yes! Somehow, someway, your are going to need FreeTDS for TinyTDS to compile against. You can avoid installing FreeTDS on your system by using our projects usage of rake-compiler and mini_portile to compile and package a native gem just for you. See the "Using MiniPortile" section below.
|
38
|
+
|
39
|
+
* <b>OK, I am installing FreeTDS, how do I configure it?</b> Contrary to what most people think, you do not need to specially configure FreeTDS in any way for client libraries like TinyTDS to use it. About the only requirement is that you compile it with libiconv for proper encoding support. FreeTDS must also be compiled with OpenSSL (or the like) to use it with Azure. See the "Using TinyTDS with Azure" section below for more info.
|
33
40
|
|
34
|
-
|
41
|
+
* <b>Do I need to configure "--with-tdsver" equal to anything?</b> Maybe! Technically you should not have too. This is only a default for clients/configs that do not specify what TDS version they want to use. We are currently having issues with passing down a TDS version with the login bit. Till we get that fixed, if you are not using a freetds.conf or a TDSVER environment variable, the make sure to use 7.1 for FreeTDS 0.91 and 8.0 for FreeTDS 0.82.
|
42
|
+
|
43
|
+
* <b>But I want to use TDS version 7.2 for SQL Server 2005 and up!</b> TinyTDS uses TDS version 7.1 (previously named 8.0) and fully supports all the data types supported by FreeTDS, this includes varchar(max) and nvarchar(max). Technically compiling and using TDS version 7.2 with FreeTDS is not supported. But this does not mean those data types will not work. I know, it's confusing If you want to learn more, read this thread. http://lists.ibiblio.org/pipermail/freetds/2011q3/027306.html
|
44
|
+
|
45
|
+
* <b>I want to configure FreeTDS using "--enable-msdblib" and/or "--enable-sybase-compat" so it works for my database. Cool?</b> It's a waste of time and totally moot! Client libraries like TinyTDS define their own C structure names where they diverge from Sybase to SQL Server. Technically we use the Sybase structures which does not mean we only work with that database vs SQL Server. These configs are just a low level default for C libraries that do not define what they want. So I repeat, you do not NEED to use any of these, nor will they hurt anything since we control what C structure names we use and this has no affect on what database you use!
|
35
46
|
|
36
47
|
|
37
48
|
|
@@ -63,7 +74,7 @@ Creating a new client takes a hash of options. For valid iconv encoding options,
|
|
63
74
|
* :port - Defaults to 1433. Only used if :host is used.
|
64
75
|
* :database - The default database to use.
|
65
76
|
* :appname - Short string seen in SQL Servers process/activity window.
|
66
|
-
* :tds_version - TDS version. Defaults to
|
77
|
+
* :tds_version - TDS version. Defaults to 71 (7.1) and is not recommended to change!
|
67
78
|
* :login_timeout - Seconds to wait for login. Default to 60 seconds.
|
68
79
|
* :timeout - Seconds to wait for a response to a SQL command. Default 5 seconds.
|
69
80
|
* :encoding - Any valid iconv value like CP1251 or ISO-8859-1. Default UTF-8.
|
@@ -202,6 +213,7 @@ Every TinyTds::Result object can pass query options to the #each method. The def
|
|
202
213
|
* :symbolize_keys => false - Row hash keys. Defaults to shared/frozen string keys.
|
203
214
|
* :cache_rows => true - Successive calls to #each returns the cached rows.
|
204
215
|
* :timezone => :local - Local to the ruby client or :utc for UTC.
|
216
|
+
* :empty_sets => true - Include empty results set in queries that return multiple result sets.
|
205
217
|
|
206
218
|
Each result gets a copy of the default options you specify at the client level and can be overridden by passing an options hash to the #each method. For example
|
207
219
|
|
@@ -227,7 +239,8 @@ By default row caching is turned on because the SQL Server adapter for ActiveRec
|
|
227
239
|
|
228
240
|
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. 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.
|
229
241
|
|
230
|
-
http://github.com/rails-sqlserver/activerecord-sqlserver-adapter
|
242
|
+
* ActiveRecord SQL Server Adapter: http://github.com/rails-sqlserver/activerecord-sqlserver-adapter
|
243
|
+
* Using TinyTDS: http://github.com/rails-sqlserver/activerecord-sqlserver-adapter/wiki/Using-TinyTds
|
231
244
|
|
232
245
|
|
233
246
|
|
@@ -237,6 +250,20 @@ TinyTDS is fully tested with the Azure platform. You must set the :azure => true
|
|
237
250
|
|
238
251
|
|
239
252
|
|
253
|
+
== Using MiniPortile
|
254
|
+
|
255
|
+
MiniPortile is a minimalistic, simplistic and stupid implementation of a port/recipe system for developers. <https://github.com/luislavena/mini_portile>
|
256
|
+
|
257
|
+
The TinyTDS project uses MiniPortile so that we can easily install a local "project specific" version of FreeTDS and supporting libraries to link against when building a test version of TinyTDS. MiniPortile is a great tool that even allows us to build statically linked components that TinyTDS relies on. Hence this allows us to publish native gems for any platform. We use this feature for gems targeted at Windows.
|
258
|
+
|
259
|
+
You too can use MiniPortile to build TinyTDS and build your own gems for your own package management needs. Here is a few simple steps that assume you have cloned a fresh copy of this repository. 1) Bundling will install all the development dependencies. 2) Running "rake compile" will basically download and install a supported version of FreeTDS in our "ports.rake" file and supporting libraries. These will all be installed into the projects tmp directory. 3) The final "rake native gem" command will build a native gem for your specific platform.
|
260
|
+
|
261
|
+
$ bundle install
|
262
|
+
$ rake compile
|
263
|
+
$ rake native gem
|
264
|
+
|
265
|
+
|
266
|
+
|
240
267
|
== Development & Testing
|
241
268
|
|
242
269
|
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 "tinytdstest" 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, 2008 or azure) to use. For example:
|
@@ -245,23 +272,26 @@ We use bundler for development. Simply run "bundle install" then "rake" to build
|
|
245
272
|
or
|
246
273
|
$ rake TINYTDS_UNIT_HOST=mydb.host.net TINYTDS_SCHEMA=sqlserver_azure
|
247
274
|
|
248
|
-
If you do not want to use MiniPortile to compile a local project version of FreeTDS, use the TINYTDS_SKIP_PORTS environment variable. This will ignore any port tasks and will instead
|
275
|
+
If you do not want to use MiniPortile to compile a local project version of FreeTDS and instead use your local system version, use the TINYTDS_SKIP_PORTS environment variable. This will ignore any port tasks and will instead build and link to your system's FreeTDS installation as a normal gem install would.
|
249
276
|
|
250
277
|
$ rake TINYTDS_SKIP_PORTS=true
|
251
278
|
|
252
|
-
|
279
|
+
|
280
|
+
|
281
|
+
== Help & Support
|
253
282
|
|
254
283
|
* Github Source: http://github.com/rails-sqlserver/tiny_tds
|
255
284
|
* Github Issues: http://github.com/rails-sqlserver/tiny_tds/issues
|
256
285
|
* Google Group: http://groups.google.com/group/rails-sqlserver-adapter
|
257
286
|
* IRC Room: #rails-sqlserver on irc.freenode.net
|
258
287
|
|
259
|
-
|
288
|
+
|
289
|
+
|
290
|
+
== TODO List
|
260
291
|
|
261
292
|
* Include OpenSSL with Windows binaries for SQL Azure.
|
262
293
|
* Install an interrupt handler.
|
263
294
|
* Allow #escape to accept all ruby primitives.
|
264
|
-
* Get bug reports!
|
265
295
|
|
266
296
|
|
267
297
|
|
@@ -278,3 +308,10 @@ My name is Ken Collins and I currently maintain the SQL Server adapter for Activ
|
|
278
308
|
* Yehuda Katz for articulating ruby's need for proper encoding support. Especially in database drivers - http://yehudakatz.com/2010/05/05/ruby-1-9-encodings-a-primer-and-the-solution-for-rails/
|
279
309
|
* Josh Clayton of Thoughtbot for writing about ruby C extensions. - http://robots.thoughtbot.com/post/1037240922/get-your-c-on
|
280
310
|
|
311
|
+
|
312
|
+
|
313
|
+
== License
|
314
|
+
|
315
|
+
TinyTDS is Copyright (c) 2010-2011 Ken Collins, <ken@metaskills.net> and is distributed under the MIT license. Windows binaries contain precompiled versions of FreeTDS <http://www.freetds.org/> which is licensed under the GNU LGPL license at <http://www.gnu.org/licenses/lgpl-2.0.html>
|
316
|
+
|
317
|
+
|
data/Rakefile
CHANGED
@@ -6,6 +6,17 @@ require 'rake/testtask'
|
|
6
6
|
require 'rake/extensiontask'
|
7
7
|
require "rubygems/package_task"
|
8
8
|
|
9
|
+
# My notes for cross compile native Windows gem.
|
10
|
+
#
|
11
|
+
# $ rake-compiler cross-ruby VERSION=1.8.7-p352
|
12
|
+
# $ rake-compiler cross-ruby VERSION=1.9.2-p290
|
13
|
+
#
|
14
|
+
# $ git clean -x -d -f
|
15
|
+
# $ bundle install
|
16
|
+
# $ ~/.rbenv/shims/rake compile
|
17
|
+
# $ ~/.rbenv/shims/rake cross compile RUBY_CC_VERSION=1.8.7:1.9.2
|
18
|
+
# $ ~/.rbenv/shims/rake cross native gem RUBY_CC_VERSION=1.8.7:1.9.2
|
19
|
+
|
9
20
|
def test_libs
|
10
21
|
['lib','test']
|
11
22
|
end
|
data/ext/tiny_tds/client.c
CHANGED
@@ -54,6 +54,8 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
|
|
54
54
|
int return_value = INT_CONTINUE;
|
55
55
|
int cancel = 0;
|
56
56
|
switch(dberr) {
|
57
|
+
case 100: /* SYBEVERDOWN */
|
58
|
+
return INT_CANCEL;
|
57
59
|
case SYBESMSG:
|
58
60
|
return return_value;
|
59
61
|
case SYBEICONVI:
|
@@ -96,7 +98,7 @@ int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, c
|
|
96
98
|
|
97
99
|
int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line) {
|
98
100
|
static char *source = "message";
|
99
|
-
if (severity)
|
101
|
+
if (severity > 10)
|
100
102
|
rb_tinytds_raise_error(dbproc, 1, msgtext, source, severity, msgno, msgstate);
|
101
103
|
return 0;
|
102
104
|
}
|
@@ -232,6 +234,14 @@ static VALUE rb_tinytds_return_code(VALUE self) {
|
|
232
234
|
}
|
233
235
|
}
|
234
236
|
|
237
|
+
static VALUE rb_tinytds_freetds_nine_one_or_higher(VALUE self) {
|
238
|
+
#ifdef DBSETLDBNAME
|
239
|
+
return Qtrue;
|
240
|
+
#else
|
241
|
+
return Qfalse;
|
242
|
+
#endif
|
243
|
+
}
|
244
|
+
|
235
245
|
|
236
246
|
// TinyTds::Client (protected)
|
237
247
|
|
@@ -313,6 +323,7 @@ void init_tinytds_client() {
|
|
313
323
|
rb_define_method(cTinyTdsClient, "encoding", rb_tinytds_encoding, 0);
|
314
324
|
rb_define_method(cTinyTdsClient, "escape", rb_tinytds_escape, 1);
|
315
325
|
rb_define_method(cTinyTdsClient, "return_code", rb_tinytds_return_code, 0);
|
326
|
+
rb_define_method(cTinyTdsClient, "freetds_091_or_higer?", rb_tinytds_freetds_nine_one_or_higher, 0);
|
316
327
|
/* Define TinyTds::Client Protected Methods */
|
317
328
|
rb_define_protected_method(cTinyTdsClient, "connect", rb_tinytds_connect, 1);
|
318
329
|
/* Symbols For Connect */
|
data/ext/tiny_tds/result.c
CHANGED
@@ -1,6 +1,45 @@
|
|
1
1
|
|
2
2
|
#include <tiny_tds_ext.h>
|
3
3
|
|
4
|
+
// TINY_TDS_MAX_TIME
|
5
|
+
|
6
|
+
#if (SIZEOF_INT < SIZEOF_LONG) || defined(HAVE_RUBY_ENCODING_H)
|
7
|
+
/* On 64bit platforms we can handle dates way outside 2038-01-19T03:14:07 */
|
8
|
+
/* (10000*31557600) + (12*2592000) + (31*86400) + (11*3600) + (59*60) + 59 */
|
9
|
+
#define TINY_TDS_MAX_TIME 315607276799ULL
|
10
|
+
#else
|
11
|
+
/* On 32bit platforms the maximum date the Time class can handle is 2038-01-19T03:14:07 */
|
12
|
+
/* 2038 years + 1 month + 19 days + 3 hours + 14 minutes + 7 seconds = 64318634047 seconds */
|
13
|
+
/* (2038*31557600) + (1*2592000) + (19*86400) + (3*3600) + (14*60) + 7 */
|
14
|
+
#define TINY_TDS_MAX_TIME 64318634047ULL
|
15
|
+
#endif
|
16
|
+
|
17
|
+
|
18
|
+
// TINY_TDS_MIN_TIME
|
19
|
+
|
20
|
+
#if defined(HAVE_RUBY_ENCODING_H)
|
21
|
+
/* Ruby 1.9 */
|
22
|
+
/* 0000-1-1 00:00:00 UTC */
|
23
|
+
/* (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0 */
|
24
|
+
#define TINY_TDS_MIN_TIME 2678400ULL
|
25
|
+
#elif SIZEOF_INT < SIZEOF_LONG
|
26
|
+
/* 64bit Ruby 1.8 */
|
27
|
+
/* 0139-1-1 00:00:00 UTC */
|
28
|
+
/* (139*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0 */
|
29
|
+
#define TINY_TDS_MIN_TIME 4389184800ULL
|
30
|
+
#elif defined(NEGATIVE_TIME_T)
|
31
|
+
/* 1901-12-13 20:45:52 UTC : The oldest time in 32-bit signed time_t. */
|
32
|
+
/* (1901*31557600) + (12*2592000) + (13*86400) + (20*3600) + (45*60) + 52 */
|
33
|
+
#define TINY_TDS_MIN_TIME 60023299552ULL
|
34
|
+
#else
|
35
|
+
/* 1970-01-01 00:00:01 UTC : The Unix epoch - the oldest time in portable time_t. */
|
36
|
+
/* (1970*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 1 */
|
37
|
+
#define TINY_TDS_MIN_TIME 62171150401ULL
|
38
|
+
#endif
|
39
|
+
|
40
|
+
|
41
|
+
// File Types/Vars
|
42
|
+
|
4
43
|
VALUE cTinyTdsResult;
|
5
44
|
extern VALUE mTinyTds, cTinyTdsClient, cTinyTdsError;
|
6
45
|
VALUE cBigDecimal, cDate, cDateTime;
|
@@ -8,7 +47,7 @@ VALUE opt_decimal_zero, opt_float_zero, opt_one, opt_zero, opt_four, opt_19hdr,
|
|
8
47
|
int opt_ruby_186;
|
9
48
|
static ID intern_new, intern_utc, intern_local, intern_localtime, intern_merge,
|
10
49
|
intern_civil, intern_new_offset, intern_plus, intern_divide, intern_Rational;
|
11
|
-
static ID sym_symbolize_keys, sym_as, sym_array, sym_cache_rows, sym_first, sym_timezone, sym_local, sym_utc;
|
50
|
+
static ID sym_symbolize_keys, sym_as, sym_array, sym_cache_rows, sym_first, sym_timezone, sym_local, sym_utc, sym_empty_sets;
|
12
51
|
|
13
52
|
|
14
53
|
// Lib Macros
|
@@ -200,8 +239,9 @@ static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_
|
|
200
239
|
msec = date_rec.datemsecond;
|
201
240
|
if (year+month+day+hour+min+sec+msec != 0) {
|
202
241
|
VALUE offset = (timezone == intern_local) ? rwrap->local_offset : opt_zero;
|
242
|
+
uint64_t seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec;
|
203
243
|
/* Use DateTime */
|
204
|
-
if (
|
244
|
+
if (seconds < TINY_TDS_MIN_TIME || seconds > TINY_TDS_MAX_TIME) {
|
205
245
|
VALUE datetime_sec = INT2NUM(sec);
|
206
246
|
if (msec != 0) {
|
207
247
|
if ((opt_ruby_186 == 1 && sec < 59) || (opt_ruby_186 != 1)) {
|
@@ -256,8 +296,12 @@ static VALUE rb_tinytds_result_fields(VALUE self) {
|
|
256
296
|
VALUE fields_processed = rb_ary_entry(rwrap->fields_processed, rwrap->number_of_results);
|
257
297
|
if ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED) && (fields_processed == Qnil)) {
|
258
298
|
/* Default query options. */
|
299
|
+
int symbolize_keys = 0, empty_sets = 1;
|
259
300
|
VALUE qopts = rb_iv_get(self, "@query_options");
|
260
|
-
|
301
|
+
if (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue)
|
302
|
+
symbolize_keys = 1;
|
303
|
+
if (rb_hash_aref(qopts, sym_empty_sets) == Qfalse)
|
304
|
+
empty_sets = 0;
|
261
305
|
/* Set number_of_fields count for this result set. */
|
262
306
|
rwrap->number_of_fields = dbnumcols(rwrap->client);
|
263
307
|
if (rwrap->number_of_fields > 0) {
|
@@ -292,7 +336,7 @@ static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
|
292
336
|
/* Local Vars */
|
293
337
|
VALUE qopts, opts, block;
|
294
338
|
ID timezone;
|
295
|
-
int symbolize_keys = 0, as_array = 0, cache_rows = 0, first = 0;
|
339
|
+
int symbolize_keys = 0, as_array = 0, cache_rows = 0, first = 0, empty_sets = 0;
|
296
340
|
/* Merge Options Hash To Query Options. Populate Opts & Block Var. */
|
297
341
|
qopts = rb_iv_get(self, "@query_options");
|
298
342
|
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1)
|
@@ -315,6 +359,8 @@ static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
|
315
359
|
rb_warn(":timezone option must be :utc or :local - defaulting to :local");
|
316
360
|
timezone = intern_local;
|
317
361
|
}
|
362
|
+
if (rb_hash_aref(qopts, sym_empty_sets) == Qtrue)
|
363
|
+
empty_sets = 1;
|
318
364
|
/* Make The Results Or Yield Existing */
|
319
365
|
if (NIL_P(rwrap->results)) {
|
320
366
|
rwrap->results = rb_ary_new();
|
@@ -322,8 +368,9 @@ static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
|
322
368
|
RETCODE dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
323
369
|
while ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED)) {
|
324
370
|
int has_rows = (DBROWS(rwrap->client) == SUCCEED) ? 1 : 0;
|
325
|
-
|
326
|
-
|
371
|
+
if (has_rows || empty_sets || (rwrap->number_of_results == 0))
|
372
|
+
rb_tinytds_result_fields(self);
|
373
|
+
if ((has_rows || empty_sets) && rwrap->number_of_fields > 0) {
|
327
374
|
/* Create rows for this result set. */
|
328
375
|
unsigned long rowi = 0;
|
329
376
|
VALUE result = rb_ary_new();
|
@@ -356,6 +403,7 @@ static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
|
356
403
|
// If we find results increment the counter that helpers use and setup the next loop.
|
357
404
|
rwrap->number_of_results = rwrap->number_of_results + 1;
|
358
405
|
dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
406
|
+
rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qnil);
|
359
407
|
} else {
|
360
408
|
// If we do not find results, side step the rb_tinytds_result_dbresults_retcode helper and
|
361
409
|
// manually populate its memoized array while nullifing any memoized fields too before loop.
|
@@ -473,6 +521,7 @@ void init_tinytds_result() {
|
|
473
521
|
sym_local = ID2SYM(intern_local);
|
474
522
|
sym_utc = ID2SYM(intern_utc);
|
475
523
|
sym_timezone = ID2SYM(rb_intern("timezone"));
|
524
|
+
sym_empty_sets = ID2SYM(rb_intern("empty_sets"));
|
476
525
|
/* Data Conversion Options */
|
477
526
|
opt_decimal_zero = rb_str_new2("0.0");
|
478
527
|
rb_global_variable(&opt_decimal_zero);
|
data/ext/tiny_tds/result.h
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
#ifndef TINYTDS_RESULT_H
|
3
3
|
#define TINYTDS_RESULT_H
|
4
4
|
|
5
|
-
|
6
|
-
typedef tds_sysdep_int64_type DBBIGINT; /*
|
5
|
+
#ifndef DBSETLDBNAME
|
6
|
+
typedef tds_sysdep_int64_type DBBIGINT; /* For FreeTDS 0.82 */
|
7
|
+
#endif
|
7
8
|
|
8
9
|
void init_tinytds_result();
|
9
10
|
VALUE rb_tinytds_new_result_obj(DBPROCESS *c);
|
data/lib/tiny_tds/client.rb
CHANGED
@@ -7,8 +7,10 @@ module TinyTds
|
|
7
7
|
'100' => 2,
|
8
8
|
'42' => 3,
|
9
9
|
'70' => 4,
|
10
|
+
'71' => 5,
|
10
11
|
'80' => 5,
|
11
|
-
'
|
12
|
+
'72' => 6,
|
13
|
+
'90' => 6
|
12
14
|
}.freeze
|
13
15
|
|
14
16
|
TDS_VERSIONS_GETTERS = {
|
@@ -21,28 +23,33 @@ module TinyTds
|
|
21
23
|
6 => {:name => 'DBTDS_4_9_5', :description => '4.9.5 (NCR) SQL Server'},
|
22
24
|
7 => {:name => 'DBTDS_5_0', :description => '5.0 SQL Server'},
|
23
25
|
8 => {:name => 'DBTDS_7_0', :description => 'Microsoft SQL Server 7.0'},
|
24
|
-
9 => {:name => '
|
25
|
-
10 => {:name => '
|
26
|
+
9 => {:name => 'DBTDS_7_1', :description => 'Microsoft SQL Server 2000'},
|
27
|
+
10 => {:name => 'DBTDS_7_2', :description => 'Microsoft SQL Server 2005'}
|
26
28
|
}.freeze
|
27
29
|
|
28
30
|
@@default_query_options = {
|
29
31
|
:as => :hash,
|
30
32
|
:symbolize_keys => false,
|
31
33
|
:cache_rows => true,
|
32
|
-
:timezone => :local
|
34
|
+
:timezone => :local,
|
35
|
+
:empty_sets => true
|
33
36
|
}
|
34
37
|
|
35
38
|
attr_reader :query_options
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
encoding
|
40
|
+
class << self
|
41
|
+
|
42
|
+
def default_query_options
|
43
|
+
@@default_query_options
|
44
|
+
end
|
45
|
+
|
46
|
+
# Most, if not all, iconv encoding names can be found by ruby. Just in case, you can
|
47
|
+
# overide this method to return a string name that Encoding.find would work with. Default
|
48
|
+
# is to return the passed encoding.
|
49
|
+
def transpose_iconv_encoding(encoding)
|
50
|
+
encoding
|
51
|
+
end
|
52
|
+
|
46
53
|
end
|
47
54
|
|
48
55
|
|
@@ -51,7 +58,7 @@ module TinyTds
|
|
51
58
|
raise ArgumentError, 'missing :host option if no :dataserver given' if opts[:dataserver].to_s.empty? && opts[:host].to_s.empty?
|
52
59
|
@query_options = @@default_query_options.dup
|
53
60
|
opts[:appname] ||= 'TinyTds'
|
54
|
-
opts[:tds_version] = TDS_VERSIONS_SETTERS[opts[:tds_version].to_s] || TDS_VERSIONS_SETTERS['
|
61
|
+
opts[:tds_version] = TDS_VERSIONS_SETTERS[opts[:tds_version].to_s] || TDS_VERSIONS_SETTERS['71']
|
55
62
|
opts[:login_timeout] ||= 60
|
56
63
|
opts[:timeout] ||= 5
|
57
64
|
opts[:encoding] = (opts[:encoding].nil? || opts[:encoding].downcase == 'utf8') ? 'UTF-8' : opts[:encoding].upcase
|
data/lib/tiny_tds/version.rb
CHANGED
data/tasks/ports.rake
CHANGED
@@ -2,12 +2,15 @@ require "mini_portile"
|
|
2
2
|
require "rake/extensioncompiler"
|
3
3
|
|
4
4
|
namespace :ports do
|
5
|
-
|
5
|
+
|
6
|
+
# If your using 0.82, you may have to make a conf file to get it to work. For example:
|
7
|
+
# $ export FREETDSCONF='/opt/local/etc/freetds/freetds.conf'
|
6
8
|
ICONV_VERSION = "1.13.1"
|
7
9
|
FREETDS_VERSION = ENV['TINYTDS_FREETDS_082'] ? "0.82" : "0.91"
|
8
10
|
FREETDS_VERSION_INFO = {
|
9
|
-
"0.82" => {:files => "http://ibiblio.org/pub/Linux/ALPHA/freetds/
|
10
|
-
"0.
|
11
|
+
"0.82" => {:files => "http://ibiblio.org/pub/Linux/ALPHA/freetds/old/0.82/freetds-0.82.tar.gz"},
|
12
|
+
# "0.82" => {:files => "http://ibiblio.org/pub/Linux/ALPHA/freetds/old/0.82/freetds-patched.tgz"},
|
13
|
+
"0.91" => {:files => "http://ibiblio.org/pub/Linux/ALPHA/freetds/stable/freetds-0.91.tar.gz"} }
|
11
14
|
|
12
15
|
ORIGINAL_HOST = RbConfig::CONFIG["arch"]
|
13
16
|
|
@@ -25,6 +28,7 @@ namespace :ports do
|
|
25
28
|
recipe = $recipes[:libiconv]
|
26
29
|
checkpoint = "ports/.#{recipe.name}.#{recipe.version}.#{recipe.host}.timestamp"
|
27
30
|
unless File.exist?(checkpoint)
|
31
|
+
recipe.configure_options << "CFLAGS='-fPIC'"
|
28
32
|
recipe.cook
|
29
33
|
touch checkpoint
|
30
34
|
end
|
@@ -39,7 +43,12 @@ namespace :ports do
|
|
39
43
|
# recipe.configure_options << "--disable-debug"
|
40
44
|
recipe.configure_options << '--sysconfdir="C:/Sites"' if recipe.host != ORIGINAL_HOST
|
41
45
|
recipe.configure_options << "--disable-odbc"
|
42
|
-
|
46
|
+
if ENV['TINYTDS_FREETDS_082']
|
47
|
+
recipe.configure_options << "--with-tdsver=8.0"
|
48
|
+
else
|
49
|
+
recipe.configure_options << "--with-tdsver=7.1"
|
50
|
+
end
|
51
|
+
recipe.configure_options << "CFLAGS='-fPIC'"
|
43
52
|
recipe.cook
|
44
53
|
touch checkpoint
|
45
54
|
end
|
@@ -84,28 +84,43 @@ end
|
|
84
84
|
|
85
85
|
=begin
|
86
86
|
|
87
|
+
Query Tinytds
|
88
|
+
=============
|
89
|
+
Author: Ken Collins
|
90
|
+
Date: September 11, 2011
|
91
|
+
Summary: Benchmark TinyTds Querys
|
92
|
+
|
87
93
|
System Information
|
88
94
|
------------------
|
89
|
-
Operating System: Mac OS X 10.
|
90
|
-
CPU:
|
91
|
-
Processor Count:
|
92
|
-
Memory:
|
93
|
-
ruby 1.8.7 (
|
95
|
+
Operating System: Mac OS X 10.7.1 (11B26)
|
96
|
+
CPU: Quad-Core Intel Xeon 2.66 GHz
|
97
|
+
Processor Count: 4
|
98
|
+
Memory: 24 GB
|
99
|
+
ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-darwin11.1.0], MBARI 0x6770, Ruby Enterprise Edition 2011.03
|
94
100
|
|
95
|
-
"Nothing" is up to 89% faster over 1,000 repetitions
|
96
101
|
----------------------------------------------------
|
97
|
-
|
98
|
-
Nothing 0.
|
99
|
-
|
100
|
-
|
101
|
-
Floats 0.
|
102
|
-
Moneys 0.
|
103
|
-
Integers 0.
|
104
|
-
Binaries 0.
|
105
|
-
Decimals 0.
|
106
|
-
|
107
|
-
|
108
|
-
All
|
102
|
+
(before 64bit times) (after 64bit times)
|
103
|
+
Nothing 0.287657022476196 secs Nothing 0.289273977279663 secs
|
104
|
+
Bits 0.406533002853394 secs Bits 0.424988031387329 secs
|
105
|
+
Guids 0.419962882995605 secs Guids 0.427381992340088 secs
|
106
|
+
Floats 0.452103137969971 secs Floats 0.455377101898193 secs
|
107
|
+
Moneys 0.481696844100952 secs Moneys 0.485175132751465 secs
|
108
|
+
Integers 0.496185064315796 secs Integers 0.525003910064697 secs
|
109
|
+
Binaries 0.538873195648193 secs Decimals 0.541536808013916 secs
|
110
|
+
Decimals 0.540570974349976 secs Binaries 0.542865991592407 secs
|
111
|
+
Dates 0.761389970779419 secs Dates 1.51440119743347 secs
|
112
|
+
Chars 0.793163061141968 secs Chars 0.666505098342896 secs
|
113
|
+
All 4.4630811214447 secs All 5.17242312431335 secs
|
109
114
|
|
110
115
|
=end
|
111
116
|
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
|
data/test/client_test.rb
CHANGED
@@ -25,7 +25,7 @@ class ClientTest < TinyTds::TestCase
|
|
25
25
|
|
26
26
|
should 'have a getters for the tds version information (brittle since conf takes precedence)' do
|
27
27
|
assert_equal 9, @client.tds_version
|
28
|
-
assert_equal '
|
28
|
+
assert_equal 'DBTDS_7_1 - Microsoft SQL Server 2000', @client.tds_version_info
|
29
29
|
end
|
30
30
|
|
31
31
|
should 'use UTF-8 client charset/encoding by default' do
|
data/test/result_test.rb
CHANGED
@@ -4,7 +4,7 @@ require 'test_helper'
|
|
4
4
|
class ResultTest < TinyTds::TestCase
|
5
5
|
|
6
6
|
context 'Basic query and result' do
|
7
|
-
|
7
|
+
|
8
8
|
setup do
|
9
9
|
@@current_schema_loaded ||= load_current_schema
|
10
10
|
@client = new_connection
|
@@ -110,7 +110,7 @@ class ResultTest < TinyTds::TestCase
|
|
110
110
|
assert_equal text, row['varchar_50']
|
111
111
|
end
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
should 'insert and find unicode data' do
|
115
115
|
rollback_transaction(@client) do
|
116
116
|
text = '✓'
|
@@ -174,7 +174,7 @@ class ResultTest < TinyTds::TestCase
|
|
174
174
|
assert_equal sql_identity+1, native_identity
|
175
175
|
end
|
176
176
|
end
|
177
|
-
|
177
|
+
|
178
178
|
should 'return bigint for #insert when needed' do
|
179
179
|
rollback_transaction(@client) do
|
180
180
|
seed = 9223372036854775805
|
@@ -326,9 +326,20 @@ class ResultTest < TinyTds::TestCase
|
|
326
326
|
end
|
327
327
|
|
328
328
|
context 'with multiple result sets' do
|
329
|
-
|
329
|
+
|
330
330
|
setup do
|
331
|
-
@
|
331
|
+
@empty_select = "SELECT 1 AS [rs1] WHERE 1 = 0"
|
332
|
+
@double_select = "SELECT 1 AS [rs1]
|
333
|
+
SELECT 2 AS [rs2]"
|
334
|
+
@triple_select_1st_empty = "SELECT 1 AS [rs1] WHERE 1 = 0
|
335
|
+
SELECT 2 AS [rs2]
|
336
|
+
SELECT 3 AS [rs3]"
|
337
|
+
@triple_select_2nd_empty = "SELECT 1 AS [rs1]
|
338
|
+
SELECT 2 AS [rs2] WHERE 1 = 0
|
339
|
+
SELECT 3 AS [rs3]"
|
340
|
+
@triple_select_3rd_empty = "SELECT 1 AS [rs1]
|
341
|
+
SELECT 2 AS [rs2]
|
342
|
+
SELECT 3 AS [rs3] WHERE 1 = 0"
|
332
343
|
end
|
333
344
|
|
334
345
|
should 'handle a command buffer with double selects' do
|
@@ -337,7 +348,7 @@ class ResultTest < TinyTds::TestCase
|
|
337
348
|
assert_equal 2, result_sets.size
|
338
349
|
assert_equal [{'rs1' => 1}], result_sets.first
|
339
350
|
assert_equal [{'rs2' => 2}], result_sets.last
|
340
|
-
assert_equal [['rs1'],['rs2']], result.fields
|
351
|
+
assert_equal [['rs1'], ['rs2']], result.fields
|
341
352
|
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
342
353
|
# As array
|
343
354
|
result = @client.execute(@double_select)
|
@@ -345,7 +356,7 @@ class ResultTest < TinyTds::TestCase
|
|
345
356
|
assert_equal 2, result_sets.size
|
346
357
|
assert_equal [[1]], result_sets.first
|
347
358
|
assert_equal [[2]], result_sets.last
|
348
|
-
assert_equal [['rs1'],['rs2']], result.fields
|
359
|
+
assert_equal [['rs1'], ['rs2']], result.fields
|
349
360
|
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
350
361
|
end
|
351
362
|
|
@@ -365,23 +376,160 @@ class ResultTest < TinyTds::TestCase
|
|
365
376
|
assert constraint_info.key?("constraint_name")
|
366
377
|
end
|
367
378
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
DECLARE @row_number TABLE (row int identity(1,1), id int)
|
375
|
-
INSERT INTO @row_number (id)
|
376
|
-
SELECT [datatypes].[id] FROM [datatypes]
|
377
|
-
SET NOCOUNT OFF
|
378
|
-
SELECT id FROM @row_number|
|
379
|
-
result = @client.execute(sql)
|
380
|
-
result.each.must_equal [{"id"=>id}]
|
381
|
-
result.fields.must_equal ['id']
|
379
|
+
context 'using :empty_sets TRUE' do
|
380
|
+
|
381
|
+
setup do
|
382
|
+
@old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
|
383
|
+
TinyTds::Client.default_query_options[:empty_sets] = true
|
384
|
+
@client = new_connection
|
382
385
|
end
|
386
|
+
|
387
|
+
teardown do
|
388
|
+
TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
|
389
|
+
end
|
390
|
+
|
391
|
+
should 'handle a basic empty result set' do
|
392
|
+
result = @client.execute(@empty_select)
|
393
|
+
assert_equal [], result.each
|
394
|
+
assert_equal ['rs1'], result.fields
|
395
|
+
end
|
396
|
+
|
397
|
+
should 'include empty result sets by default - using 1st empty buffer' do
|
398
|
+
result = @client.execute(@triple_select_1st_empty)
|
399
|
+
result_sets = result.each
|
400
|
+
assert_equal 3, result_sets.size
|
401
|
+
assert_equal [], result_sets[0]
|
402
|
+
assert_equal [{'rs2' => 2}], result_sets[1]
|
403
|
+
assert_equal [{'rs3' => 3}], result_sets[2]
|
404
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
405
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
406
|
+
# As array
|
407
|
+
result = @client.execute(@triple_select_1st_empty)
|
408
|
+
result_sets = result.each(:as => :array)
|
409
|
+
assert_equal 3, result_sets.size
|
410
|
+
assert_equal [], result_sets[0]
|
411
|
+
assert_equal [[2]], result_sets[1]
|
412
|
+
assert_equal [[3]], result_sets[2]
|
413
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
414
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
415
|
+
end
|
416
|
+
|
417
|
+
should 'include empty result sets by default - using 2nd empty buffer' do
|
418
|
+
result = @client.execute(@triple_select_2nd_empty)
|
419
|
+
result_sets = result.each
|
420
|
+
assert_equal 3, result_sets.size
|
421
|
+
assert_equal [{'rs1' => 1}], result_sets[0]
|
422
|
+
assert_equal [], result_sets[1]
|
423
|
+
assert_equal [{'rs3' => 3}], result_sets[2]
|
424
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
425
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
426
|
+
# As array
|
427
|
+
result = @client.execute(@triple_select_2nd_empty)
|
428
|
+
result_sets = result.each(:as => :array)
|
429
|
+
assert_equal 3, result_sets.size
|
430
|
+
assert_equal [[1]], result_sets[0]
|
431
|
+
assert_equal [], result_sets[1]
|
432
|
+
assert_equal [[3]], result_sets[2]
|
433
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
434
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
435
|
+
end
|
436
|
+
|
437
|
+
should 'include empty result sets by default - using 3rd empty buffer' do
|
438
|
+
result = @client.execute(@triple_select_3rd_empty)
|
439
|
+
result_sets = result.each
|
440
|
+
assert_equal 3, result_sets.size
|
441
|
+
assert_equal [{'rs1' => 1}], result_sets[0]
|
442
|
+
assert_equal [{'rs2' => 2}], result_sets[1]
|
443
|
+
assert_equal [], result_sets[2]
|
444
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
445
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
446
|
+
# As array
|
447
|
+
result = @client.execute(@triple_select_3rd_empty)
|
448
|
+
result_sets = result.each(:as => :array)
|
449
|
+
assert_equal 3, result_sets.size
|
450
|
+
assert_equal [[1]], result_sets[0]
|
451
|
+
assert_equal [[2]], result_sets[1]
|
452
|
+
assert_equal [], result_sets[2]
|
453
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
454
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
455
|
+
end
|
456
|
+
|
383
457
|
end
|
384
|
-
|
458
|
+
|
459
|
+
context 'using :empty_sets FALSE' do
|
460
|
+
|
461
|
+
setup do
|
462
|
+
@old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
|
463
|
+
TinyTds::Client.default_query_options[:empty_sets] = false
|
464
|
+
@client = new_connection
|
465
|
+
end
|
466
|
+
|
467
|
+
teardown do
|
468
|
+
TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
|
469
|
+
end
|
470
|
+
|
471
|
+
should 'handle a basic empty result set' do
|
472
|
+
result = @client.execute(@empty_select)
|
473
|
+
assert_equal [], result.each
|
474
|
+
assert_equal ['rs1'], result.fields
|
475
|
+
end
|
476
|
+
|
477
|
+
should 'not include empty result sets by default - using 1st empty buffer' do
|
478
|
+
result = @client.execute(@triple_select_1st_empty)
|
479
|
+
result_sets = result.each
|
480
|
+
assert_equal 2, result_sets.size
|
481
|
+
assert_equal [{'rs2' => 2}], result_sets[0]
|
482
|
+
assert_equal [{'rs3' => 3}], result_sets[1]
|
483
|
+
assert_equal [['rs2'], ['rs3']], result.fields
|
484
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
485
|
+
# As array
|
486
|
+
result = @client.execute(@triple_select_1st_empty)
|
487
|
+
result_sets = result.each(:as => :array)
|
488
|
+
assert_equal 2, result_sets.size
|
489
|
+
assert_equal [[2]], result_sets[0]
|
490
|
+
assert_equal [[3]], result_sets[1]
|
491
|
+
assert_equal [['rs2'], ['rs3']], result.fields
|
492
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
493
|
+
end
|
494
|
+
|
495
|
+
should 'not include empty result sets by default - using 2nd empty buffer' do
|
496
|
+
result = @client.execute(@triple_select_2nd_empty)
|
497
|
+
result_sets = result.each
|
498
|
+
assert_equal 2, result_sets.size
|
499
|
+
assert_equal [{'rs1' => 1}], result_sets[0]
|
500
|
+
assert_equal [{'rs3' => 3}], result_sets[1]
|
501
|
+
assert_equal [['rs1'], ['rs3']], result.fields
|
502
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
503
|
+
# As array
|
504
|
+
result = @client.execute(@triple_select_2nd_empty)
|
505
|
+
result_sets = result.each(:as => :array)
|
506
|
+
assert_equal 2, result_sets.size
|
507
|
+
assert_equal [[1]], result_sets[0]
|
508
|
+
assert_equal [[3]], result_sets[1]
|
509
|
+
assert_equal [['rs1'], ['rs3']], result.fields
|
510
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
511
|
+
end
|
512
|
+
|
513
|
+
should 'not include empty result sets by default - using 3rd empty buffer' do
|
514
|
+
result = @client.execute(@triple_select_3rd_empty)
|
515
|
+
result_sets = result.each
|
516
|
+
assert_equal 2, result_sets.size
|
517
|
+
assert_equal [{'rs1' => 1}], result_sets[0]
|
518
|
+
assert_equal [{'rs2' => 2}], result_sets[1]
|
519
|
+
assert_equal [['rs1'], ['rs2']], result.fields
|
520
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
521
|
+
# As array
|
522
|
+
result = @client.execute(@triple_select_3rd_empty)
|
523
|
+
result_sets = result.each(:as => :array)
|
524
|
+
assert_equal 2, result_sets.size
|
525
|
+
assert_equal [[1]], result_sets[0]
|
526
|
+
assert_equal [[2]], result_sets[1]
|
527
|
+
assert_equal [['rs1'], ['rs2']], result.fields
|
528
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
529
|
+
end
|
530
|
+
|
531
|
+
end
|
532
|
+
|
385
533
|
end
|
386
534
|
|
387
535
|
context 'when casting to native ruby values' do
|
@@ -398,6 +546,28 @@ class ResultTest < TinyTds::TestCase
|
|
398
546
|
|
399
547
|
end
|
400
548
|
|
549
|
+
context 'with data type' do
|
550
|
+
|
551
|
+
context 'char max' do
|
552
|
+
|
553
|
+
setup do
|
554
|
+
@big_text = 'x' * 2_000_000
|
555
|
+
@old_textsize = @client.execute("SELECT @@TEXTSIZE AS [textsize]").each.first['textsize'].inspect
|
556
|
+
@client.execute("SET TEXTSIZE #{(@big_text.length*2)+1}").do
|
557
|
+
end
|
558
|
+
|
559
|
+
should 'insert and select large varchar_max' do
|
560
|
+
insert_and_select_datatype :varchar_max
|
561
|
+
end
|
562
|
+
|
563
|
+
should 'insert and select large nvarchar_max' do
|
564
|
+
insert_and_select_datatype :nvarchar_max
|
565
|
+
end
|
566
|
+
|
567
|
+
end unless sqlserver_2000?
|
568
|
+
|
569
|
+
end
|
570
|
+
|
401
571
|
context 'when shit happens' do
|
402
572
|
|
403
573
|
should 'cope with nil or empty buffer' do
|
@@ -405,6 +575,21 @@ class ResultTest < TinyTds::TestCase
|
|
405
575
|
assert_equal [], @client.execute('').each
|
406
576
|
end
|
407
577
|
|
578
|
+
should 'not raise an error when severity is 10 or less' do
|
579
|
+
(1..10).to_a.each do |severity|
|
580
|
+
@client.execute("RAISERROR(N'Test #{severity} severity', #{severity}, 1)").do
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
should 'raise an error when severity is greater than 10' do
|
585
|
+
action = lambda { @client.execute("RAISERROR(N'Test 11 severity', 11, 1)").do }
|
586
|
+
assert_raise_tinytds_error(action) do |e|
|
587
|
+
assert_equal "Test 11 severity", e.message
|
588
|
+
assert_equal 11, e.severity
|
589
|
+
assert_equal 50000, e.db_error_number
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
408
593
|
should 'throw an error when you execute another query with other results pending' do
|
409
594
|
result1 = @client.execute(@query1)
|
410
595
|
action = lambda { @client.execute(@query1) }
|
@@ -414,7 +599,7 @@ class ResultTest < TinyTds::TestCase
|
|
414
599
|
assert_equal 20019, e.db_error_number
|
415
600
|
end
|
416
601
|
end
|
417
|
-
|
602
|
+
|
418
603
|
should 'error gracefully with bad table name' do
|
419
604
|
action = lambda { @client.execute('SELECT * FROM [foobar]').each }
|
420
605
|
assert_raise_tinytds_error(action) do |e|
|
@@ -425,7 +610,7 @@ class ResultTest < TinyTds::TestCase
|
|
425
610
|
assert_followup_query
|
426
611
|
end
|
427
612
|
|
428
|
-
should 'error gracefully with
|
613
|
+
should 'error gracefully with incorrect syntax' do
|
429
614
|
action = lambda { @client.execute('this will not work').each }
|
430
615
|
assert_raise_tinytds_error(action) do |e|
|
431
616
|
assert_match %r|incorrect syntax|i, e.message
|
@@ -434,6 +619,20 @@ class ResultTest < TinyTds::TestCase
|
|
434
619
|
end
|
435
620
|
assert_followup_query
|
436
621
|
end
|
622
|
+
|
623
|
+
should 'error gracefully with incorrect syntax in sp_executesql' do
|
624
|
+
if @client.freetds_091_or_higer?
|
625
|
+
action = lambda { @client.execute("EXEC sp_executesql N'this will not work'").each }
|
626
|
+
assert_raise_tinytds_error(action) do |e|
|
627
|
+
assert_match %r|incorrect syntax|i, e.message
|
628
|
+
assert_equal 15, e.severity
|
629
|
+
assert_equal 156, e.db_error_number
|
630
|
+
end
|
631
|
+
assert_followup_query
|
632
|
+
else
|
633
|
+
skip 'FreeTDS 0.91 and higher can only pass this test.'
|
634
|
+
end
|
635
|
+
end
|
437
636
|
|
438
637
|
end
|
439
638
|
|
@@ -447,5 +646,14 @@ class ResultTest < TinyTds::TestCase
|
|
447
646
|
assert_equal 1, result.each.first['one']
|
448
647
|
end
|
449
648
|
|
649
|
+
def insert_and_select_datatype(datatype)
|
650
|
+
rollback_transaction(@client) do
|
651
|
+
@client.execute("DELETE FROM [datatypes] WHERE [#{datatype}] IS NOT NULL").do
|
652
|
+
id = @client.execute("INSERT INTO [datatypes] ([#{datatype}]) VALUES (N'#{@big_text}')").insert
|
653
|
+
found_text = find_value id, datatype
|
654
|
+
flunk "Large #{datatype} data with a length of #{@big_text.length} did not match found text with length of #{found_text.length}" unless @big_text == found_text
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
450
658
|
end
|
451
659
|
|
data/test/schema_test.rb
CHANGED
@@ -39,40 +39,77 @@ class SchemaTest < TinyTds::TestCase
|
|
39
39
|
end
|
40
40
|
|
41
41
|
should 'cast datetime' do
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
42
|
+
if ruby18? && ruby32bit?
|
43
|
+
# 1753-01-01T00:00:00.000
|
44
|
+
v = find_value 61, :datetime
|
45
|
+
assert_instance_of DateTime, v, 'not in range of Time class'
|
46
|
+
assert_equal 1753, v.year
|
47
|
+
assert_equal 01, v.month
|
48
|
+
assert_equal 01, v.day
|
49
|
+
assert_equal 0, v.hour
|
50
|
+
assert_equal 0, v.min
|
51
|
+
assert_equal 0, v.sec
|
52
|
+
assert_equal 0, v.usec
|
53
|
+
# 9999-12-31T23:59:59.997
|
54
|
+
v = find_value 62, :datetime
|
55
|
+
assert_instance_of DateTime, v, 'not in range of Time class'
|
56
|
+
assert_equal 9999, v.year
|
57
|
+
assert_equal 12, v.month
|
58
|
+
assert_equal 31, v.day
|
59
|
+
assert_equal 23, v.hour
|
60
|
+
assert_equal 59, v.min
|
61
|
+
assert_equal 59, v.sec
|
62
|
+
assert_equal 997000, v.usec unless ruby186?
|
63
|
+
assert_equal local_offset, find_value(62, :datetime, :timezone => :local).offset
|
64
|
+
assert_equal 0, find_value(62, :datetime, :timezone => :utc).offset
|
65
|
+
# 2010-01-01T12:34:56.123
|
66
|
+
v = find_value 63, :datetime
|
67
|
+
assert_instance_of Time, v, 'in range of Time class'
|
68
|
+
assert_equal 2010, v.year
|
69
|
+
assert_equal 01, v.month
|
70
|
+
assert_equal 01, v.day
|
71
|
+
assert_equal 12, v.hour
|
72
|
+
assert_equal 34, v.min
|
73
|
+
assert_equal 56, v.sec
|
74
|
+
assert_equal 123000, v.usec
|
75
|
+
assert_equal utc_offset, find_value(63, :datetime, :timezone => :local).utc_offset
|
76
|
+
assert_equal 0, find_value(63, :datetime, :timezone => :utc).utc_offset
|
77
|
+
else
|
78
|
+
# 1753-01-01T00:00:00.000
|
79
|
+
v = find_value 61, :datetime
|
80
|
+
assert_instance_of Time, v, 'not in range of Time class'
|
81
|
+
assert_equal 1753, v.year
|
82
|
+
assert_equal 01, v.month
|
83
|
+
assert_equal 01, v.day
|
84
|
+
assert_equal 0, v.hour
|
85
|
+
assert_equal 0, v.min
|
86
|
+
assert_equal 0, v.sec
|
87
|
+
assert_equal 0, v.usec
|
88
|
+
# 9999-12-31T23:59:59.997
|
89
|
+
v = find_value 62, :datetime
|
90
|
+
assert_instance_of Time, v, 'not in range of Time class'
|
91
|
+
assert_equal 9999, v.year
|
92
|
+
assert_equal 12, v.month
|
93
|
+
assert_equal 31, v.day
|
94
|
+
assert_equal 23, v.hour
|
95
|
+
assert_equal 59, v.min
|
96
|
+
assert_equal 59, v.sec
|
97
|
+
assert_equal 997000, v.usec unless ruby186?
|
98
|
+
assert_equal utc_offset, find_value(62, :datetime, :timezone => :local).utc_offset
|
99
|
+
assert_equal 0, find_value(62, :datetime, :timezone => :utc).utc_offset
|
100
|
+
# 2010-01-01T12:34:56.123
|
101
|
+
v = find_value 63, :datetime
|
102
|
+
assert_instance_of Time, v, 'in range of Time class'
|
103
|
+
assert_equal 2010, v.year
|
104
|
+
assert_equal 01, v.month
|
105
|
+
assert_equal 01, v.day
|
106
|
+
assert_equal 12, v.hour
|
107
|
+
assert_equal 34, v.min
|
108
|
+
assert_equal 56, v.sec
|
109
|
+
assert_equal 123000, v.usec
|
110
|
+
assert_equal utc_offset, find_value(63, :datetime, :timezone => :local).utc_offset
|
111
|
+
assert_equal 0, find_value(63, :datetime, :timezone => :utc).utc_offset
|
112
|
+
end
|
76
113
|
end
|
77
114
|
|
78
115
|
should 'cast decimal' do
|
@@ -146,28 +183,53 @@ class SchemaTest < TinyTds::TestCase
|
|
146
183
|
end
|
147
184
|
|
148
185
|
should 'cast smalldatetime' do
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
186
|
+
if ruby18? && ruby32bit?
|
187
|
+
# 1901-01-01 15:45:00
|
188
|
+
v = find_value 231, :smalldatetime
|
189
|
+
assert_instance_of DateTime, v
|
190
|
+
assert_equal 1901, v.year
|
191
|
+
assert_equal 01, v.month
|
192
|
+
assert_equal 01, v.day
|
193
|
+
assert_equal 15, v.hour
|
194
|
+
assert_equal 45, v.min
|
195
|
+
assert_equal 00, v.sec
|
196
|
+
assert_equal local_offset, find_value(231, :smalldatetime, :timezone => :local).offset
|
197
|
+
assert_equal 0, find_value(231, :smalldatetime, :timezone => :utc).offset
|
198
|
+
# 2078-06-05 04:20:00
|
199
|
+
v = find_value 232, :smalldatetime
|
200
|
+
assert_instance_of DateTime, v
|
201
|
+
assert_equal 2078, v.year
|
202
|
+
assert_equal 06, v.month
|
203
|
+
assert_equal 05, v.day
|
204
|
+
assert_equal 04, v.hour
|
205
|
+
assert_equal 20, v.min
|
206
|
+
assert_equal 00, v.sec
|
207
|
+
assert_equal local_offset, find_value(232, :smalldatetime, :timezone => :local).offset
|
208
|
+
assert_equal 0, find_value(232, :smalldatetime, :timezone => :utc).offset
|
209
|
+
else
|
210
|
+
# 1901-01-01 15:45:00
|
211
|
+
v = find_value 231, :smalldatetime
|
212
|
+
assert_instance_of Time, v
|
213
|
+
assert_equal 1901, v.year
|
214
|
+
assert_equal 01, v.month
|
215
|
+
assert_equal 01, v.day
|
216
|
+
assert_equal 15, v.hour
|
217
|
+
assert_equal 45, v.min
|
218
|
+
assert_equal 00, v.sec
|
219
|
+
assert_equal Time.local(1901).utc_offset, find_value(231, :smalldatetime, :timezone => :local).utc_offset
|
220
|
+
assert_equal 0, find_value(231, :smalldatetime, :timezone => :utc).utc_offset
|
221
|
+
# 2078-06-05 04:20:00
|
222
|
+
v = find_value 232, :smalldatetime
|
223
|
+
assert_instance_of Time, v
|
224
|
+
assert_equal 2078, v.year
|
225
|
+
assert_equal 06, v.month
|
226
|
+
assert_equal 05, v.day
|
227
|
+
assert_equal 04, v.hour
|
228
|
+
assert_equal 20, v.min
|
229
|
+
assert_equal 00, v.sec
|
230
|
+
assert_equal Time.local(2078,6).utc_offset, find_value(232, :smalldatetime, :timezone => :local).utc_offset
|
231
|
+
assert_equal 0, find_value(232, :smalldatetime, :timezone => :utc).utc_offset
|
232
|
+
end
|
171
233
|
end
|
172
234
|
|
173
235
|
should 'cast smallint' do
|
@@ -242,33 +304,54 @@ class SchemaTest < TinyTds::TestCase
|
|
242
304
|
|
243
305
|
context 'for 2008 and up' do
|
244
306
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
end
|
268
|
-
|
269
|
-
should 'cast
|
270
|
-
|
271
|
-
|
307
|
+
# These data types always come back as SYBTEXT and there is no way I can
|
308
|
+
# find out the column's human readable name.
|
309
|
+
#
|
310
|
+
# * [date]
|
311
|
+
# * [datetime2]
|
312
|
+
# * [datetimeoffset]
|
313
|
+
# * [time]
|
314
|
+
#
|
315
|
+
# I have tried the following and I only get back either "char" or 0/null.
|
316
|
+
#
|
317
|
+
# rb_warn("SYBTEXT: dbprtype: %s", dbprtype(coltype));
|
318
|
+
# rb_warn("SYBTEXT: dbcolutype: %s", dbcolutype(rwrap->client, col));
|
319
|
+
# rb_warn("SYBTEXT: dbcolutype: %ld", dbcolutype(rwrap->client, col));
|
320
|
+
|
321
|
+
# should 'cast date' do
|
322
|
+
# value = find_value 51, :date
|
323
|
+
# assert_equal '', value
|
324
|
+
# end
|
325
|
+
#
|
326
|
+
# should 'cast datetime2' do
|
327
|
+
# value = find_value 72, :datetime2_7
|
328
|
+
# assert_equal '', value
|
329
|
+
# end
|
330
|
+
#
|
331
|
+
# should 'cast datetimeoffset' do
|
332
|
+
# value = find_value 81, :datetimeoffset_2
|
333
|
+
# assert_equal '', value
|
334
|
+
# end
|
335
|
+
#
|
336
|
+
# should 'cast geography' do
|
337
|
+
# value = find_value 111, :geography
|
338
|
+
# assert_equal '', value
|
339
|
+
# end
|
340
|
+
#
|
341
|
+
# should 'cast geometry' do
|
342
|
+
# value = find_value 121, :geometry
|
343
|
+
# assert_equal '', value
|
344
|
+
# end
|
345
|
+
#
|
346
|
+
# should 'cast hierarchyid' do
|
347
|
+
# value = find_value 131, :hierarchyid
|
348
|
+
# assert_equal '', value
|
349
|
+
# end
|
350
|
+
#
|
351
|
+
# should 'cast time' do
|
352
|
+
# value = find_value 283, :time_7
|
353
|
+
# assert_equal '', value
|
354
|
+
# end
|
272
355
|
|
273
356
|
end if sqlserver_2008? || sqlserver_azure?
|
274
357
|
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tiny_tds
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 15424071
|
5
|
+
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
- 4
|
9
8
|
- 5
|
10
|
-
|
9
|
+
- 0
|
10
|
+
- rc
|
11
|
+
- 1
|
12
|
+
version: 0.5.0.rc1
|
11
13
|
platform: ruby
|
12
14
|
authors:
|
13
15
|
- Ken Collins
|
@@ -16,7 +18,7 @@ autorequire:
|
|
16
18
|
bindir: bin
|
17
19
|
cert_chain: []
|
18
20
|
|
19
|
-
date: 2011-
|
21
|
+
date: 2011-09-12 00:00:00 -04:00
|
20
22
|
default_executable:
|
21
23
|
dependencies: []
|
22
24
|
|
@@ -83,12 +85,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
86
|
none: false
|
85
87
|
requirements:
|
86
|
-
- - "
|
88
|
+
- - ">"
|
87
89
|
- !ruby/object:Gem::Version
|
88
|
-
hash:
|
90
|
+
hash: 25
|
89
91
|
segments:
|
90
|
-
-
|
91
|
-
|
92
|
+
- 1
|
93
|
+
- 3
|
94
|
+
- 1
|
95
|
+
version: 1.3.1
|
92
96
|
requirements: []
|
93
97
|
|
94
98
|
rubyforge_project:
|