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,32 @@
|
|
1
|
+
|
2
|
+
#ifndef TINYTDS_RESULT_H
|
3
|
+
#define TINYTDS_RESULT_H
|
4
|
+
|
5
|
+
void init_tinytds_result();
|
6
|
+
VALUE rb_tinytds_new_result_obj(tinytds_client_wrapper *cwrap);
|
7
|
+
|
8
|
+
typedef struct {
|
9
|
+
tinytds_client_wrapper *cwrap;
|
10
|
+
DBPROCESS *client;
|
11
|
+
VALUE local_offset;
|
12
|
+
VALUE fields;
|
13
|
+
VALUE fields_processed;
|
14
|
+
VALUE results;
|
15
|
+
rb_encoding *encoding;
|
16
|
+
VALUE dbresults_retcodes;
|
17
|
+
unsigned int number_of_results;
|
18
|
+
unsigned int number_of_fields;
|
19
|
+
unsigned long number_of_rows;
|
20
|
+
} tinytds_result_wrapper;
|
21
|
+
|
22
|
+
|
23
|
+
// Lib Macros
|
24
|
+
|
25
|
+
#define GET_RESULT_WRAPPER(self) \
|
26
|
+
tinytds_result_wrapper *rwrap; \
|
27
|
+
Data_Get_Struct(self, tinytds_result_wrapper, rwrap)
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
#endif
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#ifndef TINYTDS_EXT
|
2
|
+
#define TINYTDS_EXT
|
3
|
+
|
4
|
+
#undef SYBDBLIB
|
5
|
+
#define MSDBLIB 1
|
6
|
+
|
7
|
+
#include <ruby.h>
|
8
|
+
#include <ruby/encoding.h>
|
9
|
+
#include <ruby/version.h>
|
10
|
+
#include <ruby/thread.h>
|
11
|
+
#include <sybfront.h>
|
12
|
+
#include <sybdb.h>
|
13
|
+
|
14
|
+
#include <client.h>
|
15
|
+
#include <result.h>
|
16
|
+
|
17
|
+
#endif
|
data/lib/tiny_tds.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'date'
|
3
|
+
require 'bigdecimal'
|
4
|
+
require 'rational'
|
5
|
+
|
6
|
+
require 'tiny_tds/version'
|
7
|
+
require 'tiny_tds/error'
|
8
|
+
require 'tiny_tds/client'
|
9
|
+
require 'tiny_tds/result'
|
10
|
+
|
11
|
+
# Support multiple ruby versions, fat binaries under Windows.
|
12
|
+
if RUBY_PLATFORM =~ /mingw|mswin/ && RUBY_VERSION =~ /(\d+.\d+)/
|
13
|
+
ver = $1
|
14
|
+
# Set the PATH environment variable, so that the DLLs can be found.
|
15
|
+
old_path = ENV['PATH']
|
16
|
+
begin
|
17
|
+
# Do the same host consolidation as in extconf.rb
|
18
|
+
ports_dir = RbConfig::CONFIG["host"].gsub('i686-pc-mingw32', 'i686-w64-mingw32')
|
19
|
+
ENV['PATH'] = "#{File.expand_path("../../ports/#{ports_dir}/bin", __FILE__)};#{old_path}"
|
20
|
+
require "tiny_tds/#{ver}/tiny_tds"
|
21
|
+
rescue LoadError
|
22
|
+
require 'tiny_tds/tiny_tds'
|
23
|
+
ensure
|
24
|
+
ENV['PATH'] = old_path
|
25
|
+
end
|
26
|
+
else
|
27
|
+
# Load dependent shared libraries into the process, so that they are already present,
|
28
|
+
# when tiny_tds.so is loaded. This ensures, that shared libraries are loaded even when
|
29
|
+
# the path is different between build and run time (e.g. Heroku).
|
30
|
+
ports_libs = File.expand_path("../../ports/#{RbConfig::CONFIG["host"]}/lib/*.so", __FILE__)
|
31
|
+
Dir[ports_libs].each do |lib|
|
32
|
+
require "fiddle"
|
33
|
+
Fiddle.dlopen(lib)
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'tiny_tds/tiny_tds'
|
37
|
+
end
|
data/lib/tiny_tds/bin.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative './version'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module TinyTds
|
5
|
+
class Bin
|
6
|
+
|
7
|
+
ROOT = File.expand_path '../../..', __FILE__
|
8
|
+
PATHS = ENV['PATH'].split File::PATH_SEPARATOR
|
9
|
+
EXTS = (ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']) | ['.exe']
|
10
|
+
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def exe(name, *args)
|
16
|
+
bin = new(name)
|
17
|
+
puts bin.info
|
18
|
+
bin.run(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(name)
|
24
|
+
@name = name
|
25
|
+
@binstub = find_bin
|
26
|
+
@exefile = find_exe
|
27
|
+
end
|
28
|
+
|
29
|
+
def run(*args)
|
30
|
+
return nil unless path
|
31
|
+
Kernel.system Shellwords.join(args.unshift(path))
|
32
|
+
$?.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
def path
|
36
|
+
return @path if defined?(@path)
|
37
|
+
@path = @exefile && File.exists?(@exefile) ? @exefile : which
|
38
|
+
end
|
39
|
+
|
40
|
+
def info
|
41
|
+
"[TinyTds][v#{TinyTds::VERSION}][#{name}]: #{path}"
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def find_bin
|
48
|
+
File.join ROOT, 'bin', name
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_exe
|
52
|
+
EXTS.each do |ext|
|
53
|
+
f = File.join ROOT, 'exe', "#{name}#{ext}"
|
54
|
+
return f if File.exists?(f)
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def which
|
60
|
+
PATHS.each do |path|
|
61
|
+
EXTS.each do |ext|
|
62
|
+
exe = File.expand_path File.join(path, "#{name}#{ext}"), ROOT
|
63
|
+
next if exe == @binstub
|
64
|
+
next if !File.executable?(exe)
|
65
|
+
next if !binary?(exe)
|
66
|
+
return exe
|
67
|
+
end
|
68
|
+
end
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Implementation directly copied from ptools.
|
73
|
+
# https://github.com/djberg96/ptools
|
74
|
+
# https://opensource.org/licenses/Artistic-2.0
|
75
|
+
#
|
76
|
+
def binary?(file)
|
77
|
+
bytes = File.stat(file).blksize
|
78
|
+
return false unless bytes
|
79
|
+
bytes = 4096 if bytes > 4096
|
80
|
+
s = (File.read(file, bytes) || "")
|
81
|
+
s = s.encode('US-ASCII', :undef => :replace).split(//)
|
82
|
+
((s.size - s.grep(" ".."~").size) / s.size.to_f) > 0.30
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module TinyTds
|
2
|
+
class Client
|
3
|
+
|
4
|
+
@@default_query_options = {
|
5
|
+
:as => :hash,
|
6
|
+
:symbolize_keys => false,
|
7
|
+
:cache_rows => true,
|
8
|
+
:timezone => :local,
|
9
|
+
:empty_sets => true
|
10
|
+
}
|
11
|
+
|
12
|
+
attr_reader :query_options
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def default_query_options
|
17
|
+
@@default_query_options
|
18
|
+
end
|
19
|
+
|
20
|
+
# Most, if not all, iconv encoding names can be found by ruby. Just in case, you can
|
21
|
+
# overide this method to return a string name that Encoding.find would work with. Default
|
22
|
+
# is to return the passed encoding.
|
23
|
+
#
|
24
|
+
def transpose_iconv_encoding(encoding)
|
25
|
+
encoding
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def initialize(opts={})
|
32
|
+
raise ArgumentError, 'missing :host option if no :dataserver given' if opts[:dataserver].to_s.empty? && opts[:host].to_s.empty?
|
33
|
+
opts[:username] = parse_username(opts)
|
34
|
+
@query_options = @@default_query_options.dup
|
35
|
+
opts[:password] = opts[:password].to_s if opts[:password] && opts[:password].to_s.strip != ''
|
36
|
+
opts[:appname] ||= 'TinyTds'
|
37
|
+
opts[:tds_version] = tds_versions_setter(opts)
|
38
|
+
opts[:login_timeout] ||= 60
|
39
|
+
opts[:timeout] ||= 5
|
40
|
+
opts[:encoding] = (opts[:encoding].nil? || opts[:encoding].downcase == 'utf8') ? 'UTF-8' : opts[:encoding].upcase
|
41
|
+
opts[:port] ||= 1433
|
42
|
+
opts[:dataserver] = "#{opts[:host]}:#{opts[:port]}" if opts[:dataserver].to_s.empty?
|
43
|
+
connect(opts)
|
44
|
+
end
|
45
|
+
|
46
|
+
def tds_73?
|
47
|
+
tds_version >= 11
|
48
|
+
end
|
49
|
+
|
50
|
+
def tds_version_info
|
51
|
+
info = TDS_VERSIONS_GETTERS[tds_version]
|
52
|
+
"#{info[:name]} - #{info[:description]}" if info
|
53
|
+
end
|
54
|
+
|
55
|
+
def active?
|
56
|
+
!closed? && !dead?
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def self.local_offset
|
63
|
+
::Time.local(2010).utc_offset.to_r / 86400
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_username(opts)
|
67
|
+
host = opts[:host]
|
68
|
+
username = opts[:username]
|
69
|
+
return username if username.nil? || !opts[:azure]
|
70
|
+
return username if username.include?("@") && !username.include?("database.windows.net")
|
71
|
+
user, domain = username.split("@")
|
72
|
+
domain ||= host
|
73
|
+
"#{user}@#{domain.split('.').first}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def tds_versions_setter(opts={})
|
77
|
+
v = opts[:tds_version] || ENV['TDSVER'] || '7.3'
|
78
|
+
TDS_VERSIONS_SETTERS[v.to_s]
|
79
|
+
end
|
80
|
+
|
81
|
+
# From sybdb.h comments:
|
82
|
+
# DBVERSION_xxx are used with dbsetversion()
|
83
|
+
#
|
84
|
+
TDS_VERSIONS_SETTERS = {
|
85
|
+
'unknown' => 0,
|
86
|
+
'46' => 1,
|
87
|
+
'100' => 2,
|
88
|
+
'42' => 3,
|
89
|
+
'70' => 4,
|
90
|
+
'7.0' => 4,
|
91
|
+
'71' => 5,
|
92
|
+
'7.1' => 5,
|
93
|
+
'80' => 5,
|
94
|
+
'8.0' => 5,
|
95
|
+
'72' => 6,
|
96
|
+
'7.2' => 6,
|
97
|
+
'90' => 6,
|
98
|
+
'9.0' => 6,
|
99
|
+
'73' => 7,
|
100
|
+
'7.3' => 7
|
101
|
+
}.freeze
|
102
|
+
|
103
|
+
# From sybdb.h comments:
|
104
|
+
# DBTDS_xxx are returned by DBTDS()
|
105
|
+
# The integer values of the constants are poorly chosen.
|
106
|
+
#
|
107
|
+
TDS_VERSIONS_GETTERS = {
|
108
|
+
0 => {:name => 'DBTDS_UNKNOWN', :description => 'Unknown'},
|
109
|
+
1 => {:name => 'DBTDS_2_0', :description => 'Pre 4.0 SQL Server'},
|
110
|
+
2 => {:name => 'DBTDS_3_4', :description => 'Microsoft SQL Server (3.0)'},
|
111
|
+
3 => {:name => 'DBTDS_4_0', :description => '4.0 SQL Server'},
|
112
|
+
4 => {:name => 'DBTDS_4_2', :description => '4.2 SQL Server'},
|
113
|
+
5 => {:name => 'DBTDS_4_6', :description => '2.0 OpenServer and 4.6 SQL Server.'},
|
114
|
+
6 => {:name => 'DBTDS_4_9_5', :description => '4.9.5 (NCR) SQL Server'},
|
115
|
+
7 => {:name => 'DBTDS_5_0', :description => '5.0 SQL Server'},
|
116
|
+
8 => {:name => 'DBTDS_7_0', :description => 'Microsoft SQL Server 7.0'},
|
117
|
+
9 => {:name => 'DBTDS_7_1/DBTDS_8_0', :description => 'Microsoft SQL Server 2000'},
|
118
|
+
10 => {:name => 'DBTDS_7_2/DBTDS_9_0', :description => 'Microsoft SQL Server 2005'},
|
119
|
+
11 => {:name => 'DBTDS_7_3', :description => 'Microsoft SQL Server 2008'},
|
120
|
+
12 => {:name => 'DBTDS_7_4', :description => 'Microsoft SQL Server 2012/2014'}
|
121
|
+
}.freeze
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
diff --git a/src/tds/tls.c b/src/tds/tls.c
|
2
|
+
index 0d11a33..b8ab2ba 100644
|
3
|
+
--- a/src/tds/tls.c
|
4
|
+
+++ b/src/tds/tls.c
|
5
|
+
@@ -72,6 +72,29 @@
|
6
|
+
#define SSL_PTR bio->ptr
|
7
|
+
#endif
|
8
|
+
|
9
|
+
+/*
|
10
|
+
+ * Add a workaround for older Mingw versions without inet_pton().
|
11
|
+
+ * This means RubyInstallers DevKit-4.7.2 in particular.
|
12
|
+
+ */
|
13
|
+
+#if defined(__MINGW64_VERSION_MAJOR) && !defined(InetPtonA)
|
14
|
+
+ #include <windows.h>
|
15
|
+
+
|
16
|
+
+ static HMODULE ws2_32 = NULL;
|
17
|
+
+ typedef INT (WINAPI * __inet_pton)(INT Family, LPCWSTR pStringBuf, PVOID pAddr);
|
18
|
+
+ static __inet_pton _inet_pton = NULL;
|
19
|
+
+
|
20
|
+
+ INT WINAPI inet_pton(INT Family, LPCWSTR pStringBuf, PVOID pAddr)
|
21
|
+
+ {
|
22
|
+
+ if (_inet_pton == NULL) {
|
23
|
+
+ ws2_32 = LoadLibraryEx("Ws2_32.dll", NULL, 0);
|
24
|
+
+
|
25
|
+
+ _inet_pton = (__inet_pton)GetProcAddress(ws2_32, "inet_pton");
|
26
|
+
+ }
|
27
|
+
+
|
28
|
+
+ return (_inet_pton)(Family, pStringBuf, pAddr);
|
29
|
+
+ }
|
30
|
+
+#endif
|
31
|
+
+
|
32
|
+
static SSL_RET
|
33
|
+
tds_pull_func_login(SSL_PULL_ARGS)
|
34
|
+
{
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
Write-Output "Setting up..."
|
3
|
+
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null
|
4
|
+
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null
|
5
|
+
|
6
|
+
Write-Output "Setting variables..."
|
7
|
+
$serverName = $env:COMPUTERNAME
|
8
|
+
$instances = @('SQL2008R2SP2', 'SQL2012SP1', 'SQL2014')
|
9
|
+
$smo = 'Microsoft.SqlServer.Management.Smo.'
|
10
|
+
$wmi = new-object ($smo + 'Wmi.ManagedComputer')
|
11
|
+
|
12
|
+
Write-Output "Configure Instances..."
|
13
|
+
foreach ($instance in $instances) {
|
14
|
+
Write-Output "Instance $instance ..."
|
15
|
+
Write-Output "Enable TCP/IP and port 1433..."
|
16
|
+
$uri = "ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instance']/ServerProtocol[@Name='Tcp']"
|
17
|
+
$tcp = $wmi.GetSmoObject($uri)
|
18
|
+
$tcp.IsEnabled = $true
|
19
|
+
foreach ($ipAddress in $Tcp.IPAddresses) {
|
20
|
+
$ipAddress.IPAddressProperties["TcpDynamicPorts"].Value = ""
|
21
|
+
$ipAddress.IPAddressProperties["TcpPort"].Value = "1433"
|
22
|
+
}
|
23
|
+
$tcp.Alter()
|
24
|
+
}
|
25
|
+
|
26
|
+
Set-Service SQLBrowser -StartupType Manual
|
27
|
+
Start-Service SQLBrowser
|
@@ -0,0 +1,9 @@
|
|
1
|
+
CREATE DATABASE [tinytdstest];
|
2
|
+
GO
|
3
|
+
CREATE LOGIN [tinytds] WITH PASSWORD = '', CHECK_POLICY = OFF, DEFAULT_DATABASE = [tinytdstest];
|
4
|
+
GO
|
5
|
+
USE [tinytdstest];
|
6
|
+
CREATE USER [tinytds] FOR LOGIN [tinytds];
|
7
|
+
GO
|
8
|
+
EXEC sp_addrolemember N'db_owner', N'tinytds';
|
9
|
+
GO
|
@@ -0,0 +1,77 @@
|
|
1
|
+
$:.unshift File.expand_path('../../../lib',__FILE__)
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bench_press'
|
4
|
+
require 'tiny_tds'
|
5
|
+
require 'odbc'
|
6
|
+
require 'odbc_utf8'
|
7
|
+
|
8
|
+
extend BenchPress
|
9
|
+
|
10
|
+
author 'Ken Collins'
|
11
|
+
summary 'Query everything.'
|
12
|
+
|
13
|
+
reps 1_000
|
14
|
+
|
15
|
+
@odbc = ODBC.connect ENV['TINYTDS_UNIT_DATASERVER'], 'tinytds', ''
|
16
|
+
@odbc.use_time = true
|
17
|
+
|
18
|
+
@odbc_utf8 = ODBC_UTF8.connect ENV['TINYTDS_UNIT_DATASERVER'], 'tinytds', ''
|
19
|
+
@odbc_utf8.use_time = true
|
20
|
+
|
21
|
+
@tinytds = TinyTds::Client.new(
|
22
|
+
:dataserver => ENV['TINYTDS_UNIT_DATASERVER'],
|
23
|
+
:username => 'tinytds',
|
24
|
+
:password => '',
|
25
|
+
:database => 'tinytdstest',
|
26
|
+
:appname => 'TinyTds Dev',
|
27
|
+
:login_timeout => 5,
|
28
|
+
:timeout => 5 )
|
29
|
+
|
30
|
+
@query_all = "SELECT * FROM [datatypes]"
|
31
|
+
|
32
|
+
|
33
|
+
measure "ODBC (ascii-8bit)" do
|
34
|
+
h = @odbc.run(@query_all)
|
35
|
+
h.fetch_all
|
36
|
+
h.drop
|
37
|
+
end
|
38
|
+
|
39
|
+
# measure "ODBC (utf8)" do
|
40
|
+
# h = @odbc_utf8.run(@query_all)
|
41
|
+
# h.fetch_all
|
42
|
+
# h.drop
|
43
|
+
# end
|
44
|
+
|
45
|
+
measure "TinyTDS (row caching)" do
|
46
|
+
@tinytds.execute(@query_all).each
|
47
|
+
end
|
48
|
+
|
49
|
+
measure "TinyTDS (no caching)" do
|
50
|
+
@tinytds.execute(@query_all).each(:cache_rows => false)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
=begin
|
56
|
+
|
57
|
+
Author: Ken Collins
|
58
|
+
Date: January 22, 2011
|
59
|
+
Summary: Query everything.
|
60
|
+
|
61
|
+
System Information
|
62
|
+
------------------
|
63
|
+
Operating System: Mac OS X 10.6.6 (10J567)
|
64
|
+
CPU: Intel Core 2 Duo 1.6 GHz
|
65
|
+
Processor Count: 2
|
66
|
+
Memory: 4 GB
|
67
|
+
ruby 1.8.7 (2010-04-19 patchlevel 253) [i686-darwin10.4.3], MBARI 0x6770, Ruby Enterprise Edition 2010.02
|
68
|
+
|
69
|
+
"TinyTDS (row caching)" is up to 79% faster over 1,000 repetitions
|
70
|
+
------------------------------------------------------------------
|
71
|
+
|
72
|
+
TinyTDS (row caching) 4.90862512588501 secs Fastest
|
73
|
+
TinyTDS (no caching) 4.91626906394958 secs 0% Slower
|
74
|
+
ODBC (ascii-8bit) 23.959536075592 secs 79% Slower
|
75
|
+
|
76
|
+
=end
|
77
|
+
|