tiny_tds_vagas 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/CHANGELOG +198 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/Gemfile +6 -0
- data/ISSUE_TEMPLATE.md +5 -0
- data/MIT-LICENSE +23 -0
- data/README.md +427 -0
- data/Rakefile +110 -0
- data/VERSION +1 -0
- data/appveyor.yml +52 -0
- data/bin/defncopy +3 -0
- data/bin/tsql +3 -0
- data/ext/tiny_tds/client.c +410 -0
- data/ext/tiny_tds/client.h +49 -0
- data/ext/tiny_tds/extconf.rb +329 -0
- data/ext/tiny_tds/extconsts.rb +15 -0
- data/ext/tiny_tds/result.c +608 -0
- data/ext/tiny_tds/result.h +32 -0
- data/ext/tiny_tds/tiny_tds_ext.c +12 -0
- data/ext/tiny_tds/tiny_tds_ext.h +17 -0
- data/lib/tiny_tds.rb +37 -0
- data/lib/tiny_tds/bin.rb +86 -0
- data/lib/tiny_tds/client.rb +124 -0
- data/lib/tiny_tds/error.rb +15 -0
- data/lib/tiny_tds/result.rb +8 -0
- data/lib/tiny_tds/version.rb +3 -0
- data/ports/patches/freetds/1.00/0001-mingw_missing_inet_pton.diff +34 -0
- data/test/appveyor/dbsetup.ps1 +27 -0
- data/test/appveyor/dbsetup.sql +9 -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 +217 -0
- data/test/result_test.rb +728 -0
- data/test/schema/1px.gif +0 -0
- data/test/schema/sqlserver_2000.sql +140 -0
- data/test/schema/sqlserver_2005.sql +140 -0
- data/test/schema/sqlserver_2008.sql +140 -0
- data/test/schema/sqlserver_2014.sql +140 -0
- data/test/schema/sqlserver_azure.sql +140 -0
- data/test/schema/sybase_ase.sql +138 -0
- data/test/schema_test.rb +443 -0
- data/test/test_helper.rb +213 -0
- data/test/thread_test.rb +98 -0
- data/tiny_tds.gemspec +28 -0
- metadata +201 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
#ifndef TINYTDS_CLIENT_H
|
3
|
+
#define TINYTDS_CLIENT_H
|
4
|
+
|
5
|
+
void init_tinytds_client();
|
6
|
+
|
7
|
+
#define ERROR_MSG_SIZE 1024
|
8
|
+
|
9
|
+
typedef struct {
|
10
|
+
short int is_set;
|
11
|
+
int cancel;
|
12
|
+
char error[ERROR_MSG_SIZE];
|
13
|
+
char source[ERROR_MSG_SIZE];
|
14
|
+
int severity;
|
15
|
+
int dberr;
|
16
|
+
int oserr;
|
17
|
+
} tinytds_errordata;
|
18
|
+
|
19
|
+
typedef struct {
|
20
|
+
short int closed;
|
21
|
+
short int timing_out;
|
22
|
+
short int dbsql_sent;
|
23
|
+
short int dbsqlok_sent;
|
24
|
+
RETCODE dbsqlok_retcode;
|
25
|
+
short int dbcancel_sent;
|
26
|
+
short int nonblocking;
|
27
|
+
tinytds_errordata nonblocking_error;
|
28
|
+
} tinytds_client_userdata;
|
29
|
+
|
30
|
+
typedef struct {
|
31
|
+
LOGINREC *login;
|
32
|
+
RETCODE return_code;
|
33
|
+
DBPROCESS *client;
|
34
|
+
short int closed;
|
35
|
+
VALUE charset;
|
36
|
+
tinytds_client_userdata *userdata;
|
37
|
+
const char *identity_insert_sql;
|
38
|
+
rb_encoding *encoding;
|
39
|
+
} tinytds_client_wrapper;
|
40
|
+
|
41
|
+
VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, int cancel, const char *error, const char *source, int severity, int dberr, int oserr);
|
42
|
+
|
43
|
+
// Lib Macros
|
44
|
+
|
45
|
+
#define GET_CLIENT_USERDATA(dbproc) \
|
46
|
+
tinytds_client_userdata *userdata = (tinytds_client_userdata *)dbgetuserdata(dbproc);
|
47
|
+
|
48
|
+
|
49
|
+
#endif
|
@@ -0,0 +1,329 @@
|
|
1
|
+
ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
|
2
|
+
|
3
|
+
# :stopdoc:
|
4
|
+
|
5
|
+
require 'mkmf'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
# The gem version constraint in the gemspec is not respected at install time.
|
9
|
+
# Keep this version in sync with the one in the gemspec !
|
10
|
+
gem 'mini_portile2', '~> 2.0'
|
11
|
+
require 'mini_portile2'
|
12
|
+
require_relative './extconsts'
|
13
|
+
|
14
|
+
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
15
|
+
|
16
|
+
# Shamelessly copied from nokogiri
|
17
|
+
#
|
18
|
+
|
19
|
+
def do_help
|
20
|
+
print <<HELP
|
21
|
+
usage: ruby #{$0} [options]
|
22
|
+
|
23
|
+
--enable-system-freetds / --disable-system-freetds
|
24
|
+
--enable-system-iconv / --disable-system-iconv
|
25
|
+
--enable-system-openssl / --disable-system-openssl
|
26
|
+
Force use of system or builtin freetds/iconv/openssl library.
|
27
|
+
Default is to prefer system libraries and fallback to builtin.
|
28
|
+
|
29
|
+
--with-freetds-dir=DIR
|
30
|
+
Use the freetds library placed under DIR.
|
31
|
+
|
32
|
+
--enable-lookup
|
33
|
+
Search for freetds through all paths in the PATH environment variable.
|
34
|
+
|
35
|
+
--disable-openssl
|
36
|
+
Disable OpenSSL for freetds build. No effect on system-freetds.
|
37
|
+
|
38
|
+
--enable-gnutls
|
39
|
+
Use GnuTLS instead of OpenSSL for freetds build.
|
40
|
+
|
41
|
+
--enable-cross-build
|
42
|
+
Do cross-build.
|
43
|
+
HELP
|
44
|
+
exit! 0
|
45
|
+
end
|
46
|
+
|
47
|
+
do_help if arg_config('--help')
|
48
|
+
|
49
|
+
FREETDSDIR = ENV['FREETDS_DIR']
|
50
|
+
|
51
|
+
if FREETDSDIR.nil? || FREETDSDIR.empty?
|
52
|
+
LIBDIR = RbConfig::CONFIG['libdir']
|
53
|
+
INCLUDEDIR = RbConfig::CONFIG['includedir']
|
54
|
+
else
|
55
|
+
puts "Will use #{FREETDSDIR}"
|
56
|
+
LIBDIR = "#{FREETDSDIR}/lib"
|
57
|
+
INCLUDEDIR = "#{FREETDSDIR}/include"
|
58
|
+
end
|
59
|
+
|
60
|
+
$CFLAGS << " #{ENV["CFLAGS"]}"
|
61
|
+
$LDFLAGS << " #{ENV["LDFLAGS"]}"
|
62
|
+
$LIBS << " #{ENV["LIBS"]}"
|
63
|
+
|
64
|
+
SEARCHABLE_PATHS = begin
|
65
|
+
eop_regexp = /#{File::SEPARATOR}bin$/
|
66
|
+
paths = ENV['PATH']
|
67
|
+
paths = paths.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
68
|
+
paths = paths.split(File::PATH_SEPARATOR)
|
69
|
+
bin_paths = paths.select{ |p| p =~ eop_regexp }
|
70
|
+
bin_paths.map{ |p| p.sub(eop_regexp,'') }.compact.reject{ |p| p.empty? }.uniq
|
71
|
+
end
|
72
|
+
|
73
|
+
def searchable_paths_with_directories(*directories)
|
74
|
+
SEARCHABLE_PATHS.map do |path|
|
75
|
+
directories.map do |paths|
|
76
|
+
dir = File.join path, *paths
|
77
|
+
File.directory?(dir) ? dir : nil
|
78
|
+
end.flatten.compact
|
79
|
+
end.flatten.compact
|
80
|
+
end
|
81
|
+
|
82
|
+
class BuildRecipe < MiniPortile
|
83
|
+
def initialize(name, version, files)
|
84
|
+
super(name, version)
|
85
|
+
self.files = files
|
86
|
+
self.target = File.expand_path('../../../ports', __FILE__)
|
87
|
+
self.host = consolidated_host(RbConfig::CONFIG["host"])
|
88
|
+
self.patch_files = Dir[File.join(self.target, "patches", self.name, self.version, "*.diff")].sort
|
89
|
+
end
|
90
|
+
|
91
|
+
def consolidated_host(name)
|
92
|
+
# Host name and prefix of build tools are different on Windows 32 bit.
|
93
|
+
name.gsub('i686-pc-mingw32', 'i686-w64-mingw32')
|
94
|
+
end
|
95
|
+
|
96
|
+
def configure_defaults
|
97
|
+
[
|
98
|
+
"--host=#{host}", # build for specific target (host)
|
99
|
+
"--disable-static",
|
100
|
+
"--enable-shared",
|
101
|
+
]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Use the same path for all recipes, so that only one include/lib path is required.
|
105
|
+
def port_path
|
106
|
+
"#{target}/#{host}"
|
107
|
+
end
|
108
|
+
|
109
|
+
# We use the same port_path for all recipes. That breaks the standard installed? method.
|
110
|
+
def installed?
|
111
|
+
false
|
112
|
+
end
|
113
|
+
|
114
|
+
# When using rake-compiler-dock on Windows, the underlying Virtualbox shared
|
115
|
+
# folders don't support symlinks, but libiconv expects it for a build on
|
116
|
+
# Linux. We work around this limitation by using the temp dir for cooking.
|
117
|
+
def chdir_for_build
|
118
|
+
build_dir = ENV['RCD_HOST_RUBY_PLATFORM'].to_s =~ /mingw|mswin|cygwin/ ? '/tmp' : '.'
|
119
|
+
Dir.chdir(build_dir) do
|
120
|
+
yield
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def cook_and_activate
|
125
|
+
checkpoint = File.join(self.target, "#{self.name}-#{self.version}-#{self.host}.installed")
|
126
|
+
unless File.exist?(checkpoint)
|
127
|
+
chdir_for_build do
|
128
|
+
self.cook
|
129
|
+
end
|
130
|
+
FileUtils.touch checkpoint
|
131
|
+
end
|
132
|
+
self.activate
|
133
|
+
self
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def define_libssl_recipe(host)
|
138
|
+
BuildRecipe.new("openssl", OPENSSL_VERSION, [OPENSSL_SOURCE_URI]).tap do |recipe|
|
139
|
+
class << recipe
|
140
|
+
def extract_file(file, target)
|
141
|
+
filename = File.basename(file)
|
142
|
+
FileUtils.mkdir_p target
|
143
|
+
|
144
|
+
message "Extracting #{filename} into #{target}... "
|
145
|
+
result = `#{tar_exe} #{tar_compression_switch(filename)}xf "#{file}" -C "#{target}" 2>&1`
|
146
|
+
if $?.success?
|
147
|
+
output "OK"
|
148
|
+
else
|
149
|
+
# tar on windows returns error exit code, because it can not extract symlinks
|
150
|
+
output "ERROR (ignored)"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def configure
|
155
|
+
config = if host=~/mingw/
|
156
|
+
host=~/x86_64/ ? 'mingw64' : 'mingw'
|
157
|
+
end
|
158
|
+
args = [ "CFLAGS=-DDSO_WIN32",
|
159
|
+
"./Configure",
|
160
|
+
"no-shared",
|
161
|
+
configure_prefix,
|
162
|
+
config,
|
163
|
+
]
|
164
|
+
args.unshift("CROSS_COMPILE=#{host}-") if enable_config("cross-build")
|
165
|
+
|
166
|
+
execute "configure", "sh -c \"#{args.join(" ")}\""
|
167
|
+
end
|
168
|
+
|
169
|
+
def compile
|
170
|
+
super
|
171
|
+
# OpenSSL DLLs are called "libeay32.dll" and "ssleay32.dll" per default,
|
172
|
+
# regardless to the version. This is best suited to meet the Windows DLL hell.
|
173
|
+
# To avoid any conflicts we do a static build and build DLLs afterwards,
|
174
|
+
# with our own naming scheme.
|
175
|
+
execute "mkdef-libeay32", "(perl util/mkdef.pl 32 libeay >libeay32.def)"
|
176
|
+
execute "mkdef-ssleay32", "(perl util/mkdef.pl 32 ssleay >ssleay32.def)"
|
177
|
+
dllwrap = consolidated_host(RbConfig::CONFIG["DLLWRAP"])
|
178
|
+
execute "dllwrap-libeay32", "#{dllwrap} --dllname libeay32-#{version}-#{host}.dll --output-lib libcrypto.dll.a --def libeay32.def libcrypto.a -lwsock32 -lgdi32 -lcrypt32"
|
179
|
+
execute "dllwrap-ssleay32", "#{dllwrap} --dllname ssleay32-#{version}-#{host}.dll --output-lib libssl.dll.a --def ssleay32.def libssl.a libcrypto.dll.a"
|
180
|
+
end
|
181
|
+
|
182
|
+
def install
|
183
|
+
super
|
184
|
+
FileUtils.cp "#{work_path}/libeay32-#{version}-#{host}.dll", "#{path}/bin/"
|
185
|
+
FileUtils.cp "#{work_path}/ssleay32-#{version}-#{host}.dll", "#{path}/bin/"
|
186
|
+
FileUtils.cp "#{work_path}/libcrypto.dll.a", "#{path}/lib/"
|
187
|
+
FileUtils.cp "#{work_path}/libssl.dll.a", "#{path}/lib/"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def define_libiconv_recipe(host)
|
194
|
+
BuildRecipe.new("libiconv", ICONV_VERSION, [ICONV_SOURCE_URI]).tap do |recipe|
|
195
|
+
# always produce position independent code
|
196
|
+
recipe.configure_options << "CFLAGS=-fPIC"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def define_freetds_recipe(host, libiconv, libssl, gnutls)
|
201
|
+
BuildRecipe.new("freetds", FREETDS_VERSION, [FREETDS_SOURCE_URI]).tap do |recipe|
|
202
|
+
with_tdsver = FREETDS_VERSION =~ /0\.91/ ? "--with-tdsver=7.1" : "--with-tdsver=7.3"
|
203
|
+
for_windows = recipe.host =~ /mswin|mingw/i
|
204
|
+
recipe.configure_options << '--with-pic'
|
205
|
+
recipe.configure_options << "--with-libiconv-prefix=#{libiconv.path}" if libiconv
|
206
|
+
if true == libssl
|
207
|
+
recipe.configure_options << "--with-openssl"
|
208
|
+
elsif libssl
|
209
|
+
recipe.configure_options << "--with-openssl=#{libssl.path}"
|
210
|
+
end
|
211
|
+
recipe.configure_options << "--with-gnutls" if gnutls
|
212
|
+
recipe.configure_options << '--sysconfdir=C:\Sites' if for_windows
|
213
|
+
recipe.configure_options << '--enable-sspi' if for_windows
|
214
|
+
recipe.configure_options << "--disable-odbc"
|
215
|
+
recipe.configure_options << with_tdsver
|
216
|
+
if libiconv
|
217
|
+
# For some reason freetds doesn't honor --with-libiconv-prefix
|
218
|
+
# so we have do add it by hand:
|
219
|
+
recipe.configure_options << "CFLAGS=-I#{libiconv.path}/include"
|
220
|
+
recipe.configure_options << "LDFLAGS=-L#{libiconv.path}/lib -liconv"
|
221
|
+
end
|
222
|
+
|
223
|
+
class << recipe
|
224
|
+
|
225
|
+
def install
|
226
|
+
super_value = super
|
227
|
+
# Install binstub target binaries.
|
228
|
+
if super_value
|
229
|
+
bin_path = File.expand_path File.join(path, 'bin')
|
230
|
+
exe_path = File.expand_path File.join(target, '..', 'exe')
|
231
|
+
return unless File.directory?(bin_path)
|
232
|
+
['tsql', 'defncopy'].each do |bin|
|
233
|
+
['.exe', ''].each do |ext|
|
234
|
+
exe = File.join bin_path, "#{bin}#{ext}"
|
235
|
+
next unless File.exists?(exe)
|
236
|
+
next unless File.executable?(exe)
|
237
|
+
FileUtils.cp exe, exe_path
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
super_value
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
if RbConfig::CONFIG['target_os'] =~ /mswin32|mingw32/
|
250
|
+
lib_prefix = 'lib' unless RbConfig::CONFIG['target_os'] =~ /mingw32/
|
251
|
+
# There's no default include/lib dir on Windows. Let's just add the Ruby ones
|
252
|
+
# and resort on the search path specified by INCLUDE and LIB environment
|
253
|
+
# variables
|
254
|
+
HEADER_DIRS = [INCLUDEDIR]
|
255
|
+
LIB_DIRS = [LIBDIR]
|
256
|
+
else
|
257
|
+
lib_prefix = ''
|
258
|
+
HEADER_DIRS = [
|
259
|
+
# First search /opt/local for macports
|
260
|
+
'/opt/local/include',
|
261
|
+
# Then search /usr/local for people that installed from source
|
262
|
+
'/usr/local/include',
|
263
|
+
# Check the ruby install locations
|
264
|
+
INCLUDEDIR,
|
265
|
+
# Finally fall back to /usr
|
266
|
+
'/usr/include'
|
267
|
+
].reject{ |dir| !File.directory?(dir) }
|
268
|
+
LIB_DIRS = [
|
269
|
+
# First search /opt/local for macports
|
270
|
+
'/opt/local/lib',
|
271
|
+
# Then search /usr/local for people that installed from source
|
272
|
+
'/usr/local/lib',
|
273
|
+
# Check the ruby install locations
|
274
|
+
LIBDIR,
|
275
|
+
# Finally fall back to /usr
|
276
|
+
'/usr/lib',
|
277
|
+
].reject{ |dir| !File.directory?(dir) }
|
278
|
+
end
|
279
|
+
|
280
|
+
FREETDS_HEADER_DIRS = (searchable_paths_with_directories(['include'],['include','freetds']) + HEADER_DIRS).uniq
|
281
|
+
FREETDS_LIB_DIRS = (searchable_paths_with_directories(['lib'],['lib','freetds']) + LIB_DIRS).uniq
|
282
|
+
|
283
|
+
# lookup over searchable paths is great for native compilation, however, when
|
284
|
+
# cross compiling we need to specify our own paths.
|
285
|
+
if enable_config("lookup", true)
|
286
|
+
dir_config('freetds', FREETDS_HEADER_DIRS, FREETDS_LIB_DIRS)
|
287
|
+
else
|
288
|
+
dir_config('freetds')
|
289
|
+
|
290
|
+
# remove LDFLAGS
|
291
|
+
$LDFLAGS = ENV.fetch("LDFLAGS", "")
|
292
|
+
end
|
293
|
+
|
294
|
+
def asplode(lib)
|
295
|
+
msg = "-----\n"
|
296
|
+
msg << "#{lib} is missing.\n"
|
297
|
+
msg << "Do you have FreeTDS 0.95.80 or higher installed?\n" if lib == 'freetds'
|
298
|
+
msg << "-----"
|
299
|
+
abort(msg)
|
300
|
+
end
|
301
|
+
|
302
|
+
def freetds_usable?(lib_prefix)
|
303
|
+
have_header('sybfront.h') && have_header('sybdb.h') &&
|
304
|
+
find_library("#{lib_prefix}sybdb", 'tdsdbopen') &&
|
305
|
+
find_library("#{lib_prefix}sybdb", 'dbanydatecrack')
|
306
|
+
end
|
307
|
+
|
308
|
+
# We use freetds, when available already, and fallback to compilation of ports
|
309
|
+
system_freetds = enable_config('system-freetds', ENV['TINYTDS_SKIP_PORTS'] || freetds_usable?(lib_prefix))
|
310
|
+
|
311
|
+
# We expect to have iconv and OpenSSL available on non-Windows systems
|
312
|
+
host = RbConfig::CONFIG["host"]
|
313
|
+
system_iconv = enable_config('system-iconv', host =~ /mingw|mswin/ ? false : true)
|
314
|
+
system_openssl = enable_config('system-openssl', host =~ /mingw|mswin/ ? false : true )
|
315
|
+
enable_gnutls = enable_config('gnutls', false )
|
316
|
+
enable_openssl = enable_config('openssl', !enable_gnutls )
|
317
|
+
|
318
|
+
unless system_freetds
|
319
|
+
libssl = define_libssl_recipe(host).cook_and_activate unless system_openssl
|
320
|
+
libiconv = define_libiconv_recipe(host).cook_and_activate unless system_iconv
|
321
|
+
freetds = define_freetds_recipe(host, libiconv, libssl || enable_openssl, enable_gnutls).cook_and_activate
|
322
|
+
dir_config('freetds', freetds.path + "/include", freetds.path + "/lib")
|
323
|
+
end
|
324
|
+
|
325
|
+
asplode 'freetds' unless freetds_usable?(lib_prefix)
|
326
|
+
|
327
|
+
create_makefile('tiny_tds/tiny_tds')
|
328
|
+
|
329
|
+
# :startdoc:
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
ICONV_VERSION = ENV['TINYTDS_ICONV_VERSION'] || "1.14"
|
3
|
+
ICONV_SOURCE_URI = "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-#{ICONV_VERSION}.tar.gz"
|
4
|
+
|
5
|
+
OPENSSL_VERSION = ENV['TINYTDS_OPENSSL_VERSION'] || '1.0.2g'
|
6
|
+
OPENSSL_SOURCE_URI = "https://www.openssl.org/source/openssl-#{OPENSSL_VERSION}.tar.gz"
|
7
|
+
|
8
|
+
FREETDS_VERSION = ENV['TINYTDS_FREETDS_VERSION'] || "1.00"
|
9
|
+
FREETDS_VERSION_INFO = Hash.new { |h,k|
|
10
|
+
h[k] = {files: "ftp://ftp.freetds.org/pub/freetds/stable/freetds-#{k}.tar.bz2"}
|
11
|
+
}
|
12
|
+
FREETDS_VERSION_INFO['1.00'] = {files: 'ftp://ftp.freetds.org/pub/freetds/stable/freetds-1.00.tar.bz2'}
|
13
|
+
FREETDS_VERSION_INFO['0.99'] = {files: 'ftp://ftp.freetds.org/pub/freetds/current/freetds-dev.0.99.678.tar.gz'}
|
14
|
+
FREETDS_VERSION_INFO['0.95'] = {files: 'ftp://ftp.freetds.org/pub/freetds/stable/freetds-0.95.92.tar.gz'}
|
15
|
+
FREETDS_SOURCE_URI = FREETDS_VERSION_INFO[FREETDS_VERSION][:files]
|
@@ -0,0 +1,608 @@
|
|
1
|
+
|
2
|
+
#include <tiny_tds_ext.h>
|
3
|
+
#include <stdint.h>
|
4
|
+
|
5
|
+
// File Types/Vars
|
6
|
+
|
7
|
+
VALUE cTinyTdsResult;
|
8
|
+
extern VALUE mTinyTds, cTinyTdsClient, cTinyTdsError;
|
9
|
+
VALUE cBigDecimal, cDate;
|
10
|
+
VALUE opt_decimal_zero, opt_float_zero, opt_one, opt_zero, opt_four, opt_19hdr, opt_onek, opt_tenk, opt_onemil, opt_onebil;
|
11
|
+
static ID intern_new, intern_utc, intern_local, intern_localtime, intern_merge,
|
12
|
+
intern_civil, intern_new_offset, intern_plus, intern_divide;
|
13
|
+
static ID sym_symbolize_keys, sym_as, sym_array, sym_cache_rows, sym_first, sym_timezone, sym_local, sym_utc, sym_empty_sets;
|
14
|
+
|
15
|
+
|
16
|
+
// Lib Macros
|
17
|
+
|
18
|
+
rb_encoding *binaryEncoding;
|
19
|
+
#define ENCODED_STR_NEW(_data, _len) ({ \
|
20
|
+
VALUE _val = rb_str_new((char *)_data, (long)_len); \
|
21
|
+
rb_enc_associate(_val, rwrap->encoding); \
|
22
|
+
_val; \
|
23
|
+
})
|
24
|
+
#define ENCODED_STR_NEW2(_data2) ({ \
|
25
|
+
VALUE _val = rb_str_new2((char *)_data2); \
|
26
|
+
rb_enc_associate(_val, rwrap->encoding); \
|
27
|
+
_val; \
|
28
|
+
})
|
29
|
+
|
30
|
+
#ifdef _WIN32
|
31
|
+
#define LONG_LONG_FORMAT "I64d"
|
32
|
+
#else
|
33
|
+
#define LONG_LONG_FORMAT "lld"
|
34
|
+
#endif
|
35
|
+
|
36
|
+
|
37
|
+
// Lib Backend (Memory Management)
|
38
|
+
|
39
|
+
static void rb_tinytds_result_mark(void *ptr) {
|
40
|
+
tinytds_result_wrapper *rwrap = (tinytds_result_wrapper *)ptr;
|
41
|
+
if (rwrap) {
|
42
|
+
rb_gc_mark(rwrap->local_offset);
|
43
|
+
rb_gc_mark(rwrap->fields);
|
44
|
+
rb_gc_mark(rwrap->fields_processed);
|
45
|
+
rb_gc_mark(rwrap->results);
|
46
|
+
rb_gc_mark(rwrap->dbresults_retcodes);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
static void rb_tinytds_result_free(void *ptr) {
|
51
|
+
xfree(ptr);
|
52
|
+
}
|
53
|
+
|
54
|
+
VALUE rb_tinytds_new_result_obj(tinytds_client_wrapper *cwrap) {
|
55
|
+
VALUE obj;
|
56
|
+
tinytds_result_wrapper *rwrap;
|
57
|
+
obj = Data_Make_Struct(cTinyTdsResult, tinytds_result_wrapper, rb_tinytds_result_mark, rb_tinytds_result_free, rwrap);
|
58
|
+
rwrap->cwrap = cwrap;
|
59
|
+
rwrap->client = cwrap->client;
|
60
|
+
rwrap->local_offset = Qnil;
|
61
|
+
rwrap->fields = rb_ary_new();
|
62
|
+
rwrap->fields_processed = rb_ary_new();
|
63
|
+
rwrap->results = Qnil;
|
64
|
+
rwrap->dbresults_retcodes = rb_ary_new();
|
65
|
+
rwrap->number_of_results = 0;
|
66
|
+
rwrap->number_of_fields = 0;
|
67
|
+
rwrap->number_of_rows = 0;
|
68
|
+
rb_obj_call_init(obj, 0, NULL);
|
69
|
+
return obj;
|
70
|
+
}
|
71
|
+
|
72
|
+
// No GVL Helpers
|
73
|
+
|
74
|
+
#define NOGVL_DBCALL(_dbfunction, _client) ( \
|
75
|
+
(RETCODE)(intptr_t)rb_thread_call_without_gvl( \
|
76
|
+
(void *(*)(void *))_dbfunction, _client, \
|
77
|
+
(rb_unblock_function_t*)dbcancel_ubf, _client ) \
|
78
|
+
)
|
79
|
+
|
80
|
+
static void dbcancel_ubf(DBPROCESS *client) {
|
81
|
+
GET_CLIENT_USERDATA(client);
|
82
|
+
dbcancel(client);
|
83
|
+
userdata->dbcancel_sent = 1;
|
84
|
+
}
|
85
|
+
|
86
|
+
static void nogvl_setup(DBPROCESS *client) {
|
87
|
+
GET_CLIENT_USERDATA(client);
|
88
|
+
userdata->nonblocking = 1;
|
89
|
+
}
|
90
|
+
|
91
|
+
static void nogvl_cleanup(DBPROCESS *client) {
|
92
|
+
GET_CLIENT_USERDATA(client);
|
93
|
+
userdata->nonblocking = 0;
|
94
|
+
/*
|
95
|
+
Now that the blocking operation is done, we can finally throw any
|
96
|
+
exceptions based on errors from SQL Server.
|
97
|
+
*/
|
98
|
+
if (userdata->nonblocking_error.is_set) {
|
99
|
+
userdata->nonblocking_error.is_set = 0;
|
100
|
+
rb_tinytds_raise_error(client,
|
101
|
+
userdata->nonblocking_error.cancel,
|
102
|
+
userdata->nonblocking_error.error,
|
103
|
+
userdata->nonblocking_error.source,
|
104
|
+
userdata->nonblocking_error.severity,
|
105
|
+
userdata->nonblocking_error.dberr,
|
106
|
+
userdata->nonblocking_error.oserr);
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
static RETCODE nogvl_dbsqlok(DBPROCESS *client) {
|
111
|
+
int retcode = FAIL;
|
112
|
+
GET_CLIENT_USERDATA(client);
|
113
|
+
nogvl_setup(client);
|
114
|
+
retcode = NOGVL_DBCALL(dbsqlok, client);
|
115
|
+
nogvl_cleanup(client);
|
116
|
+
userdata->dbsqlok_sent = 1;
|
117
|
+
return retcode;
|
118
|
+
}
|
119
|
+
|
120
|
+
static RETCODE nogvl_dbsqlexec(DBPROCESS *client) {
|
121
|
+
int retcode = FAIL;
|
122
|
+
nogvl_setup(client);
|
123
|
+
retcode = NOGVL_DBCALL(dbsqlexec, client);
|
124
|
+
nogvl_cleanup(client);
|
125
|
+
return retcode;
|
126
|
+
}
|
127
|
+
|
128
|
+
static RETCODE nogvl_dbresults(DBPROCESS *client) {
|
129
|
+
int retcode = FAIL;
|
130
|
+
nogvl_setup(client);
|
131
|
+
retcode = NOGVL_DBCALL(dbresults, client);
|
132
|
+
nogvl_cleanup(client);
|
133
|
+
return retcode;
|
134
|
+
}
|
135
|
+
|
136
|
+
static RETCODE nogvl_dbnextrow(DBPROCESS * client) {
|
137
|
+
int retcode = FAIL;
|
138
|
+
nogvl_setup(client);
|
139
|
+
retcode = NOGVL_DBCALL(dbnextrow, client);
|
140
|
+
nogvl_cleanup(client);
|
141
|
+
return retcode;
|
142
|
+
}
|
143
|
+
|
144
|
+
// Lib Backend (Helpers)
|
145
|
+
|
146
|
+
static RETCODE rb_tinytds_result_dbresults_retcode(VALUE self) {
|
147
|
+
VALUE ruby_rc;
|
148
|
+
RETCODE db_rc;
|
149
|
+
GET_RESULT_WRAPPER(self);
|
150
|
+
ruby_rc = rb_ary_entry(rwrap->dbresults_retcodes, rwrap->number_of_results);
|
151
|
+
if (NIL_P(ruby_rc)) {
|
152
|
+
db_rc = nogvl_dbresults(rwrap->client);
|
153
|
+
ruby_rc = INT2FIX(db_rc);
|
154
|
+
rb_ary_store(rwrap->dbresults_retcodes, rwrap->number_of_results, ruby_rc);
|
155
|
+
} else {
|
156
|
+
db_rc = FIX2INT(ruby_rc);
|
157
|
+
}
|
158
|
+
return db_rc;
|
159
|
+
}
|
160
|
+
|
161
|
+
static RETCODE rb_tinytds_result_ok_helper(DBPROCESS *client) {
|
162
|
+
GET_CLIENT_USERDATA(client);
|
163
|
+
if (userdata->dbsqlok_sent == 0) {
|
164
|
+
userdata->dbsqlok_retcode = nogvl_dbsqlok(client);
|
165
|
+
}
|
166
|
+
return userdata->dbsqlok_retcode;
|
167
|
+
}
|
168
|
+
|
169
|
+
static void rb_tinytds_result_exec_helper(DBPROCESS *client) {
|
170
|
+
RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(client);
|
171
|
+
GET_CLIENT_USERDATA(client);
|
172
|
+
if (dbsqlok_rc == SUCCEED) {
|
173
|
+
/*
|
174
|
+
This is to just process each result set. Commands such as backup and
|
175
|
+
restore are not done when the first result set is returned, so we need to
|
176
|
+
exhaust the result sets before it is complete.
|
177
|
+
*/
|
178
|
+
while (nogvl_dbresults(client) == SUCCEED) {
|
179
|
+
/*
|
180
|
+
If we don't loop through each row for calls to TinyTds::Result.do that
|
181
|
+
actually do return result sets, we will trigger error 20019 about trying
|
182
|
+
to execute a new command with pending results. Oh well.
|
183
|
+
*/
|
184
|
+
while (dbnextrow(client) != NO_MORE_ROWS);
|
185
|
+
}
|
186
|
+
}
|
187
|
+
dbcancel(client);
|
188
|
+
userdata->dbcancel_sent = 1;
|
189
|
+
userdata->dbsql_sent = 0;
|
190
|
+
}
|
191
|
+
|
192
|
+
static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_keys, int as_array) {
|
193
|
+
VALUE row;
|
194
|
+
/* Storing Values */
|
195
|
+
unsigned int i;
|
196
|
+
/* Wrapper And Local Vars */
|
197
|
+
GET_RESULT_WRAPPER(self);
|
198
|
+
/* Create Empty Row */
|
199
|
+
row = as_array ? rb_ary_new2(rwrap->number_of_fields) : rb_hash_new();
|
200
|
+
for (i = 0; i < rwrap->number_of_fields; i++) {
|
201
|
+
VALUE val = Qnil;
|
202
|
+
int col = i+1;
|
203
|
+
int coltype = dbcoltype(rwrap->client, col);
|
204
|
+
BYTE *data = dbdata(rwrap->client, col);
|
205
|
+
DBINT data_len = dbdatlen(rwrap->client, col);
|
206
|
+
int null_val = ((data == NULL) && (data_len == 0));
|
207
|
+
if (!null_val) {
|
208
|
+
switch(coltype) {
|
209
|
+
case SYBINT1:
|
210
|
+
val = INT2FIX(*(DBTINYINT *)data);
|
211
|
+
break;
|
212
|
+
case SYBINT2:
|
213
|
+
val = INT2FIX(*(DBSMALLINT *)data);
|
214
|
+
break;
|
215
|
+
case SYBINT4:
|
216
|
+
val = INT2NUM(*(DBINT *)data);
|
217
|
+
break;
|
218
|
+
case SYBINT8:
|
219
|
+
val = LL2NUM(*(DBBIGINT *)data);
|
220
|
+
break;
|
221
|
+
case SYBBIT:
|
222
|
+
val = *(int *)data ? Qtrue : Qfalse;
|
223
|
+
break;
|
224
|
+
case SYBNUMERIC:
|
225
|
+
case SYBDECIMAL: {
|
226
|
+
DBTYPEINFO *data_info = dbcoltypeinfo(rwrap->client, col);
|
227
|
+
int data_slength = (int)data_info->precision + (int)data_info->scale + 1;
|
228
|
+
char converted_decimal[data_slength];
|
229
|
+
dbconvert(rwrap->client, coltype, data, data_len, SYBVARCHAR, (BYTE *)converted_decimal, -1);
|
230
|
+
val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2((char *)converted_decimal));
|
231
|
+
break;
|
232
|
+
}
|
233
|
+
case SYBFLT8: {
|
234
|
+
double col_to_double = *(double *)data;
|
235
|
+
val = (col_to_double == 0.000000) ? opt_float_zero : rb_float_new(col_to_double);
|
236
|
+
break;
|
237
|
+
}
|
238
|
+
case SYBREAL: {
|
239
|
+
float col_to_float = *(float *)data;
|
240
|
+
val = (col_to_float == 0.0) ? opt_float_zero : rb_float_new(col_to_float);
|
241
|
+
break;
|
242
|
+
}
|
243
|
+
case SYBMONEY: {
|
244
|
+
DBMONEY *money = (DBMONEY *)data;
|
245
|
+
char converted_money[25];
|
246
|
+
long long money_value = ((long long)money->mnyhigh << 32) | money->mnylow;
|
247
|
+
sprintf(converted_money, "%" LONG_LONG_FORMAT, money_value);
|
248
|
+
val = rb_funcall(cBigDecimal, intern_new, 2, rb_str_new2(converted_money), opt_four);
|
249
|
+
val = rb_funcall(val, intern_divide, 1, opt_tenk);
|
250
|
+
break;
|
251
|
+
}
|
252
|
+
case SYBMONEY4: {
|
253
|
+
DBMONEY4 *money = (DBMONEY4 *)data;
|
254
|
+
char converted_money[20];
|
255
|
+
sprintf(converted_money, "%f", money->mny4 / 10000.0);
|
256
|
+
val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2(converted_money));
|
257
|
+
break;
|
258
|
+
}
|
259
|
+
case SYBBINARY:
|
260
|
+
case SYBIMAGE:
|
261
|
+
val = rb_str_new((char *)data, (long)data_len);
|
262
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
263
|
+
rb_enc_associate(val, binaryEncoding);
|
264
|
+
#endif
|
265
|
+
break;
|
266
|
+
case 36: { // SYBUNIQUE
|
267
|
+
char converted_unique[37];
|
268
|
+
dbconvert(rwrap->client, coltype, data, 37, SYBVARCHAR, (BYTE *)converted_unique, -1);
|
269
|
+
val = ENCODED_STR_NEW2(converted_unique);
|
270
|
+
break;
|
271
|
+
}
|
272
|
+
case SYBDATETIME4: {
|
273
|
+
DBDATETIME new_data;
|
274
|
+
dbconvert(rwrap->client, coltype, data, data_len, SYBDATETIME, (BYTE *)&new_data, sizeof(new_data));
|
275
|
+
data = (BYTE *)&new_data;
|
276
|
+
data_len = sizeof(new_data);
|
277
|
+
}
|
278
|
+
case SYBDATETIME: {
|
279
|
+
DBDATEREC dr;
|
280
|
+
dbdatecrack(rwrap->client, &dr, (DBDATETIME *)data);
|
281
|
+
if (dr.year + dr.month + dr.day + dr.hour + dr.minute + dr.second + dr.millisecond != 0) {
|
282
|
+
val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(dr.year), INT2NUM(dr.month), INT2NUM(dr.day), INT2NUM(dr.hour), INT2NUM(dr.minute), INT2NUM(dr.second), INT2NUM(dr.millisecond*1000));
|
283
|
+
}
|
284
|
+
break;
|
285
|
+
}
|
286
|
+
case 40: // SYBMSDATE
|
287
|
+
case 41: // SYBMSTIME
|
288
|
+
case 42: // SYBMSDATETIME2
|
289
|
+
case 43: { // SYBMSDATETIMEOFFSET
|
290
|
+
#ifdef DBVERSION_73
|
291
|
+
if (dbtds(rwrap->client) >= DBTDS_7_3) {
|
292
|
+
DBDATEREC2 dr2;
|
293
|
+
dbanydatecrack(rwrap->client, &dr2, coltype, data);
|
294
|
+
switch(coltype) {
|
295
|
+
case 40: { // SYBMSDATE
|
296
|
+
val = rb_funcall(cDate, intern_new, 3, INT2NUM(dr2.year), INT2NUM(dr2.month), INT2NUM(dr2.day));
|
297
|
+
break;
|
298
|
+
}
|
299
|
+
case 41: { // SYBMSTIME
|
300
|
+
VALUE rational_nsec = rb_Rational(INT2NUM(dr2.nanosecond), opt_onek);
|
301
|
+
val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(1900), INT2NUM(1), INT2NUM(1), INT2NUM(dr2.hour), INT2NUM(dr2.minute), INT2NUM(dr2.second), rational_nsec);
|
302
|
+
break;
|
303
|
+
}
|
304
|
+
case 42: { // SYBMSDATETIME2
|
305
|
+
VALUE rational_nsec = rb_Rational(INT2NUM(dr2.nanosecond), opt_onek);
|
306
|
+
val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(dr2.year), INT2NUM(dr2.month), INT2NUM(dr2.day), INT2NUM(dr2.hour), INT2NUM(dr2.minute), INT2NUM(dr2.second), rational_nsec);
|
307
|
+
break;
|
308
|
+
}
|
309
|
+
case 43: { // SYBMSDATETIMEOFFSET
|
310
|
+
long long numerator = ((long)dr2.second * (long long)1000000000) + (long long)dr2.nanosecond;
|
311
|
+
VALUE rational_sec = rb_Rational(LL2NUM(numerator), opt_onebil);
|
312
|
+
val = rb_funcall(rb_cTime, intern_new, 7, INT2NUM(dr2.year), INT2NUM(dr2.month), INT2NUM(dr2.day), INT2NUM(dr2.hour), INT2NUM(dr2.minute), rational_sec, INT2NUM(dr2.tzone*60));
|
313
|
+
break;
|
314
|
+
}
|
315
|
+
}
|
316
|
+
} else {
|
317
|
+
val = ENCODED_STR_NEW(data, data_len);
|
318
|
+
}
|
319
|
+
#else
|
320
|
+
val = ENCODED_STR_NEW(data, data_len);
|
321
|
+
#endif
|
322
|
+
break;
|
323
|
+
}
|
324
|
+
case SYBCHAR:
|
325
|
+
case SYBTEXT:
|
326
|
+
val = ENCODED_STR_NEW(data, data_len);
|
327
|
+
break;
|
328
|
+
default:
|
329
|
+
val = ENCODED_STR_NEW(data, data_len);
|
330
|
+
break;
|
331
|
+
}
|
332
|
+
}
|
333
|
+
if (as_array) {
|
334
|
+
rb_ary_store(row, i, val);
|
335
|
+
} else {
|
336
|
+
VALUE key;
|
337
|
+
if (rwrap->number_of_results == 0) {
|
338
|
+
key = rb_ary_entry(rwrap->fields, i);
|
339
|
+
} else {
|
340
|
+
key = rb_ary_entry(rb_ary_entry(rwrap->fields, rwrap->number_of_results), i);
|
341
|
+
}
|
342
|
+
rb_hash_aset(row, key, val);
|
343
|
+
}
|
344
|
+
}
|
345
|
+
return row;
|
346
|
+
}
|
347
|
+
|
348
|
+
|
349
|
+
// TinyTds::Client (public)
|
350
|
+
|
351
|
+
static VALUE rb_tinytds_result_fields(VALUE self) {
|
352
|
+
RETCODE dbsqlok_rc, dbresults_rc;
|
353
|
+
VALUE fields_processed;
|
354
|
+
GET_RESULT_WRAPPER(self);
|
355
|
+
dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client);
|
356
|
+
dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
357
|
+
fields_processed = rb_ary_entry(rwrap->fields_processed, rwrap->number_of_results);
|
358
|
+
if ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED) && (fields_processed == Qnil)) {
|
359
|
+
/* Default query options. */
|
360
|
+
int symbolize_keys = 0;
|
361
|
+
VALUE qopts = rb_iv_get(self, "@query_options");
|
362
|
+
if (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue)
|
363
|
+
symbolize_keys = 1;
|
364
|
+
/* Set number_of_fields count for this result set. */
|
365
|
+
rwrap->number_of_fields = dbnumcols(rwrap->client);
|
366
|
+
if (rwrap->number_of_fields > 0) {
|
367
|
+
/* Create fields for this result set. */
|
368
|
+
unsigned int fldi = 0;
|
369
|
+
VALUE fields = rb_ary_new2(rwrap->number_of_fields);
|
370
|
+
for (fldi = 0; fldi < rwrap->number_of_fields; fldi++) {
|
371
|
+
char *colname = dbcolname(rwrap->client, fldi+1);
|
372
|
+
VALUE field = symbolize_keys ? rb_str_intern(ENCODED_STR_NEW2(colname)) : rb_obj_freeze(ENCODED_STR_NEW2(colname));
|
373
|
+
rb_ary_store(fields, fldi, field);
|
374
|
+
}
|
375
|
+
/* Store the fields. */
|
376
|
+
if (rwrap->number_of_results == 0) {
|
377
|
+
rwrap->fields = fields;
|
378
|
+
} else if (rwrap->number_of_results == 1) {
|
379
|
+
VALUE multi_rs_fields = rb_ary_new();
|
380
|
+
rb_ary_store(multi_rs_fields, 0, rwrap->fields);
|
381
|
+
rb_ary_store(multi_rs_fields, 1, fields);
|
382
|
+
rwrap->fields = multi_rs_fields;
|
383
|
+
} else {
|
384
|
+
rb_ary_store(rwrap->fields, rwrap->number_of_results, fields);
|
385
|
+
}
|
386
|
+
}
|
387
|
+
rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qtrue);
|
388
|
+
}
|
389
|
+
return rwrap->fields;
|
390
|
+
}
|
391
|
+
|
392
|
+
static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) {
|
393
|
+
/* Local Vars */
|
394
|
+
VALUE qopts, opts, block;
|
395
|
+
ID timezone;
|
396
|
+
int symbolize_keys = 0, as_array = 0, cache_rows = 0, first = 0, empty_sets = 0;
|
397
|
+
tinytds_client_userdata *userdata;
|
398
|
+
GET_RESULT_WRAPPER(self);
|
399
|
+
userdata = (tinytds_client_userdata *)dbgetuserdata(rwrap->client);
|
400
|
+
/* Merge Options Hash To Query Options. Populate Opts & Block Var. */
|
401
|
+
qopts = rb_iv_get(self, "@query_options");
|
402
|
+
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1)
|
403
|
+
qopts = rb_funcall(qopts, intern_merge, 1, opts);
|
404
|
+
rb_iv_set(self, "@query_options", qopts);
|
405
|
+
/* Locals From Options */
|
406
|
+
if (rb_hash_aref(qopts, sym_first) == Qtrue)
|
407
|
+
first = 1;
|
408
|
+
if (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue)
|
409
|
+
symbolize_keys = 1;
|
410
|
+
if (rb_hash_aref(qopts, sym_as) == sym_array)
|
411
|
+
as_array = 1;
|
412
|
+
if (rb_hash_aref(qopts, sym_cache_rows) == Qtrue)
|
413
|
+
cache_rows = 1;
|
414
|
+
if (rb_hash_aref(qopts, sym_timezone) == sym_local) {
|
415
|
+
timezone = intern_local;
|
416
|
+
} else if (rb_hash_aref(qopts, sym_timezone) == sym_utc) {
|
417
|
+
timezone = intern_utc;
|
418
|
+
} else {
|
419
|
+
rb_warn(":timezone option must be :utc or :local - defaulting to :local");
|
420
|
+
timezone = intern_local;
|
421
|
+
}
|
422
|
+
if (rb_hash_aref(qopts, sym_empty_sets) == Qtrue)
|
423
|
+
empty_sets = 1;
|
424
|
+
/* Make The Results Or Yield Existing */
|
425
|
+
if (NIL_P(rwrap->results)) {
|
426
|
+
RETCODE dbsqlok_rc, dbresults_rc;
|
427
|
+
rwrap->results = rb_ary_new();
|
428
|
+
dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client);
|
429
|
+
dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
430
|
+
while ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED)) {
|
431
|
+
int has_rows = (DBROWS(rwrap->client) == SUCCEED) ? 1 : 0;
|
432
|
+
if (has_rows || empty_sets || (rwrap->number_of_results == 0))
|
433
|
+
rb_tinytds_result_fields(self);
|
434
|
+
if ((has_rows || empty_sets) && rwrap->number_of_fields > 0) {
|
435
|
+
/* Create rows for this result set. */
|
436
|
+
unsigned long rowi = 0;
|
437
|
+
VALUE result = rb_ary_new();
|
438
|
+
while (nogvl_dbnextrow(rwrap->client) != NO_MORE_ROWS) {
|
439
|
+
VALUE row = rb_tinytds_result_fetch_row(self, timezone, symbolize_keys, as_array);
|
440
|
+
if (cache_rows)
|
441
|
+
rb_ary_store(result, rowi, row);
|
442
|
+
if (!NIL_P(block))
|
443
|
+
rb_yield(row);
|
444
|
+
if (first) {
|
445
|
+
dbcanquery(rwrap->client);
|
446
|
+
userdata->dbcancel_sent = 1;
|
447
|
+
}
|
448
|
+
rowi++;
|
449
|
+
}
|
450
|
+
rwrap->number_of_rows = rowi;
|
451
|
+
/* Store the result. */
|
452
|
+
if (cache_rows) {
|
453
|
+
if (rwrap->number_of_results == 0) {
|
454
|
+
rwrap->results = result;
|
455
|
+
} else if (rwrap->number_of_results == 1) {
|
456
|
+
VALUE multi_resultsets = rb_ary_new();
|
457
|
+
rb_ary_store(multi_resultsets, 0, rwrap->results);
|
458
|
+
rb_ary_store(multi_resultsets, 1, result);
|
459
|
+
rwrap->results = multi_resultsets;
|
460
|
+
} else {
|
461
|
+
rb_ary_store(rwrap->results, rwrap->number_of_results, result);
|
462
|
+
}
|
463
|
+
}
|
464
|
+
// If we find results increment the counter that helpers use and setup the next loop.
|
465
|
+
rwrap->number_of_results = rwrap->number_of_results + 1;
|
466
|
+
dbresults_rc = rb_tinytds_result_dbresults_retcode(self);
|
467
|
+
rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qnil);
|
468
|
+
} else {
|
469
|
+
// If we do not find results, side step the rb_tinytds_result_dbresults_retcode helper and
|
470
|
+
// manually populate its memoized array while nullifing any memoized fields too before loop.
|
471
|
+
dbresults_rc = nogvl_dbresults(rwrap->client);
|
472
|
+
rb_ary_store(rwrap->dbresults_retcodes, rwrap->number_of_results, INT2FIX(dbresults_rc));
|
473
|
+
rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qnil);
|
474
|
+
}
|
475
|
+
}
|
476
|
+
if (dbresults_rc == FAIL)
|
477
|
+
rb_warn("TinyTDS: Something in the dbresults() while loop set the return code to FAIL.\n");
|
478
|
+
userdata->dbsql_sent = 0;
|
479
|
+
} else if (!NIL_P(block)) {
|
480
|
+
unsigned long i;
|
481
|
+
for (i = 0; i < rwrap->number_of_rows; i++) {
|
482
|
+
rb_yield(rb_ary_entry(rwrap->results, i));
|
483
|
+
}
|
484
|
+
}
|
485
|
+
return rwrap->results;
|
486
|
+
}
|
487
|
+
|
488
|
+
static VALUE rb_tinytds_result_cancel(VALUE self) {
|
489
|
+
tinytds_client_userdata *userdata;
|
490
|
+
GET_RESULT_WRAPPER(self);
|
491
|
+
userdata = (tinytds_client_userdata *)dbgetuserdata(rwrap->client);
|
492
|
+
if (rwrap->client && !userdata->dbcancel_sent) {
|
493
|
+
rb_tinytds_result_ok_helper(rwrap->client);
|
494
|
+
dbcancel(rwrap->client);
|
495
|
+
userdata->dbcancel_sent = 1;
|
496
|
+
userdata->dbsql_sent = 0;
|
497
|
+
}
|
498
|
+
return Qtrue;
|
499
|
+
}
|
500
|
+
|
501
|
+
static VALUE rb_tinytds_result_do(VALUE self) {
|
502
|
+
GET_RESULT_WRAPPER(self);
|
503
|
+
if (rwrap->client) {
|
504
|
+
rb_tinytds_result_exec_helper(rwrap->client);
|
505
|
+
return LONG2NUM((long)dbcount(rwrap->client));
|
506
|
+
} else {
|
507
|
+
return Qnil;
|
508
|
+
}
|
509
|
+
}
|
510
|
+
|
511
|
+
static VALUE rb_tinytds_result_affected_rows(VALUE self) {
|
512
|
+
GET_RESULT_WRAPPER(self);
|
513
|
+
if (rwrap->client) {
|
514
|
+
return LONG2NUM((long)dbcount(rwrap->client));
|
515
|
+
} else {
|
516
|
+
return Qnil;
|
517
|
+
}
|
518
|
+
}
|
519
|
+
|
520
|
+
/* Duplicated in client.c */
|
521
|
+
static VALUE rb_tinytds_result_return_code(VALUE self) {
|
522
|
+
GET_RESULT_WRAPPER(self);
|
523
|
+
if (rwrap->client && dbhasretstat(rwrap->client)) {
|
524
|
+
return LONG2NUM((long)dbretstatus(rwrap->client));
|
525
|
+
} else {
|
526
|
+
return Qnil;
|
527
|
+
}
|
528
|
+
}
|
529
|
+
|
530
|
+
static VALUE rb_tinytds_result_insert(VALUE self) {
|
531
|
+
GET_RESULT_WRAPPER(self);
|
532
|
+
if (rwrap->client) {
|
533
|
+
VALUE identity = Qnil;
|
534
|
+
rb_tinytds_result_exec_helper(rwrap->client);
|
535
|
+
dbcmd(rwrap->client, rwrap->cwrap->identity_insert_sql);
|
536
|
+
if (nogvl_dbsqlexec(rwrap->client) != FAIL
|
537
|
+
&& nogvl_dbresults(rwrap->client) != FAIL
|
538
|
+
&& DBROWS(rwrap->client) != FAIL) {
|
539
|
+
while (nogvl_dbnextrow(rwrap->client) != NO_MORE_ROWS) {
|
540
|
+
int col = 1;
|
541
|
+
BYTE *data = dbdata(rwrap->client, col);
|
542
|
+
DBINT data_len = dbdatlen(rwrap->client, col);
|
543
|
+
int null_val = ((data == NULL) && (data_len == 0));
|
544
|
+
if (!null_val)
|
545
|
+
identity = LL2NUM(*(DBBIGINT *)data);
|
546
|
+
}
|
547
|
+
}
|
548
|
+
return identity;
|
549
|
+
} else {
|
550
|
+
return Qnil;
|
551
|
+
}
|
552
|
+
}
|
553
|
+
|
554
|
+
|
555
|
+
// Lib Init
|
556
|
+
|
557
|
+
void init_tinytds_result() {
|
558
|
+
/* Data Classes */
|
559
|
+
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
560
|
+
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
561
|
+
/* Define TinyTds::Result */
|
562
|
+
cTinyTdsResult = rb_define_class_under(mTinyTds, "Result", rb_cObject);
|
563
|
+
/* Define TinyTds::Result Public Methods */
|
564
|
+
rb_define_method(cTinyTdsResult, "fields", rb_tinytds_result_fields, 0);
|
565
|
+
rb_define_method(cTinyTdsResult, "each", rb_tinytds_result_each, -1);
|
566
|
+
rb_define_method(cTinyTdsResult, "cancel", rb_tinytds_result_cancel, 0);
|
567
|
+
rb_define_method(cTinyTdsResult, "do", rb_tinytds_result_do, 0);
|
568
|
+
rb_define_method(cTinyTdsResult, "affected_rows", rb_tinytds_result_affected_rows, 0);
|
569
|
+
rb_define_method(cTinyTdsResult, "return_code", rb_tinytds_result_return_code, 0);
|
570
|
+
rb_define_method(cTinyTdsResult, "insert", rb_tinytds_result_insert, 0);
|
571
|
+
/* Intern String Helpers */
|
572
|
+
intern_new = rb_intern("new");
|
573
|
+
intern_utc = rb_intern("utc");
|
574
|
+
intern_local = rb_intern("local");
|
575
|
+
intern_merge = rb_intern("merge");
|
576
|
+
intern_localtime = rb_intern("localtime");
|
577
|
+
intern_civil = rb_intern("civil");
|
578
|
+
intern_new_offset = rb_intern("new_offset");
|
579
|
+
intern_plus = rb_intern("+");
|
580
|
+
intern_divide = rb_intern("/");
|
581
|
+
/* Symbol Helpers */
|
582
|
+
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
|
583
|
+
sym_as = ID2SYM(rb_intern("as"));
|
584
|
+
sym_array = ID2SYM(rb_intern("array"));
|
585
|
+
sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
|
586
|
+
sym_first = ID2SYM(rb_intern("first"));
|
587
|
+
sym_local = ID2SYM(intern_local);
|
588
|
+
sym_utc = ID2SYM(intern_utc);
|
589
|
+
sym_timezone = ID2SYM(rb_intern("timezone"));
|
590
|
+
sym_empty_sets = ID2SYM(rb_intern("empty_sets"));
|
591
|
+
/* Data Conversion Options */
|
592
|
+
opt_decimal_zero = rb_str_new2("0.0");
|
593
|
+
rb_global_variable(&opt_decimal_zero);
|
594
|
+
opt_float_zero = rb_float_new((double)0);
|
595
|
+
rb_global_variable(&opt_float_zero);
|
596
|
+
opt_one = INT2NUM(1);
|
597
|
+
opt_zero = INT2NUM(0);
|
598
|
+
opt_four = INT2NUM(4);
|
599
|
+
opt_19hdr = INT2NUM(1900);
|
600
|
+
opt_onek = INT2NUM(1000);
|
601
|
+
opt_tenk = INT2NUM(10000);
|
602
|
+
opt_onemil = INT2NUM(1000000);
|
603
|
+
opt_onebil = INT2NUM(1000000000);
|
604
|
+
/* Encoding */
|
605
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
606
|
+
binaryEncoding = rb_enc_find("binary");
|
607
|
+
#endif
|
608
|
+
}
|