tiny_tds 0.6.2-x64-mingw32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +19 -0
- data/CHANGELOG +180 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +23 -0
- data/README.md +385 -0
- data/Rakefile +114 -0
- data/compile/rake-compiler-dev-box.patch +31 -0
- data/ext/patch/Makefile.in.diff +29 -0
- data/ext/patch/dblib-30-char-username.diff +11 -0
- data/ext/patch/sspi_w_kerberos.diff +42 -0
- data/ext/tiny_tds/client.c +408 -0
- data/ext/tiny_tds/client.h +46 -0
- data/ext/tiny_tds/extconf.rb +102 -0
- data/ext/tiny_tds/result.c +599 -0
- data/ext/tiny_tds/result.h +36 -0
- data/ext/tiny_tds/tiny_tds_ext.c +12 -0
- data/ext/tiny_tds/tiny_tds_ext.h +15 -0
- data/lib/tiny_tds.rb +19 -0
- data/lib/tiny_tds/client.rb +96 -0
- data/lib/tiny_tds/error.rb +29 -0
- data/lib/tiny_tds/result.rb +8 -0
- data/lib/tiny_tds/version.rb +3 -0
- data/tasks/ports.rake +79 -0
- data/test/benchmark/query.rb +77 -0
- data/test/benchmark/query_odbc.rb +106 -0
- data/test/benchmark/query_tinytds.rb +126 -0
- data/test/client_test.rb +170 -0
- data/test/result_test.rb +732 -0
- data/test/schema/1px.gif +0 -0
- data/test/schema/sqlserver_2000.sql +138 -0
- data/test/schema/sqlserver_2005.sql +138 -0
- data/test/schema/sqlserver_2008.sql +138 -0
- data/test/schema/sqlserver_azure.sql +138 -0
- data/test/schema/sybase_ase.sql +138 -0
- data/test/schema_test.rb +305 -0
- data/test/test_helper.rb +195 -0
- data/test/thread_test.rb +95 -0
- metadata +171 -0
data/Rakefile
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rbconfig'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rake/extensiontask'
|
7
|
+
require 'rubygems/package_task'
|
8
|
+
|
9
|
+
def test_libs
|
10
|
+
['lib','test']
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_files
|
14
|
+
Dir.glob("test/**/*_test.rb").sort
|
15
|
+
end
|
16
|
+
|
17
|
+
gemspec = Gem::Specification::load(File.expand_path('../tiny_tds.gemspec', __FILE__))
|
18
|
+
|
19
|
+
Rake::TestTask.new do |t|
|
20
|
+
t.libs = test_libs
|
21
|
+
t.test_files = test_files
|
22
|
+
t.verbose = true
|
23
|
+
end
|
24
|
+
|
25
|
+
Gem::PackageTask.new(gemspec) do |pkg|
|
26
|
+
pkg.need_tar = false
|
27
|
+
pkg.need_zip = false
|
28
|
+
end
|
29
|
+
|
30
|
+
task :compile => ["ports:freetds"] unless ENV['TINYTDS_SKIP_PORTS']
|
31
|
+
|
32
|
+
task :build => [:clean, :compile]
|
33
|
+
|
34
|
+
task :default => [:build, :test]
|
35
|
+
|
36
|
+
Dir["tasks/*.rake"].sort.each { |f| load f }
|
37
|
+
|
38
|
+
Rake::ExtensionTask.new('tiny_tds', gemspec) do |ext|
|
39
|
+
ext.lib_dir = 'lib/tiny_tds'
|
40
|
+
if RUBY_PLATFORM =~ /mswin|mingw/ then
|
41
|
+
# Define target for extension (supporting fat binaries).
|
42
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
43
|
+
ext.lib_dir = "lib/tiny_tds/#{$1}"
|
44
|
+
else
|
45
|
+
ext.cross_compile = true
|
46
|
+
ext.cross_platform = []
|
47
|
+
ext.cross_config_options << "--disable-lookup"
|
48
|
+
config_opts = {}
|
49
|
+
|
50
|
+
platform_host_map = {
|
51
|
+
'x86-mingw32' => 'i586-mingw32msvc',
|
52
|
+
'x64-mingw32' => 'x86_64-w64-mingw32'
|
53
|
+
}
|
54
|
+
|
55
|
+
# This section ensures we setup up rake dependencies and that libiconv
|
56
|
+
# and freetds are compiled using the cross-compiler and then passed to
|
57
|
+
# extconf.rb in such a way that library detection works.
|
58
|
+
platform_host_map.each do |plat, host|
|
59
|
+
ext.cross_platform << plat
|
60
|
+
|
61
|
+
libiconv = define_libiconv_recipe(plat, host)
|
62
|
+
freetds = define_freetds_recipe(plat, host, libiconv)
|
63
|
+
task "native:#{plat}" => ["ports:freetds:#{plat}"] unless ENV['TINYTDS_SKIP_PORTS']
|
64
|
+
|
65
|
+
# For some reason --with-freetds-dir and --with-iconv-dir would not work.
|
66
|
+
# It seems the default params that extconf.rb constructs include
|
67
|
+
# --without-freetds-include, --without-freetds-lib, --without-iconv-lib
|
68
|
+
# and --without-iconv-include. Thus we must explicitly override them.
|
69
|
+
config_opts[plat] = " --with-freetds-include=#{freetds.path}/include"
|
70
|
+
config_opts[plat] += " --with-freetds-lib=#{freetds.path}/lib"
|
71
|
+
config_opts[plat] += " --with-iconv-include=#{libiconv.path}/include"
|
72
|
+
config_opts[plat] += " --with-iconv-lib=#{libiconv.path}/lib"
|
73
|
+
end
|
74
|
+
|
75
|
+
ext.cross_config_options << config_opts
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "Checks out rake-compiler-dev-box and sets up the cross-compiling box. This can take a long time."
|
80
|
+
task 'cross-compile:setup' do
|
81
|
+
# Make sure we have the command-line programs we need
|
82
|
+
sh 'git', '--version'
|
83
|
+
sh 'vagrant', '-v'
|
84
|
+
|
85
|
+
if Dir.exists? 'rake-compiler-dev-box'
|
86
|
+
Dir.chdir 'rake-compiler-dev-box'
|
87
|
+
else
|
88
|
+
sh 'git', 'clone', 'https://github.com/tjschuck/rake-compiler-dev-box.git'
|
89
|
+
Dir.chdir 'rake-compiler-dev-box'
|
90
|
+
sh 'patch -p1 < ../compile/rake-compiler-dev-box.patch'
|
91
|
+
end
|
92
|
+
|
93
|
+
sh 'vagrant', 'up'
|
94
|
+
end
|
95
|
+
|
96
|
+
desc "Invokes the cross-compiler to generate gems for windows"
|
97
|
+
task 'cross-compile' do
|
98
|
+
build_dir = './rake-compiler-dev-box/tiny_tds/'
|
99
|
+
if not Dir.exists?(build_dir)
|
100
|
+
Dir.mkdir build_dir
|
101
|
+
end
|
102
|
+
# Copy the current version of tiny_tds into the box directory
|
103
|
+
Dir.entries('./').each do |entry|
|
104
|
+
next if ['.', '..', 'rake-compiler-dev-box'].include?(entry)
|
105
|
+
cp_r entry, build_dir, :remove_destination => true
|
106
|
+
end
|
107
|
+
Dir.chdir './rake-compiler-dev-box'
|
108
|
+
sh 'vagrant ssh -c "package_win32_fat_binary tiny_tds"'
|
109
|
+
end
|
110
|
+
|
111
|
+
desc "Cleans up all of the compiled ports, gems and tmp files from cross-compile"
|
112
|
+
task 'cross-compile:clean' do
|
113
|
+
rm_r './rake-compiler-dev-box/tiny_tds'
|
114
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
diff --git a/bin/package_win32_fat_binary b/bin/package_win32_fat_binary
|
2
|
+
index b336eb7..8ef4f98 100755
|
3
|
+
--- a/bin/package_win32_fat_binary
|
4
|
+
+++ b/bin/package_win32_fat_binary
|
5
|
+
@@ -12,9 +12,8 @@ cd '/vagrant'
|
6
|
+
# passed in path of gem to be cross-compiled
|
7
|
+
cd $1
|
8
|
+
|
9
|
+
-# need to use 1.8.7 for fat-binaries (1.9.3 can't cross-build 1.8.7)
|
10
|
+
-rvm use 1.8.7
|
11
|
+
+rvm use 1.9.3
|
12
|
+
bundle install
|
13
|
+
|
14
|
+
bundle exec rake clean
|
15
|
+
-bundle exec rake cross native gem RUBY_CC_VERSION=1.8.7:1.9.3:2.0.0
|
16
|
+
+bundle exec rake cross native gem RUBY_CC_VERSION=1.9.3:2.0.0:2.1.1
|
17
|
+
diff --git a/bin/prepare_xrubies b/bin/prepare_xrubies
|
18
|
+
index 0e2043c..bd1d3c8 100755
|
19
|
+
--- a/bin/prepare_xrubies
|
20
|
+
+++ b/bin/prepare_xrubies
|
21
|
+
@@ -18,10 +18,6 @@ rvm all do gem install rake-compiler -v "~> 0.9.2"
|
22
|
+
# Use just one CPU for building 1.8.7 and 1.9.3
|
23
|
+
export MAKE="make"
|
24
|
+
|
25
|
+
-# Build 1.8.7 with mingw32 compiler (GCC 4.2)
|
26
|
+
-rvm use 1.8.7
|
27
|
+
-rake-compiler cross-ruby VERSION=1.8.7-p374 HOST=i586-mingw32msvc
|
28
|
+
-
|
29
|
+
# Build 1.9.3 using 1.9.3 as base
|
30
|
+
rvm use 1.9.3
|
31
|
+
rake-compiler cross-ruby VERSION=1.9.3-p545 HOST=i586-mingw32msvc
|
@@ -0,0 +1,29 @@
|
|
1
|
+
--- a/Makefile.in 2011-08-17 18:57:36.000000000 -0700
|
2
|
+
+++ b/Makefile.in 2014-05-02 10:27:01.275813000 -0700
|
3
|
+
@@ -772,13 +772,6 @@
|
4
|
+
|
5
|
+
|
6
|
+
install-data-local:
|
7
|
+
- $(mkinstalldirs) $(ETC)
|
8
|
+
- if test ! -f $(ETC)/freetds.conf; then \
|
9
|
+
- $(INSTALL_DATA) $(srcdir)/freetds.conf $(ETC)/freetds.conf; \
|
10
|
+
- fi
|
11
|
+
- if test ! -f $(ETC)/locales.conf; then \
|
12
|
+
- $(INSTALL_DATA) $(srcdir)/locales.conf $(ETC)/locales.conf; \
|
13
|
+
- fi
|
14
|
+
|
15
|
+
clean-local:
|
16
|
+
find . \( -name \*.test_output -o -name \*.bb -o -name \*.bbg -o -name \*.da -o -name \*.gc\* \) -exec rm -f {} \;
|
17
|
+
--- a/src/pool/Makefile.in 2011-08-17 18:57:36.000000000 -0700
|
18
|
+
+++ b/src/pool/Makefile.in 2014-05-02 10:32:39.628347600 -0700
|
19
|
+
@@ -574,10 +574,6 @@
|
20
|
+
|
21
|
+
|
22
|
+
install-data-local:
|
23
|
+
- $(mkinstalldirs) $(ETC)
|
24
|
+
- if test ! -f $(ETC)/pool.conf; then \
|
25
|
+
- $(INSTALL_DATA) $(srcdir)/pool.conf $(ETC)/pool.conf; \
|
26
|
+
- fi
|
27
|
+
|
28
|
+
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
29
|
+
# Otherwise a system limit (for SysV at least) may be exceeded.
|
@@ -0,0 +1,11 @@
|
|
1
|
+
--- a/src/dblib/dblib.c
|
2
|
+
+++ b/src/dblib/dblib.c
|
3
|
+
@@ -765,7 +765,7 @@ dbsetlname(LOGINREC * login, const char *value, int which)
|
4
|
+
return FAIL;
|
5
|
+
}
|
6
|
+
|
7
|
+
- if (TDS_MAX_LOGIN_STR_SZ < strlen(value)) {
|
8
|
+
+ if (login->tds_login->tds_version < 0x700 && TDS_MAX_LOGIN_STR_SZ < strlen(value)) {
|
9
|
+
dbperror(NULL, SYBENTLL, 0);
|
10
|
+
return FAIL;
|
11
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
--- a/src/tds/sspi.c
|
2
|
+
+++ b/src/tds/sspi.c
|
3
|
+
@@ -172,8 +172,19 @@ tds_sspi_handle_next(TDSSOCKET * tds, struct tds_authentication * tds_auth, size
|
4
|
+
|
5
|
+
free(auth_buf);
|
6
|
+
|
7
|
+
- if (status != SEC_E_OK)
|
8
|
+
+ switch (status) {
|
9
|
+
+ case SEC_I_COMPLETE_AND_CONTINUE:
|
10
|
+
+ sec_fn->CompleteAuthToken(&auth->cred_ctx, &out_desc);
|
11
|
+
+ break;
|
12
|
+
+
|
13
|
+
+ case SEC_I_CONTINUE_NEEDED:
|
14
|
+
+ case SEC_E_OK:
|
15
|
+
+ break;
|
16
|
+
+
|
17
|
+
+ default:
|
18
|
+
return TDS_FAIL;
|
19
|
+
+ }
|
20
|
+
+
|
21
|
+
if (out_buf.cbBuffer == 0)
|
22
|
+
return TDS_SUCCEED;
|
23
|
+
|
24
|
+
@@ -275,9 +286,16 @@ tds_sspi_get_auth(TDSSOCKET * tds)
|
25
|
+
&auth->cred_ctx, &desc,
|
26
|
+
&attrs, &ts);
|
27
|
+
|
28
|
+
- if (status == SEC_I_COMPLETE_AND_CONTINUE || status == SEC_I_CONTINUE_NEEDED) {
|
29
|
+
+ switch (status) {
|
30
|
+
+ case SEC_I_COMPLETE_AND_CONTINUE:
|
31
|
+
sec_fn->CompleteAuthToken(&auth->cred_ctx, &desc);
|
32
|
+
- } else if(status != SEC_E_OK) {
|
33
|
+
+ break;
|
34
|
+
+
|
35
|
+
+ case SEC_I_CONTINUE_NEEDED:
|
36
|
+
+ case SEC_E_OK:
|
37
|
+
+ break;
|
38
|
+
+
|
39
|
+
+ default:
|
40
|
+
free(auth->sname);
|
41
|
+
free(auth->tds_auth.packet);
|
42
|
+
sec_fn->FreeCredentialsHandle(&auth->cred);
|
@@ -0,0 +1,408 @@
|
|
1
|
+
#include <tiny_tds_ext.h>
|
2
|
+
#include <errno.h>
|
3
|
+
|
4
|
+
VALUE cTinyTdsClient;
|
5
|
+
extern VALUE mTinyTds, cTinyTdsError;
|
6
|
+
static ID sym_username, sym_password, sym_dataserver, sym_database, sym_appname, sym_tds_version, sym_login_timeout, sym_timeout, sym_encoding, sym_azure;
|
7
|
+
static ID intern_source_eql, intern_severity_eql, intern_db_error_number_eql, intern_os_error_number_eql;
|
8
|
+
static ID intern_new, intern_dup, intern_transpose_iconv_encoding, intern_local_offset, intern_gsub;
|
9
|
+
VALUE opt_escape_regex, opt_escape_dblquote;
|
10
|
+
|
11
|
+
|
12
|
+
// Lib Macros
|
13
|
+
|
14
|
+
#define GET_CLIENT_WRAPPER(self) \
|
15
|
+
tinytds_client_wrapper *cwrap; \
|
16
|
+
Data_Get_Struct(self, tinytds_client_wrapper, cwrap)
|
17
|
+
|
18
|
+
#define REQUIRE_OPEN_CLIENT(cwrap) \
|
19
|
+
if (cwrap->closed || cwrap->userdata->closed) { \
|
20
|
+
rb_raise(cTinyTdsError, "closed connection"); \
|
21
|
+
return Qnil; \
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
// Lib Backend (Helpers)
|
26
|
+
|
27
|
+
VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, int cancel, char *error, char *source, int severity, int dberr, int oserr) {
|
28
|
+
GET_CLIENT_USERDATA(dbproc);
|
29
|
+
if (cancel && !dbdead(dbproc) && userdata && !userdata->closed) {
|
30
|
+
userdata->dbsqlok_sent = 1;
|
31
|
+
dbsqlok(dbproc);
|
32
|
+
userdata->dbcancel_sent = 1;
|
33
|
+
dbcancel(dbproc);
|
34
|
+
}
|
35
|
+
VALUE e = rb_exc_new2(cTinyTdsError, error);
|
36
|
+
rb_funcall(e, intern_source_eql, 1, rb_str_new2(source));
|
37
|
+
if (severity)
|
38
|
+
rb_funcall(e, intern_severity_eql, 1, INT2FIX(severity));
|
39
|
+
if (dberr)
|
40
|
+
rb_funcall(e, intern_db_error_number_eql, 1, INT2FIX(dberr));
|
41
|
+
if (oserr)
|
42
|
+
rb_funcall(e, intern_os_error_number_eql, 1, INT2FIX(oserr));
|
43
|
+
rb_exc_raise(e);
|
44
|
+
return Qnil;
|
45
|
+
}
|
46
|
+
|
47
|
+
|
48
|
+
// Lib Backend (Memory Management & Handlers)
|
49
|
+
|
50
|
+
int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) {
|
51
|
+
static char *source = "error";
|
52
|
+
GET_CLIENT_USERDATA(dbproc);
|
53
|
+
|
54
|
+
/* Everything should cancel by default */
|
55
|
+
int return_value = INT_CANCEL;
|
56
|
+
int cancel = 0;
|
57
|
+
|
58
|
+
/* These error codes are documented in include/sybdb.h in FreeTDS */
|
59
|
+
switch(dberr) {
|
60
|
+
|
61
|
+
/* We don't want to raise these as a ruby exception for various reasons */
|
62
|
+
case 100: /* SYBEVERDOWN, indicating the connection can only be v7.1 */
|
63
|
+
case SYBESEOF: /* Usually accompanied by another more useful error */
|
64
|
+
case SYBESMSG: /* Generic "check messages from server" error */
|
65
|
+
case SYBEICONVI: /* Just return ?s to the client, as explained in readme */
|
66
|
+
return return_value;
|
67
|
+
|
68
|
+
case SYBEICONVO:
|
69
|
+
dbfreebuf(dbproc);
|
70
|
+
break;
|
71
|
+
|
72
|
+
case SYBETIME:
|
73
|
+
/*
|
74
|
+
SYBETIME is the only error that can send INT_TIMEOUT or INT_CONTINUE,
|
75
|
+
but we don't ever want to automatically retry. Instead have the app
|
76
|
+
decide what to do. We would use INT_TIMEOUT, however it seems tdserror()
|
77
|
+
in tds/util.c converts INT_TIMEOUT to INT_CONTINUE.
|
78
|
+
*/
|
79
|
+
cancel = 1;
|
80
|
+
break;
|
81
|
+
|
82
|
+
case SYBEWRIT:
|
83
|
+
/* Write errors may happen after we abort a statement */
|
84
|
+
if (userdata && (userdata->dbsqlok_sent || userdata->dbcancel_sent)) {
|
85
|
+
return return_value;
|
86
|
+
}
|
87
|
+
cancel = 1;
|
88
|
+
break;
|
89
|
+
}
|
90
|
+
|
91
|
+
/*
|
92
|
+
When in non-blocking mode we need to store the exception data to throw it
|
93
|
+
once the blocking call returns, otherwise we will segfault ruby since part
|
94
|
+
of the contract of the ruby non-blocking indicator is that you do not call
|
95
|
+
any of the ruby C API.
|
96
|
+
*/
|
97
|
+
if (userdata && userdata->nonblocking) {
|
98
|
+
if (cancel && !dbdead(dbproc) && !userdata->closed) {
|
99
|
+
dbcancel(dbproc);
|
100
|
+
userdata->dbcancel_sent = 1;
|
101
|
+
}
|
102
|
+
|
103
|
+
/*
|
104
|
+
If we've already captured an error message, don't overwrite it. This is
|
105
|
+
here because FreeTDS sends a generic "General SQL Server error" message
|
106
|
+
that will overwrite the real message. This is not normally a problem
|
107
|
+
because a ruby exception is normally thrown and we bail before the
|
108
|
+
generic message can be sent.
|
109
|
+
*/
|
110
|
+
if (!userdata->nonblocking_error.is_set) {
|
111
|
+
userdata->nonblocking_error.cancel = cancel;
|
112
|
+
strcpy(userdata->nonblocking_error.error, dberrstr);
|
113
|
+
strcpy(userdata->nonblocking_error.source, source);
|
114
|
+
userdata->nonblocking_error.severity = severity;
|
115
|
+
userdata->nonblocking_error.dberr = dberr;
|
116
|
+
userdata->nonblocking_error.oserr = oserr;
|
117
|
+
userdata->nonblocking_error.is_set = 1;
|
118
|
+
}
|
119
|
+
|
120
|
+
} else {
|
121
|
+
rb_tinytds_raise_error(dbproc, cancel, dberrstr, source, severity, dberr, oserr);
|
122
|
+
}
|
123
|
+
|
124
|
+
return return_value;
|
125
|
+
}
|
126
|
+
|
127
|
+
int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line) {
|
128
|
+
static char *source = "message";
|
129
|
+
GET_CLIENT_USERDATA(dbproc);
|
130
|
+
if (severity > 10) {
|
131
|
+
// See tinytds_err_handler() for info about why we do this
|
132
|
+
if (userdata && userdata->nonblocking) {
|
133
|
+
if (!userdata->nonblocking_error.is_set) {
|
134
|
+
userdata->nonblocking_error.cancel = 1;
|
135
|
+
strcpy(userdata->nonblocking_error.error, msgtext);
|
136
|
+
strcpy(userdata->nonblocking_error.source, source);
|
137
|
+
userdata->nonblocking_error.severity = severity;
|
138
|
+
userdata->nonblocking_error.dberr = msgno;
|
139
|
+
userdata->nonblocking_error.oserr = msgstate;
|
140
|
+
userdata->nonblocking_error.is_set = 1;
|
141
|
+
}
|
142
|
+
if (!dbdead(dbproc) && !userdata->closed) {
|
143
|
+
dbcancel(dbproc);
|
144
|
+
userdata->dbcancel_sent = 1;
|
145
|
+
}
|
146
|
+
} else {
|
147
|
+
rb_tinytds_raise_error(dbproc, 1, msgtext, source, severity, msgno, msgstate);
|
148
|
+
}
|
149
|
+
}
|
150
|
+
return 0;
|
151
|
+
}
|
152
|
+
|
153
|
+
static void rb_tinytds_client_reset_userdata(tinytds_client_userdata *userdata) {
|
154
|
+
userdata->timing_out = 0;
|
155
|
+
userdata->dbsql_sent = 0;
|
156
|
+
userdata->dbsqlok_sent = 0;
|
157
|
+
userdata->dbcancel_sent = 0;
|
158
|
+
userdata->nonblocking = 0;
|
159
|
+
userdata->nonblocking_error.is_set = 0;
|
160
|
+
}
|
161
|
+
|
162
|
+
static void rb_tinytds_client_mark(void *ptr) {
|
163
|
+
tinytds_client_wrapper *cwrap = (tinytds_client_wrapper *)ptr;
|
164
|
+
if (cwrap) {
|
165
|
+
rb_gc_mark(cwrap->charset);
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
static void rb_tinytds_client_free(void *ptr) {
|
170
|
+
tinytds_client_wrapper *cwrap = (tinytds_client_wrapper *)ptr;
|
171
|
+
if (cwrap->login)
|
172
|
+
dbloginfree(cwrap->login);
|
173
|
+
if (cwrap->client && !cwrap->closed) {
|
174
|
+
dbclose(cwrap->client);
|
175
|
+
cwrap->closed = 1;
|
176
|
+
cwrap->userdata->closed = 1;
|
177
|
+
}
|
178
|
+
xfree(cwrap->userdata);
|
179
|
+
xfree(ptr);
|
180
|
+
}
|
181
|
+
|
182
|
+
static VALUE allocate(VALUE klass) {
|
183
|
+
VALUE obj;
|
184
|
+
tinytds_client_wrapper *cwrap;
|
185
|
+
obj = Data_Make_Struct(klass, tinytds_client_wrapper, rb_tinytds_client_mark, rb_tinytds_client_free, cwrap);
|
186
|
+
cwrap->closed = 1;
|
187
|
+
cwrap->charset = Qnil;
|
188
|
+
cwrap->userdata = malloc(sizeof(tinytds_client_userdata));
|
189
|
+
cwrap->userdata->closed = 1;
|
190
|
+
rb_tinytds_client_reset_userdata(cwrap->userdata);
|
191
|
+
return obj;
|
192
|
+
}
|
193
|
+
|
194
|
+
|
195
|
+
// TinyTds::Client (public)
|
196
|
+
|
197
|
+
static VALUE rb_tinytds_tds_version(VALUE self) {
|
198
|
+
GET_CLIENT_WRAPPER(self);
|
199
|
+
return INT2FIX(dbtds(cwrap->client));
|
200
|
+
}
|
201
|
+
|
202
|
+
static VALUE rb_tinytds_close(VALUE self) {
|
203
|
+
GET_CLIENT_WRAPPER(self);
|
204
|
+
if (cwrap->client && !cwrap->closed) {
|
205
|
+
dbclose(cwrap->client);
|
206
|
+
cwrap->closed = 1;
|
207
|
+
cwrap->userdata->closed = 1;
|
208
|
+
}
|
209
|
+
return Qtrue;
|
210
|
+
}
|
211
|
+
|
212
|
+
static VALUE rb_tinytds_dead(VALUE self) {
|
213
|
+
GET_CLIENT_WRAPPER(self);
|
214
|
+
return dbdead(cwrap->client) ? Qtrue : Qfalse;
|
215
|
+
}
|
216
|
+
|
217
|
+
static VALUE rb_tinytds_closed(VALUE self) {
|
218
|
+
GET_CLIENT_WRAPPER(self);
|
219
|
+
return (cwrap->closed || cwrap->userdata->closed) ? Qtrue : Qfalse;
|
220
|
+
}
|
221
|
+
|
222
|
+
static VALUE rb_tinytds_canceled(VALUE self) {
|
223
|
+
GET_CLIENT_WRAPPER(self);
|
224
|
+
return cwrap->userdata->dbcancel_sent ? Qtrue : Qfalse;
|
225
|
+
}
|
226
|
+
|
227
|
+
static VALUE rb_tinytds_sqlsent(VALUE self) {
|
228
|
+
GET_CLIENT_WRAPPER(self);
|
229
|
+
return cwrap->userdata->dbsql_sent ? Qtrue : Qfalse;
|
230
|
+
}
|
231
|
+
|
232
|
+
static VALUE rb_tinytds_execute(VALUE self, VALUE sql) {
|
233
|
+
GET_CLIENT_WRAPPER(self);
|
234
|
+
rb_tinytds_client_reset_userdata(cwrap->userdata);
|
235
|
+
REQUIRE_OPEN_CLIENT(cwrap);
|
236
|
+
dbcmd(cwrap->client, StringValuePtr(sql));
|
237
|
+
if (dbsqlsend(cwrap->client) == FAIL) {
|
238
|
+
rb_warn("TinyTds: dbsqlsend() returned FAIL.\n");
|
239
|
+
return Qfalse;
|
240
|
+
}
|
241
|
+
cwrap->userdata->dbsql_sent = 1;
|
242
|
+
VALUE result = rb_tinytds_new_result_obj(cwrap);
|
243
|
+
rb_iv_set(result, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), intern_dup, 0));
|
244
|
+
GET_RESULT_WRAPPER(result);
|
245
|
+
rwrap->local_offset = rb_funcall(cTinyTdsClient, intern_local_offset, 0);
|
246
|
+
rwrap->encoding = cwrap->encoding;
|
247
|
+
return result;
|
248
|
+
}
|
249
|
+
|
250
|
+
static VALUE rb_tinytds_charset(VALUE self) {
|
251
|
+
GET_CLIENT_WRAPPER(self);
|
252
|
+
return cwrap->charset;
|
253
|
+
}
|
254
|
+
|
255
|
+
static VALUE rb_tinytds_encoding(VALUE self) {
|
256
|
+
GET_CLIENT_WRAPPER(self);
|
257
|
+
return rb_enc_from_encoding(cwrap->encoding);
|
258
|
+
}
|
259
|
+
|
260
|
+
static VALUE rb_tinytds_escape(VALUE self, VALUE string) {
|
261
|
+
Check_Type(string, T_STRING);
|
262
|
+
GET_CLIENT_WRAPPER(self);
|
263
|
+
VALUE new_string = rb_funcall(string, intern_gsub, 2, opt_escape_regex, opt_escape_dblquote);
|
264
|
+
rb_enc_associate(new_string, cwrap->encoding);
|
265
|
+
return new_string;
|
266
|
+
}
|
267
|
+
|
268
|
+
/* Duplicated in result.c */
|
269
|
+
static VALUE rb_tinytds_return_code(VALUE self) {
|
270
|
+
GET_CLIENT_WRAPPER(self);
|
271
|
+
if (cwrap->client && dbhasretstat(cwrap->client)) {
|
272
|
+
return LONG2NUM((long)dbretstatus(cwrap->client));
|
273
|
+
} else {
|
274
|
+
return Qnil;
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
static VALUE rb_tinytds_identity_sql(VALUE self) {
|
279
|
+
GET_CLIENT_WRAPPER(self);
|
280
|
+
return rb_str_new2(cwrap->identity_insert_sql);
|
281
|
+
}
|
282
|
+
|
283
|
+
static VALUE rb_tinytds_freetds_nine_one_or_higher(VALUE self) {
|
284
|
+
#ifdef DBSETLDBNAME
|
285
|
+
return Qtrue;
|
286
|
+
#else
|
287
|
+
return Qfalse;
|
288
|
+
#endif
|
289
|
+
}
|
290
|
+
|
291
|
+
|
292
|
+
// TinyTds::Client (protected)
|
293
|
+
|
294
|
+
static VALUE rb_tinytds_connect(VALUE self, VALUE opts) {
|
295
|
+
/* Parsing options hash to local vars. */
|
296
|
+
VALUE user, pass, dataserver, database, app, version, ltimeout, timeout, charset, azure;
|
297
|
+
user = rb_hash_aref(opts, sym_username);
|
298
|
+
pass = rb_hash_aref(opts, sym_password);
|
299
|
+
dataserver = rb_hash_aref(opts, sym_dataserver);
|
300
|
+
database = rb_hash_aref(opts, sym_database);
|
301
|
+
app = rb_hash_aref(opts, sym_appname);
|
302
|
+
version = rb_hash_aref(opts, sym_tds_version);
|
303
|
+
ltimeout = rb_hash_aref(opts, sym_login_timeout);
|
304
|
+
timeout = rb_hash_aref(opts, sym_timeout);
|
305
|
+
charset = rb_hash_aref(opts, sym_encoding);
|
306
|
+
azure = rb_hash_aref(opts, sym_azure);
|
307
|
+
/* Dealing with options. */
|
308
|
+
if (dbinit() == FAIL) {
|
309
|
+
rb_raise(cTinyTdsError, "failed dbinit() function");
|
310
|
+
return self;
|
311
|
+
}
|
312
|
+
dberrhandle(tinytds_err_handler);
|
313
|
+
dbmsghandle(tinytds_msg_handler);
|
314
|
+
GET_CLIENT_WRAPPER(self);
|
315
|
+
cwrap->login = dblogin();
|
316
|
+
if (!NIL_P(version))
|
317
|
+
dbsetlversion(cwrap->login, NUM2INT(version));
|
318
|
+
if (!NIL_P(user))
|
319
|
+
dbsetluser(cwrap->login, StringValuePtr(user));
|
320
|
+
if (!NIL_P(pass))
|
321
|
+
dbsetlpwd(cwrap->login, StringValuePtr(pass));
|
322
|
+
if (!NIL_P(app))
|
323
|
+
dbsetlapp(cwrap->login, StringValuePtr(app));
|
324
|
+
if (!NIL_P(ltimeout))
|
325
|
+
dbsetlogintime(NUM2INT(ltimeout));
|
326
|
+
if (!NIL_P(timeout))
|
327
|
+
dbsettime(NUM2INT(timeout));
|
328
|
+
if (!NIL_P(charset))
|
329
|
+
DBSETLCHARSET(cwrap->login, StringValuePtr(charset));
|
330
|
+
if (!NIL_P(database) && (azure == Qtrue)) {
|
331
|
+
#ifdef DBSETLDBNAME
|
332
|
+
DBSETLDBNAME(cwrap->login, StringValuePtr(database));
|
333
|
+
#else
|
334
|
+
rb_warn("TinyTds: Azure connections not supported in this version of FreeTDS.\n");
|
335
|
+
#endif
|
336
|
+
}
|
337
|
+
cwrap->client = dbopen(cwrap->login, StringValuePtr(dataserver));
|
338
|
+
if (cwrap->client) {
|
339
|
+
cwrap->closed = 0;
|
340
|
+
cwrap->charset = charset;
|
341
|
+
if (!NIL_P(version))
|
342
|
+
dbsetversion(NUM2INT(version));
|
343
|
+
dbsetuserdata(cwrap->client, (BYTE*)cwrap->userdata);
|
344
|
+
cwrap->userdata->closed = 0;
|
345
|
+
if (!NIL_P(database) && (azure != Qtrue)) {
|
346
|
+
dbuse(cwrap->client, StringValuePtr(database));
|
347
|
+
}
|
348
|
+
VALUE transposed_encoding = rb_funcall(cTinyTdsClient, intern_transpose_iconv_encoding, 1, charset);
|
349
|
+
cwrap->encoding = rb_enc_find(StringValuePtr(transposed_encoding));
|
350
|
+
if (dbtds(cwrap->client) <= 7) {
|
351
|
+
cwrap->identity_insert_sql = "SELECT CAST(@@IDENTITY AS bigint) AS Ident";
|
352
|
+
} else {
|
353
|
+
cwrap->identity_insert_sql = "SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident";
|
354
|
+
}
|
355
|
+
}
|
356
|
+
return self;
|
357
|
+
}
|
358
|
+
|
359
|
+
|
360
|
+
// Lib Init
|
361
|
+
|
362
|
+
void init_tinytds_client() {
|
363
|
+
cTinyTdsClient = rb_define_class_under(mTinyTds, "Client", rb_cObject);
|
364
|
+
rb_define_alloc_func(cTinyTdsClient, allocate);
|
365
|
+
/* Define TinyTds::Client Public Methods */
|
366
|
+
rb_define_method(cTinyTdsClient, "tds_version", rb_tinytds_tds_version, 0);
|
367
|
+
rb_define_method(cTinyTdsClient, "close", rb_tinytds_close, 0);
|
368
|
+
rb_define_method(cTinyTdsClient, "closed?", rb_tinytds_closed, 0);
|
369
|
+
rb_define_method(cTinyTdsClient, "canceled?", rb_tinytds_canceled, 0);
|
370
|
+
rb_define_method(cTinyTdsClient, "dead?", rb_tinytds_dead, 0);
|
371
|
+
rb_define_method(cTinyTdsClient, "sqlsent?", rb_tinytds_sqlsent, 0);
|
372
|
+
rb_define_method(cTinyTdsClient, "execute", rb_tinytds_execute, 1);
|
373
|
+
rb_define_method(cTinyTdsClient, "charset", rb_tinytds_charset, 0);
|
374
|
+
rb_define_method(cTinyTdsClient, "encoding", rb_tinytds_encoding, 0);
|
375
|
+
rb_define_method(cTinyTdsClient, "escape", rb_tinytds_escape, 1);
|
376
|
+
rb_define_method(cTinyTdsClient, "return_code", rb_tinytds_return_code, 0);
|
377
|
+
rb_define_method(cTinyTdsClient, "identity_sql", rb_tinytds_identity_sql, 0);
|
378
|
+
rb_define_method(cTinyTdsClient, "freetds_091_or_higer?", rb_tinytds_freetds_nine_one_or_higher, 0);
|
379
|
+
/* Define TinyTds::Client Protected Methods */
|
380
|
+
rb_define_protected_method(cTinyTdsClient, "connect", rb_tinytds_connect, 1);
|
381
|
+
/* Symbols For Connect */
|
382
|
+
sym_username = ID2SYM(rb_intern("username"));
|
383
|
+
sym_password = ID2SYM(rb_intern("password"));
|
384
|
+
sym_dataserver = ID2SYM(rb_intern("dataserver"));
|
385
|
+
sym_database = ID2SYM(rb_intern("database"));
|
386
|
+
sym_appname = ID2SYM(rb_intern("appname"));
|
387
|
+
sym_tds_version = ID2SYM(rb_intern("tds_version"));
|
388
|
+
sym_login_timeout = ID2SYM(rb_intern("login_timeout"));
|
389
|
+
sym_timeout = ID2SYM(rb_intern("timeout"));
|
390
|
+
sym_encoding = ID2SYM(rb_intern("encoding"));
|
391
|
+
sym_azure = ID2SYM(rb_intern("azure"));
|
392
|
+
/* Intern TinyTds::Error Accessors */
|
393
|
+
intern_source_eql = rb_intern("source=");
|
394
|
+
intern_severity_eql = rb_intern("severity=");
|
395
|
+
intern_db_error_number_eql = rb_intern("db_error_number=");
|
396
|
+
intern_os_error_number_eql = rb_intern("os_error_number=");
|
397
|
+
/* Intern Misc */
|
398
|
+
intern_new = rb_intern("new");
|
399
|
+
intern_dup = rb_intern("dup");
|
400
|
+
intern_transpose_iconv_encoding = rb_intern("transpose_iconv_encoding");
|
401
|
+
intern_local_offset = rb_intern("local_offset");
|
402
|
+
intern_gsub = rb_intern("gsub");
|
403
|
+
/* Escape Regexp Global */
|
404
|
+
opt_escape_regex = rb_funcall(rb_cRegexp, intern_new, 1, rb_str_new2("\\\'"));
|
405
|
+
opt_escape_dblquote = rb_str_new2("''");
|
406
|
+
rb_global_variable(&opt_escape_regex);
|
407
|
+
rb_global_variable(&opt_escape_dblquote);
|
408
|
+
}
|