Skip to content

Commit c23f8a8

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 6257e19 commit c23f8a8

File tree

11 files changed

+345
-35
lines changed

11 files changed

+345
-35
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: 105 additions & 21 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 if raw_result.blank?
43+
4244
column_name = lowercase_schema_reflection ? 'affectedrows' : 'AffectedRows'
4345
raw_result.first[column_name]
4446
end
@@ -53,18 +55,16 @@ 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)
58-
ensure
59-
finish_statement_handle(handle)
6060
end
6161

6262
def exec_delete(sql, name = nil, binds = [])
63-
super(sql, name, binds) || super("SELECT @@ROWCOUNT As AffectedRows", "", [])
63+
super || super("SELECT @@ROWCOUNT As AffectedRows", "", [])
6464
end
6565

6666
def exec_update(sql, name = nil, binds = [])
67-
super(sql, name, binds) || super("SELECT @@ROWCOUNT As AffectedRows", "", [])
67+
super || super("SELECT @@ROWCOUNT As AffectedRows", "", [])
6868
end
6969

7070
def begin_db_transaction
@@ -168,17 +168,8 @@ def execute_procedure(proc_name, *variables)
168168

169169
log(sql, "Execute Procedure") do |notification_payload|
170170
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
171+
result = send("execute_#{@config[:mode]}_procedure", sql, conn)
172+
notification_payload[:row_count] = result&.count
182173
result
183174
end
184175
end
@@ -312,7 +303,11 @@ def sql_for_insert(sql, pk, binds, returning)
312303
# === SQLServer Specific ======================================== #
313304

314305
def set_identity_insert(table_name, conn, enable)
315-
internal_raw_execute("SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}", conn , perform_do: true)
306+
if @config[:mode].to_sym == :dblib
307+
internal_raw_execute("SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}", conn , perform_do: true)
308+
else
309+
internal_raw_execute_odbc("SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}", conn , perform_do: true)
310+
end
316311
rescue Exception
317312
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
318313
end
@@ -345,7 +340,12 @@ def sp_executesql_sql_type(attr)
345340
value = active_model_attribute?(attr) ? attr.value_for_database : attr
346341

347342
if value.is_a?(Numeric)
348-
value > 2_147_483_647 ? "bigint".freeze : "int".freeze
343+
if value.is_a?(Integer)
344+
value > 2_147_483_647 ? "bigint".freeze : "int".freeze
345+
else
346+
# For Float, BigDecimal, Rational etc.
347+
value.is_a?(BigDecimal) ? "decimal(18,6)".freeze : "float".freeze
348+
end
349349
else
350350
"nvarchar(max)".freeze
351351
end
@@ -427,13 +427,26 @@ def identity_columns(table_name)
427427
# === SQLServer Specific (Selecting) ============================ #
428428

429429
def _raw_select(sql, conn)
430-
handle = internal_raw_execute(sql, conn)
430+
handle = raw_connection_run(sql, conn)
431431
handle_to_names_and_values(handle, fetch: :rows)
432+
end
433+
434+
def raw_connection_run(sql, conn, perform_do: false)
435+
case @config[:mode].to_sym
436+
when :dblib
437+
internal_raw_execute(sql, conn, perform_do: perform_do)
438+
when :odbc
439+
internal_raw_execute_odbc(sql, conn, perform_do: perform_do)
440+
end
441+
end
442+
443+
def handle_to_names_and_values(handle, options = {})
444+
send("handle_to_names_and_values_#{@config[:mode]}", handle, options)
432445
ensure
433446
finish_statement_handle(handle)
434447
end
435448

436-
def handle_to_names_and_values(handle, options = {})
449+
def handle_to_names_and_values_dblib(handle, options = {})
437450
query_options = {}.tap do |qo|
438451
qo[:timezone] = ActiveRecord.default_timezone || :utc
439452
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
@@ -448,8 +461,31 @@ def handle_to_names_and_values(handle, options = {})
448461
options[:ar_result] ? ActiveRecord::Result.new(columns, results) : results
449462
end
450463

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

@@ -462,6 +498,54 @@ def internal_raw_execute(sql, raw_connection, perform_do: false)
462498

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

0 commit comments

Comments
 (0)