Skip to content

Commit f68f22f

Browse files
committed
Add TinyTDS/dblib connection mode.
1 parent eea99aa commit f68f22f

File tree

9 files changed

+122
-23
lines changed

9 files changed

+122
-23
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ source :rubygems
33

44
gemspec :path => ENV['RAILS_SOURCE']
55
gem 'arel', :path => ENV['AREL'] if ENV['AREL']
6+
gem 'tiny_tds', :path => ENV['TINYTDS_SOURCE'] if ENV['TINYTDS_SOURCE']
67

78

89
group :development do

Rakefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ task :test => ['test:odbc']
2222

2323
namespace :test do
2424

25-
['odbc','adonet'].each do |mode|
25+
['dblib','odbc','adonet'].each do |mode|
2626

2727
Rake::TestTask.new(mode) do |t|
2828
t.libs = test_libs(mode)
@@ -44,7 +44,7 @@ end
4444

4545
namespace :profile do
4646

47-
['odbc','adonet'].each do |mode|
47+
['dblib','odbc','adonet'].each do |mode|
4848
namespace mode.to_sym do
4949

5050
Dir.glob("test/profile/*_profile_case.rb").sort.each do |test_file|

lib/active_record/connection_adapters/sqlserver/database_statements.rb

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,27 @@ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
7171
def execute_procedure(proc_name, *variables)
7272
vars = variables.map{ |v| quote(v) }.join(', ')
7373
sql = "EXEC #{proc_name} #{vars}".strip
74+
name = 'Execute Procedure'
7475
results = []
75-
log(sql,'Execute Procedure') do
76-
raw_connection_run(sql) do |handle|
77-
get_rows = lambda {
78-
rows = handle_to_names_and_values handle, :fetch => :all
79-
rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
80-
results << rows
81-
}
82-
get_rows.call
83-
while handle_more_results?(handle)
76+
case @connection_options[:mode]
77+
when :dblib
78+
results << select(sql, name).map { |r| r.with_indifferent_access }
79+
when :odbc
80+
log(sql, name) do
81+
raw_connection_run(sql) do |handle|
82+
get_rows = lambda {
83+
rows = handle_to_names_and_values handle, :fetch => :all
84+
rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
85+
results << rows
86+
}
8487
get_rows.call
88+
while handle_more_results?(handle)
89+
get_rows.call
90+
end
8591
end
8692
end
93+
when :adonet
94+
results << select(sql, name).map { |r| r.with_indifferent_access }
8795
end
8896
results.many? ? results : results.first
8997
end
@@ -179,7 +187,7 @@ def select(sql, name = nil)
179187
end
180188

181189
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
182-
super || select_value("SELECT SCOPE_IDENTITY() AS Ident")
190+
super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
183191
end
184192

185193
def update_sql(sql, name = nil)
@@ -204,6 +212,8 @@ def do_execute(sql, name = nil)
204212

205213
def raw_connection_do(sql)
206214
case @connection_options[:mode]
215+
when :dblib
216+
@connection.execute(sql).do
207217
when :odbc
208218
@connection.do(sql)
209219
else :adonet
@@ -227,6 +237,8 @@ def raw_select(sql, name=nil, options={})
227237
def raw_connection_run(sql)
228238
with_auto_reconnect do
229239
case @connection_options[:mode]
240+
when :dblib
241+
@connection.execute(sql)
230242
when :odbc
231243
block_given? ? @connection.run_block(sql) { |handle| yield(handle) } : @connection.run(sql)
232244
else :adonet
@@ -237,6 +249,7 @@ def raw_connection_run(sql)
237249

238250
def handle_more_results?(handle)
239251
case @connection_options[:mode]
252+
when :dblib
240253
when :odbc
241254
handle.more_results
242255
when :adonet
@@ -246,13 +259,24 @@ def handle_more_results?(handle)
246259

247260
def handle_to_names_and_values(handle, options={})
248261
case @connection_options[:mode]
262+
when :dblib
263+
handle_to_names_and_values_dblib(handle, options)
249264
when :odbc
250265
handle_to_names_and_values_odbc(handle, options)
251266
when :adonet
252267
handle_to_names_and_values_adonet(handle, options)
253268
end
254269
end
255-
270+
271+
def handle_to_names_and_values_dblib(handle, options={})
272+
query_options = {}.tap do |qo|
273+
qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
274+
qo[:first] = true if options[:fetch] == :one
275+
qo[:as] = options[:fetch] == :rows ? :array : :hash
276+
end
277+
handle.each(query_options)
278+
end
279+
256280
def handle_to_names_and_values_odbc(handle, options={})
257281
@connection.use_utc = ActiveRecord::Base.default_timezone == :utc if @connection_supports_native_types
258282
case options[:fetch]
@@ -351,6 +375,7 @@ def handle_to_names_and_values_adonet(handle, options={})
351375

352376
def finish_statement_handle(handle)
353377
case @connection_options[:mode]
378+
when :dblib
354379
when :odbc
355380
handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
356381
when :adonet

lib/active_record/connection_adapters/sqlserver/errors.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ module Sqlserver
88
module Errors
99

1010
LOST_CONNECTION_EXCEPTIONS = {
11+
:dblib => ['TinyTds::Error'],
1112
:odbc => ['ODBC::Error','ODBC_UTF8::Error','ODBC_NONE::Error'],
1213
:adonet => ['TypeError','System::Data::SqlClient::SqlException']
1314
}.freeze
1415

1516
LOST_CONNECTION_MESSAGES = {
17+
:dblib => [/closed connection/],
1618
:odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i],
1719
:adonet => [/current state is closed/, /network-related/]
1820
}.freeze

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ def self.sqlserver_connection(config) #:nodoc:
2020
config.reverse_merge! :mode => :odbc, :host => 'localhost', :username => 'sa', :password => ''
2121
mode = config[:mode].to_s.downcase.underscore.to_sym
2222
case mode
23+
when :dblib
24+
raise ArgumentError, 'Missing :dataserver configuration.' unless config.has_key?(:dataserver)
25+
require_library_or_gem 'tiny_tds'
2326
when :odbc
2427
raise ArgumentError, 'Missing :dsn configuration.' unless config.has_key?(:dsn)
2528
if RUBY_VERSION < '1.9'
@@ -242,6 +245,15 @@ def disable_referential_integrity
242245
# === Abstract Adapter (Connection Management) ================== #
243246

244247
def active?
248+
connected = case @connection_options[:mode]
249+
when :dblib
250+
!@connection.closed?
251+
when :odbc
252+
true
253+
else :adonet
254+
true
255+
end
256+
return false if !connected
245257
raw_connection_do("SELECT 1")
246258
true
247259
rescue *lost_connection_exceptions
@@ -256,6 +268,8 @@ def reconnect!
256268

257269
def disconnect!
258270
case @connection_options[:mode]
271+
when :dblib
272+
@connection.close rescue nil
259273
when :odbc
260274
@connection.disconnect rescue nil
261275
else :adonet
@@ -351,6 +365,23 @@ def translate_exception(e, message)
351365
def connect
352366
config = @connection_options
353367
@connection = case @connection_options[:mode]
368+
when :dblib
369+
appname = config[:appname] || Rails.application.class.name.split('::').first rescue nil
370+
encoding = config[:encoding].present? ? config[:encoding] : nil
371+
TinyTds::Client.new({
372+
:dataserver => config[:dataserver],
373+
:username => config[:username],
374+
:password => config[:password],
375+
:database => config[:database],
376+
:appname => appname,
377+
:login_timeout => config[:dblib_login_timeout],
378+
:timeout => config[:dblib_timeout],
379+
:encoding => encoding
380+
}).tap do |client|
381+
client.execute("SET ANSI_DEFAULTS ON").do
382+
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
383+
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
384+
end
354385
when :odbc
355386
odbc = ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].detect{ |odbc_ns| odbc_ns.constantize rescue nil }.constantize
356387
if config[:dsn].include?(';')

test/cases/execute_procedure_test_sqlserver.rb

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,23 @@ def setup
2020
assert_equal 'TABLE', table_info[:TABLE_TYPE], "Table Info: #{table_info.inspect}"
2121
end
2222

23-
should 'allow multiple result sets to be returned' do
24-
results1, results2 = @klass.execute_procedure('sp_helpconstraint','accounts')
25-
assert_instance_of Array, results1
26-
assert_instance_of HashWithIndifferentAccess, results1.first
27-
assert results1.first['Object Name']
28-
assert_instance_of Array, results2
29-
assert_instance_of HashWithIndifferentAccess, results2.first
30-
assert results2.first['constraint_name']
31-
assert results2.first['constraint_type']
23+
if connection_mode_odbc?
24+
25+
should 'allow multiple result sets to be returned' do
26+
results1, results2 = @klass.execute_procedure('sp_helpconstraint','accounts')
27+
assert_instance_of Array, results1
28+
assert_instance_of HashWithIndifferentAccess, results1.first
29+
assert results1.first['Object Name']
30+
assert_instance_of Array, results2
31+
assert_instance_of HashWithIndifferentAccess, results2.first
32+
assert results2.first['constraint_name']
33+
assert results2.first['constraint_type']
34+
end
35+
36+
else
37+
38+
should 'allow multiple result sets to be returned'
39+
3240
end
3341

3442

test/cases/sqlserver_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def raw_select_with_query_record(sql, name=nil, options={})
9393
module ActiveRecord
9494
class TestCase < ActiveSupport::TestCase
9595
class << self
96+
def connection_mode_dblib? ; ActiveRecord::Base.connection.instance_variable_get(:@connection_options)[:mode] == :dblib ; end
9697
def connection_mode_odbc? ; ActiveRecord::Base.connection.instance_variable_get(:@connection_options)[:mode] == :odbc ; end
9798
def sqlserver_2005? ; ActiveRecord::Base.connection.sqlserver_2005? ; end
9899
def sqlserver_2008? ; ActiveRecord::Base.connection.sqlserver_2008? ; end
@@ -109,6 +110,7 @@ def assert_sql(*patterns_to_match)
109110
end
110111
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found in:\n#{$queries_executed.inspect}"
111112
end
113+
def connection_mode_dblib? ; self.class.connection_mode_dblib? ; end
112114
def connection_mode_odbc? ; self.class.connection_mode_odbc? ; end
113115
def sqlserver_2005? ; self.class.sqlserver_2005? ; end
114116
def sqlserver_2008? ; self.class.sqlserver_2008? ; end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
print "Using SQLServer via DBLIB\n"
2+
require_dependency 'models/course'
3+
require 'logger'
4+
5+
ActiveRecord::Base.logger = Logger.new(File.expand_path(File.join(SQLSERVER_TEST_ROOT,'debug.log')))
6+
ActiveRecord::Base.logger.level = 0
7+
8+
ActiveRecord::Base.configurations = {
9+
'arunit' => {
10+
:adapter => 'sqlserver',
11+
:mode => 'dblib',
12+
:dataserver => ENV['TINYTDS_UNIT_DATASERVER'],
13+
:username => 'rails',
14+
:password => '',
15+
:database => 'activerecord_unittest',
16+
:appname => 'SQLServerUnit'
17+
},
18+
'arunit2' => {
19+
:adapter => 'sqlserver',
20+
:mode => 'dblib',
21+
:dataserver => ENV['TINYTDS_UNIT_DATASERVER'],
22+
:username => 'rails',
23+
:password => '',
24+
:database => 'activerecord_unittest2',
25+
:appname => 'SQLServerUnit2'
26+
}
27+
}
28+
29+
ActiveRecord::Base.establish_connection 'arunit'
30+
Course.establish_connection 'arunit2'

test/profile/connection_profile_case.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def test_select
2020
def test_select_one
2121
select_statement = "SELECT [topics].* FROM [topics]"
2222
ruby_profile :connection_select_one do
23-
3000.times { @connection.select_one(select_statement) }
23+
1000.times { @connection.select_one(select_statement) }
2424
end
2525
end
2626

0 commit comments

Comments
 (0)