Skip to content

Commit 0be24ae

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 0be24ae

File tree

11 files changed

+342
-32
lines changed

11 files changed

+342
-32
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: 111 additions & 18 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,18 @@ 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)
6062
end
6163

6264
def exec_delete(sql, name = nil, binds = [])
63-
super(sql, name, binds) || super("SELECT @@ROWCOUNT As AffectedRows", "", [])
65+
super || super("SELECT @@ROWCOUNT As AffectedRows", "", [])
6466
end
6567

6668
def exec_update(sql, name = nil, binds = [])
67-
super(sql, name, binds) || super("SELECT @@ROWCOUNT As AffectedRows", "", [])
69+
super || super("SELECT @@ROWCOUNT As AffectedRows", "", [])
6870
end
6971

7072
def begin_db_transaction
@@ -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
@@ -342,10 +339,16 @@ def sp_executesql_sql_type(attr)
342339
end
343340
end
344341

342+
345343
value = active_model_attribute?(attr) ? attr.value_for_database : attr
346344

347345
if value.is_a?(Numeric)
348-
value > 2_147_483_647 ? "bigint".freeze : "int".freeze
346+
if value.is_a?(Integer)
347+
value > 2_147_483_647 ? "bigint".freeze : "int".freeze
348+
else
349+
# For Float, BigDecimal, Rational etc.
350+
value.is_a?(BigDecimal) ? "decimal(18,6)".freeze : "float".freeze
351+
end
349352
else
350353
"nvarchar(max)".freeze
351354
end
@@ -427,13 +430,26 @@ def identity_columns(table_name)
427430
# === SQLServer Specific (Selecting) ============================ #
428431

429432
def _raw_select(sql, conn)
430-
handle = internal_raw_execute(sql, conn)
433+
handle = raw_connection_run(sql, conn)
431434
handle_to_names_and_values(handle, fetch: :rows)
432435
ensure
433436
finish_statement_handle(handle)
434437
end
435438

439+
def raw_connection_run(sql, conn, perform_do: false)
440+
case @config[:mode].to_sym
441+
when :dblib
442+
internal_raw_execute(sql, conn, perform_do: perform_do)
443+
when :odbc
444+
internal_raw_execute_odbc(sql, conn, perform_do: perform_do)
445+
end
446+
end
447+
436448
def handle_to_names_and_values(handle, options = {})
449+
send("handle_to_names_and_values_#{@config[:mode]}", handle, options)
450+
end
451+
452+
def handle_to_names_and_values_dblib(handle, options = {})
437453
query_options = {}.tap do |qo|
438454
qo[:timezone] = ActiveRecord.default_timezone || :utc
439455
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
@@ -448,8 +464,32 @@ def handle_to_names_and_values(handle, options = {})
448464
options[:ar_result] ? ActiveRecord::Result.new(columns, results) : results
449465
end
450466

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

@@ -462,6 +502,59 @@ def internal_raw_execute(sql, raw_connection, perform_do: false)
462502

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

0 commit comments

Comments
 (0)