Skip to content

Commit 8607d35

Browse files
committed
Version bump to 8.0.3.odbc to support ODBC
- ActiveRecord SQLServer Adapter defaults to TinyTDS. And the support for ODBC has been removed. This is the effort to support Adapter with ODBC Driver again. - ruby-odbc is now pointed to https://github.com/cloudvolumes/ruby-odbc 0.103.cv
1 parent fe21eac commit 8607d35

File tree

11 files changed

+328
-29
lines changed

11 files changed

+328
-29
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## v8.0.3.odbc
2+
3+
#### Added
4+
5+
- ODBC restoration.
6+
17
## v8.0.3
28

39
#### Fixed

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ group :tinytds do
5959
end
6060
# rubocop:enable Bundler/DuplicatedGem
6161

62+
group :odbc do
63+
gem 'ruby-odbc', :git => 'https://github.com/cloudvolumes/ruby-odbc.git', :tag => '0.103.cv'
64+
end
65+
6266
group :development do
6367
gem "minitest-spec-rails"
6468
gem "mocha"

Rakefile

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,29 @@ task test: ["test:dblib"]
99
task default: [:test]
1010

1111
namespace :test do
12-
ENV["ARCONN"] = "sqlserver"
13-
14-
%w(dblib).each do |mode|
12+
%w(dblib odbc).each do |mode|
1513
Rake::TestTask.new(mode) do |t|
1614
t.libs = ARTest::SQLServer.test_load_paths
1715
t.test_files = test_files
1816
t.warning = !!ENV["WARNING"]
1917
t.verbose = false
2018
end
2119
end
20+
21+
task "dblib:env" do
22+
ENV["ARCONN"] = "dblib"
23+
end
24+
25+
task 'odbc:env' do
26+
ENV['ARCONN'] = 'odbc'
27+
end
2228
end
2329

30+
task "test:dblib" => "test:dblib:env"
31+
task "test:odbc" => "test:odbc:env"
32+
2433
namespace :profile do
25-
["dblib"].each do |mode|
34+
["dblib", "odbc"].each do |mode|
2635
namespace mode.to_sym do
2736
Dir.glob("test/profile/*_profile_case.rb").sort.each do |test_file|
2837
profile_case = File.basename(test_file).sub("_profile_case.rb", "")

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8.0.3
1+
8.0.3.odbc
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module ActiveRecord
2+
module ConnectionAdapters
3+
module SQLServer
4+
module CoreExt
5+
module ODBC
6+
module Statement
7+
def finished?
8+
connected?
9+
false
10+
rescue ::ODBC::Error
11+
true
12+
end
13+
end
14+
15+
module Database
16+
def run_block(*args)
17+
yield sth = run(*args)
18+
19+
sth.drop
20+
end
21+
end
22+
end
23+
end
24+
end
25+
end
26+
end
27+
28+
ODBC::Statement.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::ODBC::Statement
29+
ODBC::Database.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::ODBC::Database

lib/active_record/connection_adapters/sqlserver/database_statements.rb

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ def cast_result(raw_result)
3939
end
4040

4141
def affected_rows(raw_result)
42+
return 0 if raw_result.blank?
43+
4244
column_name = lowercase_schema_reflection ? 'affectedrows' : 'AffectedRows'
4345
raw_result.first[column_name]
4446
end
@@ -53,7 +55,7 @@ def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow
5355
end
5456

5557
def internal_exec_sql_query(sql, conn)
56-
handle = internal_raw_execute(sql, conn)
58+
handle = raw_connection_run(sql, conn)
5759
handle_to_names_and_values(handle, ar_result: true)
5860
ensure
5961
finish_statement_handle(handle)
@@ -168,17 +170,8 @@ def execute_procedure(proc_name, *variables)
168170

169171
log(sql, "Execute Procedure") do |notification_payload|
170172
with_raw_connection do |conn|
171-
result = internal_raw_execute(sql, conn)
172-
verified!
173-
options = { as: :hash, cache_rows: true, timezone: ActiveRecord.default_timezone || :utc }
174-
175-
result.each(options) do |row|
176-
r = row.with_indifferent_access
177-
yield(r) if block_given?
178-
end
179-
180-
result = result.each.map { |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
181-
notification_payload[:row_count] = result.count
173+
result = send("execute_#{@config[:mode]}_procedure", sql, conn)
174+
notification_payload[:row_count] = result&.count
182175
result
183176
end
184177
end
@@ -312,7 +305,11 @@ def sql_for_insert(sql, pk, binds, returning)
312305
# === SQLServer Specific ======================================== #
313306

314307
def set_identity_insert(table_name, conn, enable)
315-
internal_raw_execute("SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}", conn , perform_do: true)
308+
if @config[:mode].to_sym == :dblib
309+
internal_raw_execute("SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}", conn , perform_do: true)
310+
else
311+
internal_raw_execute_odbc("SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}", conn , perform_do: true)
312+
end
316313
rescue Exception
317314
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
318315
end
@@ -427,13 +424,26 @@ def identity_columns(table_name)
427424
# === SQLServer Specific (Selecting) ============================ #
428425

429426
def _raw_select(sql, conn)
430-
handle = internal_raw_execute(sql, conn)
427+
handle = raw_connection_run(sql, conn)
431428
handle_to_names_and_values(handle, fetch: :rows)
432429
ensure
433430
finish_statement_handle(handle)
434431
end
435432

433+
def raw_connection_run(sql, conn, perform_do: false)
434+
case @config[:mode].to_sym
435+
when :dblib
436+
internal_raw_execute(sql, conn, perform_do: perform_do)
437+
when :odbc
438+
internal_raw_execute_odbc(sql, conn, perform_do: perform_do)
439+
end
440+
end
441+
436442
def handle_to_names_and_values(handle, options = {})
443+
send("handle_to_names_and_values_#{@config[:mode]}", handle, options)
444+
end
445+
446+
def handle_to_names_and_values_dblib(handle, options = {})
437447
query_options = {}.tap do |qo|
438448
qo[:timezone] = ActiveRecord.default_timezone || :utc
439449
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
@@ -448,8 +458,31 @@ def handle_to_names_and_values(handle, options = {})
448458
options[:ar_result] ? ActiveRecord::Result.new(columns, results) : results
449459
end
450460

461+
def handle_to_names_and_values_odbc(handle, options = {})
462+
@raw_connection.use_utc = ActiveRecord.default_timezone || :utc
463+
464+
if options[:ar_result]
465+
columns = lowercase_schema_reflection ? handle.columns(true).map { |c| c.name.downcase } : handle.columns(true).map { |c| c.name }
466+
rows = handle.fetch_all || []
467+
ActiveRecord::Result.new(columns, rows)
468+
else
469+
case options[:fetch]
470+
when :all
471+
handle.each_hash || []
472+
when :rows
473+
handle.fetch_all || []
474+
end
475+
end
476+
end
477+
451478
def finish_statement_handle(handle)
452-
handle.cancel if handle
479+
case @config[:mode].to_sym
480+
when :dblib
481+
handle.cancel if handle
482+
when :odbc
483+
handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
484+
end
485+
453486
handle
454487
end
455488

@@ -462,6 +495,55 @@ def internal_raw_execute(sql, raw_connection, perform_do: false)
462495

463496
perform_do ? result.do : result
464497
end
498+
499+
# Executing SQL for ODBC mode
500+
def internal_raw_execute_odbc(sql, raw_connection, perform_do: false)
501+
raw_connection.do(sql) if perform_do
502+
503+
block_given? ? raw_connection.run_block(sql) { |handle| yield(handle) } : raw_connection.run(sql)
504+
end
505+
506+
private
507+
508+
def execute_dblib_procedure(sql, conn)
509+
result = internal_raw_execute(sql, conn)
510+
verified!
511+
options = { as: :hash, cache_rows: true, timezone: ActiveRecord.default_timezone || :utc }
512+
513+
raw_rows = result.each(options).map do |row|
514+
row = row.with_indifferent_access
515+
yield(row) if block_given?
516+
row
517+
end
518+
519+
raw_rows.map { |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
520+
end
521+
522+
def execute_odbc_procedure(sql, conn)
523+
results = []
524+
525+
internal_raw_execute_odbc(sql, conn) do |handle|
526+
527+
get_rows = lambda do
528+
rows = handle_to_names_and_values handle, fetch: :all
529+
rows.each_with_index { |r, i| rows[i] = r.with_indifferent_access }
530+
results << rows
531+
end
532+
533+
get_rows.call
534+
get_rows.call while handle_more_results?(handle)
535+
end
536+
537+
results.many? ? results : results.first
538+
end
539+
540+
def handle_more_results?(handle)
541+
case @config[:mode].to_sym
542+
when :dblib
543+
when :odbc
544+
handle.more_results
545+
end
546+
end
465547
end
466548
end
467549
end

0 commit comments

Comments
 (0)