Skip to content

Commit fed9fed

Browse files
committed
Patch adapter for tiny_tds v4
1 parent 3231ec0 commit fed9fed

5 files changed

Lines changed: 55 additions & 45 deletions

File tree

.devcontainer/Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ FROM mcr.microsoft.com/devcontainers/ruby:${VARIANT}
66

77
# TinyTDS
88
RUN apt-get -y install libc6-dev \
9-
&& wget http://www.freetds.org/files/stable/freetds-1.4.14.tar.gz \
10-
&& tar -xzf freetds-1.4.14.tar.gz \
11-
&& cd freetds-1.4.14 \
9+
&& wget http://www.freetds.org/files/stable/freetds-1.5.14.tar.gz \
10+
&& tar -xzf freetds-1.5.14.tar.gz \
11+
&& cd freetds-1.5.14 \
1212
&& ./configure --prefix=/usr/local --with-tdsver=7.3 \
1313
&& make \
1414
&& make install

Gemfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ gem "pg", "1.5.9"
1111
gem "sqlite3", ">= 2.1"
1212
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
1313
gem "benchmark-ips"
14-
gem "minitest", ">= 5.15.0"
14+
gem "minitest", "< 6.0.0"
1515
gem "msgpack", ">= 1.7.0"
1616

1717
if ENV["RAILS_SOURCE"]
@@ -54,7 +54,7 @@ group :tinytds do
5454
elsif ENV["TINYTDS_VERSION"]
5555
gem "tiny_tds", ENV["TINYTDS_VERSION"]
5656
else
57-
gem "tiny_tds"
57+
gem "tiny_tds", github: "rails-sqlserver/tiny_tds", ref: "05ae08"
5858
end
5959
end
6060
# rubocop:enable Bundler/DuplicatedGem

activerecord-sqlserver-adapter.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ Gem::Specification.new do |spec|
2727
spec.require_paths = ["lib"]
2828

2929
spec.add_dependency "activerecord", "~> 8.1.0"
30-
spec.add_dependency "tiny_tds", "~> 3.0"
30+
spec.add_dependency "tiny_tds", "~> 4.0"
3131
end

lib/active_record/connection_adapters/sqlserver/database_statements.rb

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,10 @@ def affected_rows_from_results_or_handle(raw_result, handle)
5454
end
5555

5656
def internal_exec_sql_query(sql, conn)
57-
handle = internal_raw_execute(sql, conn)
57+
handle = internal_raw_execute(sql, conn, as: :array)
5858
results = handle_to_names_and_values(handle, ar_result: true)
5959

6060
[results, affected_rows_from_results_or_handle(results, handle)]
61-
ensure
62-
finish_statement_handle(handle)
6361
end
6462

6563
def exec_delete(sql, name = nil, binds = [])
@@ -241,16 +239,19 @@ def execute_procedure(proc_name, *variables)
241239
with_raw_connection do |conn|
242240
result = internal_raw_execute(sql, conn)
243241
verified!
244-
options = {as: :hash, cache_rows: true, timezone: ActiveRecord.default_timezone || :utc}
245242

246-
result.each(options) do |row|
247-
r = row.with_indifferent_access
248-
yield(r) if block_given?
243+
if block_given?
244+
result.rows.flatten.each do |row|
245+
r = row.with_indifferent_access
246+
yield(r)
247+
end
249248
end
250249

251-
result = result.each.map { |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
252250
notification_payload[:row_count] = result.count
253-
result
251+
252+
# existing code heavily depends on receiving an array here, and #rows is an array
253+
# e.g. #flatten is not something Enumerable provides, which is what TinyTds::Result implements
254+
result.rows
254255
end
255256
end
256257
end
@@ -498,20 +499,14 @@ def identity_columns(table_name)
498499
# === SQLServer Specific (Selecting) ============================ #
499500

500501
def _raw_select(sql, conn)
501-
handle = internal_raw_execute(sql, conn)
502-
handle_to_names_and_values(handle, fetch: :rows)
503-
ensure
504-
finish_statement_handle(handle)
502+
handle = internal_raw_execute(sql, conn, as: :array)
503+
handle_to_names_and_values(handle)
505504
end
506505

507-
def handle_to_names_and_values(handle, options = {})
508-
query_options = {}.tap do |qo|
509-
qo[:timezone] = ActiveRecord.default_timezone || :utc
510-
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
511-
end
512-
results = handle.each(query_options)
506+
def handle_to_names_and_values(handle, ar_result: false)
507+
results = handle.rows
513508

514-
if options[:ar_result]
509+
if ar_result
515510
columns = handle.fields
516511
columns = columns.last if columns.any? && columns.all? { |e| e.is_a?(Array) } # If query returns multiple result sets, only return the columns of the last one.
517512
columns = columns.map(&:downcase) if lowercase_schema_reflection
@@ -522,14 +517,8 @@ def handle_to_names_and_values(handle, options = {})
522517
end
523518
end
524519

525-
def finish_statement_handle(handle)
526-
handle&.cancel
527-
handle
528-
end
529-
530-
def internal_raw_execute(sql, raw_connection, perform_do: false)
531-
result = raw_connection.execute(sql)
532-
perform_do ? result.do : result
520+
def internal_raw_execute(sql, raw_connection, perform_do: false, as: :hash, timezone: ActiveRecord.default_timezone || :utc)
521+
perform_do ? raw_connection.do(sql) : raw_connection.execute(sql, as:, timezone:)
533522
end
534523

535524
# === SQLServer Specific (insert_all / upsert_all support) ===================== #

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,28 @@ def dbconsole(config, options = {})
115115
end
116116

117117
def new_client(config)
118-
TinyTds::Client.new(config)
118+
# we receive the entire config that is in the database.yml
119+
# however, since TinyTDS wants keyword arguments, it will complain if passing unexpected keys
120+
# so we map here the config to only the keys that TinyTDS accepts
121+
configuration = {
122+
app_name: config[:appname] || config[:app_name],
123+
azure: config[:azure],
124+
charset: config[:charset] || config[:encoding],
125+
contained: config[:contained],
126+
database: config[:database],
127+
dataserver: config[:dataserver],
128+
host: config[:host],
129+
login_timeout: config[:login_timeout],
130+
message_handler: config[:message_handler],
131+
password: config[:password],
132+
port: config[:port],
133+
tds_version: config[:tds_version],
134+
timeout: config[:timeout],
135+
username: config[:username],
136+
use_utf16: config[:use_utf16]
137+
}.compact
138+
139+
TinyTds::Client.new(**configuration)
119140
rescue TinyTds::Error => error
120141
if /database .* does not exist/i.match?(error.message)
121142
raise ActiveRecord::NoDatabaseError
@@ -553,19 +574,19 @@ def connect
553574

554575
def configure_connection
555576
if @config[:azure]
556-
@raw_connection.execute("SET ANSI_NULLS ON").do
557-
@raw_connection.execute("SET ANSI_NULL_DFLT_ON ON").do
558-
@raw_connection.execute("SET ANSI_PADDING ON").do
559-
@raw_connection.execute("SET ANSI_WARNINGS ON").do
577+
@raw_connection.do("SET ANSI_NULLS ON")
578+
@raw_connection.do("SET ANSI_NULL_DFLT_ON ON")
579+
@raw_connection.do("SET ANSI_PADDING ON")
580+
@raw_connection.do("SET ANSI_WARNINGS ON")
560581
else
561-
@raw_connection.execute("SET ANSI_DEFAULTS ON").do
582+
@raw_connection.do("SET ANSI_DEFAULTS ON")
562583
end
563584

564-
@raw_connection.execute("SET QUOTED_IDENTIFIER ON").do
565-
@raw_connection.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
566-
@raw_connection.execute("SET IMPLICIT_TRANSACTIONS OFF").do
567-
@raw_connection.execute("SET TEXTSIZE 2147483647").do
568-
@raw_connection.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
585+
@raw_connection.do("SET QUOTED_IDENTIFIER ON")
586+
@raw_connection.do("SET CURSOR_CLOSE_ON_COMMIT OFF")
587+
@raw_connection.do("SET IMPLICIT_TRANSACTIONS OFF")
588+
@raw_connection.do("SET TEXTSIZE 2147483647")
589+
@raw_connection.do("SET CONCAT_NULL_YIELDS_NULL ON")
569590

570591
@spid = _raw_select("SELECT @@SPID", @raw_connection).first.first
571592

0 commit comments

Comments
 (0)