Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ FROM mcr.microsoft.com/devcontainers/ruby:${VARIANT}

# TinyTDS
RUN apt-get -y install libc6-dev \
&& wget http://www.freetds.org/files/stable/freetds-1.4.14.tar.gz \
&& tar -xzf freetds-1.4.14.tar.gz \
&& cd freetds-1.4.14 \
&& wget http://www.freetds.org/files/stable/freetds-1.5.14.tar.gz \
&& tar -xzf freetds-1.5.14.tar.gz \
&& cd freetds-1.5.14 \
&& ./configure --prefix=/usr/local --with-tdsver=7.3 \
&& make \
&& make install
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: CI

on:
push:
branches: [ main ]
branches: [ 8-1-stable, main ]
pull_request:
branches: [ main ]
branches: [ 8-1-stable, main ]
schedule:
- cron: '0 4 * * 0'

Expand Down
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ gem "pg", "1.5.9"
gem "sqlite3", ">= 2.1"
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem "benchmark-ips"
gem "minitest", ">= 5.15.0"
gem "minitest", "< 6.0.0"
gem "msgpack", ">= 1.7.0"

if ENV["RAILS_SOURCE"]
Expand Down Expand Up @@ -54,7 +54,7 @@ group :tinytds do
elsif ENV["TINYTDS_VERSION"]
gem "tiny_tds", ENV["TINYTDS_VERSION"]
else
gem "tiny_tds"
gem "tiny_tds", github: "rails-sqlserver/tiny_tds", ref: "05ae08"
end
end
# rubocop:enable Bundler/DuplicatedGem
Expand Down
2 changes: 1 addition & 1 deletion activerecord-sqlserver-adapter.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

spec.add_dependency "activerecord", "~> 8.1.0"
spec.add_dependency "tiny_tds", "~> 3.0"
spec.add_dependency "tiny_tds", "~> 4.0"
end
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,10 @@ def affected_rows_from_results_or_handle(raw_result, handle)
end

def internal_exec_sql_query(sql, conn)
handle = internal_raw_execute(sql, conn)
handle = internal_raw_execute(sql, conn, as: :array)
results = handle_to_names_and_values(handle, ar_result: true)

[results, affected_rows_from_results_or_handle(results, handle)]
ensure
finish_statement_handle(handle)
end

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

result.each(options) do |row|
r = row.with_indifferent_access
yield(r) if block_given?
if block_given?
result.rows.flatten.each do |row|
r = row.with_indifferent_access
yield(r)
end
end

result = result.each.map { |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
notification_payload[:row_count] = result.count
result

# existing code heavily depends on receiving an array here, and #rows is an array
# e.g. #flatten is not something Enumerable provides, which is what TinyTds::Result implements
result.rows
end
end
end
Expand Down Expand Up @@ -498,20 +499,14 @@ def identity_columns(table_name)
# === SQLServer Specific (Selecting) ============================ #

def _raw_select(sql, conn)
handle = internal_raw_execute(sql, conn)
handle_to_names_and_values(handle, fetch: :rows)
ensure
finish_statement_handle(handle)
handle = internal_raw_execute(sql, conn, as: :array)
handle_to_names_and_values(handle)
end

def handle_to_names_and_values(handle, options = {})
query_options = {}.tap do |qo|
qo[:timezone] = ActiveRecord.default_timezone || :utc
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
end
results = handle.each(query_options)
def handle_to_names_and_values(handle, ar_result: false)
results = handle.rows

if options[:ar_result]
if ar_result
columns = handle.fields
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.
columns = columns.map(&:downcase) if lowercase_schema_reflection
Expand All @@ -522,14 +517,8 @@ def handle_to_names_and_values(handle, options = {})
end
end

def finish_statement_handle(handle)
handle&.cancel
handle
end

def internal_raw_execute(sql, raw_connection, perform_do: false)
result = raw_connection.execute(sql)
perform_do ? result.do : result
def internal_raw_execute(sql, raw_connection, perform_do: false, as: :hash, timezone: ActiveRecord.default_timezone || :utc)
perform_do ? raw_connection.do(sql) : raw_connection.execute(sql, as:, timezone:)
end

# === SQLServer Specific (insert_all / upsert_all support) ===================== #
Expand Down
43 changes: 32 additions & 11 deletions lib/active_record/connection_adapters/sqlserver_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,28 @@ def dbconsole(config, options = {})
end

def new_client(config)
TinyTds::Client.new(config)
# we receive the entire config that is in the database.yml
# however, since TinyTDS wants keyword arguments, it will complain if passing unexpected keys
# so we map here the config to only the keys that TinyTDS accepts
configuration = {
app_name: config[:appname] || config[:app_name],
azure: config[:azure],
charset: config[:charset] || config[:encoding],
contained: config[:contained],
database: config[:database],
dataserver: config[:dataserver],
host: config[:host],
login_timeout: config[:login_timeout],
message_handler: config[:message_handler],
password: config[:password],
port: config[:port],
tds_version: config[:tds_version],
timeout: config[:timeout],
username: config[:username],
use_utf16: config[:use_utf16]
}.compact

TinyTds::Client.new(**configuration)
rescue TinyTds::Error => error
if /database .* does not exist/i.match?(error.message)
raise ActiveRecord::NoDatabaseError
Expand Down Expand Up @@ -553,19 +574,19 @@ def connect

def configure_connection
if @config[:azure]
@raw_connection.execute("SET ANSI_NULLS ON").do
@raw_connection.execute("SET ANSI_NULL_DFLT_ON ON").do
@raw_connection.execute("SET ANSI_PADDING ON").do
@raw_connection.execute("SET ANSI_WARNINGS ON").do
@raw_connection.do("SET ANSI_NULLS ON")
@raw_connection.do("SET ANSI_NULL_DFLT_ON ON")
@raw_connection.do("SET ANSI_PADDING ON")
@raw_connection.do("SET ANSI_WARNINGS ON")
else
@raw_connection.execute("SET ANSI_DEFAULTS ON").do
@raw_connection.do("SET ANSI_DEFAULTS ON")
end

@raw_connection.execute("SET QUOTED_IDENTIFIER ON").do
@raw_connection.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
@raw_connection.execute("SET IMPLICIT_TRANSACTIONS OFF").do
@raw_connection.execute("SET TEXTSIZE 2147483647").do
@raw_connection.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
@raw_connection.do("SET QUOTED_IDENTIFIER ON")
@raw_connection.do("SET CURSOR_CLOSE_ON_COMMIT OFF")
@raw_connection.do("SET IMPLICIT_TRANSACTIONS OFF")
@raw_connection.do("SET TEXTSIZE 2147483647")
@raw_connection.do("SET CONCAT_NULL_YIELDS_NULL ON")

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

Expand Down
Loading